本篇位置:FPGA NVMe Host 实战连载 · 🟩 第一幕|共同底座 · 第 04 篇
第 02 篇解决的是链路能否进入 L0,第 03 篇解决的是 FPGA 为什么要站在 Root Port 一侧。两件事都成立后,下一步的问题很具体:FPGA 向 SSD 发出的配置访问,到底以什么形式在线上传输?SSD 又怎样把结果送回来?
答案不是一条 AXI 信号,也不是一段 NVMe 命令文本。PCIe 链路上走的是不同类别的协议单元,其中与 Host 最直接相关的是 TLP。先把这些名字和方向分清,后面看配置空间访问时就不会把“请求发出了”“返回包到了”“控制器能访问”混成一件事。
本文先给出控制路径:
FPGA Root Port → Configuration Read TLP → NVMe SSD Endpoint → Completion with Data → FPGA
Link Up 后,链路上跑的三类东西
PCIe 从训练到正常工作,会遇到 Ordered Set、DLLP 和 TLP 三类内容。它们都可能出现在同一条物理链路上,但用途不同。
图 1:PCIe 链路中三类协议单元的职责示意。图为理解和调试用途的自绘图,并未列出所有类型。
- Ordered Set:更靠近物理链路。TS1、TS2 用于训练和状态切换;链路进入 L0 后,SKP 等 Ordered Set 仍可能用于链路运行期维护。
- DLLP:Data Link Layer Packet,服务于链路层的流控、确认和维护。它不承载 SSD 的配置字段,也不承载 NVMe 用户数据。
- TLP:Transaction Layer Packet,事务层数据包。Configuration、Memory、Completion、Message 等访问都属于这一类。本篇的重点就是它。
可以把 L0 理解为“链路已经具备传输这些内容的条件”。但 L0 本身不等于某一个配置读取已经完成;是否真的完成,还要看 TLP 请求和对应返回。
三类单元的包结构并不一样
它们都走在 PCIe Link 上,却不能按同一类“包头 + 数据”的思路理解。最直接的区别是:Ordered Set 由物理层符号或块组成;DLLP 是一个固定长度的链路层小包;TLP 才是携带访问请求、返回状态和可选数据的事务包。
| 协议单元 | 在线上怎样定义 | 用途 | 本项目事务 AXI4-Stream 是否可见 |
|---|---|---|---|
| Ordered Set | 按具体类型规定的物理层 Symbol/Block 序列 | 训练、状态控制、时钟容差等链路维护 | 否 |
| DLLP | Non-Flit Mode 下固定为 6 Byte:Type + 24 bit 特定信息 + 16 bit CRC | 流控、确认、重传与电源管理等链路服务 | 否 |
| TLP | 3-DW 或 4-DW Header,加可选 Payload | Configuration、Memory、Completion、Message 等事务 | 是 |
表中“是否可见”专指本项目使用的 AMD 7 系列 PCIe IP 的事务 AXI4-Stream 接口。不同厂商或不同层级的调试接口可能提供额外状态,但不能把它们与用户事务口混为一谈。
Ordered Set:按符号位置定义,不是 TLP 式包头
Ordered Set 属于物理层使用的控制序列。它没有统一的 3-DW/4-DW Header,也不携带一次配置读取的地址或返回数据。每一种 Ordered Set 自己规定各个 Symbol/Block 的含义;接收端先根据开头的标识识别类型,再按规定的位置解释后续内容。
TS1 是最常接触到的一种。下面截取 PCIe Base 7.0 对 TS1 的前 3 个 Symbol 定义:Symbol 0 是 TS1 Identifier;Symbol 1 是 Link Number;Symbol 2 是 Lane Number。表格后续还有 N_FTS、链路控制等字段,因此这张图用于说明“按符号位置解释”,不是 TS1 的完整格式。
图 2:PCI-SIG《PCI Express Base Specification Revision 7.0》Table 4-36,PDF p.484。图中截取 TS1 Ordered Set 的 Symbol 0~2;完整表格继续定义后续 Symbol。
在第 02 篇排查 LTSSM 时,TS1/TS2 是训练侧的重要观察对象;但不要把 Ordered Set 只理解成“训练数据”。例如 SKP 也会在 L0 的正常运行期间出现,用于处理两端参考时钟之间的频率偏差。
DLLP:固定 6 Byte,服务链路本身
本项目所用的 7 系列 PCIe IP 工作在 Non-Flit Mode。该模式下,PCIe Base 7.0 §3.5.1 给出 DLLP 的固定结构:第 0 Byte 是 DLLP Type,接下来的 3 Byte 共 24 bit 为 Type-specific information,最后 2 Byte 是 16-bit CRC。因此 DLLP 一共 6 Byte。
图 3:PCI-SIG《PCI Express Base Specification Revision 7.0》Figure 3-4,PDF p.337。Non-Flit Mode 下 DLLP 的 Type、24 bit 类型特定信息和 16 bit CRC 字段。
先看 Byte 0 的 Type,就能知道这是不是哪一类 DLLP;之后再按该类型解释 24 bit 的内容。Ack/Nak 用于 TLP 的确认和重传控制,Flow Control DLLP 用于传递缓冲区 Credit。它们让链路可靠地传输 TLP,但 DLLP 本身不等于一次 SSD 配置访问,也不会带回 Vendor ID 这类配置字段。
TLP:事务内容在线路上还会加一层链路封装
TLP 本身由事务层定义,Header 为 3 DW 或 4 DW,后面是否有 Payload 由具体事务决定。Configuration Read、Memory Read 通常是“带 Header、不带返回数据”的请求;Completion with Data 则在 Header 后面带回读取结果。
在 Non-Flit Mode 的实际链路传输中,数据链路层还会在 TLP 前加入 4 bit 和 TLP Sequence Number,并在末尾追加 32-bit LCRC。图中的{TLP Header}之后可以继续接 TLP 的可选 Payload。接收端完成序号与 LCRC 校验后,才把这些链路封装剥离,将原来的 TLP 交给事务层。
图 4:PCI-SIG《PCI Express Base Specification Revision 7.0》Figure 3-17,PDF p.347。Non-Flit Mode 下,数据链路层给 TLP 加入 TLP Sequence Number 和 LCRC 后的线上形式。
因此,抓到一个“看起来像 TLP”的线上字节序列时,不能把最前面的序号和最后的 LCRC 误当成 TLP Header 或 Payload;它们属于数据链路层的可靠传输封装。
用户 AXIS 口实际看到什么
对本项目的用户逻辑,PCIe IP 提供的是事务 AXI4-Stream 接口。发送侧通过s_axis_tx_*交给 IP 的是待发送 TLP;接收侧通过m_axis_rx_*交给用户逻辑的是已通过链路检查的接收 TLP。AMD PG054 的接口章节将这组接口称为事务接口,并在 Basic TLP Receive Operation 中说明由m_axis_rx_*向用户逻辑呈现 TLP。
图 5:本项目 PCIe IP 与用户 RTL 的接口边界示意。图为自绘;接口名称和 TLP 接收行为依据 AMD《PG054 v3.3》Chapter 3。
这张图可以直接回答调试时最常见的疑问。
- ILA 接在
m_axis_rx_*一侧,看到的是 TLP 的 Header 和可选 Payload,以及tvalid、tready、tkeep、tlast等流接口信号;不是串行线上原始的 8b/10b 或 128b/130b 编码数据。 - TLP 的 Sequence Number、LCRC,以及 Ack/Nak、Flow Control 等 DLLP,由 PCIe IP 的数据链路部分生成、校验或处理,不交给这个用户事务口。
- TS1、TS2、SKP 等 Ordered Set 同样由物理层处理,不会作为 AXI4-Stream 的一个 TLP 送到用户逻辑。
这里的“只看到 TLP”不表示用户逻辑只连接一根tdata总线。实际收发仍需遵守 AXI4-Stream 握手和 IP 的tuser定义;例如 PG054 规定传输侧以tvalid和tready同时有效作为一次数据交接。它表示的是协议边界:用户接口面向事务层,而不是让 RTL 自己处理物理编码、DLLP CRC 或 TLP 的链路重传。
TLP 不是一种固定格式,而是一组事务
在 FPGA 直连 SSD 的场景中,最早会遇到的不是所有 PCIe 事务,而是下面几类。
| TLP 类别 | 谁发起 | 常见方向 | 是否有 Completion | 这类事务解决什么问题 |
|---|---|---|---|---|
| Configuration Read | Root Port | FPGA → SSD | 有,通常为 CplD | 读取配置空间中的识别与能力信息 |
| Configuration Write | Root Port | FPGA → SSD | 有,不带数据 | 写入允许由 Host 配置的字段 |
| Memory Read | 访问方 | FPGA ↔ SSD | 有,通常为 CplD | 读取控制器寄存器,或由 SSD 取得 Host 准备的数据 |
| Memory Write | 访问方 | FPGA ↔ SSD | 常规访问中没有 | 写控制器寄存器,或由 SSD 写回数据与完成信息 |
| Completion / CplD | 被请求方 | 与原请求反向 | 是对读取类请求的响应 | 返回状态;CplD 还会带回数据 |
| Message | 设备或端口 | 双向 | 取决于消息类型 | 用于特定通知和管理场景 |
图 6:PCI-SIG《PCI Express Base Specification Revision 7.0》Table 2-1,PDF p.154。规范将 Memory、I/O、Configuration、Message 分为不同地址空间,并给出各自的基本用途。
表中的 Configuration Read / Write 用于设备 Function 的配置和建立;§2.1.1.3 进一步说明,这两类 Configuration Transaction 分别由“Read Request / Completion”和“Write Request / Completion”组成。这样,配置写入也属于有返回的事务,而不是所有写入都没有返回。
这里最容易误判的是:没有 Completion 不一定是错误。本篇所说的常规 Memory Write 不包含 UIO 等特殊事务,它属于 Posted Request,发起方正常情况下不会等待一个 Completion。相反,Configuration Read 和 Memory Read 都需要看到相应的 Completion;没有返回,就需要检查请求是否到达、对端是否接受以及返回是否被接收。
一次配置读取,在线上是怎样来回的
第 03 篇已经说明 SSD 不会在 Link Up 后主动把自己的信息推给 FPGA。第一步仍由 Root Port 发起 Configuration Read。SSD 接收到请求后,再以 Completion with Data 返回本次读取的内容。
图 7:配置读取的请求—响应关系。CplD 是带数据的 Completion;请求和返回是两次方向相反的 TLP 传输。
这张图里有三个判断点。
- 请求是否真的离开 FPGA。
user_lnk_up=1只说明可以开始传输,不代表 Configuration Read 已经发出。 - SSD 是否有返回。对读取类访问,Endpoint 应给出 Completion;带回读取数据时就是 CplD。
- 返回是否属于这次访问。FPGA 不能因为看见一个包就直接当作结果,还要确认它与当前请求对应,并检查返回状态和数据是否合理。
后续配置空间实测章节会把这三个观察点放到同一条板上路径中:请求从哪里产生、返回从哪里进入、字段怎样被解析。当前先把事务关系看清,后面读到的每一个信号和波形才有位置可放。
Memory Read 与 Memory Write,后面为什么总会遇到
配置空间访问解决的是“SSD 是谁、它提供什么 PCIe 能力”。而 NVMe 控制器寄存器、命令和数据传输则会继续使用 Memory Read、Memory Write。方向看起来多,实际上只要记住“读需要数据返回,写通常不等待返回”即可。
图 8:四种常见方向。前两条是 FPGA 访问 SSD 控制器时会遇到的路径;后两条说明 SSD 执行后续命令时也可能主动发起 PCIe 事务。
图中的后两条并不改变 SSD 是 Endpoint 的事实。角色由 PCIe 拓扑和控制责任决定:Root Port 负责发现和配置下游设备;SSD 在得到 Host 提供的地址和命令后,可以发起内存访问完成数据搬运。
NVM Express 的 NVMe over PCIe Transport 规范说明,控制器属性寄存器位于 PCI BAR0/BAR1 指定的内存空间中,Host 通过内存映射访问它们。也就是说,配置读取之后,才会逐步走到 Memory Read 和 Memory Write 这一类访问。
FPGA 逻辑怎样接触这些包
PCIe IP 负责编码、链路训练、流控、DLLP 和 TLP 的链路可靠性处理。对 FPGA 用户逻辑来说,发送和接收侧通过事务 AXI4-Stream 接口交接 TLP。Host 逻辑需要做的,是在合适的时机组织访问、接收返回并推动自己的状态继续向前。
这也是 FPGA 做 PCIe Host 和调用普通寄存器外设不同的地方:一次读取不是“给地址,立刻得到数据”,而是一段跨越链路的请求—返回过程。链路上的时延、返回顺序、异常状态和接收能力,都会影响状态机下一步能否正确判断。
AMD 的 PG054 对 7 系列 PCIe IP 的事务接口和接收 TLP 行为给出了说明;它也明确区分了可由用户逻辑接收的 Posted、Non-Posted 与 Completion 事务。实际做调试时,先确认 IP 已把返回事务交给用户侧,再确认用户逻辑是否把它当作当前请求的结果处理,效率会高很多。
为什么 Link Up 后仍不能直接读 SSD
现在可以把前几篇的关系串起来:
供电/时钟/复位正确 → LTSSM 进入 L0 → 可以传输 TLP → Configuration Read 与 CplD 正常往返 → 配置空间信息有效 → 继续处理后续访问
每个箭头都是一个独立检查点。若停在 L0 前,优先回第 02 篇查物理链路;若 L0 已稳定但读取没有结果,就应把注意力转到请求、Completion 和返回解析,而不是直接怀疑 NVMe 命令。
调试时先看什么
| 现象 | 优先观察 | 容易犯的错误 |
|---|---|---|
| LTSSM 未进入 L0 | REFCLK、PERST#、Lane、收发器状态 | 把物理层问题归到 TLP |
| Configuration Read 后没有结果 | 请求是否送出、是否出现 Completion | 只看user_lnk_up |
| 收到 Completion 但结果不合理 | 返回状态、数据长度、与当前请求的对应关系 | 只因收到一个包就继续状态机 |
| 常规 Memory Write 后没有 Completion | 先确认事务类型 | 把 Posted Request 的正常现象当成丢包 |
小结
PCIe Link Up 后,FPGA 与 NVMe SSD 之间传输的不是抽象的“SSD 命令”,而是 Ordered Set、DLLP 和 TLP 等不同用途的协议单元。Ordered Set 按物理层 Symbol/Block 定义,DLLP 在 Non-Flit Mode 下是固定 6 Byte 的链路维护包,TLP 才承载配置、内存访问与 Completion 等事务;TLP 在线上还会由数据链路层加入 Sequence Number 和 LCRC。
对本项目的用户 AXI4-Stream 逻辑,收发边界是 TLP:DLLP、LCRC、序号和 Ordered Set 都在 PCIe IP 内部处理。明确这条边界后,看到 ILA 中的一段m_axis_rx_tdata时,才能知道它应按 TLP Header/Payload 解析,而不是从物理层符号或 DLLP 开始猜。
读取类事务需要看 Completion,CplD 还会带回数据;常规 Memory Write 则通常没有 Completion。理解这个差异后,才能把“链路已经可用”和“某次配置访问已经得到结果”严格分开。下一篇进入配置空间本身:SSD 的基础信息放在哪里,FPGA 读取时究竟在确认什么。
参考资料
- AMD,PG054 v3.3:7 Series FPGAs Integrated Block for PCI Express,事务层接口与接收 TLP 说明参见 Chapter 3,Root Port 相关说明参见 Chapter 4。
- NVM Express,NVMe over PCIe Transport Specification v1.4,第 2 章说明 PCIe Transport 的内存映射传输;第 3 章说明 BAR0/BAR1 与控制器属性寄存器的关系。
- PCI-SIG,PCI Express Base Specification Revision 7.0,Table 2-1(PDF p.154)和 §2.1.1.3(PDF p.155)是本章事务分类与 Configuration Transaction 请求—返回关系的直接来源。