基于
fpga/nv_hsb_ip实际 RTL 代码分析:
dp_pkt/dp_pkt_top.sv(数据面包头构造)roce/roce_ack.sv(RC 响应端,可选)roce/roce_icrc.sv(ICRC 计算)rx_parser/rx_parser_pkg.sv/rx_parser/rx_parser.sv(头部解析结构体)
一、RoCEv2 包结构
1. 整体帧格式(传感器 → 主机数据面,dp_pkt_top.sv中hdr_roce构造)
代码位置:dp_pkt/dp_pkt_top.svL1148-1163,ROCE_HDR_WIDTH = 224 + 336 = 560 bit = 70 Byte
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ┌───────────────────────────────────────────────────────────────┐ ─┐ │ Destination MAC (hif_mac_addr, 6B) │ │ ├──────────────────────────────┬────────────────────────────────┤ │ │ Source MAC (前4B) │ Source MAC (后2B) │ │ Ethernet ├──────────────────────────────┴────────────────────────────────┤ │ 14B │ EtherType = 0x0800 (IPv4) │ Ver=4/IHL=5 │ DSCP/ECN│ │ ├───────────────────────────────────────┴───────────┬───────────┤ ─┘ │ IP Total Len = dma_len+60(+64带Imm) │ ID=0 │Flg=DF/Frag│ ─┐ ├───────────┬───────────┬───────────────────────────┴───────────┤ │ │ TTL=0x40 │ Proto=0x11│ IP Header Checksum (RTL实时计算) │ │ IPv4 ├───────────────────────────────┬───────────────────────────────┤ │ 20B │ Source IP Address │ Destination IP Address │ │ ├───────────────────────────────┼───────────────────────────────┤ ─┘ │ UDP Src Port (寄存器可配) │ UDP Dst Port = 0x12B7 (4791) │ ─┐ UDP ├───────────────────────────────┼───────────────────────────────┤ │ 8B │ UDP Length = dma_len+40(+44) │ UDP Checksum = 0 (不用) │ ─┘ ├───────┬───────┬───────────────┴───────┬───────────────────────┤ ─┐ │Opcode │SE/M/P/│ P_Key = 0xFFFF │ Rsvd │ Dest QP(24b) │ │ │0x2A/2B│ TVer │ │ │ (高16b) │ │ BTH ├───────┴───┬───┴───────────┬───────────┴───────┬───────────────┤ │ 12B │ DestQP低8b│ AckReq=0/Rsvd │ PSN (24b, 每QP递增计数器) │ │ ├───────────┴───────────────┴───────────────────┴───────────────┤ ─┘ │ RETH: Virtual Address (64b, GPU显存地址) │ ─┐ ├───────────────────────────────────────┬───────────────────────┤ │ RETH │ RETH: R_Key (32b) │ DMA Length = {16'h0, │ │ 16B │ │ dma_len(16b)} │ │ ├───────────────────────────────────────┴───────────────────────┤ ─┘ │ [ImmDt 4B] 仅 opcode=0x2B: {PSN[23:0], 6'h0, buf_ptr[1:0]} │ ─┐ 可选 ├───────────────────────────────────────────────────────────────┤ │ │ [Metadata 48B] 带Imm包: md_flags/PSN/frame_crc/SOF_PTP/ │ │ (记入 │ buf_addr/frame_num/当前PTP时间戳 │ │ dma_len) ├───────────────────────────────────────────────────────────────┤ │ │ │ │ │ Sensor Payload (图像/传感器数据, ≤MTU) │ │ │ │ │ ├───────────────────────────────────────────────────────────────┤ ─┘ │ ICRC (4B, Invariant CRC-32, roce_icrc.sv 计算) │ └───────────────────────────────────────────────────────────────┘ (最后由 MAC 再追加以太网 FCS 4B)2. 关键字段说明
| 字段 | 值/来源 | 说明 |
|---|---|---|
| EtherType | 16'h0800 | IPv4 |
| IP Protocol | 8'h11(17) | UDP |
| UDP Dst Port | 0x12B7=4791 | RoCEv2 标准端口(roce_ack.svL493 注释确认) |
| UDP Checksum | 16'h0 | RoCEv2 允许置 0 |
| Opcode | 8'h2A/8'h2B | 见第二节 |
| SE/MigReq/PadCnt/TVer | 全 0 | 无请求应答事件、无迁移、无填充 |
| P_Key | 16'hFFFF | 默认分区 |
| Dest QP | roce_dest_qp,寄存器配置 | 每路 sensor 一个 QP |
| AckReq | 0 | 不要求对端回 ACK |
| PSN | 每 QP 24bit 计数器(dp_pkt_top.svL408/418),普通包每包 +1 | 同时被 host 软件用于丢包检测 |
| RETH VA | {roce_buf_fixed_msb, vaddr<<7} | GPU 显存目标地址,环形 buffer 翻页 |
| RETH R_Key | 寄存器配置 | host 注册内存时下发 |
| DMA Length | {16'h0, dma_len} | 含 metadata + payload |
| ICRC | roce_icrc.sv:按 RoCE 规范把 TOS/TTL/IP校验和/UDP校验和/BTH F/B 位掩码为全 1 后做 CRC-32 | Invariant CRC |
带 Immediate 的包(hdr_type=2'b10,帧尾/元数据上报用)在 RETH 后依次插入:
- ImmDt 4B:
{psn[19:0], buf_ptr[11:0]}(BUFFER_4K_REG模式)——把"数据写到哪个 buffer"随包通知 host - Metadata 48B(384bit,
dp_pkt_top.svL1303):
wr_imm_data = {md_flags, 8'h0, psn, frame_crc, 16'h0, sof_ptp, roce_val_addr, 16'h0, frame_num, 16'h0, cur_ptp};即帧 CRC、SOF/当前 PTP 时间戳、帧号、buffer 地址等。
BTH 结构体定义(rx_parser/rx_parser_pkg.svL67-75):
typedef struct { logic [7:0] opcode; // 操作码 logic [7:0] s_m_pad_tver; // SE | MigReq | PadCnt | TVer logic [15:0] pkey; // P_Key logic [7:0] f_b_rsv6; // F/B/Rsvd6 logic [23:0] dest_qp; // Destination QP logic [7:0] a_rsv7; // AckReq | Rsvd7 logic [23:0] psn; // Packet Sequence Number } bth_hdr;二、链路类型:不是 RC,数据面是 UC(Unreliable Connection)
判定依据
IB/RoCE opcode 编码规则:opcode[7:4] 区分传输类型,0x0=RC,0x2=UC。
dp_pkt_top.svL1146:
assign opcode = (pkt_is_imm || is_data_wr_imm) ? 8'h2B : 8'h2A;对照 IB 规范 opcode 表:
| Opcode | 含义 | 传输类型 |
|---|---|---|
| 0x0A / 0x0B | RDMA Write Only / Write Only w/ Imm | RC(未采用) |
| 0x2A / 0x2B | RDMA Write Only / Write Only w/ Imm | UC(本设计采用) |
代码自身定义印证(roce_ack.svL92-93):
localparam BTH_UC = 4'h2; // opcode[7:4] = 2 → UC localparam BTH_RC = 4'h0; // opcode[7:4] = 0 → RC为什么是 UC 而不是 RC
- 单向高吞吐图像流:FPGA 只做 RDMA Write 把传感器数据直接写进 GPU 显存环形 buffer,不需要对端回 ACK;
- BTH 中AckReq=0、Solicited Event=0,FPGA 侧没有任何等待/处理 ACK 的逻辑;
- 可靠性由上层保证:PSN + ImmDt 中的 buf_ptr + Metadata(frame_crc、帧号、PTP 时间戳)随包带给 NVIDIA host 软件,由软件检测丢包/乱序,而不是 IB 硬件重传;
- 无需建立 QP 连接状态机(无 CM/handshake),FPGA 上电配好寄存器(dest_qp、rkey、buffer 地址)即可发包。
补充:RX 方向有可选的 RC 响应端
下行(host → sensor 的寄存器写)在定义了`ifdef SIF_TX_ROCE_RC_ENABLE(HOLOLINK_top.svL377)时,FPGA 通过roce/roce_ack.sv扮演RC Responder:
Host (RC Requester) FPGA (RC Responder) │ RDMA Write/Send (opcode[7:4]=0x0) │ │──────────────────────────────────▶│ roce_ack.sv: 每QP跟踪 ePSN (L307) │ │ 乱序→丢弃/NAK, 重复→丢弃 │ ACK: opcode=0x11 + AETH(4B) │ │◀──────────────────────────────────│ AETH = {syndrome, MSN} │ ETH+IP+UDP+BTH(12B)+AETH+ICRC │ syndrome: 0x00=ACK(credit disable) │ │ 0x20+=RNR NAK / 0x60+=NAK seq err即:roce_ack.sv只对RC 入向包(opcode[7:4]==BTH_RC,L234/358/367)生成 ACKNOWLEDGE 响应包(ETH+IPv4+UDP+BTH+AETH+ICRC,L482-500),支持 PSN 检查、RNR NAK、错误注入测试。
三、总结
| 方向 | 模块 | 包格式 | 传输类型 |
|---|---|---|---|
| Sensor→Host(图像数据上行) | dp_pkt/dp_pkt_top.sv | ETH+IPv4+UDP(4791)+BTH+RETH+[ImmDt+Metadata]+Payload+ICRC | UC RDMA Write Only (0x2A/0x2B) |
| Host→Sensor(控制下行,可选) | roce/roce_ack.sv | 应答包 ETH+IPv4+UDP+BTH(opcode 0x11)+AETH+ICRC | RC(仅作 responder 回 ACK/NAK/RNR) |
核心结论:nv_hsb_ip 数据面的 RoCEv2 是 UC(Unreliable Connection)类型的 RDMA Write(带可选 Immediate),不是 RC;RC 只作为可选编译项(SIF_TX_ROCE_RC_ENABLE)用于下行的 ACK 响应。
这符合 Holoscan Sensor Bridge 的设计哲学——传感器数据流用无连接、无重传的 UC 达到最低时延,可靠性检测上移到 host 软件。
2. LA 数据读取
是否需要 GUI Vivado?
结论先行:不需要。
- 本
pynq/rfsoc-pynq工程中的 “ILA” 是 NVIDIA 自研的s_apb_ila(APB 寄存器 + 片上捕获 RAM),不是 Xilinx ILA IP core,运行阶段用 Python 经以太网控制面读取即可,完全不需要 Vivado(GUI/batch 都不需要);- 即便是一般项目中真正的 Xilinx ILA core,也不必须 GUI——可用免费的 Vivado Lab Edition +
hw_server+ Tcl 脚本在纯命令行/批处理模式经 JTAG 抓取数据(见本文第四节)。
一、本工程里的 “ILA” 是自研 APB 模块
pynq/rfsoc-pynq/rtl/top/FPGA_top.sv中例化的两个 ILA 均为nv_hsb_ip/lib_apb/s_apb_ila.sv(APB 从机 + 片上 RAM,非 XilinxilaIP):
| 实例 | 挂载 | 数据宽度/深度 | 捕获内容 | 触发/写使能 |
|---|---|---|---|---|
u_apb_ila | apb_psel[2] | 256bit × 512 | PTP 时间戳、frame_cnt、sof/eof、标记0x123456789ABCDEF、计数器 | trigger=sof |
u_apb_sif_ila | apb_psel[3] | 585bit × 512 | 整拍 AXIS:tdata[511:0]+tvalid+tlast+tkeep 计数+PTP sec/nsec | wr_en=sif_tx_axis_tvalid[0] |
工作机制(s_apb_ila.svFSM:ILA_IDLE → ILA_CAPTURE → ILA_DONE):
- 写
ctrl_reg[0].bit0 = 1(trigger_en)启动捕获; - 等待
i_trigger,随后每个i_wr_en采样一个数据写入 RAM(s_apb_ram_dyn); - 采满
DEPTH=512个样本后置fsm_done=1; - 通过 APB 读 RAM 窗口取出数据。
二、访问通道:以太网控制面,不经过 PS/JTAG
- 该设计为PL-only:
build/ip/bd_gen.tcl中只有 CMAC + clk_wiz,寄存器通路不含 Zynq PS; - ILA 挂在
HOLOLINK_top的User Reg APB 接口(o_apb_psel/paddr/...)上,其 APB 主机来自内部u_apb_intc(APB 互连),master 是ECB(以太网控制桥)——host 通过UDP 寄存器读写包(Holoscan Sensor Bridge 标准控制协议)访问; - 因此在 PYNQ(或任何能连通板卡的 PC)上用Python(NVIDIA hololink 库 / socket 发 UDP 控制包)即可 dump ILA 数据;PYNQ 框架本身只负责加载 bitstream。
三、s_apb_ila寄存器/存储器映射与读出流程
基址 = 该 psel 实例的 User Reg 地址(由 Hololink 枚举/寄存器映射决定),模块内部偏移:
| 偏移 | 名称 | 含义 |
|---|---|---|
0x00 | ctrl_reg[0] | bit0 = trigger_en,bit1 = ila_rst(清 RAM) |
0x04 | ctrl_reg[1] | 通用输出o_ctrl_reg |
0x08 | stat_reg[0] | bit0 = fsm_busy,bit1 = fsm_done,bit2 = i_trigger 状态 |
0x0C | stat_reg[1] | 当前 sample_addr |
1 << W_OFSET | RAM 窗口 | 见下 |
RAM 窗口基址W_OFSET = clog2(DEPTH) + clog2(ceil(W_DATA/32)) + 2:
- ILA#1(
u_apb_ila,256bit):W_OFSET = 9+3+2 = 14→ RAM 在偏移0x4000,512 样本 × 8 个 32bit 字,样本i第k字地址 =0x4000 + (i*8+k)*4; - ILA#2(
u_apb_sif_ila,585bit):W_OFSET = 9+5+2 = 16→ RAM 在偏移0x10000,512 样本 × 19 个有效 32bit 字(步长 32 字)。
读出流程:
写 [base+0x00] = 0x1 # trigger_en = 1 轮询 [base+0x08] 直到 bit1=1 # 等待 fsm_done 读 RAM 窗口 512 个样本 写 [base+0x00] = 0x0 # 回 IDLE(或 bit1=1 清 RAM 重采) Python 解析/绘图四、通用情况:真正的 Xilinx ILA,无 GUI 的 JTAG/Tcl 读法
即便设计中例化了 Xilinx ILA core,也不需要 Vivado GUI。可用Vivado Lab Edition(免费,无需 license)的硬件服务器 + Tcl 全流程命令行完成:
1. 启动 hw_server(板卡 JTAG 所连机器上)
hw_server&# 默认监听 3121;也可由 Tcl 内 connect_hw_server -url localhost:3121 自动拉起2. Tcl 抓取脚本read_ila.tcl
# ---- 连接 ---- open_hw_manager connect_hw_server -url localhost:3121 open_hw_target ;# 或: open_hw_target -jtag_mode ... # 选择器件(按实际 JTAG 链上的器件名,如 xczu48dr_0 / xcku060_0) set dev [lindex [get_hw_devices] 0] current_hw_device $dev refresh_hw_device $dev # ---- 关联探针文件 (.ltx,由 write_debug_probes 生成;若已随 bitstream 关联可省略) ---- set_property PROBES.FILE {design.ltx} $dev set_property FULL_PROBES.FILE {design.ltx} $dev # 若需先下载: program_hw_devices $dev # ---- 配置 ILA ---- set ila [lindex [get_hw_ilas -of_objects $dev] 0] set_property CONTROL.TRIGGER_POSITION 256 $ila ;# 触发点在窗口中的位置 set_property CONTROL.WINDOW_COUNT 1 $ila set_property CONTROL.DATA_DEPTH 512 $ila # 触发条件示例:probe "sof" == 1 set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes sof -of_objects $ila] # ---- 布防并等待触发 ---- run_hw_ila $ila wait_on_hw_ila $ila ;# 阻塞直到触发完成;可加 -timeout # ---- 上传 & 导出 ---- set data [upload_hw_ila_data $ila] current_hw_ila_data $data write_hw_ila_data -csv_file ila_capture.csv $data # 也可直接读属性: report_property $data # ---- 收尾 ---- close_hw_target disconnect_hw_server close_hw_manager3. 批处理运行
vivado-modebatch-sourceread_ila.tcl# 或用 Lab Edition:# vivado_lab -mode batch -source read_ila.tcl要点:
- GUI 从来不是必须的;GUI(Hardware Manager)只是交互最方便的方式;
- 无 GUI 时必需的两样:JTAG 连接(本地或远程
hw_server)+ 免费的 Vivado Lab Edition 运行时; .ltx探针文件在实现阶段用write_debug_probes生成,离线分析(Python/pandas 读 CSV)不依赖任何 Vivado;- 本仓库的替代方案(
s_apb_ila走以太网寄存器面)进一步连 JTAG 和 Lab Edition 都省掉了,更适合 PYNQ 这类无头(headless)远程调试场景。
五、总结
| 场景 | 是否需要 GUI Vivado | 需要什么 |
|---|---|---|
本 pynq 工程(s_apb_ila) | 否(连 Vivado 都不需要) | Python + 以太网(Hololink 寄存器协议) |
| 一般 Xilinx ILA,交互调试 | 否(GUI 可选) | Vivado Lab Edition + JTAG + Tcl 批处理 |
| 一般 Xilinx ILA,完全脚本化 | 否 | vivado_lab -mode batch -source xxx.tcl+ CSV 离线分析 |