【经验分享】CPU是如何“发现”PCIe协议分析仿真的Endpoint的?一文讲透PCIe中的BDF分配机制
2026-04-23 09:47:32

我们知道由PCIe训练器仿真的协议层PCIe RC 是用来测试用户开发的EP,市场上大多数公司是开发各类PCIe 外设的,不论GPU,AI加速卡,DPU,网卡,SSD主控芯片,等等,所有需要PCIe 6.0训练器仿真RC的场景多,PCI SIG进行CTS测试也是以测试外设为主;但是PCIe 训练器仿真的EP则专门用来测试用户开发的RC设备,一般情况是各类CPU,当然也包括PCIe Switch的RC端(连接下行EP外设的部分)。

SerialTek PCIe 6.0协议分析仪的训练器Tester(也叫Exerciser)功能支持设置成仿真RC或者EP。有用户问如果设置成EP来测试他们开发的CPU的时候,CPU是否可以在初始化过程中会扫描到这个仿真的EP device并且给它分配一个BDF(Bus: Device. Function)呢?这个就要了解BDF扫描机制是如何在系统里面实现的,我们今天先简单讲一下 PCIe 枚举里 BDF 的实现细节,再给你一个尽量贴近工程实际、尤其适合“CPU + PCIe协议仿真的 EP”场景的回答。

先上答案:

SerialTek公司的PCIe 6.0训练器在设置成EP emulation的时候是可以被CPU正确识别到BDF的,该设备在业内不同的主机系统里面都正确通过PCIe枚举的过程验证并且获得BDF分配,该仿真的EP和正常的PCIe EP device没有什么特别不同的地方。

这么说吧,如果这个仿真的 Endpoint 最终被 CPU/Root Complex 成功枚举到,CPU 在初始化扫描过程中就会“看到”并给它分配一个 BDF。但要注意,BDF 通常不是 Endpoint 自己预先决定并上报给 CPU 的固定号码,而是 Root Complex / 固件 / 操作系统在枚举过程中分配和记录的地址。Linux 文档里也直接把设备地址写成 domain:bus:device.function 这种形式,并明确这是系统里的 PCI 设备地址表示法。

我们上面这个场景里可以这样理解:

1)CPU 会不会“扫描到”这个 FPGA EP 的 BDF?会,但更准确地说是:CPU/RC 在扫描配置空间时发现了这个设备,然后给它安排了一个 BDF,并在后续访问中按这个 BDF 去访问它。前提是链路先起来,LTSSM 至少走到能正常进行配置访问的状态,并且 FPGA 这边把一个合法的 PCIe 配置空间端点行为模拟出来了,比如 Vendor ID / Device ID、Header Type、BAR、Class Code 等基本字段能被正确读到。Linux 内核文档也说明了 PCI 设备是通过总线上的配置与资源模型被内核识别和管理的。

2)BDF 到底是什么?BDF = Bus / Device / Function。 通常写成:

  1. bb:dd.f

如果把 PCI domain 也带上,Linux 常写成:

  1. dddd:bb:dd.f

例如 0000:17:00.0,其中 Linux 文档明确说明前面的部分可表示 PCI domain 和 bus,而后面的目录对应某个 slot/function 设备。

它本质上是配置访问地址,不是设备内部某个永久烧录的“身份证号”。

3)BDF 一般是怎么实现出来的?核心在 PCIe 枚举(enumeration)

大致过程是:

  • Root Complex 从某个 bus 开始扫描;
  • 对每个可能的 device number、 functionnumber 发起配置读;
  • 如果读到有效的 Vendor ID,说明这里有设备;
  • 然后系统给它建立对应的 bus/device/function 记录;
  • 如果遇到桥(Bridge / Switch Port),再给下游分配新的 bus number,继续往下枚举。

所以:

  • Bus Number:通常由 RC/固件/OS 在枚举桥和下游总线时分配。
  • Device Number:对直连 RC 端口下的设备,常由 RC 侧拓扑/端口位置决定;在 switch 下游也和枚举拓扑位置有关。
  • Function Number:如果一个设备是多功能设备,则 function 号通常体现为 0~7 中的某个功能号;SR-IOV 场景里还会出现更多由 PF 派生出来的 VF 逻辑函数。Linux 文档对 function、PF/VF 的表述也能印证这一点。

4)PCIe协议分析仪的RC/EP仿真都是通过设备内部的高端FPGA实现的,在 FPGA 仿真 Endpoint 时,哪些东西是 FPGA 自己决定的,哪些不是?一般来说:

FPGA / EP 自己决定的:

  • Vendor ID / Device ID
  • Class Code
  • Revision ID
  • Header Type
  • BAR 大小与类型
  • Capability / Extended Capability
  • 是否多功能设备(multifunction)
  • MSI/MSI-X、AER、PCIe Capability 等配置空间内容

不是 FPGA 自己最终决定的:

  • Bus Number
  • 多数情况下的 Device Number
  • 系统里最终看到的完整 BDF

也就是说,FPGA 提供“我是什么设备”的配置空间内容;系统决定“你在这台机器里住哪个地址号”。 有些 SoC/PCIe EP 控制器会提供“PCIe gadget”一类接口,让你配置 vendorid、deviceid、BAR 大小等,但这不等于你能直接指定最终 BDF。Linux 文档里这类 EP gadget 接口就体现了这一点:可配置的是设备属性,而不是最终系统分配给它的完整 BDF。

5)那 CPU 初始化时,是“先知道 BDF”,还是“先发现设备再形成 BDF”?更贴近真实实现的说法是:先按候选的 bus/device/function 地址去探测配置空间,读到有效设备后,这个地址就成为该设备的 BDF。

所以从软件视角看,像是在“扫描 BDF”; 从设备视角看,是“系统在一堆可能地址里探测,发现你后把那个地址作为你的 BDF”。

6)这个 BDF 是不是每次开机都固定?不一定。 Linux 内核参数文档就明确提醒,PCI 的 bus/device/function 地址可能会因为插入新硬件、主板固件变化、或者内核参数变化而改变。

因此:

  • 在简单、固定拓扑里,BDF 往往看起来比较稳定;
  • 但只要拓扑、桥分配策略、BIOS/UEFI 行为变了,BDF 就可能变化;
  • 所以工程上通常不要把 BDF 当作设备的永久身份标识。

7)结合你这个“CPU + FPGA 仿真 EP”的场景,最实用的结论如果你的 FPGA Endpoint 仿真是正常的,那么 CPU 初始化时通常会:

  • 建链成功;
  • 对配置空间发起读请求;
  • 读到有效 Vendor ID / Device ID;
  • 给它纳入系统 PCI 设备树;
  • 在软件里表现成一个 BDF,比如 00:01.0、 01:00.0 之类。

但如果下面任一项有问题,CPU 就可能根本“看不到”这个 BDF:

  • 链路没起来;
  • FPGA 没有正确响应配置读;
  • Vendor ID 读回 0xFFFF
  • Type 0 / Type 1 Header 模拟错了;
  • BAR 或 capability 结构异常,导致枚举中断或驱动阶段失败。

8)一句话总结BDF 不是 FPGA Endpoint 预先“报给 CPU”的固定编号,而是 CPU/Root Complex 在 PCIe 枚举过程中,根据系统拓扑和配置访问结果,为这个被发现的 Endpoint 确定并使用的地址。

更多关于PCIe 6.0/CXL的测试工具和技术,请下载Saniffer公司2026.1.6最新更新的白皮书15.1版本 - PCIe5&6.0, CXL, NVMeNVMoF, SSD, NAND, DDR5, 800GE测试技术和工具白皮书_ver15.1-low resolution.pdf(低分辨率版本,file size: 63MB);需要高清图片pdf版本的请参见本文底部的联系方式联系我们获取(file size: 204MB)

链接: https://pan.baidu.com/s/1R-tJEqwBlzBaDR0WLuMU0Q?pwd=9av3 提取码: 9av3

图片

如果你有其任何关于PCIe5&6.0, CXL, NVMe/NVMoF, NAND, DDR5/LPDDR5以及UFS测试方面的我问题想咨询,请访问:访问www.saniffer.cn / www.saniffer.com 访问我们的相关测试工具和产品;或者添加点击左下角“阅读原文”留言,或者saniffer公众号留言,致电021-50807071 / 13127856862,sales@saniffer.com。

图片