基于 PTP 的 FPGA→RoCE→CPU 单向延迟测量实验方案

1. 原实验的时间戳

论文 FPGA 收/发包 PTP 时间戳的产生机制分析

针对论文 arXiv:2510.25213v2 §2.4 "Network proof of concept" 的一个关键细节:

该方案没有使用主机 PTP 同步 。本文结合

holoscan-sensor-bridge/fpga/nv_hsb_ip/(HSB IP)与

holoscan-sensor-bridge/fpga/pynq/rfsoc-pynq/(论文所用 RFSoC-PYNQ 参考设计)

的 RTL 代码,回答一个问题:

FPGA 收到 GPU 环回的数据包时,收包 PTP 时间戳是从哪里来的?

1. 结论(先说答案)

论文中的"PTP 时间戳"来自 FPGA 内部一个自由运行(free-running)的本地 PTP
格式定时器
------即 HSB IP 的 ptp_timer 模块(fpga/nv_hsb_ip/ptp/ptp_timer.sv)。

  • 发包时间戳 T1ram_player 数据发生器在发出每个数据 beat 的时刻,采样该
    本地定时器的当前值,写进 payload(80-bit PTP + 12-bit 包号)。
  • 收包时间戳 T4 :环回包经 HSB IP 的 rx_parser 剥离 RoCE 头部、把 payload
    交付到 Sensor TX AXI 流的那一拍,顶层设计把同一个本地定时器的当前值 连同
    从 payload 里还原出的 T1 和包号一起锁存进 ILA。

T1 和 T4 采样自同一个 FPGA 本地定时器 ,RTT = T4 − T1 是单时钟域内的
差分测量
:绝对时刻准不准无关紧要,偏移在相减时完全抵消,µs 级测量窗口内

晶振漂移也可忽略(ppm 级 × 4 µs ≪ 1 ns)。因此根本不需要与主机做 PTP
同步
------"PTP"在论文里承担的角色是"纳秒级分辨率的时间戳格式/发生器 ",

而不是"IEEE 1588 时钟同步协议"。主机侧的 ptp4l/phc2sys 只在做跨设备单向

测量时才需要(例如本仓库 latency_measurement.md 中设计的 FPGA→CPU 单向

方案)。

2. 证据链:从定时器到 ILA 的完整路径

2.1 本地 PTP 定时器:复位即自增,无需任何主机

fpga/nv_hsb_ip/ptp/ptp_timer.sv

systemverilog 复制代码
localparam [W_NS:0] default_inc = 10**9 * (2**W_FRAC_NS) / PTP_CLK_FREQ; // 每拍纳秒数(Q24定点)

always_ff @(posedge i_pclk) begin
  if (i_prst) begin
    sec <= 0;  nano_sec <= 0; ...          // 复位后从 0 开始
  end
  else begin
    sec      <= i_sync_ts_vld ? i_sync_ts[79:32] :      // 可选:收到Sync才锁存主机T1
                 nano_sec_gt_bil ? sec_inc : sec;
    nano_sec <= i_sync_ts_vld ? i_sync_ts[31:0]  :      // 否则每拍 += inc
                 nano_sec_gt_bil ? nano_sec_sub_bil : nano_sec_inc[...];
  end
end
assign o_sec      = {16'd0, sec};                       // 48-bit 秒
assign o_nano_sec = {2'h0, nano_sec[W_NS-1:W_FRAC_NS]}; // 32-bit 纳秒

要点:

  • 复位释放后,定时器每个 i_pclk 自增 10^9/PTP_CLK_FREQ ns(pynq 设计中
    PTP_CLK_FREQ = 100 MHz,即每拍 10 ns,内部另有 24-bit 小数纳秒累加,
    输出四舍五入到 1 ns)。它是一个无需任何外部输入的纳秒级单调计数器
  • i_sync_ts_vld(Sync 锁存)与 i_fa_adj(DPLL 频率微调)是可选的同步
    附加功能
    ;不同步时定时器照常工作。这正对应论文的表述:"a Precision Time
    Protocol (PTP) time stamp generator was used, to produce nanosecond level
    time values"------用的是它的时间戳生成能力,不是同步能力。

定时器由 ptp_top 包装(fpga/nv_hsb_ip/ptp/ptp_top.sv,使能端

i_enable = init_done_hif_clk),经 HOLOLINK_top 引出(o_ptp_sec /

o_ptp_nanosec,见 fpga/nv_hsb_ip/top/HOLOLINK_top.sv:681 附近)。

2.2 时间流入数据通路时钟域:streaming_cdc

定时器工作在 100 MHz ptp_clk 域,而数据通路在 322.265625 MHz usr_clk

域(HIF_CLK_FREQ)。fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv:258-270

systemverilog 复制代码
streaming_cdc #(.DATA_WIDTH(80), .SRC_FREQ(`PTP_CLK_FREQ), .DST_FREQ(`HIF_CLK_FREQ))
u_ptp_hif_cdc (
  .i_src_clk(ptp_clk), .i_dst_clk(usr_clk),
  .i_src_data({ptp_sec, ptp_nsec}),                 // 80b = {48b sec, 32b nsec}
  .o_dst_data({ptp_sec_sync_usr, ptp_nsec_sync_usr}),
  .o_dst_valid(ptp_sync_usr_valid)
);

fpga/nv_hsb_ip/misc/streaming_cdc.sv约 99% 目标时钟频率 的速率把源时钟

域的时间值持续刷新进目的时钟域(带流控的异步 FIFO 推送)。于是 usr_clk

的任何逻辑在任意一拍都能读到"当前 PTP 时间",陈旧度最坏约一个源时钟周期

(~10 ns)加几拍同步延迟;且该陈旧度对下面 T1、T4 是共模 的,在差值中

基本抵消。

2.3 发包时间戳 T1:ram_player 在 beat 握手时刻采样

fpga/nv_hsb_ip/data_gen/ram_player.sv(APB 基址 0x5000_0000):

systemverilog 复制代码
// i_ptp = {ptp_sec_sync_usr, ptp_nsec_sync_usr}  (FPGA_top.sv:404)
assign ptp = ((o_axis_tvalid && !tvalid_q) || (tready_q)) ? i_ptp : ptp_q; // 握手沿采样
...
assign o_axis_tdata = ptp_ena ? {'0, window_cnt[11:0], ptp} : axis_tdata;  // PTP 模式

ptp_ena(enable 寄存器 0x5000_0004 bit1)置位时,每个 512-bit beat 的

低 92 bit 为 {80b PTP, 12b window_cnt}------即论文"96-bit PTP + 16-bit packet

number"payload 的 HSB 实现版本。T1 = 数据离开发生器、进入 HSB 打包流水线的
那一拍的本地 PTP 时间
(Sensor RX0 入口)。

2.4 收包路径:剥掉 RoCE 头,payload 交付到 Sensor TX 流

环回包返回 FPGA 后:

复制代码
ETH MAC → HSB IP rx_parser (校验并按 BTH dest QP/报文类型路由,
            剥离 Eth/IP/UDP/BTH/RETH 头)
        → Sensor TX AXI 流  o_sif_axis_*[0]  (FPGA_top.sv:469-474)

即论文所述:"on the receive side, it detaches the RoCE headers and delivers

the payload to the FPGA"。payload 原样(含 T1 与包号)出现在

sif_tx_axis_tdata[0] 上。

2.5 收包时间戳 T4:顶层设计在交付拍锁存并存入 ILA

fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv:234-312 是本问题的核心逻辑------

该参考设计内置了论文的完整测量结构

systemverilog 复制代码
// (a) 交付拍:从环回 payload 中还原 T1 与包号
always_ff @(posedge usr_clk) begin
  ptp_ts    <= (sof) ? sif_tx_axis_tdata[0][79:0]  : ptp_ts;   // T1(payload内嵌)
  frame_cnt <= (sof) ? sif_tx_axis_tdata[0][91:80] : frame_cnt; // 12b包号
  ptp_ts_en <= sof;
end
assign sof = sif_tx_axis_tvalid[0];
assign eof = sif_tx_axis_tlast[0];

// (b) ILA 记录:T1、T4(当前本地PTP时间)、包号、标志位、序号
assign ila_wr_data[63:0]    = ptp_ts[63:0];                                     // T1 = {sec[31:0],nsec[31:0]}
assign ila_wr_data[127:64]  = {ptp_sec_sync_usr[31:0], ptp_nsec_sync_usr[31:0]}; // T4 = 同一定时器的当前值
assign ila_wr_data[139:128] = frame_cnt;                                        // 包号
assign ila_wr_data[140]     = sof;
assign ila_wr_data[141]     = eof;
assign ila_wr_data[223:142] = 'h123456789ABCDEF;                                // magic 标记
assign ila_wr_data[255:224] = cnt;                                              // 自由运行采样序号

s_apb_ila #(.DEPTH(16384), .W_DATA(256)) u_apb_ila (
  .i_pclk(usr_clk), .i_wr_data(ila_wr_data), .i_wr_en(ptp_ts_en), ... );

因此 T4 = {ptp_sec_sync_usr[31:0], ptp_nsec_sync_usr[31:0]} ,即同一个
本地 PTP 定时器
(经 streaming_cdc 流入 usr_clk 域)在"环回 payload 第

一拍出现在 Sensor TX 流上"那一 usr_clk 时刻的值。

记录格式与论文一一对应------"the current time stamp and the timestamp in the

packet, alongside its packet number, are sent to a laptop through the ILA":

ILA 字段 论文对应
ptp_ts[63:0](T1) "the timestamp in the packet"
{ptp_sec_sync_usr, ptp_nsec_sync_usr}(T4) "the current time stamp"(收包时刻)
frame_cnt[11:0] "its packet number"(笔记本上据此检查连续性)

2.6 读出:APB ILA(论文用 JTAG ILA + 独立笔记本,效果相同)

论文用独立笔记本经 JTAG 连接 Xilinx ILA 读出数据,"not to have any impact on

the latency measurements"。本参考设计用 s_apb_ila(APB 可读的片内逻辑分析仪)

达到同样的非侵入读出:读出走控制面(APB/ECB),不触碰数据通路。

APB 地址映射(docs/user_guide/register_interface.mdx:pseln

0x(n+1)_0000_0000):

模块 psel 基址 内容
u_apb_ila psel2 0x3000_0000 延迟测量记录(上表,256b × 16384)
u_apb_sif_ila psel3 0x4000_0000 通用流嗅探:每拍 {512b data, tvalid, tlast, 字节数, 64b PTP}
u_ram_player psel4 0x5000_0000 数据发生器(与 gpu_roce_loopback.py 一致)

读出后在主机/笔记本上逐条计算:

复制代码
RTT_ns = (T4_sec − T1_sec) × 10^9 + (T4_nsec − T1_nsec)     // 秒域仅保留低32位,格式一致

并检查 frame_cnt 逐包连续(模 4096),与论文"packets are checked to be

consecutive, and per packet the time difference is measured"一致。

3. 数据流总图

复制代码
                              ┌──────────────────── FPGA (RFSoC) ─────────────────────┐
                              │                                                       │
 本地PTP定时器                 │   ptp_timer (100MHz, 自由运行, 10ns/拍)               │
 (ptp_clk域, 无需主机同步) ────┼──► streaming_cdc ──► {sec,nsec}_usr (322MHz域, ~实时) │
                              │          │                          ▲               │
                              │          ▼                          │               │
                              │   ram_player ──T1写入payload──► SIF RX[0]           │
                              │     (PTP模式)                   │ packetizer        │
                              │                                 ▼ RoCE封装          │
                              │                              ETH MAC ───────────────┼──► 100G ──► NIC ──► GPU环回(论文)
                              │                                 ▲                   │       (或CPU, 见 latency_measurement.md)
                              │   SIF TX[0] ◄── rx_parser ◄── ETH MAC ◄───────────┼──◄────────
                              │     │           (剥RoCE头,                         │
                              │     ▼            按dest QP路由)                    │
                              │  sof锁存: T1←payload[79:0], 包号←payload[91:80]    │
                              │  ILA(psel[2],0x3000_0000) ◄── T4=当前本地PTP时间   │
                              │     │                                             │
                              └─────┼─────────────────────────────────────────────┘
                                    ▼ APB/控制面读出(或论文的JTAG ILA笔记本)
                          离线计算 RTT = T4 − T1,检查包号连续,统计
                          mean/median/std/max(论文: 3.839µs / 35ns / 3.96µs)

4. 测量边界与精度讨论

  1. T1 取点 :ram_player 输出进入打包流水线之前(SIF RX 入口)。
    T4 取点 :rx_parser 解包后 payload 交付到 SIF TX 的那一拍。
    因此 RTT 覆盖:打包/RoCE 封装 → MAC/PCS → 光/电缆 → NIC →(GPU 或 CPU
    环回处理)→ NIC → 链路 → MAC/PCS → 解包交付,即论文所称的
    "end-to-end latency"。FPGA 内部发生器之前与 ILA 之后的部分不计入。
  2. 分辨率与陈旧度 :定时器输出四舍五入到 1 ns;streaming_cdc
    usr_clk 域副本最坏滞后约一个 ptp_clk(10 ns)+ 数拍同步延迟,但该滞后对
    T1(ram_player 采样)与 T4(ILA 采样)是共模 ,差分后基本消除------这是
    同源时间戳差分测量的固有优势。论文 35 ns 的 std 主要来自链路/MAC 抖动与
    主机端(GPU 内核调度)抖动,而非时间戳本身。
  3. 为什么不希望同步反而更好 :若启用 PTP 同步,DPLL 会在测量过程中微调
    定时器频率(i_fa_adj),给差分测量引入微小扰动。自由运行时定时器只受
    板载晶振 ppm 级漂移影响,µs 尺度下完全可忽略。所以"不做主机同步"既是
    论文的事实,也是该测量拓扑下的合理选择。
  4. 何时必须同步 :只有当测量改为跨设备单向 (如 FPGA→CPU one-way,
    时间戳分别取自两台设备)时,才必须借助 ptp4l/phc2sys 把两个时钟对齐到
    同一时间基准(参见本仓库 latency_measurement.md 的方案与
    docs/user_guide/ptp_app_note.mdx 实测 <10 ns 的同步精度)。

5. 关键代码索引

机制 文件 位置
本地自由运行 PTP 定时器(10 ns/拍,Q24 小数) fpga/nv_hsb_ip/ptp/ptp_timer.sv default_inc、主 always_ff
定时器包装/输出 o_sec/o_nano_sec fpga/nv_hsb_ip/ptp/ptp_top.sv u_ptp_timer
PTP 时间引出到顶层 fpga/nv_hsb_ip/top/HOLOLINK_top.sv o_ptp_sec/o_ptp_nanosec
ptp_clk→usr_clk 时间流 fpga/nv_hsb_ip/misc/streaming_cdc.sv;例化于 fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv:258 u_ptp_hif_cdc
发包时间戳 T1 写入 payload fpga/nv_hsb_ip/data_gen/ram_player.sv o_axis_tdata PTP 模式
收包路径(剥头、按 QP 路由到 SIF TX) fpga/nv_hsb_ip/rx_parser/rx_parser.sv is_stx/bth_dest_qp
收包时间戳 T4 锁存与 ILA 记录 fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv:234-312 ptp_ts/frame_cnt 锁存、u_apb_ila
ILA/寄存器地址映射(pseln→0x(n+1)×16M) docs/user_guide/register_interface.mdx User Registers
论文方法原文 arXiv:2510.25213v2 §2.4 "Network proof of concept"

2. cpu 方案

本文档描述如何在 NVIDIA Holoscan Sensor Bridge(HSB)生态上,使用与论文

arXiv:2510.25213v2《Platform Architecture for Tight Coupling of High-Performance
Computing with Quantum Processors》
第 2.4 节("Network proof of concept")相同的
PTP 时间戳机制
,测量 FPGA → RoCE → CPU 的单向通信延迟。

  • 论文原实验:FPGA 构造携带 96-bit PTP 时间戳 + 16-bit 包号 (加 18 字节 0,共
    32 字节 payload)的 RoCE 包,主机侧由 GPU(DOCA GPUNetIO 常驻内核)环回,FPGA
    收到环回包后用 ILA 读出收发 PTP 时间戳,计算往返延迟(RTT)。测得稳态
    mean=median=3.839 µs,std=35 ns,max=3.96 µs。
  • 本方案:把论文中的"GPU 环回端点"替换为"CPU 测量端点 "。FPGA 以相同机制在
    payload 中携带 PTP 时间戳与包号;CPU 侧用 ibverbs 忙轮询接收 RDMA 包并在完成
    事件时刻读取 PTP 同步的主机时钟,逐包计算 FPGA→RoCE→CPU 单向延迟
    所有统计口径(mean/median/std/max、warm-up 与稳态分段、直方图、包号连续性
    检查)与论文保持一致,便于对照。

涉及的代码位置:

  • FPGA 数据发生器(PTP 模式):fpga/nv_hsb_ip/data_gen/ram_player.sv
  • FPGA PTP 从时钟:fpga/nv_hsb_ip/ptp/ptp_top.svptp_timer.svdpll.sv 等)
  • RFSoC-PYNQ 顶层设计:fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv
  • 主机控制面/PTP API:src/hololink/core/hololink.hpp/.cppdata_channel.cpp
  • 参考接收器(RoCE/RDMA):src/hololink/operators/roce_receiver/roce_receiver.cpp
  • 论文所用 GPU 环回示例:examples/gpu_roce_loopback.py
  • 本项目文档(在线版):https://docs.nvidia.com/holoscan/sensor-bridge/getting-started/introduction
    及其后续页面(本地镜像在 docs/user_guide/*.mdx

1. 测量原理

1.1 时钟同步链(与论文相同的 PTP 机制)

复制代码
┌──────────────────────────── Host ───────────────────────────┐
│  ptp4l (grandmaster, IEEE1588 E2E, L2) ──┐                  │
│  phc2sys: NIC PHC ↔ CLOCK_REALTIME       │ PTP Sync/        │
│                                          │ Follow_Up/       │
│  hsb_latency: CLOCK_REALTIME @ CQE       │ Delay_Req/Resp   │
└──────────────────────────────────────────┼──────────────────┘
                                           │  (同一 100G 链路)
┌──────────────────────── FPGA (HSB) ──────┼──────────────────┐
│  HSB IP PTP slave (receiver-only) ◀──────┘                  │
│    ptp_top: DPLL 锁相 → PTP timer (sec[47:0], nsec[31:0])   │
│  ram_player (PTP 模式): 每 64 B beat 打上发送时刻 PTP + 包号 │
└─────────────────────────────────────────────────────────────┘
  1. 主机运行 ptp4l(L2、IEEE 1588 E2E profile)作为 PTP grandmaster;

    phc2sys 把网卡硬件时钟(PHC)与 CLOCK_REALTIME 对齐(偏移 0)。

    HSB 官方文档(docs/user_guide/ptp_app_note.mdx)实测该配置下 FPGA 与主机

    PPS 偏差 < 10 ns(std < 25 ns)。

  2. HSB IP 内部的 PTP 模块(fpga/nv_hsb_ip/ptp/)是只收不发 的从时钟:

    解析 Sync/Follow_Up,发出 Delay_Req、收 Delay_Resp,用 DPLL 调整本地

    timer 的频率增量,最终 o_sec/o_nano_sec 输出与主机对齐的 PTP 时间。

  3. FPGA 发送数据时把该 PTP 时间写入 payload;CPU 收到包时读取本机

    CLOCK_REALTIME。两个时钟处于同一时间基准,因此:

    复制代码
    one_way_latency = host_rx_time(CLOCK_REALTIME @CQE) − fpga_tx_time(payload PTP)

1.2 FPGA payload 格式(对应论文的 32 字节探针包)

fpga/nv_hsb_ip/data_gen/ram_player.sv 的 PTP 模式(ptp_ena,enable 寄存器

bit1)把每个 64 字节(512 bit)AXI beat 的数据替换为:

复制代码
o_axis_tdata = { '0, window_cnt[11:0], ptp[79:0] }
               ptp = { sec[47:0], nsec[31:0] }

线缆上(AXI 小端字节序)每个 64 字节 beat 的布局:

字节 内容 说明
0--3 PTP 纳秒(LE u32) beat 离开 FPGA 的时刻
4--9 PTP 秒47:0(LE u48) 同上
10--11 窗口计数器11:0(LE) 即"包号",相当于论文的 16-bit packet number
12--63 0 填充

这与论文"96-bit PTP + 16-bit 包号 + 18 字节 0 = 32 字节 payload"完全同构

(HSB IP 的 PTP 为 80 bit、包号 12 bit、每个 beat 64 字节)。

1.3 每个窗口的另外两组 FPGA PTP 时间戳(帧级交叉验证)

HSB IP 在每个 sensor window 结束时会自动发出一个 RoCE metadata 包

(RDMA Write-with-Immediate,docs/user_guide/dataplane.mdx "Metadata Packet"),

其中 48 字节记录包含(主机侧由 Hololink::deserialize_metadata 解析):

字段 含义
timestamp_s/ns 本窗口第一拍数据到达 FPGA 数据通路时的 PTP 时间(frame start)
metadata_s/ns metadata 包在 FPGA 内组装发出时的 PTP 时间(frame end)
frame_number 16-bit 帧计数器

由此得到三条单向延迟序列(均以 CPU 收到 Write-with-Imm 完成事件

(CQE)时的 CLOCK_REALTIME 为接收时刻):

序列 定义 物理意义
packet_one_way host_rx − beat0 的 payload PTP 窗口首拍离开发送端 → CPU 感知,主指标(对应论文逐包测量)
frame_end_one_way host_rx − metadata PTP FPGA 发出帧尾 → CPU 感知(HSB latency 文档的口径)
frame_start_one_way host_rx − frame-start PTP 与 packet_one_way 互相校验

注:CPU 侧打时刻的位置是"ibv_poll_cq 返回完成事件后第一条指令",因此测量

结果 = 线缆+光模块+NIC RDMA 引擎+PCIe+完成队列处理+少量 CPU 指令延迟,

即论文中 FPGA→主机方向在 CPU 端点的对应量。误差预算见 §8。

1.4 与论文实验的对应关系

论文 §2.4 本方案
AMD RFSoC FPGA + HSB IP 相同硬件:fpga/pynq/rfsoc-pynq 设计(RFSoC + PYNQ)
ConnectX-7 NIC ConnectX(mlx5 设备,RoCEv2, UC QP)
GPU 常驻内核环回(GPUNetIO) CPU ibverbs 忙轮询接收(不环回),终点即测量点
FPGA 内 PTP 时间戳生成器 相同:HSB IP ptp/ + ram_player PTP 模式
payload = 96b PTP + 16b 包号 payload = 80b PTP + 12b 包号(同一机制的既有实现)
包号连续性检查 相同:逐窗口检查 12-bit 计数器连续,统计 lost/out-of-order
ILA 读出时间差后离线统计 CPU 在线统计 + CSV 离线分析(latency_analysis.py
Fig.4(含 warm-up)/ Fig.5(稳态) warm-up 与稳态分段统计,同样的两张图
RTT(FPGA→GPU→FPGA) 单向 FPGA→CPU(时钟已由 PTP 对齐,故可测单向)

2. 实验设备与软件清单

2.1 硬件

  1. FPGA 板 :AMD RFSoC 板(PYNQ 镜像),烧录 fpga/pynq/rfsoc-pynq 构建的
    HSB 固件(即论文使用的平台;其它带 ram_player 的 HSB 设计亦可,仅需相应
    修改 --datagen-base--sif-mhz)。
  2. 主机:x86_64 或 ARM 服务器,带 ConnectX-6/7(或更新)网卡,Linux。
  3. 链路 :100G 以太网直连 (QSFP28 DAC/AOC,论文同;经交换机亦可,但会
    增加延迟与 residence time,需在结果中说明)。链路同时承载 PTP 与 RoCE 流量
    (HSB IP 的 PTP 仅在 Host Interface 0 上工作)。

2.2 软件

软件 版本/说明
holoscan-sensor-bridge 本仓库(含 latency/ 工具)
linuxptp(ptp4l/phc2sys) 发行版包,setup_host_ptp.sh 自动安装
libibverbs + rdma-core RDMA verbs 栈(ConnectX 驱动 mlx5)
CUDA driver(libcuda) 仅链接需要(hololink_core 依赖),测量路径不使用 GPU
fmt(≥8, header-only) 构建 hololink_core 所需(libfmt-dev 或 HSDK 自带)
CMake ≥ 3.24, g++ 构建工具
Python 3(可选 matplotlib) latency_analysis.py 离线分析;无 matplotlib 时输出文本报告

容器/完整环境也可按仓库文档构建(docs/user_guide/build.mdx),但本工具只

依赖 hololink::core,可用 native-only 构建,无需 Holoscan SDK(见 §5)。


3. FPGA 侧准备

3.1 固件

使用 fpga/pynq/rfsoc-pynq 参考设计(构建方法见 fpga/pynq/README.md

docs/user_guide/sensor_bridge_firmware_setup.mdx)。该设计已经实例化了本实验

需要的全部模块,无需修改 RTL

  • ram_player(APB 基址 0x5000_0000,APB psel4):数据源,PTP 模式产生
    上述 payload;i_sif_clk = usr_clk = 322.265625 MHzHOLOLINK_def.svh
    HIF_CLK_FREQ=322265625),timer 寄存器以此时钟周期为单位。
  • ptp_top(寄存器基址 0x100 段):PTP 从时钟。
  • 数据通路:ram_player → SIF RX0 → packetizer → RoCE → Host IF。
  • (进阶用)s_apb_ila(APB psel3,基址 0x4000_0000):SIF TX 方向抓取
    {512b data, tvalid, tlast, 64b PTP},可用于环回实验的 FPGA 侧时间戳读出。

3.2 ram_player 寄存器(本实验用到)

偏移 名称 本实验取值
+0x04 enable bit0 ram_ena=1, bit1 ptp_ena=1(即写 0x3);停止写 0
+0x08 timer 相邻窗口间隔的 sif 时钟周期数;0 = 背靠背满速率
+0x0C window_size 每窗口字节数(本实验默认 256;须为 64 的倍数且 >128 才有 metadata)
+0x10 window_number 每轮窗口数(4096;timer≠0 且 loop_dis=0 时自动循环)

hsb_latency 会自动完成以上写寄存器操作,无需手动。)

3.3 枚举 UUID 注意

仓库默认枚举策略认识官方固件 UUID。若你的 RFSoC 固件 UUID 未被默认策略覆盖

(例如 examples/gpu_roce_loopback.py 中使用的

889b7ce3-65a5-4247-8b05-4ff1904c3359),枚举会超时。此时用

--uuid <UUID> [--sensors N --dataplanes N --sifs N] 显式指定(与

gpu_roce_loopback.py 中 BasicEnumerationStrategy(total_sensors=1, total_dataplanes=1, sifs_per_sensor=2) 对应,本工具默认值即 1/1/2)。


4. 主机侧准备

4.1 网络配置

bash 复制代码
# 假设与 FPGA 直连的网口为 ens1f0np0(按实际修改,下文记为 $EN0)
export EN0=ens1f0np0
sudo ip addr add 192.168.0.101/24 dev $EN0      # FPGA 默认 192.168.0.2
sudo ip link set $EN0 up
sudo ip link set $EN0 mtu 9000                   # 可选;默认 1500 即可工作
ping -c 3 192.168.0.2

确认 RDMA 设备名(hsb_latency --list-ibvibv_devices),常见为

mlx5_0/mlx5_1 或 IGX 上的 roceP5p3s0f0

4.2 PTP 服务(关键步骤,决定测量有效性)

运行随附脚本(流程与 docs/user_guide/setup.mdx "Enable PTP" 完全一致):

bash 复制代码
cd latency
sudo ./setup_host_ptp.sh $EN0                 # 安装+启动 phc2sys/ptp4l(1588 E2E, L2)
# 如需论文/应用笔记性能测试时的更快 Sync 速率(8 次/秒):
# sudo ./setup_host_ptp.sh $EN0 --sync-rate -3

脚本内容(供审阅):

  • /etc/linuxptp/hsb-ptp.confnetwork_transport L2logSyncInterval -1
    logMinDelayReqInterval -1(HSB PTP 仅走 L2,且只支持单一 master)。
  • phc2sys-$EN0.servicephc2sys -c $EN0 -s CLOCK_REALTIME -O 0 -S 0.0001
    (把 PHC 对齐到系统实时钟)。
  • ptp4l-$EN0.serviceptp4l -i $EN0 -f /etc/linuxptp/hsb-ptp.conf(主机做
    grandmaster 发 Sync)。

验证同步(三个互相独立的检查):

bash 复制代码
# (1) 服务状态与日志
./setup_host_ptp.sh $EN0 --status
# ptp4l 日志应显示 selected best master / assuming the grandmaster role
# phc2sys 日志 offset 应很小(典型 <100 ns)

# (2) 用 Wireshark/tcpdump 在 $EN0 上应看到 PTPv2 (ethertype 0x88f7) 的
#     Sync/Follow_Up(主机发出)与 Delay_Req(FPGA 发出)

# (3) 运行 hsb_latency 时打印的 FPGA 侧状态:
#     PTP SYNC_STAT=0xf  OFM≈0 ns  mean_delay≈链路延迟(数百 ns 量级)
#     SYNC_STAT bit[0..3] 全 1 = Sync 与 Delay_Resp 均已收到且 PTP 使能
#     (见 docs/user_guide/ptp_app_note.mdx 的 Debug 表)

排错提示:若 SYNC_STAT0=0(收不到 Sync),检查 ptp4l 是否在运行、报文

MajorSdoID 是否为 0(1588 profile 要求);若 OFM 振荡且幅度增大,说明 DPLL

不稳,可增大 Coarse/Fine Gain(寄存器 0x110/0x114,见 ptp_app_note.mdx)。

4.3 CPU 侧优化(可选但推荐)

bash 复制代码
# 增大 RDMA 锁页上限(mlock 接收缓冲)
ulimit -l unlimited
# 关闭测量核的频率波动(示例:CPU 7)
echo performance | sudo tee /sys/devices/system/cpu/cpu7/cpufreq/scaling_governor

测量时用 --cpu 7 --fifo 80(root)把接收线程绑到空闲核并提升为实时优先级,

可显著降低 CPU 侧时刻戳的抖动(对 std 指标影响明显)。


5. 构建测量程序

工具位于 latency/,作为顶层构建的一部分(option(HOLOLINK_BUILD_LATENCY ON)

仅在有 ibverbs 时构建)。最小构建(无需 Holoscan SDK / GPU 运行时):

bash 复制代码
# 依赖:libibverbs-dev、CUDA driver 头文件、fmt(libfmt-dev 或 HSDK)
cmake -S . -B build \
  -DHOLOLINK_BUILD_ONLY_NATIVE=ON \
  -DHOLOLINK_BUILD_PYTHON=OFF -DHOLOLINK_BUILD_TESTS=OFF \
  -DHOLOLINK_BUILD_TOOLS=OFF -DHOLOLINK_BUILD_EXAMPLES=OFF \
  -DHOLOLINK_BUILD_EMULATOR=OFF
cmake --build build --target hsb_latency -j
# 产物:build/latency/hsb_latency

完整容器/全量构建方式见 docs/user_guide/build.mdx(此时 hsb_latency 会随

顶层构建自动包含)。


6. 运行测量

6.1 典型命令

bash 复制代码
# 默认:256 B 窗口、10 us 间隔、采集 100000 个窗口、前 1000 个计入 warm-up
sudo ./build/latency/hsb_latency \
    --hololink 192.168.0.2 \
    --ibv-name mlx5_0 --ibv-port 1 \
    --cpu 7 --fifo 80 \
    --count 100000 --warmup 1000 \
    --csv run1.csv

预期输出(示例数值仅为示意):

复制代码
[1/6] Enumerating HSB at 192.168.0.2 ...
[2/6] Configuring PTP ...
       PTP SYNC_STAT=0xf OFM=-8 ns mean_delay=412 ns
[3/6] Setting up CPU RoCE data plane ...
       ibv device=mlx5_0 port=1 qp=0x2f rkey=0x1a2b buffer=393216 bytes pages=1024 page_size=384
[4/6] Configuring ram_player @0x50000000 (PTP mode) ...
       window=256 bytes, timer=3223 cycles (10.000 us @ 322.265625 MHz)
[5/6] Capturing (count=100000, warmup=1000) ... Ctrl-C to stop

[6/6] Shutting down ...
=================== FPGA -> RoCE -> CPU latency ===================
windows=100000 cqes=100000 errors=0 lost_windows=0 out_of_order=0 bad_payload=0 elapsed=1.02 s (98039 windows/s)
PTP offset-from-master (FPGA view): start=-8 ns end=-5 ns
------------------------------------------------------------------
  packet_one_way (warmup)      : N=1000 mean=... [Fig.4 analog: beginning of run]
  frame_end_one_way (warmup)   : ...
  packet_one_way (steady)      : N=99000 mean=... ns median=... std=... min=... p99=... p999=... max=... [Fig.5 analog: steady state]
  frame_end_one_way (steady)   : ...
  frame_start_one_way (steady) : ...
==================================================================
CSV samples written to run1.csv (analyze with latency_analysis.py)

6.2 常用参数

参数 默认 说明
--frame-size B 256 窗口字节数(64 的倍数,>128)。改大可观察串行化延迟的影响
--spacing-us F 10.0 窗口间隔;0 = 背靠背满速率(压力测试,注意丢包统计)
--sif-mhz F 322.265625 timer 寄存器的时钟(RFSoC-PYNQ);其它平台按实际填
--pages N 1024 接收环形页数(1--4096),决定抗突发能力
--count N 100000 采集窗口数;0 = 直到 Ctrl-C
--seconds F 0 或按墙钟时长停止
--warmup N 1000 warm-up 窗口数(论文 Fig.4 观察区)
--no-datagen off 不配置 ram_player(用外部数据源/真实 sensor,仅帧级指标)
--datagen-base 0x50000000 ram_player APB 基址
--ptp-profile 1588e2e 可选 gptp / 1588p2p(须与主机 ptp4l/gptp4l 对应)
--no-ptp-config off 不写 FPGA PTP 寄存器(用固件默认配置)
--cpu N / --fifo P off 绑核 / SCHED_FIFO(建议,需 root)
--uuid ... off 自定义枚举 UUID 策略(见 §3.3)

6.3 建议的实验矩阵

  1. 重复性:同一配置跑 ≥3 次,比较 mean/std/max 的离散度。
  2. 包速率--spacing-us 100 / 10 / 1 / 0,观察 CPU 跟得上的最大速率与
    lost_windows 何时出现(CPU 处理能力的边界)。
  3. 包大小--frame-size 192 / 256 / 512 / 1408,观察串行化延迟对
    packet_one_way 的影响(首拍延迟应几乎不随包长变化,frame_end 随包长线性增长)。
  4. warm-up 观察--count 200000 --warmup 0 后用分析脚本观察前几百个
    样本的抬升(对应论文 Fig.4 的 warm-up 现象:NIC 队列元素首次填充、缓存
    分配等)。可以用先发 dummy 流量的方式预热(把 --warmup 设大即可在统计
    中剔除)。
  5. 绑核对照 :有无 --cpu/--fifo 的 std 对比,量化 CPU 调度抖动。
  6. (对照组)GPU 环回 :运行仓库自带的 examples/gpu_roce_loopback.py
    与本文 CPU 测量对比论文的 GPU 路径。

6.4 结果分析

bash 复制代码
python3 latency/latency_analysis.py run1.csv --warmup 1000 --histogram --out run1
# 生成 run1_packet_timeseries.png / run1_packet_histogram.png(对应论文 Fig.4a/4b/5a/5b)
# 终端同时输出 mean/median/std/min/p99/p999/max 与 warm-up 对比

判读要点:

  • packet_one_way(稳态) 是主指标:FPGA 首拍 → CPU 完成的单向延迟。
    论文 GPU 路径 RTT 3.839 µs(std 35 ns);CPU 单向的典型期望在数 µs 量级,
    主要由 NIC RDMA 处理、PCIe、CQ 轮询发现延迟构成;std 反映 CPU 调度与
    PTP 残余抖动(PTP 本身 <25 ns)。
  • frame_end − packet_one_way(同窗口) ≈ FPGA 内一个窗口的组装/发送耗时,
    可用于自检。
  • lost_windows/out_of_order 应为 0;非 0 说明 CPU 消费跟不上或链路有误码
    (参考论文对低 BER 线缆的要求)。
  • OFM(FPGA 视角的 offset-from-master)应稳定在 |100| ns 以内,且 start/end
    接近;异常漂移说明 PTP 未锁稳(此时单向绝对值不可信,但同一次运行的
    相对抖动仍有参考价值)。

7. 测量程序说明(实现要点)

latency/hsb_latency.cpp 的执行流程(与 RoceReceiverOp 的控制面完全一致,

数据面改为纯 CPU 忙轮询):

  1. 枚举与控制面hololink::Enumerator::find_channel
    hololink::DataChannelhololink->start()/reset()
  2. PTPhololink->configure_ptp(profile, domain)(写 0x104/0x108/0x11C
    寄存器)→ ptp_synchronize()(轮询 0x188 SYNC_STAT 直到 0xF)→ 打印
    OFM(0x18C)/mean_delay(0x190)。
  3. RDMA 数据面CpuRoceReceiver,仿
    src/hololink/operators/roce_receiver/roce_receiver.cpp):
    IPv4-mapped RoCEv2 GID 选择 → PD/CQ → ibv_reg_mr_iova(IOVA=0 的锁页
    主机内存)→ UC QP → INIT → RTR(dest_qp=0,FPGA 为主动写方)→ 预投
    1024 个 0 长 receive WR。
  4. FPGA 数据面编程channel.authenticate(qpn, rkey) +
    channel.configure_roce(0, frame_size, page_size, pages, 4791)(写
    DP_QP/DP_RKEY/DP_PAGE_*/DP_MAX_BUFF/DP_PACKET_SIZE/目的 MAC-IP-UDP 等寄存器,
    RoceReceiverOp 相同)。
  5. 启动 ram_player(PTP 模式):写 enable=0x3。
  6. 测量循环 :绑核/RT 后 ibv_poll_cq 忙轮询;每个 CQE:
    clock_gettime(CLOCK_REALTIME) → 由 imm_data 取页号 → 解析页内各 beat 的
    PTP/包号 → Hololink::deserialize_metadata 解析尾随 128 B metadata →
    记录样本、检查包号连续性 → 补投 receive WR。
  7. 收尾 :停 ram_player、channel.unconfigure()、QP→ERR 排空销毁、
    hololink->stop();输出统计与 CSV。

目录内容:

复制代码
latency/
├── CMakeLists.txt          # 目标 hsb_latency(顶层 HOLOLINK_BUILD_LATENCY=ON 时构建)
├── hsb_latency.cpp         # 主测量程序(C++,hololink::core + ibverbs)
├── latency_analysis.py     # CSV 离线分析:统计 + 论文风格图(matplotlib 可选)
└── setup_host_ptp.sh       # 主机 PTP(ptp4l/phc2sys)安装、启动与状态检查

8. 误差来源与注意事项

  1. 时钟基准 :主机 CLOCK_REALTIME 经 phc2sys↔PHC、ptp4l↔FPGA 两跳对齐。
    HSB 应用笔记实测 FPGA↔主机 PPS 偏差 <10 ns(std <25 ns)。若机器未正确运行
    上述服务,或 ptp4l 使用 TAI 而系统钟为 UTC(相差 37 s),单向延迟将出现
    巨大偏差;程序检测到 |latency|>1 s 会告警。
  2. CPU 侧时刻戳位置 :CQE 可见 → clock_gettime 之间存在数十~数百 ns 的
    指令/缓存延迟(忙轮询+绑核时最小)。这是"CPU 端点"定义的一部分,论文的
    GPU 端点对应量是 GPUNetIO 内核检测到包的时刻。
  3. 串行化延迟 :100G 下每 64 B 约 5.12 ns;256 B 窗口首尾拍相差 ~15 ns,
    会体现在同窗口不同 beat 的样本中(属物理真实,非误差)。
  4. FPGA 内部延迟不对称 :HSB IP 收发路径的 delay asymmetry 已由 IP 自动
    计算并补偿(见 ptp_app_note.mdx),MAC/PCS 厂商部分由 Delay Asymmetry
    寄存器(默认 0x33=51 ns)补偿;若更换 MAC IP 需重新标定该寄存器。
  5. PCIe/IOMMU :NIC→内存的 RDMA 写经过 PCIe,若共享 Root Complex 上有其它
    流量会引入抖动(论文对 GPU 路径也有同样讨论)。测量时保持机器空闲。
  6. PTP 报文与数据同链路 :Sync 间隔(默认 2 次/秒)对数据流量的影响可忽略;
    如需更接近应用笔记的 PTP 性能(<10 ns),用 --sync-rate -3(8 次/秒)。
  7. 不要把本结果与论文 RTT 直接比数值 :论文是 FPGA→GPU→FPGA 往返且端点为
    GPU 内核;本实验是 FPGA→CPU 单向。两者路径与端点均不同,比较时应看
    "链路+NIC 共性部分"与各自端点的增量。

9. 进阶:CPU 环回测 FPGA 侧 RTT(可选扩展)

若需要与论文更一致的"FPGA 测 RTT"口径,可在现有固件上做 CPU 环回:

  1. CPU 端增加一个 UC QP,连接到 FPGA 的接收 QP(tx_ibv_qp=2,参考
    GpuRoceTransceiverOp/gpu_roce_transceiver.cpp
    dest_qp_num = 0x2, FPGA specific port 的做法),把收到的窗口 payload 以
    RDMA Write 发回 FPGA。
  2. FPGA 侧主机→sensor 方向(SIF TX0)已有 s_apb_ila(APB 基址
    0x4000_0000,见 FPGA_top.sv),它把每个到达 beat 的 {data, PTP} 存入
    ILA RAM;经控制面读出后与 payload 中的发送 PTP、包号匹配,即得
    FPGA 视角的 RTT(等价于论文用 ILA 读时间差的方法,只是 ILA 通过 APB
    读取而非独立笔记本)。
  3. 该扩展不改变 §6 的单向测量;两者结合可同时报告"CPU 端单向"与"FPGA 端
    RTT"。

10. 参考资料

  1. arXiv:2510.25213v2, §2.4 "Network proof of concept"(测量方法与指标口径)。
  2. Holoscan Sensor Bridge 文档:
    • Getting Started / Introduction:
      https://docs.nvidia.com/holoscan/sensor-bridge/getting-started/introduction
    • Host setup(PTP 配置):docs/user_guide/setup.mdx
    • PTP / PTP Application Note:docs/user_guide/ptp.mdx
      docs/user_guide/ptp_app_note.mdx
    • Latency(metadata 时间戳语义):docs/user_guide/latency.mdx
    • Dataplane(RoCE/metadata 包格式):docs/user_guide/dataplane.mdx
  3. 代码:
    • fpga/nv_hsb_ip/ptp/(PTP 从时钟)、fpga/nv_hsb_ip/data_gen/ram_player.sv
      (PTP payload 发生器)、fpga/pynq/rfsoc-pynq/(RFSoC 顶层设计)
    • src/hololink/operators/roce_receiver/roce_receiver.cpp(RDMA 接收参考实现)
    • examples/gpu_roce_loopback.py(论文 GPU 环回示例)
    • examples/imx274_latency.py(HSB 帧延迟测量示例,同一 PTP 比对方法)
相关推荐
博览鸿蒙2 小时前
新手怎么挑一块靠谱的FPGA开发板?
fpga开发·fpga·fpga开发板
Eloudy7 小时前
gpunetio_verbs_write_lat 延迟测试使用了哪些 GPUNetIO 能力
gpu
FPGA小迷弟21 小时前
【无标题】
fpga开发·dsp开发·fpga·sdr·无线电
Eloudy1 天前
全文 - DOCA GPUNetIO 闭源实现的 Doc
gpu
Eloudy1 天前
全文 - DOCA GPUNetIO Open Source 仓库 README
gpu
guwentian1 天前
WebGPU 和 WebTransport 2026 真的能上生产了吗?
web·gpu·transport
Eloudy1 天前
RAM Player 实验:hololink-write 开流 + RoCEv2 接收分析
fpga
FPGA小迷弟2 天前
FPGA 应用技术能力的完整闭环(5-8)
fpga开发·dsp开发·fpga·sdr·无线电
Eloudy2 天前
vitis 工程 ping FPGA 丢包(~50% 随机)问题分析
fpga