diff --git a/rtl/ptp/taxi_ptp_td_rel2tod.sv b/rtl/ptp/taxi_ptp_td_rel2tod.sv new file mode 100644 index 0000000..ac17660 --- /dev/null +++ b/rtl/ptp/taxi_ptp_td_rel2tod.sv @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2024-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1fs +`default_nettype none + +/* + * PTP time distribution ToD timestamp reconstruction module + */ +module taxi_ptp_td_rel2tod # +( + parameter TS_FNS_W = 16, + parameter TS_REL_NS_W = 32, + parameter TS_TOD_S_W = 48, + parameter TS_REL_W = TS_REL_NS_W + TS_FNS_W, + parameter TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W, + parameter TD_SDI_PIPELINE = 2 +) +( + input wire logic clk, + input wire logic rst, + + /* + * PTP clock interface + */ + input wire logic ptp_clk, + input wire logic ptp_rst, + input wire logic ptp_td_sdi, + + /* + * Timestamp conversion + */ + taxi_axis_if.snk s_axis_ts_rel, + taxi_axis_if.src m_axis_ts_tod +); + +localparam TS_ID_W = s_axis_ts_rel.ID_W; +localparam TS_DEST_W = s_axis_ts_rel.DEST_W; +localparam TS_USER_W = s_axis_ts_rel.USER_W; + +localparam TS_TOD_NS_W = 30; +localparam TS_NS_W = TS_TOD_NS_W+1; + +localparam [30:0] NS_PER_S = 31'd1_000_000_000; + +// pipeline to facilitate long input path +wire ptp_td_sdi_pipe[0:TD_SDI_PIPELINE]; + +assign ptp_td_sdi_pipe[0] = ptp_td_sdi; + +for (genvar n = 0; n < TD_SDI_PIPELINE; n = n + 1) begin : pipe_stage + + (* shreg_extract = "no" *) + reg ptp_td_sdi_reg = 0; + + assign ptp_td_sdi_pipe[n+1] = ptp_td_sdi_reg; + + always_ff @(posedge ptp_clk) begin + ptp_td_sdi_reg <= ptp_td_sdi_pipe[n]; + end + +end + +// deserialize data +logic [15:0] td_shift_reg = '0; +logic [4:0] bit_cnt_reg = '0; +logic td_valid_reg = 1'b0; +logic [3:0] td_index_reg = '0; +logic [3:0] td_msg_reg = '0; + +logic [15:0] td_tdata_reg = '0; +logic td_tvalid_reg = 1'b0; +logic td_tlast_reg = 1'b0; +logic [7:0] td_tid_reg = '0; +logic td_sync_reg = 1'b0; + +always_ff @(posedge ptp_clk) begin + td_shift_reg <= {ptp_td_sdi_pipe[TD_SDI_PIPELINE], td_shift_reg[15:1]}; + + td_tvalid_reg <= 1'b0; + + if (bit_cnt_reg != 0) begin + bit_cnt_reg <= bit_cnt_reg - 1; + end else begin + td_valid_reg <= 1'b0; + if (td_valid_reg) begin + td_tdata_reg <= td_shift_reg; + td_tvalid_reg <= 1'b1; + td_tlast_reg <= ptp_td_sdi_pipe[TD_SDI_PIPELINE]; + td_tid_reg <= {td_msg_reg, td_index_reg}; + if (td_index_reg == 0) begin + td_msg_reg <= td_shift_reg[3:0]; + td_tid_reg[7:4] <= td_shift_reg[3:0]; + end + td_index_reg <= td_index_reg + 1; + td_sync_reg = !td_sync_reg; + end + if (ptp_td_sdi_pipe[TD_SDI_PIPELINE] == 0) begin + bit_cnt_reg <= 16; + td_valid_reg <= 1'b1; + end else begin + td_index_reg <= 0; + end + end + + if (ptp_rst) begin + bit_cnt_reg <= 0; + td_valid_reg <= 1'b0; + + td_tvalid_reg <= 1'b0; + end +end + +// sync TD data +logic [15:0] dst_td_tdata_reg = '0; +logic dst_td_tvalid_reg = 1'b0; +logic [7:0] dst_td_tid_reg = '0; + +(* shreg_extract = "no" *) +logic td_sync_sync1_reg = 1'b0; +(* shreg_extract = "no" *) +logic td_sync_sync2_reg = 1'b0; +(* shreg_extract = "no" *) +logic td_sync_sync3_reg = 1'b0; + +always_ff @(posedge clk) begin + td_sync_sync1_reg <= td_sync_reg; + td_sync_sync2_reg <= td_sync_sync1_reg; + td_sync_sync3_reg <= td_sync_sync2_reg; +end + +always_ff @(posedge clk) begin + dst_td_tvalid_reg <= 1'b0; + + if (td_sync_sync3_reg ^ td_sync_sync2_reg) begin + dst_td_tdata_reg <= td_tdata_reg; + dst_td_tvalid_reg <= 1'b1; + dst_td_tid_reg <= td_tid_reg; + end + + if (rst) begin + dst_td_tvalid_reg <= 1'b0; + end +end + +logic ts_sel_reg = 1'b0; + +logic [47:0] ts_tod_s_0_reg = '0; +logic [31:0] ts_tod_offset_ns_0_reg = '0; +logic [47:0] ts_tod_s_1_reg = '0; +logic [31:0] ts_tod_offset_ns_1_reg = '0; + +logic [TS_TOD_S_W-1:0] output_ts_tod_s_reg = '0, output_ts_tod_s_next; +logic [TS_TOD_NS_W-1:0] output_ts_tod_ns_reg = '0, output_ts_tod_ns_next; +logic [TS_FNS_W-1:0] output_ts_fns_reg = '0, output_ts_fns_next; +logic [TS_ID_W-1:0] output_ts_id_reg = '0, output_ts_id_next; +logic [TS_DEST_W-1:0] output_ts_dest_reg = '0, output_ts_dest_next; +logic [TS_USER_W-1:0] output_ts_user_reg = '0, output_ts_user_next; +logic output_ts_valid_reg = 1'b0, output_ts_valid_next; + +logic [TS_NS_W-1:0] ts_tod_ns_0; +logic [TS_NS_W-1:0] ts_tod_ns_1; + +assign s_axis_ts_rel.tready = 1'b1; + +assign m_axis_ts_tod.tdata = {output_ts_tod_s_reg, 2'b00, output_ts_tod_ns_reg, output_ts_fns_reg}; +assign m_axis_ts_tod.tkeep = '1; +assign m_axis_ts_tod.tstrb = m_axis_ts_tod.tkeep; +assign m_axis_ts_tod.tlast = 1'b1; +assign m_axis_ts_tod.tid = output_ts_id_reg; +assign m_axis_ts_tod.tdest = output_ts_dest_reg; +assign m_axis_ts_tod.tuser = output_ts_user_reg; +assign m_axis_ts_tod.tvalid = output_ts_valid_reg; + +always_comb begin + // reconstruct timestamp + // apply both offsets + ts_tod_ns_0 = TS_NS_W'(s_axis_ts_rel.tdata[TS_FNS_W +: TS_REL_NS_W] + ts_tod_offset_ns_0_reg); + ts_tod_ns_1 = TS_NS_W'(s_axis_ts_rel.tdata[TS_FNS_W +: TS_REL_NS_W] + ts_tod_offset_ns_1_reg); + + // pick the correct result + // 2 MSB clear = lower half of range (0-536,870,911) + // 1 MSB clear = upper half of range, but could also be over 1 billion (536,870,912-1,073,741,823) + // 1 MSB set = overflow or underflow + // prefer 2 MSB clear over 1 MSB clear if neither result was overflow or underflow + if (ts_tod_ns_0[30:29] == 0 || (ts_tod_ns_0[30] == 0 && ts_tod_ns_1[30:29] != 0)) begin + output_ts_tod_s_next = ts_tod_s_0_reg; + output_ts_tod_ns_next = ts_tod_ns_0[TS_TOD_NS_W-1:0]; + end else begin + output_ts_tod_s_next = ts_tod_s_1_reg; + output_ts_tod_ns_next = ts_tod_ns_1[TS_TOD_NS_W-1:0]; + end + output_ts_fns_next = s_axis_ts_rel.tdata[TS_FNS_W-1:0]; + output_ts_id_next = s_axis_ts_rel.tid; + output_ts_dest_next = s_axis_ts_rel.tdest; + output_ts_user_next = s_axis_ts_rel.tuser; + output_ts_valid_next = s_axis_ts_rel.tvalid; +end + +always_ff @(posedge clk) begin + // extract data + if (dst_td_tvalid_reg) begin + if (dst_td_tid_reg[3:0] == 4'd0) begin + ts_sel_reg <= dst_td_tdata_reg[9]; + end + // current + if (dst_td_tid_reg == {4'd1, 4'd1}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd1, 4'd2}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd0, 4'd3}) begin + if (ts_sel_reg) begin + ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd0, 4'd4}) begin + if (ts_sel_reg) begin + ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd0, 4'd5}) begin + if (ts_sel_reg) begin + ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg; + end else begin + ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg; + end + end + // alternate + if (dst_td_tid_reg == {4'd2, 4'd1}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_0_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_1_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd2}) begin + if (ts_sel_reg) begin + ts_tod_offset_ns_0_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_offset_ns_1_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd3}) begin + if (ts_sel_reg) begin + ts_tod_s_0_reg[15:0] <= dst_td_tdata_reg; + end else begin + ts_tod_s_1_reg[15:0] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd4}) begin + if (ts_sel_reg) begin + ts_tod_s_0_reg[31:16] <= dst_td_tdata_reg; + end else begin + ts_tod_s_1_reg[31:16] <= dst_td_tdata_reg; + end + end + if (dst_td_tid_reg == {4'd2, 4'd5}) begin + if (ts_sel_reg) begin + ts_tod_s_0_reg[47:32] <= dst_td_tdata_reg; + end else begin + ts_tod_s_1_reg[47:32] <= dst_td_tdata_reg; + end + end + end + + output_ts_tod_s_reg <= output_ts_tod_s_next; + output_ts_tod_ns_reg <= output_ts_tod_ns_next; + output_ts_fns_reg <= output_ts_fns_next; + output_ts_id_reg <= output_ts_id_next; + output_ts_dest_reg <= output_ts_dest_next; + output_ts_user_reg <= output_ts_user_next; + output_ts_valid_reg <= output_ts_valid_next; + + if (rst) begin + output_ts_valid_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/tb/ptp/taxi_ptp_td_rel2tod/Makefile b/tb/ptp/taxi_ptp_td_rel2tod/Makefile new file mode 100644 index 0000000..e64d0fb --- /dev/null +++ b/tb/ptp/taxi_ptp_td_rel2tod/Makefile @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0 +# +# Copyright (c) 2024-2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich + +TOPLEVEL_LANG = verilog + +SIM ?= verilator +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = taxi_ptp_td_rel2tod +COCOTB_TEST_MODULES = test_$(DUT) +COCOTB_TOPLEVEL = test_$(DUT) +MODULE = $(COCOTB_TEST_MODULES) +TOPLEVEL = $(COCOTB_TOPLEVEL) +VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv +VERILOG_SOURCES += ../../../rtl/ptp/$(DUT).sv +VERILOG_SOURCES += ../../../rtl/axis/taxi_axis_if.sv + +# handle file list files +process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1))) +process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f)) +uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1)) +VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES))) + +# module parameters +export PARAM_TS_FNS_W := 16 +export PARAM_TS_REL_NS_W := 32 +export PARAM_TS_TOD_S_W := 48 +export PARAM_TD_SDI_PIPELINE := 2 + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v))) +else ifeq ($(SIM), verilator) + COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v))) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + VERILATOR_TRACE = 1 + endif +endif + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/tb/ptp/taxi_ptp_td_rel2tod/ptp_td.py b/tb/ptp/taxi_ptp_td_rel2tod/ptp_td.py new file mode 120000 index 0000000..fec11b6 --- /dev/null +++ b/tb/ptp/taxi_ptp_td_rel2tod/ptp_td.py @@ -0,0 +1 @@ +../ptp_td.py \ No newline at end of file diff --git a/tb/ptp/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.py b/tb/ptp/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.py new file mode 100644 index 0000000..5d199cb --- /dev/null +++ b/tb/ptp/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: CERN-OHL-S-2.0 +""" + +Copyright (c) 2024-2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +""" + +import logging +import os +import sys +from decimal import Decimal + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge + +from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSource, AxiStreamSink + +try: + from ptp_td import PtpTdSource +except ImportError: + # attempt import from current directory + sys.path.insert(0, os.path.join(os.path.dirname(__file__))) + try: + from ptp_td import PtpTdSource + finally: + del sys.path[0] + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.ptp_clk, 6.4, units="ns").start()) + cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start()) + + self.ptp_td_source = PtpTdSource( + data=dut.ptp_td_sdi, + clock=dut.ptp_clk, + reset=dut.ptp_rst, + period_ns=6.4 + ) + + self.ts_source = AxiStreamSource(AxiStreamBus.from_entity(dut.s_axis_ts_rel), dut.clk, dut.rst) + self.ts_sink = AxiStreamSink(AxiStreamBus.from_entity(dut.m_axis_ts_tod), dut.clk, dut.rst) + + async def reset(self): + self.dut.ptp_rst.setimmediatevalue(0) + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.ptp_clk) + await RisingEdge(self.dut.ptp_clk) + self.dut.ptp_rst.value = 1 + self.dut.rst.value = 1 + for k in range(10): + await RisingEdge(self.dut.ptp_clk) + self.dut.ptp_rst.value = 0 + self.dut.rst.value = 0 + for k in range(10): + await RisingEdge(self.dut.ptp_clk) + + +@cocotb.test() +async def run_test(dut): + + tb = TB(dut) + + await tb.reset() + + for start_rel, start_tod in [ + ('1234', '123456789.987654321'), + ('1234', '123456788.987654321'), + ('1234.9', '123456789.987654321'), + ('1234.9', '123456788.987654321'), + ('1234', '123456789.907654321'), + ('1234', '123456788.907654321'), + ('1234.9', '123456789.907654321'), + ('1234.9', '123456788.907654321'), + ]: + + tb.log.info(f"Start rel ts: {start_rel} ns") + tb.log.info(f"Start ToD ts: {start_tod} ns") + + tb.ptp_td_source.set_ts_rel_s(start_rel) + tb.ptp_td_source.set_ts_tod_s(start_tod) + + for k in range(256*6): + await RisingEdge(dut.clk) + + for offset in ['0', '0.05', '-0.9']: + + tb.log.info(f"Offset {offset} sec") + ts_rel = tb.ptp_td_source.get_ts_rel_ns() + ts_tod = tb.ptp_td_source.get_ts_tod_ns() + + tb.log.info(f"Current rel ts: {ts_rel} ns") + tb.log.info(f"Current ToD ts: {ts_tod} ns") + + ts_rel += Decimal(offset).scaleb(9) + ts_tod += Decimal(offset).scaleb(9) + rel = int(ts_rel*2**16) & 0xffffffffffff + + tb.log.info(f"Input rel ts: {ts_rel} ns") + tb.log.info(f"Input ToD ts: {ts_tod} ns") + tb.log.info(f"Input relative ts raw: {rel} ({rel:#x})") + + await tb.ts_source.send(AxiStreamFrame(tdata=[rel], tid=0)) + out_ts = await tb.ts_sink.recv() + + tod = out_ts.tdata[0] + tb.log.info(f"Output ToD ts raw: {tod} ({tod:#x})") + ns = Decimal(tod & 0xffff) / Decimal(2**16) + ns = tb.ptp_td_source.ctx.add(ns, Decimal((tod >> 16) & 0xffffffff)) + tod = tb.ptp_td_source.ctx.add(ns, Decimal(tod >> 48).scaleb(9)) + tb.log.info(f"Output ToD ts: {tod} ns") + + tb.log.info(f"Output ns portion only: {ns} ns") + + diff = tod - ts_tod + tb.log.info(f"Difference: {diff} ns") + + assert abs(diff) < 1e-3 + assert ns < 1000000000 + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +# cocotb-test + +tests_dir = os.path.abspath(os.path.dirname(__file__)) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', '..', 'rtl')) + + +def process_f_files(files): + lst = {} + for f in files: + if f[-2:].lower() == '.f': + with open(f, 'r') as fp: + l = fp.read().split() + for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]): + lst[os.path.basename(f)] = f + else: + lst[os.path.basename(f)] = f + return list(lst.values()) + + +def test_taxi_ptp_td_rel2tod(request): + dut = "taxi_ptp_td_rel2tod" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = module + + verilog_sources = [ + os.path.join(tests_dir, f"{toplevel}.sv"), + os.path.join(rtl_dir, "ptp", f"{dut}.sv"), + os.path.join(rtl_dir, "axis", "taxi_axis_if.sv"), + ] + + verilog_sources = process_f_files(verilog_sources) + + parameters = {} + + parameters['TS_FNS_W'] = 16 + parameters['TS_REL_NS_W'] = 32 + parameters['TS_TOD_S_W'] = 48 + parameters['TD_SDI_PIPELINE'] = 2 + + extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} + + sim_build = os.path.join(tests_dir, "sim_build", + request.node.name.replace('[', '-').replace(']', '')) + + cocotb_test.simulator.run( + simulator="verilator", + python_search=[tests_dir], + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + parameters=parameters, + sim_build=sim_build, + extra_env=extra_env, + ) diff --git a/tb/ptp/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.sv b/tb/ptp/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.sv new file mode 100644 index 0000000..ad63564 --- /dev/null +++ b/tb/ptp/taxi_ptp_td_rel2tod/test_taxi_ptp_td_rel2tod.sv @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2025 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * PTP time distribution ToD timestamp reconstruction module testbench + */ +module test_taxi_ptp_td_rel2tod # +( + /* verilator lint_off WIDTHTRUNC */ + parameter TS_FNS_W = 16, + parameter TS_REL_NS_W = 32, + parameter TS_TOD_S_W = 48, + parameter TD_SDI_PIPELINE = 2, + parameter logic ID_EN = 1'b0, + parameter ID_W = 8, + parameter logic DEST_EN = 1'b0, + parameter DEST_W = 8, + parameter logic USER_EN = 1'b1, + parameter USER_W = 1 + /* verilator lint_on WIDTHTRUNC */ +) +(); + +localparam TS_REL_W = TS_REL_NS_W + TS_FNS_W; +localparam TS_TOD_W = TS_TOD_S_W + 32 + TS_FNS_W; + +logic clk; +logic rst; + +logic ptp_clk; +logic ptp_rst; +logic ptp_td_sdi; + +taxi_axis_if #( + .DATA_W(TS_REL_W), + .KEEP_EN(0), + .KEEP_W(1), + .STRB_EN(0), + .LAST_EN(0), + .ID_EN(ID_EN), + .ID_W(ID_W), + .DEST_EN(DEST_EN), + .DEST_W(DEST_W), + .USER_EN(USER_EN), + .USER_W(USER_W) +) s_axis_ts_rel(); + +taxi_axis_if #( + .DATA_W(TS_TOD_W), + .KEEP_EN(0), + .KEEP_W(1), + .STRB_EN(0), + .LAST_EN(0), + .ID_EN(ID_EN), + .ID_W(ID_W), + .DEST_EN(DEST_EN), + .DEST_W(DEST_W), + .USER_EN(USER_EN), + .USER_W(USER_W) +) m_axis_ts_tod(); + +taxi_ptp_td_rel2tod #( + .TS_FNS_W(TS_FNS_W), + .TS_REL_NS_W(TS_REL_NS_W), + .TS_TOD_S_W(TS_TOD_S_W), + .TS_REL_W(TS_REL_W), + .TS_TOD_W(TS_TOD_W), + .TD_SDI_PIPELINE(TD_SDI_PIPELINE) +) +uut ( + .clk(clk), + .rst(rst), + + /* + * PTP clock interface + */ + .ptp_clk(ptp_clk), + .ptp_rst(ptp_rst), + .ptp_td_sdi(ptp_td_sdi), + + /* + * Timestamp conversion + */ + .s_axis_ts_rel(s_axis_ts_rel), + .m_axis_ts_tod(m_axis_ts_tod) +); + +endmodule + +`resetall