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 --- .../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 +++++++++++++++++++++ 3 files changed, 882 insertions(+), 780 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/sim/io_cap_gen') 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