mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-09 17:08:38 -08:00
ptp: Add PTP TD PHC module and testbench
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
645
rtl/ptp/taxi_ptp_td_phc.sv
Normal file
645
rtl/ptp/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
|
||||||
590
tb/ptp/ptp_td.py
Normal file
590
tb/ptp/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 = []
|
||||||
46
tb/ptp/taxi_ptp_td_phc/Makefile
Normal file
46
tb/ptp/taxi_ptp_td_phc/Makefile
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
DUT = taxi_ptp_td_phc
|
||||||
|
COCOTB_TEST_MODULES = test_$(DUT)
|
||||||
|
COCOTB_TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(COCOTB_TEST_MODULES)
|
||||||
|
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||||
|
VERILOG_SOURCES += ../../../rtl/ptp/$(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
tb/ptp/taxi_ptp_td_phc/ptp_td.py
Symbolic link
1
tb/ptp/taxi_ptp_td_phc/ptp_td.py
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../ptp_td.py
|
||||||
550
tb/ptp/taxi_ptp_td_phc/test_taxi_ptp_td_phc.py
Normal file
550
tb/ptp/taxi_ptp_td_phc/test_taxi_ptp_td_phc.py
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
#!/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'))
|
||||||
|
|
||||||
|
|
||||||
|
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, "ptp", 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,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user