mirror of
https://github.com/corundum/corundum.git
synced 2025-01-16 08:12:53 +08:00
Add PTP time distribution components
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
parent
009560f583
commit
bd8e8e5b20
10
README.md
10
README.md
@ -307,6 +307,14 @@ PTP clock CDC module with PPS output. Use this module to transfer and deskew a
|
||||
free-running PTP clock across clock domains. Supports both 64 and 96 bit
|
||||
timestamp formats.
|
||||
|
||||
### `ptp_td_leaf` module
|
||||
|
||||
PTP time distribution leaf clock module. Accepts PTP time distribution messages from the `ptp_td_phc` module, and outputs both the 96-bit time-of-day timestamp and 64-bit relative timestamp in the destination clock domain, as well as both single-cycle and stretched PPS outputs. Also supports pipelining the serial data input, automatically compensating for the pipeline delay.
|
||||
|
||||
### `ptp_td_phc` module
|
||||
|
||||
PTP time distribution master clock module. Generates PTP time distribution messages over a serial interface that can provide PTP time to one or more leaf clocks (`ptp_td_leaf`), as well as both single-cycle and stretched PPS outputs. The fractional nanoseconds portion is shared between the time-of-day and relative timestamps to support reconstruction of the 96-bit time-of-day timestamp from a truncated relative timestamp. The module supports coarse setting of both the ToD and relative timestamps as well as atomically applying offsets to the ToD and relative timestamps and the shared fractional nanoseconds.
|
||||
|
||||
### `ptp_ts_extract` module
|
||||
|
||||
PTP timestamp extract module. Use this module to extract a PTP timestamp
|
||||
@ -466,6 +474,8 @@ and data lines.
|
||||
rtl/oddr.v : Generic DDR output register
|
||||
rtl/ptp_clock.v : PTP clock
|
||||
rtl/ptp_clock_cdc.v : PTP clock CDC
|
||||
rtl/ptp_td_leaf.v : PTP time distribution leaf clock
|
||||
rtl/ptp_td_phc.v : PTP time distribution master clock
|
||||
rtl/ptp_ts_extract.v : PTP timestamp extract
|
||||
rtl/ptp_perout.v : PTP period out
|
||||
rtl/rgmii_phy_if.v : RGMII PHY interface
|
||||
|
1015
rtl/ptp_td_leaf.v
Normal file
1015
rtl/ptp_td_leaf.v
Normal file
File diff suppressed because it is too large
Load Diff
603
rtl/ptp_td_phc.v
Normal file
603
rtl/ptp_td_phc.v
Normal file
@ -0,0 +1,603 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* PTP time distribution PHC
|
||||
*/
|
||||
module ptp_td_phc #
|
||||
(
|
||||
parameter PERIOD_NS_NUM = 32,
|
||||
parameter PERIOD_NS_DENOM = 5
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* ToD timestamp control
|
||||
*/
|
||||
input wire [47:0] input_ts_tod_s,
|
||||
input wire [29:0] input_ts_tod_ns,
|
||||
input wire input_ts_tod_valid,
|
||||
output wire input_ts_tod_ready,
|
||||
input wire [29:0] input_ts_tod_offset_ns,
|
||||
input wire input_ts_tod_offset_valid,
|
||||
output wire input_ts_tod_offset_ready,
|
||||
|
||||
/*
|
||||
* Relative timestamp control
|
||||
*/
|
||||
input wire [47:0] input_ts_rel_ns,
|
||||
input wire input_ts_rel_valid,
|
||||
output wire input_ts_rel_ready,
|
||||
input wire [31:0] input_ts_rel_offset_ns,
|
||||
input wire input_ts_rel_offset_valid,
|
||||
output wire input_ts_rel_offset_ready,
|
||||
|
||||
/*
|
||||
* Fractional ns control
|
||||
*/
|
||||
input wire [31:0] input_ts_offset_fns,
|
||||
input wire input_ts_offset_valid,
|
||||
output wire input_ts_offset_ready,
|
||||
|
||||
/*
|
||||
* Period control
|
||||
*/
|
||||
input wire [7:0] input_period_ns,
|
||||
input wire [31:0] input_period_fns,
|
||||
input wire input_period_valid,
|
||||
output wire input_period_ready,
|
||||
input wire [15:0] input_drift_num,
|
||||
input wire [15:0] input_drift_denom,
|
||||
input wire input_drift_valid,
|
||||
output wire input_drift_ready,
|
||||
|
||||
/*
|
||||
* Time distribution serial data output
|
||||
*/
|
||||
output wire ptp_td_sdo,
|
||||
|
||||
/*
|
||||
* PPS output
|
||||
*/
|
||||
output wire output_pps,
|
||||
output wire output_pps_str
|
||||
);
|
||||
|
||||
localparam INC_NS_W = 9+8;
|
||||
|
||||
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}}}) / 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;
|
||||
|
||||
reg [7:0] period_ns_reg = PERIOD_NS;
|
||||
reg [FNS_W-1:0] period_fns_reg = PERIOD_FNS;
|
||||
|
||||
reg [15:0] drift_num_reg = PERIOD_FNS_REM;
|
||||
reg [15:0] drift_denom_reg = PERIOD_NS_DENOM;
|
||||
reg [15:0] drift_cnt_reg = 0;
|
||||
reg [15:0] drift_cnt_d1_reg = 0;
|
||||
reg drift_apply_reg = 1'b0;
|
||||
reg [23:0] drift_acc_reg = 0;
|
||||
|
||||
reg [INC_NS_W-1:0] ts_inc_ns_reg = 0;
|
||||
reg [FNS_W-1:0] ts_fns_reg = 0;
|
||||
|
||||
reg [32:0] ts_rel_ns_inc_reg = 0;
|
||||
reg [47:0] ts_rel_ns_reg = 0;
|
||||
reg ts_rel_updated_reg = 1'b0;
|
||||
|
||||
reg [47:0] ts_tod_s_reg = 0;
|
||||
reg [29:0] ts_tod_ns_reg = 0;
|
||||
reg ts_tod_updated_reg = 1'b0;
|
||||
|
||||
reg [31:0] ts_tod_offset_ns_reg = 0;
|
||||
|
||||
reg [47:0] ts_tod_alt_s_reg = 0;
|
||||
reg [31:0] ts_tod_alt_offset_ns_reg = 0;
|
||||
|
||||
reg [7:0] td_update_cnt_reg = 0;
|
||||
reg td_update_reg = 1'b0;
|
||||
reg [1:0] td_msg_i_reg = 0;
|
||||
|
||||
reg input_ts_tod_ready_reg = 1'b0;
|
||||
reg input_ts_tod_offset_ready_reg = 1'b0;
|
||||
reg input_ts_rel_ready_reg = 1'b0;
|
||||
reg input_ts_rel_offset_ready_reg = 1'b0;
|
||||
reg input_ts_offset_ready_reg = 1'b0;
|
||||
|
||||
reg [17*14-1:0] td_shift_reg = {17*14{1'b1}};
|
||||
|
||||
reg [15:0] pps_gen_fns_reg = 0;
|
||||
reg [9:0] pps_gen_ns_inc_reg = 0;
|
||||
reg [30:0] pps_gen_ns_reg = 31'h40000000;
|
||||
|
||||
reg [9:0] pps_delay_reg = 0;
|
||||
reg pps_reg = 0;
|
||||
reg pps_str_reg = 0;
|
||||
|
||||
reg [3:0] update_state_reg = 0;
|
||||
|
||||
reg [47:0] adder_a_reg = 0;
|
||||
reg [47:0] adder_b_reg = 0;
|
||||
reg adder_cin_reg = 0;
|
||||
wire [47:0] adder_sum;
|
||||
wire adder_cout;
|
||||
|
||||
assign {adder_cout, adder_sum} = adder_a_reg + adder_b_reg + adder_cin_reg;
|
||||
|
||||
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 @(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) begin
|
||||
if (drift_cnt_reg == 0) begin
|
||||
drift_cnt_reg <= drift_denom_reg - 1;
|
||||
drift_apply_reg <= 1'b1;
|
||||
end else begin
|
||||
drift_cnt_reg <= drift_cnt_reg - 1;
|
||||
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 + 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]} + 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]} + pps_gen_fns_reg;
|
||||
end
|
||||
pps_gen_ns_reg <= pps_gen_ns_reg + pps_gen_ns_inc_reg;
|
||||
|
||||
if (!pps_gen_ns_reg[30]) begin
|
||||
// pps_delay_reg <= 14*17 + 32 + 1;
|
||||
pps_delay_reg <= 14*17 + 32 + 248;
|
||||
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) 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
|
||||
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 <= drift_acc_reg + drift_num_reg;
|
||||
end else begin
|
||||
adder_a_reg <= drift_acc_reg;
|
||||
end
|
||||
adder_b_reg <= input_ts_offset_valid ? $signed(input_ts_offset_fns) : 0;
|
||||
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;
|
||||
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;
|
||||
adder_b_reg <= {period_ns_reg, period_fns_reg, 8'd0};
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 2;
|
||||
end
|
||||
2: begin
|
||||
// compute next fns
|
||||
adder_a_reg <= adder_sum;
|
||||
adder_b_reg <= ts_fns_reg;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 3;
|
||||
end
|
||||
3: begin
|
||||
// store fns; compute relative timestamp 1 - add previous value and offset
|
||||
{ts_inc_ns_reg, ts_fns_reg} <= {adder_cout, adder_sum};
|
||||
|
||||
adder_a_reg <= ts_rel_ns_reg;
|
||||
adder_b_reg <= 0;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
// offset relative timestamp if requested
|
||||
if (input_ts_rel_offset_valid) begin
|
||||
adder_b_reg <= $signed(input_ts_rel_offset_ns);
|
||||
input_ts_rel_offset_ready_reg <= 1'b1;
|
||||
ts_rel_updated_reg <= 1'b1;
|
||||
end
|
||||
|
||||
update_state_reg <= 4;
|
||||
end
|
||||
4: begin
|
||||
// compute relative timestamp 2 - add increment
|
||||
adder_a_reg <= adder_sum;
|
||||
adder_b_reg <= ts_inc_ns_reg;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 5;
|
||||
end
|
||||
5: begin
|
||||
// store relative timestamp; compute ToD timestamp 1 - add previous value and increment
|
||||
ts_rel_ns_reg <= adder_sum;
|
||||
|
||||
adder_a_reg <= ts_tod_ns_reg;
|
||||
adder_b_reg <= ts_inc_ns_reg;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 6;
|
||||
end
|
||||
6: begin
|
||||
// compute ToD timestamp 2 - add offset
|
||||
adder_a_reg <= adder_sum;
|
||||
adder_b_reg <= 0;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
// offset ToD timestamp if requested
|
||||
if (input_ts_tod_offset_valid) begin
|
||||
adder_b_reg <= $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;
|
||||
end
|
||||
7: begin
|
||||
// compute ToD timestamp 2 - check for underflow/overflow
|
||||
ts_tod_ns_reg <= adder_sum;
|
||||
|
||||
if (adder_b_reg[47] && !adder_cout) begin
|
||||
// borrowed; add 1 billion
|
||||
adder_a_reg <= adder_sum;
|
||||
adder_b_reg <= NS_PER_S;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 8;
|
||||
end else begin
|
||||
// did not borrow; subtract 1 billion to check for overflow
|
||||
adder_a_reg <= adder_sum;
|
||||
adder_b_reg <= -NS_PER_S;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 9;
|
||||
end
|
||||
end
|
||||
8: begin
|
||||
// seconds decrement
|
||||
ts_tod_ns_reg <= adder_sum;
|
||||
pps_gen_ns_reg[30] <= 1'b1;
|
||||
|
||||
adder_a_reg <= ts_tod_s_reg;
|
||||
adder_b_reg <= -1;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 10;
|
||||
end
|
||||
9: begin
|
||||
// seconds increment
|
||||
pps_gen_ns_reg <= adder_sum;
|
||||
|
||||
if (!adder_cout) 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; decrement seconds
|
||||
ts_tod_ns_reg <= adder_sum;
|
||||
|
||||
adder_a_reg <= ts_tod_s_reg;
|
||||
adder_b_reg <= 1;
|
||||
adder_cin_reg <= 0;
|
||||
end
|
||||
|
||||
update_state_reg <= 10;
|
||||
end
|
||||
10: begin
|
||||
// store seconds; compute offset
|
||||
ts_tod_s_reg <= adder_sum;
|
||||
|
||||
if (adder_sum == ts_tod_alt_s_reg) begin
|
||||
// store previous offset as alternate
|
||||
ts_tod_alt_s_reg <= ts_tod_s_reg;
|
||||
ts_tod_alt_offset_ns_reg <= ts_tod_offset_ns_reg;
|
||||
end
|
||||
|
||||
adder_a_reg <= ts_tod_ns_reg;
|
||||
adder_b_reg <= ~ts_rel_ns_reg;
|
||||
adder_cin_reg <= 1;
|
||||
|
||||
update_state_reg <= 11;
|
||||
end
|
||||
11: begin
|
||||
// store offset
|
||||
ts_tod_offset_ns_reg <= adder_sum;
|
||||
|
||||
adder_a_reg <= adder_sum;
|
||||
adder_b_reg <= -NS_PER_S;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
if (ts_tod_ns_reg[29]) begin
|
||||
// latter half of second; compute offset for next second
|
||||
adder_b_reg <= -NS_PER_S;
|
||||
update_state_reg <= 12;
|
||||
end else begin
|
||||
// former half of second; compute offset for previous second
|
||||
adder_b_reg <= NS_PER_S;
|
||||
update_state_reg <= 14;
|
||||
end
|
||||
end
|
||||
12: begin
|
||||
// store alternate offset for next second
|
||||
ts_tod_alt_offset_ns_reg <= adder_sum;
|
||||
|
||||
adder_a_reg <= ts_tod_s_reg;
|
||||
adder_b_reg <= 1;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 13;
|
||||
end
|
||||
13: begin
|
||||
// store alternate second for next second
|
||||
ts_tod_alt_s_reg <= adder_sum;
|
||||
|
||||
update_state_reg <= 0;
|
||||
end
|
||||
14: begin
|
||||
// store alternate offset for previous second
|
||||
ts_tod_alt_offset_ns_reg <= adder_sum;
|
||||
|
||||
adder_a_reg <= ts_tod_s_reg;
|
||||
adder_b_reg <= -1;
|
||||
adder_cin_reg <= 0;
|
||||
|
||||
update_state_reg <= 15;
|
||||
end
|
||||
15: begin
|
||||
// store alternate second for previous second
|
||||
ts_tod_alt_s_reg <= adder_sum;
|
||||
|
||||
update_state_reg <= 0;
|
||||
end
|
||||
default: begin
|
||||
// invalid state; return to idle
|
||||
update_state_reg <= 0;
|
||||
end
|
||||
endcase
|
||||
|
||||
// time distribution message generation
|
||||
td_shift_reg <= {1'b1, td_shift_reg} >> 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] <= 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] <= 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
|
||||
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 +: 16] <= period_ns_reg[7:0];
|
||||
end
|
||||
|
||||
if (rst) begin
|
||||
period_ns_reg <= PERIOD_NS;
|
||||
period_fns_reg <= PERIOD_FNS;
|
||||
drift_num_reg <= PERIOD_FNS_REM;
|
||||
drift_denom_reg <= 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 <= {17*14{1'b1}};
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
104
syn/vivado/ptp_td_leaf.tcl
Normal file
104
syn/vivado/ptp_td_leaf.tcl
Normal file
@ -0,0 +1,104 @@
|
||||
# Copyright (c) 2019-2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# PTP time distribution leaf module
|
||||
|
||||
foreach inst [get_cells -hier -filter {(ORIG_REF_NAME == ptp_td_leaf || REF_NAME == ptp_td_leaf)}] {
|
||||
puts "Inserting timing constraints for 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
|
||||
}
|
||||
}
|
604
tb/ptp_td.py
Normal file
604
tb/ptp_td.py
Normal file
@ -0,0 +1,604 @@
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
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_s = ts_s.scaleb(-9).to_integral_value()
|
||||
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 & (1 << 29):
|
||||
# latter half 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 half 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 = []
|
75
tb/ptp_td_leaf/Makefile
Normal file
75
tb/ptp_td_leaf/Makefile
Normal file
@ -0,0 +1,75 @@
|
||||
# Copyright (c) 2020 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= icarus
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
DUT = ptp_td_leaf
|
||||
TOPLEVEL = $(DUT)
|
||||
MODULE = test_$(DUT)
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT).v
|
||||
|
||||
# module parameters
|
||||
export PARAM_TS_REL_EN := 1
|
||||
export PARAM_TS_TOD_EN := 1
|
||||
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 $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
COMPILE_ARGS += -s iverilog_dump
|
||||
endif
|
||||
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
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||
echo 'end' >> $@
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
1
tb/ptp_td_leaf/ptp_td.py
Symbolic link
1
tb/ptp_td_leaf/ptp_td.py
Symbolic link
@ -0,0 +1 @@
|
||||
../ptp_td.py
|
507
tb/ptp_td_leaf/test_ptp_td_leaf.py
Normal file
507
tb/ptp_td_leaf/test_ptp_td_leaf.py
Normal file
@ -0,0 +1,507 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
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("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(rtl_dir, '..', 'lib'))
|
||||
axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl'))
|
||||
|
||||
|
||||
def test_ptp_td_leaf(request):
|
||||
dut = "ptp_td_leaf"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.v"),
|
||||
]
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['TS_REL_EN'] = 1
|
||||
parameters['TS_TOD_EN'] = 1
|
||||
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(
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
69
tb/ptp_td_phc/Makefile
Normal file
69
tb/ptp_td_phc/Makefile
Normal file
@ -0,0 +1,69 @@
|
||||
# Copyright (c) 2020 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= icarus
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
DUT = ptp_td_phc
|
||||
TOPLEVEL = $(DUT)
|
||||
MODULE = test_$(DUT)
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT).v
|
||||
|
||||
# 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 $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
COMPILE_ARGS += -s iverilog_dump
|
||||
endif
|
||||
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
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||
echo 'end' >> $@
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
1
tb/ptp_td_phc/ptp_td.py
Symbolic link
1
tb/ptp_td_phc/ptp_td.py
Symbolic link
@ -0,0 +1 @@
|
||||
../ptp_td.py
|
550
tb/ptp_td_phc/test_ptp_td_phc.py
Normal file
550
tb/ptp_td_phc/test_ptp_td_phc.py
Normal file
@ -0,0 +1,550 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
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(rtl_dir, '..', 'lib'))
|
||||
axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl'))
|
||||
|
||||
|
||||
def test_ptp_td_phc(request):
|
||||
dut = "ptp_td_phc"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.v"),
|
||||
]
|
||||
|
||||
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(
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user