Reorganize repository
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
1
src/ptp/lib/taxi
Symbolic link
1
src/ptp/lib/taxi
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../
|
||||
343
src/ptp/rtl/taxi_ptp_clock.sv
Normal file
343
src/ptp/rtl/taxi_ptp_clock.sv
Normal 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
|
||||
813
src/ptp/rtl/taxi_ptp_clock_cdc.sv
Normal file
813
src/ptp/rtl/taxi_ptp_clock_cdc.sv
Normal 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
|
||||
313
src/ptp/rtl/taxi_ptp_perout.sv
Normal file
313
src/ptp/rtl/taxi_ptp_perout.sv
Normal 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
|
||||
1011
src/ptp/rtl/taxi_ptp_td_leaf.sv
Normal file
1011
src/ptp/rtl/taxi_ptp_td_leaf.sv
Normal file
File diff suppressed because it is too large
Load Diff
645
src/ptp/rtl/taxi_ptp_td_phc.sv
Normal file
645
src/ptp/rtl/taxi_ptp_td_phc.sv
Normal 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
|
||||
303
src/ptp/rtl/taxi_ptp_td_rel2tod.sv
Normal file
303
src/ptp/rtl/taxi_ptp_td_rel2tod.sv
Normal 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
|
||||
96
src/ptp/syn/vivado/taxi_ptp_clock_cdc.tcl
Normal file
96
src/ptp/syn/vivado/taxi_ptp_clock_cdc.tcl
Normal file
@@ -0,0 +1,96 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2019-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
#
|
||||
|
||||
# PTP timestamp capture module
|
||||
|
||||
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "taxi_ptp_clock_cdc(__\w+__\d+)?" ||
|
||||
REF_NAME =~ "taxi_ptp_clock_cdc(__\w+__\d+)?")}] {
|
||||
puts "Inserting timing constraints for taxi_ptp_clock_cdc instance $inst"
|
||||
|
||||
# get clock periods
|
||||
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 [if {[llength $input_clk]} {get_property -min PERIOD $input_clk} {expr 1.0}]
|
||||
set output_clk_period [if {[llength $output_clk]} {get_property -min PERIOD $output_clk} {expr 1.0}]
|
||||
|
||||
# timestamp synchronization
|
||||
set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/src_ts_(s|ns|step)_sync_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"]
|
||||
|
||||
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/src_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/src_ts_s_sync_reg_reg[*]"] $input_clk_period
|
||||
}
|
||||
|
||||
set_max_delay -from [get_cells "$inst/src_ts_ns_capt_reg_reg[*]"] -to [get_cells "$inst/src_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/src_ts_ns_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/src_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/src_ts_step_sync_reg_reg"] $input_clk_period
|
||||
}
|
||||
|
||||
# sample clock
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/src_sync_sample_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/src_sync_reg_reg"] -to [get_cells "$inst/src_sync_sample_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/dest_sync_sample_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/dest_sync_reg_reg"] -to [get_cells "$inst/dest_sync_sample_sync1_reg_reg"] -datapath_only $output_clk_period
|
||||
}
|
||||
|
||||
# sample update sync
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/sample_update_sync\[123\]_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/sample_update_reg_reg/C"]]
|
||||
|
||||
set src_clk_period [if {[llength $src_clk]} {get_property -min PERIOD $src_clk} {expr 1.0}]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/sample_update_reg_reg"] -to [get_cells "$inst/sample_update_sync1_reg_reg"] -datapath_only $src_clk_period
|
||||
|
||||
set_max_delay -from [get_cells "$inst/sample_acc_out_reg_reg[*]"] -to [get_cells $inst/sample_acc_sync_reg_reg[*]] -datapath_only $src_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/sample_acc_out_reg_reg[*]"] -to [get_cells $inst/sample_acc_sync_reg_reg[*]] $output_clk_period
|
||||
}
|
||||
|
||||
# timestamp transfer sync
|
||||
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_max_delay -from [get_cells "$inst/src_sync_reg_reg"] -to [get_cells "$inst/src_sync_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
|
||||
# phase sync
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/src_phase_sync_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
# hunt down source
|
||||
set dest_pins [get_pins -of_objects [get_cells "$inst/src_phase_sync_sync1_reg_reg"] -filter {REF_PIN_NAME == "D"}]
|
||||
set nets [get_nets -segments -of_objects $dest_pins]
|
||||
set source_pins [get_pins -of_objects $nets -filter {IS_LEAF && DIRECTION == "OUT"}]
|
||||
set source [get_cells -of_objects $source_pins]
|
||||
|
||||
if {[llength $source]} {
|
||||
set_max_delay -from $source -to [get_cells "$inst/src_phase_sync_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/ptp/syn/vivado/taxi_ptp_td_leaf.tcl
Normal file
93
src/ptp/syn/vivado/taxi_ptp_td_leaf.tcl
Normal file
@@ -0,0 +1,93 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2019-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
#
|
||||
|
||||
# PTP time distribution leaf module
|
||||
|
||||
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "taxi_ptp_td_leaf(__\w+__\d+)?" ||
|
||||
REF_NAME =~ "taxi_ptp_td_leaf(__\w+__\d+)?")}] {
|
||||
puts "Inserting timing constraints for taxi_ptp_td_leaf instance $inst"
|
||||
|
||||
# get clock periods
|
||||
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/dst_sync_reg_reg/C"]]
|
||||
|
||||
set input_clk_period [if {[llength $input_clk]} {get_property -min PERIOD $input_clk} {expr 1.0}]
|
||||
set output_clk_period [if {[llength $output_clk]} {get_property -min PERIOD $output_clk} {expr 1.0}]
|
||||
|
||||
# TD data sync
|
||||
set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/dst_td_(tdata|tid)_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] $input_clk_period
|
||||
set_max_delay -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] $input_clk_period
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/td_sync_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/td_sync_reg_reg"] -to [get_cells "$inst/td_sync_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
|
||||
# timestamp sync
|
||||
set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/src_ns_sync_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/src_ns_reg_reg[*]"] -to [get_cells "$inst/src_ns_sync_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/src_ns_reg_reg[*]"] -to [get_cells "$inst/src_ns_sync_reg_reg[*]"] $input_clk_period
|
||||
|
||||
# sample clock
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/src_sync_sample_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/src_sync_reg_reg"] -to [get_cells "$inst/src_sync_sample_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/dst_sync_sample_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/dst_sync_reg_reg"] -to [get_cells "$inst/dst_sync_sample_sync1_reg_reg"] -datapath_only $output_clk_period
|
||||
}
|
||||
|
||||
# sample update sync
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/sample_update_sync\[123\]_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/sample_update_reg_reg/C"]]
|
||||
|
||||
set src_clk_period [if {[llength $src_clk]} {get_property -min PERIOD $src_clk} {expr 1.0}]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/sample_update_reg_reg"] -to [get_cells "$inst/sample_update_sync1_reg_reg"] -datapath_only $src_clk_period
|
||||
|
||||
set_max_delay -from [get_cells "$inst/sample_acc_out_reg_reg[*]"] -to [get_cells $inst/sample_acc_sync_reg_reg[*]] -datapath_only $src_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/sample_acc_out_reg_reg[*]"] -to [get_cells $inst/sample_acc_sync_reg_reg[*]] $output_clk_period
|
||||
}
|
||||
|
||||
# timestamp transfer sync
|
||||
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_max_delay -from [get_cells "$inst/src_sync_reg_reg"] -to [get_cells "$inst/src_sync_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/src_marker_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/src_marker_reg_reg"] -to [get_cells "$inst/src_marker_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
}
|
||||
37
src/ptp/syn/vivado/taxi_ptp_td_rel2tod.tcl
Normal file
37
src/ptp/syn/vivado/taxi_ptp_td_rel2tod.tcl
Normal file
@@ -0,0 +1,37 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2019-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
#
|
||||
|
||||
# PTP time distribution ToD timestamp reconstruction module
|
||||
|
||||
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "taxi_ptp_td_rel2tod(__\w+__\d+)?" ||
|
||||
REF_NAME =~ "taxi_ptp_td_rel2tod(__\w+__\d+)?")}] {
|
||||
puts "Inserting timing constraints for taxi_ptp_td_rel2tod instance $inst"
|
||||
|
||||
# get clock periods
|
||||
set input_clk [get_clocks -of_objects [get_pins "$inst/td_sync_reg_reg/C"]]
|
||||
set output_clk [get_clocks -of_objects [get_pins "$inst/td_sync_sync1_reg_reg/C"]]
|
||||
|
||||
set input_clk_period [if {[llength $input_clk]} {get_property -min PERIOD $input_clk} {expr 1.0}]
|
||||
set output_clk_period [if {[llength $output_clk]} {get_property -min PERIOD $output_clk} {expr 1.0}]
|
||||
|
||||
# TD data sync
|
||||
set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/dst_td_(tdata|tid)_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] $input_clk_period
|
||||
set_max_delay -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] $input_clk_period
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/td_sync_sync\[12\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/td_sync_reg_reg"] -to [get_cells "$inst/td_sync_sync1_reg_reg"] -datapath_only $input_clk_period
|
||||
}
|
||||
}
|
||||
590
src/ptp/tb/ptp_td.py
Normal file
590
src/ptp/tb/ptp_td.py
Normal file
@@ -0,0 +1,590 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2023-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
from decimal import Decimal, Context
|
||||
from fractions import Fraction
|
||||
|
||||
import cocotb
|
||||
from cocotb.triggers import RisingEdge, Event
|
||||
from cocotb.utils import get_sim_time
|
||||
|
||||
from cocotbext.eth.reset import Reset
|
||||
|
||||
|
||||
class PtpTdSource(Reset):
|
||||
def __init__(self,
|
||||
data=None,
|
||||
clock=None,
|
||||
reset=None,
|
||||
reset_active_level=True,
|
||||
period_ns=6.4,
|
||||
td_delay=32,
|
||||
*args, **kwargs):
|
||||
|
||||
self.log = logging.getLogger(f"cocotb.{data._path}")
|
||||
self.data = data
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
|
||||
self.log.info("PTP time distribution source")
|
||||
self.log.info("Copyright (c) 2023 Alex Forencich")
|
||||
self.log.info("https://github.com/alexforencich/verilog-ethernet")
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.ctx = Context(prec=60)
|
||||
|
||||
self.period_ns = 0
|
||||
self.period_fns = 0
|
||||
self.drift_num = 0
|
||||
self.drift_denom = 0
|
||||
self.drift_cnt = 0
|
||||
self.set_period_ns(period_ns)
|
||||
|
||||
self.ts_fns = 0
|
||||
|
||||
self.ts_rel_ns = 0
|
||||
self.ts_rel_updated = False
|
||||
|
||||
self.ts_tod_s = 0
|
||||
self.ts_tod_ns = 0
|
||||
self.ts_tod_updated = False
|
||||
|
||||
self.ts_tod_offset_ns = 0
|
||||
|
||||
self.ts_tod_alt_s = 0
|
||||
self.ts_tod_alt_offset_ns = 0
|
||||
|
||||
self.td_delay = td_delay
|
||||
|
||||
self.timestamp_delay = [(0, 0, 0, 0)]
|
||||
|
||||
self.data.setimmediatevalue(1)
|
||||
|
||||
self.pps = Event()
|
||||
|
||||
self._run_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def set_period(self, ns, fns):
|
||||
self.period_ns = int(ns)
|
||||
self.period_fns = int(fns) & 0xffffffff
|
||||
|
||||
def set_drift(self, num, denom):
|
||||
self.drift_num = int(num)
|
||||
self.drift_denom = int(denom)
|
||||
|
||||
def set_period_ns(self, t):
|
||||
t = Decimal(t)
|
||||
period, drift = self.ctx.divmod(Decimal(t) * Decimal(2**32), Decimal(1))
|
||||
period = int(period)
|
||||
frac = Fraction(drift).limit_denominator(2**16-1)
|
||||
self.set_period(period >> 32, period & 0xffffffff)
|
||||
self.set_drift(frac.numerator, frac.denominator)
|
||||
|
||||
self.log.info("Set period: %s ns", t)
|
||||
self.log.info("Period: 0x%x ns 0x%08x fns", self.period_ns, self.period_fns)
|
||||
self.log.info("Drift: 0x%04x / 0x%04x fns", self.drift_num, self.drift_denom)
|
||||
|
||||
def get_period_ns(self):
|
||||
p = Decimal((self.period_ns << 32) | self.period_fns)
|
||||
if self.drift_denom:
|
||||
p += Decimal(self.drift_num) / Decimal(self.drift_denom)
|
||||
return p / Decimal(2**32)
|
||||
|
||||
def set_ts_tod(self, ts_s, ts_ns, ts_fns):
|
||||
self.ts_tod_s = int(ts_s)
|
||||
self.ts_tod_ns = int(ts_ns)
|
||||
self.ts_fns = int(ts_fns)
|
||||
self.ts_tod_updated = True
|
||||
|
||||
def set_ts_tod_64(self, ts):
|
||||
ts = int(ts)
|
||||
self.set_ts_tod(ts >> 48, (ts >> 32) & 0x3fffffff, (ts & 0xffff) << 16)
|
||||
|
||||
def set_ts_tod_ns(self, t):
|
||||
ts_s, ts_ns = self.ctx.divmod(Decimal(t), Decimal(1000000000))
|
||||
ts_ns, ts_fns = self.ctx.divmod(ts_ns, Decimal(1))
|
||||
ts_ns = ts_ns.to_integral_value()
|
||||
ts_fns = (ts_fns * Decimal(2**32)).to_integral_value()
|
||||
self.set_ts_tod(ts_s, ts_ns, ts_fns)
|
||||
|
||||
def set_ts_tod_s(self, t):
|
||||
self.set_ts_tod_ns(Decimal(t).scaleb(9, self.ctx))
|
||||
|
||||
def set_ts_tod_sim_time(self):
|
||||
self.set_ts_tod_ns(Decimal(get_sim_time('fs')).scaleb(-6))
|
||||
|
||||
def get_ts_tod(self):
|
||||
ts_tod_s, ts_tod_ns, ts_rel_ns, ts_fns = self.timestamp_delay[0]
|
||||
return (ts_tod_s, ts_tod_ns, ts_fns)
|
||||
|
||||
def get_ts_tod_96(self):
|
||||
ts_tod_s, ts_tod_ns, ts_fns = self.get_ts_tod()
|
||||
return (ts_tod_s << 48) | (ts_tod_ns << 16) | (ts_fns >> 16)
|
||||
|
||||
def get_ts_tod_ns(self):
|
||||
ts_tod_s, ts_tod_ns, ts_fns = self.get_ts_tod()
|
||||
ns = Decimal(ts_fns) / Decimal(2**32)
|
||||
ns = self.ctx.add(ns, Decimal(ts_tod_ns))
|
||||
return self.ctx.add(ns, Decimal(ts_tod_s).scaleb(9))
|
||||
|
||||
def get_ts_tod_s(self):
|
||||
return self.get_ts_tod_ns().scaleb(-9, self.ctx)
|
||||
|
||||
def set_ts_rel(self, ts_ns, ts_fns):
|
||||
self.ts_rel_ns = int(ts_ns)
|
||||
self.ts_fns = int(ts_fns)
|
||||
self.ts_rel_updated = True
|
||||
|
||||
def set_ts_rel_64(self, ts):
|
||||
ts = int(ts)
|
||||
self.set_ts_rel(ts >> 16, (ts & 0xffff) << 16)
|
||||
|
||||
def set_ts_rel_ns(self, t):
|
||||
ts_ns, ts_fns = self.ctx.divmod(Decimal(t), Decimal(1))
|
||||
ts_ns = ts_ns.to_integral_value()
|
||||
ts_fns = (ts_fns * Decimal(2**32)).to_integral_value()
|
||||
self.set_ts_rel(ts_ns, ts_fns)
|
||||
|
||||
def set_ts_rel_s(self, t):
|
||||
self.set_ts_rel_ns(Decimal(t).scaleb(9, self.ctx))
|
||||
|
||||
def set_ts_rel_sim_time(self):
|
||||
self.set_ts_rel_ns(Decimal(get_sim_time('fs')).scaleb(-6))
|
||||
|
||||
def get_ts_rel(self):
|
||||
ts_tod_s, ts_tod_ns, ts_rel_ns, ts_fns = self.timestamp_delay[0]
|
||||
return (ts_rel_ns, ts_fns)
|
||||
|
||||
def get_ts_rel_64(self):
|
||||
ts_rel_ns, ts_fns = self.get_ts_rel()
|
||||
return (ts_rel_ns << 16) | (ts_fns >> 16)
|
||||
|
||||
def get_ts_rel_ns(self):
|
||||
ts_rel_ns, ts_fns = self.get_ts_rel()
|
||||
return self.ctx.add(Decimal(ts_fns) / Decimal(2**32), Decimal(ts_rel_ns))
|
||||
|
||||
def get_ts_rel_s(self):
|
||||
return self.get_ts_rel_ns().scaleb(-9, self.ctx)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._run_cr is not None:
|
||||
self._run_cr.kill()
|
||||
self._run_cr = None
|
||||
|
||||
self.ts_tod_s = 0
|
||||
self.ts_tod_ns = 0
|
||||
self.ts_rel_ns = 0
|
||||
self.ts_fns = 0
|
||||
self.drift_cnt = 0
|
||||
|
||||
self.data.value = 1
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._run_cr is None:
|
||||
self._run_cr = cocotb.start_soon(self._run())
|
||||
|
||||
async def _run(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
msg_index = 0
|
||||
msg = None
|
||||
msg_delay = 0
|
||||
word = None
|
||||
bit_index = 0
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
# delay timestamp
|
||||
self.timestamp_delay.append((self.ts_tod_s, self.ts_tod_ns, self.ts_rel_ns, self.ts_fns))
|
||||
while len(self.timestamp_delay) > 14*17+self.td_delay:
|
||||
self.timestamp_delay.pop(0)
|
||||
|
||||
# increment fns portion
|
||||
self.ts_fns += ((self.period_ns << 32) + self.period_fns)
|
||||
|
||||
if self.drift_denom:
|
||||
if self.drift_cnt > 0:
|
||||
self.drift_cnt -= 1
|
||||
else:
|
||||
self.drift_cnt = self.drift_denom-1
|
||||
self.ts_fns += self.drift_num
|
||||
|
||||
ns_inc = self.ts_fns >> 32
|
||||
self.ts_fns &= 0xffffffff
|
||||
|
||||
# increment relative timestamp
|
||||
self.ts_rel_ns = (self.ts_rel_ns + ns_inc) & 0xffffffffffff
|
||||
|
||||
# increment ToD timestamp
|
||||
self.ts_tod_ns = self.ts_tod_ns + ns_inc
|
||||
|
||||
if self.ts_tod_ns >= 1000000000:
|
||||
self.log.info("Seconds rollover")
|
||||
self.pps.set()
|
||||
self.ts_tod_s += 1
|
||||
self.ts_tod_ns -= 1000000000
|
||||
|
||||
# compute offset for current second
|
||||
self.ts_tod_offset_ns = (self.ts_tod_ns - self.ts_rel_ns) & 0xffffffff
|
||||
|
||||
# compute alternate offset
|
||||
if self.ts_tod_ns >> 27 == 7:
|
||||
# latter portion of second; compute offset for next second
|
||||
self.ts_tod_alt_s = self.ts_tod_s+1
|
||||
self.ts_tod_alt_offset_ns = (self.ts_tod_offset_ns - 1000000000) & 0xffffffff
|
||||
else:
|
||||
# former portion of second; compute offset for previous second
|
||||
self.ts_tod_alt_s = self.ts_tod_s-1
|
||||
self.ts_tod_alt_offset_ns = (self.ts_tod_offset_ns + 1000000000) & 0xffffffff
|
||||
|
||||
if msg_delay <= 0:
|
||||
# build message
|
||||
|
||||
msg = []
|
||||
|
||||
# word 0: control
|
||||
ctrl = 0
|
||||
ctrl |= msg_index & 0xf
|
||||
ctrl |= bool(self.ts_rel_updated) << 8
|
||||
ctrl |= bool(self.ts_tod_s & 1) << 9
|
||||
self.ts_rel_updated = False
|
||||
msg.append(ctrl)
|
||||
|
||||
if msg_index == 0:
|
||||
# msg 0 word 1: current ToD TS ns 15:0
|
||||
msg.append(self.ts_tod_ns & 0xffff)
|
||||
# msg 0 word 2: current ToD TS ns 29:16 and flag bit
|
||||
msg.append(((self.ts_tod_ns >> 16) & 0x3fff) | (0x8000 if self.ts_tod_updated else 0))
|
||||
self.ts_tod_updated = False
|
||||
# msg 0 word 3: current ToD TS seconds 15:0
|
||||
msg.append(self.ts_tod_s & 0xffff)
|
||||
# msg 0 word 4: current ToD TS seconds 31:16
|
||||
msg.append((self.ts_tod_s >> 16) & 0xffff)
|
||||
# msg 0 word 5: current ToD TS seconds 47:32
|
||||
msg.append((self.ts_tod_s >> 32) & 0xffff)
|
||||
msg_index = 1
|
||||
elif msg_index == 1:
|
||||
# msg 1 word 1: current ToD TS ns offset 15:0
|
||||
msg.append(self.ts_tod_offset_ns & 0xffff)
|
||||
# msg 1 word 2: current ToD TS ns offset 31:16
|
||||
msg.append((self.ts_tod_offset_ns >> 16) & 0xffff)
|
||||
# msg 1 word 3: drift num
|
||||
msg.append(self.drift_num)
|
||||
# msg 1 word 4: drift denom
|
||||
msg.append(self.drift_denom)
|
||||
# msg 1 word 5: drift state
|
||||
msg.append(self.drift_cnt)
|
||||
msg_index = 2
|
||||
elif msg_index == 2:
|
||||
# msg 2 word 1: alternate ToD TS ns offset 15:0
|
||||
msg.append(self.ts_tod_alt_offset_ns & 0xffff)
|
||||
# msg 2 word 2: alternate ToD TS ns offset 31:16
|
||||
msg.append((self.ts_tod_alt_offset_ns >> 16) & 0xffff)
|
||||
# msg 2 word 3: alternate ToD TS seconds 15:0
|
||||
msg.append(self.ts_tod_alt_s & 0xffff)
|
||||
# msg 2 word 4: alternate ToD TS seconds 31:16
|
||||
msg.append((self.ts_tod_alt_s >> 16) & 0xffff)
|
||||
# msg 2 word 5: alternate ToD TS seconds 47:32
|
||||
msg.append((self.ts_tod_alt_s >> 32) & 0xffff)
|
||||
msg_index = 0
|
||||
|
||||
# word 6: current fns 15:0
|
||||
msg.append(self.ts_fns & 0xffff)
|
||||
# word 7: current fns 31:16
|
||||
msg.append((self.ts_fns >> 16) & 0xffff)
|
||||
# word 8: current relative TS ns 15:0
|
||||
msg.append(self.ts_rel_ns & 0xffff)
|
||||
# word 9: current relative TS ns 31:16
|
||||
msg.append((self.ts_rel_ns >> 16) & 0xffff)
|
||||
# word 10: current relative TS ns 47:32
|
||||
msg.append((self.ts_rel_ns >> 32) & 0xffff)
|
||||
# word 11: current phase increment fns 15:0
|
||||
msg.append(self.period_fns & 0xffff)
|
||||
# word 12: current phase increment fns 31:16
|
||||
msg.append((self.period_fns >> 16) & 0xffff)
|
||||
# word 13: current phase increment ns 7:0 + crc
|
||||
msg.append(self.period_ns & 0xff)
|
||||
|
||||
msg_delay = 255
|
||||
else:
|
||||
msg_delay -= 1
|
||||
|
||||
# serialize message
|
||||
if word is None:
|
||||
if msg:
|
||||
word = msg.pop(0)
|
||||
bit_index = 0
|
||||
self.data.value = 0
|
||||
else:
|
||||
self.data.value = 1
|
||||
else:
|
||||
self.data.value = bool((word >> bit_index) & 1)
|
||||
bit_index += 1
|
||||
if bit_index == 16:
|
||||
word = None
|
||||
|
||||
|
||||
class PtpTdSink(Reset):
|
||||
def __init__(self,
|
||||
data=None,
|
||||
clock=None,
|
||||
reset=None,
|
||||
reset_active_level=True,
|
||||
period_ns=6.4,
|
||||
td_delay=32,
|
||||
*args, **kwargs):
|
||||
|
||||
self.log = logging.getLogger(f"cocotb.{data._path}")
|
||||
self.data = data
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
|
||||
self.log.info("PTP time distribution sink")
|
||||
self.log.info("Copyright (c) 2023 Alex Forencich")
|
||||
self.log.info("https://github.com/alexforencich/verilog-ethernet")
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.ctx = Context(prec=60)
|
||||
|
||||
self.period_ns = 0
|
||||
self.period_fns = 0
|
||||
self.drift_num = 0
|
||||
self.drift_denom = 0
|
||||
|
||||
self.ts_fns = 0
|
||||
|
||||
self.ts_rel_ns = 0
|
||||
|
||||
self.ts_tod_s = 0
|
||||
self.ts_tod_ns = 0
|
||||
|
||||
self.ts_tod_offset_ns = 0
|
||||
|
||||
self.ts_tod_alt_s = 0
|
||||
self.ts_tod_alt_offset_ns = 0
|
||||
|
||||
self.td_delay = td_delay
|
||||
|
||||
self.drift_cnt = 0
|
||||
|
||||
self.pps = Event()
|
||||
|
||||
self._run_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def get_period_ns(self):
|
||||
p = Decimal((self.period_ns << 32) | self.period_fns)
|
||||
if self.drift_denom:
|
||||
return p + Decimal(self.drift_num) / Decimal(self.drift_denom)
|
||||
return p / Decimal(2**32)
|
||||
|
||||
def get_ts_tod(self):
|
||||
return (self.ts_tod_s, self.ts_tod_ns, self.ts_fns)
|
||||
|
||||
def get_ts_tod_96(self):
|
||||
ts_tod_s, ts_tod_ns, ts_fns = self.get_ts_tod()
|
||||
return (ts_tod_s << 48) | (ts_tod_ns << 16) | (ts_fns >> 16)
|
||||
|
||||
def get_ts_tod_ns(self):
|
||||
ts_tod_s, ts_tod_ns, ts_fns = self.get_ts_tod()
|
||||
ns = Decimal(ts_fns) / Decimal(2**32)
|
||||
ns = self.ctx.add(ns, Decimal(ts_tod_ns))
|
||||
return self.ctx.add(ns, Decimal(ts_tod_s).scaleb(9))
|
||||
|
||||
def get_ts_tod_s(self):
|
||||
return self.get_ts_tod_ns().scaleb(-9, self.ctx)
|
||||
|
||||
def get_ts_rel(self):
|
||||
return (self.ts_rel_ns, self.ts_fns)
|
||||
|
||||
def get_ts_rel_64(self):
|
||||
ts_rel_ns, ts_fns = self.get_ts_rel()
|
||||
return (ts_rel_ns << 16) | (ts_fns >> 16)
|
||||
|
||||
def get_ts_rel_ns(self):
|
||||
ts_rel_ns, ts_fns = self.get_ts_rel()
|
||||
return self.ctx.add(Decimal(ts_fns) / Decimal(2**32), Decimal(ts_rel_ns))
|
||||
|
||||
def get_ts_rel_s(self):
|
||||
return self.get_ts_rel_ns().scaleb(-9, self.ctx)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._run_cr is not None:
|
||||
self._run_cr.kill()
|
||||
self._run_cr = None
|
||||
|
||||
self.ts_tod_s = 0
|
||||
self.ts_tod_ns = 0
|
||||
self.ts_rel_ns = 0
|
||||
self.ts_fns = 0
|
||||
self.drift_cnt = 0
|
||||
|
||||
self.data.value = 1
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._run_cr is None:
|
||||
self._run_cr = cocotb.start_soon(self._run())
|
||||
|
||||
async def _run(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
msg_index = 0
|
||||
msg = None
|
||||
msg_delay = 0
|
||||
cur_msg = []
|
||||
word = None
|
||||
bit_index = 0
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
sdi_sample = self.data.value.integer
|
||||
|
||||
# increment fns portion
|
||||
self.ts_fns += ((self.period_ns << 32) + self.period_fns)
|
||||
|
||||
if self.drift_denom:
|
||||
if self.drift_cnt > 0:
|
||||
self.drift_cnt -= 1
|
||||
else:
|
||||
self.drift_cnt = self.drift_denom-1
|
||||
self.ts_fns += self.drift_num
|
||||
|
||||
ns_inc = self.ts_fns >> 32
|
||||
self.ts_fns &= 0xffffffff
|
||||
|
||||
# increment relative timestamp
|
||||
self.ts_rel_ns = (self.ts_rel_ns + ns_inc) & 0xffffffffffff
|
||||
|
||||
# increment ToD timestamp
|
||||
self.ts_tod_ns = self.ts_tod_ns + ns_inc
|
||||
|
||||
if self.ts_tod_ns >= 1000000000:
|
||||
self.log.info("Seconds rollover")
|
||||
self.pps.set()
|
||||
self.ts_tod_s += 1
|
||||
self.ts_tod_ns -= 1000000000
|
||||
|
||||
# process messages
|
||||
if msg_delay > 0:
|
||||
msg_delay -= 1
|
||||
|
||||
if msg_delay == 0 and msg:
|
||||
self.log.info("process message %r", msg)
|
||||
|
||||
# word 0: control
|
||||
msg_index = msg[0] & 0xf
|
||||
|
||||
if msg_index == 0:
|
||||
# msg 0 word 1: current ToD TS ns 15:0
|
||||
# msg 0 word 2: current ToD TS ns 29:16
|
||||
val = ((msg[2] & 0x3fff) << 16) | msg[1]
|
||||
if self.ts_tod_ns != val:
|
||||
self.log.info("update ts_tod_ns: old 0x%x, new 0x%x", self.ts_tod_ns, val)
|
||||
self.ts_tod_ns = val
|
||||
# msg 0 word 3: current ToD TS seconds 15:0
|
||||
# msg 0 word 4: current ToD TS seconds 31:16
|
||||
# msg 0 word 5: current ToD TS seconds 47:32
|
||||
val = (msg[5] << 32) | (msg[4] << 16) | msg[3]
|
||||
if self.ts_tod_s != val:
|
||||
self.log.info("update ts_tod_s: old 0x%x, new 0x%x", self.ts_tod_s, val)
|
||||
self.ts_tod_s = val
|
||||
elif msg_index == 1:
|
||||
# msg 1 word 1: current ToD TS ns offset 15:0
|
||||
# msg 1 word 2: current ToD TS ns offset 31:16
|
||||
val = (msg[2] << 16) | msg[1]
|
||||
if self.ts_tod_offset_ns != val:
|
||||
self.log.info("update ts_tod_offset_ns: old 0x%x, new 0x%x", self.ts_tod_offset_ns, val)
|
||||
self.ts_tod_offset_ns = val
|
||||
# msg 1 word 3: drift num
|
||||
val = msg[3]
|
||||
if self.drift_num != val:
|
||||
self.log.info("update drift_num: old 0x%x, new 0x%x", self.drift_num, val)
|
||||
self.drift_num = val
|
||||
# msg 1 word 4: drift denom
|
||||
val = msg[4]
|
||||
if self.drift_denom != val:
|
||||
self.log.info("update drift_denom: old 0x%x, new 0x%x", self.drift_denom, val)
|
||||
self.drift_denom = val
|
||||
# msg 1 word 5: drift state
|
||||
val = msg[5]
|
||||
if self.drift_cnt != val:
|
||||
self.log.info("update drift_cnt: old 0x%x, new 0x%x", self.drift_cnt, val)
|
||||
self.drift_cnt = val
|
||||
elif msg_index == 2:
|
||||
# msg 2 word 1: alternate ToD TS ns offset 15:0
|
||||
# msg 2 word 2: alternate ToD TS ns offset 31:16
|
||||
val = (msg[2] << 16) | msg[1]
|
||||
if self.ts_tod_alt_offset_ns != val:
|
||||
self.log.info("update ts_tod_alt_offset_ns: old 0x%x, new 0x%x", self.ts_tod_alt_offset_ns, val)
|
||||
self.ts_tod_alt_offset_ns = val
|
||||
# msg 2 word 3: alternate ToD TS seconds 15:0
|
||||
# msg 2 word 4: alternate ToD TS seconds 31:16
|
||||
# msg 2 word 5: alternate ToD TS seconds 47:32
|
||||
val = (msg[5] << 32) | (msg[4] << 16) | msg[3]
|
||||
if self.ts_tod_alt_s != val:
|
||||
self.log.info("update ts_tod_alt_s: old 0x%x, new 0x%x", self.ts_tod_alt_s, val)
|
||||
self.ts_tod_alt_s = val
|
||||
|
||||
# word 6: current fns 15:0
|
||||
# word 7: current fns 31:16
|
||||
val = (msg[7] << 16) | msg[6]
|
||||
if self.ts_fns != val:
|
||||
self.log.info("update ts_fns: old 0x%x, new 0x%x", self.ts_fns, val)
|
||||
self.ts_fns = val
|
||||
# word 8: current relative TS ns 15:0
|
||||
# word 9: current relative TS ns 31:16
|
||||
# word 10: current relative TS ns 47:32
|
||||
val = (msg[10] << 32) | (msg[9] << 16) | msg[8]
|
||||
if self.ts_rel_ns != val:
|
||||
self.log.info("update ts_rel_ns: old 0x%x, new 0x%x", self.ts_rel_ns, val)
|
||||
self.ts_rel_ns = val
|
||||
# word 11: current phase increment fns 15:0
|
||||
# word 12: current phase increment fns 31:16
|
||||
val = (msg[12] << 16) | msg[11]
|
||||
if self.period_fns != val:
|
||||
self.log.info("update period_fns: old 0x%x, new 0x%x", self.period_fns, val)
|
||||
self.period_fns = val
|
||||
# word 13: current phase increment ns 7:0 + crc
|
||||
val = msg[13] & 0xff
|
||||
if self.period_ns != val:
|
||||
self.log.info("update period_ns: old 0x%x, new 0x%x", self.period_ns, val)
|
||||
self.period_ns = val
|
||||
|
||||
msg = None
|
||||
|
||||
# deserialize message
|
||||
if word is not None:
|
||||
word = word | (sdi_sample << bit_index)
|
||||
bit_index += 1
|
||||
|
||||
if bit_index == 16:
|
||||
cur_msg.append(word)
|
||||
word = None
|
||||
else:
|
||||
if not sdi_sample:
|
||||
# start bit
|
||||
word = 0
|
||||
bit_index = 0
|
||||
elif cur_msg:
|
||||
# idle
|
||||
msg = cur_msg
|
||||
msg_delay = self.td_delay
|
||||
cur_msg = []
|
||||
54
src/ptp/tb/taxi_ptp_clock/Makefile
Normal file
54
src/ptp/tb/taxi_ptp_clock/Makefile
Normal file
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= verilator
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
RTL_DIR = ../../rtl
|
||||
LIB_DIR = ../../lib
|
||||
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
|
||||
|
||||
DUT = taxi_ptp_clock
|
||||
COCOTB_TEST_MODULES = test_$(DUT)
|
||||
COCOTB_TOPLEVEL = $(DUT)
|
||||
MODULE = $(COCOTB_TEST_MODULES)
|
||||
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
|
||||
|
||||
# handle file list files
|
||||
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
|
||||
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
|
||||
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
|
||||
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
|
||||
|
||||
# module parameters
|
||||
export PARAM_PERIOD_NS_W := 4
|
||||
export PARAM_OFFSET_NS_W := 4
|
||||
export PARAM_FNS_W := 16
|
||||
export PARAM_PERIOD_NS_NUM := 32
|
||||
export PARAM_PERIOD_NS_DENOM := 5
|
||||
export PARAM_PIPELINE_OUTPUT := 0
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
VERILATOR_TRACE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
403
src/ptp/tb/taxi_ptp_clock/test_taxi_ptp_clock.py
Normal file
403
src/ptp/tb/taxi_ptp_clock/test_taxi_ptp_clock.py
Normal file
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
import cocotb_test.simulator
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotb.utils import get_sim_time
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
|
||||
|
||||
dut.input_ts_tod.setimmediatevalue(0)
|
||||
dut.input_ts_tod_valid.setimmediatevalue(0)
|
||||
dut.input_ts_rel.setimmediatevalue(0)
|
||||
dut.input_ts_rel_valid.setimmediatevalue(0)
|
||||
|
||||
dut.input_period_ns.setimmediatevalue(0)
|
||||
dut.input_period_fns.setimmediatevalue(0)
|
||||
dut.input_period_valid.setimmediatevalue(0)
|
||||
|
||||
dut.input_adj_ns.setimmediatevalue(0)
|
||||
dut.input_adj_fns.setimmediatevalue(0)
|
||||
dut.input_adj_count.setimmediatevalue(0)
|
||||
dut.input_adj_valid.setimmediatevalue(0)
|
||||
dut.input_adj_active.setimmediatevalue(0)
|
||||
|
||||
dut.input_drift_num.setimmediatevalue(0)
|
||||
dut.input_drift_denom.setimmediatevalue(0)
|
||||
dut.input_drift_valid.setimmediatevalue(0)
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
def get_output_ts_tod_ns(self):
|
||||
ts = self.dut.output_ts_tod.value.integer
|
||||
return Decimal(ts >> 48).scaleb(9) + (Decimal(ts & 0xffffffffffff) / Decimal(2**16))
|
||||
|
||||
def get_output_ts_tod_s(self):
|
||||
return self.get_output_ts_tod_ns().scaleb(-9)
|
||||
|
||||
def get_output_ts_rel_ns(self):
|
||||
ts = self.dut.output_ts_rel.value.integer
|
||||
return Decimal(ts) / Decimal(2**16)
|
||||
|
||||
def get_output_ts_rel_s(self):
|
||||
return self.get_output_ts_rel_ns().scaleb(-9)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_default_rate(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.get_output_ts_tod_ns()
|
||||
start_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.get_output_ts_tod_ns()
|
||||
stop_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta
|
||||
ts_rel_diff = time_delta - ts_rel_delta
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_load_timestamps(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod.value = 12345678
|
||||
dut.input_ts_tod_valid.value = 1
|
||||
dut.input_ts_rel.value = 12345678
|
||||
dut.input_ts_rel_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_valid.value = 0
|
||||
dut.input_ts_rel_valid.value = 0
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert dut.output_ts_tod.value.integer == 12345678
|
||||
assert dut.output_ts_tod_step.value.integer == 1
|
||||
assert dut.output_ts_rel.value.integer == 12345678
|
||||
assert dut.output_ts_rel_step.value.integer == 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.get_output_ts_tod_ns()
|
||||
start_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.get_output_ts_tod_ns()
|
||||
stop_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta
|
||||
ts_rel_diff = time_delta - ts_rel_delta
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_seconds_increment(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod.value = 999990000 << 16
|
||||
dut.input_ts_tod_valid.value = 1
|
||||
dut.input_ts_rel.value = 999990000 << 16
|
||||
dut.input_ts_rel_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_valid.value = 0
|
||||
dut.input_ts_rel_valid.value = 0
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.get_output_ts_tod_ns()
|
||||
start_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
saw_pps = False
|
||||
|
||||
for k in range(3000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
if dut.output_pps.value.integer:
|
||||
saw_pps = True
|
||||
tb.log.info("Got PPS with sink ToD TS %s", tb.get_output_ts_tod_ns())
|
||||
assert (tb.get_output_ts_tod_s() - 1) < 6.4e-9
|
||||
|
||||
assert saw_pps
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.get_output_ts_tod_ns()
|
||||
stop_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta
|
||||
ts_rel_diff = time_delta - ts_rel_delta
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_frequency_adjustment(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_period_ns.value = 0x6
|
||||
dut.input_period_fns.value = 0x6624
|
||||
dut.input_period_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_period_valid.value = 0
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.get_output_ts_tod_ns()
|
||||
start_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.get_output_ts_tod_ns()
|
||||
stop_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta * Decimal(6.4/(6+(0x6624+2/5)/2**16))
|
||||
ts_rel_diff = time_delta - ts_rel_delta * Decimal(6.4/(6+(0x6624+2/5)/2**16))
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_drift_adjustment(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.input_drift_num.value = 20
|
||||
dut.input_drift_denom.value = 5
|
||||
dut.input_drift_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_drift_valid.value = 0
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.get_output_ts_tod_ns()
|
||||
start_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.get_output_ts_tod_ns()
|
||||
stop_ts_rel = tb.get_output_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta * Decimal(6.4/(6+(0x6666+20/5)/2**16))
|
||||
ts_rel_diff = time_delta - ts_rel_delta * Decimal(6.4/(6+(0x6666+20/5)/2**16))
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
|
||||
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
|
||||
|
||||
|
||||
def process_f_files(files):
|
||||
lst = {}
|
||||
for f in files:
|
||||
if f[-2:].lower() == '.f':
|
||||
with open(f, 'r') as fp:
|
||||
l = fp.read().split()
|
||||
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
|
||||
lst[os.path.basename(f)] = f
|
||||
else:
|
||||
lst[os.path.basename(f)] = f
|
||||
return list(lst.values())
|
||||
|
||||
|
||||
def test_taxi_ptp_clock(request):
|
||||
dut = "taxi_ptp_clock"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.sv"),
|
||||
]
|
||||
|
||||
verilog_sources = process_f_files(verilog_sources)
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['PERIOD_NS_W'] = 4
|
||||
parameters['OFFSET_NS_W'] = 4
|
||||
parameters['FNS_W'] = 16
|
||||
parameters['PERIOD_NS_NUM'] = 32
|
||||
parameters['PERIOD_NS_DENOM'] = 5
|
||||
parameters['PIPELINE_OUTPUT'] = 0
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
simulator="verilator",
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
||||
54
src/ptp/tb/taxi_ptp_clock_cdc/Makefile
Normal file
54
src/ptp/tb/taxi_ptp_clock_cdc/Makefile
Normal file
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= verilator
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
RTL_DIR = ../../rtl
|
||||
LIB_DIR = ../../lib
|
||||
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
|
||||
|
||||
DUT = taxi_ptp_clock_cdc
|
||||
COCOTB_TEST_MODULES = test_$(DUT)
|
||||
COCOTB_TOPLEVEL = $(DUT)
|
||||
MODULE = $(COCOTB_TEST_MODULES)
|
||||
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
|
||||
|
||||
# handle file list files
|
||||
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
|
||||
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
|
||||
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
|
||||
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
|
||||
|
||||
# module parameters
|
||||
export PARAM_TS_W := 96
|
||||
export PARAM_NS_W := 4
|
||||
export PARAM_LOG_RATE := 3
|
||||
export PARAM_PIPELINE_OUTPUT := 0
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
VERILATOR_TRACE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
493
src/ptp/tb/taxi_ptp_clock_cdc/test_taxi_ptp_clock_cdc.py
Normal file
493
src/ptp/tb/taxi_ptp_clock_cdc/test_taxi_ptp_clock_cdc.py
Normal file
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from statistics import mean, stdev
|
||||
|
||||
import pytest
|
||||
import cocotb_test.simulator
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
from cocotb.utils import get_sim_steps, get_sim_time
|
||||
|
||||
from cocotbext.eth import PtpClock
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.sample_clk, 9.9, units="ns").start())
|
||||
|
||||
if len(dut.input_ts) == 64:
|
||||
self.ptp_clock = PtpClock(
|
||||
ts_rel=dut.input_ts,
|
||||
ts_step=dut.input_ts_step,
|
||||
clock=dut.input_clk,
|
||||
reset=dut.input_rst,
|
||||
period_ns=6.4
|
||||
)
|
||||
else:
|
||||
self.ptp_clock = PtpClock(
|
||||
ts_tod=dut.input_ts,
|
||||
ts_step=dut.input_ts_step,
|
||||
clock=dut.input_clk,
|
||||
reset=dut.input_rst,
|
||||
period_ns=6.4
|
||||
)
|
||||
|
||||
self.input_clock_period = 6.4
|
||||
dut.input_clk.setimmediatevalue(0)
|
||||
cocotb.start_soon(self._run_input_clock())
|
||||
|
||||
self.output_clock_period = 6.4
|
||||
dut.output_clk.setimmediatevalue(0)
|
||||
cocotb.start_soon(self._run_output_clock())
|
||||
|
||||
async def reset(self):
|
||||
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.value = 1
|
||||
self.dut.output_rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.input_clk)
|
||||
self.dut.input_rst.value = 0
|
||||
self.dut.output_rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.input_clk)
|
||||
|
||||
def set_input_clock_period(self, period):
|
||||
self.input_clock_period = period
|
||||
|
||||
async def _run_input_clock(self):
|
||||
period = None
|
||||
steps_per_ns = get_sim_steps(1.0, 'ns')
|
||||
|
||||
while True:
|
||||
if period != self.input_clock_period:
|
||||
period = self.input_clock_period
|
||||
t = Timer(int(steps_per_ns * period / 2.0))
|
||||
await t
|
||||
self.dut.input_clk.value = 1
|
||||
await t
|
||||
self.dut.input_clk.value = 0
|
||||
|
||||
def set_output_clock_period(self, period):
|
||||
self.output_clock_period = period
|
||||
|
||||
async def _run_output_clock(self):
|
||||
period = None
|
||||
steps_per_ns = get_sim_steps(1.0, 'ns')
|
||||
|
||||
while True:
|
||||
if period != self.output_clock_period:
|
||||
period = self.output_clock_period
|
||||
t = Timer(int(steps_per_ns * period / 2.0))
|
||||
await t
|
||||
self.dut.output_clk.value = 1
|
||||
await t
|
||||
self.dut.output_clk.value = 0
|
||||
|
||||
def get_input_ts_ns(self):
|
||||
ts = self.dut.input_ts.value.integer
|
||||
if len(self.dut.input_ts) == 64:
|
||||
return ts/2**16*1e-9
|
||||
else:
|
||||
return (ts >> 48) + ((ts & 0xffffffffffff)/2**16*1e-9)
|
||||
|
||||
def get_output_ts_ns(self):
|
||||
ts = self.dut.output_ts.value.integer
|
||||
if len(self.dut.output_ts) == 64:
|
||||
return ts/2**16*1e-9
|
||||
else:
|
||||
return (ts >> 48) + ((ts & 0xffffffffffff)/2**16*1e-9)
|
||||
|
||||
async def measure_ts_diff(self, N=100):
|
||||
input_ts_lst = []
|
||||
output_ts_lst = []
|
||||
|
||||
async def collect_timestamps(clk, get_ts, lst):
|
||||
while True:
|
||||
await RisingEdge(clk)
|
||||
lst.append((get_sim_time('sec'), get_ts()))
|
||||
|
||||
input_cr = cocotb.start_soon(collect_timestamps(self.dut.input_clk, self.get_input_ts_ns, input_ts_lst))
|
||||
output_cr = cocotb.start_soon(collect_timestamps(self.dut.output_clk, self.get_output_ts_ns, output_ts_lst))
|
||||
|
||||
for k in range(N):
|
||||
await RisingEdge(self.dut.output_clk)
|
||||
|
||||
input_cr.kill()
|
||||
output_cr.kill()
|
||||
|
||||
diffs = []
|
||||
|
||||
its1 = input_ts_lst.pop(0)
|
||||
its2 = input_ts_lst.pop(0)
|
||||
|
||||
for ots in output_ts_lst:
|
||||
while its2[0] < ots[0] and input_ts_lst:
|
||||
its1 = its2
|
||||
its2 = input_ts_lst.pop(0)
|
||||
|
||||
if its2[0] < ots[0]:
|
||||
break
|
||||
|
||||
dt = its2[0] - its1[0]
|
||||
dts = its2[1] - its1[1]
|
||||
|
||||
its = its1[1]+dts/dt*(ots[0]-its1[0])
|
||||
|
||||
diffs.append(ots[1] - its)
|
||||
|
||||
return diffs
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_test(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Same clock speed")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("10 ppm slower")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4*(1+.00001))
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("10 ppm faster")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4*(1-.00001))
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("200 ppm slower")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4*(1+.0002))
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("200 ppm faster")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4*(1-.0002))
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Coherent tracking (+/- 10 ppm)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
period = 6.400
|
||||
step = 0.000002
|
||||
period_min = 6.4*(1-.00001)
|
||||
period_max = 6.4*(1+.00001)
|
||||
|
||||
for i in range(500):
|
||||
period += step
|
||||
|
||||
if period <= period_min:
|
||||
step = abs(step)
|
||||
if period >= period_max:
|
||||
step = -abs(step)
|
||||
|
||||
tb.set_output_clock_period(period)
|
||||
|
||||
for i in range(200):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Coherent tracking (+/- 200 ppm)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.4)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
period = 6.400
|
||||
step = 0.000002
|
||||
period_min = 6.4*(1-.0002)
|
||||
period_max = 6.4*(1+.0002)
|
||||
|
||||
for i in range(5000):
|
||||
period += step
|
||||
|
||||
if period <= period_min:
|
||||
step = abs(step)
|
||||
if period >= period_max:
|
||||
step = -abs(step)
|
||||
|
||||
tb.set_output_clock_period(period)
|
||||
|
||||
for i in range(20):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Slightly faster (6.3 ns)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.3)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Slightly slower (6.5 ns)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(6.5)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Significantly faster (250 MHz)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(4.0)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
# diffs = await tb.measure_ts_diff()
|
||||
# tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
# assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
# await RisingEdge(dut.input_clk)
|
||||
# tb.log.info("Coherent tracking (250 MHz +0/-0.5%)")
|
||||
|
||||
# tb.set_input_clock_period(6.4)
|
||||
# tb.set_output_clock_period(4.0)
|
||||
|
||||
# await RisingEdge(dut.input_clk)
|
||||
|
||||
# period = 4.000
|
||||
# step = 0.0002
|
||||
# period_min = 4.0
|
||||
# period_max = 4.0*(1+.005)
|
||||
|
||||
# for i in range(5000):
|
||||
# period += step
|
||||
|
||||
# if period <= period_min:
|
||||
# step = abs(step)
|
||||
# if period >= period_max:
|
||||
# step = -abs(step)
|
||||
|
||||
# tb.set_output_clock_period(period)
|
||||
|
||||
# for i in range(20):
|
||||
# await RisingEdge(dut.input_clk)
|
||||
|
||||
# assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Significantly slower (100 MHz)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(10.0)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
tb.log.info("Significantly faster (390.625 MHz)")
|
||||
|
||||
tb.set_input_clock_period(6.4)
|
||||
tb.set_output_clock_period(2.56)
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference: {mean(diffs)*1e9} ns (stdev: {stdev(diffs)*1e9})")
|
||||
assert abs(mean(diffs)*1e9) < 5
|
||||
|
||||
await RisingEdge(dut.input_clk)
|
||||
await RisingEdge(dut.input_clk)
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
|
||||
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
|
||||
|
||||
|
||||
def process_f_files(files):
|
||||
lst = {}
|
||||
for f in files:
|
||||
if f[-2:].lower() == '.f':
|
||||
with open(f, 'r') as fp:
|
||||
l = fp.read().split()
|
||||
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
|
||||
lst[os.path.basename(f)] = f
|
||||
else:
|
||||
lst[os.path.basename(f)] = f
|
||||
return list(lst.values())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ts_w", [96, 64])
|
||||
def test_taxi_ptp_clock_cdc(request, ts_w):
|
||||
dut = "taxi_ptp_clock_cdc"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.sv"),
|
||||
]
|
||||
|
||||
verilog_sources = process_f_files(verilog_sources)
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['TS_W'] = ts_w
|
||||
parameters['NS_W'] = 4
|
||||
parameters['LOG_RATE'] = 3
|
||||
parameters['PIPELINE_OUTPUT'] = 0
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
simulator="verilator",
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
||||
58
src/ptp/tb/taxi_ptp_perout/Makefile
Normal file
58
src/ptp/tb/taxi_ptp_perout/Makefile
Normal file
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= verilator
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
RTL_DIR = ../../rtl
|
||||
LIB_DIR = ../../lib
|
||||
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
|
||||
|
||||
DUT = taxi_ptp_perout
|
||||
COCOTB_TEST_MODULES = test_$(DUT)
|
||||
COCOTB_TOPLEVEL = $(DUT)
|
||||
MODULE = $(COCOTB_TEST_MODULES)
|
||||
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
|
||||
|
||||
# handle file list files
|
||||
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
|
||||
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
|
||||
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
|
||||
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
|
||||
|
||||
# module parameters
|
||||
export PARAM_FNS_EN := "1'b1"
|
||||
export PARAM_OUT_START_S := 0
|
||||
export PARAM_OUT_START_NS := 0
|
||||
export PARAM_OUT_START_FNS := 0
|
||||
export PARAM_OUT_PERIOD_S := 1
|
||||
export PARAM_OUT_PERIOD_NS := 0
|
||||
export PARAM_OUT_PERIOD_FNS := 0
|
||||
export PARAM_OUT_WIDTH_S := 0
|
||||
export PARAM_OUT_WIDTH_NS := 1000
|
||||
export PARAM_OUT_WIDTH_FNS := 0
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
VERILATOR_TRACE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
167
src/ptp/tb/taxi_ptp_perout/test_taxi_ptp_perout.py
Normal file
167
src/ptp/tb/taxi_ptp_perout/test_taxi_ptp_perout.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2020-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import cocotb_test.simulator
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
|
||||
from cocotbext.eth import PtpClock
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
|
||||
|
||||
self.ptp_clock = PtpClock(
|
||||
ts_tod=dut.input_ts_tod,
|
||||
ts_step=dut.input_ts_tod_step,
|
||||
clock=dut.clk,
|
||||
reset=dut.rst,
|
||||
period_ns=6.4
|
||||
)
|
||||
|
||||
dut.enable.setimmediatevalue(0)
|
||||
dut.input_start.setimmediatevalue(0)
|
||||
dut.input_start_valid.setimmediatevalue(0)
|
||||
dut.input_period.setimmediatevalue(0)
|
||||
dut.input_period_valid.setimmediatevalue(0)
|
||||
dut.input_width.setimmediatevalue(0)
|
||||
dut.input_width_valid.setimmediatevalue(0)
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_test(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.enable.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_start.value = 100 << 16
|
||||
dut.input_start_valid.value = 1
|
||||
dut.input_period.value = 100 << 16
|
||||
dut.input_period_valid.value = 1
|
||||
dut.input_width.value = 50 << 16
|
||||
dut.input_width_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_start_valid.value = 0
|
||||
dut.input_period_valid.value = 0
|
||||
dut.input_width_valid.value = 0
|
||||
|
||||
await Timer(10000, 'ns')
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_start.value = 0 << 16
|
||||
dut.input_start_valid.value = 1
|
||||
dut.input_period.value = 100 << 16
|
||||
dut.input_period_valid.value = 1
|
||||
dut.input_width.value = 50 << 16
|
||||
dut.input_width_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_start_valid.value = 0
|
||||
dut.input_period_valid.value = 0
|
||||
dut.input_width_valid.value = 0
|
||||
|
||||
await Timer(10000, 'ns')
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
|
||||
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
|
||||
|
||||
|
||||
def process_f_files(files):
|
||||
lst = {}
|
||||
for f in files:
|
||||
if f[-2:].lower() == '.f':
|
||||
with open(f, 'r') as fp:
|
||||
l = fp.read().split()
|
||||
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
|
||||
lst[os.path.basename(f)] = f
|
||||
else:
|
||||
lst[os.path.basename(f)] = f
|
||||
return list(lst.values())
|
||||
|
||||
|
||||
def test_taxi_ptp_perout(request):
|
||||
dut = "taxi_ptp_perout"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.sv"),
|
||||
]
|
||||
|
||||
verilog_sources = process_f_files(verilog_sources)
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['FNS_EN'] = "1'b1"
|
||||
parameters['OUT_START_S'] = 0
|
||||
parameters['OUT_START_NS'] = 0
|
||||
parameters['OUT_START_FNS'] = 0x0000
|
||||
parameters['OUT_PERIOD_S'] = 1
|
||||
parameters['OUT_PERIOD_NS'] = 0
|
||||
parameters['OUT_PERIOD_FNS'] = 0x0000
|
||||
parameters['OUT_WIDTH_S'] = 0
|
||||
parameters['OUT_WIDTH_NS'] = 1000
|
||||
parameters['OUT_WIDTH_FNS'] = 0x0000
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
simulator="verilator",
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
||||
56
src/ptp/tb/taxi_ptp_td_leaf/Makefile
Normal file
56
src/ptp/tb/taxi_ptp_td_leaf/Makefile
Normal file
@@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2023-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= verilator
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
RTL_DIR = ../../rtl
|
||||
LIB_DIR = ../../lib
|
||||
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
|
||||
|
||||
DUT = taxi_ptp_td_leaf
|
||||
COCOTB_TEST_MODULES = test_$(DUT)
|
||||
COCOTB_TOPLEVEL = $(DUT)
|
||||
MODULE = $(COCOTB_TEST_MODULES)
|
||||
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
|
||||
|
||||
# handle file list files
|
||||
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
|
||||
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
|
||||
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
|
||||
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
|
||||
|
||||
# module parameters
|
||||
export PARAM_TS_REL_EN := "1'b1"
|
||||
export PARAM_TS_TOD_EN := "1'b1"
|
||||
export PARAM_TS_FNS_W := 16
|
||||
export PARAM_TS_REL_NS_W := 48
|
||||
export PARAM_TS_TOD_S_W := 48
|
||||
export PARAM_TS_REL_W := $(shell expr $(PARAM_TS_REL_NS_W) + $(PARAM_TS_FNS_W))
|
||||
export PARAM_TS_TOD_W := $(shell expr $(PARAM_TS_TOD_S_W) + 32 + $(PARAM_TS_FNS_W))
|
||||
export PARAM_TD_SDI_PIPELINE := 2
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
VERILATOR_TRACE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
1
src/ptp/tb/taxi_ptp_td_leaf/ptp_td.py
Symbolic link
1
src/ptp/tb/taxi_ptp_td_leaf/ptp_td.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../ptp_td.py
|
||||
543
src/ptp/tb/taxi_ptp_td_leaf/test_taxi_ptp_td_leaf.py
Normal file
543
src/ptp/tb/taxi_ptp_td_leaf/test_taxi_ptp_td_leaf.py
Normal file
@@ -0,0 +1,543 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2023-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from statistics import mean, stdev
|
||||
|
||||
import cocotb_test.simulator
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
from cocotb.utils import get_sim_steps, get_sim_time
|
||||
|
||||
try:
|
||||
from ptp_td import PtpTdSource
|
||||
except ImportError:
|
||||
# attempt import from current directory
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||
try:
|
||||
from ptp_td import PtpTdSource
|
||||
finally:
|
||||
del sys.path[0]
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.sample_clk, 9.9, units="ns").start())
|
||||
|
||||
self.ptp_td_source = PtpTdSource(
|
||||
data=dut.ptp_td_sdi,
|
||||
clock=dut.ptp_clk,
|
||||
reset=dut.ptp_rst,
|
||||
period_ns=6.4
|
||||
)
|
||||
|
||||
self.ptp_clock_period = 6.4
|
||||
dut.ptp_clk.setimmediatevalue(0)
|
||||
cocotb.start_soon(self._run_ptp_clock())
|
||||
|
||||
self.clock_period = 6.4
|
||||
dut.clk.setimmediatevalue(0)
|
||||
cocotb.start_soon(self._run_clock())
|
||||
|
||||
self.ref_ts_rel = []
|
||||
self.ref_ts_tod = []
|
||||
self.output_ts_rel = []
|
||||
self.output_ts_tod = []
|
||||
|
||||
cocotb.start_soon(self._run_collect_ref_ts())
|
||||
cocotb.start_soon(self._run_collect_output_ts())
|
||||
|
||||
async def reset(self):
|
||||
self.dut.ptp_rst.setimmediatevalue(0)
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
self.dut.ptp_rst.value = 1
|
||||
self.dut.rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
self.dut.ptp_rst.value = 0
|
||||
self.dut.rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
|
||||
def set_ptp_clock_period(self, period):
|
||||
self.ptp_clock_period = period
|
||||
|
||||
async def _run_ptp_clock(self):
|
||||
period = None
|
||||
steps_per_ns = get_sim_steps(1.0, 'ns')
|
||||
|
||||
while True:
|
||||
if period != self.ptp_clock_period:
|
||||
period = self.ptp_clock_period
|
||||
t = Timer(int(steps_per_ns * period / 2.0))
|
||||
await t
|
||||
self.dut.ptp_clk.value = 1
|
||||
await t
|
||||
self.dut.ptp_clk.value = 0
|
||||
|
||||
def set_clock_period(self, period):
|
||||
self.clock_period = period
|
||||
|
||||
def get_output_ts_tod_ns(self):
|
||||
ts = self.dut.output_ts_tod.value.integer
|
||||
return Decimal(ts >> 48).scaleb(9) + (Decimal(ts & 0xffffffffffff) / Decimal(2**16))
|
||||
|
||||
def get_output_ts_rel_ns(self):
|
||||
ts = self.dut.output_ts_rel.value.integer
|
||||
return Decimal(ts) / Decimal(2**16)
|
||||
|
||||
async def _run_clock(self):
|
||||
period = None
|
||||
steps_per_ns = get_sim_steps(1.0, 'ns')
|
||||
|
||||
while True:
|
||||
if period != self.clock_period:
|
||||
period = self.clock_period
|
||||
t = Timer(int(steps_per_ns * period / 2.0))
|
||||
await t
|
||||
self.dut.clk.value = 1
|
||||
await t
|
||||
self.dut.clk.value = 0
|
||||
|
||||
async def _run_collect_ref_ts(self):
|
||||
clk_event = RisingEdge(self.dut.ptp_clk)
|
||||
while True:
|
||||
await clk_event
|
||||
st = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
self.ref_ts_rel.append((st, self.ptp_td_source.get_ts_rel_ns()))
|
||||
self.ref_ts_tod.append((st, self.ptp_td_source.get_ts_tod_ns()))
|
||||
|
||||
async def _run_collect_output_ts(self):
|
||||
clk_event = RisingEdge(self.dut.clk)
|
||||
while True:
|
||||
await clk_event
|
||||
st = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
self.output_ts_rel.append((st, self.get_output_ts_rel_ns()))
|
||||
self.output_ts_tod.append((st, self.get_output_ts_tod_ns()))
|
||||
|
||||
def compute_ts_diff(self, ts_lst_1, ts_lst_2):
|
||||
ts_lst_1 = [x for x in ts_lst_1]
|
||||
|
||||
diffs = []
|
||||
|
||||
its1 = ts_lst_1.pop(0)
|
||||
its2 = ts_lst_1.pop(0)
|
||||
|
||||
for ots in ts_lst_2:
|
||||
while its2[0] < ots[0] and ts_lst_1:
|
||||
its1 = its2
|
||||
its2 = ts_lst_1.pop(0)
|
||||
|
||||
if its2[0] < ots[0]:
|
||||
break
|
||||
|
||||
dt = its2[0] - its1[0]
|
||||
dts = its2[1] - its1[1]
|
||||
|
||||
its = its1[1]+dts/dt*(ots[0]-its1[0])
|
||||
|
||||
# diffs.append(ots[1] - its)
|
||||
diffs.append(float(ots[1] - its))
|
||||
|
||||
return diffs
|
||||
|
||||
async def measure_ts_diff(self, N=100):
|
||||
self.ref_ts_rel = []
|
||||
self.ref_ts_tod = []
|
||||
self.output_ts_rel = []
|
||||
self.output_ts_tod = []
|
||||
|
||||
for k in range(N):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
rel_diffs = self.compute_ts_diff(self.ref_ts_rel, self.output_ts_rel)
|
||||
tod_diffs = self.compute_ts_diff(self.ref_ts_tod, self.output_ts_tod)
|
||||
|
||||
return rel_diffs, tod_diffs
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_test(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
# set small offset between timestamps
|
||||
tb.ptp_td_source.set_ts_rel_ns(0)
|
||||
tb.ptp_td_source.set_ts_tod_ns(10000)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Same clock speed")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("10 ppm slower")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4*(1+.00001))
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("10 ppm faster")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4*(1-.00001))
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("200 ppm slower")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4*(1+.0002))
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("200 ppm faster")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4*(1-.0002))
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Coherent tracking (+/- 10 ppm)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
period = 6.400
|
||||
step = 0.000002
|
||||
period_min = 6.4*(1-.00001)
|
||||
period_max = 6.4*(1+.00001)
|
||||
|
||||
for i in range(500):
|
||||
period += step
|
||||
|
||||
if period <= period_min:
|
||||
step = abs(step)
|
||||
if period >= period_max:
|
||||
step = -abs(step)
|
||||
|
||||
tb.set_clock_period(period)
|
||||
|
||||
for i in range(200):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Coherent tracking (+/- 200 ppm)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.4)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
period = 6.400
|
||||
step = 0.000002
|
||||
period_min = 6.4*(1-.0002)
|
||||
period_max = 6.4*(1+.0002)
|
||||
|
||||
for i in range(5000):
|
||||
period += step
|
||||
|
||||
if period <= period_min:
|
||||
step = abs(step)
|
||||
if period >= period_max:
|
||||
step = -abs(step)
|
||||
|
||||
tb.set_clock_period(period)
|
||||
|
||||
for i in range(20):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Slightly faster (6.3 ns)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.3)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Slightly slower (6.5 ns)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(6.5)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Significantly faster (250 MHz)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(4.0)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Coherent tracking (250 MHz +0/-0.5%)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(4.0)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
period = 4.000
|
||||
step = 0.0002
|
||||
period_min = 4.0
|
||||
period_max = 4.0*(1+0.005)
|
||||
|
||||
for i in range(5000):
|
||||
period += step
|
||||
|
||||
if period <= period_min:
|
||||
step = abs(step)
|
||||
if period >= period_max:
|
||||
step = -abs(step)
|
||||
|
||||
tb.set_clock_period(period)
|
||||
|
||||
for i in range(20):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Significantly slower (100 MHz)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(10.0)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
tb.log.info("Significantly faster (390.625 MHz)")
|
||||
|
||||
tb.set_ptp_clock_period(6.4)
|
||||
tb.set_clock_period(2.56)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for i in range(100000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.dut.locked.value.integer
|
||||
|
||||
rel_diffs, tod_diffs = await tb.measure_ts_diff()
|
||||
tb.log.info(f"Difference (rel): {mean(rel_diffs)} ns (stdev: {stdev(rel_diffs)})")
|
||||
tb.log.info(f"Difference (ToD): {mean(tod_diffs)} ns (stdev: {stdev(tod_diffs)})")
|
||||
assert abs(mean(rel_diffs)) < 5
|
||||
assert abs(mean(tod_diffs)) < 5
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
|
||||
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
|
||||
|
||||
|
||||
def process_f_files(files):
|
||||
lst = {}
|
||||
for f in files:
|
||||
if f[-2:].lower() == '.f':
|
||||
with open(f, 'r') as fp:
|
||||
l = fp.read().split()
|
||||
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
|
||||
lst[os.path.basename(f)] = f
|
||||
else:
|
||||
lst[os.path.basename(f)] = f
|
||||
return list(lst.values())
|
||||
|
||||
|
||||
def test_taxi_ptp_td_leaf(request):
|
||||
dut = "taxi_ptp_td_leaf"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.sv"),
|
||||
]
|
||||
|
||||
verilog_sources = process_f_files(verilog_sources)
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['TS_REL_EN'] = "1'b1"
|
||||
parameters['TS_TOD_EN'] = "1'b1"
|
||||
parameters['TS_FNS_W'] = 16
|
||||
parameters['TS_REL_NS_W'] = 48
|
||||
parameters['TS_TOD_S_W'] = 48
|
||||
parameters['TS_REL_W'] = parameters['TS_REL_NS_W'] + parameters['TS_FNS_W']
|
||||
parameters['TS_TOD_W'] = parameters['TS_TOD_S_W'] + 32 + parameters['TS_FNS_W']
|
||||
parameters['TD_SDI_PIPELINE'] = 2
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
simulator="verilator",
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
||||
50
src/ptp/tb/taxi_ptp_td_phc/Makefile
Normal file
50
src/ptp/tb/taxi_ptp_td_phc/Makefile
Normal file
@@ -0,0 +1,50 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2023-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= verilator
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
RTL_DIR = ../../rtl
|
||||
LIB_DIR = ../../lib
|
||||
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
|
||||
|
||||
DUT = taxi_ptp_td_phc
|
||||
COCOTB_TEST_MODULES = test_$(DUT)
|
||||
COCOTB_TOPLEVEL = $(DUT)
|
||||
MODULE = $(COCOTB_TEST_MODULES)
|
||||
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
|
||||
|
||||
# handle file list files
|
||||
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
|
||||
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
|
||||
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
|
||||
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
|
||||
|
||||
# module parameters
|
||||
export PARAM_PERIOD_NS_NUM := 32
|
||||
export PARAM_PERIOD_NS_DENOM := 5
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
VERILATOR_TRACE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
1
src/ptp/tb/taxi_ptp_td_phc/ptp_td.py
Symbolic link
1
src/ptp/tb/taxi_ptp_td_phc/ptp_td.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../ptp_td.py
|
||||
552
src/ptp/tb/taxi_ptp_td_phc/test_taxi_ptp_td_phc.py
Normal file
552
src/ptp/tb/taxi_ptp_td_phc/test_taxi_ptp_td_phc.py
Normal file
@@ -0,0 +1,552 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2023-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
|
||||
import cocotb_test.simulator
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotb.utils import get_sim_time
|
||||
|
||||
try:
|
||||
from ptp_td import PtpTdSink
|
||||
except ImportError:
|
||||
# attempt import from current directory
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||
try:
|
||||
from ptp_td import PtpTdSink
|
||||
finally:
|
||||
del sys.path[0]
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
|
||||
|
||||
self.ptp_td_sink = PtpTdSink(
|
||||
data=dut.ptp_td_sdo,
|
||||
clock=dut.clk,
|
||||
reset=dut.rst,
|
||||
period_ns=6.4
|
||||
)
|
||||
|
||||
dut.input_ts_rel_ns.setimmediatevalue(0)
|
||||
dut.input_ts_rel_valid.setimmediatevalue(0)
|
||||
dut.input_ts_rel_offset_ns.setimmediatevalue(0)
|
||||
dut.input_ts_rel_offset_valid.setimmediatevalue(0)
|
||||
|
||||
dut.input_ts_tod_s.setimmediatevalue(0)
|
||||
dut.input_ts_tod_ns.setimmediatevalue(0)
|
||||
dut.input_ts_tod_valid.setimmediatevalue(0)
|
||||
dut.input_ts_tod_offset_ns.setimmediatevalue(0)
|
||||
dut.input_ts_tod_offset_valid.setimmediatevalue(0)
|
||||
|
||||
dut.input_ts_offset_fns.setimmediatevalue(0)
|
||||
dut.input_ts_offset_valid.setimmediatevalue(0)
|
||||
|
||||
dut.input_period_ns.setimmediatevalue(0)
|
||||
dut.input_period_fns.setimmediatevalue(0)
|
||||
dut.input_period_valid.setimmediatevalue(0)
|
||||
dut.input_drift_num.setimmediatevalue(0)
|
||||
dut.input_drift_denom.setimmediatevalue(0)
|
||||
dut.input_drift_valid.setimmediatevalue(0)
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_default_rate(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
start_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
stop_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta
|
||||
ts_rel_diff = time_delta - ts_rel_delta
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_load_timestamps(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_s.value = 12
|
||||
dut.input_ts_tod_ns.value = 123456789
|
||||
dut.input_ts_tod_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_tod_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_valid.value = 0
|
||||
|
||||
dut.input_ts_rel_ns.value = 123456789
|
||||
dut.input_ts_rel_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_rel_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_rel_valid.value = 0
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
# assert tb.ptp_td_sink.get_ts_tod_s() - (12.123456789 + (256*6-(14*17+32)-2)*6.4e-9) < 6.4e-9
|
||||
# assert tb.ptp_td_sink.get_ts_rel_ns() - (123456789 + (256*6-(14*17+32)-1)*6.4) < 6.4
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
start_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
stop_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta
|
||||
ts_rel_diff = time_delta - ts_rel_delta
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_offsets(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
start_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.log.info("Offset FNS (positive)")
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_offset_fns.value = 0x78000000 & 0xffffffff
|
||||
dut.input_ts_offset_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_offset_valid.value = 0
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.log.info("Offset FNS (negative)")
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_offset_fns.value = -0x70000000 & 0xffffffff
|
||||
dut.input_ts_offset_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_offset_valid.value = 0
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.log.info("Offset relative TS (positive)")
|
||||
|
||||
dut.input_ts_rel_offset_ns.value = 30000 & 0xffffffff
|
||||
dut.input_ts_rel_offset_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_rel_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_rel_offset_valid.value = 0
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.log.info("Offset relative TS (negative)")
|
||||
|
||||
dut.input_ts_rel_offset_ns.value = -10000 & 0xffffffff
|
||||
dut.input_ts_rel_offset_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_rel_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_rel_offset_valid.value = 0
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.log.info("Offset ToD TS (positive)")
|
||||
|
||||
dut.input_ts_tod_offset_ns.value = 510000000 & 0x3fffffff
|
||||
dut.input_ts_tod_offset_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_tod_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_tod_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_offset_valid.value = 0
|
||||
|
||||
for k in range(2000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.log.info("Offset ToD TS (negative)")
|
||||
|
||||
dut.input_ts_tod_offset_ns.value = -500000000 & 0x3fffffff
|
||||
dut.input_ts_tod_offset_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_tod_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_tod_offset_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_offset_valid.value = 0
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
stop_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta + Decimal(0.03125) + Decimal(20000000)
|
||||
ts_rel_diff = time_delta - ts_rel_delta + Decimal(0.03125) + Decimal(20000)
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_seconds_increment(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_s.value = 0
|
||||
dut.input_ts_tod_ns.value = 999990000
|
||||
dut.input_ts_tod_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
while not dut.input_ts_tod_ready.value:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_ts_tod_valid.value = 0
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
start_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
saw_pps = False
|
||||
|
||||
for k in range(3000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
if dut.output_pps.value.integer:
|
||||
saw_pps = True
|
||||
tb.log.info("Got PPS with sink ToD TS %s", tb.ptp_td_sink.get_ts_tod_ns())
|
||||
assert (tb.ptp_td_sink.get_ts_tod_s() - 1) < 6.4e-9
|
||||
|
||||
assert saw_pps
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
stop_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta
|
||||
ts_rel_diff = time_delta - ts_rel_delta
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_frequency_adjustment(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_period_ns.value = 0x6
|
||||
dut.input_period_fns.value = 0x66240000
|
||||
dut.input_period_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_period_valid.value = 0
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
start_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
stop_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta * Decimal(6.4/(6+(0x66240000+2/5)/2**32))
|
||||
ts_rel_diff = time_delta - ts_rel_delta * Decimal(6.4/(6+(0x66240000+2/5)/2**32))
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_drift_adjustment(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.input_drift_num.value = 20000
|
||||
dut.input_drift_denom.value = 5
|
||||
dut.input_drift_valid.value = 1
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
dut.input_drift_valid.value = 0
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
start_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
start_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
for k in range(10000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
|
||||
stop_ts_tod = tb.ptp_td_sink.get_ts_tod_ns()
|
||||
stop_ts_rel = tb.ptp_td_sink.get_ts_rel_ns()
|
||||
|
||||
time_delta = stop_time-start_time
|
||||
ts_tod_delta = stop_ts_tod-start_ts_tod
|
||||
ts_rel_delta = stop_ts_rel-start_ts_rel
|
||||
|
||||
tb.log.info("sim time delta : %s ns", time_delta)
|
||||
tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
|
||||
tb.log.info("Rel ts delta : %s ns", ts_rel_delta)
|
||||
|
||||
ts_tod_diff = time_delta - ts_tod_delta * Decimal(6.4/(6+(0x66666666+20000/5)/2**32))
|
||||
ts_rel_diff = time_delta - ts_rel_delta * Decimal(6.4/(6+(0x66666666+20000/5)/2**32))
|
||||
|
||||
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
|
||||
tb.log.info("Rel ts diff : %s ns", ts_rel_diff)
|
||||
|
||||
assert abs(ts_tod_diff) < 1e-3
|
||||
assert abs(ts_rel_diff) < 1e-3
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
|
||||
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
|
||||
|
||||
|
||||
def process_f_files(files):
|
||||
lst = {}
|
||||
for f in files:
|
||||
if f[-2:].lower() == '.f':
|
||||
with open(f, 'r') as fp:
|
||||
l = fp.read().split()
|
||||
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
|
||||
lst[os.path.basename(f)] = f
|
||||
else:
|
||||
lst[os.path.basename(f)] = f
|
||||
return list(lst.values())
|
||||
|
||||
|
||||
def test_taxi_ptp_td_phc(request):
|
||||
dut = "taxi_ptp_td_phc"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.sv"),
|
||||
]
|
||||
|
||||
verilog_sources = process_f_files(verilog_sources)
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['PERIOD_NS_NUM'] = 32
|
||||
parameters['PERIOD_NS_DENOM'] = 5
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
simulator="verilator",
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
||||
54
src/ptp/tb/taxi_ptp_td_rel2tod/Makefile
Normal file
54
src/ptp/tb/taxi_ptp_td_rel2tod/Makefile
Normal file
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
#
|
||||
# Copyright (c) 2024-2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= verilator
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
RTL_DIR = ../../rtl
|
||||
LIB_DIR = ../../lib
|
||||
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
|
||||
|
||||
DUT = taxi_ptp_td_rel2tod
|
||||
COCOTB_TEST_MODULES = test_$(DUT)
|
||||
COCOTB_TOPLEVEL = test_$(DUT)
|
||||
MODULE = $(COCOTB_TEST_MODULES)
|
||||
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||
VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv
|
||||
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).sv
|
||||
VERILOG_SOURCES += $(TAXI_SRC_DIR)/axis/rtl/taxi_axis_if.sv
|
||||
|
||||
# handle file list files
|
||||
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
|
||||
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
|
||||
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
|
||||
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
|
||||
|
||||
# module parameters
|
||||
export PARAM_TS_FNS_W := 16
|
||||
export PARAM_TS_REL_NS_W := 32
|
||||
export PARAM_TS_TOD_S_W := 48
|
||||
export PARAM_TD_SDI_PIPELINE := 2
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
VERILATOR_TRACE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
1
src/ptp/tb/taxi_ptp_td_rel2tod/ptp_td.py
Symbolic link
1
src/ptp/tb/taxi_ptp_td_rel2tod/ptp_td.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../ptp_td.py
|
||||
192
src/ptp/tb/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.py
Normal file
192
src/ptp/tb/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.py
Normal file
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
"""
|
||||
|
||||
Copyright (c) 2024-2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
|
||||
import cocotb_test.simulator
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
|
||||
from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSource, AxiStreamSink
|
||||
|
||||
try:
|
||||
from ptp_td import PtpTdSource
|
||||
except ImportError:
|
||||
# attempt import from current directory
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||
try:
|
||||
from ptp_td import PtpTdSource
|
||||
finally:
|
||||
del sys.path[0]
|
||||
|
||||
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.ptp_clk, 6.4, units="ns").start())
|
||||
cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
|
||||
|
||||
self.ptp_td_source = PtpTdSource(
|
||||
data=dut.ptp_td_sdi,
|
||||
clock=dut.ptp_clk,
|
||||
reset=dut.ptp_rst,
|
||||
period_ns=6.4
|
||||
)
|
||||
|
||||
self.ts_source = AxiStreamSource(AxiStreamBus.from_entity(dut.s_axis_ts_rel), dut.clk, dut.rst)
|
||||
self.ts_sink = AxiStreamSink(AxiStreamBus.from_entity(dut.m_axis_ts_tod), dut.clk, dut.rst)
|
||||
|
||||
async def reset(self):
|
||||
self.dut.ptp_rst.setimmediatevalue(0)
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
self.dut.ptp_rst.value = 1
|
||||
self.dut.rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
self.dut.ptp_rst.value = 0
|
||||
self.dut.rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.ptp_clk)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def run_test(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
for start_rel, start_tod in [
|
||||
('1234', '123456789.987654321'),
|
||||
('1234', '123456788.987654321'),
|
||||
('1234.9', '123456789.987654321'),
|
||||
('1234.9', '123456788.987654321'),
|
||||
('1234', '123456789.907654321'),
|
||||
('1234', '123456788.907654321'),
|
||||
('1234.9', '123456789.907654321'),
|
||||
('1234.9', '123456788.907654321'),
|
||||
]:
|
||||
|
||||
tb.log.info(f"Start rel ts: {start_rel} ns")
|
||||
tb.log.info(f"Start ToD ts: {start_tod} ns")
|
||||
|
||||
tb.ptp_td_source.set_ts_rel_s(start_rel)
|
||||
tb.ptp_td_source.set_ts_tod_s(start_tod)
|
||||
|
||||
for k in range(256*6):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for offset in ['0', '0.05', '-0.9']:
|
||||
|
||||
tb.log.info(f"Offset {offset} sec")
|
||||
ts_rel = tb.ptp_td_source.get_ts_rel_ns()
|
||||
ts_tod = tb.ptp_td_source.get_ts_tod_ns()
|
||||
|
||||
tb.log.info(f"Current rel ts: {ts_rel} ns")
|
||||
tb.log.info(f"Current ToD ts: {ts_tod} ns")
|
||||
|
||||
ts_rel += Decimal(offset).scaleb(9)
|
||||
ts_tod += Decimal(offset).scaleb(9)
|
||||
rel = int(ts_rel*2**16) & 0xffffffffffff
|
||||
|
||||
tb.log.info(f"Input rel ts: {ts_rel} ns")
|
||||
tb.log.info(f"Input ToD ts: {ts_tod} ns")
|
||||
tb.log.info(f"Input relative ts raw: {rel} ({rel:#x})")
|
||||
|
||||
await tb.ts_source.send(AxiStreamFrame(tdata=[rel], tid=0))
|
||||
out_ts = await tb.ts_sink.recv()
|
||||
|
||||
tod = out_ts.tdata[0]
|
||||
tb.log.info(f"Output ToD ts raw: {tod} ({tod:#x})")
|
||||
ns = Decimal(tod & 0xffff) / Decimal(2**16)
|
||||
ns = tb.ptp_td_source.ctx.add(ns, Decimal((tod >> 16) & 0xffffffff))
|
||||
tod = tb.ptp_td_source.ctx.add(ns, Decimal(tod >> 48).scaleb(9))
|
||||
tb.log.info(f"Output ToD ts: {tod} ns")
|
||||
|
||||
tb.log.info(f"Output ns portion only: {ns} ns")
|
||||
|
||||
diff = tod - ts_tod
|
||||
tb.log.info(f"Difference: {diff} ns")
|
||||
|
||||
assert abs(diff) < 1e-3
|
||||
assert ns < 1000000000
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
|
||||
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
|
||||
|
||||
|
||||
def process_f_files(files):
|
||||
lst = {}
|
||||
for f in files:
|
||||
if f[-2:].lower() == '.f':
|
||||
with open(f, 'r') as fp:
|
||||
l = fp.read().split()
|
||||
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
|
||||
lst[os.path.basename(f)] = f
|
||||
else:
|
||||
lst[os.path.basename(f)] = f
|
||||
return list(lst.values())
|
||||
|
||||
|
||||
def test_taxi_ptp_td_rel2tod(request):
|
||||
dut = "taxi_ptp_td_rel2tod"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = module
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(tests_dir, f"{toplevel}.sv"),
|
||||
os.path.join(rtl_dir, f"{dut}.sv"),
|
||||
os.path.join(taxi_src_dir, "axis", "rtl", "taxi_axis_if.sv"),
|
||||
]
|
||||
|
||||
verilog_sources = process_f_files(verilog_sources)
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['TS_FNS_W'] = 16
|
||||
parameters['TS_REL_NS_W'] = 32
|
||||
parameters['TS_TOD_S_W'] = 48
|
||||
parameters['TD_SDI_PIPELINE'] = 2
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
simulator="verilator",
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
||||
101
src/ptp/tb/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.sv
Normal file
101
src/ptp/tb/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.sv
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: CERN-OHL-S-2.0
|
||||
/*
|
||||
|
||||
Copyright (c) 2025 FPGA Ninja, LLC
|
||||
|
||||
Authors:
|
||||
- Alex Forencich
|
||||
|
||||
*/
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* PTP time distribution ToD timestamp reconstruction module testbench
|
||||
*/
|
||||
module test_taxi_ptp_td_rel2tod #
|
||||
(
|
||||
/* verilator lint_off WIDTHTRUNC */
|
||||
parameter TS_FNS_W = 16,
|
||||
parameter TS_REL_NS_W = 32,
|
||||
parameter TS_TOD_S_W = 48,
|
||||
parameter TD_SDI_PIPELINE = 2,
|
||||
parameter logic ID_EN = 1'b0,
|
||||
parameter ID_W = 8,
|
||||
parameter logic DEST_EN = 1'b0,
|
||||
parameter DEST_W = 8,
|
||||
parameter logic USER_EN = 1'b1,
|
||||
parameter USER_W = 1
|
||||
/* verilator lint_on WIDTHTRUNC */
|
||||
)
|
||||
();
|
||||
|
||||
localparam TS_REL_W = TS_REL_NS_W + TS_FNS_W;
|
||||
localparam TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W;
|
||||
|
||||
logic clk;
|
||||
logic rst;
|
||||
|
||||
logic ptp_clk;
|
||||
logic ptp_rst;
|
||||
logic ptp_td_sdi;
|
||||
|
||||
taxi_axis_if #(
|
||||
.DATA_W(TS_REL_W),
|
||||
.KEEP_EN(0),
|
||||
.KEEP_W(1),
|
||||
.STRB_EN(0),
|
||||
.LAST_EN(0),
|
||||
.ID_EN(ID_EN),
|
||||
.ID_W(ID_W),
|
||||
.DEST_EN(DEST_EN),
|
||||
.DEST_W(DEST_W),
|
||||
.USER_EN(USER_EN),
|
||||
.USER_W(USER_W)
|
||||
) s_axis_ts_rel();
|
||||
|
||||
taxi_axis_if #(
|
||||
.DATA_W(TS_TOD_W),
|
||||
.KEEP_EN(0),
|
||||
.KEEP_W(1),
|
||||
.STRB_EN(0),
|
||||
.LAST_EN(0),
|
||||
.ID_EN(ID_EN),
|
||||
.ID_W(ID_W),
|
||||
.DEST_EN(DEST_EN),
|
||||
.DEST_W(DEST_W),
|
||||
.USER_EN(USER_EN),
|
||||
.USER_W(USER_W)
|
||||
) m_axis_ts_tod();
|
||||
|
||||
taxi_ptp_td_rel2tod #(
|
||||
.TS_FNS_W(TS_FNS_W),
|
||||
.TS_REL_NS_W(TS_REL_NS_W),
|
||||
.TS_TOD_S_W(TS_TOD_S_W),
|
||||
.TS_REL_W(TS_REL_W),
|
||||
.TS_TOD_W(TS_TOD_W),
|
||||
.TD_SDI_PIPELINE(TD_SDI_PIPELINE)
|
||||
)
|
||||
uut (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
|
||||
/*
|
||||
* PTP clock interface
|
||||
*/
|
||||
.ptp_clk(ptp_clk),
|
||||
.ptp_rst(ptp_rst),
|
||||
.ptp_td_sdi(ptp_td_sdi),
|
||||
|
||||
/*
|
||||
* Timestamp conversion
|
||||
*/
|
||||
.s_axis_ts_rel(s_axis_ts_rel),
|
||||
.m_axis_ts_tod(m_axis_ts_tod)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
||||
Reference in New Issue
Block a user