diff --git a/fpga/lib/eth/README.md b/fpga/lib/eth/README.md index 9e7a73fe1..ac882ebab 100644 --- a/fpga/lib/eth/README.md +++ b/fpga/lib/eth/README.md @@ -289,8 +289,8 @@ nanoseconds field. ### ptp_clock_cdc module PTP clock CDC module with PPS output. Use this module to transfer and deskew a -free-running PTP clock across clock domains. Currently supports 96 bit -timestamps. +free-running PTP clock across clock domains. Supports both 64 and 96 bit +timestamp formats. ### ptp_ts_extract module diff --git a/fpga/lib/eth/rtl/ptp_clock_cdc.v b/fpga/lib/eth/rtl/ptp_clock_cdc.v index baac193a4..aa0a1395a 100644 --- a/fpga/lib/eth/rtl/ptp_clock_cdc.v +++ b/fpga/lib/eth/rtl/ptp_clock_cdc.v @@ -68,8 +68,8 @@ module ptp_clock_cdc # // bus width assertions initial begin - if (TS_WIDTH != 96) begin - $error("Error: Timestamp width must be 96"); + if (TS_WIDTH != 64 && TS_WIDTH != 96) begin + $error("Error: Timestamp width must be 64 or 96"); $finish; end end @@ -330,7 +330,7 @@ reg sec_mismatch_reg = 1'b0; reg diff_valid_reg = 1'b0; reg diff_offset_valid_reg = 1'b0; -reg [30:0] ts_ns_diff_reg = 31'd0; +reg [TS_NS_WIDTH+1-1:0] ts_ns_diff_reg = 31'd0; reg [FNS_WIDTH-1:0] ts_fns_diff_reg = 16'd0; reg [48:0] time_err_int_reg = 32'd0; @@ -342,45 +342,71 @@ always @(posedge output_clk) begin diff_offset_valid_reg <= 1'b0; // 96 bit timestamp - if (!ts_ns_ovf_reg[30]) begin - // if the overflow lookahead did not borrow, one second has elapsed - // increment seconds field, pre-compute both normal increment and overflow values - {ts_ns_inc_reg, ts_fns_inc_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg}; - {ts_ns_ovf_reg, ts_fns_ovf_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; - {ts_ns_reg, ts_fns_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg}; - ts_s_reg <= ts_s_reg + 1; - end else begin - // no increment seconds field, pre-compute both normal increment and overflow values - {ts_ns_inc_reg, ts_fns_inc_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg}; - {ts_ns_ovf_reg, ts_fns_ovf_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; - {ts_ns_reg, ts_fns_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg}; - ts_s_reg <= ts_s_reg; + if (TS_WIDTH == 96) begin + if (!ts_ns_ovf_reg[30]) begin + // if the overflow lookahead did not borrow, one second has elapsed + // increment seconds field, pre-compute both normal increment and overflow values + {ts_ns_inc_reg, ts_fns_inc_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg}; + {ts_ns_ovf_reg, ts_fns_ovf_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; + {ts_ns_reg, ts_fns_reg} <= {ts_ns_ovf_reg, ts_fns_ovf_reg}; + ts_s_reg <= ts_s_reg + 1; + end else begin + // no increment seconds field, pre-compute both normal increment and overflow values + {ts_ns_inc_reg, ts_fns_inc_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg}; + {ts_ns_ovf_reg, ts_fns_ovf_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg} + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; + {ts_ns_reg, ts_fns_reg} <= {ts_ns_inc_reg, ts_fns_inc_reg}; + ts_s_reg <= ts_s_reg; + end + end else if (TS_WIDTH == 64) begin + {ts_ns_reg, ts_fns_reg} <= {ts_ns_reg, ts_fns_reg} + {period_ns_reg, period_fns_reg}; end // FIFO dequeue if (read) begin // dequeue from FIFO - if (mem_read_data_reg[95:48] != ts_s_reg) begin - // seconds field doesn't match - if (!sec_mismatch_reg) begin - // ignore the first time - sec_mismatch_reg <= 1'b1; - end else begin - // two seconds mismatches in a row; step the clock - sec_mismatch_reg <= 1'b0; + if (TS_WIDTH == 96) begin + if (mem_read_data_reg[95:48] != ts_s_reg) begin + // seconds field doesn't match + if (!sec_mismatch_reg) begin + // ignore the first time + sec_mismatch_reg <= 1'b1; + end else begin + // two seconds mismatches in a row; step the clock + sec_mismatch_reg <= 1'b0; - {ts_ns_inc_reg, ts_fns_inc_reg} <= (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)) + {period_ns_reg, period_fns_reg}; - {ts_ns_ovf_reg, ts_fns_ovf_reg} <= (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)) + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; - ts_s_reg <= mem_read_data_reg[95:48]; - ts_ns_reg <= mem_read_data_reg[45:16]; - ts_fns_reg <= FNS_WIDTH > 16 ? mem_read_data_reg[15:0] << (FNS_WIDTH-16) : mem_read_data_reg[15:0] >> (16-FNS_WIDTH); - ts_step_reg <= 1; + {ts_ns_inc_reg, ts_fns_inc_reg} <= (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)) + {period_ns_reg, period_fns_reg}; + {ts_ns_ovf_reg, ts_fns_ovf_reg} <= (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)) + {period_ns_reg, period_fns_reg} - {31'd1_000_000_000, {FNS_WIDTH{1'b0}}}; + ts_s_reg <= mem_read_data_reg[95:48]; + ts_ns_reg <= mem_read_data_reg[45:16]; + ts_fns_reg <= FNS_WIDTH > 16 ? mem_read_data_reg[15:0] << (FNS_WIDTH-16) : mem_read_data_reg[15:0] >> (16-FNS_WIDTH); + ts_step_reg <= 1; + end + end else begin + // compute difference + sec_mismatch_reg <= 1'b0; + diff_valid_reg <= 1'b1; + {ts_ns_diff_reg, ts_fns_diff_reg} <= {ts_ns_reg, ts_fns_reg} - (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)); + end + end else if (TS_WIDTH == 64) begin + if (mem_read_data_reg[63:48] != ts_ns_reg[47:32]) begin + // high-order bits don't match + if (!sec_mismatch_reg) begin + // ignore the first time + sec_mismatch_reg <= 1'b1; + end else begin + // two seconds mismatches in a row; step the clock + sec_mismatch_reg <= 1'b0; + + ts_ns_reg <= mem_read_data_reg[63:16]; + ts_fns_reg <= FNS_WIDTH > 16 ? mem_read_data_reg[15:0] << (FNS_WIDTH-16) : mem_read_data_reg[15:0] >> (16-FNS_WIDTH); + ts_step_reg <= 1; + end + end else begin + // compute difference + sec_mismatch_reg <= 1'b0; + diff_valid_reg <= 1'b1; + {ts_ns_diff_reg, ts_fns_diff_reg} <= {ts_ns_reg, ts_fns_reg} - (FNS_WIDTH > 16 ? mem_read_data_reg[63:0] << (FNS_WIDTH-16) : mem_read_data_reg[63:0] >> (16-FNS_WIDTH)); end - end else begin - // compute difference - sec_mismatch_reg <= 1'b0; - diff_valid_reg <= 1'b1; - {ts_ns_diff_reg, ts_fns_diff_reg} <= {ts_ns_reg, ts_fns_reg} - (FNS_WIDTH > 16 ? mem_read_data_reg[45:0] << (FNS_WIDTH-16) : mem_read_data_reg[45:0] >> (16-FNS_WIDTH)); end end else if (diff_valid_reg) begin // offset difference by FIFO delay @@ -404,7 +430,11 @@ always @(posedge output_clk) begin end end - pps_reg <= !ts_ns_ovf_reg[30]; + if (TS_WIDTH == 96) begin + pps_reg <= !ts_ns_ovf_reg[30]; + end else if (TS_WIDTH == 64) begin + pps_reg <= 1'b0; // not currently implemented for 64 bit timestamp format + end if (output_rst) begin period_ns_reg <= OUTPUT_PERIOD_NS; @@ -414,7 +444,7 @@ always @(posedge output_clk) begin ts_fns_reg <= 0; ts_ns_inc_reg <= 0; ts_fns_inc_reg <= 0; - ts_ns_ovf_reg <= 31'h7fffffff; + ts_ns_ovf_reg <= {TS_NS_WIDTH{1'b1}}; ts_fns_ovf_reg <= {FNS_WIDTH{1'b1}}; ts_step_reg <= 0; pps_reg <= 0; diff --git a/fpga/lib/eth/tb/test_ptp_clock_cdc_64.py b/fpga/lib/eth/tb/test_ptp_clock_cdc_64.py new file mode 100755 index 000000000..79746c69a --- /dev/null +++ b/fpga/lib/eth/tb/test_ptp_clock_cdc_64.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2019 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. + +""" + +from myhdl import * +import os + +import ptp + +module = 'ptp_clock_cdc' +testbench = 'test_%s_64' % module + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def bench(): + + # Parameters + TS_WIDTH = 64 + NS_WIDTH = 4 + FNS_WIDTH = 16 + INPUT_PERIOD_NS = 0x6 + INPUT_PERIOD_FNS = 0x6666 + OUTPUT_PERIOD_NS = 0x6 + OUTPUT_PERIOD_FNS = 0x6666 + USE_SAMPLE_CLOCK = 1 + LOG_FIFO_DEPTH = 3 + LOG_RATE = 3 + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + input_clk = Signal(bool(0)) + input_rst = Signal(bool(0)) + output_clk = Signal(bool(0)) + output_rst = Signal(bool(0)) + sample_clk = Signal(bool(0)) + input_ts = Signal(intbv(0)[96:]) + + # Outputs + output_ts = Signal(intbv(0)[96:]) + output_ts_step = Signal(bool(0)) + output_pps = Signal(bool(0)) + + # PTP clock + ptp_clock = ptp.PtpClock() + + ptp_logic = ptp_clock.create_logic( + input_clk, + input_rst, + ts_64=input_ts + ) + + # DUT + if os.system(build_cmd): + raise Exception("Error running build command") + + dut = Cosimulation( + "vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + input_clk=input_clk, + input_rst=input_rst, + output_clk=output_clk, + output_rst=output_rst, + sample_clk=sample_clk, + input_ts=input_ts, + output_ts=output_ts, + output_ts_step=output_ts_step, + output_pps=output_pps + ) + + @always(delay(3200)) + def clkgen(): + clk.next = not clk + input_clk.next = not input_clk + + output_clk_hp = Signal(int(3200)) + + @instance + def clkgen_output(): + while True: + yield delay(int(output_clk_hp)) + output_clk.next = not output_clk + + @always(delay(5000)) + def clkgen_sample(): + sample_clk.next = not sample_clk + + @instance + def check(): + yield delay(100000) + yield clk.posedge + rst.next = 1 + input_rst.next = 1 + output_rst.next = 1 + yield clk.posedge + yield clk.posedge + yield clk.posedge + input_rst.next = 0 + output_rst.next = 0 + yield clk.posedge + yield delay(100000) + yield clk.posedge + + # testbench stimulus + + yield clk.posedge + print("test 1: Same clock speed") + current_test.next = 1 + + yield clk.posedge + + for i in range(20000): + yield clk.posedge + + input_stop_ts = input_ts/2**16*1e-9 + output_stop_ts = output_ts/2**16*1e-9 + + print(input_stop_ts-output_stop_ts) + + assert abs(input_stop_ts-output_stop_ts) < 1e-8 + + yield delay(100000) + + yield clk.posedge + print("test 2: Slightly faster") + current_test.next = 2 + + output_clk_hp.next = 3100 + + yield clk.posedge + + for i in range(20000): + yield clk.posedge + + input_stop_ts = input_ts/2**16*1e-9 + output_stop_ts = output_ts/2**16*1e-9 + + print(input_stop_ts-output_stop_ts) + + assert abs(input_stop_ts-output_stop_ts) < 1e-8 + + yield delay(100000) + + yield clk.posedge + print("test 3: Slightly slower") + current_test.next = 3 + + output_clk_hp.next = 3300 + + yield clk.posedge + + for i in range(20000): + yield clk.posedge + + input_stop_ts = input_ts/2**16*1e-9 + output_stop_ts = output_ts/2**16*1e-9 + + print(input_stop_ts-output_stop_ts) + + assert abs(input_stop_ts-output_stop_ts) < 1e-8 + + yield delay(100000) + + yield clk.posedge + print("test 4: Significantly faster") + current_test.next = 4 + + output_clk_hp.next = 2000 + + yield clk.posedge + + for i in range(20000): + yield clk.posedge + + input_stop_ts = input_ts/2**16*1e-9 + output_stop_ts = output_ts/2**16*1e-9 + + print(input_stop_ts-output_stop_ts) + + assert abs(input_stop_ts-output_stop_ts) < 1e-8 + + yield delay(100000) + + yield clk.posedge + print("test 5: Significantly slower") + current_test.next = 5 + + output_clk_hp.next = 5000 + + yield clk.posedge + + for i in range(30000): + yield clk.posedge + + input_stop_ts = input_ts/2**16*1e-9 + output_stop_ts = output_ts/2**16*1e-9 + + print(input_stop_ts-output_stop_ts) + + assert abs(input_stop_ts-output_stop_ts) < 1e-8 + + yield delay(100000) + + raise StopSimulation + + return instances() + +def test_bench(): + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() diff --git a/fpga/lib/eth/tb/test_ptp_clock_cdc_64.v b/fpga/lib/eth/tb/test_ptp_clock_cdc_64.v new file mode 100644 index 000000000..777d12cd8 --- /dev/null +++ b/fpga/lib/eth/tb/test_ptp_clock_cdc_64.v @@ -0,0 +1,111 @@ +/* + +Copyright (c) 2019 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 + +`timescale 1ps / 1fs + +/* + * Testbench for ptp_clock_cdc + */ +module test_ptp_clock_cdc_64; + +// Parameters +parameter TS_WIDTH = 64; +parameter NS_WIDTH = 4; +parameter FNS_WIDTH = 16; +parameter INPUT_PERIOD_NS = 4'h6; +parameter INPUT_PERIOD_FNS = 16'h6666; +parameter OUTPUT_PERIOD_NS = 4'h6; +parameter OUTPUT_PERIOD_FNS = 16'h6666; +parameter USE_SAMPLE_CLOCK = 1; +parameter LOG_FIFO_DEPTH = 3; +parameter LOG_RATE = 3; + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg input_clk = 0; +reg input_rst = 0; +reg output_clk = 0; +reg output_rst = 0; +reg sample_clk = 0; +reg [TS_WIDTH-1:0] input_ts = 0; + +// Outputs +wire [TS_WIDTH-1:0] output_ts; +wire output_ts_step; +wire output_pps; + +initial begin + // myhdl integration + $from_myhdl( + clk, + rst, + current_test, + input_clk, + input_rst, + output_clk, + output_rst, + sample_clk, + input_ts + ); + $to_myhdl( + output_ts, + output_ts_step, + output_pps + ); + + // dump file + $dumpfile("test_ptp_clock_cdc_64.lxt"); + $dumpvars(0, test_ptp_clock_cdc_64); +end + +ptp_clock_cdc #( + .TS_WIDTH(TS_WIDTH), + .NS_WIDTH(NS_WIDTH), + .FNS_WIDTH(FNS_WIDTH), + .INPUT_PERIOD_NS(INPUT_PERIOD_NS), + .INPUT_PERIOD_FNS(INPUT_PERIOD_FNS), + .OUTPUT_PERIOD_NS(OUTPUT_PERIOD_NS), + .OUTPUT_PERIOD_FNS(OUTPUT_PERIOD_FNS), + .USE_SAMPLE_CLOCK(USE_SAMPLE_CLOCK), + .LOG_FIFO_DEPTH(LOG_FIFO_DEPTH), + .LOG_RATE(LOG_RATE) +) +UUT ( + .input_clk(input_clk), + .input_rst(input_rst), + .output_clk(output_clk), + .output_rst(output_rst), + .sample_clk(sample_clk), + .input_ts(input_ts), + .output_ts(output_ts), + .output_ts_step(output_ts_step), + .output_pps(output_pps) +); + +endmodule