Reorganize repository

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2025-05-18 12:25:59 -07:00
parent 8cdae180a1
commit 66b53d98a2
690 changed files with 2314 additions and 1581 deletions

View File

@@ -0,0 +1,343 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2015-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* PTP clock module
*/
module taxi_ptp_clock #
(
parameter PERIOD_NS_W = 4,
parameter OFFSET_NS_W = 4,
parameter FNS_W = 16,
parameter PERIOD_NS_NUM = 32,
parameter PERIOD_NS_DENOM = 5,
parameter PIPELINE_OUTPUT = 0
)
(
input wire logic clk,
input wire logic rst,
/*
* Timestamp inputs for synchronization
*/
input wire logic [95:0] input_ts_tod,
input wire logic input_ts_tod_valid,
input wire logic [63:0] input_ts_rel,
input wire logic input_ts_rel_valid,
/*
* Period adjustment
*/
input wire logic [PERIOD_NS_W-1:0] input_period_ns,
input wire logic [FNS_W-1:0] input_period_fns,
input wire logic input_period_valid,
/*
* Offset adjustment
*/
input wire logic [OFFSET_NS_W-1:0] input_adj_ns,
input wire logic [FNS_W-1:0] input_adj_fns,
input wire logic [15:0] input_adj_count,
input wire logic input_adj_valid,
output wire logic input_adj_active,
/*
* Drift adjustment
*/
input wire logic [FNS_W-1:0] input_drift_num,
input wire logic [15:0] input_drift_denom,
input wire logic input_drift_valid,
/*
* Timestamp outputs
*/
output wire logic [95:0] output_ts_tod,
output wire logic output_ts_tod_step,
output wire logic [63:0] output_ts_rel,
output wire logic output_ts_rel_step,
/*
* PPS output
*/
output wire logic output_pps,
output wire logic output_pps_str
);
localparam PERIOD_NS = PERIOD_NS_NUM / PERIOD_NS_DENOM;
localparam PERIOD_NS_REM = PERIOD_NS_NUM - PERIOD_NS*PERIOD_NS_DENOM;
localparam PERIOD_FNS = (PERIOD_NS_REM * {32'd1, {FNS_W{1'b0}}}) / (32+FNS_W)'(PERIOD_NS_DENOM);
localparam PERIOD_FNS_REM = (PERIOD_NS_REM * {32'd1, {FNS_W{1'b0}}}) - PERIOD_FNS*PERIOD_NS_DENOM;
localparam INC_NS_W = $clog2(2**PERIOD_NS_W + 2**OFFSET_NS_W);
localparam [30:0] NS_PER_S = 31'd1_000_000_000;
logic [PERIOD_NS_W-1:0] period_ns_reg = PERIOD_NS_W'(PERIOD_NS);
logic [FNS_W-1:0] period_fns_reg = FNS_W'(PERIOD_FNS);
logic [OFFSET_NS_W-1:0] adj_ns_reg = '0;
logic [FNS_W-1:0] adj_fns_reg = '0;
logic [15:0] adj_count_reg = '0;
logic adj_active_reg = '0;
logic [FNS_W-1:0] drift_num_reg = FNS_W'(PERIOD_FNS_REM);
logic [15:0] drift_denom_reg = 16'(PERIOD_NS_DENOM);
logic [15:0] drift_cnt_reg = '0;
logic drift_apply_reg = 1'b0;
logic [INC_NS_W-1:0] ts_inc_ns_reg = '0;
logic [FNS_W-1:0] ts_inc_fns_reg = '0;
logic [INC_NS_W-1:0] ts_inc_ns_delay_reg = '0;
logic [FNS_W-1:0] ts_inc_fns_delay_reg = '0;
logic [30:0] ts_inc_ns_ovf_reg = '0;
logic [FNS_W-1:0] ts_inc_fns_ovf_reg = '0;
logic [47:0] ts_tod_s_reg = '0;
logic [29:0] ts_tod_ns_reg = '0;
logic [FNS_W-1:0] ts_tod_fns_reg = '0;
logic [29:0] ts_tod_ns_inc_reg = '0;
logic [FNS_W-1:0] ts_tod_fns_inc_reg = '0;
logic [30:0] ts_tod_ns_ovf_reg = '1;
logic [FNS_W-1:0] ts_tod_fns_ovf_reg = '1;
logic [47:0] ts_rel_ns_reg = '0;
logic [FNS_W-1:0] ts_rel_fns_reg = '0;
logic ts_tod_step_reg = 1'b0;
logic ts_rel_step_reg = 1'b0;
logic [47:0] temp;
logic pps_reg = 0;
logic pps_str_reg = 0;
assign input_adj_active = adj_active_reg;
if (PIPELINE_OUTPUT > 0) begin
// pipeline
(* shreg_extract = "no" *)
logic [95:0] output_ts_tod_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_ts_tod_step_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic [63:0] output_ts_rel_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_ts_rel_step_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_pps_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_pps_str_reg[0:PIPELINE_OUTPUT-1];
assign output_ts_tod = output_ts_tod_reg[PIPELINE_OUTPUT-1];
assign output_ts_tod_step = output_ts_tod_step_reg[PIPELINE_OUTPUT-1];
assign output_ts_rel = output_ts_rel_reg[PIPELINE_OUTPUT-1];
assign output_ts_rel_step = output_ts_rel_step_reg[PIPELINE_OUTPUT-1];
assign output_pps = output_pps_reg[PIPELINE_OUTPUT-1];
assign output_pps_str = output_pps_str_reg[PIPELINE_OUTPUT-1];
initial begin
for (integer i = 0; i < PIPELINE_OUTPUT; i = i + 1) begin
output_ts_tod_reg[i] = '0;
output_ts_tod_step_reg[i] = 1'b0;
output_ts_rel_reg[i] = '0;
output_ts_rel_step_reg[i] = 1'b0;
output_pps_reg[i] = 1'b0;
output_pps_str_reg[i] = 1'b0;
end
end
always_ff @(posedge clk) begin
output_ts_tod_reg[0][95:48] <= ts_tod_s_reg;
output_ts_tod_reg[0][47:46] <= 2'b00;
output_ts_tod_reg[0][45:16] <= ts_tod_ns_reg;
output_ts_tod_reg[0][15:0] <= {ts_tod_fns_reg, 16'd0} >> FNS_W;
output_ts_tod_step_reg[0] <= ts_tod_step_reg;
output_ts_rel_reg[0][63:16] <= ts_rel_ns_reg;
output_ts_rel_reg[0][15:0] <= {ts_rel_fns_reg, 16'd0} >> FNS_W;
output_ts_rel_step_reg[0] <= ts_rel_step_reg;
output_pps_reg[0] <= pps_reg;
output_pps_str_reg[0] <= pps_str_reg;
for (integer i = 0; i < PIPELINE_OUTPUT-1; i = i + 1) begin
output_ts_tod_reg[i+1] <= output_ts_tod_reg[i];
output_ts_tod_step_reg[i+1] <= output_ts_tod_step_reg[i];
output_ts_rel_reg[i+1] <= output_ts_rel_reg[i];
output_ts_rel_step_reg[i+1] <= output_ts_rel_step_reg[i];
output_pps_reg[i+1] <= output_pps_reg[i];
output_pps_str_reg[i+1] <= output_pps_str_reg[i];
end
if (rst) begin
for (integer i = 0; i < PIPELINE_OUTPUT; i = i + 1) begin
output_ts_tod_reg[i] <= '0;
output_ts_tod_step_reg[i] <= 1'b0;
output_ts_rel_reg[i] <= '0;
output_ts_rel_step_reg[i] <= 1'b0;
output_pps_reg[i] <= 1'b0;
output_pps_str_reg[i] <= 1'b0;
end
end
end
end else begin
assign output_ts_tod[95:48] = ts_tod_s_reg;
assign output_ts_tod[47:46] = 2'b00;
assign output_ts_tod[45:16] = ts_tod_ns_reg;
assign output_ts_tod[15:0] = 16'({ts_tod_fns_reg, 16'd0} >> FNS_W);
assign output_ts_tod_step = ts_tod_step_reg;
assign output_ts_rel[63:16] = ts_rel_ns_reg;
assign output_ts_rel[15:0] = 16'({ts_rel_fns_reg, 16'd0} >> FNS_W);
assign output_ts_rel_step = ts_rel_step_reg;
assign output_pps = pps_reg;
assign output_pps_str = pps_str_reg;
end
always_ff @(posedge clk) begin
ts_tod_step_reg <= 1'b0;
ts_rel_step_reg <= 1'b0;
drift_apply_reg <= 1'b0;
pps_reg <= 1'b0;
// latch parameters
if (input_period_valid) begin
period_ns_reg <= input_period_ns;
period_fns_reg <= input_period_fns;
end
if (input_adj_valid) begin
adj_ns_reg <= input_adj_ns;
adj_fns_reg <= input_adj_fns;
adj_count_reg <= input_adj_count;
end
if (input_drift_valid) begin
drift_num_reg <= input_drift_num;
drift_denom_reg <= input_drift_denom;
end
// timestamp increment calculation
{ts_inc_ns_reg, ts_inc_fns_reg} <= $signed({1'b0, period_ns_reg, period_fns_reg}) +
(adj_active_reg ? (INC_NS_W+FNS_W)'($signed({adj_ns_reg, adj_fns_reg})) : '0) +
(drift_apply_reg ? (INC_NS_W+FNS_W)'($signed(drift_num_reg)) : '0);
// offset adjust counter
if (adj_count_reg != 0) begin
adj_count_reg <= adj_count_reg - 1;
adj_active_reg <= 1;
ts_tod_step_reg <= 1;
ts_rel_step_reg <= 1;
end else begin
adj_active_reg <= 0;
end
// drift counter
if (drift_cnt_reg != 0) begin
drift_cnt_reg <= drift_cnt_reg - 1;
end else begin
drift_cnt_reg <= drift_denom_reg-1;
drift_apply_reg <= 1'b1;
end
// 96 bit timestamp
{ts_inc_ns_delay_reg, ts_inc_fns_delay_reg} <= {ts_inc_ns_reg, ts_inc_fns_reg};
{ts_inc_ns_ovf_reg, ts_inc_fns_ovf_reg} <= {NS_PER_S, {FNS_W{1'b0}}} - (31+FNS_W)'({ts_inc_ns_reg, ts_inc_fns_reg});
{ts_tod_ns_inc_reg, ts_tod_fns_inc_reg} <= {ts_tod_ns_inc_reg, ts_tod_fns_inc_reg} + (30+FNS_W)'({ts_inc_ns_delay_reg, ts_inc_fns_delay_reg});
{ts_tod_ns_ovf_reg, ts_tod_fns_ovf_reg} <= {ts_tod_ns_inc_reg, ts_tod_fns_inc_reg} - (31+FNS_W)'({ts_inc_ns_ovf_reg[29:0], ts_inc_fns_ovf_reg});
{ts_tod_ns_reg, ts_tod_fns_reg} <= {ts_tod_ns_inc_reg, ts_tod_fns_inc_reg};
if (ts_tod_ns_reg[29]) begin
pps_str_reg <= 1'b0;
end
if (!ts_tod_ns_ovf_reg[30]) begin
// if the overflow lookahead did not borrow, one second has elapsed
// increment seconds field, pre-compute normal increment, force overflow lookahead borrow bit set
{ts_tod_ns_inc_reg, ts_tod_fns_inc_reg} <= (30+FNS_W)'({ts_tod_ns_ovf_reg[29:0], ts_tod_fns_ovf_reg} + {ts_inc_ns_delay_reg, ts_inc_fns_delay_reg});
ts_tod_ns_ovf_reg[30] <= 1'b1;
{ts_tod_ns_reg, ts_tod_fns_reg} <= {ts_tod_ns_ovf_reg[29:0], ts_tod_fns_ovf_reg};
ts_tod_s_reg <= ts_tod_s_reg + 1;
pps_reg <= 1'b1;
pps_str_reg <= 1'b1;
end
if (input_ts_tod_valid) begin
// load timestamp
ts_tod_s_reg <= input_ts_tod[95:48];
ts_tod_ns_reg <= input_ts_tod[45:16];
ts_tod_ns_inc_reg <= input_ts_tod[45:16];
ts_tod_ns_ovf_reg[30] <= 1'b1;
ts_tod_fns_reg <= FNS_W > 16 ? input_ts_tod[15:0] << (FNS_W-16) : input_ts_tod[15:0] >> (16-FNS_W);
ts_tod_fns_inc_reg <= FNS_W > 16 ? input_ts_tod[15:0] << (FNS_W-16) : input_ts_tod[15:0] >> (16-FNS_W);
ts_tod_step_reg <= 1;
end
// 64 bit timestamp
{ts_rel_ns_reg, ts_rel_fns_reg} <= {ts_rel_ns_reg, ts_rel_fns_reg} + (48+FNS_W)'({ts_inc_ns_reg, ts_inc_fns_reg});
if (input_ts_rel_valid) begin
// load timestamp
{ts_rel_ns_reg, ts_rel_fns_reg} <= input_ts_rel;
ts_rel_step_reg <= 1;
end
if (rst) begin
period_ns_reg <= PERIOD_NS_W'(PERIOD_NS);
period_fns_reg <= FNS_W'(PERIOD_FNS);
adj_ns_reg <= '0;
adj_fns_reg <= '0;
adj_count_reg <= '0;
adj_active_reg <= '0;
drift_num_reg <= FNS_W'(PERIOD_FNS_REM);
drift_denom_reg <= 16'(PERIOD_NS_DENOM);
drift_cnt_reg <= '0;
drift_apply_reg <= 1'b0;
ts_tod_s_reg <= '0;
ts_tod_ns_reg <= '0;
ts_tod_fns_reg <= '0;
ts_tod_ns_inc_reg <= '0;
ts_tod_fns_inc_reg <= '0;
ts_tod_ns_ovf_reg[30] <= 1'b1;
ts_tod_step_reg <= '0;
ts_rel_ns_reg <= '0;
ts_rel_fns_reg <= '0;
ts_rel_step_reg <= '0;
pps_reg <= '0;
pps_str_reg <= '0;
end
end
endmodule
`resetall

View File

@@ -0,0 +1,813 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2019-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1fs
`default_nettype none
/*
* PTP clock CDC (clock domain crossing) module
*/
module taxi_ptp_clock_cdc #
(
parameter TS_W = 96,
parameter NS_W = 4,
parameter LOG_RATE = 3,
parameter PIPELINE_OUTPUT = 0
)
(
input wire logic input_clk,
input wire logic input_rst,
input wire logic output_clk,
input wire logic output_rst,
input wire logic sample_clk,
/*
* Timestamp inputs from source PTP clock
*/
input wire logic [TS_W-1:0] input_ts,
input wire logic input_ts_step,
/*
* Timestamp outputs
*/
output wire logic [TS_W-1:0] output_ts,
output wire logic output_ts_step,
/*
* PPS output
*/
output wire logic output_pps,
output wire logic output_pps_str,
/*
* Status
*/
output wire logic locked
);
// Check configuration
if (TS_W != 64 && TS_W != 96)
$fatal(0, "Error: Timestamp width must be 64 or 96");
localparam FNS_W = 16;
localparam TS_NS_W = TS_W == 96 ? 30 : 48;
localparam TS_FNS_W = FNS_W > 16 ? 16 : FNS_W;
localparam CMP_FNS_W = 4;
localparam PHASE_CNT_W = LOG_RATE;
localparam PHASE_ACC_W = PHASE_CNT_W+16;
localparam LOG_SAMPLE_SYNC_RATE = LOG_RATE;
localparam SAMPLE_ACC_W = LOG_SAMPLE_SYNC_RATE+2;
localparam LOG_PHASE_ERR_RATE = 4;
localparam PHASE_ERR_ACC_W = LOG_PHASE_ERR_RATE+2;
localparam DEST_SYNC_LOCK_W = 7;
localparam FREQ_LOCK_W = 8;
localparam PTP_LOCK_W = 8;
localparam TIME_ERR_INT_W = NS_W+FNS_W;
localparam [30:0] NS_PER_S = 31'd1_000_000_000;
logic [NS_W+FNS_W-1:0] period_ns_reg = '0, period_ns_next;
logic [NS_W+FNS_W-1:0] period_ns_delay_reg = '0, period_ns_delay_next;
logic [31+FNS_W-1:0] period_ns_ovf_reg = '0, period_ns_ovf_next;
logic [47:0] src_ts_s_capt_reg = '0;
logic [TS_NS_W+CMP_FNS_W-1:0] src_ts_ns_capt_reg = '0;
logic src_ts_step_capt_reg = '0;
logic [47:0] dest_ts_s_capt_reg = '0;
logic [TS_NS_W+CMP_FNS_W-1:0] dest_ts_ns_capt_reg = '0;
logic [47:0] src_ts_s_sync_reg = '0;
logic [TS_NS_W+CMP_FNS_W-1:0] src_ts_ns_sync_reg = '0;
logic src_ts_step_sync_reg = '0;
logic [47:0] ts_s_reg = '0, ts_s_next;
logic [TS_NS_W+FNS_W-1:0] ts_ns_reg = '0, ts_ns_next;
logic [TS_NS_W+FNS_W-1:0] ts_ns_inc_reg = '0, ts_ns_inc_next;
logic [TS_NS_W+FNS_W+1-1:0] ts_ns_ovf_reg = '1, ts_ns_ovf_next;
logic ts_step_reg = 1'b0, ts_step_next;
logic pps_reg = 1'b0;
logic pps_str_reg = 1'b0;
logic [47:0] ts_s_pipe_reg[0:PIPELINE_OUTPUT-1];
logic [TS_NS_W+CMP_FNS_W-1:0] ts_ns_pipe_reg[0:PIPELINE_OUTPUT-1];
logic ts_step_pipe_reg[0:PIPELINE_OUTPUT-1];
logic pps_pipe_reg[0:PIPELINE_OUTPUT-1];
logic [PHASE_CNT_W-1:0] src_phase_reg = '0;
logic [PHASE_ACC_W-1:0] dest_phase_reg = '0, dest_phase_next;
logic [PHASE_ACC_W-1:0] dest_phase_inc_reg = '0, dest_phase_inc_next;
logic src_sync_reg = 1'b0;
logic src_update_reg = 1'b0;
logic src_phase_sync_reg = 1'b0;
logic dest_sync_reg = 1'b0;
logic dest_update_reg = 1'b0, dest_update_next;
logic dest_phase_sync_reg = 1'b0;
logic src_sync_sync1_reg = 1'b0;
logic src_sync_sync2_reg = 1'b0;
logic src_sync_sync3_reg = 1'b0;
logic src_phase_sync_sync1_reg = 1'b0;
logic src_phase_sync_sync2_reg = 1'b0;
logic src_phase_sync_sync3_reg = 1'b0;
logic dest_phase_sync_sync1_reg = 1'b0;
logic dest_phase_sync_sync2_reg = 1'b0;
logic dest_phase_sync_sync3_reg = 1'b0;
logic src_sync_sample_sync1_reg = 1'b0;
logic src_sync_sample_sync2_reg = 1'b0;
logic src_sync_sample_sync3_reg = 1'b0;
logic dest_sync_sample_sync1_reg = 1'b0;
logic dest_sync_sample_sync2_reg = 1'b0;
logic dest_sync_sample_sync3_reg = 1'b0;
logic [SAMPLE_ACC_W-1:0] sample_acc_reg = '0;
logic [SAMPLE_ACC_W-1:0] sample_acc_out_reg = '0;
logic [LOG_SAMPLE_SYNC_RATE-1:0] sample_cnt_reg = '0;
logic sample_update_reg = 1'b0;
logic sample_update_sync1_reg = 1'b0;
logic sample_update_sync2_reg = 1'b0;
logic sample_update_sync3_reg = 1'b0;
if (PIPELINE_OUTPUT > 0) begin
// pipeline
(* shreg_extract = "no" *)
logic [TS_W-1:0] output_ts_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_ts_step_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_pps_reg[0:PIPELINE_OUTPUT-1];
(* shreg_extract = "no" *)
logic output_pps_str_reg[0:PIPELINE_OUTPUT-1];
assign output_ts = output_ts_reg[PIPELINE_OUTPUT-1];
assign output_ts_step = output_ts_step_reg[PIPELINE_OUTPUT-1];
assign output_pps = output_pps_reg[PIPELINE_OUTPUT-1];
assign output_pps_str = output_pps_str_reg[PIPELINE_OUTPUT-1];
initial begin
for (integer i = 0; i < PIPELINE_OUTPUT; i = i + 1) begin
output_ts_reg[i] = 0;
output_ts_step_reg[i] = 1'b0;
output_pps_reg[i] = 1'b0;
output_pps_str_reg[i] = 1'b0;
end
end
always_ff @(posedge output_clk) begin
if (TS_W == 96) begin
output_ts_reg[0][95:48] <= ts_s_reg;
output_ts_reg[0][47:46] <= 2'b00;
output_ts_reg[0][45:0] <= 46'({ts_ns_reg, 16'd0} >> FNS_W);
end else if (TS_W == 64) begin
output_ts_reg[0] <= 64'({ts_ns_reg, 16'd0} >> FNS_W);
end
output_ts_step_reg[0] <= ts_step_reg;
output_pps_reg[0] <= pps_reg;
output_pps_str_reg[0] <= pps_str_reg;
for (integer i = 0; i < PIPELINE_OUTPUT-1; i = i + 1) begin
output_ts_reg[i+1] <= output_ts_reg[i];
output_ts_step_reg[i+1] <= output_ts_step_reg[i];
output_pps_reg[i+1] <= output_pps_reg[i];
output_pps_str_reg[i+1] <= output_pps_str_reg[i];
end
if (output_rst) begin
for (integer i = 0; i < PIPELINE_OUTPUT; i = i + 1) begin
output_ts_reg[i] <= 0;
output_ts_step_reg[i] <= 1'b0;
output_pps_reg[i] <= 1'b0;
output_pps_str_reg[i] <= 1'b0;
end
end
end
end else begin
if (TS_W == 96) begin
assign output_ts[95:48] = ts_s_reg;
assign output_ts[47:46] = 2'b00;
assign output_ts[45:0] = 46'({ts_ns_reg, 16'd0} >> FNS_W);
end else if (TS_W == 64) begin
assign output_ts = 64'({ts_ns_reg, 16'd0} >> FNS_W);
end
assign output_ts_step = ts_step_reg;
assign output_pps = pps_reg;
assign output_pps_str = pps_str_reg;
end
initial begin
for (integer i = 0; i < PIPELINE_OUTPUT; i = i + 1) begin
ts_s_pipe_reg[i] = 0;
ts_ns_pipe_reg[i] = 0;
ts_step_pipe_reg[i] = 1'b0;
pps_pipe_reg[i] = 1'b0;
end
end
// source PTP clock capture and sync logic
logic input_ts_step_reg = 1'b0;
always_ff @(posedge input_clk) begin
input_ts_step_reg <= input_ts_step || input_ts_step_reg;
src_phase_sync_reg <= input_ts[16+8];
{src_update_reg, src_phase_reg} <= src_phase_reg+1;
if (src_update_reg) begin
// capture source TS
if (TS_W == 96) begin
src_ts_s_capt_reg <= 48'(input_ts >> 48);
src_ts_ns_capt_reg <= (TS_NS_W+CMP_FNS_W)'(input_ts[45:0] >> (16-CMP_FNS_W));
end else begin
src_ts_ns_capt_reg <= (TS_NS_W+CMP_FNS_W)'(input_ts >> (16-CMP_FNS_W));
end
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
if (input_rst) begin
input_ts_step_reg <= 1'b0;
src_phase_reg <= '0;
src_sync_reg <= 1'b0;
src_update_reg <= 1'b0;
end
end
// CDC logic
always_ff @(posedge output_clk) begin
src_sync_sync1_reg <= src_sync_reg;
src_sync_sync2_reg <= src_sync_sync1_reg;
src_sync_sync3_reg <= src_sync_sync2_reg;
src_phase_sync_sync1_reg <= src_phase_sync_reg;
src_phase_sync_sync2_reg <= src_phase_sync_sync1_reg;
src_phase_sync_sync3_reg <= src_phase_sync_sync2_reg;
dest_phase_sync_sync1_reg <= dest_phase_sync_reg;
dest_phase_sync_sync2_reg <= dest_phase_sync_sync1_reg;
dest_phase_sync_sync3_reg <= dest_phase_sync_sync2_reg;
end
always_ff @(posedge sample_clk) begin
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
logic edge_1_reg = 1'b0;
logic edge_2_reg = 1'b0;
logic [3:0] active_reg = '0;
always_ff @(posedge sample_clk) begin
// 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;
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;
end
// accumulator
sample_acc_reg <= $signed(sample_acc_reg) + SAMPLE_ACC_W'($signed({1'b0, edge_2_reg})) - SAMPLE_ACC_W'($signed({1'b0, edge_1_reg}));
sample_cnt_reg <= sample_cnt_reg + 1;
if (src_sync_sample_sync2_reg && !src_sync_sample_sync3_reg) begin
active_reg[0] <= 1'b1;
end
if (sample_cnt_reg == 0) begin
active_reg <= {active_reg[2:0], src_sync_sample_sync2_reg && !src_sync_sample_sync3_reg};
sample_acc_reg <= SAMPLE_ACC_W'($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
always_ff @(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
logic [SAMPLE_ACC_W-1:0] sample_acc_sync_reg = '0;
logic sample_acc_sync_valid_reg = '0;
logic [PHASE_ACC_W-1:0] dest_err_int_reg = '0, dest_err_int_next;
logic [1:0] dest_ovf;
logic [DEST_SYNC_LOCK_W-1:0] dest_sync_lock_count_reg = '0, dest_sync_lock_count_next;
logic dest_sync_locked_reg = 1'b0, dest_sync_locked_next;
always_comb begin
{dest_update_next, dest_phase_next} = dest_phase_reg + dest_phase_inc_reg;
dest_phase_inc_next = dest_phase_inc_reg;
dest_err_int_next = dest_err_int_reg;
dest_sync_lock_count_next = dest_sync_lock_count_reg;
dest_sync_locked_next = dest_sync_locked_reg;
dest_ovf = 0;
if (sample_acc_sync_valid_reg) begin
// updated sampled dest_phase error
// time integral of error
if (dest_sync_locked_reg) begin
{dest_ovf, dest_err_int_next} = $signed({1'b0, dest_err_int_reg}) + (PHASE_ACC_W+2)'($signed(sample_acc_sync_reg));
end else begin
{dest_ovf, dest_err_int_next} = $signed({1'b0, dest_err_int_reg}) + (PHASE_ACC_W+2)'($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 = '0;
end else if (dest_ovf[0]) begin
// sign bit clear but carry bit set indicating overflow; saturate to all 1
dest_err_int_next = '1;
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 = '0;
end else if (dest_ovf[0]) begin
// sign bit clear but carry bit set indicating overflow; saturate to all 1
dest_phase_inc_next = '1;
end
// locked status
if ($signed(sample_acc_sync_reg[SAMPLE_ACC_W-1:2]) == 0 || $signed(sample_acc_sync_reg[SAMPLE_ACC_W-1:1]) == -1) begin
if (&dest_sync_lock_count_reg) 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
end
logic [PHASE_ERR_ACC_W-1:0] phase_err_acc_reg = '0;
logic [PHASE_ERR_ACC_W-1:0] phase_err_out_reg = '0;
logic [LOG_PHASE_ERR_RATE-1:0] phase_err_cnt_reg = '0;
logic phase_err_out_valid_reg = '0;
logic phase_edge_1_reg = 1'b0;
logic phase_edge_2_reg = 1'b0;
logic [5:0] phase_active_reg = '0;
logic ts_sync_valid_reg = 1'b0;
always_ff @(posedge output_clk) begin
dest_phase_reg <= dest_phase_next;
dest_phase_inc_reg <= dest_phase_inc_next;
dest_update_reg <= dest_update_next;
sample_acc_sync_valid_reg <= 1'b0;
if (sample_update_sync2_reg ^ sample_update_sync3_reg) begin
// latch in synchronized counts from phase detector
sample_acc_sync_reg <= sample_acc_out_reg;
sample_acc_sync_valid_reg <= 1'b1;
end
if (PIPELINE_OUTPUT > 0) begin
dest_phase_sync_reg <= ts_ns_pipe_reg[PIPELINE_OUTPUT-1][8+FNS_W];
end else begin
dest_phase_sync_reg <= ts_ns_reg[8+FNS_W];
end
// phase and frequency detector
if (dest_phase_sync_sync2_reg && !dest_phase_sync_sync3_reg) begin
if (src_phase_sync_sync2_reg && !src_phase_sync_sync3_reg) begin
phase_edge_1_reg <= 1'b0;
phase_edge_2_reg <= 1'b0;
end else begin
phase_edge_1_reg <= !phase_edge_2_reg;
phase_edge_2_reg <= 1'b0;
end
end else if (src_phase_sync_sync2_reg && !src_phase_sync_sync3_reg) begin
phase_edge_1_reg <= 1'b0;
phase_edge_2_reg <= !phase_edge_1_reg;
end
// accumulator
phase_err_acc_reg <= $signed(phase_err_acc_reg) + PHASE_ERR_ACC_W'($signed({1'b0, phase_edge_2_reg})) - PHASE_ERR_ACC_W'($signed({1'b0, phase_edge_1_reg}));
phase_err_cnt_reg <= phase_err_cnt_reg + 1;
if (src_phase_sync_sync2_reg && !src_phase_sync_sync3_reg) begin
phase_active_reg[0] <= 1'b1;
end
phase_err_out_valid_reg <= 1'b0;
if (phase_err_cnt_reg == 0) begin
phase_active_reg <= {phase_active_reg[4:0], src_phase_sync_sync2_reg && !src_phase_sync_sync3_reg};
phase_err_acc_reg <= PHASE_ERR_ACC_W'($signed({1'b0, phase_edge_2_reg}) - $signed({1'b0, phase_edge_1_reg}));
phase_err_out_reg <= phase_err_acc_reg;
if (phase_active_reg != 0) begin
phase_err_out_valid_reg <= 1'b1;
end
end
if (dest_update_reg) begin
// capture local TS
if (PIPELINE_OUTPUT > 0) begin
dest_ts_s_capt_reg <= ts_s_pipe_reg[PIPELINE_OUTPUT-1];
dest_ts_ns_capt_reg <= ts_ns_pipe_reg[PIPELINE_OUTPUT-1];
end else begin
dest_ts_s_capt_reg <= ts_s_reg;
dest_ts_ns_capt_reg <= (TS_NS_W+CMP_FNS_W)'(ts_ns_reg >> FNS_W-CMP_FNS_W);
end
dest_sync_reg <= !dest_sync_reg;
end
ts_sync_valid_reg <= 1'b0;
if (src_sync_sync2_reg ^ src_sync_sync3_reg) begin
// store captured source TS
if (TS_W == 96) begin
src_ts_s_sync_reg <= src_ts_s_capt_reg;
end
src_ts_ns_sync_reg <= src_ts_ns_capt_reg;
src_ts_step_sync_reg <= src_ts_step_capt_reg;
ts_sync_valid_reg <= 1'b1;
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
dest_phase_reg <= '0;
dest_phase_inc_reg <= '0;
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;
end
end
logic ts_diff_reg = 1'b0, ts_diff_next;
logic ts_diff_valid_reg = 1'b0, ts_diff_valid_next;
logic [3:0] mismatch_cnt_reg = '0, mismatch_cnt_next;
logic load_ts_reg = 1'b0, load_ts_next;
logic [9+CMP_FNS_W-1:0] ts_ns_diff_reg = '0, ts_ns_diff_next;
logic [TIME_ERR_INT_W-1:0] time_err_int_reg = '0, time_err_int_next;
logic [1:0] ptp_ovf;
logic [FREQ_LOCK_W-1:0] freq_lock_count_reg = '0, freq_lock_count_next;
logic freq_locked_reg = 1'b0, freq_locked_next;
logic [PTP_LOCK_W-1:0] ptp_lock_count_reg = '0, ptp_lock_count_next;
logic ptp_locked_reg = 1'b0, ptp_locked_next;
logic gain_sel_reg = '0, gain_sel_next;
assign locked = ptp_locked_reg && freq_locked_reg && dest_sync_locked_reg;
always_comb begin
period_ns_next = period_ns_reg;
ts_s_next = ts_s_reg;
ts_ns_next = ts_ns_reg;
ts_ns_inc_next = ts_ns_inc_reg;
ts_ns_ovf_next = ts_ns_ovf_reg;
ts_step_next = '0;
ts_diff_next = 1'b0;
ts_diff_valid_next = 1'b0;
mismatch_cnt_next = mismatch_cnt_reg;
load_ts_next = load_ts_reg;
ts_ns_diff_next = ts_ns_diff_reg;
time_err_int_next = time_err_int_reg;
freq_lock_count_next = freq_lock_count_reg;
freq_locked_next = freq_locked_reg;
ptp_lock_count_next = ptp_lock_count_reg;
ptp_locked_next = ptp_locked_reg;
gain_sel_next = gain_sel_reg;
// PTP clock
period_ns_delay_next = period_ns_reg;
if (TS_W == 96) begin
// 96 bit timestamp
ts_ns_inc_next = ts_ns_inc_reg + (TS_NS_W+FNS_W)'(period_ns_delay_reg);
ts_ns_ovf_next = ts_ns_inc_reg - (TS_NS_W+FNS_W)'(period_ns_ovf_reg);
ts_ns_next = ts_ns_inc_reg;
period_ns_ovf_next = (31+FNS_W)'({NS_PER_S, {FNS_W{1'b0}}}) - (31+FNS_W)'(period_ns_reg);
if (!ts_ns_ovf_reg[30+FNS_W]) begin
// if the overflow lookahead did not borrow, one second has elapsed
// increment seconds field, pre-compute normal increment, force overflow lookahead borrow bit set
ts_ns_inc_next = ts_ns_ovf_reg[TS_NS_W+FNS_W-1:0] + (TS_NS_W+FNS_W)'(period_ns_delay_reg);
ts_ns_ovf_next[30+FNS_W] = 1'b1;
ts_ns_next = ts_ns_ovf_reg[TS_NS_W+FNS_W-1:0];
ts_s_next = ts_s_reg + 1;
end
end else if (TS_W == 64) begin
// 64 bit timestamp
ts_ns_next = ts_ns_reg + (TS_NS_W+FNS_W)'(period_ns_reg);
end
if (ts_sync_valid_reg) begin
// Read new value
if (TS_W == 96) begin
if (src_ts_step_sync_reg || load_ts_reg) begin
// input stepped
load_ts_next = 1'b0;
ts_s_next = src_ts_s_sync_reg;
ts_ns_next[TS_NS_W+FNS_W-1:9+FNS_W] = src_ts_ns_sync_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W];
ts_ns_inc_next[TS_NS_W+FNS_W-1:9+FNS_W] = src_ts_ns_sync_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W];
ts_ns_ovf_next[30+FNS_W] = 1'b1;
ts_step_next = 1;
end else begin
// input did not step
load_ts_next = 1'b0;
ts_diff_valid_next = freq_locked_reg;
end
// compute difference
ts_ns_diff_next = (9+CMP_FNS_W)'(src_ts_ns_sync_reg - dest_ts_ns_capt_reg);
ts_diff_next = src_ts_s_sync_reg != dest_ts_s_capt_reg || src_ts_ns_sync_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W] != dest_ts_ns_capt_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W];
end else if (TS_W == 64) begin
if (src_ts_step_sync_reg || load_ts_reg) begin
// input stepped
load_ts_next = 1'b0;
ts_ns_next[TS_NS_W+FNS_W-1:9+FNS_W] = src_ts_ns_sync_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W];
ts_step_next = 1;
end else begin
// input did not step
load_ts_next = 1'b0;
ts_diff_valid_next = freq_locked_reg;
end
// compute difference
ts_ns_diff_next = (9+CMP_FNS_W)'(src_ts_ns_sync_reg - dest_ts_ns_capt_reg);
ts_diff_next = src_ts_ns_sync_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W] != dest_ts_ns_capt_reg[TS_NS_W+CMP_FNS_W-1:9+CMP_FNS_W];
end
end
if (ts_diff_valid_reg) begin
if (ts_diff_reg) begin
if (&mismatch_cnt_reg) begin
load_ts_next = 1'b1;
mismatch_cnt_next = '0;
end else begin
mismatch_cnt_next = mismatch_cnt_reg + 1;
end
end else begin
mismatch_cnt_next = '0;
end
end
if (phase_err_out_valid_reg) begin
// coarse phase/frequency lock of PTP clock
if ($signed(phase_err_out_reg) > 4 || $signed(phase_err_out_reg) < -4) begin
if (freq_lock_count_reg != 0) begin
freq_lock_count_next = freq_lock_count_reg - 1;
end else begin
freq_locked_next = 1'b0;
end
end else begin
if (&freq_lock_count_reg) begin
freq_locked_next = 1'b1;
end else begin
freq_lock_count_next = freq_lock_count_reg + 1;
end
end
if (!freq_locked_reg) begin
ts_ns_diff_next = $signed(phase_err_out_reg) * 8 * 2**CMP_FNS_W;
ts_diff_valid_next = 1'b1;
end
end
if (ts_diff_valid_reg) begin
// PI control
// gain scheduling
casez (ts_ns_diff_reg[9+CMP_FNS_W-5 +: 5])
5'b01zzz: gain_sel_next = 1'b1;
5'b001zz: gain_sel_next = 1'b1;
5'b0001z: gain_sel_next = 1'b1;
5'b00001: gain_sel_next = 1'b1;
5'b00000: gain_sel_next = 1'b0;
5'b11111: gain_sel_next = 1'b0;
5'b11110: gain_sel_next = 1'b1;
5'b1110z: gain_sel_next = 1'b1;
5'b110zz: gain_sel_next = 1'b1;
5'b10zzz: gain_sel_next = 1'b1;
default: gain_sel_next = 1'b0;
endcase
// time integral of error
case (gain_sel_reg)
1'b0: {ptp_ovf, time_err_int_next} = $signed({1'b0, time_err_int_reg}) + (TIME_ERR_INT_W+2)'($signed(ts_ns_diff_reg) / 2**4);
1'b1: {ptp_ovf, time_err_int_next} = $signed({1'b0, time_err_int_reg}) + (TIME_ERR_INT_W+2)'($signed(ts_ns_diff_reg) * 2**2);
endcase
// saturate
if (ptp_ovf[1]) begin
// sign bit set indicating underflow across zero; saturate to zero
time_err_int_next = '0;
end else if (ptp_ovf[0]) begin
// sign bit clear but carry bit set indicating overflow; saturate to all 1
time_err_int_next = '1;
end
// compute output
case (gain_sel_reg)
1'b0: {ptp_ovf, period_ns_next} = $signed({1'b0, time_err_int_reg}) + (TIME_ERR_INT_W+2)'($signed(ts_ns_diff_reg) * 2**2);
1'b1: {ptp_ovf, period_ns_next} = $signed({1'b0, time_err_int_reg}) + (TIME_ERR_INT_W+2)'($signed(ts_ns_diff_reg) * 2**6);
endcase
// saturate
if (ptp_ovf[1]) begin
// sign bit set indicating underflow across zero; saturate to zero
period_ns_next = '0;
end else if (ptp_ovf[0]) begin
// sign bit clear but carry bit set indicating overflow; saturate to all 1
period_ns_next = '1;
end
// adjust period if integrator is saturated
if (time_err_int_reg == 0) begin
period_ns_next = '0;
end else if (~time_err_int_reg == 0) begin
period_ns_next = '1;
end
// locked status
if (!freq_locked_reg) begin
ptp_lock_count_next = 0;
ptp_locked_next = 1'b0;
end else if (gain_sel_reg == 1'b0) begin
if (&ptp_lock_count_reg) begin
ptp_locked_next = 1'b1;
end else begin
ptp_lock_count_next = ptp_lock_count_reg + 1;
end
end else begin
if (ptp_lock_count_reg != 0) begin
ptp_lock_count_next = ptp_lock_count_reg - 1;
end else begin
ptp_locked_next = 1'b0;
end
end
end
end
always_ff @(posedge output_clk) begin
period_ns_reg <= period_ns_next;
period_ns_delay_reg <= period_ns_delay_next;
period_ns_ovf_reg <= period_ns_ovf_next;
ts_s_reg <= ts_s_next;
ts_ns_reg <= ts_ns_next;
ts_ns_inc_reg <= ts_ns_inc_next;
ts_ns_ovf_reg <= ts_ns_ovf_next;
ts_step_reg <= ts_step_next;
ts_diff_reg <= ts_diff_next;
ts_diff_valid_reg <= ts_diff_valid_next;
mismatch_cnt_reg <= mismatch_cnt_next;
load_ts_reg <= load_ts_next;
ts_ns_diff_reg <= ts_ns_diff_next;
time_err_int_reg <= time_err_int_next;
freq_lock_count_reg <= freq_lock_count_next;
freq_locked_reg <= freq_locked_next;
ptp_lock_count_reg <= ptp_lock_count_next;
ptp_locked_reg <= ptp_locked_next;
gain_sel_reg <= gain_sel_next;
// PPS output
pps_reg <= 1'b0;
if (TS_W == 96) begin
if (ts_ns_reg[29]) begin
pps_str_reg <= 1'b0;
end
if (!ts_ns_ovf_reg[30+FNS_W]) begin
pps_reg <= 1'b1;
pps_str_reg <= 1'b1;
end
end
// pipeline
if (PIPELINE_OUTPUT > 0) begin
ts_s_pipe_reg[0] <= ts_s_reg;
ts_ns_pipe_reg[0] <= (TS_NS_W+CMP_FNS_W)'(ts_ns_reg >> FNS_W-TS_FNS_W);
ts_step_pipe_reg[0] <= ts_step_reg;
pps_pipe_reg[0] <= pps_reg;
for (integer i = 0; i < PIPELINE_OUTPUT-1; i = i + 1) begin
ts_s_pipe_reg[i+1] <= ts_s_pipe_reg[i];
ts_ns_pipe_reg[i+1] <= ts_ns_pipe_reg[i];
ts_step_pipe_reg[i+1] <= ts_step_pipe_reg[i];
pps_pipe_reg[i+1] <= pps_pipe_reg[i];
end
end
if (output_rst) begin
period_ns_reg <= '0;
ts_s_reg <= '0;
ts_ns_reg <= '0;
ts_ns_inc_reg <= '0;
ts_ns_ovf_reg[30+FNS_W] <= 1'b1;
ts_step_reg <= '0;
pps_reg <= '0;
ts_diff_reg <= 1'b0;
ts_diff_valid_reg <= 1'b0;
mismatch_cnt_reg <= '0;
load_ts_reg <= '0;
time_err_int_reg <= '0;
freq_lock_count_reg <= '0;
freq_locked_reg <= 1'b0;
ptp_lock_count_reg <= '0;
ptp_locked_reg <= 1'b0;
for (integer i = 0; i < PIPELINE_OUTPUT; i = i + 1) begin
ts_s_pipe_reg[i] <= '0;
ts_ns_pipe_reg[i] <= '0;
ts_step_pipe_reg[i] <= 1'b0;
pps_pipe_reg[i] <= 1'b0;
end
end
end
endmodule
`resetall

View File

@@ -0,0 +1,313 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2019-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* PTP period out module
*/
module taxi_ptp_perout #
(
parameter logic FNS_EN = 1'b1,
parameter OUT_START_S = 48'h0,
parameter OUT_START_NS = 30'h0,
parameter OUT_START_FNS = 16'h0000,
parameter OUT_PERIOD_S = 48'd1,
parameter OUT_PERIOD_NS = 30'd0,
parameter OUT_PERIOD_FNS = 16'h0000,
parameter OUT_WIDTH_S = 48'h0,
parameter OUT_WIDTH_NS = 30'd1000,
parameter OUT_WIDTH_FNS = 16'h0000
)
(
input wire logic clk,
input wire logic rst,
/*
* Timestamp input from PTP clock
*/
input wire logic [95:0] input_ts_tod,
input wire logic input_ts_tod_step,
/*
* Control
*/
input wire logic enable,
input wire logic [95:0] input_start,
input wire logic input_start_valid,
input wire logic [95:0] input_period,
input wire logic input_period_valid,
input wire logic [95:0] input_width,
input wire logic input_width_valid,
/*
* Status
*/
output wire logic locked,
output wire logic error,
/*
* Pulse output
*/
output wire logic output_pulse
);
localparam [1:0]
STATE_IDLE = 2'd0,
STATE_UPDATE_RISE = 2'd1,
STATE_UPDATE_FALL = 2'd2;
logic [1:0] state_reg = STATE_IDLE, state_next;
logic [47:0] time_s_reg = '0;
logic [29:0] time_ns_reg = '0;
logic [15:0] time_fns_reg = '0;
logic [47:0] next_rise_s_reg = '0, next_rise_s_next;
logic [29:0] next_rise_ns_reg = '0, next_rise_ns_next;
logic [15:0] next_rise_fns_reg = '0, next_rise_fns_next;
logic [47:0] next_edge_s_reg = '0, next_edge_s_next;
logic [29:0] next_edge_ns_reg = '0, next_edge_ns_next;
logic [15:0] next_edge_fns_reg = '0, next_edge_fns_next;
logic [47:0] start_s_reg = 48'(OUT_START_S);
logic [29:0] start_ns_reg = 30'(OUT_START_NS);
logic [15:0] start_fns_reg = 16'(OUT_START_FNS);
logic [47:0] period_s_reg = 48'(OUT_PERIOD_S);
logic [29:0] period_ns_reg = 30'(OUT_PERIOD_NS);
logic [15:0] period_fns_reg = 16'(OUT_PERIOD_FNS);
logic [47:0] width_s_reg = 48'(OUT_WIDTH_S);
logic [29:0] width_ns_reg = 30'(OUT_WIDTH_NS);
logic [15:0] width_fns_reg = 16'(OUT_WIDTH_FNS);
logic [29:0] ts_tod_ns_inc_reg = '0, ts_tod_ns_inc_next;
logic [15:0] ts_tod_fns_inc_reg = '0, ts_tod_fns_inc_next;
logic [30:0] ts_tod_ns_ovf_reg = '0, ts_tod_ns_ovf_next;
logic [15:0] ts_tod_fns_ovf_reg = '0, ts_tod_fns_ovf_next;
logic restart_reg = 1'b1;
logic locked_reg = 1'b0, locked_next;
logic error_reg = 1'b0, error_next;
logic ffwd_reg = 1'b0, ffwd_next;
logic level_reg = 1'b0, level_next;
logic output_reg = 1'b0, output_next;
assign locked = locked_reg;
assign error = error_reg;
assign output_pulse = output_reg;
always_comb begin
state_next = STATE_IDLE;
next_rise_s_next = next_rise_s_reg;
next_rise_ns_next = next_rise_ns_reg;
next_rise_fns_next = next_rise_fns_reg;
next_edge_s_next = next_edge_s_reg;
next_edge_ns_next = next_edge_ns_reg;
next_edge_fns_next = next_edge_fns_reg;
ts_tod_ns_inc_next = ts_tod_ns_inc_reg;
ts_tod_fns_inc_next = ts_tod_fns_inc_reg;
ts_tod_ns_ovf_next = ts_tod_ns_ovf_reg;
ts_tod_fns_ovf_next = ts_tod_fns_ovf_reg;
locked_next = locked_reg;
error_next = error_reg;
ffwd_next = ffwd_reg;
level_next = level_reg;
output_next = output_reg;
case (state_reg)
STATE_IDLE: begin
if (ffwd_reg || level_reg) begin
// fast forward or falling edge, set up for next rising edge
// set next rise time to previous rise time plus period
{ts_tod_ns_inc_next, ts_tod_fns_inc_next} = {next_rise_ns_reg, next_rise_fns_reg} + {period_ns_reg, period_fns_reg};
{ts_tod_ns_ovf_next, ts_tod_fns_ovf_next} = {next_rise_ns_reg, next_rise_fns_reg} + {period_ns_reg, period_fns_reg} - {30'd1_000_000_000, 16'd0};
end else begin
// rising edge; set up for next falling edge
// set next fall time to previous rise time plus width
{ts_tod_ns_inc_next, ts_tod_fns_inc_next} = {next_rise_ns_reg, next_rise_fns_reg} + {width_ns_reg, width_fns_reg};
{ts_tod_ns_ovf_next, ts_tod_fns_ovf_next} = {next_rise_ns_reg, next_rise_fns_reg} + {width_ns_reg, width_fns_reg} - {30'd1_000_000_000, 16'd0};
end
// wait for edge
if ((time_s_reg > next_edge_s_reg) || (time_s_reg == next_edge_s_reg && {time_ns_reg, time_fns_reg} > {next_edge_ns_reg, next_edge_fns_reg})) begin
if (ffwd_reg || level_reg) begin
// fast forward or falling edge, set up for next rising edge
output_next = 1'b0;
level_next = 1'b0;
state_next = STATE_UPDATE_RISE;
end else begin
// rising edge; set up for next falling edge
locked_next = 1'b1;
error_next = 1'b0;
output_next = enable;
level_next = 1'b1;
state_next = STATE_UPDATE_FALL;
end
end else begin
ffwd_next = 1'b0;
state_next = STATE_IDLE;
end
end
STATE_UPDATE_RISE: begin
if (!ts_tod_ns_ovf_reg[30]) begin
// if the overflow lookahead did not borrow, one second has elapsed
next_edge_s_next = next_rise_s_reg + period_s_reg + 1;
next_edge_ns_next = ts_tod_ns_ovf_reg[29:0];
next_edge_fns_next = ts_tod_fns_ovf_reg;
end else begin
// no increment seconds field
next_edge_s_next = next_rise_s_reg + period_s_reg;
next_edge_ns_next = ts_tod_ns_inc_reg;
next_edge_fns_next = ts_tod_fns_inc_reg;
end
next_rise_s_next = next_edge_s_next;
next_rise_ns_next = next_edge_ns_next;
next_rise_fns_next = next_edge_fns_next;
state_next = STATE_IDLE;
end
STATE_UPDATE_FALL: begin
if (!ts_tod_ns_ovf_reg[30]) begin
// if the overflow lookahead did not borrow, one second has elapsed
next_edge_s_next = next_rise_s_reg + width_s_reg + 1;
next_edge_ns_next = ts_tod_ns_ovf_reg[29:0];
next_edge_fns_next = ts_tod_fns_ovf_reg;
end else begin
// no increment seconds field
next_edge_s_next = next_rise_s_reg + width_s_reg;
next_edge_ns_next = ts_tod_ns_inc_reg;
next_edge_fns_next = ts_tod_fns_inc_reg;
end
state_next = STATE_IDLE;
end
default: begin
state_next = STATE_IDLE;
end
endcase
if (restart_reg || input_ts_tod_step) begin
// set next rise and next edge to start time
next_rise_s_next = start_s_reg;
next_rise_ns_next = start_ns_reg;
if (FNS_EN) begin
next_rise_fns_next = start_fns_reg;
end
next_edge_s_next = start_s_reg;
next_edge_ns_next = start_ns_reg;
if (FNS_EN) begin
next_edge_fns_next = start_fns_reg;
end
locked_next = 1'b0;
ffwd_next = 1'b1;
output_next = 1'b0;
level_next = 1'b0;
error_next = input_ts_tod_step;
state_next = STATE_IDLE;
end
end
always_ff @(posedge clk) begin
state_reg <= state_next;
restart_reg <= 1'b0;
time_s_reg <= input_ts_tod[95:48];
time_ns_reg <= input_ts_tod[45:16];
if (FNS_EN) begin
time_fns_reg <= input_ts_tod[15:0];
end
if (input_start_valid) begin
start_s_reg <= input_start[95:48];
start_ns_reg <= input_start[45:16];
if (FNS_EN) begin
start_fns_reg <= input_start[15:0];
end
restart_reg <= 1'b1;
end
if (input_period_valid) begin
period_s_reg <= input_period[95:48];
period_ns_reg <= input_period[45:16];
if (FNS_EN) begin
period_fns_reg <= input_period[15:0];
end
restart_reg <= 1'b1;
end
if (input_width_valid) begin
width_s_reg <= input_width[95:48];
width_ns_reg <= input_width[45:16];
if (FNS_EN) begin
width_fns_reg <= input_width[15:0];
end
end
next_rise_s_reg <= next_rise_s_next;
next_rise_ns_reg <= next_rise_ns_next;
if (FNS_EN) begin
next_rise_fns_reg <= next_rise_fns_next;
end
next_edge_s_reg <= next_edge_s_next;
next_edge_ns_reg <= next_edge_ns_next;
if (FNS_EN) begin
next_edge_fns_reg <= next_edge_fns_next;
end
ts_tod_ns_inc_reg <= ts_tod_ns_inc_next;
if (FNS_EN) begin
ts_tod_fns_inc_reg <= ts_tod_fns_inc_next;
end
ts_tod_ns_ovf_reg <= ts_tod_ns_ovf_next;
if (FNS_EN) begin
ts_tod_fns_ovf_reg <= ts_tod_fns_ovf_next;
end
locked_reg <= locked_next;
error_reg <= error_next;
ffwd_reg <= ffwd_next;
level_reg <= level_next;
output_reg <= output_next;
if (rst) begin
state_reg <= STATE_IDLE;
start_s_reg <= 48'(OUT_START_S);
start_ns_reg <= 30'(OUT_START_NS);
start_fns_reg <= 16'(OUT_START_FNS);
period_s_reg <= 48'(OUT_PERIOD_S);
period_ns_reg <= 30'(OUT_PERIOD_NS);
period_fns_reg <= 16'(OUT_PERIOD_FNS);
width_s_reg <= 48'(OUT_WIDTH_S);
width_ns_reg <= 30'(OUT_WIDTH_NS);
width_fns_reg <= 16'(OUT_WIDTH_FNS);
restart_reg <= 1'b1;
locked_reg <= 1'b0;
error_reg <= 1'b0;
output_reg <= 1'b0;
end
end
endmodule
`resetall

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,645 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2023-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* PTP time distribution PHC
*/
module taxi_ptp_td_phc #
(
parameter PERIOD_NS_NUM = 32,
parameter PERIOD_NS_DENOM = 5
)
(
input wire logic clk,
input wire logic rst,
/*
* ToD timestamp control
*/
input wire logic [47:0] input_ts_tod_s,
input wire logic [29:0] input_ts_tod_ns,
input wire logic input_ts_tod_valid,
output wire logic input_ts_tod_ready,
input wire logic [29:0] input_ts_tod_offset_ns,
input wire logic input_ts_tod_offset_valid,
output wire logic input_ts_tod_offset_ready,
/*
* Relative timestamp control
*/
input wire logic [47:0] input_ts_rel_ns,
input wire logic input_ts_rel_valid,
output wire logic input_ts_rel_ready,
input wire logic [31:0] input_ts_rel_offset_ns,
input wire logic input_ts_rel_offset_valid,
output wire logic input_ts_rel_offset_ready,
/*
* Fractional ns control
*/
input wire logic [31:0] input_ts_offset_fns,
input wire logic input_ts_offset_valid,
output wire logic input_ts_offset_ready,
/*
* Period control
*/
input wire logic [7:0] input_period_ns,
input wire logic [31:0] input_period_fns,
input wire logic input_period_valid,
output wire logic input_period_ready,
input wire logic [15:0] input_drift_num,
input wire logic [15:0] input_drift_denom,
input wire logic input_drift_valid,
output wire logic input_drift_ready,
/*
* Time distribution serial data output
*/
output wire logic ptp_td_sdo,
/*
* PPS output
*/
output wire logic output_pps,
output wire logic output_pps_str
);
localparam INC_NS_W = 9+8;
localparam PERIOD_NS_W = 8;
localparam TS_REL_NS_W = 48;
localparam TS_TOD_S_W = 48;
localparam TS_TOD_NS_W = 30;
localparam FNS_W = 32;
localparam PERIOD_NS = PERIOD_NS_NUM / PERIOD_NS_DENOM;
localparam PERIOD_NS_REM = PERIOD_NS_NUM - PERIOD_NS*PERIOD_NS_DENOM;
localparam PERIOD_FNS = (PERIOD_NS_REM * {32'd1, {FNS_W{1'b0}}}) / (32+FNS_W)'(PERIOD_NS_DENOM);
localparam PERIOD_FNS_REM = (PERIOD_NS_REM * {32'd1, {FNS_W{1'b0}}}) - PERIOD_FNS*PERIOD_NS_DENOM;
localparam [30:0] NS_PER_S = 31'd1_000_000_000;
logic [PERIOD_NS_W-1:0] period_ns_reg = PERIOD_NS_W'(PERIOD_NS);
logic [FNS_W-1:0] period_fns_reg = FNS_W'(PERIOD_FNS);
logic [15:0] drift_num_reg = 16'(PERIOD_FNS_REM);
logic [15:0] drift_denom_reg = 16'(PERIOD_NS_DENOM);
logic [15:0] drift_cnt_reg = '0;
logic [15:0] drift_cnt_d1_reg = '0;
logic drift_apply_reg = 1'b0;
logic [23:0] drift_acc_reg = '0;
logic [INC_NS_W-1:0] ts_inc_ns_reg = '0;
logic [FNS_W-1:0] ts_fns_reg = '0;
logic [32:0] ts_rel_ns_inc_reg = '0;
logic [TS_REL_NS_W-1:0] ts_rel_ns_reg = '0;
logic ts_rel_updated_reg = 1'b0;
logic [TS_TOD_S_W-1:0] ts_tod_s_reg = '0;
logic [TS_TOD_NS_W-1:0] ts_tod_ns_reg = '0;
logic ts_tod_updated_reg = 1'b0;
logic [31:0] ts_tod_offset_ns_reg = '0;
logic [TS_TOD_S_W-1:0] ts_tod_alt_s_reg = '0;
logic [31:0] ts_tod_alt_offset_ns_reg = '0;
logic [7:0] td_update_cnt_reg = '0;
logic td_update_reg = 1'b0;
logic [1:0] td_msg_i_reg = '0;
logic input_ts_tod_ready_reg = 1'b0;
logic input_ts_tod_offset_ready_reg = 1'b0;
logic input_ts_rel_ready_reg = 1'b0;
logic input_ts_rel_offset_ready_reg = 1'b0;
logic input_ts_offset_ready_reg = 1'b0;
logic [17*14-1:0] td_shift_reg = '1;
logic [15:0] pps_gen_fns_reg = '0;
logic [8:0] pps_gen_ns_inc_reg = '0;
logic [30:0] pps_gen_ns_reg = 31'h40000000;
logic [9:0] pps_delay_reg = '0;
logic pps_reg = '0;
logic pps_str_reg = '0;
logic [3:0] update_state_reg = '0;
logic [47:0] adder_a_reg = '0;
logic [47:0] adder_b_reg = '0;
logic adder_cin_reg = '0;
logic [47:0] adder_sum_reg = '0;
logic adder_cout_reg = '0;
logic adder_busy_reg = '0;
assign input_ts_tod_ready = input_ts_tod_ready_reg;
assign input_ts_tod_offset_ready = input_ts_tod_offset_ready_reg;
assign input_ts_rel_ready = input_ts_rel_ready_reg;
assign input_ts_rel_offset_ready = input_ts_rel_offset_ready_reg;
assign input_ts_offset_ready = input_ts_offset_ready_reg;
assign input_period_ready = 1'b1;
assign input_drift_ready = 1'b1;
assign output_pps = pps_reg;
assign output_pps_str = pps_str_reg;
assign ptp_td_sdo = td_shift_reg[0];
always_ff @(posedge clk) begin
drift_apply_reg <= 1'b0;
input_ts_tod_ready_reg <= 1'b0;
input_ts_tod_offset_ready_reg <= 1'b0;
input_ts_rel_ready_reg <= 1'b0;
input_ts_rel_offset_ready_reg <= 1'b0;
input_ts_offset_ready_reg <= 1'b0;
// update and message generation cadence
{td_update_reg, td_update_cnt_reg} <= td_update_cnt_reg + 1;
// latch drift setting
if (input_drift_valid) begin
drift_num_reg <= input_drift_num;
drift_denom_reg <= input_drift_denom;
end
// drift
if (drift_denom_reg != 0) begin
if (drift_cnt_reg != 0) begin
drift_cnt_reg <= drift_cnt_reg - 1;
end else begin
drift_cnt_reg <= drift_denom_reg - 1;
drift_apply_reg <= 1'b1;
end
end else begin
drift_cnt_reg <= 0;
end
drift_cnt_d1_reg <= drift_cnt_reg;
// drift accumulation
if (drift_apply_reg) begin
drift_acc_reg <= drift_acc_reg + 24'(drift_num_reg);
end
// latch period setting
if (input_period_valid) begin
period_ns_reg <= input_period_ns;
period_fns_reg <= input_period_fns;
end
// PPS generation
if (td_update_reg) begin
{pps_gen_ns_inc_reg, pps_gen_fns_reg} <= {period_ns_reg, period_fns_reg[31:16]} + 24'(ts_fns_reg[31:16]);
end else begin
{pps_gen_ns_inc_reg, pps_gen_fns_reg} <= {period_ns_reg, period_fns_reg[31:16]} + 24'(pps_gen_fns_reg);
end
pps_gen_ns_reg <= pps_gen_ns_reg + 31'(pps_gen_ns_inc_reg);
if (!pps_gen_ns_reg[30]) begin
pps_delay_reg <= 14*17 + 32 + 240;
pps_gen_ns_reg[30] <= 1'b1;
end
pps_reg <= 1'b0;
if (ts_tod_ns_reg[29]) begin
pps_str_reg <= 1'b0;
end
if (pps_delay_reg != 0) begin
pps_delay_reg <= pps_delay_reg - 1;
if (pps_delay_reg == 1) begin
pps_reg <= 1'b1;
pps_str_reg <= 1'b1;
end
end
// update state machine
{adder_cout_reg, adder_sum_reg} <= adder_a_reg + adder_b_reg + 48'(adder_cin_reg);
adder_busy_reg <= 1'b0;
// computes the following:
// {ts_inc_ns_reg, ts_fns_reg} = drift_acc_reg + $signed(input_ts_offset_fns) + {period_ns_reg, period_fns_reg} * 256 + ts_fns_reg
// ts_rel_ns_reg = ts_rel_ns_reg + ts_inc_ns_reg + $signed(input_ts_rel_offset_ns);
// ts_tod_ns_reg = ts_tod_ns_reg + ts_inc_ns_reg + $signed(input_ts_tod_offset_ns);
// if that borrowed,
// ts_tod_ns_reg = ts_tod_ns_reg + NS_PER_S
// ts_tod_s_reg = ts_tod_s_reg - 1
// else
// pps_gen_ns_reg = ts_tod_ns_reg - NS_PER_S
// if that did not borrow,
// ts_tod_ns_reg = ts_tod_ns_reg - NS_PER_S
// ts_tod_s_reg = ts_tod_s_reg + 1
// ts_tod_offset_ns_reg = ts_tod_ns_reg - ts_rel_ns_reg
// if ts_tod_ns_reg[29]
// ts_tod_alt_offset_ns_reg = ts_tod_offset_ns_reg - NS_PER_S
// ts_tod_alt_s_reg = ts_tod_s_reg + 1
// else
// ts_tod_alt_offset_ns_reg = ts_tod_offset_ns_reg + NS_PER_S
// ts_tod_alt_s_reg = ts_tod_s_reg - 1
if (!adder_busy_reg) begin
case (update_state_reg)
0: begin
// idle
// set relative timestamp
if (input_ts_rel_valid) begin
ts_rel_ns_reg <= input_ts_rel_ns;
input_ts_rel_ready_reg <= 1'b1;
ts_rel_updated_reg <= 1'b1;
end
// set ToD timestamp
if (input_ts_tod_valid) begin
ts_tod_s_reg <= input_ts_tod_s;
ts_tod_ns_reg <= input_ts_tod_ns;
input_ts_tod_ready_reg <= 1'b1;
ts_tod_updated_reg <= 1'b1;
end
// compute period 1 - add drift and requested offset
if (drift_apply_reg) begin
adder_a_reg <= 48'(drift_acc_reg + drift_num_reg);
end else begin
adder_a_reg <= 48'(drift_acc_reg);
end
adder_b_reg <= '0;
if (input_ts_offset_valid) begin
adder_b_reg <= 48'($signed(input_ts_offset_fns));
end
adder_cin_reg <= 0;
if (td_update_reg) begin
drift_acc_reg <= 0;
input_ts_offset_ready_reg <= input_ts_offset_valid;
update_state_reg <= 1;
adder_busy_reg <= 1'b1;
end else begin
update_state_reg <= 0;
end
end
1: begin
// compute period 2 - add drift and offset to period
adder_a_reg <= adder_sum_reg;
adder_b_reg <= 48'({period_ns_reg, period_fns_reg, 8'd0});
adder_cin_reg <= 0;
update_state_reg <= 2;
adder_busy_reg <= 1'b1;
end
2: begin
// compute next fns
adder_a_reg <= adder_sum_reg;
adder_b_reg <= 48'(ts_fns_reg);
adder_cin_reg <= 0;
update_state_reg <= 3;
adder_busy_reg <= 1'b1;
end
3: begin
// store fns
{ts_inc_ns_reg, ts_fns_reg} <= {adder_cout_reg, adder_sum_reg};
// compute relative timestamp 1 - add previous value and increment
adder_a_reg <= 48'(ts_rel_ns_reg);
adder_b_reg <= 48'({adder_cout_reg, adder_sum_reg} >> FNS_W); // ts_inc_ns_reg
adder_cin_reg <= 0;
update_state_reg <= 4;
adder_busy_reg <= 1'b1;
end
4: begin
// compute relative timestamp 2 - add offset
adder_a_reg <= adder_sum_reg;
adder_b_reg <= '0;
adder_cin_reg <= 0;
// offset relative timestamp if requested
if (input_ts_rel_offset_valid) begin
adder_b_reg <= 48'($signed(input_ts_rel_offset_ns));
input_ts_rel_offset_ready_reg <= 1'b1;
ts_rel_updated_reg <= 1'b1;
end
update_state_reg <= 5;
adder_busy_reg <= 1'b1;
end
5: begin
// store relative timestamp
ts_rel_ns_reg <= adder_sum_reg;
// compute ToD timestamp 1 - add previous value and increment
adder_a_reg <= 48'(ts_tod_ns_reg);
adder_b_reg <= 48'(ts_inc_ns_reg);
adder_cin_reg <= 0;
update_state_reg <= 6;
adder_busy_reg <= 1'b1;
end
6: begin
// compute ToD timestamp 2 - add offset
adder_a_reg <= adder_sum_reg;
adder_b_reg <= '0;
adder_cin_reg <= 0;
// offset ToD timestamp if requested
if (input_ts_tod_offset_valid) begin
adder_b_reg <= 48'($signed(input_ts_tod_offset_ns));
input_ts_tod_offset_ready_reg <= 1'b1;
ts_tod_updated_reg <= 1'b1;
end
update_state_reg <= 7;
adder_busy_reg <= 1'b1;
end
7: begin
// compute ToD timestamp 3 - check for underflow/overflow
ts_tod_ns_reg <= TS_TOD_NS_W'(adder_sum_reg);
if (adder_b_reg[47] && !adder_cout_reg) begin
// borrowed; add 1 billion
adder_a_reg <= adder_sum_reg;
adder_b_reg <= 48'(NS_PER_S);
adder_cin_reg <= 0;
update_state_reg <= 8;
adder_busy_reg <= 1'b1;
end else begin
// did not borrow; subtract 1 billion to check for overflow
adder_a_reg <= adder_sum_reg;
adder_b_reg <= 48'(-NS_PER_S);
adder_cin_reg <= 0;
update_state_reg <= 9;
adder_busy_reg <= 1'b1;
end
end
8: begin
// seconds decrement
ts_tod_ns_reg <= TS_TOD_NS_W'(adder_sum_reg);
pps_gen_ns_reg[30] <= 1'b1;
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 48'(-1);
adder_cin_reg <= 0;
update_state_reg <= 10;
adder_busy_reg <= 1'b1;
end
9: begin
// seconds increment
pps_gen_ns_reg <= 31'(adder_sum_reg);
if (!adder_cout_reg) begin
// borrowed; leave seconds alone
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= '0;
adder_cin_reg <= 0;
end else begin
// did not borrow; increment seconds
ts_tod_ns_reg <= TS_TOD_NS_W'(adder_sum_reg);
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 48'(1);
adder_cin_reg <= 0;
end
update_state_reg <= 10;
adder_busy_reg <= 1'b1;
end
10: begin
// store seconds
ts_tod_s_reg <= adder_sum_reg;
// compute offset
adder_a_reg <= 48'(ts_tod_ns_reg);
adder_b_reg <= 48'(~ts_rel_ns_reg);
adder_cin_reg <= 1;
update_state_reg <= 11;
adder_busy_reg <= 1'b1;
end
11: begin
// store offset
ts_tod_offset_ns_reg <= 32'(adder_sum_reg);
adder_a_reg <= adder_sum_reg;
adder_b_reg <= 48'(-NS_PER_S);
adder_cin_reg <= 0;
if (ts_tod_ns_reg[29:27] == 3'b111) begin
// latter portion of second; compute offset for next second
adder_b_reg <= 48'(-NS_PER_S);
update_state_reg <= 12;
adder_busy_reg <= 1'b1;
end else begin
// former portion of second; compute offset for previous second
adder_b_reg <= 48'(NS_PER_S);
update_state_reg <= 14;
adder_busy_reg <= 1'b1;
end
end
12: begin
// store alternate offset for next second
ts_tod_alt_offset_ns_reg <= 32'(adder_sum_reg);
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 48'(1);
adder_cin_reg <= 0;
update_state_reg <= 13;
adder_busy_reg <= 1'b1;
end
13: begin
// store alternate second for next second
ts_tod_alt_s_reg <= adder_sum_reg;
update_state_reg <= 0;
end
14: begin
// store alternate offset for previous second
ts_tod_alt_offset_ns_reg <= 32'(adder_sum_reg);
adder_a_reg <= ts_tod_s_reg;
adder_b_reg <= 48'(-1);
adder_cin_reg <= 0;
update_state_reg <= 15;
adder_busy_reg <= 1'b1;
end
15: begin
// store alternate second for previous second
ts_tod_alt_s_reg <= adder_sum_reg;
update_state_reg <= 0;
end
default: begin
// invalid state; return to idle
update_state_reg <= 0;
end
endcase
end
// time distribution message generation
td_shift_reg <= {1'b1, td_shift_reg[17*14-1:1]};
if (td_update_reg) begin
// word 0: control
td_shift_reg[17*0+0 +: 1] <= 1'b0;
td_shift_reg[17*0+1 +: 16] <= 0;
td_shift_reg[17*0+1+0 +: 4] <= 4'(td_msg_i_reg);
td_shift_reg[17*0+1+8 +: 1] <= ts_rel_updated_reg;
td_shift_reg[17*0+1+9 +: 1] <= ts_tod_s_reg[0];
ts_rel_updated_reg <= 1'b0;
case (td_msg_i_reg)
2'd0: begin
// msg 0 word 1: current ToD ns 15:0
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= ts_tod_ns_reg[15:0];
// msg 0 word 2: current ToD ns 29:16
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1+0 +: 15] <= 15'(ts_tod_ns_reg[29:16]);
td_shift_reg[17*2+1+15 +: 1] <= ts_tod_updated_reg;
ts_tod_updated_reg <= 1'b0;
// msg 0 word 3: current ToD seconds 15:0
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= ts_tod_s_reg[15:0];
// msg 0 word 4: current ToD seconds 31:16
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= ts_tod_s_reg[31:16];
// msg 0 word 5: current ToD seconds 47:32
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= ts_tod_s_reg[47:32];
td_msg_i_reg <= 2'd1;
end
2'd1: begin
// msg 1 word 1: current ToD ns offset 15:0
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= ts_tod_offset_ns_reg[15:0];
// msg 1 word 2: current ToD ns offset 31:16
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1 +: 16] <= ts_tod_offset_ns_reg[31:16];
// msg 1 word 3: drift num
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= drift_num_reg;
// msg 1 word 4: drift denom
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= drift_denom_reg;
// msg 1 word 5: drift state
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= drift_cnt_d1_reg;
td_msg_i_reg <= 2'd2;
end
2'd2: begin
// msg 2 word 1: alternate ToD ns offset 15:0
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= ts_tod_alt_offset_ns_reg[15:0];
// msg 2 word 2: alternate ToD ns offset 31:16
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1 +: 16] <= ts_tod_alt_offset_ns_reg[31:16];
// msg 2 word 3: alternate ToD seconds 15:0
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= ts_tod_alt_s_reg[15:0];
// msg 2 word 4: alternate ToD seconds 31:16
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= ts_tod_alt_s_reg[31:16];
// msg 2 word 5: alternate ToD seconds 47:32
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= ts_tod_alt_s_reg[47:32];
td_msg_i_reg <= 2'd0;
end
default: begin
td_shift_reg[17*1+0 +: 1] <= 1'b0;
td_shift_reg[17*1+1 +: 16] <= '0;
td_shift_reg[17*2+0 +: 1] <= 1'b0;
td_shift_reg[17*2+1 +: 16] <= '0;
td_shift_reg[17*3+0 +: 1] <= 1'b0;
td_shift_reg[17*3+1 +: 16] <= '0;
td_shift_reg[17*4+0 +: 1] <= 1'b0;
td_shift_reg[17*4+1 +: 16] <= '0;
td_shift_reg[17*5+0 +: 1] <= 1'b0;
td_shift_reg[17*5+1 +: 16] <= '0;
td_msg_i_reg <= 2'd0;
end
endcase
// word 6: current fns 15:0
td_shift_reg[17*6+0 +: 1] <= 1'b0;
td_shift_reg[17*6+1 +: 16] <= ts_fns_reg[15:0];
// word 7: current fns 31:16
td_shift_reg[17*7+0 +: 1] <= 1'b0;
td_shift_reg[17*7+1 +: 16] <= ts_fns_reg[31:16];
// word 8: current ns 15:0
td_shift_reg[17*8+0 +: 1] <= 1'b0;
td_shift_reg[17*8+1 +: 16] <= ts_rel_ns_reg[15:0];
// word 9: current ns 31:16
td_shift_reg[17*9+0 +: 1] <= 1'b0;
td_shift_reg[17*9+1 +: 16] <= ts_rel_ns_reg[31:16];
// word 10: current ns 47:32
td_shift_reg[17*10+0 +: 1] <= 1'b0;
td_shift_reg[17*10+1 +: 16] <= ts_rel_ns_reg[47:32];
// word 11: current phase increment fns 15:0
td_shift_reg[17*11+0 +: 1] <= 1'b0;
td_shift_reg[17*11+1 +: 16] <= period_fns_reg[15:0];
// word 12: current phase increment fns 31:16
td_shift_reg[17*12+0 +: 1] <= 1'b0;
td_shift_reg[17*12+1 +: 16] <= period_fns_reg[31:16];
// word 13: current phase increment ns 7:0 + crc
td_shift_reg[17*13+0 +: 1] <= 1'b0;
td_shift_reg[17*13+1+0 +: 8] <= period_ns_reg[7:0];
td_shift_reg[17*13+1+8 +: 8] <= '0;
end
if (rst) begin
period_ns_reg <= PERIOD_NS_W'(PERIOD_NS);
period_fns_reg <= FNS_W'(PERIOD_FNS);
drift_num_reg <= 16'(PERIOD_FNS_REM);
drift_denom_reg <= 16'(PERIOD_NS_DENOM);
drift_cnt_reg <= '0;
drift_acc_reg <= '0;
ts_fns_reg <= '0;
ts_rel_ns_reg <= '0;
ts_rel_updated_reg <= '0;
ts_tod_s_reg <= '0;
ts_tod_ns_reg <= '0;
ts_tod_updated_reg <= '0;
pps_gen_ns_reg[30] <= 1'b1;
pps_delay_reg <= '0;
pps_reg <= '0;
pps_str_reg <= '0;
td_update_cnt_reg <= '0;
td_update_reg <= 1'b0;
td_msg_i_reg <= '0;
td_shift_reg <= '1;
end
end
endmodule
`resetall

View File

@@ -0,0 +1,303 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2024-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1fs
`default_nettype none
/*
* PTP time distribution ToD timestamp reconstruction module
*/
module taxi_ptp_td_rel2tod #
(
parameter TS_FNS_W = 16,
parameter TS_REL_NS_W = 32,
parameter TS_TOD_S_W = 48,
parameter TS_REL_W = TS_REL_NS_W + TS_FNS_W,
parameter TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W,
parameter TD_SDI_PIPELINE = 2
)
(
input wire logic clk,
input wire logic rst,
/*
* PTP clock interface
*/
input wire logic ptp_clk,
input wire logic ptp_rst,
input wire logic ptp_td_sdi,
/*
* Timestamp conversion
*/
taxi_axis_if.snk s_axis_ts_rel,
taxi_axis_if.src m_axis_ts_tod
);
localparam TS_ID_W = s_axis_ts_rel.ID_W;
localparam TS_DEST_W = s_axis_ts_rel.DEST_W;
localparam TS_USER_W = s_axis_ts_rel.USER_W;
localparam TS_TOD_NS_W = 30;
localparam TS_NS_W = TS_TOD_NS_W+1;
localparam [30:0] NS_PER_S = 31'd1_000_000_000;
// pipeline to facilitate long input path
wire ptp_td_sdi_pipe[0:TD_SDI_PIPELINE];
assign ptp_td_sdi_pipe[0] = ptp_td_sdi;
for (genvar n = 0; n < TD_SDI_PIPELINE; n = n + 1) begin : pipe_stage
(* shreg_extract = "no" *)
reg ptp_td_sdi_reg = 0;
assign ptp_td_sdi_pipe[n+1] = ptp_td_sdi_reg;
always_ff @(posedge ptp_clk) begin
ptp_td_sdi_reg <= ptp_td_sdi_pipe[n];
end
end
// deserialize data
logic [15:0] td_shift_reg = '0;
logic [4:0] bit_cnt_reg = '0;
logic td_valid_reg = 1'b0;
logic [3:0] td_index_reg = '0;
logic [3:0] td_msg_reg = '0;
logic [15:0] td_tdata_reg = '0;
logic td_tvalid_reg = 1'b0;
logic td_tlast_reg = 1'b0;
logic [7:0] td_tid_reg = '0;
logic td_sync_reg = 1'b0;
always_ff @(posedge ptp_clk) begin
td_shift_reg <= {ptp_td_sdi_pipe[TD_SDI_PIPELINE], td_shift_reg[15:1]};
td_tvalid_reg <= 1'b0;
if (bit_cnt_reg != 0) begin
bit_cnt_reg <= bit_cnt_reg - 1;
end else begin
td_valid_reg <= 1'b0;
if (td_valid_reg) begin
td_tdata_reg <= td_shift_reg;
td_tvalid_reg <= 1'b1;
td_tlast_reg <= ptp_td_sdi_pipe[TD_SDI_PIPELINE];
td_tid_reg <= {td_msg_reg, td_index_reg};
if (td_index_reg == 0) begin
td_msg_reg <= td_shift_reg[3:0];
td_tid_reg[7:4] <= td_shift_reg[3:0];
end
td_index_reg <= td_index_reg + 1;
td_sync_reg = !td_sync_reg;
end
if (ptp_td_sdi_pipe[TD_SDI_PIPELINE] == 0) begin
bit_cnt_reg <= 16;
td_valid_reg <= 1'b1;
end else begin
td_index_reg <= 0;
end
end
if (ptp_rst) begin
bit_cnt_reg <= 0;
td_valid_reg <= 1'b0;
td_tvalid_reg <= 1'b0;
end
end
// sync TD data
logic [15:0] dst_td_tdata_reg = '0;
logic dst_td_tvalid_reg = 1'b0;
logic [7:0] dst_td_tid_reg = '0;
(* shreg_extract = "no" *)
logic td_sync_sync1_reg = 1'b0;
(* shreg_extract = "no" *)
logic td_sync_sync2_reg = 1'b0;
(* shreg_extract = "no" *)
logic td_sync_sync3_reg = 1'b0;
always_ff @(posedge clk) begin
td_sync_sync1_reg <= td_sync_reg;
td_sync_sync2_reg <= td_sync_sync1_reg;
td_sync_sync3_reg <= td_sync_sync2_reg;
end
always_ff @(posedge clk) begin
dst_td_tvalid_reg <= 1'b0;
if (td_sync_sync3_reg ^ td_sync_sync2_reg) begin
dst_td_tdata_reg <= td_tdata_reg;
dst_td_tvalid_reg <= 1'b1;
dst_td_tid_reg <= td_tid_reg;
end
if (rst) begin
dst_td_tvalid_reg <= 1'b0;
end
end
logic ts_sel_reg = 1'b0;
logic [47:0] ts_tod_s_0_reg = '0;
logic [31:0] ts_tod_offset_ns_0_reg = '0;
logic [47:0] ts_tod_s_1_reg = '0;
logic [31:0] ts_tod_offset_ns_1_reg = '0;
logic [TS_TOD_S_W-1:0] output_ts_tod_s_reg = '0, output_ts_tod_s_next;
logic [TS_TOD_NS_W-1:0] output_ts_tod_ns_reg = '0, output_ts_tod_ns_next;
logic [TS_FNS_W-1:0] output_ts_fns_reg = '0, output_ts_fns_next;
logic [TS_ID_W-1:0] output_ts_id_reg = '0, output_ts_id_next;
logic [TS_DEST_W-1:0] output_ts_dest_reg = '0, output_ts_dest_next;
logic [TS_USER_W-1:0] output_ts_user_reg = '0, output_ts_user_next;
logic output_ts_valid_reg = 1'b0, output_ts_valid_next;
logic [TS_NS_W-1:0] ts_tod_ns_0;
logic [TS_NS_W-1:0] ts_tod_ns_1;
assign s_axis_ts_rel.tready = 1'b1;
assign m_axis_ts_tod.tdata = {output_ts_tod_s_reg, 2'b00, output_ts_tod_ns_reg, output_ts_fns_reg};
assign m_axis_ts_tod.tkeep = '1;
assign m_axis_ts_tod.tstrb = m_axis_ts_tod.tkeep;
assign m_axis_ts_tod.tlast = 1'b1;
assign m_axis_ts_tod.tid = output_ts_id_reg;
assign m_axis_ts_tod.tdest = output_ts_dest_reg;
assign m_axis_ts_tod.tuser = output_ts_user_reg;
assign m_axis_ts_tod.tvalid = output_ts_valid_reg;
always_comb begin
// reconstruct timestamp
// apply both offsets
ts_tod_ns_0 = TS_NS_W'(s_axis_ts_rel.tdata[TS_FNS_W +: TS_REL_NS_W] + ts_tod_offset_ns_0_reg);
ts_tod_ns_1 = TS_NS_W'(s_axis_ts_rel.tdata[TS_FNS_W +: TS_REL_NS_W] + ts_tod_offset_ns_1_reg);
// pick the correct result
// 2 MSB clear = lower half of range (0-536,870,911)
// 1 MSB clear = upper half of range, but could also be over 1 billion (536,870,912-1,073,741,823)
// 1 MSB set = overflow or underflow
// prefer 2 MSB clear over 1 MSB clear if neither result was overflow or underflow
if (ts_tod_ns_0[30:29] == 0 || (ts_tod_ns_0[30] == 0 && ts_tod_ns_1[30:29] != 0)) begin
output_ts_tod_s_next = ts_tod_s_0_reg;
output_ts_tod_ns_next = ts_tod_ns_0[TS_TOD_NS_W-1:0];
end else begin
output_ts_tod_s_next = ts_tod_s_1_reg;
output_ts_tod_ns_next = ts_tod_ns_1[TS_TOD_NS_W-1:0];
end
output_ts_fns_next = s_axis_ts_rel.tdata[TS_FNS_W-1:0];
output_ts_id_next = s_axis_ts_rel.tid;
output_ts_dest_next = s_axis_ts_rel.tdest;
output_ts_user_next = s_axis_ts_rel.tuser;
output_ts_valid_next = s_axis_ts_rel.tvalid;
end
always_ff @(posedge clk) begin
// extract data
if (dst_td_tvalid_reg) begin
if (dst_td_tid_reg[3:0] == 4'd0) begin
ts_sel_reg <= dst_td_tdata_reg[9];
end
// current
if (dst_td_tid_reg == {4'd1, 4'd1}) begin
if (ts_sel_reg) begin
ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg;
end else begin
ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd1, 4'd2}) begin
if (ts_sel_reg) begin
ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg;
end else begin
ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd0, 4'd3}) begin
if (ts_sel_reg) begin
ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg;
end else begin
ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd0, 4'd4}) begin
if (ts_sel_reg) begin
ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg;
end else begin
ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd0, 4'd5}) begin
if (ts_sel_reg) begin
ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg;
end else begin
ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg;
end
end
// alternate
if (dst_td_tid_reg == {4'd2, 4'd1}) begin
if (ts_sel_reg) begin
ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg;
end else begin
ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd2, 4'd2}) begin
if (ts_sel_reg) begin
ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg;
end else begin
ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd2, 4'd3}) begin
if (ts_sel_reg) begin
ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg;
end else begin
ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd2, 4'd4}) begin
if (ts_sel_reg) begin
ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg;
end else begin
ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg;
end
end
if (dst_td_tid_reg == {4'd2, 4'd5}) begin
if (ts_sel_reg) begin
ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg;
end else begin
ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg;
end
end
end
output_ts_tod_s_reg <= output_ts_tod_s_next;
output_ts_tod_ns_reg <= output_ts_tod_ns_next;
output_ts_fns_reg <= output_ts_fns_next;
output_ts_id_reg <= output_ts_id_next;
output_ts_dest_reg <= output_ts_dest_next;
output_ts_user_reg <= output_ts_user_next;
output_ts_valid_reg <= output_ts_valid_next;
if (rst) begin
output_ts_valid_reg <= 1'b0;
end
end
endmodule
`resetall