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)。
- 发包时间戳 T1 :
ram_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_FREQns(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. 测量边界与精度讨论
- 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 之后的部分不计入。 - 分辨率与陈旧度 :定时器输出四舍五入到 1 ns;
streaming_cdc的
usr_clk 域副本最坏滞后约一个 ptp_clk(10 ns)+ 数拍同步延迟,但该滞后对
T1(ram_player 采样)与 T4(ILA 采样)是共模 ,差分后基本消除------这是
同源时间戳差分测量的固有优势。论文 35 ns 的 std 主要来自链路/MAC 抖动与
主机端(GPU 内核调度)抖动,而非时间戳本身。 - 为什么不希望同步反而更好 :若启用 PTP 同步,DPLL 会在测量过程中微调
定时器频率(i_fa_adj),给差分测量引入微小扰动。自由运行时定时器只受
板载晶振 ppm 级漂移影响,µs 尺度下完全可忽略。所以"不做主机同步"既是
论文的事实,也是该测量拓扑下的合理选择。 - 何时必须同步 :只有当测量改为跨设备单向 (如 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.sv、ptp_timer.sv、dpll.sv等)- RFSoC-PYNQ 顶层设计:
fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv- 主机控制面/PTP API:
src/hololink/core/hololink.hpp/.cpp、data_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 + 包号 │
└─────────────────────────────────────────────────────────────┘
-
主机运行
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)。
-
HSB IP 内部的 PTP 模块(
fpga/nv_hsb_ip/ptp/)是只收不发 的从时钟:解析 Sync/Follow_Up,发出 Delay_Req、收 Delay_Resp,用 DPLL 调整本地
timer 的频率增量,最终
o_sec/o_nano_sec输出与主机对齐的 PTP 时间。 -
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 硬件
- FPGA 板 :AMD RFSoC 板(PYNQ 镜像),烧录
fpga/pynq/rfsoc-pynq构建的
HSB 固件(即论文使用的平台;其它带 ram_player 的 HSB 设计亦可,仅需相应
修改--datagen-base与--sif-mhz)。 - 主机:x86_64 或 ARM 服务器,带 ConnectX-6/7(或更新)网卡,Linux。
- 链路 :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 MHz(HOLOLINK_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-ibv 或 ibv_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.conf:network_transport L2,logSyncInterval -1,
logMinDelayReqInterval -1(HSB PTP 仅走 L2,且只支持单一 master)。phc2sys-$EN0.service:phc2sys -c $EN0 -s CLOCK_REALTIME -O 0 -S 0.0001
(把 PHC 对齐到系统实时钟)。ptp4l-$EN0.service:ptp4l -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 建议的实验矩阵
- 重复性:同一配置跑 ≥3 次,比较 mean/std/max 的离散度。
- 包速率 :
--spacing-us 100 / 10 / 1 / 0,观察 CPU 跟得上的最大速率与
lost_windows 何时出现(CPU 处理能力的边界)。 - 包大小 :
--frame-size 192 / 256 / 512 / 1408,观察串行化延迟对
packet_one_way 的影响(首拍延迟应几乎不随包长变化,frame_end 随包长线性增长)。 - warm-up 观察 :
--count 200000 --warmup 0后用分析脚本观察前几百个
样本的抬升(对应论文 Fig.4 的 warm-up 现象:NIC 队列元素首次填充、缓存
分配等)。可以用先发 dummy 流量的方式预热(把--warmup设大即可在统计
中剔除)。 - 绑核对照 :有无
--cpu/--fifo的 std 对比,量化 CPU 调度抖动。 - (对照组)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 忙轮询):
- 枚举与控制面 :
hololink::Enumerator::find_channel→
hololink::DataChannel→hololink->start()/reset()。 - PTP :
hololink->configure_ptp(profile, domain)(写 0x104/0x108/0x11C
寄存器)→ptp_synchronize()(轮询 0x188 SYNC_STAT 直到 0xF)→ 打印
OFM(0x18C)/mean_delay(0x190)。 - 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。 - 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相同)。 - 启动 ram_player(PTP 模式):写 enable=0x3。
- 测量循环 :绑核/RT 后
ibv_poll_cq忙轮询;每个 CQE:
clock_gettime(CLOCK_REALTIME)→ 由 imm_data 取页号 → 解析页内各 beat 的
PTP/包号 →Hololink::deserialize_metadata解析尾随 128 B metadata →
记录样本、检查包号连续性 → 补投 receive WR。 - 收尾 :停 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. 误差来源与注意事项
- 时钟基准 :主机
CLOCK_REALTIME经 phc2sys↔PHC、ptp4l↔FPGA 两跳对齐。
HSB 应用笔记实测 FPGA↔主机 PPS 偏差 <10 ns(std <25 ns)。若机器未正确运行
上述服务,或 ptp4l 使用 TAI 而系统钟为 UTC(相差 37 s),单向延迟将出现
巨大偏差;程序检测到 |latency|>1 s 会告警。 - CPU 侧时刻戳位置 :CQE 可见 →
clock_gettime之间存在数十~数百 ns 的
指令/缓存延迟(忙轮询+绑核时最小)。这是"CPU 端点"定义的一部分,论文的
GPU 端点对应量是 GPUNetIO 内核检测到包的时刻。 - 串行化延迟 :100G 下每 64 B 约 5.12 ns;256 B 窗口首尾拍相差 ~15 ns,
会体现在同窗口不同 beat 的样本中(属物理真实,非误差)。 - FPGA 内部延迟不对称 :HSB IP 收发路径的 delay asymmetry 已由 IP 自动
计算并补偿(见 ptp_app_note.mdx),MAC/PCS 厂商部分由 Delay Asymmetry
寄存器(默认 0x33=51 ns)补偿;若更换 MAC IP 需重新标定该寄存器。 - PCIe/IOMMU :NIC→内存的 RDMA 写经过 PCIe,若共享 Root Complex 上有其它
流量会引入抖动(论文对 GPU 路径也有同样讨论)。测量时保持机器空闲。 - PTP 报文与数据同链路 :Sync 间隔(默认 2 次/秒)对数据流量的影响可忽略;
如需更接近应用笔记的 PTP 性能(<10 ns),用--sync-rate -3(8 次/秒)。 - 不要把本结果与论文 RTT 直接比数值 :论文是 FPGA→GPU→FPGA 往返且端点为
GPU 内核;本实验是 FPGA→CPU 单向。两者路径与端点均不同,比较时应看
"链路+NIC 共性部分"与各自端点的增量。
9. 进阶:CPU 环回测 FPGA 侧 RTT(可选扩展)
若需要与论文更一致的"FPGA 测 RTT"口径,可在现有固件上做 CPU 环回:
- 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。 - 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
读取而非独立笔记本)。 - 该扩展不改变 §6 的单向测量;两者结合可同时报告"CPU 端单向"与"FPGA 端
RTT"。
10. 参考资料
- arXiv:2510.25213v2, §2.4 "Network proof of concept"(测量方法与指标口径)。
- 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
- Getting Started / Introduction:
- 代码:
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 比对方法)