From c6578eda2ba482b87583ebd989cdf5cbd5c3f672 Mon Sep 17 00:00:00 2001 From: Wade Fife Date: Tue, 13 Oct 2020 16:52:34 -0500 Subject: fpga: e320: Improve timing on LVDS interface --- fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v | 260 ++++--- .../cat_io_lvds/cat_io_lvds_dual_mode_tb.v | 780 -------------------- .../io_cap_gen/cat_io_lvds_dual_mode_tb/Makefile | 77 ++ .../cat_io_lvds_dual_mode_tb.sv | 805 +++++++++++++++++++++ 4 files changed, 1053 insertions(+), 869 deletions(-) delete mode 100644 fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_dual_mode_tb.v create mode 100644 fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/Makefile create mode 100644 fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/cat_io_lvds_dual_mode_tb.sv (limited to 'fpga/usrp3/lib') diff --git a/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v b/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v index 13b6ae1f9..f49cf58d2 100644 --- a/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v +++ b/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v @@ -7,15 +7,15 @@ // // Description: // -// This is an LVDS interface for the AD9361 (Catalina). It uses the cat_io_lvds -// module to implement the interface, but supports both 1R1T and 2R2T timing -// modes while using full LVDS bandwidth. That is, it can support 1R1T at twice +// This is an LVDS interface for the AD9361 (Catalina). It uses the cat_io_lvds +// module to implement the interface, but supports both 1R1T and 2R2T timing +// modes while using full LVDS bandwidth. That is, it can support 1R1T at twice // the sample rate of 2R2T. // -// This is controlled by the a_mimo control signal. When MIMO = 0 (1R1T mode), -// the radio_clk frequency equals that of rx_clk/2 and the data is output to -// both radio channels. If MIMO = 1 (2R2T), the frequency of radio_clk equals -// rx_clk/4 and the data stream is split between channel 0 and channel 1. This is used +// This is controlled by the a_mimo control signal. When MIMO = 0 (1R1T mode), +// the radio_clk frequency equals that of rx_clk/2 and the data is output to +// both radio channels. If MIMO = 1 (2R2T), the frequency of radio_clk equals +// rx_clk/4 and the data stream is split between channel 0 and channel 1. This is used // for 2R2T mode. // @@ -37,7 +37,6 @@ module cat_io_lvds_dual_mode #( parameter OUTPUT_CLOCK_DELAY = 16, parameter OUTPUT_DATA_DELAY = 0 ) ( - input rst, input clk200, // Data and frame timing (asynchronous, glitch free) @@ -51,18 +50,19 @@ module cat_io_lvds_dual_mode #( input ctrl_ld_in_data_delay, input ctrl_ld_in_clk_delay, input [4:0] ctrl_out_data_delay, - input [4:0] ctrl_out_clk_delay, + input [4:0] ctrl_out_clk_delay, input ctrl_ld_out_data_delay, input ctrl_ld_out_clk_delay, // Baseband sample interface + input radio_rst, // Glitch-free, synchronous to radio_clk output radio_clk, // - output reg rx_aligned, - output reg [11:0] rx_i0, - output reg [11:0] rx_q0, - output reg [11:0] rx_i1, - output reg [11:0] rx_q1, + output reg rx_aligned, + output [11:0] rx_i0, + output [11:0] rx_q0, + output [11:0] rx_i1, + output [11:0] rx_q1, // input [11:0] tx_i0, input [11:0] tx_q0, @@ -84,7 +84,7 @@ module cat_io_lvds_dual_mode #( output [5:0] tx_d_p, output [5:0] tx_d_n ); - + wire radio_clk_1x; // rx_clk_p divided by 4 wire radio_clk_2x; // rx_clk_p divided by 2 @@ -115,7 +115,8 @@ module cat_io_lvds_dual_mode #( // Clock Mux //--------------------------------------------------------------------------- - // Use radio_clk_1x when MIMO = 1, radio_clk_2x when MIMO = 0 + // Select the source for radio_clk. Use radio_clk_1x when MIMO = 1 or + // radio_clk_2x when MIMO = 0. BUFGCTRL BUFGCTRL_radio_clk ( .I0 (radio_clk_1x), .I1 (radio_clk_2x), @@ -146,7 +147,7 @@ module cat_io_lvds_dual_mode #( // // align_2x ______|‾‾‾‾‾‾‾‾‾‾‾|___________|‾‾‾‾‾‾‾‾‾‾‾|___________| // - // These two alignment signals allow us to tell where in the frame period we + // These two alignment signals allow us to tell where in the frame period we // are so that we can deserialize in the correct order. // //--------------------------------------------------------------------------- @@ -161,7 +162,7 @@ module cat_io_lvds_dual_mode #( always @(posedge radio_clk_2x) begin - // Align data capture to 1x clock so that we stay in sync with data. + // Align data capture to 1x clock so that we stay in sync with data. // Otherwise, the data might be serialized in the wrong order. align_2x <= align_1x; end @@ -171,12 +172,20 @@ module cat_io_lvds_dual_mode #( // Rx MIMO/SISO Serialization //--------------------------------------------------------------------------- // - // This block of code takes the dual outputs when in SISO mode and serializes - // them. Because we use the 2x clock when in SISO mode, this allows us to + // This block of code takes the dual outputs when in SISO mode and serializes + // them. Because we use the 2x clock when in SISO mode, this allows us to // double the data rate when using a single channel. // //--------------------------------------------------------------------------- + wire rx_aligned_t; + reg rx_aligned_reg; + + wire [11:0] rx_i0_t; + wire [11:0] rx_q0_t; + wire [11:0] rx_i1_t; + wire [11:0] rx_q1_t; + reg [11:0] rx_i0_ser; reg [11:0] rx_q0_ser; reg [11:0] rx_i1_ser; @@ -187,19 +196,21 @@ module cat_io_lvds_dual_mode #( reg [11:0] rx_i1_out; reg [11:0] rx_q1_out; + reg rx_out_val; + always @(posedge radio_clk_2x) begin - rx_aligned <= rx_aligned_t; + rx_aligned_reg <= rx_aligned_t; if (align_1x ^ align_2x) begin - // This clock cycle corresponds to the first 1x cycle in which two + // This clock cycle corresponds to the first 1x cycle in which two // samples are output, so grab data from port 0. rx_i0_ser <= rx_i0_t; rx_q0_ser <= rx_q0_t; rx_i1_ser <= rx_i0_t; rx_q1_ser <= rx_q0_t; end else begin - // This radio_clk_2x cycle corresponds to the second 1x cycle in which + // This radio_clk_2x cycle corresponds to the second 1x cycle in which // two samples are output, so grab data from port 1. rx_i0_ser <= rx_i1_t; rx_q0_ser <= rx_q1_t; @@ -209,11 +220,17 @@ module cat_io_lvds_dual_mode #( // Select the correct Rx output based on MIMO setting if (r_mimo) begin + // In MIMO mode, we get new data for both channels every other + // radio_clk_2x clock cycle. + rx_out_val <= ~rx_out_val; rx_i0_out <= rx_i0_t; rx_q0_out <= rx_q0_t; rx_i1_out <= rx_i1_t; rx_q1_out <= rx_q1_t; end else begin + // In SISO mode, we get new data for one channel on every radio_clk_2x + // cock cycle. + rx_out_val <= 1'b1; rx_i0_out <= rx_i0_ser; rx_q0_out <= rx_q0_ser; rx_i1_out <= rx_i1_ser; @@ -223,35 +240,106 @@ module cat_io_lvds_dual_mode #( //--------------------------------------------------------------------------- - // Synchronize Rx to radio_clk Domain + // Cross RX Data from radio_clk_2x to radio_clk Domain //--------------------------------------------------------------------------- // - // This crosses the radio data from the radio_clk_1x domain to the radio_clk - // domain. We use the falling edge of radio_clk to allow for the BUFG - // insertion delay. + // The clocks are synchronous and the data input rate matches the data + // output rate, so this FIFO should never overflow or underflow once it is + // primed and starts being read. // //--------------------------------------------------------------------------- - reg [11:0] rx_i0_fall; - reg [11:0] rx_q0_fall; - reg [11:0] rx_i1_fall; - reg [11:0] rx_q1_fall; - - always @(negedge radio_clk) - begin - rx_i0_fall <= rx_i0_out; - rx_q0_fall <= rx_q0_out; - rx_i1_fall <= rx_i1_out; - rx_q1_fall <= rx_q1_out; + wire rx_fifo_full; + wire rx_fifo_empty; + reg rx_fifo_rd_en; + + fifo_short_2clk fifo_short_2clk_rx ( + .rst (radio_rst), // Asynchronous reset input + .wr_clk (radio_clk_2x), + .rd_clk (radio_clk), + .din ({rx_i1_out, rx_q1_out, rx_i0_out, rx_q0_out}), + .wr_en (rx_out_val & ~rx_fifo_full & rx_aligned_reg), + .rd_en (rx_fifo_rd_en & ~rx_fifo_empty), + .dout ({rx_i1, rx_q1, rx_i0, rx_q0 }), + .full (rx_fifo_full), + .empty (rx_fifo_empty), + .rd_data_count (), + .wr_data_count () + ); + + // Wait until the FIFO is partially filled before we start reading out data. + // Go back to waiting if the FIFO empties. + always @(posedge radio_clk) begin + if (radio_rst) begin + rx_fifo_rd_en <= 1'b0; + rx_aligned <= 1'b0; + end else begin + if (!rx_fifo_empty) begin + rx_fifo_rd_en <= 1'b1; + rx_aligned <= 1'b1; + end else if (rx_fifo_empty) begin + rx_fifo_rd_en <= 1'b0; + rx_aligned <= 1'b0; + end + end end - // Re-clock data on the rising edge to present the whole period to external IP - always @(posedge radio_clk) - begin - rx_i0 <= rx_i0_fall; - rx_q0 <= rx_q0_fall; - rx_i1 <= rx_i1_fall; - rx_q1 <= rx_q1_fall; + + //--------------------------------------------------------------------------- + // Cross TX Data from radio_clk domain to radio_clk_2x Domain + //--------------------------------------------------------------------------- + // + // The clocks are synchronous and the data input rate matches the data + // output rate, so this FIFO should never overflow or underflow once it is + // primed and starts being read. + // + //--------------------------------------------------------------------------- + + // Cross the radio_rst to radio_clk_2x + synchronizer #( + .INITIAL_VAL (1'b1) + ) synchronizer_radio_rst_2x ( + .clk (radio_clk_2x), + .rst (1'b0), + .in (radio_rst), + .out (radio_rst_2x) + ); + + wire [11:0] tx_i0_del0; + wire [11:0] tx_q0_del0; + wire [11:0] tx_i1_del0; + wire [11:0] tx_q1_del0; + + wire tx_fifo_full; + wire tx_fifo_empty; + reg tx_fifo_rd_en; + + fifo_short_2clk fifo_short_2clk_tx ( + .rst (radio_rst), // Asynchronous reset input + .wr_clk (radio_clk), + .rd_clk (radio_clk_2x), + .din ({tx_i1, tx_q1, tx_i0, tx_q0}), + .wr_en (~tx_fifo_full), + .rd_en (tx_fifo_rd_en & ~tx_fifo_empty), + .dout ({tx_i1_del0, tx_q1_del0, tx_i0_del0, tx_q0_del0}), + .full (tx_fifo_full), + .empty (tx_fifo_empty), + .rd_data_count (), + .wr_data_count () + ); + + // Wait until the FIFO is partially filled before we start reading out data. + // Go back to waiting if the FIFO empties. + always @(posedge radio_clk_2x) begin + if (radio_rst_2x) begin + tx_fifo_rd_en <= 1'b0; + end else begin + if (!tx_fifo_empty) begin + tx_fifo_rd_en <= 1'b1; + end else if (tx_fifo_empty) begin + tx_fifo_rd_en <= 1'b0; + end + end end @@ -259,51 +347,56 @@ module cat_io_lvds_dual_mode #( // Tx MIMO/SISO Deserialization //--------------------------------------------------------------------------- // - // This block of code takes the serialized output from the radios and - // parallelizes it onto the two radio ports of the Catalina interface. It - // also takes the radio data, output on the radio_clk domain, and crosses it + // This block of code takes the serialized output from the radios and + // parallelizes it onto the two radio ports of the Catalina interface. It + // also takes the radio data, output on the radio_clk domain, and crosses it // to the radio_clk_1x domain. // //--------------------------------------------------------------------------- - reg [11:0] tx_i0_del; - reg [11:0] tx_q0_del; - reg [11:0] tx_i1_del; - reg [11:0] tx_q1_del; + reg [11:0] tx_i0_del1; + reg [11:0] tx_q0_del1; + reg [11:0] tx_i1_del1; + reg [11:0] tx_q1_del1; + + reg [11:0] tx_i0_t; + reg [11:0] tx_q0_t; + reg [11:0] tx_i1_t; + reg [11:0] tx_q1_t; always @(posedge radio_clk_2x) begin - // Capture copy of the data delayed by one radio_clk_2c cycle. - tx_i0_del <= tx_i0; - tx_q0_del <= tx_q0; - tx_i1_del <= tx_i1; - tx_q1_del <= tx_q1; + // Capture copy of the data delayed by one radio_clk_2x cycle. + tx_i0_del1 <= tx_i0_del0; + tx_q0_del1 <= tx_q0_del0; + tx_i1_del1 <= tx_i1_del0; + tx_q1_del1 <= tx_q1_del0; end always @(posedge radio_clk_1x) begin if (r_mimo) begin - // In MIMO mode, radio_clk is radio_clk_1x, so we just capture the same + // In MIMO mode, radio_clk is radio_clk_1x, so we just capture the same // data for each radio_clk_1x cycle. - tx_i0_t <= tx_i0; - tx_q0_t <= tx_q0; - tx_i1_t <= tx_i1; - tx_q1_t <= tx_q1; + tx_i0_t <= tx_i0_del0; + tx_q0_t <= tx_q0_del0; + tx_i1_t <= tx_i1_del0; + tx_q1_t <= tx_q1_del0; end else begin - // In SISO mode, data is updated every radio_clk_2x cycle, so we output - // the data from the previous radio_clk_2x cycle onto channel 0 and the - // data from the current radio_clk_2x cycle onto channel 1. This puts the + // In SISO mode, data is updated every radio_clk_2x cycle, so we output + // the data from the previous radio_clk_2x cycle onto channel 0 and the + // data from the current radio_clk_2x cycle onto channel 1. This puts the // data in the correct order when in 1R1T mode. if (r_tx_ch == 0) begin - tx_i0_t <= tx_i0_del; - tx_q0_t <= tx_q0_del; - tx_i1_t <= tx_i0; - tx_q1_t <= tx_q0; + tx_i0_t <= tx_i0_del1; + tx_q0_t <= tx_q0_del1; + tx_i1_t <= tx_i0_del0; + tx_q1_t <= tx_q0_del0; end else begin - tx_i0_t <= tx_i1_del; - tx_q0_t <= tx_q1_del; - tx_i1_t <= tx_i1; - tx_q1_t <= tx_q1; + tx_i0_t <= tx_i1_del1; + tx_q0_t <= tx_q1_del1; + tx_i1_t <= tx_i1_del0; + tx_q1_t <= tx_q1_del0; end end end @@ -313,17 +406,6 @@ module cat_io_lvds_dual_mode #( // Catalina TX/RX Interface //--------------------------------------------------------------------------- - wire rx_aligned_t; - wire [11:0] rx_i0_t; - wire [11:0] rx_q0_t; - wire [11:0] rx_i1_t; - wire [11:0] rx_q1_t; - - reg [11:0] tx_i0_t; - reg [11:0] tx_q0_t; - reg [11:0] tx_i1_t; - reg [11:0] tx_q1_t; - cat_io_lvds #( .INVERT_FRAME_RX (0), .INVERT_DATA_RX (6'b00_0000), @@ -343,13 +425,13 @@ module cat_io_lvds_dual_mode #( .OUTPUT_DATA_DELAY (OUTPUT_DATA_DELAY), .USE_BUFG (0) ) cat_io_lvds_i0 ( - .rst (rst), + .rst (radio_rst), .clk200 (clk200), - + // Data and frame timing .mimo (1), // Set to 1 to always return all samples .frame_sample (~r_mimo), // Frame timing corresponds to SISO/MIMO setting - + // Delay control interface .ctrl_clk (ctrl_clk), // @@ -362,7 +444,7 @@ module cat_io_lvds_dual_mode #( .ctrl_out_clk_delay (ctrl_out_clk_delay), .ctrl_ld_out_data_delay (ctrl_ld_out_data_delay), .ctrl_ld_out_clk_delay (ctrl_ld_out_clk_delay), - + // Baseband sample interface .radio_clk (radio_clk_1x), .radio_clk_2x (radio_clk_2x), @@ -377,7 +459,7 @@ module cat_io_lvds_dual_mode #( .tx_q0 (tx_q0_t), .tx_i1 (tx_i1_t), .tx_q1 (tx_q1_t), - + // Catalina interface .rx_clk_p (rx_clk_p), .rx_clk_n (rx_clk_n), diff --git a/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_dual_mode_tb.v b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_dual_mode_tb.v deleted file mode 100644 index ecb744086..000000000 --- a/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_dual_mode_tb.v +++ /dev/null @@ -1,780 +0,0 @@ -// -// Copyright 2016 Ettus Research, A National Instruments Company -// -// SPDX-License-Identifier: LGPL-3.0-or-later -// -// Module: cat_io_lvds_dual_mode_tb -// -// Description: Testbench for cat_io_lvds_dual_mode. -// - -`timescale 1ns/1ps - -module cat_io_lvds_dual_mode_tb(); - - localparam CLK_PERIOD = 10; - localparam CLK200_PERIOD = 2.5; - - localparam USE_CLOCK_IDELAY = 1; - localparam USE_DATA_IDELAY = 1; - localparam DATA_IDELAY_MODE = "FIXED"; - localparam CLOCK_IDELAY_MODE = "FIXED"; - localparam INPUT_CLOCK_DELAY = 16; - localparam INPUT_DATA_DELAY = 0; - localparam USE_CLOCK_ODELAY = 1; - localparam USE_DATA_ODELAY = 1; - localparam DATA_ODELAY_MODE = "FIXED"; - localparam CLOCK_ODELAY_MODE = "FIXED"; - localparam OUTPUT_CLOCK_DELAY = 31; - localparam OUTPUT_DATA_DELAY = 0; - - reg [8*19:0] test_status; - reg check_enabled; // Controls when output checking is performed - - reg clk = 0; - reg rx_clk = 0; - reg clk200 = 0; - - reg reset; - reg mimo; - reg tx_ch; - reg [5:0] rx_d; - reg rx_frame; - reg [7:0] rx_count = 0; - - // Each channel's data begins with a unique identifier (A../B.. or C../D..) - // followed by a count, which should always be sequential. - wire [11:0] i0 = { 4'hA, rx_count }; - wire [11:0] q0 = { 4'hB, rx_count }; - wire [11:0] i1 = { 4'hC, rx_count }; - wire [11:0] q1 = { 4'hD, rx_count }; - - wire radio_clk; - - reg [11:0] tx_i0; - reg [11:0] tx_q0; - reg [11:0] tx_i1; - reg [11:0] tx_q1; - - wire [11:0] rx_i0; - wire [11:0] rx_q0; - wire [11:0] rx_i1; - wire [11:0] rx_q1; - - wire rx_aligned; - - wire tx_clk_p, tx_clk_n; - wire tx_frame_p, tx_frame_n; - wire [5:0] tx_d_p, tx_d_n; - - reg [4:0] ctrl_in_data_delay; - reg [4:0] ctrl_in_clk_delay; - reg ctrl_ld_in_data_delay; - reg ctrl_ld_in_clk_delay; - - reg [4:0] ctrl_out_data_delay; - reg [4:0] ctrl_out_clk_delay; - reg ctrl_ld_out_data_delay; - reg ctrl_ld_out_clk_delay; - - - //--------------------------------------------------------------------------- - // Clock Generation - //--------------------------------------------------------------------------- - - // IODELAYCTRL reference clock - always #(CLK200_PERIOD) clk200 = ~clk200; - - // Create an internal clock we'll use to drive the data - always #(CLK_PERIOD) clk = ~clk; - - // RF interface clock. Half the rate of clk and out of phase - always @(negedge clk) rx_clk <= ~rx_clk; - - - //--------------------------------------------------------------------------- - // Tasks - //--------------------------------------------------------------------------- - - // Output a single burst of 2*len samples. In MIMO mode, this consists of len - // samples on each channel. In SISO mode, this consists of 2*len samples on - // the same channel. - task Burst; - input [31:0] len; - input do_mimo; - begin - repeat(len) - begin - mimo <= do_mimo; - - // Channel 0 sample - @(posedge clk); - rx_d <= i0[11:6]; - rx_frame <= 1; - @(posedge clk); - rx_d <= q0[11:6]; - rx_frame <= 1; - @(posedge clk); - rx_d <= i0[5:0]; - rx_frame <= do_mimo; - @(posedge clk); - rx_d <= q0[5:0]; - rx_frame <= do_mimo; - - // Channel 1 sample / Second channel 0 sample - @(posedge clk); - rx_d <= i1[11:6]; - rx_frame <= ~do_mimo; - @(posedge clk); - rx_d <= q1[11:6]; - rx_frame <= ~do_mimo; - @(posedge clk); - rx_d <= i1[5:0]; - rx_frame <= 0; - @(posedge clk); - rx_d <= q1[5:0]; - rx_frame <= 0; - - rx_count <= rx_count + 1; - end - end - endtask // Burst - - - // Test receiving/transmitting 2*len samples, checking len-2 for correctness. - // The output is checked by the Tx and Rx Output Checkers below. We have to - // be a little bit careful when we enable output checking, because it takes a - // few clock cycles for data to propagate through, and we don't want to check - // the outputs when the outputs are not valid. - task TestBurst; - input [31:0] len; - input do_mimo; - begin - if (len <= 2) begin - $display("ERROR @%0t in %m: In TestBurst, len must be > 2", $time); - $finish; - end - - // Input several bursts, to fill the pipeline and cause results on the - // outputs before we start checking. - Burst(1, do_mimo); - - // Enable output checking - check_enabled <= 1'b1; - - // Do the requested length, minus 1 - Burst(len-2, do_mimo); - - // Disable output checking - check_enabled <= 1'b0; - - // Give an extra output to allow data to propagate to the output - Burst(1, do_mimo); - end - endtask // TestBurst - - - //--------------------------------------------------------------------------- - // Test Procedure - //--------------------------------------------------------------------------- - - initial - begin - // Initial values - check_enabled <= 1'b0; - test_status <= "Reset"; - reset = 1; - mimo = 1; - ctrl_in_clk_delay = INPUT_CLOCK_DELAY; - ctrl_in_data_delay = INPUT_DATA_DELAY; - ctrl_ld_in_data_delay = 1'b0; - ctrl_ld_in_clk_delay = 1'b0; - ctrl_out_clk_delay = OUTPUT_CLOCK_DELAY; - ctrl_out_data_delay = OUTPUT_DATA_DELAY; - ctrl_ld_out_data_delay = 1'b0; - ctrl_ld_out_clk_delay = 1'b0; - repeat(10) @(negedge rx_clk); - reset = 0; - @(negedge rx_clk); - - //----------------------------------------------------------------------- - // Test Changing Delays - - test_status <= "Load IO delays"; - - if (CLOCK_IDELAY_MODE == "VAR_LOAD") begin - ctrl_ld_in_clk_delay = 1'b1; - @(negedge rx_clk); - ctrl_ld_in_clk_delay = 1'b0; - @(negedge rx_clk); - end - - if (DATA_IDELAY_MODE == "VAR_LOAD") begin - ctrl_ld_in_data_delay = 1'b1; - @(negedge rx_clk); - ctrl_ld_in_data_delay = 1'b0; - @(negedge rx_clk); - end - - if (CLOCK_ODELAY_MODE == "VAR_LOAD") begin - ctrl_ld_out_clk_delay = 1'b1; - @(negedge rx_clk); - ctrl_ld_out_clk_delay = 1'b0; - @(negedge rx_clk); - end - - if (DATA_ODELAY_MODE == "VAR_LOAD") begin - ctrl_ld_out_data_delay = 1'b1; - @(negedge rx_clk); - ctrl_ld_out_data_delay = 1'b0; - @(negedge rx_clk); - end - - //----------------------------------------------------------------------- - // Startup - - test_status <= "Startup"; - - // Pump a few clock cycles to get things started (flush out X values) - Burst(2,1); - - //----------------------------------------------------------------------- - // Test MIMO - - // Input data until the Rx circuit aligns - test_status <= "Wait align 1"; - while (!rx_aligned) begin - Burst(1,1); - end - - // Input some new samples - test_status <= "Burst 1 (MIMO)"; - TestBurst(30, 1); - - // Reset and do another burst - test_status <= "Reset 2"; - reset = 1; - repeat(20) @(negedge rx_clk); - reset = 0; - repeat(2) @(negedge rx_clk); - - // Input data until the Rx circuit aligns - test_status <= "Wait align 2"; - while (!rx_aligned) begin - Burst(1,1); - end - - // Input some new samples - test_status <= "Burst 2 (MIMO)"; - TestBurst(23, 1); - - //----------------------------------------------------------------------- - // Test SISO (transmit channel 0) - - tx_ch <= 1'b0; - - // Reset and do another burst - test_status <= "Reset 3"; - reset = 1; - repeat(20) @(negedge rx_clk); - reset = 0; - repeat(2) @(negedge rx_clk); - - // Input data until the Rx circuit aligns in SISO mode - test_status <= "Wait align 3"; - while (!rx_aligned) begin - Burst(1,0); - end - - // Test SISO mode - test_status <= "Burst 3 (SISO, Ch 0)"; - TestBurst(25, 0); - - // Reset and do another burst - test_status <= "Reset 4"; - reset = 1; - repeat(20) @(negedge rx_clk); - reset = 0; - repeat(2) @(negedge rx_clk); - - // Input data until the Rx circuit aligns in SISO mode - test_status <= "Wait align 4"; - while (!rx_aligned) begin - Burst(1,0); - end - - // Test SISO mode - test_status <= "Burst 4 (SISO, Ch 0)"; - TestBurst(27, 0); - - //----------------------------------------------------------------------- - // Test SISO (transmit channel 1) - - tx_ch <= 1'b1; - - // Reset and do another burst - test_status <= "Reset 5"; - reset = 1; - repeat(20) @(negedge rx_clk); - reset = 0; - repeat(2) @(negedge rx_clk); - - // Input data until the Rx circuit aligns in SISO mode - test_status <= "Wait align 5"; - while (!rx_aligned) begin - Burst(1,0); - end - - // Test SISO mode - test_status <= "Burst 5 (SISO, Ch 1)"; - TestBurst(25, 0); - - // Reset and do another burst - test_status <= "Reset 6"; - reset = 1; - repeat(20) @(negedge rx_clk); - reset = 0; - repeat(2) @(negedge rx_clk); - - // Input data until the Rx circuit aligns in SISO mode - test_status <= "Wait align 6"; - while (!rx_aligned) begin - Burst(1,0); - end - - // Test SISO mode - test_status <= "Burst 6 (SISO, Ch 1)"; - TestBurst(27, 0); - - //----------------------------------------------------------------------- - // Done - - test_status <= "Finished"; - repeat(50) @(negedge rx_clk); - - $finish; - end - - - //--------------------------------------------------------------------------- - // Rx Output Checker - //--------------------------------------------------------------------------- - // - // In MIMO mode, we expect to see: - // - // rx_i0: A00, A01, A02, A03, ... - // rx_q0: B00, B01, B02, B03, ... - // rx_i1: C00, C01, C02, C03, ... - // rx_q1: D00, D01, D02, D03, ... - // - // In SISO mode, we expect to see (with twice the clock rate): - // - // rx_i0: A00, C00, A01, C01, ... - // rx_q0: B00, D00, B01, D01, ... - // rx_i1: A00, C00, A01, C01, ... - // rx_q1: B00, D00, B01, D01, ... - // - //--------------------------------------------------------------------------- - - reg first_rx_check = 1'b1; - reg [11:0] rx_i0_del1, rx_i0_del2; - reg [11:0] rx_q0_del1, rx_q0_del2; - reg [11:0] rx_i1_del1, rx_i1_del2; - reg [11:0] rx_q1_del1, rx_q1_del2; - - always @(posedge radio_clk) - begin - if (check_enabled) begin - if (!first_rx_check) begin - - if (mimo) begin - - // Check prefix for channel 0 - if (rx_i0[11:8] != 4'hA || rx_q0[11:8] != 4'hB) begin - $display("ERROR @%0t in %m: Rx channel 0 didn't have expected A/B prefix in MIMO mode", $time); - $finish; - end - - // Check prefix for channel 1 - if (rx_i1[11:8] != 4'hC || rx_q1[11:8] != 4'hD) begin - $display("ERROR @%0t in %m: Rx channel 1 didn't have expected C/D in MIMO mode", $time); - $finish; - end - - // All outputs should have the same count in MIMO mode - if (! (rx_i0[7:0] == rx_q0[7:0] && - rx_i0[7:0] == rx_i1[7:0] && - rx_i0[7:0] == rx_q1[7:0]) ) begin - $display("ERROR @%0t in %m: Rx data counts didn't match on all outputs in MIMO mode", $time); - $finish; - end - - // Make sure the count increments - if (rx_i0[7:0] != rx_i0_del1[7:0] + 8'd1 || rx_q0[7:0] != rx_q0_del1[7:0] + 8'd1 || - rx_i1[7:0] != rx_i1_del1[7:0] + 8'd1 || rx_q1[7:0] != rx_q1_del1[7:0] + 8'd1) begin - $display("ERROR @%0t in %m: Rx data count didn't increment as expected", $time); - $finish; - end - - end else begin // if (mimo) - - // In SISO mode, both outputs should be the same - if (rx_i0 != rx_i1 || rx_q0 != rx_q1) begin - $display("ERROR @%0t in %m: Rx channel 0 and 1 don't match in SISO mode", $time); - $finish; - end - - // Check channel 0 prefix. No need to check channel 1, since we - // already checked that the channels match. - if (!((rx_i0[11:8] == 4'hA && rx_q0[11:8] == 4'hB) || - (rx_i0[11:8] == 4'hC && rx_q0[11:8] == 4'hD))) begin - $display("ERROR @%0t in %m: Rx data didn't have expected A/B or C/D prefix in SISO mode", $time); - $finish; - end - - // Make sure we're alternating between channel data. No need to check - // channel 1, since we already checked that the channels match. - if (!((rx_i0[11:8] == 4'hA && rx_i0_del1[11:8] == 4'hC) || - (rx_i0[11:8] == 4'hC && rx_i0_del1[11:8] == 4'hA) || - (rx_q0[11:8] == 4'hB && rx_q0_del1[11:8] == 4'hD) || - (rx_q0[11:8] == 4'hD && rx_q0_del1[11:8] == 4'hB))) begin - $display("ERROR @%0t in %m: Rx data not toggling between channel data in SISO mode", $time); - $finish; - end - - // Make sure the counts are the same for both I and Q. No need to - // check channel 1, since we already checked that the channels match. - if (rx_i0[7:0] != rx_q0[7:0]) begin - $display("ERROR @%0t in %m: Rx data counts didn't match on all outputs in SISO mode", $time); - $finish; - end - - // Make sure the count increments every other clock cycle. No need to - // check channel 1, since we already checked that the channels match. - if (!( - rx_i0[7:0] != rx_i0_del2[7:0] + 8'd1 && (rx_i0[7:0] == rx_i0_del1[7:0] || rx_i0[7:0] == rx_i0_del1[7:0] + 8'd1) && - rx_q0[7:0] != rx_q0_del2[7:0] + 8'd1 && (rx_q0[7:0] == rx_q0_del1[7:0] || rx_q0[7:0] == rx_q0_del1[7:0] + 8'd1) - )) begin - $display("ERROR @%0t in %m: Rx data count didn't increment as expected", $time); - $finish; - end - - end // if (mimo) - end // if (!first_rx_check) - - // Make sure we've captured at least one set of values, so we have a - // previous set to look back to. - first_rx_check <= 1'b0; - - end else begin // if (check_enabled) - first_rx_check <= 1'b1; - end // if (check_enabled) - - // Save values seen this cycle - rx_i0_del1 <= rx_i0; - rx_q0_del1 <= rx_q0; - rx_i1_del1 <= rx_i1; - rx_q1_del1 <= rx_q1; - rx_i0_del2 <= rx_i0_del2; - rx_q0_del2 <= rx_q0_del2; - rx_i1_del2 <= rx_i1_del2; - rx_q1_del2 <= rx_q1_del2; - end - - - //--------------------------------------------------------------------------- - // Tx Output Checker - //--------------------------------------------------------------------------- - // - // The code implements a loopback, so the output should match the input. In - // SISO mode, however, the frame signal may not be aligned. - // - //--------------------------------------------------------------------------- - - reg first_tx_check; - reg [11:0] tx_i0_del1; - reg [11:0] tx_q0_del1; - reg [11:0] tx_i1_del1; - reg [11:0] tx_q1_del1; - reg tx_frame_del1; - - reg [11:0] tx_i0_check; - reg [11:0] tx_q0_check; - reg [11:0] tx_i1_check; - reg [11:0] tx_q1_check; - reg [7:0] tx_frame_check; - - - always @(posedge tx_clk_p) - begin - tx_frame_del1 <= tx_frame_p; - end - - - always @(posedge tx_clk_p) - begin - if (tx_frame_p && !tx_frame_del1) begin - //----------------------------------------------------------------------- - // Grab two samples from the output, starting at frame boundary - //----------------------------------------------------------------------- - - // Channel 0 sample - tx_i0_check[11:6] <= tx_d_p; - tx_frame_check[7] <= tx_frame_p; - @(posedge tx_clk_n); - tx_q0_check[11:6] <= tx_d_p; - tx_frame_check[6] <= tx_frame_p; - @(posedge tx_clk_p); - tx_i0_check[5:0] <= tx_d_p; - tx_frame_check[5] <= tx_frame_p; - @(posedge tx_clk_n); - tx_q0_check[5:0] <= tx_d_p; - tx_frame_check[4] <= tx_frame_p; - - // Channel 1 sample / Second channel 0 sample - @(posedge tx_clk_p); - tx_i1_check[11:6] <= tx_d_p; - tx_frame_check[3] <= tx_frame_p; - @(posedge tx_clk_n); - tx_q1_check[11:6] <= tx_d_p; - tx_frame_check[2] <= tx_frame_p; - @(posedge tx_clk_p); - tx_i1_check[5:0] <= tx_d_p; - tx_frame_check[1] <= tx_frame_p; - @(posedge tx_clk_n); - tx_q1_check[5:0] <= tx_d_p; - tx_frame_check[0] <= tx_frame_p; - - #1 // Minimum delay for *_check registers to update in simulation - - if (check_enabled) begin - if (!first_tx_check) begin - - if (mimo) begin - //----------------------------------------------------------------- - // Check MIMO output - //----------------------------------------------------------------- - - // Check that the frame signal is correct - if (tx_frame_check != 8'b11110000) begin - $display("ERROR @%0t in %m: Tx frame was not correct in MIMO mode", $time); - $finish; - end - - // Check prefix for channel 0 - if (tx_i0_check[11:8] != 4'hA || tx_q0_check[11:8] != 4'hB) begin - $display("ERROR @%0t in %m: Tx channel 0 didn't have expected A/B prefix in MIMO mode", $time); - $finish; - end - - // Check prefix for channel 1 - if (tx_i1_check[11:8] != 4'hC || tx_q1_check[11:8] != 4'hD) begin - $display("ERROR @%0t in %m: Tx channel 1 didn't have expected C/D in MIMO mode", $time); - $finish; - end - - // All outputs should have the same count in MIMO mode - if (! (tx_i0_check[7:0] == tx_q0_check[7:0] && - tx_i0_check[7:0] == tx_i1_check[7:0] && - tx_i0_check[7:0] == tx_q1_check[7:0]) ) begin - $display("ERROR @%0t in %m: Rx data counts didn't match on all outputs in MIMO mode", $time); - $finish; - end - - // Make sure the count increments - if (tx_i0_check[7:0] != tx_i0_del1[7:0] + 8'd1 || tx_q0_check[7:0] != tx_q0_del1[7:0] + 8'd1 || - tx_i1_check[7:0] != tx_i1_del1[7:0] + 8'd1 || tx_q1_check[7:0] != tx_q1_del1[7:0] + 8'd1) begin - $display("ERROR @%0t in %m: Rx data count didn't increment as expected", $time); - $finish; - end - - end else begin - //----------------------------------------------------------------- - // Check SISO Output - //----------------------------------------------------------------- - - // Check that the frame signal is correct - if (tx_frame_check != 8'b11001100) begin - $display("ERROR @%0t in %m: Tx frame was not correct in SISO mode", $time); - $finish; - end - - - // In SISO mode, the data we get depends on which channel is - // selected. - // - // Channel 0: Channel 1: - // ...,A01,B01,A02,B02,... OR ...,C01,D01,C02,D02,... - // - // So we should receive - // - // A01 A03 A05 - // ... B01 B03 B05 ... - // A02 B04 A06 - // B02 B04 A07 - // - // or - // C01 C03 C05 - // ... D01 D03 D05 ... - // C02 C04 C06 - // D02 D04 D07 - // - - // Check prefixes - if (!( - // Either A,B on channel 0 or C,D on channel 1 - ((tx_ch == 0 && - tx_i0_check[11:8] == 4'hA && - tx_q0_check[11:8] == 4'hB) || - (tx_ch == 1 && - tx_i0_check[11:8] == 4'hC && - tx_q0_check[11:8] == 4'hD)) && - // Samples 0 and 1 prefixes equal samples 2 and 3 prefixes - (tx_i0_check[11:8] == tx_i1_check[11:8] && - tx_q0_check[11:8] == tx_q1_check[11:8]) - )) begin - $display("ERROR @%0t in %m: Tx channel didn't have expected prefixes in SISO mode", $time); - $finish; - end - - // Check that the data count matches between samples - if (!( - tx_i0_check[7:0] == tx_q0_check[7:0] && - tx_i1_check[7:0] == tx_q1_check[7:0] && - tx_i0_check[7:0] == tx_i1_check[7:0] - 8'd1 - )) begin - $display("ERROR @%0t in %m: Tx channel data counts didn't correlate in SISO mode", $time); - $finish; - end - - // Make sure the count increments form one burst to the next - if (tx_i0_check[7:0] != tx_i0_del1[7:0] + 8'd2 || - tx_q0_check[7:0] != tx_q0_del1[7:0] + 8'd2 || - tx_i1_check[7:0] != tx_i1_del1[7:0] + 8'd2 || - tx_q1_check[7:0] != tx_q1_del1[7:0] + 8'd2) begin - $display("ERROR @%0t in %m: Tx data count didn't increment as expected", $time); - $finish; - end - - end - - end else begin // if (!first_tx_check) - // Make sure we've captured at least one set of values, so we have a - // previous set to look back to. - first_tx_check <= 1'b0; - end // if (!first_tx_check) - - // Save values seen this cycle - tx_i0_del1 <= tx_i0_check; - tx_q0_del1 <= tx_q0_check; - tx_i1_del1 <= tx_i1_check; - tx_q1_del1 <= tx_q1_check; - - end else begin // if (check_enabled) - first_tx_check <= 1'b1; - - end // if (check_enabled) - - end // if (tx_frame_p && !tx_frame_del1) - - end - - - //--------------------------------------------------------------------------- - // Tx Input Data Generation - //--------------------------------------------------------------------------- - // - // Input a known data pattern similar to the Rx patten. - // - // I0: A01 A02 A03 - // Q0: ... B01 B02 B03 ... - // I1: C01 C02 C03 - // Q1: D01 D02 D03 - // - //--------------------------------------------------------------------------- - - reg [7:0] tx_count = 0; - - // Loop the Rx interface of DUT back to its Tx interface - always @(posedge radio_clk) begin - tx_i0 <= { 4'hA, tx_count }; - tx_q0 <= { 4'hB, tx_count }; - tx_i1 <= { 4'hC, tx_count }; - tx_q1 <= { 4'hD, tx_count }; - tx_count <= tx_count + 7'd1; - end - - - //--------------------------------------------------------------------------- - // DUT - //--------------------------------------------------------------------------- - - cat_io_lvds_dual_mode #( - .INVERT_FRAME_RX (0), - .INVERT_DATA_RX (6'b00_0000), - .INVERT_FRAME_TX (0), - .INVERT_DATA_TX (6'b00_0000), - .USE_CLOCK_IDELAY (USE_CLOCK_IDELAY), - .USE_DATA_IDELAY (USE_DATA_IDELAY), - .DATA_IDELAY_MODE (DATA_IDELAY_MODE), - .CLOCK_IDELAY_MODE (CLOCK_IDELAY_MODE), - .INPUT_CLOCK_DELAY (INPUT_CLOCK_DELAY), - .INPUT_DATA_DELAY (INPUT_DATA_DELAY), - .USE_CLOCK_ODELAY (USE_CLOCK_ODELAY), - .USE_DATA_ODELAY (USE_DATA_ODELAY), - .DATA_ODELAY_MODE (DATA_ODELAY_MODE), - .CLOCK_ODELAY_MODE (CLOCK_ODELAY_MODE), - .OUTPUT_CLOCK_DELAY (OUTPUT_CLOCK_DELAY), - .OUTPUT_DATA_DELAY (OUTPUT_DATA_DELAY) - ) cat_io_lvds_dual_mode_dut ( - .rst (reset), - .clk200 (clk200), - - // Data and frame timing - .a_mimo (mimo), - .a_tx_ch (tx_ch), - - // Delay control interface - .ctrl_clk (rx_clk), - // - .ctrl_in_data_delay (ctrl_in_data_delay), - .ctrl_in_clk_delay (ctrl_in_clk_delay), - .ctrl_ld_in_data_delay (ctrl_ld_in_data_delay), - .ctrl_ld_in_clk_delay (ctrl_ld_in_clk_delay), - // - .ctrl_out_data_delay (ctrl_out_data_delay), - .ctrl_out_clk_delay (ctrl_out_clk_delay), - .ctrl_ld_out_data_delay (ctrl_ld_out_data_delay), - .ctrl_ld_out_clk_delay (ctrl_ld_out_clk_delay), - - // Baseband sample interface - .radio_clk (radio_clk), - .rx_aligned (rx_aligned), - // - .rx_i0 (rx_i0), - .rx_q0 (rx_q0), - .rx_i1 (rx_i1), - .rx_q1 (rx_q1), - // - .tx_i0 (tx_i0), - .tx_q0 (tx_q0), - .tx_i1 (tx_i1), - .tx_q1 (tx_q1), - - // Catalina interface - .rx_clk_p (rx_clk), - .rx_clk_n (~rx_clk), - .rx_frame_p (rx_frame), - .rx_frame_n (~rx_frame), - .rx_d_p (rx_d), - .rx_d_n (~rx_d), - // - .tx_clk_p (tx_clk_p), - .tx_clk_n (tx_clk_n), - .tx_frame_p (tx_frame_p), - .tx_frame_n (tx_frame_n), - .tx_d_p (tx_d_p), - .tx_d_n (tx_d_n) - ); - -endmodule // cat_io_lvds_dual_mode_tb diff --git a/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/Makefile b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/Makefile new file mode 100644 index 000000000..20795a9a8 --- /dev/null +++ b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/Makefile @@ -0,0 +1,77 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir. +BASE_DIR = $(abspath ../../../../top) +# Include viv_sim_preample after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Define part using PART_ID (//) +ARCH = kintex7 +PART_ID = xc7k410t/ffg900/-2 + +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/fifo/Makefile.srcs +include $(BASE_DIR)/../lib/axi/Makefile.srcs +include $(BASE_DIR)/../lib/control/Makefile.srcs +include $(BASE_DIR)/../lib/io_cap_gen/Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(FIFO_SRCS) \ +$(AXI_SRCS) \ +$(CONTROL_LIB_SRCS) \ +$(CAT_CAP_GEN_SRCS) \ +) + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +IP_DIR = $(BASE_DIR)/e320/ip +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the IP_DIR +include $(IP_DIR)/fifo_short_2clk/Makefile.inc + +DESIGN_SRCS += $(abspath \ +$(IP_FIFO_SHORT_2CLK_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +include $(BASE_DIR)/../sim/general/Makefile.srcs +include $(BASE_DIR)/../sim/axi/Makefile.srcs + +MODELSIM_LIBS += unisims_ver secureip fifo_generator_v13_2_4 + +# Define only one top-level module +SIM_TOP = cat_io_lvds_dual_mode_tb glbl + +# Simulation runtime in microseconds +SIM_RUNTIME_US = 1000 + +SIM_SRCS = \ +$(IP_BUILD_DIR)/fifo_short_2clk/simulation/fifo_generator_vlog_beh.v \ +$(IP_BUILD_DIR)/fifo_short_2clk/sim/fifo_short_2clk.v \ +$(abspath cat_io_lvds_dual_mode_tb.sv) \ +$(VIVADO_PATH)/data/verilog/src/glbl.v \ + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/cat_io_lvds_dual_mode_tb.sv b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/cat_io_lvds_dual_mode_tb.sv new file mode 100644 index 000000000..c2a1d1e3c --- /dev/null +++ b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds_dual_mode_tb/cat_io_lvds_dual_mode_tb.sv @@ -0,0 +1,805 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: cat_io_lvds_dual_mode_tb +// +// Description: Testbench for cat_io_lvds_dual_mode. +// + +`timescale 1ns/1ps + +module cat_io_lvds_dual_mode_tb(); + + `include "test_exec.svh" + import PkgTestExec::*; + + localparam CLK_PERIOD = 10; + localparam CLK200_PERIOD = 2.5; + + localparam USE_CLOCK_IDELAY = 1; + localparam USE_DATA_IDELAY = 1; + localparam DATA_IDELAY_MODE = "FIXED"; + localparam CLOCK_IDELAY_MODE = "FIXED"; + localparam INPUT_CLOCK_DELAY = 16; + localparam INPUT_DATA_DELAY = 0; + localparam USE_CLOCK_ODELAY = 1; + localparam USE_DATA_ODELAY = 1; + localparam DATA_ODELAY_MODE = "FIXED"; + localparam CLOCK_ODELAY_MODE = "FIXED"; + localparam OUTPUT_CLOCK_DELAY = 31; + localparam OUTPUT_DATA_DELAY = 0; + + reg check_enabled; // Controls when output checking is performed + + reg clk = 0; + reg rx_clk = 0; + reg clk200 = 0; + + reg reset; + reg mimo; + reg tx_ch; + reg [5:0] rx_d; + reg rx_frame; + reg [7:0] rx_count = 0; + + // Each channel's data begins with a unique identifier (A../B.. or C../D..) + // followed by a count, which should always be sequential. + wire [11:0] i0 = { 4'hA, rx_count }; + wire [11:0] q0 = { 4'hB, rx_count }; + wire [11:0] i1 = { 4'hC, rx_count }; + wire [11:0] q1 = { 4'hD, rx_count }; + + wire radio_clk; + + reg [11:0] tx_i0; + reg [11:0] tx_q0; + reg [11:0] tx_i1; + reg [11:0] tx_q1; + + wire [11:0] rx_i0; + wire [11:0] rx_q0; + wire [11:0] rx_i1; + wire [11:0] rx_q1; + + wire rx_aligned; + + wire tx_clk_p, tx_clk_n; + wire tx_frame_p, tx_frame_n; + wire [5:0] tx_d_p, tx_d_n; + + reg [4:0] ctrl_in_data_delay; + reg [4:0] ctrl_in_clk_delay; + reg ctrl_ld_in_data_delay; + reg ctrl_ld_in_clk_delay; + + reg [4:0] ctrl_out_data_delay; + reg [4:0] ctrl_out_clk_delay; + reg ctrl_ld_out_data_delay; + reg ctrl_ld_out_clk_delay; + + + //--------------------------------------------------------------------------- + // Clock Generation + //--------------------------------------------------------------------------- + + // IODELAYCTRL reference clock + always #(CLK200_PERIOD) clk200 = ~clk200; + + // Create an internal clock we'll use to drive the data + always #(CLK_PERIOD) clk = ~clk; + + // RF interface clock. Half the rate of clk and out of phase + always @(negedge clk) rx_clk <= ~rx_clk; + + + //--------------------------------------------------------------------------- + // Tasks + //--------------------------------------------------------------------------- + + // Output a single burst of 2*len samples. In MIMO mode, this consists of len + // samples on each channel. In SISO mode, this consists of 2*len samples on + // the same channel. + task Burst(int len, logic do_mimo); + repeat(len) + begin + mimo <= do_mimo; + + // Channel 0 sample + @(posedge clk); + rx_d <= i0[11:6]; + rx_frame <= 1; + @(posedge clk); + rx_d <= q0[11:6]; + rx_frame <= 1; + @(posedge clk); + rx_d <= i0[5:0]; + rx_frame <= do_mimo; + @(posedge clk); + rx_d <= q0[5:0]; + rx_frame <= do_mimo; + + // Channel 1 sample / Second channel 0 sample + @(posedge clk); + rx_d <= i1[11:6]; + rx_frame <= ~do_mimo; + @(posedge clk); + rx_d <= q1[11:6]; + rx_frame <= ~do_mimo; + @(posedge clk); + rx_d <= i1[5:0]; + rx_frame <= 0; + @(posedge clk); + rx_d <= q1[5:0]; + rx_frame <= 0; + + rx_count <= rx_count + 1; + end + endtask : Burst + + + // Output zeros for len clk cycles (model what happens when the RFDC stops + // giving us data). + task Idle(int len = 100); + rx_d <= 0; + rx_frame <= 0; + repeat (len) @(posedge clk); + endtask : Idle + + + task Reset(int num_cycles = 20); + reset <= 1; + repeat(num_cycles) @(negedge rx_clk); + reset <= 0; + repeat(2) @(negedge rx_clk); + endtask : Reset + + + // Test receiving/transmitting 2*len samples, checking len-2 for correctness. + // The output is checked by the Tx and Rx Output Checkers below. We have to + // be a little bit careful when we enable output checking, because it takes a + // few clock cycles for data to propagate through, and we don't want to check + // the outputs when the outputs are not valid. + task TestBurst; + input [31:0] len; + input do_mimo; + begin + if (len <= 2) begin + $fatal(1, "ERROR @%0t in %m: In TestBurst, len must be > 2", $time); + end + + // Input several bursts, to fill the pipeline and cause results on the + // outputs before we start checking. + Burst(1, do_mimo); + + // Enable output checking + check_enabled <= 1'b1; + + // Do the requested length, minus 1 + Burst(len-2, do_mimo); + + // Disable output checking + check_enabled <= 1'b0; + + // Give an extra output to allow data to propagate to the output + Burst(1, do_mimo); + end + endtask // TestBurst + + + //--------------------------------------------------------------------------- + // Test Procedure + //--------------------------------------------------------------------------- + + initial begin : main + + test.start_tb("cat_io_lvds_dual_mode_tb"); + + // Initial values + check_enabled <= 1'b0; + reset = 1; + mimo = 1; + ctrl_in_clk_delay = INPUT_CLOCK_DELAY; + ctrl_in_data_delay = INPUT_DATA_DELAY; + ctrl_ld_in_data_delay = 1'b0; + ctrl_ld_in_clk_delay = 1'b0; + ctrl_out_clk_delay = OUTPUT_CLOCK_DELAY; + ctrl_out_data_delay = OUTPUT_DATA_DELAY; + ctrl_ld_out_data_delay = 1'b0; + ctrl_ld_out_clk_delay = 1'b0; + + Reset(); + + //----------------------------------------------------------------------- + // Test Changing Delays + + test.start_test("Load IO delays"); + + if (CLOCK_IDELAY_MODE == "VAR_LOAD") begin + ctrl_ld_in_clk_delay = 1'b1; + @(negedge rx_clk); + ctrl_ld_in_clk_delay = 1'b0; + @(negedge rx_clk); + end + + if (DATA_IDELAY_MODE == "VAR_LOAD") begin + ctrl_ld_in_data_delay = 1'b1; + @(negedge rx_clk); + ctrl_ld_in_data_delay = 1'b0; + @(negedge rx_clk); + end + + if (CLOCK_ODELAY_MODE == "VAR_LOAD") begin + ctrl_ld_out_clk_delay = 1'b1; + @(negedge rx_clk); + ctrl_ld_out_clk_delay = 1'b0; + @(negedge rx_clk); + end + + if (DATA_ODELAY_MODE == "VAR_LOAD") begin + ctrl_ld_out_data_delay = 1'b1; + @(negedge rx_clk); + ctrl_ld_out_data_delay = 1'b0; + @(negedge rx_clk); + end + + test.end_test(); + + //----------------------------------------------------------------------- + // Startup + + test.start_test("Load IO delays"); + + // Pump a few clock cycles to get things started (flush out X values) + Burst(2,1); + + test.end_test(); + + + //----------------------------------------------------------------------- + // Test MIMO + + test.start_test("Test MIMO"); + + // Input data until the Rx circuit aligns + $display("Wait align 1"); + while (!rx_aligned) begin + Burst(1,1); + end + + // Input some new samples + $display("Burst 1 (MIMO)"); + TestBurst(30, 1); + + // Reset and do another burst + $display("Reset 2"); + Reset(); + + // Input data until the Rx circuit aligns + $display("Wait align 2"); + while (!rx_aligned) begin + Burst(1,1); + end + + // Input some new samples + $display("Burst 2 (MIMO)"); + TestBurst(23, 1); + + test.end_test(); + + //----------------------------------------------------------------------- + // Test SISO (transmit channel 0) + + test.start_test("Test SISO (transmit channel 0)"); + + tx_ch <= 1'b0; + + // Reset and do another burst + $display("Reset 3"); + Reset(); + + // Input data until the Rx circuit aligns in SISO mode + $display("Wait align 3"); + while (!rx_aligned) Burst(1,0); + + // Test SISO mode + $display("Burst 3 (SISO, Ch 0)"); + TestBurst(25, 0); + + // Reset and do another burst + $display("Reset 4"); + Reset(); + + // Input data until the Rx circuit aligns in SISO mode + $display("Wait align 4"); + while (!rx_aligned) Burst(1,0); + + // Test SISO mode + $display("Burst 4 (SISO, Ch 0)"); + TestBurst(27, 0); + + test.end_test(); + + //----------------------------------------------------------------------- + // Test SISO (transmit channel 1) + + test.start_test("Test SISO (transmit channel 1)"); + + tx_ch <= 1'b1; + + // Reset and do another burst + $display("Reset 5"); + Reset(); + + // Input data until the Rx circuit aligns in SISO mode + $display("Wait align 5"); + while (!rx_aligned) Burst(1,0); + + // Test SISO mode + $display("Burst 5 (SISO, Ch 1)"); + TestBurst(25, 0); + + // Reset and do another burst + $display("Reset 6"); + Reset(); + + // Input data until the Rx circuit aligns in SISO mode + $display("Wait align 6"); + while (!rx_aligned) Burst(1,0); + + // Test SISO mode + $display("Burst 6 (SISO, Ch 1)"); + TestBurst(27, 0); + + test.end_test(); + + //----------------------------------------------------------------------- + // Test going Idle then starting SISO, without reset + + test.start_test("Test Idle then SISO"); + + tx_ch <= 1'b1; + + $display("Wait idle flush 6"); + while (rx_aligned) Idle(1); + $display("Wait align 6"); + while (!rx_aligned) Burst(1,0); + + // Test SISO mode + $display("Burst 6 (SISO, Ch 1)"); + TestBurst(25, 0); + + test.end_test(); + + //----------------------------------------------------------------------- + // Test going Idle then starting MIMO, without reset + + test.start_test("Test Idle then MIMO"); + + tx_ch <= 1'b1; + + $display("Wait idle flush 7"); + while (rx_aligned) Idle(1); + $display("Wait align 7"); + while (!rx_aligned) begin + Burst(1,1); + end + + // Test SISO mode + $display("Burst 7 (SISO, Ch 1)"); + TestBurst(25, 1); + + test.end_test(); + + //----------------------------------------------------------------------- + // Done + + test.end_tb(); + end : main + + + //--------------------------------------------------------------------------- + // Rx Output Checker + //--------------------------------------------------------------------------- + // + // In MIMO mode, we expect to see: + // + // rx_i0: A00, A01, A02, A03, ... + // rx_q0: B00, B01, B02, B03, ... + // rx_i1: C00, C01, C02, C03, ... + // rx_q1: D00, D01, D02, D03, ... + // + // In SISO mode, we expect to see (with twice the clock rate): + // + // rx_i0: A00, C00, A01, C01, ... + // rx_q0: B00, D00, B01, D01, ... + // rx_i1: A00, C00, A01, C01, ... + // rx_q1: B00, D00, B01, D01, ... + // + //--------------------------------------------------------------------------- + + reg first_rx_check = 1'b1; + reg [11:0] rx_i0_del1, rx_i0_del2; + reg [11:0] rx_q0_del1, rx_q0_del2; + reg [11:0] rx_i1_del1, rx_i1_del2; + reg [11:0] rx_q1_del1, rx_q1_del2; + + always @(posedge radio_clk) + begin + if (check_enabled) begin + if (!first_rx_check) begin + + if (mimo) begin + + // Check prefix for channel 0 + if (rx_i0[11:8] != 4'hA || rx_q0[11:8] != 4'hB) begin + $fatal(1, "ERROR in %m: Rx channel 0 didn't have expected A/B prefix in MIMO mode"); + end + + // Check prefix for channel 1 + if (rx_i1[11:8] != 4'hC || rx_q1[11:8] != 4'hD) begin + $fatal(1, "ERROR in %m: Rx channel 1 didn't have expected C/D in MIMO mode"); + end + + // All outputs should have the same count in MIMO mode + if (! (rx_i0[7:0] == rx_q0[7:0] && + rx_i0[7:0] == rx_i1[7:0] && + rx_i0[7:0] == rx_q1[7:0]) ) begin + $fatal(1, "ERROR in %m: Rx data counts didn't match on all outputs in MIMO mode"); + end + + // Make sure the count increments + if (rx_i0[7:0] != rx_i0_del1[7:0] + 8'd1 || rx_q0[7:0] != rx_q0_del1[7:0] + 8'd1 || + rx_i1[7:0] != rx_i1_del1[7:0] + 8'd1 || rx_q1[7:0] != rx_q1_del1[7:0] + 8'd1) begin + $fatal(1, "ERROR in %m: Rx data count didn't increment as expected"); + end + + end else begin // if (mimo) + + // In SISO mode, both outputs should be the same + if (rx_i0 != rx_i1 || rx_q0 != rx_q1) begin + $fatal(1, "ERROR in %m: Rx channel 0 and 1 don't match in SISO mode"); + end + + // Check channel 0 prefix. No need to check channel 1, since we + // already checked that the channels match. + if (!((rx_i0[11:8] == 4'hA && rx_q0[11:8] == 4'hB) || + (rx_i0[11:8] == 4'hC && rx_q0[11:8] == 4'hD))) begin + $fatal(1, "ERROR in %m: Rx data didn't have expected A/B or C/D prefix in SISO mode"); + end + + // Make sure we're alternating between channel data. No need to check + // channel 1, since we already checked that the channels match. + if (!((rx_i0[11:8] == 4'hA && rx_i0_del1[11:8] == 4'hC) || + (rx_i0[11:8] == 4'hC && rx_i0_del1[11:8] == 4'hA) || + (rx_q0[11:8] == 4'hB && rx_q0_del1[11:8] == 4'hD) || + (rx_q0[11:8] == 4'hD && rx_q0_del1[11:8] == 4'hB))) begin + $fatal(1, "ERROR in %m: Rx data not toggling between channel data in SISO mode"); + end + + // Make sure the counts are the same for both I and Q. No need to + // check channel 1, since we already checked that the channels match. + if (rx_i0[7:0] != rx_q0[7:0]) begin + $fatal(1, "ERROR in %m: Rx data counts didn't match on all outputs in SISO mode"); + end + + // Make sure the count increments every other clock cycle. No need to + // check channel 1, since we already checked that the channels match. + if (!( + rx_i0[7:0] != rx_i0_del2[7:0] + 8'd1 && (rx_i0[7:0] == rx_i0_del1[7:0] || rx_i0[7:0] == rx_i0_del1[7:0] + 8'd1) && + rx_q0[7:0] != rx_q0_del2[7:0] + 8'd1 && (rx_q0[7:0] == rx_q0_del1[7:0] || rx_q0[7:0] == rx_q0_del1[7:0] + 8'd1) + )) begin + $fatal(1, "ERROR in %m: Rx data count didn't increment as expected"); + end + + end // if (mimo) + end // if (!first_rx_check) + + // Make sure we've captured at least one set of values, so we have a + // previous set to look back to. + first_rx_check <= 1'b0; + + end else begin // if (check_enabled) + first_rx_check <= 1'b1; + end // if (check_enabled) + + // Save values seen this cycle + rx_i0_del1 <= rx_i0; + rx_q0_del1 <= rx_q0; + rx_i1_del1 <= rx_i1; + rx_q1_del1 <= rx_q1; + rx_i0_del2 <= rx_i0_del2; + rx_q0_del2 <= rx_q0_del2; + rx_i1_del2 <= rx_i1_del2; + rx_q1_del2 <= rx_q1_del2; + end + + + //--------------------------------------------------------------------------- + // Tx Output Checker + //--------------------------------------------------------------------------- + // + // The code implements a loopback, so the output should match the input. In + // SISO mode, however, the frame signal may not be aligned. + // + //--------------------------------------------------------------------------- + + reg first_tx_check; + reg [11:0] tx_i0_del1; + reg [11:0] tx_q0_del1; + reg [11:0] tx_i1_del1; + reg [11:0] tx_q1_del1; + reg tx_frame_del1; + + reg [11:0] tx_i0_check; + reg [11:0] tx_q0_check; + reg [11:0] tx_i1_check; + reg [11:0] tx_q1_check; + reg [7:0] tx_frame_check; + + + always @(posedge tx_clk_p) + begin + tx_frame_del1 <= tx_frame_p; + end + + + always @(posedge tx_clk_p) + begin + if (tx_frame_p && !tx_frame_del1) begin + //----------------------------------------------------------------------- + // Grab two samples from the output, starting at frame boundary + //----------------------------------------------------------------------- + + // Channel 0 sample + tx_i0_check[11:6] <= tx_d_p; + tx_frame_check[7] <= tx_frame_p; + @(posedge tx_clk_n); + tx_q0_check[11:6] <= tx_d_p; + tx_frame_check[6] <= tx_frame_p; + @(posedge tx_clk_p); + tx_i0_check[5:0] <= tx_d_p; + tx_frame_check[5] <= tx_frame_p; + @(posedge tx_clk_n); + tx_q0_check[5:0] <= tx_d_p; + tx_frame_check[4] <= tx_frame_p; + + // Channel 1 sample / Second channel 0 sample + @(posedge tx_clk_p); + tx_i1_check[11:6] <= tx_d_p; + tx_frame_check[3] <= tx_frame_p; + @(posedge tx_clk_n); + tx_q1_check[11:6] <= tx_d_p; + tx_frame_check[2] <= tx_frame_p; + @(posedge tx_clk_p); + tx_i1_check[5:0] <= tx_d_p; + tx_frame_check[1] <= tx_frame_p; + @(posedge tx_clk_n); + tx_q1_check[5:0] <= tx_d_p; + tx_frame_check[0] <= tx_frame_p; + + #1 // Minimum delay for *_check registers to update in simulation + + if (check_enabled) begin + if (!first_tx_check) begin + + if (mimo) begin + //----------------------------------------------------------------- + // Check MIMO output + //----------------------------------------------------------------- + + // Check that the frame signal is correct + if (tx_frame_check != 8'b11110000) begin + $fatal(1, "ERROR @%0t in %m: Tx frame was not correct in MIMO mode", $time); + end + + // Check prefix for channel 0 + if (tx_i0_check[11:8] != 4'hA || tx_q0_check[11:8] != 4'hB) begin + $fatal(1, "ERROR @%0t in %m: Tx channel 0 didn't have expected A/B prefix in MIMO mode", $time); + end + + // Check prefix for channel 1 + if (tx_i1_check[11:8] != 4'hC || tx_q1_check[11:8] != 4'hD) begin + $fatal(1, "ERROR @%0t in %m: Tx channel 1 didn't have expected C/D in MIMO mode", $time); + end + + // All outputs should have the same count in MIMO mode + if (! (tx_i0_check[7:0] == tx_q0_check[7:0] && + tx_i0_check[7:0] == tx_i1_check[7:0] && + tx_i0_check[7:0] == tx_q1_check[7:0]) ) begin + $fatal(1, "ERROR @%0t in %m: Rx data counts didn't match on all outputs in MIMO mode", $time); + end + + // Make sure the count increments + if (tx_i0_check[7:0] != tx_i0_del1[7:0] + 8'd1 || tx_q0_check[7:0] != tx_q0_del1[7:0] + 8'd1 || + tx_i1_check[7:0] != tx_i1_del1[7:0] + 8'd1 || tx_q1_check[7:0] != tx_q1_del1[7:0] + 8'd1) begin + $fatal(1, "ERROR @%0t in %m: Rx data count didn't increment as expected", $time); + end + + end else begin + //----------------------------------------------------------------- + // Check SISO Output + //----------------------------------------------------------------- + + // Check that the frame signal is correct + if (tx_frame_check != 8'b11001100) begin + $fatal(1, "ERROR @%0t in %m: Tx frame was not correct in SISO mode", $time); + end + + + // In SISO mode, the data we get depends on which channel is + // selected. + // + // Channel 0: Channel 1: + // ...,A01,B01,A02,B02,... OR ...,C01,D01,C02,D02,... + // + // So we should receive + // + // A01 A03 A05 + // ... B01 B03 B05 ... + // A02 B04 A06 + // B02 B04 A07 + // + // or + // C01 C03 C05 + // ... D01 D03 D05 ... + // C02 C04 C06 + // D02 D04 D07 + // + + // Check prefixes + if (!( + // Either A,B on channel 0 or C,D on channel 1 + ((tx_ch == 0 && + tx_i0_check[11:8] == 4'hA && + tx_q0_check[11:8] == 4'hB) || + (tx_ch == 1 && + tx_i0_check[11:8] == 4'hC && + tx_q0_check[11:8] == 4'hD)) && + // Samples 0 and 1 prefixes equal samples 2 and 3 prefixes + (tx_i0_check[11:8] == tx_i1_check[11:8] && + tx_q0_check[11:8] == tx_q1_check[11:8]) + )) begin + $fatal(1, "ERROR @%0t in %m: Tx channel didn't have expected prefixes in SISO mode", $time); + end + + // Check that the data count matches between samples + if (!( + tx_i0_check[7:0] == tx_q0_check[7:0] && + tx_i1_check[7:0] == tx_q1_check[7:0] && + tx_i0_check[7:0] == tx_i1_check[7:0] - 8'd1 + )) begin + $fatal(1, "ERROR @%0t in %m: Tx channel data counts didn't correlate in SISO mode", $time); + end + + // Make sure the count increments form one burst to the next + if (tx_i0_check[7:0] != tx_i0_del1[7:0] + 8'd2 || + tx_q0_check[7:0] != tx_q0_del1[7:0] + 8'd2 || + tx_i1_check[7:0] != tx_i1_del1[7:0] + 8'd2 || + tx_q1_check[7:0] != tx_q1_del1[7:0] + 8'd2) begin + $fatal(1, "ERROR @%0t in %m: Tx data count didn't increment as expected", $time); + end + + end + + end else begin // if (!first_tx_check) + // Make sure we've captured at least one set of values, so we have a + // previous set to look back to. + first_tx_check <= 1'b0; + end // if (!first_tx_check) + + // Save values seen this cycle + tx_i0_del1 <= tx_i0_check; + tx_q0_del1 <= tx_q0_check; + tx_i1_del1 <= tx_i1_check; + tx_q1_del1 <= tx_q1_check; + + end else begin // if (check_enabled) + first_tx_check <= 1'b1; + + end // if (check_enabled) + + end // if (tx_frame_p && !tx_frame_del1) + + end + + + //--------------------------------------------------------------------------- + // Tx Input Data Generation + //--------------------------------------------------------------------------- + // + // Input a known data pattern similar to the Rx patten. + // + // I0: A01 A02 A03 + // Q0: ... B01 B02 B03 ... + // I1: C01 C02 C03 + // Q1: D01 D02 D03 + // + //--------------------------------------------------------------------------- + + reg [7:0] tx_count = 0; + + // Loop the Rx interface of DUT back to its Tx interface + always @(posedge radio_clk) begin + tx_i0 <= { 4'hA, tx_count }; + tx_q0 <= { 4'hB, tx_count }; + tx_i1 <= { 4'hC, tx_count }; + tx_q1 <= { 4'hD, tx_count }; + tx_count <= tx_count + 7'd1; + end + + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + cat_io_lvds_dual_mode #( + .INVERT_FRAME_RX (0), + .INVERT_DATA_RX (6'b00_0000), + .INVERT_FRAME_TX (0), + .INVERT_DATA_TX (6'b00_0000), + .USE_CLOCK_IDELAY (USE_CLOCK_IDELAY), + .USE_DATA_IDELAY (USE_DATA_IDELAY), + .DATA_IDELAY_MODE (DATA_IDELAY_MODE), + .CLOCK_IDELAY_MODE (CLOCK_IDELAY_MODE), + .INPUT_CLOCK_DELAY (INPUT_CLOCK_DELAY), + .INPUT_DATA_DELAY (INPUT_DATA_DELAY), + .USE_CLOCK_ODELAY (USE_CLOCK_ODELAY), + .USE_DATA_ODELAY (USE_DATA_ODELAY), + .DATA_ODELAY_MODE (DATA_ODELAY_MODE), + .CLOCK_ODELAY_MODE (CLOCK_ODELAY_MODE), + .OUTPUT_CLOCK_DELAY (OUTPUT_CLOCK_DELAY), + .OUTPUT_DATA_DELAY (OUTPUT_DATA_DELAY) + ) cat_io_lvds_dual_mode_dut ( + .clk200 (clk200), + + // Data and frame timing + .a_mimo (mimo), + .a_tx_ch (tx_ch), + + // Delay control interface + .ctrl_clk (rx_clk), + // + .ctrl_in_data_delay (ctrl_in_data_delay), + .ctrl_in_clk_delay (ctrl_in_clk_delay), + .ctrl_ld_in_data_delay (ctrl_ld_in_data_delay), + .ctrl_ld_in_clk_delay (ctrl_ld_in_clk_delay), + // + .ctrl_out_data_delay (ctrl_out_data_delay), + .ctrl_out_clk_delay (ctrl_out_clk_delay), + .ctrl_ld_out_data_delay (ctrl_ld_out_data_delay), + .ctrl_ld_out_clk_delay (ctrl_ld_out_clk_delay), + + // Baseband sample interface + .radio_rst (reset), + .radio_clk (radio_clk), + .rx_aligned (rx_aligned), + // + .rx_i0 (rx_i0), + .rx_q0 (rx_q0), + .rx_i1 (rx_i1), + .rx_q1 (rx_q1), + // + .tx_i0 (tx_i0), + .tx_q0 (tx_q0), + .tx_i1 (tx_i1), + .tx_q1 (tx_q1), + + // Catalina interface + .rx_clk_p (rx_clk), + .rx_clk_n (~rx_clk), + .rx_frame_p (rx_frame), + .rx_frame_n (~rx_frame), + .rx_d_p (rx_d), + .rx_d_n (~rx_d), + // + .tx_clk_p (tx_clk_p), + .tx_clk_n (tx_clk_n), + .tx_frame_p (tx_frame_p), + .tx_frame_n (tx_frame_n), + .tx_d_p (tx_d_p), + .tx_d_n (tx_d_n) + ); + +endmodule // cat_io_lvds_dual_mode_tb -- cgit v1.2.3