4. 基于FPGA的TSN时间同步时钟源设计

任务说明

本案例实现一个可综合的本地时钟源模块 tsn_clk_src:以 125 MHz 系统时钟为基准,通过可配置的纳秒累加步进(频率调节)产生纳秒级时间戳,并支持相位对齐(时间戳预置/校正)。输出 ns_time 为 48 位纳秒计数,pps 为每秒脉冲,供 IEEE 802.1AS 时间同步使用。

设计要点

  • 频率可调:ns_inc 为每个系统时钟周期累加的纳秒数(Q16.16 定点,1 ns = 65536)。标称 125 MHz 时 ns_inc = 8 * 65536 = 524288。
  • 相位对齐:load_en 有效时把 load_val 载入累加器,实现时间戳预置或校正。
  • 纳秒输出:累加器高 16 位为整数纳秒,低 16 位为小数部分。
  • PPS:纳秒整数部分跨秒时输出一个周期脉冲。

完整代码

module tsn_clk_src #(
    parameter integer SYS_CLK_HZ = 125_000_000,
    parameter integer NS_INC_DEF = 524288   // 8 ns * 65536
)(
    input  wire        clk,
    input  wire        rst_n,
    input  wire        load_en,      // 相位对齐/预置
    input  wire [47:0] load_val,     // 载入的纳秒值
    input  wire [31:0] ns_inc,       // Q16.16 每周期纳秒步进
    output reg  [47:0] ns_time,      // 纳秒时间戳
    output reg         pps           // 每秒脉冲
);
    reg [63:0] acc;          // 高16位整数ns,低16位小数
    reg [47:0] ns_int;
    reg [47:0] ns_int_d;

    wire [63:0] acc_next = acc + {32'd0, ns_inc};

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            acc      <= 64'd0;
            ns_int   <= 48'd0;
            ns_int_d <= 48'd0;
            ns_time  <= 48'd0;
            pps      <= 1'b0;
        end else if (load_en) begin
            acc      <= {load_val, 16'd0};
            ns_int   <= load_val;
            ns_int_d <= load_val;
            ns_time  <= load_val;
            pps      <= 1'b0;
        end else begin
            acc      <= acc_next;
            ns_int   <= acc_next[63:16];
            ns_int_d <= ns_int;
            ns_time  <= acc_next[63:16];
            pps      <= (acc_next[63:16] != ns_int) &&
                        (acc_next[63:16][15:0] == 16'd0);
        end
    end
endmodule

仿真验证

module tb_tsn_clk_src;
    reg clk = 0, rst_n = 0, load_en = 0;
    reg [47:0] load_val = 0;
    reg [31:0] ns_inc = 524288;
    wire [47:0] ns_time;
    wire pps;

    tsn_clk_src dut(.clk(clk), .rst_n(rst_n), .load_en(load_en),
                    .load_val(load_val), .ns_inc(ns_inc),
                    .ns_time(ns_time), .pps(pps));

    always #4 clk = ~clk;   // 125 MHz

    initial begin
        #20 rst_n = 1;
        // 相位对齐到 1_000_000_000 ns
        @(posedge clk); load_en = 1; load_val = 48'd1_000_000_000;
        @(posedge clk); load_en = 0;
        // 运行 2 秒观察 PPS
        #(2_000_000_000);
        $display("ns_time=%0d pps=%b", ns_time, pps);
        $finish;
    end
endmodule

运行与判定

  • 仿真:iverilog -o tb tb_tsn_clk_src.v tsn_clk_src.v && vvp tb(或 Vivado/ModelSim 直接跑 tb)。
  • 通过条件:复位后 ns_time 从 0 开始,每 125 个时钟周期增加约 1000 ns;load_en 后 ns_time 立即变为 load_val;每约 1 秒 pps 出现一个时钟周期高电平。
  • 频率调节验证:把 ns_inc 改为 524288±N,观察 ns_time 增长速率相应变化,用于 802.1AS 频率补偿。