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_ns、ts_tod_ns、ts_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)
- 相对时间(Relative)
输出还包括对应的 *_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] 用于帧错误标记等)。
- 一旦检测到 Start(并且
- 结果:上层逻辑(比如 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 时间戳流程核心节点)
- 时钟 & 时间源输入:
ptp_clk+ptp_td_sdi→taxi_ptp_td_leaf - 时间同步生成:PI 调节本地计时 → 输出时间戳 + PPS + locked
- 包时间戳绑定:MAC 核抓取当前
ptp_ts→ 绑定到 AXI-Stream tuser - 驱动读取:CPL 描述符 / 时钟寄存器读回 → Linux PHC
如果你希望我把“PTP 时间分发协议格式(TD 数据结构)”或“PTP 时间戳在驱动/描述符里的具体字段”也做成流程图/时序说明,告诉我我可以补充更细的步骤和对应寄存器地址。
✅ taxi_ptp_td_leaf 在整个架构中的“时间戳处理流程”(完整路径)
taxi_ptp_td_leaf 是 PTP 时间分发(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_pps、output_pps_str - lock 指示:
locked
2️⃣ 核心流程:从串行 TD 数据到本地时钟(简单流程图)
TD‑SDI 串行输入
ptp_td_sdi进入taxi_ptp_td_leaf。- 先经过
TD_SDI_PIPELINE延迟(对齐 + 路径平衡)。
反序列化:从 bit 流变成 16-bit 字数据
- 模块内部用 16-bit shift‑reg 接收 bit 并每 16 bit 输出一个
td_tdata+td_tid。 td_tid表示该 16-bit 属于哪种时间数据(秒、纳秒、周期、标志等)。
- 模块内部用 16-bit shift‑reg 接收 bit 并每 16 bit 输出一个
基于
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)后才被用于更新主时钟。
- 根据
本地时间生成(主时钟 / 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,直到锁定。
时间同步 / 锁定输出
- 当本地时间与外部分发时间一致时,
locked输出变高。 - 若 ToD 模式,还会输出
output_pps/output_pps_str(1PPS 脉冲 + “strobe”)。
- 当本地时间与外部分发时间一致时,
最终输出(给 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捕获并写入 AXIStuser; - 所以每帧都会被“标签化”一个精确时间戳(来自
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 机制:
用户态开启
SO_TIMESTAMPING_RX_HARDWARE/SO_TIMESTAMPING_TX_HARDWARE。内核驱动(上面那段代码)把硬件时间戳放到
skb_hwtstamps(skb)。用户态从
recvmsg()拿到SCM_TIMESTAMPING控制消息,得到struct timespec(硬件时间)。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
- Sync/Follow_Up:
驱动本身不“算公式”,它只把硬件时间戳交给 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 描述符状态通道传递到 completion(
dma_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 内部)
✅ 流程概述
- 外部时间源生成串行数据(硬件/软件)。
- FPGA 接收并反序列化(
taxi_ptp_td_leaf)。 - 解析数据字段(根据 tid 提取时间信息)。
- 更新本地时间戳(PI 控制同步)。
- 输出同步时间(给 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_ns、ts_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。 - 传递到 MAC:
ptp_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_sdi 是 FPGA/NIC驱动/硬件逻辑生成的“时间分发流”
LinuxPTP只是:
1 | |
一、ptp_td_sdi 本质是什么?
从你给的代码可以看出:
1 | |
然后第一段逻辑在干:
1 | |
👉 说明:
✅ 它是一个 串行bit流接口
类似:
1 | |
👉 它传的不是“包”,而是:
时间帧(TD Frame)
从代码解析:
1 | |
👉 数据结构类似:
1 | |
👉 实际承载内容:
你在代码中看到:
1 | |
说明 TD 流里包含:
✔ 时间信息
- ns(纳秒)
- 秒(ToD)
✔ 时钟参数
- period(周期)
- step(是否跳变)
二、ptp_td_sdi 在系统中的位置
完整路径应该是:
1 | |
三、LinuxPTP 在这里到底干了什么?
LinuxPTP只做两件事:
1️⃣ 计算“标准PTP时间”
1 | |
得到:
1 | |
2️⃣ 写入 PHC(网卡硬件时钟)
通过:
1 | |
👉 把时间写到 NIC/FPGA:
1 | |
四、关键问题:ptp_td_sdi 是谁生成的?
👉 不是 LinuxPTP
而是:
✅ FPGA 或 NIC 硬件逻辑生成
常见实现方式(3种)
🟢 方式1:FPGA内部 TD Master(推荐)
1 | |
具体实现:
你需要写一个模块:
1 | |
做的事情:
1 | |
🟡 方式2:CPU/驱动写FPGA寄存器
1 | |
FPGA:
1 | |
🔴 方式3:软件bit-bang(几乎不用)
CPU直接生成bit流 → GPIO
👉 不现实(抖动大)
五、TD编码协议(从代码反推)
你这段代码已经暴露协议细节:
1️⃣ 帧起始
1 | |
👉 0 = start bit
2️⃣ 数据长度
1 | |
3️⃣ 数据结构
1 | |
4️⃣ 示例(推测)
| tid | 含义 |
|---|---|
| 1-5 | ToD seconds |
| 6-8 | ns |
| 11-13 | period |
六、LinuxPTP 如何“间接生成” ptp_td_sdi?
关键链路:
Step 1:LinuxPTP 同步时间
1 | |
Step 2:写 NIC 硬件时钟(PHC)
1 | |
Step 3:驱动把 PHC 映射到 FPGA
1 | |
Step 4:FPGA 读取时间
1 | |
Step 5:编码成 TD 流
1 | |
七、和 Taxi 的关系(关键理解)
Taxi 实际分成两块:
1️⃣ TD Master(你没贴出来的部分)
👉 负责生成:
1 | |
2️⃣ TD Leaf(你贴的代码)
👉 负责:
1 | |
八、为什么要搞 ptp_td_sdi?(核心价值)
相比标准 PTP:
| 项目 | Ethernet PTP | TD |
|---|---|---|
| 介质 | 网络包 | 串行线 |
| 抖动 | 高 | 极低 |
| 延迟 | 不确定 | 固定 |
| 精度 | 100ns~1us | <10ns |
👉 本质:
1 | |
九、结合你当前项目(重点建议)
你现在做:
👉 FPGA NIC + PTP + TSN
建议这样设计:
✅ 推荐架构
1 | |
✅ 如果是 Zynq
1 | |
👉 ARM作用:
- 跑 ptp4l
- 更新 PHC
十、你可以做的进阶优化(非常适合你)
🔥 1:完全去 LinuxPTP
👉 FPGA实现:
- Sync解析
- Delay计算
- PI servo
🔥 2:做“多节点同步网络”
1 | |
🔥 3:结合 TSN
1 | |
如果你想更深入
我可以帮你:
✅ 写一个 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 | |
👉 本质:FPGA内部“时间总线”
二、ptp_td_sdi 的生成链路(你这段代码)
我们按你代码真实路径走一遍:
1️⃣ LinuxPTP(用户态)
运行:
1 | |
它干三件事:
✔ 1. 解析PTP报文
- Sync / Follow_Up
- Delay_Req / Delay_Resp
算出:
1 | |
✔ 2. 调整 PHC(硬件时钟)
通过:
1 | |
驱动调用:
- adjtime(时间偏移)
- adjfreq(频率调整)
2️⃣ FPGA 驱动 → 写 APB 寄存器
对应你代码:
1 | |
👉 LinuxPTP 做的事情就是:
✔ Step(跳变)
1 | |
✔ Slew(微调)
1 | |
3️⃣ taxi_ptp_td_phc(核心时钟模块)
👉 这是时间生成器
功能:
1 | |
ptp_td_sdo 生成本质:
它会不断输出:
1 | |
类似:
| 字段 | 含义 |
|---|---|
| ns | 纳秒 |
| sec | 秒 |
| period | 时钟周期 |
| control | step标志 |
👉 以串行 bit stream输出(你看到的 sdo)
4️⃣ ptp_td_sdi(广播 + 反序列化)
在 taxi_ptp_td_leaf:
1 | |
做了三件事:
✔ ① 串行 → 并行(Deserializer)
1 | |
每 16bit 拼成:
1 | |
✔ ② 协议解析
1 | |
解析:
| TID | 含义 |
|---|---|
| 1~5 | ToD 时间 |
| 6~8 | ns |
| 11~13 | period |
✔ ③ 时间恢复
恢复:
1 | |
5️⃣ 硬件 PI Servo(关键)
你代码里这一段:
1 | |
👉 作用:
1 | |
三、完整数据流(系统级)
给你一张工程图(重点):
1 | |
四、关键理解(你必须掌握)
1️⃣ LinuxPTP 不直接控制 FPGA 时钟
👉 它只做:
1 | |
👉 真正执行的是:
1 | |
2️⃣ ptp_td_sdi ≠ PTP 报文
👉 区别:
| 项目 | PTP报文 | ptp_td_sdi |
|---|---|---|
| 层级 | 网络层 | FPGA内部 |
| 内容 | Sync/Delay | 时间流 |
| 频率 | ms级 | cycle级 |
3️⃣ 为什么这样设计?
核心优势:
✔ 极低延迟
1 | |
✔ 全局同步
1 | |
✔ 可扩展 TSN / TDMA
👉 这也是 Taxi / Corundum 强项
五、你这个设计的工程价值(重点)
结合你背景(FPGA + 网络安全 + TSN):
这个架构可以直接扩展:
✔ 1. TSN Qbv
用:
1 | |
做:
1 | |
✔ 2. 多端口时间一致 NIC
多个 MAC:
1 | |
→ 纳秒级同步
✔ 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 | |
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 | |
2️⃣ 计算 offset
1 | |
👉 等价写法:
1 | |
直观理解
1 | |
- (t2 - t1):包含 delay + offset
- (t4 - t3):包含 delay - offset
👉 一减就把 delay 抵消了
三、LinuxPTP servo架构
LinuxPTP 中核心模块:
clock.cservo.cpi.c(关键)
servo输入
1 | |
四、PI Servo 原理(核心)
LinuxPTP 默认使用:
👉 PI控制器(比例 + 积分)
1️⃣ 控制目标
让:
1 | |
2️⃣ 控制量
调节:
1 | |
3️⃣ PI公式
1 | |
离散化后(LinuxPTP实际用):
1 | |
4️⃣ 各项含义
| 项 | 含义 |
|---|---|
| Kp | 快速响应 |
| Ki | 消除长期误差 |
| sum(offset) | 历史累计误差 |
五、LinuxPTP 实际实现细节(重点)
在 pi.c 中核心逻辑:
1️⃣ 第一阶段:初始同步(step)
1 | |
👉 大误差直接跳时间(避免慢慢调)
2️⃣ 稳定后进入 PI 模式
1 | |
👉 这里:
ppb:频率调整(parts per billion)drift:积分项
3️⃣ 限幅(非常关键)
1 | |
👉 防止震荡 / 发散
4️⃣ 低通滤波(实际有)
LinuxPTP 会对 offset 做滤波:
1 | |
👉 抗 jitter
六、频率调节本质(关键理解)
你 FPGA 很重要的一点👇
时钟模型
1 | |
其中:
- ε = frequency error
PI servo做的事:
调整 ε,使:
1 | |
换句话说:
| 操作 | 作用 |
|---|---|
| offset correction | 修正当前时间 |
| frequency correction | 修正未来时间 |
七、LinuxPTP → 硬件映射(你最关心)
你现在有:
1 | |
1️⃣ LinuxPTP输出
通过:
clock_adjtime()adjtimex()
给 NIC:
两类控制:
A. step(时间跳变)
1 | |
👉 对应 FPGA:
1 | |
B. frequency(频率调节)
1 | |
👉 对应 FPGA:
1 | |
或:
1 | |
2️⃣ 转成 ptp_td_sdi
在你的架构中:
1 | |
八、ptp_td_sdi里承载的本质信息
其实只有三类:
1️⃣ 当前时间
1 | |
2️⃣ 步进标志
1 | |
3️⃣ 频率信息(隐含)
通过:
1 | |
👉 等价:
1 | |
九、用一句话总结 PI servo
👉 LinuxPTP 本质在做:
1 | |
十、结合你FPGA设计的关键建议(非常重要)
如果你要做 硬件 servo(比 LinuxPTP 强10倍):
1️⃣ 把 PI 搬进 FPGA
直接实现:
1 | |
2️⃣ offset来源
来自:
1 | |
而不是 Linux
3️⃣ 优势
| 软件PI | 硬件PI |
|---|---|
| ms级 | ns级 |
| jitter大 | jitter极低 |
| 非实时 | 完全实时 |
4️⃣ 可进阶
- Kalman Filter(更高级)
- Hybrid servo(CPU + FPGA)
十一、总结(核心理解)
LinuxPTP本质:
1 | |
关键公式:
1 | |
硬件对应:
| LinuxPTP | FPGA |
|---|---|
| offset | timestamp差 |
| freq | period_ns/fns |
| step | ts_tod_valid |
如果你下一步想深入(建议)
可以继续往这几个方向走(我可以帮你直接出RTL级方案):
- FPGA实现 PI servo(Verilog版)
- ptp_td_sdi协议反向解析(bit级)
- 硬件 timestamp → offset计算 pipeline
- 亚纳秒级 TSN / gPTP servo设计
直接说你要哪一块,我可以给你做到“可上板”的级别。