ptp处理逻辑

✅ PTP 时间戳的逻辑流程(从 FPGA 到包时间戳)

在这个设计里,PTP 时间戳的生成和使用是通过一条“时间分发”链路(ptp_td_sdi)+ 本地计时器 + MAC 抓取机制完成的。下面按关键模块和流程分段说明。


1️⃣ 时间源:ptp_td_sdi + PTP 时钟(ptp_clk / ptp_sample_clk

  • **ptp_clk**:来自 SFP 参考时钟(在 fpga_core.sv 里通过 BUFG 生成),是 PTP 时间基本时钟域。
  • **ptp_td_sdi**:串行时间分发数据流(TD SDI),用于把底层外部主时钟(或上游 PTP 主源)的时间信息发送到 FPGA 内部。

2️⃣ 核心:taxi_ptp_td_leaf(PTP Time Distribution Leaf)

这个模块是 PTP 时间戳生成的“大脑”,实现过程可以拆成两大部分:

✅ 2.1 串行接收 & 解码(时间分发数据)

  • ptp_td_sdi流水线延迟后进入反序列化逻辑(16-bit 单元)。
  • td_tdata + td_tid 形式输出(内部使用 td_msg/td_index 作帧识别),代表来自主源的:
    • 时间戳秒/纳秒片段(TOD)
    • 时钟周期/频率信息(Period)
    • 其他同步标志(类似 PPS)

✅ 2.2 本地时钟生成(锁相 / 计数器)

  • 模块维护本地计数器:ts_rel_nsts_tod_nsts_tod_s(秒)等。
  • 通过 PI 控制循环ts_ns_diff / time_err_int / period_ns)将本地计时与接收到的时间分发数据同步:
    • 如果检测到本地时间与目标时间不一致,会逐步调整 period_ns(相当于调整时钟周期长度);
    • 锁定状态由 locked 输出指示(需要频率 + 时间同步都稳定)。
  • 在 ToD 模式下还会生成 output_pps / output_pps_str(1PPS 信号和同步脉冲)。

3️⃣ 时间戳输出(output_ts_rel / output_ts_tod

  • 根据参数 PTP_TS_FMT_TOD(在 fpga_core.sv 里设定),输出有两种格式:
    • 相对时间(Relative)output_ts_rel:一般表示“从某个起始点起的纳秒计数”
    • 时间-日(Time-of-Day)(TOD)output_ts_tod: 包含秒 + 纳秒(更适合直接映射到 PHC)

输出还包括对应的 *_step 信号,用来指示“此时刻刚好发生了纳秒进位”,便于做精确戳定位。


4️⃣ MAC 侧的时间戳捕获(TX/RX 的具体过程)

🔹 RX:taxi_axis_xgmii_rx_32.sv

  • 这个模块监测 XGMII “Start” 开始符号,并在检测到一帧开始时,将当前 ptp_ts 直接捕获到 tuser
  • 核心逻辑节点在 STATE_IDLE
    • 一旦检测到 Start(并且 cfg_rx_enable 有效),会把 ptp_ts_out_next = ptp_ts; 存到寄存器。
    • 最终输出到 AXI-Stream tuser[1+:PTP_TS_W](tuser[0] 用于帧错误标记等)。
  • 结果:上层逻辑(比如 DMA/软件)可以在接收数据包时,直接读取包的时间戳。

🔹 TX:taxi_eth_mac_phy_10g.sv + tx_ptp_ts_out

  • TX 路径也有对应的时间戳输出(tx_ptp_ts_out / tx_ptp_ts_step_out),用于 TX 侧时间戳(通常是用于队列完成时记录发送时间)。

5️⃣ 驱动层(Linux PHC + 描述符时间戳)

在驱动: cndm_ptp.c 中:

  • cndm_read_cpl_ts() 会通过 FPGA 寄存器读出时间戳字段(ts_ns, ts_fns, ts_s 等);
  • gettime/settime/adjtime/adjfine 通过 CNDM_CMD_OP_PTP 命令访问 FPGA 时钟,并最终跟 Linux PHC(ptp_clock)绑定。

因此,从 FPGA 里的 taxi_ptp_td_leaf 生成的时间戳,最终会通过 DMA descriptor + 驱动转换成 Linux 可用的 PHC 时间。


✅ 重点回顾(PTP 时间戳流程核心节点)

  1. 时钟 & 时间源输入ptp_clk + ptp_td_sditaxi_ptp_td_leaf
  2. 时间同步生成:PI 调节本地计时 → 输出时间戳 + PPS + locked
  3. 包时间戳绑定:MAC 核抓取当前 ptp_ts → 绑定到 AXI-Stream tuser
  4. 驱动读取:CPL 描述符 / 时钟寄存器读回 → Linux PHC

如果你希望我把“PTP 时间分发协议格式(TD 数据结构)”或“PTP 时间戳在驱动/描述符里的具体字段”也做成流程图/时序说明,告诉我我可以补充更细的步骤和对应寄存器地址。

taxi_ptp_td_leaf 在整个架构中的“时间戳处理流程”(完整路径)

taxi_ptp_td_leafPTP 时间分发(Time Distribution)模块,它把外部时钟源/PTP 主时钟的 时间信息(TD‑SDI) 解码并生成本地时间戳,同时输出同步信号 (PPS / locked),供 MAC、核心逻辑 / 驱动直接使用。这个模块是整个 FPGA 里 PTP 时间戳获取的“核心源头”。


1️⃣ 在架构中的位置(哪里用到它)

✅ 在 taxi_eth_mac_phy_10g.sv 里被实例化(TX/RX 双方向)

  • TX 侧tx_leaf_inst
  • RX 侧rx_leaf_inst

它们都通过相同的输入获得时间:

  • ptp_clk / ptp_rst:核心 PTP 时钟域
  • ptp_sample_clk:用于同步 + 采样
  • ptp_td_sdi:从外部(例如 SFP 光模块或者上游 PTP 主时钟)来的串行时间分发数据

然后它输出:

  • 生成本地时间戳:output_ts_rel / output_ts_tod
  • “步进”指示:*_step
  • PPS 信号(ToD 模式):output_ppsoutput_pps_str
  • lock 指示:locked

2️⃣ 核心流程:从串行 TD 数据到本地时钟(简单流程图)

  1. TD‑SDI 串行输入

    • ptp_td_sdi 进入 taxi_ptp_td_leaf
    • 先经过 TD_SDI_PIPELINE 延迟(对齐 + 路径平衡)。
  2. 反序列化:从 bit 流变成 16-bit 字数据

    • 模块内部用 16-bit shift‑reg 接收 bit 并每 16 bit 输出一个 td_tdata + td_tid
    • td_tid 表示该 16-bit 属于哪种时间数据(秒、纳秒、周期、标志等)。
  3. 基于 td_tid 的寄存器写入(shadow registers)

    • 根据 td_tid 把时间分发包里的字段写入:
      • dst_tod_ns_shadow / dst_tod_s_shadow(ToD 秒/纳秒)
      • dst_rel_ns_shadow(相对时间纳秒)
      • 以及 “step” 标志(time step / 时间步进)
    • 这些 shadow 只在全部字段接收完毕(shadow_valid)后才被用于更新主时钟。
  4. 本地时间生成(主时钟 / PI 环)

    • 模块维护本地时间计数器:

      • ts_rel_ns(相对纳秒计数)
      • ts_tod_ns / ts_tod_s(ToD 纳秒秒)
    • 通过 period_ns 变量控制本地“钟速”,相当于调节时钟周期,实现频率跟踪。

    • 通过 ts_ns_diff / phase_err / time_err_int -> PI 控制算法(误差积分)来逼近外部时间源:

      ✅ 如果本地时间与接收到的 TD 时间发生差值(ts_ns_diff),模块会逐步调整 period_ns,直到锁定。

  5. 时间同步 / 锁定输出

    • 当本地时间与外部分发时间一致时,locked 输出变高。
    • 若 ToD 模式,还会输出 output_pps / output_pps_str(1PPS 脉冲 + “strobe”)。
  6. 最终输出(给 MAC / 其他模块用)

    • output_ts_rel:用于 MAC RX/TX 时间戳(相对纳秒格式)
    • output_ts_tod:用于 ToD 时间戳(秒 + 纳秒格式)
    • *_step:标识该周期刚跨纳秒边界(用于补偿计数器跨界)
    • locked:告诉上层时间是否同步稳定

3️⃣ 它的输出在系统里怎么用(典型用途)

✅ 1) 以太网包时间戳(RX/TX)

taxi_axis_xgmii_rx_32.sv 内部,

  • 收到帧开始(XGMII START)时,会把当前 ptp_ts 捕获并写入 AXIS tuser
  • 所以每帧都会被“标签化”一个精确时间戳(来自 taxi_ptp_td_leaf)。

✅ 2) Linux PHC / 驱动时间读取

驱动 cndm_ptp.c 通过读寄存器(比如 0x0324, 0x0328, 0x032C 等)获取当前 PTP 时钟,最终交给 Linux PHC,使系统可做 phc_gettime, phc_settime, phc_adjtime 等操作。


4️⃣ 核心关键点总结(你需要记住的重点)

  • ptp_td_sdi 是时间分发数据源,taxi_ptp_td_leaf 把它解码为时间
  • 它输出的 output_ts_\* 是整个 FPGA 里所有时间戳的唯一来源(MAC timestamp / 驱动 timestamp)
  • 同步算法不是简单“直接写”,而是通过 PI 回路平滑锁定(避免抖动、避免突变)
  • 锁定与 PPS 由模块直接产生,外部逻辑可直接使用 locked / pps

如果你想要我进一步说明「TD‑SDI 数据包格式(各个 td_tid 对应什么内容)」或者「驱动里寄存器对应的具体地址/字段(比如 ts_fns, ts_ns

✅ 找到写入 AXIS tuser 的代码(taxi_axis_xgmii_rx_32.sv

taxi_axis_xgmii_rx_32.sv 里,ptp_ts 在收到帧开始(XGMII START)时被捕获,并被 **挂到 AXI-Stream m_axis_rx.tuser**:

然后在输出端写到 tuser 里(user 字段最低位用于“错误标记”,其余位用于时间戳):


✅ “上层 PTP 协议”如何拿到时间戳并计算(从 tuser 到 PTP 算法)

1) MAC → DMA(tuser 被携带进 DMA 描述符)

  • m_axis_rx.tuser 会被 taxi_dma_client_axis_sink 读出并放入 DMA descriptor 的 sts_user 字段中。
  • cndm_micro_rx.sv 中,它被拆成两部分:
    • 低位 sts_user[0] 作为时间戳有效标志(tuser 本来用于错误/标记)
    • 剩余高位 sts_user[USER_W-1:1] 作为时间戳数据(rel 或 tod 格式)

代码摘录(cndm_micro_rx.sv):

如果需要 TOD 格式,它还会经过 taxi_ptp_td_rel2tod 进行转换,然后把时间戳写回 rx_ptp_ts


2) DMA 完成后 → completion 队列里带时间戳

cndm_micro_rx 会把最终 rx_ptp_ts 写入 completion packet(m_axis_cpl.tdata),然后被 PCIe 传回主机。

在驱动里(cndm_rq.c)对应的完成处理:

cndm_read_cpl_ts() 从 completion entry(struct cndm_cpl)取出:

  • ts_ns(纳秒部分)
  • ts_s(秒高位,用寄存器补全)

所以最终时间戳被写入 skb->tstamp


3) 上层 PTP 协议如何使用这个时间戳(典型流程)

上层 PTP 进程(例如 ptp4l)通过 Linux 的 SO_TIMESTAMPING/phc 机制:

  1. 用户态开启 SO_TIMESTAMPING_RX_HARDWARE / SO_TIMESTAMPING_TX_HARDWARE

  2. 内核驱动(上面那段代码)把硬件时间戳放到 skb_hwtstamps(skb)

  3. 用户态从 recvmsg() 拿到 SCM_TIMESTAMPING 控制消息,得到 struct timespec(硬件时间)。

  4. PTP 协议用这些时间戳计算偏移与延迟,例如:

    • Sync/Follow_Up:t1 = 发送 Sync 的硬件时间(TX timestamp)
    • Sync 接收:t2 = 本地接收时间(RX timestamp)
    • DelayReq 发出:t3 = 发送时间(TX timestamp)
    • DelayResp 接收:t4 = 接收时间(RX timestamp)

    经典公式:

    • offset = (t2 + t3 - t1 - t4) / 2
    • delay = (t2 - t1 + t4 - t3) / 2

驱动本身不“算公式”,它只把硬件时间戳交给 Linux stack / ptp4l,后者负责把时间戳和 PTP 报文里本身的时间戳字段(originTimestamp 等)关联起来。


✅ 关键信息总结

  • **写入 AXIS tuser 的代码在 taxi_axis_xgmii_rx_32.sv**:m_axis_rx.tuser[1+:PTP_TS_W] = ptp_ts_out_reg;
  • 时间戳通过 DMA 描述符状态通道传递到 completiondma_desc.sts_user -> cndm_micro_rx -> completion cndm_cpl
  • 驱动读取 completion 并写入 skb_hwtstamps → 上层 PTP 进程可以拿到硬件时间戳并用于 offset/delay 计算

ptp_td_sdi 分析讲解与实现流程

ptp_td_sdi(PTP Time Distribution Serial Data Input)是 FPGA 内部 PTP 时间分发模块(taxi_ptp_td_leaf)的串行输入信号,用于接收来自外部 PTP 主时钟(或上游时间源)的时间同步数据。它是整个 PTP 时间戳生成链路的“源头输入”,通过串行 bit 流传递时间戳、时钟周期等信息,实现 FPGA 本地时钟与外部主时钟的同步。


1️⃣ ptp_td_sdi 的基本分析

✅ 信号特性

  • 类型:单 bit 串行输入(logic 类型)。
  • 时钟域:在 ptp_clk 时钟域下采样(通常是 PTP 参考时钟,如 156.25 MHz)。
  • 数据格式:串行 bit 流,编码为 16-bit 字(word)的序列,每个字代表时间数据包的不同字段。
  • 协议:自定义 PTP 时间分发协议(TD - Time Distribution),非标准 PTP 报文,而是 FPGA 内部优化的串行传输格式。
  • 用途:将外部主时钟的时间信息(秒、纳秒、周期)传递到 FPGA,用于生成本地时间戳(output_ts_rel / output_ts_tod)和 PPS 信号。

✅ 数据包结构(从代码反推)

每个 TD 数据包由多个 16-bit 字组成,字段通过 td_tid(Type ID)标识:

  • **tid[7:4]**:消息类型(msg_reg,例如时间戳类型)。
  • **tid[3:0]**:字段索引(index_reg,从 0 开始递增)。
  • **tdata[15:0]**:数据内容。

典型字段(从 taxi_ptp_td_leaf.sv 解析逻辑):

  • tid=0x01:ToD 纳秒低 16-bit。
  • tid=0x02:ToD 纳秒高 14-bit + 步进标志。
  • tid=0x03:ToD 秒低 16-bit。
  • tid=0x04:ToD 秒中 16-bit。
  • tid=0x05:ToD 秒高 16-bit。
  • tid=0x06:相对纳秒低 16-bit。
  • tid=0x07:相对纳秒高 16-bit。
  • tid=0x08:相对纳秒最高 16-bit。
  • tid=0x0B:时钟周期低 16-bit。
  • tid=0x0C:时钟周期中 16-bit。
  • tid=0x0D:时钟周期高 8-bit。
  • tid=0x11:ToD 偏移纳秒。

包以 td_tlast=1 结束(当串行 bit 为 1 时)。

✅ 优势

  • 低开销:串行传输,节省引脚和带宽。
  • 实时性:直接在 PTP 时钟域处理,避免跨时钟同步延迟。
  • 灵活:支持相对时间(Relative)和时间-日(ToD)两种格式。

2️⃣ ptp_td_sdi 的实现流程(从外部到 FPGA 内部)

✅ 流程概述

  1. 外部时间源生成串行数据(硬件/软件)。
  2. FPGA 接收并反序列化taxi_ptp_td_leaf)。
  3. 解析数据字段(根据 tid 提取时间信息)。
  4. 更新本地时间戳(PI 控制同步)。
  5. 输出同步时间(给 MAC / 驱动使用)。

✅ 详细步骤

步骤 1: 外部时间源生成串行数据

  • 来源:外部 PTP 主时钟设备(例如,通过 SFP 光模块或专用串行接口连接)。
  • 生成方式
    • 主时钟设备周期性发送 TD 数据包(例如,每秒或按需)。
    • 数据包字段按 tid 顺序串行化:先发送 tid=0x01 的 16-bit 数据,然后 tid=0x02,以此类推。
    • 每个 16-bit 字以 LSB-first 方式串行发送(td_shift_reg 移位逻辑)。
    • 包起始:发送 bit=0(触发 FPGA 开始计数 16 bit)。
    • 包结束:发送 bit=1(td_tlast)。
  • 示例:如果 ToD 时间是 2026-03-19 12:00:00.000000000,纳秒=0,秒=1700846400,则:
    • tid=0x01: 纳秒低 16-bit = 0x0000
    • tid=0x03: 秒低 16-bit = 0x6400 (部分秒值)
    • … 依此类推。

步骤 2: FPGA 接收并反序列化(taxi_ptp_td_leaf 模块)

  • 流水线延迟ptp_td_sdi 先通过 TD_SDI_PIPELINE(默认 2 级)延迟,优化时序路径。
  • 移位接收
    • ptp_clk 上升沿,bit 移入 td_shift_reg(16-bit 寄存器)。
  • 帧检测
    • ptp_td_sdi == 0 时,启动 16-bit 计数器(bit_cnt_reg <= 16)。
    • 计数到 0 时,输出 td_tdata(当前 16-bit 字)、td_tid(类型 ID)、td_tlast(结束标志)。
  • 同步到主时钟域td_sync_reg 翻转触发跨时钟同步(td_sync_sync1/2/3),将数据传递到 clk 域。

步骤 3: 解析数据字段

  • 根据 dst_td_tid_reg 写入 shadow 寄存器
  • 字段累积:多个 16-bit 字组合成完整时间值(例如,纳秒 = 低16 + 高14)。
  • 有效性检查:当所有字段接收完毕(dst_tod_shadow_valid_next = 1'b1),标记 shadow 数据有效。

步骤 4: 更新本地时间戳(PI 控制同步)

  • 加载 shadow 数据:当 dst_load_cnt_reg == 0 时,将 shadow 复制到主时间寄存器(ts_tod_nsts_tod_s 等)。
  • 误差计算:比较本地时间与接收到的外部时间,计算 ts_ns_diff(纳秒差值)。
  • PI 控制
    • 积分器:time_err_int_next = $signed(time_err_int_reg) + ts_ns_diff
    • 输出:调整 period_ns(时钟周期),逼近外部时间。
    • 锁定:当误差稳定时,locked 输出高电平。
  • 时间生成:基于调整后的周期,更新 ts_rel_ns / ts_tod_ns / ts_tod_s

步骤 5: 输出同步时间

  • 时间戳输出
    • output_ts_rel:相对纳秒(用于 MAC RX/TX 时间戳)。
    • output_ts_tod:ToD 秒 + 纳秒(用于 PHC)。
  • PPS 输出:在 ToD 模式下,当秒进位时生成 output_pps / output_pps_str
  • 传递到 MACptp_ts 输入到 taxi_axis_xgmii_rx_32,用于帧时间戳捕获。
  • 传递到驱动:通过 DMA / completion,最终写入 skb_hwtstamps

✅ 关键代码片段总结

  • 反序列化核心taxi_ptp_td_leaf.sv):
  • 字段解析
  • PI 同步

✅ 注意事项

  • 时钟同步ptp_td_sdi 必须在 ptp_clk 域稳定采样,避免亚稳态。
  • 数据完整性:外部源需确保包顺序和完整性(FPGA 不校验 CRC)。
  • 调试:可以通过 td_tvalid_reg / dst_td_tvalid_reg 监控接收状态。
  • 扩展:如果需要更高精度,可调整 TD_SDI_PIPELINE 或增加字段。

如果你需要“TD 数据包的具体 bit 布局图”或“如何在外部硬件生成 ptp_td_sdi”,我可以进一步扩展。—

✅ 注意事项

  • 时钟同步ptp_td_sdi 必须在 ptp_clk 域稳定采样,避免亚稳态。
  • 数据完整性:外部源需确保包顺序和完整性(FPGA 不校验 CRC)。
  • 调试:可以通过 td_tvalid_reg / dst_td_tvalid_reg 监控接收状态。
  • 扩展:如果需要更高精度,可调整 TD_SDI_PIPELINE 或增加字段。

这个问题非常关键,本质是在问:

👉 Taxi 里的 ptp_td_sdi(Time Distribution Serial Interface)到底从哪来?LinuxPTP 怎么把“PTP时间”变成这根串行线?

先给结论(避免走弯路):


✅ 一句话结论

👉 LinuxPTP 本身不会直接产生 ptp_td_sdi
👉 ptp_td_sdiFPGA/NIC驱动/硬件逻辑生成的“时间分发流”

LinuxPTP只是:

1
算时间 → 提供时间 → 写入PHC/NIC → FPGA再编码成ptp_td_sdi

一、ptp_td_sdi 本质是什么?

从你给的代码可以看出:

1
input wire logic ptp_td_sdi

然后第一段逻辑在干:

1
td_shift_reg <= {ptp_td_sdi, td_shift_reg[15:1]};

👉 说明:

✅ 它是一个 串行bit流接口

类似:

1
1根线 + 时钟 → 传输时间帧

👉 它传的不是“包”,而是:

时间帧(TD Frame)

从代码解析:

1
td_tid_reg <= {td_msg_reg, td_index_reg};

👉 数据结构类似:

1
[MSG_ID | WORD_INDEX] + DATA(16bit)

👉 实际承载内容:

你在代码中看到:

1
2
src_ns_shadow_reg
src_period_shadow_reg

说明 TD 流里包含:

✔ 时间信息

  • ns(纳秒)
  • 秒(ToD)

✔ 时钟参数

  • period(周期)
  • step(是否跳变)

二、ptp_td_sdi 在系统中的位置

完整路径应该是:

1
2
3
4
5
6
7
8
9
10
11
     LinuxPTP (ptp4l)

PHC (硬件时钟)

NIC driver

FPGA (TD master encoder)

ptp_td_sdi (串行)

taxi_ptp_td_leaf (你这段代码)

三、LinuxPTP 在这里到底干了什么?

LinuxPTP只做两件事:


1️⃣ 计算“标准PTP时间”

1
Sync / Follow_Up / Delay_Req / Delay_Resp

得到:

1
当前精确时间(master time)

2️⃣ 写入 PHC(网卡硬件时钟)

通过:

1
2
clock_settime()
ioctl(PHC)

👉 把时间写到 NIC/FPGA:

1
PHC = 当前PTP时间

四、关键问题:ptp_td_sdi 是谁生成的?

👉 不是 LinuxPTP

而是:

✅ FPGA 或 NIC 硬件逻辑生成


常见实现方式(3种)


🟢 方式1:FPGA内部 TD Master(推荐)

1
2
3
4
5
PHC寄存器(FPGA)

TD Encoder(你自己写)

ptp_td_sdi

具体实现:

你需要写一个模块:

1
2
3
4
5
module ptp_td_master (
input clk,
input [63:0] ptp_time,
output ptp_td_sdi
);

做的事情:

1
时间 → 切成16bit → 加header → 串行输出

🟡 方式2:CPU/驱动写FPGA寄存器

1
LinuxPTP → driver → AXI → FPGA寄存器

FPGA:

1
读取寄存器 → TD编码 → ptp_td_sdi

🔴 方式3:软件bit-bang(几乎不用)

CPU直接生成bit流 → GPIO

👉 不现实(抖动大)


五、TD编码协议(从代码反推)

你这段代码已经暴露协议细节:


1️⃣ 帧起始

1
2
if (ptp_td_sdi == 0) begin
bit_cnt_reg <= 16;

👉 0 = start bit


2️⃣ 数据长度

1
每帧 = 16 bit

3️⃣ 数据结构

1
2
[MSG_ID | INDEX] → td_tid
DATA → td_tdata

4️⃣ 示例(推测)

tid 含义
1-5 ToD seconds
6-8 ns
11-13 period

六、LinuxPTP 如何“间接生成” ptp_td_sdi?

关键链路:


Step 1:LinuxPTP 同步时间

1
ptp4l -i eth0

Step 2:写 NIC 硬件时钟(PHC)

1
phc2sys -s eth0 -c CLOCK_REALTIME

Step 3:驱动把 PHC 映射到 FPGA

1
driver → BAR / AXI → FPGA time register

Step 4:FPGA 读取时间

1
ptp_time_reg <= AXI_read();

Step 5:编码成 TD 流

1
time → TD frame → 串行输出

七、和 Taxi 的关系(关键理解)

Taxi 实际分成两块:


1️⃣ TD Master(你没贴出来的部分)

👉 负责生成:

1
ptp_td_sdi

2️⃣ TD Leaf(你贴的代码)

👉 负责:

1
恢复时间 + PI同步

八、为什么要搞 ptp_td_sdi?(核心价值)

相比标准 PTP:

项目 Ethernet PTP TD
介质 网络包 串行线
抖动 极低
延迟 不确定 固定
精度 100ns~1us <10ns

👉 本质:

1
TD = “PTP物理层加速版”

九、结合你当前项目(重点建议)

你现在做:

👉 FPGA NIC + PTP + TSN

建议这样设计:


✅ 推荐架构

1
2
3
4
5
6
7
8
9
LinuxPTP

PHC(FPGA寄存器)

TD Master(FPGA)

ptp_td_sdi

多个 Leaf(多端口/多FPGA)

✅ 如果是 Zynq

1
2
3
4
5
ARM (LinuxPTP)
↓ AXI
PL (TD Master)

ptp_td_sdi

👉 ARM作用:

  • 跑 ptp4l
  • 更新 PHC

十、你可以做的进阶优化(非常适合你)

🔥 1:完全去 LinuxPTP

👉 FPGA实现:

  • Sync解析
  • Delay计算
  • PI servo

🔥 2:做“多节点同步网络”

1
1 master FPGA → N leaf FPGA

🔥 3:结合 TSN

1
TD clock → Qbv scheduler

如果你想更深入

我可以帮你:

✅ 写一个 ptp_td_master Verilog模板(可直接对接你这段leaf)
✅ 或给你画一个 Zynq + Taxi + LinuxPTP完整架构图(工程级)
✅ 或做一个 “去LinuxPTP纯FPGA PTP NIC方案”(非常适合你现在的方向)

你这段代码已经把 Taxi / Corundum 系列 PTP 架构里最核心的一条链路打通了

👉 LinuxPTP(软件) → PHC(寄存器) → ptp_td_sdo(串行) → ptp_td_sdi(分发) → 各模块硬件时间

下面我帮你把这条链路完整拆开(工程级视角)


一、核心结论(先给你抓重点)

ptp_td_sdi 本质是什么?

👉 不是 PTP 报文
👉 是 已经同步好的“时间码流(Time Distribution Stream)”

可以理解为:

1
2
3
4
5
LinuxPTP → 算出时间/频率修正
→ 写寄存器(PHC)
→ FPGA生成“标准时间流”
→ ptp_td_sdo 串行发出
→ ptp_td_sdi 在全FPGA广播

👉 本质:FPGA内部“时间总线”


二、ptp_td_sdi 的生成链路(你这段代码)

我们按你代码真实路径走一遍:


1️⃣ LinuxPTP(用户态)

运行:

1
2
ptp4l -i eth0 -m
phc2sys -s eth0 -c CLOCK_REALTIME

它干三件事:

✔ 1. 解析PTP报文

  • Sync / Follow_Up
  • Delay_Req / Delay_Resp

算出:

1
2
offset = master_time - local_time
drift = 频率偏差

✔ 2. 调整 PHC(硬件时钟)

通过:

1
clock_adjtime()

驱动调用:

  • adjtime(时间偏移)
  • adjfreq(频率调整)

2️⃣ FPGA 驱动 → 写 APB 寄存器

对应你代码:

1
2
3
4
5
6
7
8
// 写时间
0x54 / 0x58 / 0x5C -> set_ptp_ts_tod

// 微调
0x50 -> offset_ptp_ts_tod

// 调频
0x78 / 0x7C -> set_ptp_period

👉 LinuxPTP 做的事情就是:

✔ Step(跳变)

1
直接写时间

✔ Slew(微调)

1
offset +period

3️⃣ taxi_ptp_td_phc(核心时钟模块)

👉 这是时间生成器

功能:

1
2
3
4
5
6
输入:
时间设置 / offset / period

输出:
ptp_td_sdo(串行时间流)
PPS

ptp_td_sdo 生成本质:

它会不断输出:

1
2
时间帧:
[MSG_ID | INDEX | DATA]

类似:

字段 含义
ns 纳秒
sec
period 时钟周期
control step标志

👉 以串行 bit stream输出(你看到的 sdo)


4️⃣ ptp_td_sdi(广播 + 反序列化)

taxi_ptp_td_leaf

1
ptp_td_sdi → shift_reg → td_tdata_reg

做了三件事:


✔ ① 串行 → 并行(Deserializer)

1
td_shift_reg <= {ptp_td_sdi, td_shift_reg[15:1]};

每 16bit 拼成:

1
td_tdata_reg

✔ ② 协议解析

1
td_tid_reg <= {td_msg, td_index}

解析:

TID 含义
1~5 ToD 时间
6~8 ns
11~13 period

✔ ③ 时间恢复

恢复:

1
2
src_ns_reg
src_period_reg

5️⃣ 硬件 PI Servo(关键)

你代码里这一段:

1
2
3
// PI control
time_err_int_reg
period_ns_next

👉 作用:

1
让本地 clk 收敛到 ptp_td_sdi 时间

三、完整数据流(系统级)

给你一张工程图(重点):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──────────────┐
│ LinuxPTP │
│ (ptp4l) │
└─────┬────────┘
│ offset / freq

┌──────────────┐
│ Driver │
│ (PHC接口) │
└─────┬────────┘
│ APB写寄存器

┌──────────────┐
│ ptp_td_phc │ ← 时间源
│ (Master) │
└─────┬────────┘
│ ptp_td_sdo

┌──────────────┐
│ ptp_td_sdi │ ← 时间分发总线
└─────┬────────┘

┌──────────────┐
│ ptp_td_leaf │ ← 每个模块
│ (PI同步) │
└─────┬────────┘

MAC / TSU / DMA / NIC

四、关键理解(你必须掌握)

1️⃣ LinuxPTP 不直接控制 FPGA 时钟

👉 它只做:

1
计算 offset + 频率

👉 真正执行的是:

1
FPGA 硬件 PI servo

2️⃣ ptp_td_sdi ≠ PTP 报文

👉 区别:

项目 PTP报文 ptp_td_sdi
层级 网络层 FPGA内部
内容 Sync/Delay 时间流
频率 ms级 cycle级

3️⃣ 为什么这样设计?

核心优势:

✔ 极低延迟

1
2
3
不走CPU
不走AXI
不走PCIe

✔ 全局同步

1
一个时间 → 全FPGA一致

✔ 可扩展 TSN / TDMA

👉 这也是 Taxi / Corundum 强项


五、你这个设计的工程价值(重点)

结合你背景(FPGA + 网络安全 + TSN):

这个架构可以直接扩展:


✔ 1. TSN Qbv

用:

1
ptp_sync_ts_rel

做:

1
时间窗口调度

✔ 2. 多端口时间一致 NIC

多个 MAC:

1
共享 ptp_td_sdi

→ 纳秒级同步


✔ 3. 硬件时间戳 NIC(你项目方向)

结合你:

AI + 网络安全 FPGA

可以做:

1
时间 + 流量特征 + 安全检测

六、给你一个“本质一句话总结”

👉 整个系统可以用一句话概括:

LinuxPTP 负责“算时间”,FPGA 负责“发时间”,ptp_td_sdi 负责“分时间”,硬件 PI 负责“锁时间”。


七、如果你要继续深入(建议方向)

我建议你下一步可以继续问我:

👉 1. 如何自己实现 ptp_td_sdi 协议(替代 taxi)

👉 2. 如何把这个架构移植到 Zynq(PS + PL)

👉 3. 如何结合 TSN(Qbv / Qch / CQF)

👉 4. 如何做“完全脱离 LinuxPTP 的纯硬件 Grandmaster”

我可以帮你把它直接升级成:

👉 军工级 TSN + PTP FPGA 网卡架构

下面把你关心的 LinuxPTP 的 offset / 频率计算 + PI servo 原理,从“工程实现 + 数学本质 + FPGA可落地”三个层面讲清楚,尽量贴近你现在在做的 FPGA PTP / ptp_td_sdi / 硬件servo 场景。


一、先把核心问题说清楚

LinuxPTP(如 ptp4l)本质做三件事:

1️⃣ 算 offset(时间偏差)

1
offset = 本地时钟 - 主时钟

2️⃣ 算 delay(链路时延)

3️⃣ 用 servo 算法调整本地时钟:

  • 调 phase(时间跳变 / offset correction)
  • 调 frequency(频率微调)

二、PTP四步报文 → offset计算(核心)

2-step clock 为例:

报文时间戳

名称 含义
t1 master 发 Sync 时间
t2 slave 收 Sync 时间
t3 slave 发 Delay_Req
t4 master 收 Delay_Req

1️⃣ 计算 delay

1
delay = ((t2 - t1) + (t4 - t3)) / 2

2️⃣ 计算 offset

1
offset = (t2 - t1) - delay

👉 等价写法:

1
offset = ((t2 - t1) - (t4 - t3)) / 2

直观理解

1
2
3
4
5
master -----> slave
t1 t2

slave -----> master
t3 t4
  • (t2 - t1):包含 delay + offset
  • (t4 - t3):包含 delay - offset

👉 一减就把 delay 抵消了


三、LinuxPTP servo架构

LinuxPTP 中核心模块:

  • clock.c
  • servo.c
  • pi.c(关键)

servo输入

1
2
3
4
5
6
7
输入:
offset (ns)
delay (ns)
本地时间戳

输出:
freq_adjust (ppb)

四、PI Servo 原理(核心)

LinuxPTP 默认使用:

👉 PI控制器(比例 + 积分)


1️⃣ 控制目标

让:

1
offset → 0

2️⃣ 控制量

调节:

1
clock frequency(频率)

3️⃣ PI公式

1
freq = Kp * offset + Ki * ∫offset dt

离散化后(LinuxPTP实际用):

1
freq[n] = Kp * offset[n] + Ki * sum(offset)

4️⃣ 各项含义

含义
Kp 快速响应
Ki 消除长期误差
sum(offset) 历史累计误差

五、LinuxPTP 实际实现细节(重点)

pi.c 中核心逻辑:

1️⃣ 第一阶段:初始同步(step)

1
2
if (abs(offset) > step_threshold)
step_clock(offset);

👉 大误差直接跳时间(避免慢慢调)


2️⃣ 稳定后进入 PI 模式

1
2
ppb = Kp * offset + Ki * drift;
drift += offset;

👉 这里:

  • ppb:频率调整(parts per billion)
  • drift:积分项

3️⃣ 限幅(非常关键)

1
ppb = clamp(ppb, -max, +max);

👉 防止震荡 / 发散


4️⃣ 低通滤波(实际有)

LinuxPTP 会对 offset 做滤波:

1
filtered_offset = α * new + (1-α) * old

👉 抗 jitter


六、频率调节本质(关键理解)

你 FPGA 很重要的一点👇


时钟模型

1
T_local = T_real * (1 + ε)

其中:

  • ε = frequency error

PI servo做的事:

调整 ε,使:

1
d(offset)/dt → 0

换句话说:

操作 作用
offset correction 修正当前时间
frequency correction 修正未来时间

七、LinuxPTP → 硬件映射(你最关心)

你现在有:

1
LinuxPTP → ptp_td_sdi → FPGA PHC

1️⃣ LinuxPTP输出

通过:

  • clock_adjtime()
  • adjtimex()

给 NIC:

两类控制:

A. step(时间跳变)

1
settimeofday()

👉 对应 FPGA:

1
input_ts_tod_valid

B. frequency(频率调节)

1
adjfine / adjfreq

👉 对应 FPGA:

1
2
input_period_ns
input_period_fns

或:

1
drift compensation

2️⃣ 转成 ptp_td_sdi

在你的架构中:

1
2
3
4
5
6
7
8
9
LinuxPTP

PHC(软件)

FPGA PHC(taxi_ptp_td_phc)

ptp_td_sdo

ptp_td_sdi(广播)

八、ptp_td_sdi里承载的本质信息

其实只有三类:


1️⃣ 当前时间

1
ToD timestamp

2️⃣ 步进标志

1
ts_step

3️⃣ 频率信息(隐含)

通过:

1
period_ns + period_fns

👉 等价:

1
freq adjustment

九、用一句话总结 PI servo

👉 LinuxPTP 本质在做:

1
用 offset 推导 frequency correction,让未来不再产生 offset

十、结合你FPGA设计的关键建议(非常重要)

如果你要做 硬件 servo(比 LinuxPTP 强10倍)


1️⃣ 把 PI 搬进 FPGA

直接实现:

1
freq = Kp * offset + Ki * integral

2️⃣ offset来源

来自:

1
硬件 timestamp pipeline

而不是 Linux


3️⃣ 优势

软件PI 硬件PI
ms级 ns级
jitter大 jitter极低
非实时 完全实时

4️⃣ 可进阶

  • Kalman Filter(更高级)
  • Hybrid servo(CPU + FPGA)

十一、总结(核心理解)

LinuxPTP本质:

1
PTP报文 → 算 offset → PI servo → 调频率 → 消除offset

关键公式:

1
2
offset = ((t2 - t1) - (t4 - t3)) / 2
freq = Kp * offset + Ki * ∑offset

硬件对应:

LinuxPTP FPGA
offset timestamp差
freq period_ns/fns
step ts_tod_valid

如果你下一步想深入(建议)

可以继续往这几个方向走(我可以帮你直接出RTL级方案):

  1. FPGA实现 PI servo(Verilog版)
  2. ptp_td_sdi协议反向解析(bit级)
  3. 硬件 timestamp → offset计算 pipeline
  4. 亚纳秒级 TSN / gPTP servo设计

直接说你要哪一块,我可以给你做到“可上板”的级别。


ptp处理逻辑
http://witbit.cn/FPGA/taxi/ptp处理逻辑.html
作者
朝彻
发布于
2026年3月19日
许可协议