以下文章来源于AdriftCoreFPGA芯研社,作者CNL中子
前言
如果把整个 PCIe 系统比作一个庞大的社会,那么每一个 EP 设备都需要在入职时向系统提交一份详细的个人档案。这份档案不仅记录了它是谁(Vendor/Device ID)、由谁代工(Subsystem ID),还声明了它的职业能力(Class Code)以及它需要占用的办公资源(BAR 空间)。我们将深入剖析 PCIe 配置空间中Type 0 Header的核心成员,探讨这些寄存器背后的意义。
配置空间
在 PCIe 协议中,Endpoint (EP)具有独立的配置空间。其中,EP 的配置空间首部(Header)被定义为Type 0,主要用于存放 EP 设备的各种配置寄存器(例如上一章提到的 BAR 寄存器),软件通过修改这些寄存器的值来完成对设备的初始化。与之对应,Type 1主要用于Root Complex (RC)以及Switch的配置空间首部。

image
image
在整个配置空间结构中,前64 DW (256B)是 PCI 与 PCIe 共同拥有的标准配置空间,而后续的960 DW (3840B)则是 PCIe 特有的扩展配置空间(Extended Capability)。

image
ID
在 PCIe 配置空间(Configuration Space)的头部(Header)中,Vendor ID(厂商标识)和Device ID(设备标识)是两个最基础、最重要的只读寄存器。
它们位于配置空间的起始位置(Offset 00h ~ 03h),相当于 PCIe 设备的身份证。操作系统和 BIOS 正是通过这两个 ID 来识别设备,并为其加载正确的驱动程序。
Vendor ID (厂商 ID)
定义:代表生产该 PCIe 芯片的厂商。
分配权:由PCI-SIG统一分配,不能随意填写。
典型值:
•0x8086:Intel (因 x86 架构得名)
•0x10DE:NVIDIA
•0x1002:AMD (ATI)
•0x10EE:AMD (XILINX)
注意:如果 CPU 读取某个设备的 Vendor ID 返回0xFFFF,通常表示该位置不存在设备(Device Not Present),或者链路训练失败导致无法读取。
Device ID (设备 ID)
定义:代表厂商生产的具体某一款芯片或产品。
分配权:由厂商自己定义。只要保证在该 Vendor ID 下唯一即可。
作用:用于区分同一厂商的不同产品。例如 NVIDIA 的不同显卡芯片(RTX 3060 vs RTX 4090)会有相同的 Vendor ID,但 Device ID 不同。
Sub ID
在 PC 产业中,芯片厂商(如 NVIDIA, Intel, Realtek)通常只卖芯片,而板卡厂商(如 华硕, 戴尔, 联想)会将这些芯片买回去,做成显卡、网卡或集成在主板上。
如果说 Vendor ID/Device ID 识别的是这颗PCIe芯片是谁造的、是什么型号,那么 Subsystem Vendor ID/Subsystem ID 识别的就是这块板卡(或成品设备)是谁造的、是什么型号。
例如显卡,NVIDIA生产了RTX 3070芯片。华硕 (ASUS)和微星 (MSI)都买了这款芯片,分别做成了华硕 RTX 3070猛禽和微星 RTX 3070魔龙 。
对于操作系统来说,这两张卡的核心芯片是一模一样的(Vendor ID 都是 NVIDIA,Device ID 都是 3070)。如果没有 Subsystem ID,系统就无法区分这两张卡。
这就导致了两个问题:
1. 华硕可能有专门的 RGB 灯光控制软件或风扇策略,不能用在微星的卡上。 2. OEM 厂商(如联想)可能希望系统里显示Lenovo Graphics而不是公版的NVIDIA Graphics。
Subsystem Vendor ID (SVID, 子系统厂商 ID)
定义:代表板卡制造厂商(Add-in Card Manufacturer)或系统集成商。
分配:由PCI-SIG组织分配。通常,华硕、戴尔、惠普都有自己独立的 PCI-SIG 厂商代码。
•0x1043:ASUS (华硕)
•0x1028:Dell (戴尔)
•0x10EE:XILINX
Subsystem ID (SID, 子系统 ID)
含义:代表该厂商生产的具体板卡型号。
分配:由SVID 的持有者(即板卡厂商自己)定义。
FPGA ID
对于FPGA来说电脑会认为插了一个Xilinx 的设备。你可以直接使用 Xilinx 提供的官方驱动(如 XDMA 驱动或 QDMA 驱动),因为这些驱动默认识别
Vendor ID 保持默认的0x10EE(Xilinx 的 ID)。Device ID 保持默认取决于具体的 FPGA 型号,或者改成你为了区分不同工程而自定义的数字(比如0x7020)。Subsystem ID 通常不关心,保持默认。
如果你要把这个 FPGA 板卡作为一个独立产品出售(比如做了一个 FPGA 加速卡),有两种方法
正式途径
你的公司向 PCI-SIG 组织交钱(年费约 4000 美元),申请一个属于你们公司的Vendor ID(比如0xABCD)。
在 Vivado 里,把 Vendor ID 改成0xABCD。优点是显得非常专业,拥有独立的驱动签名。缺点是费钱。
常规做法
Vendor ID 继续填0x10EE(Xilinx)。Device ID 填一个你自定义的数(例如0x8888)。Subsystem Vendor ID 填 Xilinx 的 ID。Subsystem ID 填你定义的产品型号。这种情况下,你需要在你的驱动程序(Driver)的.inf文件里添加这个特定的 Device ID,否则系统不知道这个 Xilinx 设备该用什么驱动。
Class Code
Class Code(类代码)是 PCIe 配置空间中一个非常巧妙且关键的字段。
如果说 Vendor ID 和 Device ID 是设备的品牌和型号例如:我是 NVIDIA 生产的 RTX 3080;那么 Class Code 就是设备的职业资格证例如:我是做图形显示的,或者是做网络通信的。
它位于配置空间头部的Offset 08h位置,总共占用24 bits (3 字节)。
它由三个独立的 8-bit 寄存器组成,层级非常分明,从左到右(高位到低位)依次是:
| 字节位置 | 名称 | 含义 (Role) | 举例 |
|---|---|---|---|
| 高字节 (Offset 0Bh) | Base Class (主类) | 大行业 这设备属于哪个大类 | 存储控制器 (01h)、网络控制器 (02h)、显示控制器 (03h) |
| 中字节 (Offset 0Ah) | Sub-Class (子类) | 具体工种 在大类里具体干啥 | 存储里的 Flash (08h)、网络里的 Ethernet (00h) |
| 低字节 (Offset 09h) | Prog IF (编程接口) | 工作语言 用什么标准协议沟通 | 符合 NVMe 标准 (02h)、符合 AHCI 标准 (01h) |
对于FPGA的意义
对于FPGA来说Class Code的意义主要是实现免驱。
所谓的免驱(Driver-free),并不是说操作系统不需要驱动程序,而是指直接使用操作系统内置的通用驱动程序(In-box Driver),用户不需要手动下载安装特定的.exe或编译内核模块。
例如,操作系统(Windows 10/11, Linux, macOS)都内置了NVMe 驱动。如果你的 FPGA 宣称自己是一个 NVMe SSD,系统就会自动加载内置的stornvme(Windows) 或nvme(Linux) 驱动。但是你不能只改Class Code为NVMe设备,如果你的 FPGA 被识别为 NVMe,操作系统会立刻发出一堆标准的 NVMe 命令(如Identify Controller,Create IO Queue)。你的 FPGA 逻辑必须能够严格遵循 NVMe 协议来响应这些命令。又或者我们更改Class code将 FPGA 伪装成一个 PCI 串口卡。Windows 会自动将其识别为 COM 口。但同样,你的 FPGA 必须在 BAR 空间里模拟 16550 UART 的寄存器行为。
写在最后
Type 0 Header 看似只是一些静态寄存器的集合,但它决定了操作系统如何看待你:你是谁、你从哪来、你能干什么、以及系统该用什么方式与你对话。
无论是做 ASIC 还是 FPGA,理解这些字段背后的规则,才能避免“设备能枚举却用不了”“驱动始终加载不上”这类看似玄学、实则配置层就已埋雷的问题。
当你真正把 Vendor / Device / Subsystem / Class Code 这些信息设计清楚,PCIe 设备才算完成了从能被看到到能被正确使用的关键一步。后面的 BAR、Capability、MSI/MSI-X,才有发挥空间。










