CXL 是一种和 PCIe 兼容的扩展协议,CXLMemory 本质是插在 PCIe 插槽上的扩展内存设备(可以理解成 "外置内存条")。它的核心能力:
- 能被主机识别(走 PCIe 枚举流程,配置空间 / BAR 这些是识别的关键);
- 有自己的存储空间,能响应主机的读写请求;
- 能解析 CXL 协议的数据包(区分读 / 写指令,模拟协议处理延迟)。
自己的存储空间:通过实现一个Memory嵌套类,子类成员变量包括存储空间地址范围、指向存储地址的指针;成员函数主要包括access()访问函数,以根据不同的命令访问存储空间
读写功能:通过对PciDevice的继承,实现read()和write()函数,在read()和write()内部会调用access()访问存储空间
解析cxl协议的数据包:主要是在这里确定数据包的格式并模拟一个解析的延迟返回回去
- PCIe 接口 ↔ 连主机(gem5/QEMU)
- Ethernet 接口 ↔ 连网络(ns-3/switch

核心作用是:创建可同时支持 PCIe 总线 + 以太网功能的虚拟网卡(如 Intel I40e、E1000 真实网卡的仿真模型)
class SimplePCIeNIC(pcie.PCIeSimpleDevice, eth.EthSimpleNIC):
这个类同时继承两个父类 ,让虚拟网卡拥有双重能力:
pcie.PCIeSimpleDevice→ 让网卡支持 PCIe 总线连接(电脑主板插卡的接口)eth.EthSimpleNIC→ 让网卡支持以太网功能(收发网络数据包)
核心方法:add_if(添加接口)
这是整个代码最重要的方法,作用是:给网卡绑定对应的硬件接口。
参数 interface:可以是 以太网接口 或 PCIe 设备接口 (| 是 Python 类型注解:二选一)

在静态类型语言(如 Java, C++)中,类型检查通常发生在编译阶段(代码运行之前)。如果类型不对,代码根本无法编译通过。
但在 Python 这种动态类型语言中,变量的类型是在程序运行过程中才确定的
- 编译时 :Python 解释器通常不知道
c到底是不是EthChannel。 - 运行时 :当代码执行到
connect函数时,程序必须停下来检查一下:"传入的c到底是不是EthChannel类型?"

这行代码正在检查传入参数 c 的数据类型。
- 检查对象 :变量
c(它代表一个 Channel/通道)。 - 预期目标 :
EthChannel(以太网通道类型)。 - 检查逻辑 :
utils_base.has_expected_type这个函数的作用是判断c是否属于EthChannel类(或者是它的子类)。 - 后果 :如果
c不是EthChannel类型,这个检查通常会抛出一个错误(Exception),阻止程序继续执行,防止后续代码因为类型不匹配而崩溃。


NIC1 (EthSimpleNIC)
↓ (EthInterface)
网线 (EthWire)
↓ (EthInterface)
NIC2 (EthSimpleNIC)
nic1.connect_eth_peer_if(eth_wire)
nic2.connect_eth_peer_if(eth_wire)
是 Python 模型(system/nic.py、pcie.py)与真实 C++ 模拟器之间的 "胶水"
它不做模拟,只做 3 件事:
- 告诉框架要启动哪个 C++ 可执行文件
- 把 PCIe 接口、以太网接口的连接信息传给 C++ 模拟器
- 生成启动命令(命令行参数)


- 把 Python 模型(SimplePCIeNIC)绑定到模拟器
- 一个网卡模拟器 只能绑定一个网卡

. class NICSim(PCIDevSim)
所有网卡模拟器的父类
它处理 所有网卡共通逻辑:
双接口(PCIe + Ethernet)
延迟、同步周期
生成通用启动命令
绑定网卡设备
1.2 toJSON / fromJSON
作用:
- 序列化 / 反序列化
- 把模拟器配置保存成 JSON,供底层 C++ 读取
1.4 add(nic) 🔥【关键:绑定设备】
python
运行
def add(self, nic: sys_nic.SimplePCIeNIC):
assert len(self._components) < 1
super().add(nic)
作用:
-
把 Python 定义的 NIC 设备绑定到模拟器
-
一个模拟器 只能绑定一个网卡
-
建立
设备模型 ↔ 模拟器的关系 -
latency(延迟)
- 数据从一端传到另一端需要的时间
- 单位:picoseconds 皮秒
- 例:PCIe 延迟 200ns
-
sync_period(同步周期)
- 模拟器之间多久同步一次时间
- 单位:picoseconds
- 例:每 1000ns 同步一次
-
run_sync(是否同步模式)
-
True= 严格时序同步(高精度仿真) -
False= 异步运行(速度快,但精度低)sync_period = (
min(sync_period, channel.sync_period)
if sync_period
else channel.sync_period
)
-
所有通道里,同步周期 取 最小的那个!
- 同步周期越小,精度越高
- 必须按最小的同步周期运行,否则会时序错乱
只要有一个通道需要同步,整个模拟器就必须同步!
False or False → FalseFalse or True → TrueTrue or True → True
所有通道里,延迟 取 最大的那个!
- 必须保证最坏情况的延迟
- 不能用最小延迟,否则会出现数据提前到达
1. 同步周期 = 取最小的
要保证最快的同步频率
2. 延迟 = 取最大的
要保证最坏情况的延迟
3. 是否同步 = 只要有一个是同步,整体就必须同步
一票否决制
因为 NIC 有两个接口:
- PCIe 接口
- Ethernet 接口
两边的延迟、同步周期可能不一样!
所以必须:
- 把 PCIe 的参数
- 把 Ethernet 的参数合并成一组参数,给 C++ 模拟器用!

返回 SimBricks 源码根目录 + 你传入的相对路径
它做了什么?
找到 SimBricks 项目根目录
把你给的 相对路径 拼在后面
返回 完整绝对路径
inst = Instantiation(实例化)
inst.env = Instantiation Environment
意思:
当前仿真运行的环境对象
它里面保存了:
SimBricks 根目录
输出目录
日志目录
临时文件目录
环境变量
路径工具(例如 repo_base)
所以:
inst.env.repo_base()
就是:让当前运行环境帮我找到模拟器的可执行文件路径
给 C++ 模拟器传递它必须的 "连接参数"
让 C++ 模拟器知道:
我要连哪个 socket
同步模式开不开
延迟是多少
同步周期是多少
从什么时候开始运行
def get_socket(self, interface: sys_base.Interface) -> inst_socket.Socket | None:
作用:给一个硬件接口(Eth 口、PCIe 口)分配一个通信 Socket

- 生成一个唯一的路径,例如
/tmp/simbricks-1234 - 创建一个 Unix Socket(进程间通信通道)
get_socket = 给每个硬件接口创建一根 "虚拟电线"
这根电线就是 Socket
Socket = 两个模拟器之间的【虚拟电线】
现实世界:
- 网卡 ↔ 交换机 → 用网线连接
- CPU ↔ 网卡 → 用PCIe 插槽连接
SimBricks 仿真世界:
- 模拟器 A ↔ 模拟器 B → 用 Socket 连接
本质:
**Socket 就是 Linux 系统提供的【进程间通信通道】**让两个独立的 C++ 程序(比如主机模拟器、网卡模拟器)互相发数据。
第 3 层:IPv4 地址(你配置的 IP)
第 2 层:以太网帧(Eth 接口收发的包)
第 1 层:Socket(真正传输数据的通道)
1. EthInterface(Python)
- 模型:网卡有一个网口
- 作用:逻辑上的网口
2. EthChannel(Python)
- 模型:两根网口连起来
- 作用:逻辑连线
3. Socket(Instantiation)
- 真实:创建 Unix Socket
- 作用:真正的物理传输通道
4. C++ 模拟器
- 真实收发数据包
- 通过 Socket 传输
nic_devices = self.filter_components_by_type(ty=sys_nic.SimplePCIeNIC)
assert len(nic_devices) == 1
nic_device = nic_devices[0]
在NICSim中,要得到自身模拟的对象nic_device,
整个流程 = 搭积木 → 配对 → 绑定 → 生成命令 → 启动进程
- 搭积木 :
sys = System(),创建主机、网卡、交换机(只是模型) - 配对 :
compmap={ 模型类: 模拟器类 },告诉系统 "这个模型用哪个模拟器" - 绑定 :
simulator.add(comp)这就是 component 被赋值的时刻! - 生成命令 :
run_cmd()把模型参数变成 C++ 启动命令 - 启动进程:运行 C++ 模拟器,真正开始仿真



simulator = I40eNicSim(simulation) # 新建模拟器,此时 components = []
simulator.add(nic0) # 🔥 绑定!
I40eNicSim→ 对应sims/nic/i40e_bm/i40e_bm(可执行文件)E1000NIC→ 对应sims/nic/e1000_gem5/e1000_gem5(可执行文件)

e1000_gem5.cc
├── 全局变量:runner(SimBricks 运行器)、debug
├── Gem5DMAOp:DMA 操作封装类
├── IGbE 类方法:E1000 硬件实现 + SimBricks 回调
│ ├── SetupIntro:配置PCIe信息
│ ├── RegRead/RegWrite:寄存器读写
│ ├── DmaComplete:DMA 完成回调
│ ├── EthRx:收到以太网包
│ ├── Timed:事件调度
│ ├── gem5 API:schedule/intrPost/dmaRead/dmaWrite/sendPacket
├── 工具函数:warn/panic/debug_printf
└── main:主函数(模拟器入口)