1. fpga-latency 指标分析
run_latency.sh 与 run_ramplayer_analyzer.sh 输出的 fpga-latency 的含义与完整变量逻辑链条。
1. 定义
fpga-latency 是纯 FPGA 内部的一段延迟,由主机侧分析器用同一帧 metadata 中的两个 FPGA 时间戳相减得到:
fpga-latency = metadata_s/ns − timestamp_s/ns
= cur_ptp@hdr_trigger − sof_ptp@i_sof
= 帧尾 write-with-imm 发出时刻 − 帧首数据进入打包器时刻
两个时间戳来自 FPGA 内同一个自由运行的 PTP 计数器(同一时钟域),主机只计算差值,因此无需与主机对时,测量是精确的。
计算位置:tools/roce_ramplayer_analyzer/roce_ramplayer_analyzer.cpp:778-788
cpp
const double ts_ns = m.timestamp_s * 1e9 + m.timestamp_ns; // 起点: sof_ptp
const double meta_ns = m.metadata_s * 1e9 + m.metadata_ns; // 终点: cur_ptp
if (meta_ns >= ts_ns) {
st->add_fpga_latency(meta_ns - ts_ns);
}
2. 两个端点的确切含义
| 端点 | metadata 字段 | FPGA 变量 | 采集时刻 |
|---|---|---|---|
| 起点 | timestamp_s/ns |
sof_ptp |
一帧的第一个数据 beat 进入 RoCE 打包器 (dp_pkt,i_sof 脉冲)时锁存的 PTP 时间 |
| 终点 | metadata_s/ns |
cur_ptp |
FPGA 发完该帧全部数据包、开始发出帧尾 metadata / RDMA write-with-immediate 包 那一刻(hdr_trigger)锁存的当前 PTP 时间 |
3. 物理意义
衡量一帧数据在 FPGA 内部的驻留/处理时间,即"帧首进入发包流水线"到"整帧全部推上网线"之间的时间,包含:
- 帧打包成 MTU 大小 RoCE 包的时间
- HIF 调度/仲裁/排队时间
- 以太网 MAC 串行化发送时间(如 4096B 帧在 10GbE 上仅串行化约 3.3 µs)
不包含:
- 网线/交换机传输延迟(FPGA MAC → 主机 NIC)
- 主机侧 NIC DMA、驱动、completion 轮询、用户态处理时间
- 控制面(UDP/APB)延迟 ------ 由
rtt指标测量
与脚本中其他指标的分工:rtt ≈ 链路往返;fpga-latency = FPGA 内部驻留;host-interval 对比 fpga-interval = 网络/主机引入的抖动。
4. 完整变量逻辑链条
① FPGA 侧:两个时间戳的诞生(i_pclk 包时钟域)
PTP 计数器 (ptp_clk 域, 自由运行, 无需与主机对时)
ptp_sec[47:0], ptp_nsec[31:0]
│ CDC 同步 (fpga/pynq/rfsoc-pynq/rtl/top/FPGA_top.sv:262 u_ptp_hif_cdc)
▼
i_cur_ptp[79:0] = {sec[47:0], nsec[31:0]} ─┐
│ │
├─── 起点 ──────────────────────────────┐ │
│ dp_pkt_top.sv:208 i_sof[vp] 脉冲 │ │
│ (帧首数据 beat 进入打包器) │ │
▼ │ │
dp_pkt_ts.sv:56 FIFO 锁存 i_ptp[63:0] │ │
= {sec[31:0], nsec[31:0]} │ │
│ 按 VP 存入 Timestamp RAM (line 177) │ │
│ 帧尾时读出 (line 198) │ │
▼ │ │
sof_ptp[79:0] = {16'h0, sec[31:0], nsec} │ │
│ │
├─── 终点 ──────────────────────────────┤ │
│ dp_pkt_top.sv:771 │ │
▼ │ │
cur_ptp_sync = i_cur_ptp ◄──────────────────┘ │
│ dp_pkt_top.sv:1321-1322 │
│ hdr_trigger=1 时锁存 │
│ (整帧数据包发完, 开始发 metadata/write-imm) │
▼ │
cur_ptp[79:0] = {sec[47:0], nsec[31:0]} ◄───────────┘
② FPGA 侧:组包发出(fpga/nv_hsb_ip/dp_pkt/dp_pkt_top.sv)
wr_imm_data[383:0] = { md_flags, ──→ flags
8'h0, psn, ──→ psn
frame_crc, ──→ crc
16'h0, sof_ptp, ──→ timestamp_s/ns (起点, 96bit)
roce_val_addr, ──→ bytes_written
16'h0, frame_num, ──→ frame_number
16'h0, cur_ptp } ──→ metadata_s/ns (终点, 96bit)
(line 1303, 共 384bit = 48B)
│ line 1310: pkt_commit 时 wr_imm_meta_r <= wr_imm_data[383:80]
│ line 1314: wr_imm_data_hdr = {wr_imm_meta_r, cur_ptp}
│ ※ cur_ptp 在最后时刻才拼入, 最贴近真实发出点
▼ line 1338: gen_imm_byte_align 字节反转 → 大端
wr_imm_pkt_be ──以太网──► 作为该帧最后一个包:
RDMA Write-with-Immediate 的 payload
(imm = {psn[19:0], buf_ptr[11:0]}, line 1302)
③ 主机侧:接收解析(tools/roce_ramplayer_analyzer/roce_ramplayer_analyzer.cpp)
NIC RDMA 写入 ring buffer 一页:
page = imm & 0xFFF (line 735)
frame = buffer + page * page_size (line 744)
meta = frame + metadata_offset (metadata_offset = aligned_frame, line 489)
▼
parse_metadata(meta) (line 133, BeReader 大端, 与 FPGA 组包顺序一一对应;
布局同 src/hololink/core/hololink.cpp:3139 deserialize_metadata)
→ m.timestamp_s / m.timestamp_ns ← sof_ptp (起点)
→ m.metadata_s / m.metadata_ns ← cur_ptp (终点)
④ 主机侧:计算与输出
ts_ns = m.timestamp_s*1e9 + m.timestamp_ns ┐ line 778
meta_ns = m.metadata_s*1e9 + m.metadata_ns ┘ line 785
▼
if (meta_ns >= ts_ns)
add_fpga_latency(meta_ns - ts_ns) line 786-788
▼ 累加进
fpga_lat_sum / fpga_lat_min / fpga_lat_max / fpga_lat_count (Stats, line 257)
▼ Stats::print() line 316
"fpga-latency mean %.2f us min %.2f max %.2f us" (ns÷1e3 → µs)
▼ 写入 /tmp/latency.log (run_latency.sh) 或 /tmp/final.log (run_ramplayer_analyzer.sh)
grep "fpga-latency" | tail -4 ──► 终端输出
5. 保证测量正确性的两个关键设计
- 同时钟域 :起点和终点都来自同一个自由运行 PTP 计数器,主机只算差值,所以分析器可以安全地设置
ptp_enable=0(跳过 PTP 对时配置,见 roce_ramplayer_analyzer.cpp:444)。 - 终点采得晚 :
cur_ptp不随其他字段在pkt_commit时一起寄存,而是在组包最后一刻(wr_imm_data_hdr = {wr_imm_meta_r, cur_ptp},dp_pkt_top.sv:1314)才拼入,确保它真实反映 metadata 包的发出时刻,而非提前锁存的旧值。
6. 关键文件索引
| 文件 | 作用 |
|---|---|
fpga/nv_hsb_ip/dp_pkt/dp_pkt_top.sv:1303 |
metadata 48B 组包(sof_ptp / cur_ptp 拼接点) |
fpga/nv_hsb_ip/dp_pkt/dp_pkt_top.sv:1316-1325 |
cur_ptp 在 hdr_trigger 时锁存(终点) |
fpga/nv_hsb_ip/dp_pkt/dp_pkt_ts.sv |
SOF 时刻 PTP 锁存 FIFO + 按 VP 的 Timestamp RAM(起点) |
src/hololink/core/hololink.cpp:3139 |
deserialize_metadata,主机侧 metadata 布局定义 |
tools/roce_ramplayer_analyzer/roce_ramplayer_analyzer.cpp:778-788 |
fpga-latency 计算 |
tools/roce_ramplayer_analyzer/roce_ramplayer_analyzer.cpp:316 |
fpga-latency 打印 |
run_latency.sh / run_ramplayer_analyzer.sh |
采集流程与结果 grep |
2. fpga-latency 两个 PTP 时间戳的触发机制
fpga-latency 的两个端点(sof_ptp / cur_ptp)由 FPGA 硬件走两条完全不同的触发路径自动注入数据包,主机端程序只负责收包解析,不参与触发。
1. 起点 sof_ptp:SOF 脉冲 → 按 VP 缓存 → 帧尾读出
① SOF 检测(sensor 时钟域 sclk)
fpga/nv_hsb_ip/packetizer/packetizer_top.sv:219
sif_sof = i_axis_tvalid && !pkt_active && o_axis_tready
── 一帧第一个数据 beat 握手成功、且当前无活跃包 → 产生 1 拍脉冲
② 跨时钟域同步(sclk → pclk)
packetizer_top.sv:201 pulse_sync u_sof_sync
sif_sof ──► sif_sof_sync ──► o_sof[vp] (line 224)
③ 传入打包器
fpga/nv_hsb_ip/top/HOLOLINK_top.sv:1467/1514
pkt_raw_sof → sif_sof → dp_pkt_top.i_sof
④ SOF 时刻锁存 PTP(关键!)
fpga/nv_hsb_ip/dp_pkt/dp_pkt_ts.sv:52-56 reg_fifo
wr = |i_req (= i_sof)
din = {i_req, i_ptp[63:0]} ← 这一拍把"当前 PTP"和 VP 号一起推进 FIFO
⑤ 仲裁后按 VP 写入 Timestamp RAM
dp_pkt_ts.sv:118 priority_arb → ram_wr (line 200)
每个 VP 有 4 槽循环缓冲(wr_ptr/rd_ptr),
允许多帧在飞,SOF 时间戳按序排队
⑥ 帧尾读出
fpga/nv_hsb_ip/dp_pkt/dp_pkt_top.sv:513
ram_rd = (roce_state == ROCE_IDLE) && tlast_aligned
── 该 VP 的帧数据全部发完 → 取出它排队中的 SOF 时间戳
o_ptp = {16'h0, ram_rd_data} = sof_ptp ──► 拼入 wr_imm_data (line 1303)
为什么要缓存:SOF 时刻帧才刚刚开始发送,metadata 包要等整帧发完才生成,所以 SOF 时间戳必须先存起来,等帧尾再取出。
2. 终点 cur_ptp:FSM 帧尾提交 → hdr_trigger → 最后一刻拼包
① 帧尾识别
dp_pkt_top.sv:833/893 sif_eof <= sif_eof | sif_wtlast (传感器侧 tlast)
line 846 pkt_is_imm <= sif_eof[...]
── 下一包 = 本帧最后一包 = write-with-immediate(hdr_type=2'b10, opcode=0x2B)
② FSM 逐包发送:ROCE_IDLE→LOAD_STS→GRANT→CALC→STORE→HDR_WAIT
数据包全部提交后,最后一个 imm 包在 CALC 态等待 commit
③ 提交触发(关键!)
line 953 pkt_commit = commit_pending_r && !hif_busy && (pkt_is_imm || ...)
line 877 hdr_trigger <= md_disable ? !pkt_is_imm : '1
── imm 包被实际提交发往 MAC 的那一拍,hdr_trigger 拉高
④ 锁存当前 PTP(关键!)
line 1321-1322
if (hdr_trigger) cur_ptp <= cur_ptp_sync; ← 此刻的实时 PTP
⑤ 最后一刻才拼进数据包
line 1310 wr_imm_meta_r <= wr_imm_data[383:80] (pkt_commit 时先寄存前 304 bit,
其中已含 ①⑥ 读出的 sof_ptp)
line 1314 wr_imm_data_hdr = {wr_imm_meta_r, cur_ptp} ← cur_ptp 组合逻辑即时拼接
line 1338 字节反转 → wr_imm_pkt_be(大端)
── 所以 cur_ptp 采的是"包头真正流出"那一拍的值,而不是提前寄存
⑥ 包头发出
dp_pkt_hdr (line 1395): i_metadata(wr_imm_pkt_be) + i_trigger(hdr_trigger)
→ RoCE header + 4B imm + 128B metadata(payload, 前48B有效) 串成 AXI 流
→ hdr_mux (line 1464) → MAC → 以太网
3. 主机端"看到"的接力
FPGA 发出 RDMA Write-with-Immediate (dma_len=128, dp_pkt_top.sv:863)
├─ payload = 128B metadata(含两个时间戳),RDMA 地址紧接帧数据之后
│ → NIC 硬件 DMA 直接写进主机页内 frame + metadata_offset,CPU 零拷贝
└─ imm = {psn[19:0], buf_ptr[11:0]}
↓
ibv_poll_cq → IBV_WC_RECV_RDMA_WITH_IMM
page = imm & 0xFFF (roce_ramplayer_analyzer.cpp:735)
parse_metadata(frame + metadata_offset) 大端解析 (analyzer:133/745)
→ timestamp_s/ns(起点)与 metadata_s/ns(终点)同时到手
→ fpga-latency = metadata_ts − frame_ts (analyzer:778-788)
4. 设计要点小结
起点 sof_ptp |
终点 cur_ptp |
|
|---|---|---|
| 触发信号 | i_sof(帧首 beat 握手) |
hdr_trigger(imm 包 commit) |
| 锁存方式 | FIFO + 按 VP 的循环 RAM(需跨整帧保存) | 单寄存器,随用随采 |
| 拼包时机 | pkt_commit 时随 wr_imm_meta_r 寄存 |
发包头那一刻组合逻辑拼入(最新值) |
| 精度保障 | SOF 与发包并发进行,不丢帧 | 采得晚,贴近真实发出点 |
两条路径一"早"一"晚":起点必须先存后取 (SOF 时 metadata 还不存在),终点必须随采随用(保证是发出时刻的真实时间)。二者共用同一个自由运行 PTP 计数器,差值即精确的 FPGA 内部驻留时间。