From 31c7349f90654d5607c808b54449f5d1f4092399 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Tue, 30 Mar 2021 15:57:46 -0700 Subject: [PATCH] Rewrite PTP clock CDC module for improved performance and timing closure at 25G --- rtl/eth_mac_10g_fifo.v | 19 +- rtl/ptp_clock_cdc.v | 718 ++++++++++++------- syn/ptp_clock_cdc.tcl | 76 +- tb/eth_mac_10g_fifo/Makefile | 6 - tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py | 3 +- tb/ptp_clock_cdc/Makefile | 15 - tb/ptp_clock_cdc/test_ptp_clock_cdc.py | 26 +- 7 files changed, 537 insertions(+), 326 deletions(-) diff --git a/rtl/eth_mac_10g_fifo.v b/rtl/eth_mac_10g_fifo.v index 8d3c21564..c6fc825aa 100644 --- a/rtl/eth_mac_10g_fifo.v +++ b/rtl/eth_mac_10g_fifo.v @@ -49,8 +49,6 @@ module eth_mac_10g_fifo # parameter RX_FRAME_FIFO = 1, parameter RX_DROP_BAD_FRAME = RX_FRAME_FIFO, parameter RX_DROP_WHEN_FULL = RX_FRAME_FIFO, - parameter LOGIC_PTP_PERIOD_NS = 4'h6, - parameter LOGIC_PTP_PERIOD_FNS = 16'h6666, parameter PTP_PERIOD_NS = 4'h6, parameter PTP_PERIOD_FNS = 16'h6666, parameter PTP_USE_SAMPLE_CLOCK = 0, @@ -138,6 +136,7 @@ module eth_mac_10g_fifo # * PTP clock */ input wire [PTP_TS_WIDTH-1:0] ptp_ts_96, + input wire ptp_ts_step, /* * Configuration @@ -252,10 +251,6 @@ if (TX_PTP_TS_ENABLE) begin .TS_WIDTH(PTP_TS_WIDTH), .NS_WIDTH(4), .FNS_WIDTH(16), - .INPUT_PERIOD_NS(LOGIC_PTP_PERIOD_NS), - .INPUT_PERIOD_FNS(LOGIC_PTP_PERIOD_FNS), - .OUTPUT_PERIOD_NS(PTP_PERIOD_NS), - .OUTPUT_PERIOD_FNS(PTP_PERIOD_FNS), .USE_SAMPLE_CLOCK(PTP_USE_SAMPLE_CLOCK) ) tx_ptp_cdc ( @@ -265,9 +260,11 @@ if (TX_PTP_TS_ENABLE) begin .output_rst(tx_rst), .sample_clk(ptp_sample_clk), .input_ts(ptp_ts_96), + .input_ts_step(ptp_ts_step), .output_ts(tx_ptp_ts_96), .output_ts_step(), - .output_pps() + .output_pps(), + .locked() ); if (TX_PTP_TAG_ENABLE) begin @@ -432,10 +429,6 @@ if (RX_PTP_TS_ENABLE) begin .TS_WIDTH(PTP_TS_WIDTH), .NS_WIDTH(4), .FNS_WIDTH(16), - .INPUT_PERIOD_NS(LOGIC_PTP_PERIOD_NS), - .INPUT_PERIOD_FNS(LOGIC_PTP_PERIOD_FNS), - .OUTPUT_PERIOD_NS(PTP_PERIOD_NS), - .OUTPUT_PERIOD_FNS(PTP_PERIOD_FNS), .USE_SAMPLE_CLOCK(PTP_USE_SAMPLE_CLOCK) ) rx_ptp_cdc ( @@ -445,9 +438,11 @@ if (RX_PTP_TS_ENABLE) begin .output_rst(rx_rst), .sample_clk(ptp_sample_clk), .input_ts(ptp_ts_96), + .input_ts_step(ptp_ts_step), .output_ts(rx_ptp_ts_96), .output_ts_step(), - .output_pps() + .output_pps(), + .locked() ); axis_fifo #( diff --git a/rtl/ptp_clock_cdc.v b/rtl/ptp_clock_cdc.v index 8045abca2..db3e4935b 100644 --- a/rtl/ptp_clock_cdc.v +++ b/rtl/ptp_clock_cdc.v @@ -1,6 +1,6 @@ /* -Copyright (c) 2019 Alex Forencich +Copyright (c) 2019-2021 Alex Forencich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,12 +34,7 @@ module ptp_clock_cdc # parameter TS_WIDTH = 96, parameter NS_WIDTH = 4, parameter FNS_WIDTH = 16, - parameter INPUT_PERIOD_NS = 4'h6, - parameter INPUT_PERIOD_FNS = 16'h6666, - parameter OUTPUT_PERIOD_NS = 4'h6, - parameter OUTPUT_PERIOD_FNS = 16'h6666, parameter USE_SAMPLE_CLOCK = 1, - parameter LOG_FIFO_DEPTH = 3, parameter LOG_RATE = 3 ) ( @@ -53,6 +48,7 @@ module ptp_clock_cdc # * Timestamp inputs from source PTP clock */ input wire [TS_WIDTH-1:0] input_ts, + input wire input_ts_step, /* * Timestamp outputs @@ -63,7 +59,12 @@ module ptp_clock_cdc # /* * PPS output */ - output wire output_pps + output wire output_pps, + + /* + * Status + */ + output wire locked ); // bus width assertions @@ -76,79 +77,75 @@ end parameter TS_NS_WIDTH = TS_WIDTH == 96 ? 30 : 48; -parameter FIFO_ADDR_WIDTH = LOG_FIFO_DEPTH+1; -parameter LOG_AVG = 6; -parameter LOG_AVG_SCALE = LOG_AVG+8; -parameter LOG_AVG_SYNC_RATE = LOG_RATE; -parameter WR_PERIOD = ((((INPUT_PERIOD_NS << 16) + INPUT_PERIOD_FNS) + 64'd0) << 16) / ((OUTPUT_PERIOD_NS << 16) + OUTPUT_PERIOD_FNS) / 2**(LOG_RATE+1); +parameter PHASE_CNT_WIDTH = LOG_RATE; +parameter PHASE_ACC_WIDTH = PHASE_CNT_WIDTH+16; -reg [NS_WIDTH-1:0] period_ns_reg = OUTPUT_PERIOD_NS; -reg [FNS_WIDTH-1:0] period_fns_reg = OUTPUT_PERIOD_FNS; +parameter LOG_SAMPLE_SYNC_RATE = LOG_RATE; +parameter SAMPLE_ACC_WIDTH = LOG_SAMPLE_SYNC_RATE+2; -reg [47:0] ts_s_reg = 0; -reg [TS_NS_WIDTH-1:0] ts_ns_reg = 0; -reg [FNS_WIDTH-1:0] ts_fns_reg = 0; -reg [TS_NS_WIDTH-1:0] ts_ns_inc_reg = 0; -reg [FNS_WIDTH-1:0] ts_fns_inc_reg = 0; -reg [TS_NS_WIDTH+1-1:0] ts_ns_ovf_reg = {TS_NS_WIDTH+1{1'b1}}; -reg [FNS_WIDTH-1:0] ts_fns_ovf_reg = {FNS_WIDTH{1'b1}}; +parameter DEST_SYNC_LOCK_WIDTH = 7; +parameter PTP_LOCK_WIDTH = 8; -reg ts_step_reg = 1'b0; +reg [NS_WIDTH-1:0] period_ns_reg = 0, period_ns_next; +reg [FNS_WIDTH-1:0] period_fns_reg = 0, period_fns_next; + +reg [47:0] src_ts_s_capt_reg = 0; +reg [TS_NS_WIDTH-1:0] src_ts_ns_capt_reg = 0; +reg [FNS_WIDTH-1:0] src_ts_fns_capt_reg = 0; +reg src_ts_step_capt_reg = 0; + +reg [47:0] dest_ts_s_capt_reg = 0; +reg [TS_NS_WIDTH-1:0] dest_ts_ns_capt_reg = 0; +reg [FNS_WIDTH-1:0] dest_ts_fns_capt_reg = 0; + +reg [47:0] ts_s_sync_reg = 0; +reg [TS_NS_WIDTH-1:0] ts_ns_sync_reg = 0; +reg [FNS_WIDTH-1:0] ts_fns_sync_reg = 0; +reg ts_step_sync_reg = 0; + +reg [47:0] ts_s_reg = 0, ts_s_next; +reg [TS_NS_WIDTH-1:0] ts_ns_reg = 0, ts_ns_next; +reg [FNS_WIDTH-1:0] ts_fns_reg = 0, ts_fns_next; +reg [TS_NS_WIDTH-1:0] ts_ns_inc_reg = 0, ts_ns_inc_next; +reg [FNS_WIDTH-1:0] ts_fns_inc_reg = 0, ts_fns_inc_next; +reg [TS_NS_WIDTH+1-1:0] ts_ns_ovf_reg = {TS_NS_WIDTH+1{1'b1}}, ts_ns_ovf_next; +reg [FNS_WIDTH-1:0] ts_fns_ovf_reg = {FNS_WIDTH{1'b1}}, ts_fns_ovf_next; + +reg ts_step_reg = 1'b0, ts_step_next; reg pps_reg = 0; -reg [FIFO_ADDR_WIDTH:0] wr_ptr_reg = {FIFO_ADDR_WIDTH+1{1'b0}}, wr_ptr_next; -reg [FIFO_ADDR_WIDTH:0] wr_ptr_gray_reg = {FIFO_ADDR_WIDTH+1{1'b0}}, wr_ptr_gray_next; -reg [FIFO_ADDR_WIDTH:0] rd_ptr_reg = {FIFO_ADDR_WIDTH+1{1'b0}}, rd_ptr_next; -reg [FIFO_ADDR_WIDTH:0] rd_ptr_gray_reg = {FIFO_ADDR_WIDTH+1{1'b0}}, rd_ptr_gray_next; +reg [PHASE_CNT_WIDTH-1:0] src_phase_reg = {PHASE_CNT_WIDTH{1'b0}}; +reg [PHASE_ACC_WIDTH-1:0] dest_phase_reg = {PHASE_ACC_WIDTH{1'b0}}, dest_phase_next; +reg [PHASE_ACC_WIDTH-1:0] dest_phase_inc_reg = {PHASE_ACC_WIDTH{1'b0}}, dest_phase_inc_next; -reg [FIFO_ADDR_WIDTH:0] wr_ptr_gray_sync1_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -reg [FIFO_ADDR_WIDTH:0] wr_ptr_gray_sync2_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -wire [FIFO_ADDR_WIDTH:0] wr_ptr_sync2; -reg [FIFO_ADDR_WIDTH:0] rd_ptr_gray_sync1_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -reg [FIFO_ADDR_WIDTH:0] rd_ptr_gray_sync2_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -wire [FIFO_ADDR_WIDTH:0] rd_ptr_sync2; +reg src_sync_reg = 1'b0; +reg src_update_reg = 1'b0; +reg dest_sync_reg = 1'b0, dest_sync_next = 1'b0; +reg dest_update_reg = 1'b0, dest_update_next = 1'b0; -reg [FIFO_ADDR_WIDTH:0] wr_ptr_gray_sample_sync1_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -reg [FIFO_ADDR_WIDTH:0] wr_ptr_gray_sample_sync2_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -wire [FIFO_ADDR_WIDTH:0] wr_ptr_sample_sync2; -reg [FIFO_ADDR_WIDTH:0] rd_ptr_gray_sample_sync1_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -reg [FIFO_ADDR_WIDTH:0] rd_ptr_gray_sample_sync2_reg = {FIFO_ADDR_WIDTH+1{1'b0}}; -wire [FIFO_ADDR_WIDTH:0] rd_ptr_sample_sync2; +reg src_sync_sync1_reg = 1'b0; +reg src_sync_sync2_reg = 1'b0; +reg src_sync_sync3_reg = 1'b0; +reg dest_sync_sync1_reg = 1'b0; +reg dest_sync_sync2_reg = 1'b0; +reg dest_sync_sync3_reg = 1'b0; -reg [15:0] wr_acc_reg = 16'd0, wr_acc_next; -reg [15:0] wr_inc_reg = WR_PERIOD, wr_inc_next; -reg [31:0] err_int_reg = 0, err_int_next; +reg src_sync_sample_sync1_reg = 1'b0; +reg src_sync_sample_sync2_reg = 1'b0; +reg src_sync_sample_sync3_reg = 1'b0; +reg dest_sync_sample_sync1_reg = 1'b0; +reg dest_sync_sample_sync2_reg = 1'b0; +reg dest_sync_sample_sync3_reg = 1'b0; -reg [LOG_RATE-1:0] rd_cnt_reg = {LOG_RATE{1'b0}}, rd_cnt_next; - -reg [LOG_FIFO_DEPTH+LOG_AVG_SCALE+2-1:0] sample_acc_reg = 0; -reg [LOG_FIFO_DEPTH+LOG_AVG_SCALE+2-1:0] sample_avg_reg = 0; -reg [LOG_AVG_SYNC_RATE-1:0] sample_cnt_reg = 0; +reg [SAMPLE_ACC_WIDTH-1:0] sample_acc_reg = 0, sample_acc_next = 0; +reg [SAMPLE_ACC_WIDTH-1:0] sample_acc_out_reg = 0; +reg [LOG_SAMPLE_SYNC_RATE-1:0] sample_cnt_reg = 0; reg sample_update_reg = 1'b0; reg sample_update_sync1_reg = 1'b0; reg sample_update_sync2_reg = 1'b0; reg sample_update_sync3_reg = 1'b0; -reg [TS_WIDTH-1:0] mem[(2**FIFO_ADDR_WIDTH)-1:0]; -reg [TS_WIDTH-1:0] mem_read_data_reg = 0; - -// full when first TWO MSBs do NOT match, but rest matches -// (gray code equivalent of first MSB different but rest same) -wire full = ((wr_ptr_gray_reg[FIFO_ADDR_WIDTH] != rd_ptr_gray_sync2_reg[FIFO_ADDR_WIDTH]) && - (wr_ptr_gray_reg[FIFO_ADDR_WIDTH-1] != rd_ptr_gray_sync2_reg[FIFO_ADDR_WIDTH-1]) && - (wr_ptr_gray_reg[FIFO_ADDR_WIDTH-2:0] == rd_ptr_gray_sync2_reg[FIFO_ADDR_WIDTH-2:0])); -// empty when pointers match exactly -wire empty = rd_ptr_gray_reg == wr_ptr_gray_sync2_reg; - -wire [FIFO_ADDR_WIDTH:0] wr_depth = wr_ptr_reg - rd_ptr_sync2; -wire [FIFO_ADDR_WIDTH:0] rd_depth = wr_ptr_sync2 - rd_ptr_reg; -wire [FIFO_ADDR_WIDTH:0] sample_depth = wr_ptr_sample_sync2 - rd_ptr_sample_sync2; - -// control signals -reg write; -reg read; - generate if (TS_WIDTH == 96) begin @@ -167,279 +164,491 @@ assign output_ts_step = ts_step_reg; assign output_pps = pps_reg; -generate +// source PTP clock capture and sync logic +reg input_ts_step_reg = 1'b0; - genvar n; +always @(posedge input_clk) begin + input_ts_step_reg <= input_ts_step || input_ts_step_reg; - for (n = 0; n < FIFO_ADDR_WIDTH+1; n = n + 1) begin - assign wr_ptr_sync2[n] = ^wr_ptr_gray_sync2_reg[FIFO_ADDR_WIDTH+1-1:n]; - assign rd_ptr_sync2[n] = ^rd_ptr_gray_sync2_reg[FIFO_ADDR_WIDTH+1-1:n]; - assign wr_ptr_sample_sync2[n] = ^wr_ptr_gray_sample_sync2_reg[FIFO_ADDR_WIDTH+1-1:n]; - assign rd_ptr_sample_sync2[n] = ^rd_ptr_gray_sample_sync2_reg[FIFO_ADDR_WIDTH+1-1:n]; + {src_update_reg, src_phase_reg} <= src_phase_reg+1; + + if (src_update_reg) begin + // capture source TS + if (TS_WIDTH == 96) begin + src_ts_s_capt_reg <= input_ts[95:48]; + src_ts_ns_capt_reg <= input_ts[45:16]; + end else begin + src_ts_ns_capt_reg <= input_ts[63:16]; + end + src_ts_fns_capt_reg <= FNS_WIDTH > 16 ? input_ts[15:0] << (FNS_WIDTH-16) : input_ts[15:0] >> (16-FNS_WIDTH); + src_ts_step_capt_reg <= input_ts_step || input_ts_step_reg; + input_ts_step_reg <= 1'b0; + src_sync_reg <= !src_sync_reg; end -endgenerate - -// pointer sync -always @(posedge input_clk) begin if (input_rst) begin - rd_ptr_gray_sync1_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - rd_ptr_gray_sync2_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - end else begin - rd_ptr_gray_sync1_reg <= rd_ptr_gray_reg; - rd_ptr_gray_sync2_reg <= rd_ptr_gray_sync1_reg; + input_ts_step_reg <= 1'b0; + + src_phase_reg <= {PHASE_CNT_WIDTH{1'b0}}; + src_sync_reg <= 1'b0; + src_update_reg <= 1'b0; end end +// CDC logic always @(posedge output_clk) begin - if (output_rst) begin - wr_ptr_gray_sync1_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - wr_ptr_gray_sync2_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - end else begin - wr_ptr_gray_sync1_reg <= wr_ptr_gray_reg; - wr_ptr_gray_sync2_reg <= wr_ptr_gray_sync1_reg; - end + src_sync_sync1_reg <= src_sync_reg; + src_sync_sync2_reg <= src_sync_sync1_reg; + src_sync_sync3_reg <= src_sync_sync2_reg; + dest_sync_sync1_reg <= dest_sync_reg; + dest_sync_sync2_reg <= dest_sync_sync1_reg; + dest_sync_sync3_reg <= dest_sync_sync2_reg; end always @(posedge sample_clk) begin - rd_ptr_gray_sample_sync1_reg <= rd_ptr_gray_reg; - rd_ptr_gray_sample_sync2_reg <= rd_ptr_gray_sample_sync1_reg; - wr_ptr_gray_sample_sync1_reg <= wr_ptr_gray_reg; - wr_ptr_gray_sample_sync2_reg <= wr_ptr_gray_sample_sync1_reg; + src_sync_sample_sync1_reg <= src_sync_reg; + src_sync_sample_sync2_reg <= src_sync_sample_sync1_reg; + src_sync_sample_sync3_reg <= src_sync_sample_sync2_reg; + dest_sync_sample_sync1_reg <= dest_sync_reg; + dest_sync_sample_sync2_reg <= dest_sync_sample_sync1_reg; + dest_sync_sample_sync3_reg <= dest_sync_sample_sync2_reg; end +reg edge_1_reg = 1'b0; +reg edge_2_reg = 1'b0; + +reg [3:0] active_reg = 0; + always @(posedge sample_clk) begin if (USE_SAMPLE_CLOCK) begin - sample_acc_reg <= sample_acc_reg + ((sample_depth * 2**LOG_AVG_SCALE - sample_acc_reg) >> LOG_AVG); + // phase and frequency detector + if (dest_sync_sample_sync2_reg && !dest_sync_sample_sync3_reg) begin + if (src_sync_sample_sync2_reg && !src_sync_sample_sync3_reg) begin + edge_1_reg <= 1'b0; + edge_2_reg <= 1'b0; + active_reg[0] <= 1'b1; + end else begin + edge_1_reg <= !edge_2_reg; + edge_2_reg <= 1'b0; + end + end else if (src_sync_sample_sync2_reg && !src_sync_sample_sync3_reg) begin + edge_1_reg <= 1'b0; + edge_2_reg <= !edge_1_reg; + active_reg[0] <= 1'b1; + end + + // accumulator + sample_acc_reg <= $signed(sample_acc_reg) + $signed({1'b0, edge_2_reg}) - $signed({1'b0, edge_1_reg}); + sample_cnt_reg <= sample_cnt_reg + 1; if (sample_cnt_reg == 0) begin - sample_update_reg <= !sample_update_reg; - sample_avg_reg <= sample_acc_reg; + active_reg <= active_reg << 1; + sample_acc_reg <= $signed({1'b0, edge_2_reg}) - $signed({1'b0, edge_1_reg}); + sample_acc_out_reg <= sample_acc_reg; + if (active_reg != 0) begin + sample_update_reg <= !sample_update_reg; + end end end end -always @(posedge input_clk) begin +always @(posedge output_clk) begin sample_update_sync1_reg <= sample_update_reg; sample_update_sync2_reg <= sample_update_sync1_reg; sample_update_sync3_reg <= sample_update_sync2_reg; end -reg [LOG_FIFO_DEPTH+LOG_AVG_SCALE+2-1:0] sample_avg_sync_reg = 0; -reg sample_avg_sync_valid_reg = 0; +reg [SAMPLE_ACC_WIDTH-1:0] sample_acc_sync_reg = 0; +reg sample_acc_sync_valid_reg = 0; -always @(posedge input_clk) begin +always @(posedge output_clk) begin if (USE_SAMPLE_CLOCK) begin - sample_avg_sync_valid_reg <= 1'b0; + // latch in synchronized counts from phase detector + sample_acc_sync_valid_reg <= 1'b0; if (sample_update_sync2_reg ^ sample_update_sync3_reg) begin - sample_avg_sync_reg <= sample_avg_reg; - sample_avg_sync_valid_reg <= 1'b1; + sample_acc_sync_reg <= sample_acc_out_reg; + sample_acc_sync_valid_reg <= 1'b1; end end else begin - sample_acc_reg <= sample_acc_reg + ((wr_depth * 2**LOG_AVG_SCALE - sample_acc_reg) >> LOG_AVG); + // phase and frequency detector + if (dest_sync_sync2_reg && !dest_sync_sync3_reg) begin + if (src_sync_sync2_reg && !src_sync_sync3_reg) begin + edge_1_reg <= 1'b0; + edge_2_reg <= 1'b0; + active_reg[0] <= 1'b1; + end else begin + edge_1_reg <= !edge_2_reg; + edge_2_reg <= 1'b0; + end + end else if (src_sync_sync2_reg && !src_sync_sync3_reg) begin + edge_1_reg <= 1'b0; + edge_2_reg <= !edge_1_reg; + active_reg[0] <= 1'b1; + end + + // accumulator + sample_acc_reg <= $signed(sample_acc_reg) + $signed({1'b0, edge_2_reg}) - $signed({1'b0, edge_1_reg}); + sample_cnt_reg <= sample_cnt_reg + 1; - sample_avg_sync_valid_reg <= 1'b0; + sample_acc_sync_valid_reg <= 1'b0; if (sample_cnt_reg == 0) begin - sample_avg_sync_reg <= sample_acc_reg; - sample_avg_sync_valid_reg <= 1'b1; + active_reg <= active_reg << 1; + sample_acc_reg <= $signed({1'b0, edge_2_reg}) - $signed({1'b0, edge_1_reg}); + sample_acc_sync_reg <= sample_acc_reg; + if (active_reg != 0) begin + sample_update_reg <= !sample_update_reg; + sample_acc_sync_valid_reg <= 1'b1; + end end end end +reg [PHASE_ACC_WIDTH-1:0] dest_err_int_reg = 0, dest_err_int_next = 0; +reg [1:0] dest_ovf; + +reg [DEST_SYNC_LOCK_WIDTH-1:0] dest_sync_lock_count_reg = 0, dest_sync_lock_count_next; +reg dest_sync_locked_reg = 1'b0, dest_sync_locked_next; + always @* begin - write = 1'b0; + {dest_update_next, dest_phase_next} = dest_phase_reg + dest_phase_inc_reg; + dest_phase_inc_next = dest_phase_inc_reg; - wr_ptr_next = wr_ptr_reg; - wr_ptr_gray_next = wr_ptr_gray_reg; + dest_err_int_next = dest_err_int_reg; - wr_acc_next = wr_acc_reg + wr_inc_reg; - wr_inc_next = wr_inc_reg; + dest_sync_lock_count_next = dest_sync_lock_count_reg; + dest_sync_locked_next = dest_sync_locked_reg; - err_int_next = err_int_reg; + if (sample_acc_sync_valid_reg) begin + // updated sampled dest_phase error - if (sample_avg_sync_valid_reg) begin - // updated sampled FIFO depth - err_int_next = err_int_reg + (sample_avg_sync_reg - (2**LOG_FIFO_DEPTH * 2**LOG_AVG_SCALE)); - wr_inc_next = WR_PERIOD + (((2**LOG_FIFO_DEPTH * 2**LOG_AVG_SCALE) - sample_avg_sync_reg) >> 8) - ($signed(err_int_reg) >> 13); - if ($signed(wr_inc_next) > $signed(WR_PERIOD*4)) begin - wr_inc_next = WR_PERIOD*4; - end else if ($signed(wr_inc_next) < $signed(WR_PERIOD/4)) begin - wr_inc_next = WR_PERIOD/4; + // time integral of error + if (dest_sync_locked_reg) begin + {dest_ovf, dest_err_int_next} = $signed({1'b0, dest_err_int_reg}) + $signed(sample_acc_sync_reg); + end else begin + {dest_ovf, dest_err_int_next} = $signed({1'b0, dest_err_int_reg}) + ($signed(sample_acc_sync_reg) * 2**6); + end + + // saturate + if (dest_ovf[1]) begin + // sign bit set indicating underflow across zero; saturate to zero + dest_err_int_next = {PHASE_ACC_WIDTH{1'b0}}; + end else if (dest_ovf[0]) begin + // sign bit clear but carry bit set indicating overflow; saturate to all 1 + dest_err_int_next = {PHASE_ACC_WIDTH{1'b1}}; + end + + // compute output + if (dest_sync_locked_reg) begin + {dest_ovf, dest_phase_inc_next} = $signed({1'b0, dest_err_int_reg}) + ($signed(sample_acc_sync_reg) * 2**4); + end else begin + {dest_ovf, dest_phase_inc_next} = $signed({1'b0, dest_err_int_reg}) + ($signed(sample_acc_sync_reg) * 2**10); + end + + // saturate + if (dest_ovf[1]) begin + // sign bit set indicating underflow across zero; saturate to zero + dest_phase_inc_next = {PHASE_ACC_WIDTH{1'b0}}; + end else if (dest_ovf[0]) begin + // sign bit clear but carry bit set indicating overflow; saturate to all 1 + dest_phase_inc_next = {PHASE_ACC_WIDTH{1'b1}}; + end + + // locked status + if ($signed(sample_acc_sync_reg[SAMPLE_ACC_WIDTH-1:2]) == 0 || $signed(sample_acc_sync_reg[SAMPLE_ACC_WIDTH-1:1]) == -1) begin + if (dest_sync_lock_count_reg == {DEST_SYNC_LOCK_WIDTH{1'b1}}) begin + dest_sync_locked_next = 1'b1; + end else begin + dest_sync_lock_count_next = dest_sync_lock_count_reg + 1; + end + end else begin + dest_sync_lock_count_next = 0; + dest_sync_locked_next = 1'b0; end end - - if (!full && wr_acc_reg[15] != wr_acc_next[15]) begin - write = 1'b1; - wr_ptr_next = wr_ptr_reg + 1; - wr_ptr_gray_next = wr_ptr_next ^ (wr_ptr_next >> 1); - end end -always @(posedge input_clk) begin - wr_ptr_reg <= wr_ptr_next; - wr_ptr_gray_reg <= wr_ptr_gray_next; - - wr_acc_reg <= wr_acc_next; - wr_inc_reg <= wr_inc_next; - - err_int_reg <= err_int_next; - - if (write) begin - mem[wr_ptr_reg[FIFO_ADDR_WIDTH-1:0]] <= input_ts; - end - - if (input_rst) begin - wr_ptr_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - wr_ptr_gray_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - wr_acc_reg <= 16'd0; - wr_inc_reg <= WR_PERIOD; - - err_int_reg <= 0; - end -end - -always @* begin - read = 1'b0; - - rd_ptr_next = rd_ptr_reg; - rd_ptr_gray_next = rd_ptr_gray_reg; - - rd_cnt_next = rd_cnt_reg + 1; - - if (!empty && rd_cnt_reg == 0) begin - read = 1'b1; - rd_ptr_next = rd_ptr_reg + 1; - rd_ptr_gray_next = rd_ptr_next ^ (rd_ptr_next >> 1); - end -end +reg ts_sync_valid_reg = 1'b0; +reg ts_capt_valid_reg = 1'b0; always @(posedge output_clk) begin - rd_ptr_reg <= rd_ptr_next; - rd_ptr_gray_reg <= rd_ptr_gray_next; + dest_phase_reg <= dest_phase_next; + dest_phase_inc_reg <= dest_phase_inc_next; + dest_update_reg <= dest_update_next; - rd_cnt_reg <= rd_cnt_next; + if (dest_update_reg) begin + // capture local TS + dest_ts_s_capt_reg <= ts_s_reg; + dest_ts_ns_capt_reg <= ts_ns_reg; + dest_ts_fns_capt_reg <= ts_fns_reg; - if (!empty) begin - mem_read_data_reg <= mem[rd_ptr_reg[FIFO_ADDR_WIDTH-1:0]]; + dest_sync_reg <= !dest_sync_reg; + ts_capt_valid_reg <= 1'b1; end - if (read) begin - + ts_sync_valid_reg <= 1'b0; + + if (src_sync_sync2_reg ^ src_sync_sync3_reg) begin + // store captured source TS + if (TS_WIDTH == 96) begin + ts_s_sync_reg <= src_ts_s_capt_reg; + end + ts_ns_sync_reg <= src_ts_ns_capt_reg; + ts_fns_sync_reg <= src_ts_fns_capt_reg; + ts_step_sync_reg <= src_ts_step_capt_reg; + + ts_sync_valid_reg <= ts_capt_valid_reg; + ts_capt_valid_reg <= 1'b0; end + dest_err_int_reg <= dest_err_int_next; + + dest_sync_lock_count_reg <= dest_sync_lock_count_next; + dest_sync_locked_reg <= dest_sync_locked_next; + if (output_rst) begin - rd_ptr_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - rd_ptr_gray_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; - rd_cnt_reg <= {LOG_RATE{1'b0}}; + dest_phase_reg <= {PHASE_ACC_WIDTH{1'b0}}; + dest_phase_inc_reg <= {PHASE_ACC_WIDTH{1'b0}}; + dest_sync_reg <= 1'b0; + dest_update_reg <= 1'b0; + + dest_err_int_reg <= 0; + + dest_sync_lock_count_reg <= 0; + dest_sync_locked_reg <= 1'b0; + + ts_sync_valid_reg <= 1'b0; + ts_capt_valid_reg <= 1'b0; end end -reg sec_mismatch_reg = 1'b0; -reg diff_valid_reg = 1'b0; -reg diff_offset_valid_reg = 1'b0; +parameter TIME_ERR_INT_WIDTH = NS_WIDTH+FNS_WIDTH+16; -reg [TS_NS_WIDTH+1-1:0] ts_ns_diff_reg = 31'd0; -reg [FNS_WIDTH-1:0] ts_fns_diff_reg = 16'd0; +reg sec_mismatch_reg = 1'b0, sec_mismatch_next; +reg diff_valid_reg = 1'b0, diff_valid_next; +reg diff_corr_valid_reg = 1'b0, diff_corr_valid_next; -reg [48:0] time_err_int_reg = 32'd0; +reg [47:0] ts_s_diff_reg = 0, ts_s_diff_next; +reg [TS_NS_WIDTH+1-1:0] ts_ns_diff_reg = 0, ts_ns_diff_next; +reg [FNS_WIDTH-1:0] ts_fns_diff_reg = 0, ts_fns_diff_next; -always @(posedge output_clk) begin - ts_step_reg <= 0; +reg [16:0] ts_ns_diff_corr_reg = 0, ts_ns_diff_corr_next; +reg [FNS_WIDTH-1:0] ts_fns_diff_corr_reg = 0, ts_fns_diff_corr_next; - diff_valid_reg <= 1'b0; - diff_offset_valid_reg <= 1'b0; +reg [TIME_ERR_INT_WIDTH-1:0] time_err_int_reg = 0, time_err_int_next; - // 96 bit timestamp +reg [1:0] ptp_ovf; + +reg [PTP_LOCK_WIDTH-1:0] ptp_lock_count_reg = 0, ptp_lock_count_next; +reg ptp_locked_reg = 1'b0, ptp_locked_next; + +assign locked = ptp_locked_reg && dest_sync_locked_reg; + +always @* begin + period_ns_next = period_ns_reg; + period_fns_next = period_fns_reg; + + ts_s_next = ts_s_reg; + ts_ns_next = ts_ns_reg; + ts_fns_next = ts_fns_reg; + ts_ns_inc_next = ts_ns_inc_reg; + ts_fns_inc_next = ts_fns_inc_reg; + ts_ns_ovf_next = ts_ns_ovf_reg; + ts_fns_ovf_next = ts_fns_ovf_reg; + + ts_step_next = 0; + + diff_valid_next = 1'b0; + diff_corr_valid_next = 1'b0; + + sec_mismatch_next = sec_mismatch_reg; + diff_valid_next = 1'b0; + diff_corr_valid_next = 1'b0; + + ts_s_diff_next = ts_s_diff_reg; + ts_ns_diff_next = ts_ns_diff_reg; + ts_fns_diff_next = ts_fns_diff_reg; + + ts_ns_diff_corr_next = ts_ns_diff_corr_reg; + ts_fns_diff_corr_next = ts_fns_diff_corr_reg; + + time_err_int_next = time_err_int_reg; + + // PTP clock if (TS_WIDTH == 96) begin + // 96 bit timestamp if (!ts_ns_ovf_reg[30]) begin // if the overflow lookahead did not borrow, one second has elapsed // increment seconds field, pre-compute both normal increment and overflow values - {ts_ns_inc_reg, ts_fns_inc_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg}; - {ts_ns_ovf_reg, ts_fns_ovf_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; - {ts_ns_reg, ts_fns_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg}; - ts_s_reg <= ts_s_reg + 1; + {ts_ns_inc_next, ts_fns_inc_next} = {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg}; + {ts_ns_ovf_next, ts_fns_ovf_next} = {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; + {ts_ns_next, ts_fns_next} = {ts_ns_ovf_reg, ts_fns_ovf_reg}; + ts_s_next = ts_s_next + 1; end else begin // no increment seconds field, pre-compute both normal increment and overflow values - {ts_ns_inc_reg, ts_fns_inc_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg}; - {ts_ns_ovf_reg, ts_fns_ovf_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; - {ts_ns_reg, ts_fns_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg}; - ts_s_reg <= ts_s_reg; + {ts_ns_inc_next, ts_fns_inc_next} = {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg}; + {ts_ns_ovf_next, ts_fns_ovf_next} = {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; + {ts_ns_next, ts_fns_next} = {ts_ns_inc_reg, ts_fns_inc_reg}; + ts_s_next = ts_s_next; end end else if (TS_WIDTH == 64) begin - {ts_ns_reg, ts_fns_reg} <= {ts_ns_reg, ts_fns_reg} + {period_ns_reg, period_fns_reg}; + // 64 bit timestamp + {ts_ns_next, ts_fns_next} = {ts_ns_reg, ts_fns_reg} + {period_ns_reg, period_fns_reg}; end - // FIFO dequeue - if (read) begin - // dequeue from FIFO + if (ts_sync_valid_reg) begin + // Read new value if (TS_WIDTH == 96) begin - if (mem_read_data_reg[95:48] != ts_s_reg) begin - // seconds field doesn't match - if (!sec_mismatch_reg) begin - // ignore the first time - sec_mismatch_reg <= 1'b1; - end else begin - // two seconds mismatches in a row; step the clock - sec_mismatch_reg <= 1'b0; + if (ts_step_sync_reg || sec_mismatch_reg) begin + // input stepped + sec_mismatch_next = 1'b0; - {ts_ns_inc_reg, ts_fns_inc_reg} <= (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)) + {period_ns_reg, period_fns_reg}; - {ts_ns_ovf_reg, ts_fns_ovf_reg} <= (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)) + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; - ts_s_reg <= mem_read_data_reg[95:48]; - ts_ns_reg <= mem_read_data_reg[45:16]; - ts_fns_reg <= FNS_WIDTH > 16 ? mem_read_data_reg[15:0] << (FNS_WIDTH-16) : mem_read_data_reg[15:0] >> (16-FNS_WIDTH); - ts_step_reg <= 1; - end + {ts_ns_inc_next, ts_fns_inc_next} = {ts_ns_sync_reg, ts_fns_sync_reg} + {period_ns_reg, period_fns_reg}; + {ts_ns_ovf_next, ts_fns_ovf_next} = {ts_ns_sync_reg, ts_fns_sync_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; + ts_s_next = ts_s_sync_reg; + ts_ns_next = ts_ns_sync_reg; + ts_fns_next = ts_fns_sync_reg; + ts_step_next = 1; end else begin // compute difference - sec_mismatch_reg <= 1'b0; - diff_valid_reg <= 1'b1; - {ts_ns_diff_reg, ts_fns_diff_reg} <= {ts_ns_reg, ts_fns_reg} - (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)); + sec_mismatch_next = 1'b0; + diff_valid_next = 1'b1; + ts_s_diff_next = ts_s_sync_reg - dest_ts_s_capt_reg; + {ts_ns_diff_next, ts_fns_diff_next} = {ts_ns_sync_reg, ts_fns_sync_reg} - {dest_ts_ns_capt_reg, dest_ts_fns_capt_reg}; end end else if (TS_WIDTH == 64) begin - if (mem_read_data_reg[63:48] != ts_ns_reg[47:32]) begin - // high-order bits don't match - if (!sec_mismatch_reg) begin - // ignore the first time - sec_mismatch_reg <= 1'b1; - end else begin - // two seconds mismatches in a row; step the clock - sec_mismatch_reg <= 1'b0; + if (ts_step_sync_reg || sec_mismatch_reg) begin + // input stepped + sec_mismatch_next = 1'b0; - ts_ns_reg <= mem_read_data_reg[63:16]; - ts_fns_reg <= FNS_WIDTH > 16 ? mem_read_data_reg[15:0] << (FNS_WIDTH-16) : mem_read_data_reg[15:0] >> (16-FNS_WIDTH); - ts_step_reg <= 1; - end + ts_ns_next = ts_ns_sync_reg; + ts_fns_next = ts_fns_sync_reg; + ts_step_next = 1; end else begin // compute difference - sec_mismatch_reg <= 1'b0; - diff_valid_reg <= 1'b1; - {ts_ns_diff_reg, ts_fns_diff_reg} <= {ts_ns_reg, ts_fns_reg} - (FNS_WIDTH > 16 ? mem_read_data_reg[63:0] << (FNS_WIDTH-16) : mem_read_data_reg[63:0] >> (16-FNS_WIDTH)); + sec_mismatch_next = 1'b0; + diff_valid_next = 1'b1; + {ts_ns_diff_next, ts_fns_diff_next} = {ts_ns_sync_reg, ts_fns_sync_reg} - {dest_ts_ns_capt_reg, dest_ts_fns_capt_reg}; end end - end else if (diff_valid_reg) begin - // offset difference by FIFO delay - diff_offset_valid_reg <= 1'b1; - diff_valid_reg <= 1'b0; - {ts_ns_diff_reg, ts_fns_diff_reg} <= {ts_ns_diff_reg, ts_fns_diff_reg} - ({period_ns_reg, period_fns_reg} * 2**(LOG_RATE + LOG_FIFO_DEPTH)); - end else if (diff_offset_valid_reg) begin - // PI control - diff_offset_valid_reg <= 1'b0; - if (($signed({ts_ns_diff_reg, ts_fns_diff_reg}) / 2**10) + ($signed(time_err_int_reg) / 2**16) > 4*2**16) begin - // limit positive adjustment - time_err_int_reg <= 0; - {period_ns_reg, period_fns_reg} <= $signed(OUTPUT_PERIOD_NS*2**16 + OUTPUT_PERIOD_FNS) - ({4'd4, {FNS_WIDTH{1'b0}}}); - end else if (($signed({ts_ns_diff_reg, ts_fns_diff_reg}) / 2**10) + ($signed(time_err_int_reg) / 2**16) < -8*2**16) begin - // limit negative adjustment - time_err_int_reg <= 0; - {period_ns_reg, period_fns_reg} <= $signed(OUTPUT_PERIOD_NS*2**16 + OUTPUT_PERIOD_FNS) + ({4'd8, {FNS_WIDTH{1'b0}}}); - end else begin - time_err_int_reg <= $signed(time_err_int_reg) + $signed({ts_ns_diff_reg, ts_fns_diff_reg}); - {period_ns_reg, period_fns_reg} <= $signed(OUTPUT_PERIOD_NS*2**16 + OUTPUT_PERIOD_FNS) - ($signed({ts_ns_diff_reg, ts_fns_diff_reg}) / 2**10) - ($signed(time_err_int_reg) / 2**16); - end end + if (diff_valid_reg) begin + // seconds field correction + if (TS_WIDTH == 96) begin + if ($signed(ts_s_diff_reg) == 0 && ($signed(ts_ns_diff_reg[30:16]) == 0 || $signed(ts_ns_diff_reg[30:16]) == -1)) begin + // difference is small and no seconds difference; slew + ts_ns_diff_corr_next = ts_ns_diff_reg[16:0]; + ts_fns_diff_corr_next = ts_fns_diff_reg; + diff_corr_valid_next = 1'b1; + end else if ($signed(ts_s_diff_reg) == 1 && ts_ns_diff_reg[30:16] == 15'h4465) begin + // difference is small with 1 second difference; adjust and slew + ts_ns_diff_corr_next = ts_ns_diff_reg[16:0] + 17'h0ca00; + ts_fns_diff_corr_next = ts_fns_diff_reg; + diff_corr_valid_next = 1'b1; + end else if ($signed(ts_s_diff_reg) == -1 && ts_ns_diff_reg[30:16] == 15'h3b9a) begin + // difference is small with 1 second difference; adjust and slew + ts_ns_diff_corr_next = ts_ns_diff_reg[16:0] - 17'h0ca00; + ts_fns_diff_corr_next = ts_fns_diff_reg; + diff_corr_valid_next = 1'b1; + end else begin + // difference is too large; step the clock + sec_mismatch_next = 1'b1; + end + end else if (TS_WIDTH == 64) begin + if ($signed(ts_ns_diff_reg[47:16]) == 0 || $signed(ts_ns_diff_reg[47:16]) == -1) begin + // difference is small enough to slew + ts_ns_diff_corr_next = ts_ns_diff_reg[16:0]; + ts_fns_diff_corr_next = ts_fns_diff_reg; + diff_corr_valid_next = 1'b1; + end else begin + // difference is too large; step the clock + sec_mismatch_next = 1'b1; + end + end + end + + if (diff_corr_valid_reg) begin + // PI control + + // time integral of error + if (ptp_locked_reg) begin + {ptp_ovf, time_err_int_next} = $signed({1'b0, time_err_int_reg}) + $signed({ts_ns_diff_corr_reg, ts_fns_diff_corr_reg}); + end else begin + {ptp_ovf, time_err_int_next} = $signed({1'b0, time_err_int_reg}) + ($signed({ts_ns_diff_corr_reg, ts_fns_diff_corr_reg}) * 2**3); + end + + // saturate + if (ptp_ovf[1]) begin + // sign bit set indicating underflow across zero; saturate to zero + time_err_int_next = {TIME_ERR_INT_WIDTH{1'b0}}; + end else if (ptp_ovf[0]) begin + // sign bit clear but carry bit set indicating overflow; saturate to all 1 + time_err_int_next = {TIME_ERR_INT_WIDTH{1'b1}}; + end + + // compute output + if (ptp_locked_reg) begin + {ptp_ovf, period_ns_next, period_fns_next} = ($signed({1'b0, time_err_int_reg}) / 2**16) + ($signed({ts_ns_diff_corr_reg, ts_fns_diff_corr_reg}) / 2**10); + end else begin + {ptp_ovf, period_ns_next, period_fns_next} = ($signed({1'b0, time_err_int_reg}) / 2**16) + ($signed({ts_ns_diff_corr_reg, ts_fns_diff_corr_reg}) / 2**7); + end + + // saturate + if (ptp_ovf[1]) begin + // sign bit set indicating underflow across zero; saturate to zero + {period_ns_next, period_fns_next} = {NS_WIDTH+FNS_WIDTH{1'b0}}; + end else if (ptp_ovf[0]) begin + // sign bit clear but carry bit set indicating overflow; saturate to all 1 + {period_ns_next, period_fns_next} = {NS_WIDTH+FNS_WIDTH{1'b1}}; + end + + // locked status + if ($signed(ts_ns_diff_corr_reg[17-1:4]) == 0 || $signed(ts_ns_diff_corr_reg[17-1:4]) == -1) begin + if (ptp_lock_count_reg == {PTP_LOCK_WIDTH{1'b1}}) begin + ptp_locked_next = 1'b1; + end else begin + ptp_lock_count_next = ptp_lock_count_reg + 1; + end + end else begin + ptp_lock_count_next = 0; + ptp_locked_next = 1'b0; + end + end +end + +always @(posedge output_clk) begin + period_ns_reg <= period_ns_next; + period_fns_reg <= period_fns_next; + + ts_s_reg <= ts_s_next; + ts_ns_reg <= ts_ns_next; + ts_fns_reg <= ts_fns_next; + ts_ns_inc_reg <= ts_ns_inc_next; + ts_fns_inc_reg <= ts_fns_inc_next; + ts_ns_ovf_reg <= ts_ns_ovf_next; + ts_fns_ovf_reg <= ts_fns_ovf_next; + + ts_step_reg <= ts_step_next; + + sec_mismatch_reg <= sec_mismatch_next; + diff_valid_reg <= diff_valid_next; + diff_corr_valid_reg <= diff_corr_valid_next; + + ts_s_diff_reg <= ts_s_diff_next; + ts_ns_diff_reg <= ts_ns_diff_next; + ts_fns_diff_reg <= ts_fns_diff_next; + + ts_ns_diff_corr_reg <= ts_ns_diff_corr_next; + ts_fns_diff_corr_reg <= ts_fns_diff_corr_next; + + time_err_int_reg <= time_err_int_next; + + ptp_lock_count_reg <= ptp_lock_count_next; + ptp_locked_reg <= ptp_locked_next; + + // PPS output if (TS_WIDTH == 96) begin pps_reg <= !ts_ns_ovf_reg[30]; end else if (TS_WIDTH == 64) begin @@ -447,8 +656,8 @@ always @(posedge output_clk) begin end if (output_rst) begin - period_ns_reg <= OUTPUT_PERIOD_NS; - period_fns_reg <= OUTPUT_PERIOD_FNS; + period_ns_reg <= 0; + period_fns_reg <= 0; ts_s_reg <= 0; ts_ns_reg <= 0; ts_fns_reg <= 0; @@ -458,6 +667,13 @@ always @(posedge output_clk) begin ts_fns_ovf_reg <= {FNS_WIDTH{1'b1}}; ts_step_reg <= 0; pps_reg <= 0; + + diff_valid_reg <= 1'b0; + diff_corr_valid_reg <= 1'b0; + time_err_int_reg <= 0; + + ptp_lock_count_reg <= 0; + ptp_locked_reg <= 1'b0; end end diff --git a/syn/ptp_clock_cdc.tcl b/syn/ptp_clock_cdc.tcl index 408b6eb45..9f798f82a 100644 --- a/syn/ptp_clock_cdc.tcl +++ b/syn/ptp_clock_cdc.tcl @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Alex Forencich +# Copyright (c) 2019-2021 Alex Forencich # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -24,50 +24,52 @@ foreach inst [get_cells -hier -filter {(ORIG_REF_NAME == ptp_clock_cdc || REF_NA puts "Inserting timing constraints for ptp_clock_cdc instance $inst" # get clock periods - set input_clk [get_clocks -of_objects [get_pins $inst/wr_ptr_reg_reg[0]/C]] - set output_clk [get_clocks -of_objects [get_pins $inst/rd_ptr_reg_reg[0]/C]] + set input_clk [get_clocks -of_objects [get_pins "$inst/src_sync_reg_reg/C"]] + set output_clk [get_clocks -of_objects [get_pins "$inst/dest_sync_reg_reg/C"]] set input_clk_period [get_property -min PERIOD $input_clk] set output_clk_period [get_property -min PERIOD $output_clk] - # pointer synchronization - set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/(wr|rd)_ptr_gray_sync\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"] + # timestamp synchronization + set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/ts_(s|ns|fns|step)_sync_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"] - set_max_delay -from [get_cells "$inst/rd_ptr_reg_reg[*] $inst/rd_ptr_gray_reg_reg[*]"] -to [get_cells $inst/rd_ptr_gray_sync1_reg_reg[*]] -datapath_only $output_clk_period - set_bus_skew -from [get_cells "$inst/rd_ptr_reg_reg[*] $inst/rd_ptr_gray_reg_reg[*]"] -to [get_cells $inst/rd_ptr_gray_sync1_reg_reg[*]] $input_clk_period - set_max_delay -from [get_cells "$inst/wr_ptr_reg_reg[*] $inst/wr_ptr_gray_reg_reg[*]"] -to [get_cells $inst/wr_ptr_gray_sync1_reg_reg[*]] -datapath_only $input_clk_period - set_bus_skew -from [get_cells "$inst/wr_ptr_reg_reg[*] $inst/wr_ptr_gray_reg_reg[*]"] -to [get_cells $inst/wr_ptr_gray_sync1_reg_reg[*]] $output_clk_period - - # output register (needed for distributed RAM sync write/async read) - set output_reg_ffs [get_cells -quiet "$inst/mem_read_data_reg_reg[*]"] - - if {[llength $output_reg_ffs]} { - set_false_path -from $input_clk -to $output_reg_ffs + if {[llength [get_cells "$inst/src_ts_s_capt_reg_reg[*]"]]} { + set_max_delay -from [get_cells "$inst/src_ts_s_capt_reg_reg[*]"] -to [get_cells "$inst/ts_s_sync_reg_reg[*]"] -datapath_only $output_clk_period + set_bus_skew -from [get_cells "$inst/src_ts_s_capt_reg_reg[*]"] -to [get_cells "$inst/ts_s_sync_reg_reg[*]"] $input_clk_period } - # pointer synchronization (depth measurement) - set sync_ffs [get_cells -quiet -hier -regexp ".*/rd_ptr_gray_sample_sync\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"] + set_max_delay -from [get_cells "$inst/src_ts_ns_capt_reg_reg[*]"] -to [get_cells "$inst/ts_ns_sync_reg_reg[*]"] -datapath_only $output_clk_period + set_bus_skew -from [get_cells "$inst/src_ts_ns_capt_reg_reg[*]"] -to [get_cells "$inst/ts_ns_sync_reg_reg[*]"] $input_clk_period + + set_max_delay -from [get_cells "$inst/src_ts_fns_capt_reg_reg[*]"] -to [get_cells "$inst/ts_fns_sync_reg_reg[*]"] -datapath_only $output_clk_period + set_bus_skew -from [get_cells "$inst/src_ts_fns_capt_reg_reg[*]"] -to [get_cells "$inst/ts_fns_sync_reg_reg[*]"] $input_clk_period + + if {[llength [get_cells "$inst/src_ts_step_capt_reg_reg"]]} { + set_max_delay -from [get_cells "$inst/src_ts_step_capt_reg_reg"] -to [get_cells "$inst/ts_step_sync_reg_reg"] -datapath_only $output_clk_period + set_bus_skew -from [get_cells "$inst/src_ts_step_capt_reg_reg"] -to [get_cells "$inst/ts_step_sync_reg_reg"] $input_clk_period + } + + # sample clock + set sync_ffs [get_cells -quiet -hier -regexp ".*/src_sample_sync\[12\]_reg_reg" -filter "PARENT == $inst"] if {[llength $sync_ffs]} { set_property ASYNC_REG TRUE $sync_ffs - set src_clk [get_clocks -of_objects [get_pins $inst/rd_ptr_gray_reg_reg[0]/C]] - set dest_clk [get_clocks -of_objects [get_pins $inst/rd_ptr_gray_sample_sync1_reg_reg[0]/C]] + set src_clk [get_clocks -of_objects [get_pins "$inst/src_sync_reg_reg/C"]] + set dest_clk [get_clocks -of_objects [get_pins "$inst/src_sample_sync1_reg_reg/C"]] - set_max_delay -from [get_cells "$inst/rd_ptr_reg_reg[*] $inst/rd_ptr_gray_reg_reg[*]"] -to [get_cells $inst/rd_ptr_gray_sample_sync1_reg_reg[*]] -datapath_only [get_property -min PERIOD $src_clk] - set_bus_skew -from [get_cells "$inst/rd_ptr_reg_reg[*] $inst/rd_ptr_gray_reg_reg[*]"] -to [get_cells $inst/rd_ptr_gray_sample_sync1_reg_reg[*]] [get_property -min PERIOD $dest_clk] + set_max_delay -from [get_cells "$inst/src_sync_reg_reg"] -to [get_cells "$inst/src_sample_sync1_reg_reg"] -datapath_only [get_property -min PERIOD $src_clk] } - set sync_ffs [get_cells -quiet -hier -regexp ".*/wr_ptr_gray_sample_sync\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"] + set sync_ffs [get_cells -quiet -hier -regexp ".*/dest_sample_sync\[12\]_reg_reg" -filter "PARENT == $inst"] if {[llength $sync_ffs]} { set_property ASYNC_REG TRUE $sync_ffs - set src_clk [get_clocks -of_objects [get_pins $inst/wr_ptr_gray_reg_reg[0]/C]] - set dest_clk [get_clocks -of_objects [get_pins $inst/wr_ptr_gray_sample_sync1_reg_reg[0]/C]] + set src_clk [get_clocks -of_objects [get_pins "$inst/dest_sync_reg_reg/C"]] + set dest_clk [get_clocks -of_objects [get_pins "$inst/dest_sample_sync1_reg_reg/C"]] - set_max_delay -from [get_cells "$inst/wr_ptr_reg_reg[*] $inst/wr_ptr_gray_reg_reg[*]"] -to [get_cells $inst/wr_ptr_gray_sample_sync1_reg_reg[*]] -datapath_only [get_property -min PERIOD $src_clk] - set_bus_skew -from [get_cells "$inst/wr_ptr_reg_reg[*] $inst/wr_ptr_gray_reg_reg[*]"] -to [get_cells $inst/wr_ptr_gray_sample_sync1_reg_reg[*]] [get_property -min PERIOD $dest_clk] + set_max_delay -from [get_cells "$inst/dest_sync_reg_reg"] -to [get_cells "$inst/dest_sample_sync1_reg_reg"] -datapath_only [get_property -min PERIOD $src_clk] } # sample update sync @@ -76,12 +78,24 @@ foreach inst [get_cells -hier -filter {(ORIG_REF_NAME == ptp_clock_cdc || REF_NA if {[llength $sync_ffs]} { set_property ASYNC_REG TRUE $sync_ffs - set src_clk [get_clocks -of_objects [get_pins $inst/sample_update_reg_reg/C]] - set dest_clk [get_clocks -of_objects [get_pins $inst/sample_update_sync1_reg_reg/C]] + set src_clk [get_clocks -of_objects [get_pins "$inst/sample_update_reg_reg/C"]] + set dest_clk [get_clocks -of_objects [get_pins "$inst/sample_update_sync1_reg_reg/C"]] - set_max_delay -from [get_cells $inst/sample_update_reg_reg] -to [get_cells $inst/sample_update_sync1_reg_reg] -datapath_only [get_property -min PERIOD $src_clk] + set_max_delay -from [get_cells "$inst/sample_update_reg_reg"] -to [get_cells "$inst/sample_update_sync1_reg_reg"] -datapath_only [get_property -min PERIOD $src_clk] - set_max_delay -from [get_cells "$inst/sample_avg_reg_reg[*]"] -to [get_cells $inst/sample_avg_sync_reg_reg[*]] -datapath_only [get_property -min PERIOD $src_clk] - set_bus_skew -from [get_cells "$inst/sample_avg_reg_reg[*]"] -to [get_cells $inst/sample_avg_sync_reg_reg[*]] [get_property -min PERIOD $dest_clk] + set_max_delay -from [get_cells "$inst/sample_acc_out_reg_reg[*]"] -to [get_cells $inst/sample_acc_sync_reg_reg[*]] -datapath_only [get_property -min PERIOD $src_clk] + set_bus_skew -from [get_cells "$inst/sample_acc_out_reg_reg[*]"] -to [get_cells $inst/sample_acc_sync_reg_reg[*]] [get_property -min PERIOD $dest_clk] + } + + # no sample clock + set sync_ffs [get_cells -quiet -hier -regexp ".*/src_sync_sync\[12\]_reg_reg" -filter "PARENT == $inst"] + + if {[llength $sync_ffs]} { + set_property ASYNC_REG TRUE $sync_ffs + + set src_clk [get_clocks -of_objects [get_pins "$inst/src_sync_reg_reg/C"]] + set dest_clk [get_clocks -of_objects [get_pins "$inst/src_sync_sync1_reg_reg/C"]] + + set_max_delay -from [get_cells "$inst/src_sync_reg_reg"] -to [get_cells "$inst/src_sync_sync1_reg_reg"] -datapath_only [get_property -min PERIOD $src_clk] } } diff --git a/tb/eth_mac_10g_fifo/Makefile b/tb/eth_mac_10g_fifo/Makefile index cf15b7346..2765864ac 100644 --- a/tb/eth_mac_10g_fifo/Makefile +++ b/tb/eth_mac_10g_fifo/Makefile @@ -59,8 +59,6 @@ export PARAM_RX_FIFO_PIPELINE_OUTPUT ?= 2 export PARAM_RX_FRAME_FIFO ?= 1 export PARAM_RX_DROP_BAD_FRAME ?= $(PARAM_RX_FRAME_FIFO) export PARAM_RX_DROP_WHEN_FULL ?= $(PARAM_RX_FRAME_FIFO) -export PARAM_LOGIC_PTP_PERIOD_NS ?= 6 -export PARAM_LOGIC_PTP_PERIOD_FNS ?= 26214 export PARAM_PTP_PERIOD_NS ?= 6 export PARAM_PTP_PERIOD_FNS ?= 26214 export PARAM_PTP_USE_SAMPLE_CLOCK ?= 0 @@ -93,8 +91,6 @@ ifeq ($(SIM), icarus) COMPILE_ARGS += -P $(TOPLEVEL).RX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) COMPILE_ARGS += -P $(TOPLEVEL).RX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) - COMPILE_ARGS += -P $(TOPLEVEL).LOGIC_PTP_PERIOD_NS=$(PARAM_LOGIC_PTP_PERIOD_NS) - COMPILE_ARGS += -P $(TOPLEVEL).LOGIC_PTP_PERIOD_FNS=$(PARAM_LOGIC_PTP_PERIOD_FNS) COMPILE_ARGS += -P $(TOPLEVEL).PTP_PERIOD_NS=$(PARAM_PTP_PERIOD_NS) COMPILE_ARGS += -P $(TOPLEVEL).PTP_PERIOD_FNS=$(PARAM_PTP_PERIOD_FNS) COMPILE_ARGS += -P $(TOPLEVEL).PTP_USE_SAMPLE_CLOCK=$(PARAM_PTP_USE_SAMPLE_CLOCK) @@ -131,8 +127,6 @@ else ifeq ($(SIM), verilator) COMPILE_ARGS += -GRX_FRAME_FIFO=$(PARAM_RX_FRAME_FIFO) COMPILE_ARGS += -GRX_DROP_BAD_FRAME=$(PARAM_RX_DROP_BAD_FRAME) COMPILE_ARGS += -GRX_DROP_WHEN_FULL=$(PARAM_RX_DROP_WHEN_FULL) - COMPILE_ARGS += -GLOGIC_PTP_PERIOD_NS=$(PARAM_LOGIC_PTP_PERIOD_NS) - COMPILE_ARGS += -GLOGIC_PTP_PERIOD_FNS=$(PARAM_LOGIC_PTP_PERIOD_FNS) COMPILE_ARGS += -GPTP_PERIOD_NS=$(PARAM_PTP_PERIOD_NS) COMPILE_ARGS += -GPTP_PERIOD_FNS=$(PARAM_PTP_PERIOD_FNS) COMPILE_ARGS += -GPTP_USE_SAMPLE_CLOCK=$(PARAM_PTP_USE_SAMPLE_CLOCK) diff --git a/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py b/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py index d7ba8290c..c459f7ee2 100644 --- a/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py +++ b/tb/eth_mac_10g_fifo/test_eth_mac_10g_fifo.py @@ -63,6 +63,7 @@ class TB: dut.ptp_sample_clk.setimmediatevalue(0) dut.ptp_ts_96.setimmediatevalue(0) + dut.ptp_ts_step.setimmediatevalue(0) async def reset(self): self.dut.logic_rst.setimmediatevalue(0) @@ -281,8 +282,6 @@ def test_eth_mac_10g_fifo(request, data_width, enable_dic): parameters['RX_FRAME_FIFO'] = 1 parameters['RX_DROP_BAD_FRAME'] = parameters['RX_FRAME_FIFO'] parameters['RX_DROP_WHEN_FULL'] = parameters['RX_FRAME_FIFO'] - parameters['LOGIC_PTP_PERIOD_NS'] = 0x6 if parameters['AXIS_DATA_WIDTH'] == 64 else 0x3 - parameters['LOGIC_PTP_PERIOD_FNS'] = 0x6666 if parameters['AXIS_DATA_WIDTH'] == 64 else 0x3333 parameters['PTP_PERIOD_NS'] = 0x6 if parameters['DATA_WIDTH'] == 64 else 0x3 parameters['PTP_PERIOD_FNS'] = 0x6666 if parameters['DATA_WIDTH'] == 64 else 0x3333 parameters['PTP_USE_SAMPLE_CLOCK'] = 0 diff --git a/tb/ptp_clock_cdc/Makefile b/tb/ptp_clock_cdc/Makefile index 6cfe4a1d6..ff4c9f9c4 100644 --- a/tb/ptp_clock_cdc/Makefile +++ b/tb/ptp_clock_cdc/Makefile @@ -35,12 +35,7 @@ VERILOG_SOURCES += ../../rtl/$(DUT).v export PARAM_TS_WIDTH ?= 96 export PARAM_NS_WIDTH ?= 4 export PARAM_FNS_WIDTH ?= 16 -export PARAM_INPUT_PERIOD_NS ?= 6 -export PARAM_INPUT_PERIOD_FNS ?= 26214 -export PARAM_OUTPUT_PERIOD_NS ?= 6 -export PARAM_OUTPUT_PERIOD_FNS ?= 26214 export PARAM_USE_SAMPLE_CLOCK ?= 1 -export PARAM_LOG_FIFO_DEPTH ?= 3 export PARAM_LOG_RATE ?= 3 ifeq ($(SIM), icarus) @@ -49,12 +44,7 @@ ifeq ($(SIM), icarus) COMPILE_ARGS += -P $(TOPLEVEL).TS_WIDTH=$(PARAM_TS_WIDTH) COMPILE_ARGS += -P $(TOPLEVEL).NS_WIDTH=$(PARAM_NS_WIDTH) COMPILE_ARGS += -P $(TOPLEVEL).FNS_WIDTH=$(PARAM_FNS_WIDTH) - COMPILE_ARGS += -P $(TOPLEVEL).INPUT_PERIOD_NS=$(PARAM_INPUT_PERIOD_NS) - COMPILE_ARGS += -P $(TOPLEVEL).INPUT_PERIOD_FNS=$(PARAM_INPUT_PERIOD_FNS) - COMPILE_ARGS += -P $(TOPLEVEL).OUTPUT_PERIOD_NS=$(PARAM_OUTPUT_PERIOD_NS) - COMPILE_ARGS += -P $(TOPLEVEL).OUTPUT_PERIOD_FNS=$(PARAM_OUTPUT_PERIOD_FNS) COMPILE_ARGS += -P $(TOPLEVEL).USE_SAMPLE_CLOCK=$(PARAM_USE_SAMPLE_CLOCK) - COMPILE_ARGS += -P $(TOPLEVEL).LOG_FIFO_DEPTH=$(PARAM_LOG_FIFO_DEPTH) COMPILE_ARGS += -P $(TOPLEVEL).LOG_RATE=$(PARAM_LOG_RATE) ifeq ($(WAVES), 1) @@ -67,12 +57,7 @@ else ifeq ($(SIM), verilator) COMPILE_ARGS += -GTS_WIDTH=$(PARAM_TS_WIDTH) COMPILE_ARGS += -GNS_WIDTH=$(PARAM_NS_WIDTH) COMPILE_ARGS += -GFNS_WIDTH=$(PARAM_FNS_WIDTH) - COMPILE_ARGS += -GINPUT_PERIOD_NS=$(PARAM_INPUT_PERIOD_NS) - COMPILE_ARGS += -GINPUT_PERIOD_FNS=$(PARAM_INPUT_PERIOD_FNS) - COMPILE_ARGS += -GOUTPUT_PERIOD_NS=$(PARAM_OUTPUT_PERIOD_NS) - COMPILE_ARGS += -GOUTPUT_PERIOD_FNS=$(PARAM_OUTPUT_PERIOD_FNS) COMPILE_ARGS += -GUSE_SAMPLE_CLOCK=$(PARAM_USE_SAMPLE_CLOCK) - COMPILE_ARGS += -GLOG_FIFO_DEPTH=$(PARAM_LOG_FIFO_DEPTH) COMPILE_ARGS += -GLOG_RATE=$(PARAM_LOG_RATE) ifeq ($(WAVES), 1) diff --git a/tb/ptp_clock_cdc/test_ptp_clock_cdc.py b/tb/ptp_clock_cdc/test_ptp_clock_cdc.py index 5c7ef551a..a3cb90569 100644 --- a/tb/ptp_clock_cdc/test_ptp_clock_cdc.py +++ b/tb/ptp_clock_cdc/test_ptp_clock_cdc.py @@ -51,6 +51,7 @@ class TB: if len(dut.input_ts) == 64: self.ptp_clock = PtpClock( ts_64=dut.input_ts, + ts_step=dut.input_ts_step, clock=dut.input_clk, reset=dut.input_rst, period_ns=6.4 @@ -58,6 +59,7 @@ class TB: else: self.ptp_clock = PtpClock( ts_96=dut.input_ts, + ts_step=dut.input_ts_step, clock=dut.input_clk, reset=dut.input_rst, period_ns=6.4 @@ -70,6 +72,7 @@ class TB: self.dut.input_rst.setimmediatevalue(0) self.dut.output_rst.setimmediatevalue(0) await RisingEdge(self.dut.input_clk) + await RisingEdge(self.dut.input_clk) self.dut.input_rst <= 1 self.dut.output_rst <= 1 for k in range(10): @@ -131,9 +134,11 @@ async def run_test(dut): await RisingEdge(dut.input_clk) - for i in range(20000): + for i in range(40000): await RisingEdge(dut.input_clk) + assert tb.dut.locked.value.integer + diff = await tb.measure_ts_diff()*1e9 tb.log.info(f"Difference: {diff} ns") @@ -147,9 +152,11 @@ async def run_test(dut): await RisingEdge(dut.input_clk) - for i in range(20000): + for i in range(40000): await RisingEdge(dut.input_clk) + assert tb.dut.locked.value.integer + diff = await tb.measure_ts_diff()*1e9 tb.log.info(f"Difference: {diff} ns") @@ -163,9 +170,11 @@ async def run_test(dut): await RisingEdge(dut.input_clk) - for i in range(20000): + for i in range(40000): await RisingEdge(dut.input_clk) + assert tb.dut.locked.value.integer + diff = await tb.measure_ts_diff()*1e9 tb.log.info(f"Difference: {diff} ns") @@ -179,9 +188,11 @@ async def run_test(dut): await RisingEdge(dut.input_clk) - for i in range(20000): + for i in range(40000): await RisingEdge(dut.input_clk) + assert tb.dut.locked.value.integer + diff = await tb.measure_ts_diff()*1e9 tb.log.info(f"Difference: {diff} ns") @@ -198,6 +209,8 @@ async def run_test(dut): for i in range(30000): await RisingEdge(dut.input_clk) + assert tb.dut.locked.value.integer + diff = await tb.measure_ts_diff()*1e9 tb.log.info(f"Difference: {diff} ns") @@ -232,12 +245,7 @@ def test_ptp_clock_cdc(request, ts_width, sample_clock): parameters['TS_WIDTH'] = ts_width parameters['NS_WIDTH'] = 4 parameters['FNS_WIDTH'] = 16 - parameters['INPUT_PERIOD_NS'] = 0x6 - parameters['INPUT_PERIOD_FNS'] = 0x6666 - parameters['OUTPUT_PERIOD_NS'] = 0x6 - parameters['OUTPUT_PERIOD_FNS'] = 0x6666 parameters['USE_SAMPLE_CLOCK'] = sample_clock - parameters['LOG_FIFO_DEPTH'] = 3 parameters['LOG_RATE'] = 3 extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}