ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

RoCE V2 Read Request 数据包全解剖:ACK、NAK 与握手流程一文讲透

RoCE V2 Read Request 数据包全解剖:ACK、NAK 与握手流程一文讲透

RoCE V2 Read Request 数据包全解剖:ACK、NAK 与握手流程一文讲透

做 RDMA 绕不开 RoCE V2,做 RoCE V2 绕不开 Read Request。

本文从数据包结构出发,把 Read Request、ACK、NAK 三种包的字段逐一拆解,再结合 FPGA 硬件实现讲清楚通信握手全流程。读完这篇,你应该能对着 Wireshark 抓包或 RTL 波形,独立排查 RoCE Read 事务的每一个细节。


一、先搞清楚角色:Requester 与 Responder

RoCE V2 的 RDMA Read 操作涉及两端:

角色职责类比
Requester(发起方)发送 Read Request,等待数据回填像 HTTP 客户端发 GET 请求
Responder(响应方)收到 Read Request,从本地内存读数据,分包回传像 HTTP 服务器返回响应体

关键点:Read 操作的数据流向是Responder → Requester,但控制流的发起者是 Requester。这一点和 Write 操作正好相反。


二、Read Request 数据包结构

Read Request 是 RoCE V2 中最"重"的控制包之一,它携带了 Responder 执行读操作所需的全部信息。

2.1 包格式总览

┌─────────┬─────────┬─────────┬─────────┬─────────┐ │ BTH │ RETH │ ICRC │ │ │ │ (12B) │ (16B) │ (4B) │ │ │ └─────────┴─────────┴─────────┴─────────┴─────────┘ Word0 Word3 Word6 Word1 Word4 Word2 Word5

2.2 BTH(Base Transport Header)— 12 字节

BTH 是所有 RoCE V2 包的公共头部,Read Request 也不例外。

Word位域字段含义
Word 0[31:24]Opcode0x0C— RDMA Read Request
[23]SSolicited Event(通常为 0)
[22:20]Mig迁移标志(通常为 0)
[19:16]Pad尾部填充字节数
[15:10]TVer传输版本号(通常为 0)
[9:0]Partition Key分区键,常用0x8001
Word 1[31]F/R保留位
[30:28]保留—
[27:24]保留—
[23:0]QP Number目标 QP 号,Responder 据此匹配连接
Word 2[31]AAcknowledge Request 位(Read 必须为 1)
[30:24]保留—
[23:0]PSN起始包序列号,整个 Read 事务的锚点

重点字段:

  • Opcode = 0x0C:唯一标识这是 Read Request
  • QP Number:决定哪个 Responder 连接处理此请求
  • PSN:后续所有响应包的序列号起点

2.3 RETH(RDMA Extended Transport Header)— 16 字节

RETH 是 Read Request 特有的扩展头部,告诉 Responder “从哪里读、读多少”。

Word位域字段含义
Word 3[63:0]Virtual Address目标虚拟地址,Responder 的内存偏移
Word 4[31:8]R_Key远程内存注册键,用于权限验证
[7:0]保留—
Word 5[31:0]DMA Length请求读取的字节数
Word 6[31:0]保留Read Request 中未使用

重点字段:

  • Virtual Address:Responder 根据此地址计算 DMA 读取的起始位置
  • R_Key:安全机制,防止越权访问(轻量级 FPGA 实现中可能省略验证)
  • DMA Length:决定了 Responder 需要回传多少数据

三、ACK 与 NAK 数据包

Read 事务完成后,Requester 需要确认收到数据。确认机制通过 ACK 或 NAK 包实现。

3.1 ACK/NAK 共用 Opcode

冷知识:ACK 和 NAK 使用相同的 Opcode——0x11(Acknowledge)。

这意味着你不能仅凭 BTH Word 0 的 opcode 字段来区分 ACK 和 NAK。

3.2 AETH 头部 — 区分 ACK/NAK 的关键

ACK/NAK 包在 BTH 之后紧跟一个AETH(Acknowledge Extended Transport Header),4 字节:

Word 3: { Syndrome[7:0], Reserved[7:0], MSN[15:0] }
字段位宽含义
Syndrome8 bitACK/NAK 标志 + 语义值
Reserved8 bit保留
MSN16 bitMessage Sequence Number

3.3 Syndrome 字段解码规则

Syndrome 最高位(bit 7)是 ACK/NAK 的分水岭:

Syndrome[7]Syndrome 范围类型值的含义
00x00~0x7FACK值 = 接收方返回的credits 数量
10x80~0xFFNAK值 =NAK 原因代码

常见 NAK 原因代码:

代码含义
0x80RNR(Receiver Not Ready)— 接收方资源不足
0x81远程访问错误 — 地址或权限问题
0x82远程无效请求

3.4 PSN 的位置

无论是 ACK 还是 NAK,PSN 始终位于 BTH Word 2 的低 24 位:

  • ACK 的 PSN:表示"我已成功收到 PSN 及之前的所有包"
  • NAK 的 PSN:表示"请从 PSN 指定的包开始重传"

3.5 ACK/NAK 包完整格式

ACK 包: ┌─────────┬─────────┬─────────┬─────────┬─────────┐ │ BTH │ BTH │ BTH │ AETH │ ICRC │ │ Word 0 │ Word 1 │ Word 2 │ Word 3 │ Word 4 │ │ 0x11 │ QP Num │ PSN │ Syndrome│ │ └─────────┴─────────┴─────────┴─────────┴─────────┘ NAK 包: 结构完全相同,仅 Syndrome[7]=1

四、通信握手全流程

Read 事务的完整握手流程涉及多个来回,下面从时序角度逐步拆解。

4.1 正常流程(无 NAK)

Requester Responder │ │ │─── Read Request (0x0C) ───►│ ① 发起请求 │ [VA, R_Key, DMA_Len] │ │ │── 解析 RETH │ │── 计算 DMA 地址 │ │── 从本地内存读数据 │ │── 分包:每包 ≤ 4096B │ │ │◄── Read Response (0x0D) ───│ ② 第一包数据 │ [PSN=起始PSN] │ │◄── Read Response (0x0D) ───│ ③ 第二包数据 │ [PSN=起始PSN+1] │ │◄── ... │ │◄── Read Response (Last) ───│ ④ 最后一包 │ │ │─── ACK (0x11) ────────────►│ ⑤ 确认收到 │ [Syndrome=credits] │ │ │── resp_done │ │

4.2 带 NAK 的重传流程

Requester Responder │ │ │─── Read Request ──────────►│ ① 发起请求 │ │── DMA 读 + 分包发送 │◄── Read Response #0 ───────│ ② 第一包 │◄── Read Response #1 ───────│ ③ 第二包(传输错误/丢包) │ │ │─── NAK (0x11) ────────────►│ ④ 请求重传 │ [Syndrome=0x80+, │ │ PSN=重传起始PSN] │ │ │── 收到 NAK │ │── 重新 DMA 读 │ │── 重新发包头 + 数据 │◄── Read Response #1 ───────│ ⑤ 重传第二包 │◄── Read Response #2 ───────│ ⑥ 继续后续包 │ │ │─── ACK ───────────────────►│ ⑦ 确认 │ │

4.3 FPGA 硬件状态机实现

在实际 FPGA 实现中,Responder 端的握手由状态机驱动。以下是典型的五状态设计:

┌──────────┐ ┌────────►│ S_IDLE │◄──────────────┐ │ │ 等待输入 │ │ │ └────┬─────┘ │ │ │ Read Request/NAK │ │ │ + fifo_space_ok │ │ ▼ │ │ ┌──────────┐ │ │ │ S_DMA │ │ │ │ 发起DMA读 │ │ │ └────┬─────┘ │ │ │ rctrl_done │ │ ▼ │ │ ┌──────────┐ │ │ │ S_HDR │ │ │ │ 发包头5W │ │ │ └────┬─────┘ │ │ │ hdr 全发完 │ │ ▼ │ │ ┌──────────┐ │ │ │ S_NEXT │── 还有包 ──────┘ │ │ 检查下一包│ │ └────┬─────┘ │ │ 全部发完 │ ▼ │ ┌──────────┐ │ │S_WAIT_ACK│── NAK ──► 回到 S_DMA 重传 │ │ 等确认/超时│ │ └────┬─────┘ │ │ 超时 / ACK └──────────────┘ resp_done

各状态职责:

状态动作退出条件
S_IDLE等待输入 FIFO 命令,检查fifo_space_ok收到 Read Request 或 NAK,且 FIFO 有空间
S_DMA发起 DDR 读命令,传输大小 = min(4096, 剩余长度)rctrl_done脉冲
S_HDR发送 5 个 Word:Payload_Size + BTH(3W) + AETH(1W)hdr_tlast握手完成
S_NEXT包索引 +1,判断是否还有后续包有包→S_DMA;无包→S_WAIT_ACK
S_WAIT_ACK启动超时计时器,等待 ACK 或 NAK超时→resp_done;NAK→S_DMA 重传

4.4 握手关键细节

1. inp_ready 的流控逻辑

assign inp_ready = (state == S_IDLE) && fifo_space_ok;
  • 只有 S_IDLE 且 FIFO 有空间时才拉高inp_ready
  • NAK 也受背压约束:FIFO 满时,NAK 保留在输入 FIFO 中不消费
  • 这避免了在 DDR 数据尚未读出时误接收新命令

2. DMA 传输大小计算

// 非最后一包: 4096 字节 // 最后一包: total_len % 4096 (若为 0 则仍为 4096) wire [12:0] dma_xfer_size = (next_pkt_idx == r_num_pkts - 1) ? (r_total_len[12:0] == 0 ? 4096 : r_total_len[12:0]) : 4096;
  • 单包事务(num_pkts=1)时,首包即末包,需特殊处理
  • 硬编码 4096 会在数据不足时发送错误内容

3. PSN 递增规则

  • Read Response 的 PSN = 起始 PSN + 包索引
  • 每发一包,PSN 自增 1
  • NAK 重传时,PSN 回退到指定值重新发送

4. 超时机制

localparam ACK_TIMEOUT_CYCLES = 125000; // 1ms @ 125MHz
  • S_WAIT_ACK 状态启动计数器
  • 超时未收到 ACK/NAK → 认为事务完成,拉高resp_done
  • 超时值需根据网络 RTT 合理设置

五、包大小与分包策略

5.1 为什么是 4096 字节?

RoCE V2 Read Response 的单包最大 Payload 通常为4096 字节(4KB),原因:

  • 与内存页大小对齐,方便 DMA 对齐访问
  • 以太网 MTU(1500B)限制下,4KB 数据需要 IP 分片,但 RDMA 网卡/硬件会自动处理
  • 4KB 是 FPGA 内部 FIFO 和 AXI Burst 的自然边界

5.2 分包数量计算

num_pkts = ceil(DMA_Length / 4096)

例如:

  • DMA_Length = 8192 → 2 包
  • DMA_Length = 10000 → 3 包(前两包 4096B,末包 1808B)
  • DMA_Length = 4096 → 1 包

5.3 包头结构(FPGA 实现)

每包响应前需发送 20 字节包头(5 个 32-bit Word):

Word内容说明
Word 0{19'd0, pkt_payload}本包 Payload 大小
Word 1BTH Word 0Opcode =0x0D(Read Response)
Word 2BTH Word 1QP Number
Word 3BTH Word 2PSN
Word 4AETHSyndrome + MSN(hdr_tlast)

六、实战调试要点

6.1 用 Wireshark 抓包看什么

  1. Opcode 字段:确认0x0C(Read Request)和0x0D(Read Response)
  2. PSN 连续性:Response 的 PSN 应逐包递增,若出现跳跃说明有丢包
  3. AETH Syndrome:
    • 0x00~0x7F→ 正常 ACK
    • 0x80+→ NAK,关注原因码
  4. DMA Length:与预期传输大小对比,不一致说明 RETH 解析有误

6.2 FPGA 波形调试检查清单

  • inp_valid && inp_ready同拍有效,确认握手成功
  • rctrl_start只拉高 1 拍,rctrl_done在预期时间内返回
  • hdr_tvalid && hdr_tready每拍握手,5 个 Word 连续发出
  • tx_fifo_empty在最后一包发出后拉高
  • resp_done脉冲宽度 = 1 个时钟周期
  • NAK 到达时,若state != S_IDLE,inp_ready应为 0

6.3 常见坑

问题根因解决
首包数据错误DMA 大小硬编码 4096,实际不足动态计算min(4096, remaining)
NAK 后状态混乱NAK 在 FIFO 满时被误消费inp_ready必须包含fifo_space_ok
PSN 不连续包索引计算错误PSN = start_psn + pkt_idx
超时不触发计数器位宽不够或条件错误确认ACK_TIMEOUT_CYCLES与时钟频率匹配

七、总结

RoCE V2 Read 事务的核心可以用三句话概括:

  1. Read Request 是"订单":告诉 Responder 从哪读、读多少、用哪个 QP
  2. Read Response 是"发货":按 4KB 分包,每包带独立 PSN,包头含 BTH + AETH
  3. ACK/NAK 是"签收/拒收":共用 Opcode0x11,靠 Syndrome 最高位区分;NAK 触发重传

在 FPGA 硬件实现中,状态机的核心设计原则是:先 DMA 读数据到 FIFO,再发包头通知下游,整个流程受fifo_space_ok流控保护,确保不丢命令、不发错数据。


作者:FPGA 工程师实战笔记 | 基于安路 PH1 FPGA 验证

参考工程:gitee.com/mstar_ypc/mstar-collect-fpga

技术博客:CSDN - 幻智星

RELATED READING

延伸阅读

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