ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

第十一章:PCIe Configuration TLP 和 Memory TLP 有什么区别?FPGA 访问 NVMe BAR 前必须弄清楚

第十一章:PCIe Configuration TLP 和 Memory TLP 有什么区别?FPGA 访问 NVMe BAR 前必须弄清楚

本篇位置:FPGA NVMe Host 实战连载 · 第二阶段 · 第 11 篇

第 08 章通过 Configuration Read 读到了三星 980 的 Vendor ID,第 10 章又说明了 BAR、NVMe 控制器属性和队列之间的关系。接下来要访问CAP、VS、CC、CSTS和 Doorbell,不能继续使用 Configuration Read,也不能把目标 BDF 换成一个 BAR 数值后照搬原来的包头。

此时控制路径变为:

PL 请求逻辑 → PCIe Memory Read/Write → SSD BAR 寄存器窗口 → Completion with Data → PL 解析

本章先把 Configuration TLP 和 Memory TLP 的差异讲清楚。我们仍以项目使用的 64 位 AXI4-Stream 接口、Non-Flit Mode TLP 为例,但本章不宣称已经完成新的 BAR 或 NVMe 寄存器板测。下一章会在这些规则基础上处理 BAR0/BAR1 的探测和地址分配。

1. 先看结论:它们不是同一种地址访问

Configuration Request 和 Memory Request 都从 FPGA 的 PCIe 事务接口发出,也都属于 TLP,但它们解决的问题不同。

对比项Configuration Read/WriteMemory Read/Write
目标空间PCIe Function 的配置空间已映射的系统内存地址空间
定位方式目标 BDF + Register Number32 位或 64 位地址
典型用途枚举、读 Vendor ID、设置 Command 和 BAR访问 BAR/MMIO、队列内存和数据缓冲区
请求头固定为 3-DW Header32 位地址用 3-DW,64 位地址用 4-DW
读取返回CplD;失败时可能为不带数据的 CplCplD;较大读取可能拆成多条 Completion
成功写入需要 Completion普通 Memory Write 是 Posted Request,不返回事务层 Completion

这张表里最重要的不是包头多一个 DWORD,而是路由依据变了。配置访问的目标是 PCIe 树中的某个 Function,Memory 访问的目标是系统已经分配好的地址。BAR 正是连接两者的桥梁:Host 先在配置空间发现并配置 BAR,之后才使用对应的 Memory 地址访问控制器寄存器。

图 1:项目事务方向示意。Configuration Read 按 BDF 和寄存器号定位;Memory Read/Write 按地址定位。MRd 需要 CplD,普通 MWr 没有对应的事务层 Completion。

图 1 也说明,不能只用“有没有返回”判断请求类型。Configuration Read 和 Memory Read 都要等返回;区别主要在请求头和目标定位。Memory Write 没有 Completion,也不等于写入结果可以完全不检查,Host 仍应通过后续寄存器状态、读取回值或更高层协议状态确认写入是否产生了预期效果。

2. Fmt 与 Type 要一起看

Non-Flit Mode TLP 的 Byte 0 由Fmt[2:0]和Type[4:0]拼成。Fmt表示 Header 长度及是否带 Payload,Type表示事务类别。

图 2:PCI-SIG《PCI Express Base Specification Revision 7.0》§2.2.1.1、Figure 2-4/2-5、Table 2-2 与 Table 2-3 起始行及脚注 4,PDF p.157~158 原文裁切拼接。完整保留 Fmt/Type 的字段定义、Fmt 取值表及 MRd 两种 Fmt 对应 32/64 位地址格式的说明。

以本章涉及的几类 TLP 为例:

TLPFmtTypeByte 0含义
3-DW MRd000000008'h0032 位地址、无 Payload 的 Memory Read
4-DW MRd001000008'h2064 位地址、无 Payload 的 Memory Read
3-DW MWr010000008'h4032 位地址、带 Payload 的 Memory Write
4-DW MWr011000008'h6064 位地址、带 Payload 的 Memory Write
CfgRd0000001008'h04Type 0 Configuration Read
CfgWr0010001008'h44Type 0 Configuration Write,带写入数据
Cpl000010108'h0A不带数据的 Completion
CplD010010108'h4A带数据的 Completion

图 3:PCI-SIG《PCI Express Base Specification Revision 7.0》Table 2-3 续表,PDF p.159。图中完整显示 MWr、CfgRd0/CfgWr0、CfgRd1/CfgWr1、Cpl 与 CplD 的 Fmt、Type 和用途说明。

这里有两个容易混淆的地方。

第一,MRd 和 MWr 的Type都是00000,读写差别由Fmt是否带数据体现。第二,8'h40只是“3-DW、带数据的 Memory Write”,而项目采用 64 位 MMIO 地址时使用 4-DW MWr,对应Fmt=011,Byte 0 是8'h60。不能看到 Memory Write 就把包头第一个字节固定写成40h。

可以在 RTL 中保留字段含义,不必把每个组合写成没有来历的常量:

localparam logic [4:0] TLP_TYPE_MEM = 5'b00000; wire [7:0] mrd64_byte0 = {3'b001, TLP_TYPE_MEM}; // 8'h20 wire [7:0] mwr64_byte0 = {3'b011, TLP_TYPE_MEM}; // 8'h60

实际 DW0 还包含 TC、Attr、AT、TD、EP 和 Length 等字段。只检查 Byte 0 能识别大类,不能代替对完整 Header 的生成和校验。

3. Memory Request 为什么有 3-DW 和 4-DW 两种 Header

PCIe Memory Request 可以携带 32 位或 64 位地址。32 位地址只需要一个地址 DWORD,因此整个 Header 是 3 DW;64 位地址需要高、低两个地址 DWORD,因此 Header 是 4 DW。

图 4:PCI-SIG《PCI Express Base Specification Revision 7.0》Figure 2-36、Figure 2-37,PDF p.209。上图为 64 位地址的 4-DW Memory Request Header,下图为 32 位地址的 3-DW Header。

两个格式都包含 Requester ID、Tag、Last DW BE、First DW BE 和 Length。区别集中在地址部分:

  • 4-DW Header 的 DW2 放Address[63:32],DW3 放Address[31:2]。
  • 3-DW Header 的 DW2 放Address[31:2]。
  • Memory Read 没有 Payload;Memory Write 在 Header 后继续携带写入数据。

地址低两位没有作为普通地址位直接放进图中的字段,因为 TLP 数据按 DWORD 对齐,首尾哪些字节真正有效由 Byte Enable 配合描述。构造请求时,不能把一个未经对齐处理的 64 位地址原样塞进 DW2/DW3。

项目使用 64 位地址接口,因此后续 MMIO 请求采用 4-DW Header。即使当前分配的 BAR 地址落在 4 GiB 以下,也应让地址格式的选择与系统地址管理保持一致;若改用 3-DW Header,则必须确认目标地址确实能由 32 位表示。

4. Configuration Header 里的 BDF 到 Memory Header 中不见了

Configuration Request 的 Header 明确带有 Destination ID。对于前面使用的 3-DW、非 ARI Type 0 请求,Destination ID 中就是 Bus Number、Device Number 和 Function Number。

图 5:PCI-SIG《PCI Express Base Specification Revision 7.0》Figure 2-39,PDF p.211。Configuration Request 使用 Destination ID 和 Register Number 定位目标配置空间。

图 4 的 Memory Request Header 没有 Destination ID。Memory Request 按地址路由,Switch、Root Port 和 Endpoint 根据地址窗口决定请求向哪里转发。对 NVMe 控制器 BAR 的访问,Host 需要先完成下面的关系:

SSD 配置空间中的 BAR0/BAR1 → Host 分配的 Memory 地址窗口 → NVMe 寄存器偏移

随后某个寄存器的实际请求地址才是:

MMIO 请求地址 = 已分配的 BAR 基地址 + NVMe 寄存器 Offset

例如CAP的 Offset 是00h,VS的 Offset 是08h。这里的 Offset 只是相对控制器寄存器窗口的位置,不是完整的 PCIe Memory 地址。若 BAR 尚未配置、Command 寄存器的 Memory Space Enable 尚未满足,直接对00h发 MRd 并不是在读取 NVMe CAP。

5. Requester ID 和 Tag 还在,但用途要分清

从图 4 和图 5 可以看到,Configuration Request 与 Memory Request 都有 Requester ID 和 Tag。

  • Requester ID 表明请求由哪个 Function 发出。
  • Tag 由请求方分配,用于把返回的 Completion 对应到原请求。
  • Configuration Request 还带 Destination ID,用于指出目标 BDF。
  • Memory Request 不带目标 BDF,它依靠 Address 路由。

对于 Memory Read,接收侧仍要保存 Requester ID 和 Tag,并核对 CplD 中回填的关联字段。以后允许多笔读取同时在链路上时,Tag 会从单个寄存器变成未完成请求表的索引之一。

Memory Write 虽然 Header 里也有 Requester ID 和 Tag,但普通 Posted Memory Write 不会像 MRd 那样返回一条成功 Completion。因此不能为每笔 MWr 都进入“等待同 Tag CplD”的状态,否则状态机只能等到超时。

6. Length 表示什么,不能只理解成 Payload 长度

PCIe Base 7.0 对 Non-Flit Mode Header 的说明是:Length 表示 Payload 的长度,或者请求所引用的数据长度,单位为 DWORD。

这句话放到三类请求中看就比较清楚:

  • 对 MRd,Request 本身没有 Payload,Length=1表示请求读取 1 DWORD。
  • 对 MWr,Request 自身携带 Payload,Length=1表示写入 1 DWORD。
  • 对 CfgRd0/CfgWr0,配置访问按规范限制为 1 DWORD,因此Length=1。
  • 对 CplD,Length 表示当前这条 Completion 实际携带多少 DWORD 数据。

Memory Read 与前面的 1-DWORD Configuration Read 还有一个重要差别:较大的 MRd 可能由多条 CplD 返回。此时接收端不能看到第一条 CplD 就认为整笔请求已经完成,而要结合 Length、Byte Count、Lower Address、Tag 和累计接收量判断。

另外,Memory Request 的地址和长度组合不能跨越 4 KiB 边界。大块数据访问应在请求生成侧提前拆分,不能把跨界包交给对端后再依赖错误返回补救。

7. First DW BE 和 Last DW BE 决定哪些字节有效

First DW Byte Enable 和 Last DW Byte Enable 各有 4 位,对应首、尾 DWORD 的四个字节。对于最常见的“读取或写入一个完整 32 位寄存器”:

字段取值
Length1 DW
First DW BE4'b1111
Last DW BE4'b0000

当 Length 为 1 DW 时,有效字节只由 First DW BE 描述,Last DW BE 必须为0000b。这和直觉上“首尾其实是同一个 DWORD,所以两个 BE 都写 F”不同。

读取 64 位 CAP 时,可以发起一个Length=2的 MRd,也可以按设计能力拆成两个 1-DWORD MRd。若采用 2-DWORD 请求,首尾 Byte Enable、地址对齐以及返回数据拼接都要一起检查;若拆成两笔,则要分别分配和匹配 Tag,并避免把两个返回顺序写死在没有依据的假设上。

8. Completion Header 没有因为读取空间不同而换一套格式

Configuration Read 和 Memory Read 成功后都通过 CplD 返回。Completion Header 中有 Completer ID、Completion Status、Byte Count、Requester ID、Tag 和 Lower Address。

图 6:PCI-SIG《PCI Express Base Specification Revision 7.0》Figure 2-79,PDF p.243。Configuration Read 和 Memory Read 的返回都使用这一类 Completion Header。

这意味着第 07 章已有的 Completion 解析思路仍然有用:先确认包边界和 Cpl/CplD,再检查 Completion Status、Requester ID 和 Tag,最后交付 Payload。但进入 Memory Read 后,需要补上更多能力:

  1. 允许一笔 MRd 对应多条 CplD。
  2. 根据 Byte Count 和 Lower Address 放置每段返回数据。
  3. 管理多笔未完成请求及其 Tag。
  4. 处理 Completion 的乱序到达,但保持同一 Read Request 内的数据顺序要求。
  5. 区分寄存器小读和面向队列、数据缓冲区的较大读取。

所以,Vendor ID 示例中的“两拍 CplD 接收器”可以作为最小起点,但不能直接当作通用 Memory Read 返回引擎。

9. Memory Write 为什么没有成功 Completion

普通 Memory Write 是 Posted Request。发送端将完整 TLP 交给 PCIe IP 后,不会收到一条表示“写入成功”的事务层 Completion。

这种设计减少了每笔写入都等待应答的开销,但也改变了 RTL 的完成定义。MWr 发送状态机中的done最多表示:所有 Header 和 Payload 拍已经通过tvalid && tready交给 PCIe IP。它不能直接表示:

  • SSD 已经执行了寄存器写入;
  • NVMe 控制器状态已经改变;
  • 写入的命令已经执行完成;
  • 用户数据已经持久保存到 NAND。

例如后续写CC.EN后,应轮询CSTS.RDY;写 Submission Queue Tail Doorbell 后,应等待对应 Completion Queue Entry;写入用户数据后,应以 NVMe 命令完成状态和数据校验为准。不同层的“完成”不能混成一个信号。

Configuration Write 则不同。它是 Non-Posted Request,需要 Completion。不能因为 CfgWr 和 MWr 都携带写入 Payload,就给它们使用同一套结束条件。

10. 64 位 AXI4-Stream 上,一个最小请求占几拍

PCIe Base 定义 TLP 字段,AMD PG054 规定这些字节怎样放到 PCIe IP 的 AXI4-Stream 接口。项目当前使用 64 位接口,每拍最多传输 8 字节。

图 7:AMD《PG054 v3.3:7 Series FPGAs Integrated Block for PCI Express》Figure 3,PDF p.46。TLP 的第一个字节位于首个 QWORD 的tdata[31:24],Header 和 Payload 必须按该 Byte Order 放置。

对项目采用的 4-DW、64 位地址格式:

  • 读取 1 DWORD 的 MRd 只有 16 字节 Header,占两个完整拍。
  • 写入 1 DWORD 的 MWr 有 16 字节 Header 和 4 字节 Payload,占三个拍。
  • 返回 1 DWORD 的 CplD 有 12 字节 Header 和 4 字节 Payload,共 16 字节,占两个完整拍。

图 8:项目接口布局示意。图中采用 4-DW Memory Request Header;每拍只有在tvalid && tready同时成立时才完成传输。

图 8 中的 MWr 第三拍只有低 32 位有效,所以tkeep=8'h0F,并在该拍给出tlast=1。MRd 的第二拍已经是 Header 末尾,因此tkeep=8'hFF、tlast=1。CplD 的第二拍同时放 DW2 和 Payload,两个 DWORD 都有效。

下面这段只展示 1-DWORD、64 位地址请求的 DW0 核心差异:

// MRd64:4-DW Header,无 Payload,请求读取 1 DW wire [31:0] mrd_dw0 = { 3'b001, 5'b00000, 1'b0, 3'b000, 4'b0000, 1'b0, 1'b0, 2'b00, 2'b00, 10'd1 }; // MWr64:4-DW Header,带 Payload,写入 1 DW wire [31:0] mwr_dw0 = { 3'b011, 5'b00000, 1'b0, 3'b000, 4'b0000, 1'b0, 1'b0, 2'b00, 2'b00, 10'd1 };

这不是一个完整的通用 TLP 生成器。Requester ID、Tag、Address、Byte Enable、Payload、握手、流控和错误处理仍要由外围逻辑正确管理;TD、EP、Attr、AT 等字段也不能在所有应用中永远照抄为零。

11. 从 Configuration Read 改成 MRd/MWr,RTL 至少要改哪些地方

如果以第 08 章的最小配置读取引擎为起点,进入 BAR/MMIO 前至少要处理下面这些变化。

模块位置Configuration Read 阶段BAR/MMIO 阶段需要增加的能力
请求参数目标 BDF、Register Number32/64 位 Memory Address、读写长度、写入 Payload
Header 生成3-DW CfgRd0/CfgWr03-DW 或 4-DW MRd/MWr
发送状态机两拍配置读 Header两拍 MRd Header;MWr 还要连续发送 Payload
返回匹配单笔、单 DWORD CplDMRd 分段、Byte Count、Lower Address、并发 Tag
完成判定CplD 或配置写 CplMRd 等 CplD;MWr 发送结束后由上层状态确认
地址管理固定 BDF 和配置 OffsetBAR 基地址、寄存器 Offset、地址范围与对齐

还有一个常被漏掉的条件:Host 必须正确配置 SSD Function 的 Memory Space Enable,地址窗口也要与 BAR 探测和分配结果一致。否则 TLP 格式即使正确,Endpoint 也未必接受该 Memory Request。

12. 仿真和 ILA 应该怎样验证

这一阶段先验证事务结构,不必一开始就依赖真实 SSD 的 CAP 数值。

仿真建议

  1. 让 4-DW MRd 在两个 AXI 拍内完成,主动拉低tready,确认未握手时所有输出保持不变。
  2. 为 MRd 返回合法的 1-DWORD CplD,检查 Requester ID、Tag、Status 和 Payload 重组。
  3. 为同一个 Tag 返回 UR 或 CA,确认失败状态不会被当成有效寄存器数据。
  4. 让 MWr 连续发送两个 Header 拍和一个 Payload 拍,确认末拍tkeep=0F、tlast=1。
  5. 确认 MWr 发送结束后不会进入等待 CplD 的状态。
  6. 增加地址跨 4 KiB 边界的非法用例,要求请求生成侧拒绝或拆分。

上板 ILA 建议

检查点需要观察的信号或字段可以证明什么
请求起点BAR 基地址、寄存器 Offset、最终 Memory Address地址计算没有把 Offset 当完整地址
TLP 首拍Fmt、Type、Length、Requester ID、Tag请求类别和关联信息正确
地址拍Address[63:32]、Address[31:2]、tlast、tkeep4-DW Header 排列正确
MWr Payload写入数据、tkeep、tlastPayload 没有错拍或丢字节
MRd 返回CplD、Status、Byte Count、Lower Address、Tag返回属于当前读取且数据位置合理
上层结果超时、成功、错误原因、读取数据事务结束条件没有混淆

正式读取 SSD 寄存器时,建议第一笔选择只读的CAP或VS,不要从修改控制器状态的CC、Doorbell 开始。先证明 Memory Read 路径可靠,再加入 Memory Write,可以明显缩小调试范围。

13. 本章完成了什么

到这里,Configuration TLP 与 Memory TLP 的边界已经明确:

  • Configuration Request 用 BDF 和寄存器号访问配置空间。
  • Memory Request 用系统地址访问 BAR/MMIO 或 Host 内存。
  • MRd 不带 Payload,但必须等待 CplD。
  • MWr 自带 Payload,是没有成功 Completion 的 Posted Request。
  • 64 位地址的 MRd/MWr 使用 4-DW Header。
  • Length、Byte Enable、Tag 和 Completion 规则决定请求能否被可靠处理。

后续真正访问 NVMe 控制器时,数据路径为:

PL Memory Request → PCIe Root Port → SSD BAR/MMIO → CplD 或寄存器状态变化 → PL 校验

下一章将进入 BAR0/BAR1:怎样读取 BAR 属性、判断 32 位或 64 位窗口、探测所需空间、恢复原值并建立可用于 NVMe MMIO 的地址映射。

参考资料

  1. PCI-SIG,PCI Express Base Specification Revision 7.0,Figure 2-5、Table 2-2/2-3(PDF p.158~159):Non-Flit Mode TLP 的 Fmt、Type 和 Length 字段。
  2. PCI-SIG,PCI Express Base Specification Revision 7.0,§2.2.5~§2.2.7、Figure 2-36/2-37/2-39(PDF p.194~211):Byte Enable、Transaction ID、Memory Request 与 Configuration Request Header 及路由规则。
  3. PCI-SIG,PCI Express Base Specification Revision 7.0,§2.2.9、Figure 2-79(PDF p.243 起):Completion Header、Status、Byte Count、Requester ID、Tag 和 Lower Address。
  4. AMD,PG054 v3.3:7 Series FPGAs Integrated Block for PCI Express,Chapter 4 的TLP Format on the AXI4-Stream Interface与Basic TLP Transmit/Receive Operation(PDF p.45~48):64 位接口的字节顺序、tkeep、tlast 和握手规则。
  5. NVM Express,NVMe over PCIe Transport Specification Revision 1.4,§3.8、Figure 20/21(PDF p.19):NVMe 控制器使用 MLBAR/BAR0 和 MUBAR/BAR1 提供控制器寄存器内存空间基地址。
  6. NVM Express,NVM Express Base Specification Revision 2.4,§3.1.4、Figure 33/34(PDF p.78~80):CAP、VS、CC、CSTS、Admin Queue 属性和 Doorbell 的寄存器 Offset。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进