diff --git a/rtl/ptp_td_rel2tod.v b/rtl/ptp_td_rel2tod.v new file mode 100644 index 00000000..81483914 --- /dev/null +++ b/rtl/ptp_td_rel2tod.v @@ -0,0 +1,313 @@ +/* + +Copyright (c) 2024 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 / 1fs +`default_nettype none + +/* + * PTP time distribution ToD timestamp reconstruction module + */ +module ptp_td_rel2tod # +( + parameter TS_FNS_W = 16, + parameter TS_REL_NS_W = 32, + parameter TS_TOD_S_W = 48, + parameter TS_REL_W = TS_REL_NS_W + TS_FNS_W, + parameter TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W, + parameter TS_TAG_W = 8, + parameter TD_SDI_PIPELINE = 2 +) +( + input wire clk, + input wire rst, + + /* + * PTP clock interface + */ + input wire ptp_clk, + input wire ptp_rst, + input wire ptp_td_sdi, + + /* + * Timestamp conversion + */ + input wire [TS_REL_W-1:0] input_ts_rel, + input wire [TS_TAG_W-1:0] input_ts_tag, + input wire input_ts_valid, + output wire [TS_TOD_W-1:0] output_ts_tod, + output wire [TS_TAG_W-1:0] output_ts_tag, + output wire output_ts_valid +); + +localparam TS_TOD_NS_W = 30; +localparam TS_NS_W = TS_TOD_NS_W+1; + +localparam [30:0] NS_PER_S = 31'd1_000_000_000; + +// pipeline to facilitate long input path +wire ptp_td_sdi_pipe[0:TD_SDI_PIPELINE]; + +assign ptp_td_sdi_pipe[0] = ptp_td_sdi; + +generate + +genvar n; + +for (n = 0; n < TD_SDI_PIPELINE; n = n + 1) begin : pipe_stage + + (* shreg_extract = "no" *) + reg ptp_td_sdi_reg = 0; + + assign ptp_td_sdi_pipe[n+1] = ptp_td_sdi_reg; + + always @(posedge ptp_clk) begin + ptp_td_sdi_reg <= ptp_td_sdi_pipe[n]; + end + +end + +endgenerate + +// deserialize data +reg [15:0] td_shift_reg = 0; +reg [4:0] bit_cnt_reg = 0; +reg td_valid_reg = 1'b0; +reg [3:0] td_index_reg = 0; +reg [3:0] td_msg_reg = 0; + +reg [15:0] td_tdata_reg = 0; +reg td_tvalid_reg = 1'b0; +reg td_tlast_reg = 1'b0; +reg [7:0] td_tid_reg = 0; +reg td_sync_reg = 1'b0; + +always @(posedge ptp_clk) begin + td_shift_reg <= {ptp_td_sdi_pipe[TD_SDI_PIPELINE], td_shift_reg[15:1]}; + + td_tvalid_reg <= 1'b0; + + if (bit_cnt_reg) begin + bit_cnt_reg <= bit_cnt_reg - 1; + end else begin + td_valid_reg <= 1'b0; + if (td_valid_reg) begin + td_tdata_reg <= td_shift_reg; + td_tvalid_reg <= 1'b1; + td_tlast_reg <= ptp_td_sdi_pipe[TD_SDI_PIPELINE]; + td_tid_reg <= {td_msg_reg, td_index_reg}; + if (td_index_reg == 0) begin + td_msg_reg <= td_shift_reg[3:0]; + td_tid_reg[7:4] <= td_shift_reg[3:0]; + end + td_index_reg <= td_index_reg + 1; + td_sync_reg = !td_sync_reg; + end + if (ptp_td_sdi_pipe[TD_SDI_PIPELINE] == 0) begin + bit_cnt_reg <= 16; + td_valid_reg <= 1'b1; + end else begin + td_index_reg <= 0; + end + end + + if (ptp_rst) begin + bit_cnt_reg <= 0; + td_valid_reg <= 1'b0; + + td_tvalid_reg <= 1'b0; + end +end + +// sync TD data +reg [15:0] dst_td_tdata_reg = 0; +reg dst_td_tvalid_reg = 1'b0; +reg [7:0] dst_td_tid_reg = 0; + +(* shreg_extract = "no" *) +reg td_sync_sync1_reg = 1'b0; +(* shreg_extract = "no" *) +reg td_sync_sync2_reg = 1'b0; +(* shreg_extract = "no" *) +reg td_sync_sync3_reg = 1'b0; + +always @(posedge clk) begin + td_sync_sync1_reg <= td_sync_reg; + td_sync_sync2_reg <= td_sync_sync1_reg; + td_sync_sync3_reg <= td_sync_sync2_reg; +end + +always @(posedge clk) begin + dst_td_tvalid_reg <= 1'b0; + + if (td_sync_sync3_reg ^ td_sync_sync2_reg) begin + dst_td_tdata_reg <= td_tdata_reg; + dst_td_tvalid_reg <= 1'b1; + dst_td_tid_reg <= td_tid_reg; + end + + if (rst) begin + dst_td_tvalid_reg <= 1'b0; + end +end + +reg ts_sel_reg = 0; + +reg [47:0] ts_tod_s_0_reg = 0; +reg [31:0] ts_tod_offset_ns_0_reg = 0; +reg [47:0] ts_tod_s_1_reg = 0; +reg [31:0] ts_tod_offset_ns_1_reg = 0; + +reg [TS_TOD_S_W-1:0] output_ts_tod_s_reg = 0, output_ts_tod_s_next; +reg [TS_TOD_NS_W-1:0] output_ts_tod_ns_reg = 0, output_ts_tod_ns_next; +reg [TS_FNS_W-1:0] output_ts_fns_reg = 0, output_ts_fns_next; +reg [TS_TAG_W-1:0] output_ts_tag_reg = 0, output_ts_tag_next; +reg output_ts_valid_reg = 0, output_ts_valid_next; + +reg [TS_NS_W-1:0] ts_tod_ns_0; +reg [TS_NS_W-1:0] ts_tod_ns_1; + +assign output_ts_tod = {output_ts_tod_s_reg, 2'b00, output_ts_tod_ns_reg, output_ts_fns_reg}; +assign output_ts_tag = output_ts_tag_reg; +assign output_ts_valid = output_ts_valid_reg; + +always @* begin + // reconstruct timestamp + // apply both offsets + ts_tod_ns_0 = (input_ts_rel >> TS_FNS_W) + ts_tod_offset_ns_0_reg; + ts_tod_ns_1 = (input_ts_rel >> TS_FNS_W) + ts_tod_offset_ns_1_reg; + + // pick the correct result + // 2 MSB clear = lower half of range (0-536,870,911) + // 1 MSB clear = upper half of range, but could also be over 1 billion (536,870,912-1,073,741,823) + // 1 MSB set = overflow or underflow + // prefer 2 MSB clear over 1 MSB clear if neither result was overflow or underflow + if (ts_tod_ns_0[30:29] == 0 || (ts_tod_ns_0[30] == 0 && ts_tod_ns_1[30:29] != 0)) begin + output_ts_tod_s_next = ts_tod_s_0_reg; + output_ts_tod_ns_next = ts_tod_ns_0; + end else begin + output_ts_tod_s_next = ts_tod_s_1_reg; + output_ts_tod_ns_next = ts_tod_ns_1; + end + output_ts_fns_next = input_ts_rel; + output_ts_tag_next = input_ts_tag; + output_ts_valid_next = input_ts_valid; +end + +always @(posedge clk) begin + // extract data + if (dst_td_tvalid_reg) begin + if (dst_td_tid_reg[3:0] == 4'd0) begin + ts_sel_reg <= dst_td_tdata_reg[9]; + end + // current + if (dst_td_tid_reg == {4'd1, 4'd1}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd1, 4'd2}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd0, 4'd3}) begin + if (ts_sel_reg) begin + ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd0, 4'd4}) begin + if (ts_sel_reg) begin + ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd0, 4'd5}) begin + if (ts_sel_reg) begin + ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg; + end else begin + ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg; + end + end + // alternate + if (dst_td_tid_reg == {4'd2, 4'd1}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd2}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd3}) begin + if (ts_sel_reg) begin + ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd4}) begin + if (ts_sel_reg) begin + ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd5}) begin + if (ts_sel_reg) begin + ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg; + end else begin + ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg; + end + end + end + + output_ts_tod_s_reg <= output_ts_tod_s_next; + output_ts_tod_ns_reg <= output_ts_tod_ns_next; + output_ts_fns_reg <= output_ts_fns_next; + output_ts_tag_reg <= output_ts_tag_next; + output_ts_valid_reg <= output_ts_valid_next; + + if (rst) begin + output_ts_valid_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/syn/vivado/ptp_td_rel2tod.tcl b/syn/vivado/ptp_td_rel2tod.tcl new file mode 100644 index 00000000..ed741b11 --- /dev/null +++ b/syn/vivado/ptp_td_rel2tod.tcl @@ -0,0 +1,49 @@ +# Copyright (c) 2019-2024 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 ToD timestamp reconstruction module + +foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "ptp_td_rel2tod(__\w+__\d+)?" || + REF_NAME =~ "ptp_td_rel2tod(__\w+__\d+)?")}] { + puts "Inserting timing constraints for ptp_td_rel2tod instance $inst" + + # get clock periods + set input_clk [get_clocks -of_objects [get_pins "$inst/td_sync_reg_reg/C"]] + set output_clk [get_clocks -of_objects [get_pins "$inst/td_sync_sync1_reg_reg/C"]] + + set input_clk_period [if {[llength $input_clk]} {get_property -min PERIOD $input_clk} {expr 1.0}] + set output_clk_period [if {[llength $output_clk]} {get_property -min PERIOD $output_clk} {expr 1.0}] + + # TD data sync + set_property ASYNC_REG TRUE [get_cells -hier -regexp ".*/dst_td_(tdata|tid)_reg_reg(\\\[\\d+\\\])?" -filter "PARENT == $inst"] + + set_max_delay -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] -datapath_only $output_clk_period + set_bus_skew -from [get_cells "$inst/td_tdata_reg_reg[*]"] -to [get_cells "$inst/dst_td_tdata_reg_reg[*]"] $input_clk_period + set_max_delay -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] -datapath_only $output_clk_period + set_bus_skew -from [get_cells "$inst/td_tid_reg_reg[*]"] -to [get_cells "$inst/dst_td_tid_reg_reg[*]"] $input_clk_period + + set sync_ffs [get_cells -quiet -hier -regexp ".*/td_sync_sync\[12\]_reg_reg" -filter "PARENT == $inst"] + + if {[llength $sync_ffs]} { + set_property ASYNC_REG TRUE $sync_ffs + + set_max_delay -from [get_cells "$inst/td_sync_reg_reg"] -to [get_cells "$inst/td_sync_sync1_reg_reg"] -datapath_only $input_clk_period + } +} diff --git a/tb/ptp_td_rel2tod/Makefile b/tb/ptp_td_rel2tod/Makefile new file mode 100644 index 00000000..d23c1170 --- /dev/null +++ b/tb/ptp_td_rel2tod/Makefile @@ -0,0 +1,73 @@ +# Copyright (c) 2024 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_rel2tod +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES += ../../rtl/$(DUT).v + +# module parameters +export PARAM_TS_FNS_W := 16 +export PARAM_TS_REL_NS_W := 32 +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 diff --git a/tb/ptp_td_rel2tod/ptp_td.py b/tb/ptp_td_rel2tod/ptp_td.py new file mode 120000 index 00000000..fec11b65 --- /dev/null +++ b/tb/ptp_td_rel2tod/ptp_td.py @@ -0,0 +1 @@ +../ptp_td.py \ No newline at end of file diff --git a/tb/ptp_td_rel2tod/test_ptp_td_rel2tod.py b/tb/ptp_td_rel2tod/test_ptp_td_rel2tod.py new file mode 100644 index 00000000..d8b7ae6b --- /dev/null +++ b/tb/ptp_td_rel2tod/test_ptp_td_rel2tod.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2024 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 cocotbext.axi.stream import define_stream + +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] + + +TsBus, TsTransaction, TsSource, TsSink, TsMonitor = define_stream("Ts", + signals=["valid"], + optional_signals=["rel", "tod", "tag"] +) + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.ptp_clk, 6.4, units="ns").start()) + cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start()) + + self.ptp_td_source = PtpTdSource( + data=dut.ptp_td_sdi, + clock=dut.ptp_clk, + reset=dut.ptp_rst, + period_ns=6.4 + ) + + self.ts_source = TsSource(TsBus(dut, "input_ts"), dut.clk, dut.rst) + self.ts_sink = TsSink(TsBus(dut, "output_ts"), dut.clk, dut.rst) + + async def reset(self): + self.dut.ptp_rst.setimmediatevalue(0) + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.ptp_clk) + await RisingEdge(self.dut.ptp_clk) + self.dut.ptp_rst.value = 1 + self.dut.rst.value = 1 + for k in range(10): + await RisingEdge(self.dut.ptp_clk) + self.dut.ptp_rst.value = 0 + self.dut.rst.value = 0 + for k in range(10): + await RisingEdge(self.dut.ptp_clk) + + +@cocotb.test() +async def run_test(dut): + + tb = TB(dut) + + await tb.reset() + + for start_rel, start_tod in [('1234', '123456789.987654321'), + ('1234', '123456788.987654321'), + ('1234.9', '123456789.987654321'), + ('1234.9', '123456788.987654321'), + ('1234', '123456789.907654321'), + ('1234', '123456788.907654321'), + ('1234.9', '123456789.907654321'), + ('1234.9', '123456788.907654321')]: + + tb.log.info(f"Start rel ts: {start_rel} ns") + tb.log.info(f"Start ToD ts: {start_tod} ns") + + tb.ptp_td_source.set_ts_rel_s(start_rel) + tb.ptp_td_source.set_ts_tod_s(start_tod) + + for k in range(256*6): + await RisingEdge(dut.clk) + + for offset in ['0', '0.05', '-0.9']: + + tb.log.info(f"Offset {offset} sec") + ts_rel = tb.ptp_td_source.get_ts_rel_ns() + ts_tod = tb.ptp_td_source.get_ts_tod_ns() + + tb.log.info(f"Current rel ts: {ts_rel} ns") + tb.log.info(f"Current ToD ts: {ts_tod} ns") + + ts_rel += Decimal(offset).scaleb(9) + ts_tod += Decimal(offset).scaleb(9) + rel = int(ts_rel*2**16) & 0xffffffffffff + + tb.log.info(f"Input rel ts: {ts_rel} ns") + tb.log.info(f"Input ToD ts: {ts_tod} ns") + tb.log.info(f"Input relative ts raw: {rel} ({rel:#x})") + + await tb.ts_source.send(TsTransaction(rel=rel)) + out_ts = await tb.ts_sink.recv() + + tod = out_ts.tod.integer + tb.log.info(f"Output ToD ts raw: {tod} ({tod:#x})") + ns = Decimal(tod & 0xffff) / Decimal(2**16) + ns = tb.ptp_td_source.ctx.add(ns, Decimal((tod >> 16) & 0xffffffff)) + tod = tb.ptp_td_source.ctx.add(ns, Decimal(tod >> 48).scaleb(9)) + tb.log.info(f"Output ToD ts: {tod} ns") + + tb.log.info(f"Output ns portion only: {ns} ns") + + diff = tod - ts_tod + tb.log.info(f"Difference: {diff} ns") + + assert abs(diff) < 1e-3 + assert ns < 1000000000 + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) +lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib')) +axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl')) + + +def test_ptp_td_rel2tod(request): + dut = "ptp_td_rel2tod" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + ] + + parameters = {} + + parameters['TS_FNS_W'] = 16 + parameters['TS_REL_NS_W'] = 32 + 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, + )