aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/sim/io_cap_gen
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/sim/io_cap_gen')
-rw-r--r--fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/Makefile53
-rw-r--r--fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/cap_pattern_verifier_tb.sv199
-rw-r--r--fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_dual_mode_tb.v780
-rw-r--r--fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_tb.v350
-rwxr-xr-xfpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.build21
-rw-r--r--fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.v115
-rwxr-xr-xfpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.build21
-rw-r--r--fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.v103
8 files changed, 1642 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/Makefile b/fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/Makefile
new file mode 100644
index 000000000..d80b63901
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/Makefile
@@ -0,0 +1,53 @@
+#
+# Copyright 2015 Ettus Research LLC
+# Copyright 2016 Ettus Research, a National Instruments Company
+#
+# 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_preamble after defining BASE_DIR
+include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
+
+#-------------------------------------------------
+# Design Specific
+#-------------------------------------------------
+# Define part using PART_ID (<device>/<package>/<speedgrade>)
+ARCH = kintex7
+PART_ID = xc7k410t/ffg900/-2
+
+include $(BASE_DIR)/../lib/control/Makefile.srcs
+
+DESIGN_SRCS = $(abspath \
+$(CONTROL_LIB_SRCS) \
+)
+
+DESIGN_SRCS += $(abspath \
+../../../io_cap_gen/cap_pattern_verifier.v \
+)
+
+#-------------------------------------------------
+# Testbench Specific
+#-------------------------------------------------
+include $(BASE_DIR)/../sim/general/Makefile.srcs
+include $(BASE_DIR)/../sim/axi/Makefile.srcs
+
+# Define only one toplevel module
+SIM_TOP = cap_pattern_verifier_tb
+
+SIM_SRCS = \
+$(abspath cap_pattern_verifier_tb.sv) \
+$(SIM_GENERAL_SRCS) \
+$(SIM_AXI_SRCS)
+
+#-------------------------------------------------
+# 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/cap_pattern_verifier/cap_pattern_verifier_tb.sv b/fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/cap_pattern_verifier_tb.sv
new file mode 100644
index 000000000..53aae3719
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/cap_pattern_verifier/cap_pattern_verifier_tb.sv
@@ -0,0 +1,199 @@
+//
+// Copyright 2015 Ettus Research LLC
+// Copyright 2015 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+//
+
+
+`timescale 1ns/1ps
+`define NS_PER_TICK 1
+`define NUM_TEST_CASES 10
+
+`include "sim_clks_rsts.vh"
+`include "sim_exec_report.vh"
+`include "sim_axis_lib.svh"
+
+
+module cap_pattern_verifier_tb();
+ `TEST_BENCH_INIT("cap_pattern_verifier_tb",`NUM_TEST_CASES,`NS_PER_TICK)
+
+ // Define all clocks and resets
+ `DEFINE_CLK(clk, 5, 50) //100MHz sys_clk to generate DDR3 clocking
+ `DEFINE_RESET(rst, 0, 100) //100ns for GSR to deassert
+
+ axis_master data0 (.clk(clk));
+ axis_master data1 (.clk(clk));
+ assign data0.axis.tready = 1;
+ assign data1.axis.tready = 1;
+
+ wire [31:0] count0, errors0, count1, errors1;
+ wire locked0, failed0, locked1, failed1;
+
+ cap_pattern_verifier #(
+ .WIDTH(14),
+ .PATTERN("RAMP"),
+ .RAMP_START(14'h0000),
+ .RAMP_STOP(14'h3FFF),
+ .RAMP_INCR(14'h0001),
+ .NTH_CYCLE(1)
+ ) dut0 (
+ .clk(clk),
+ .rst(rst),
+ .valid(data0.axis.tvalid),
+ .data(data0.axis.tdata[13:0]),
+ .count(count0),
+ .errors(errors0),
+ .locked(locked0),
+ .failed(failed0)
+ );
+
+ cap_pattern_verifier #(
+ .WIDTH(14),
+ .PATTERN("RAMP"),
+ .RAMP_START(14'h0100),
+ .RAMP_STOP(14'h0FFF),
+ .RAMP_INCR(14'h0001),
+ .NTH_CYCLE(1)
+ ) dut1 (
+ .clk(clk),
+ .rst(rst),
+ .valid(data1.axis.tvalid),
+ .data(data1.axis.tdata[13:0]),
+ .count(count1),
+ .errors(errors1),
+ .locked(locked1),
+ .failed(failed1)
+ );
+
+ //------------------------------------------
+ //Main thread for testbench execution
+ //------------------------------------------
+ initial begin : tb_main
+ localparam ASYNC_RST_LEN = 10;
+ localparam OUTPUT_LATENCY = 2;
+
+ `TEST_CASE_START("Wait for reset");
+ while (rst) @(posedge clk);
+ @(posedge clk);
+ `TEST_CASE_DONE((rst==0));
+
+ repeat (10) @(posedge clk);
+
+ `TEST_CASE_START("Check reset state");
+ `ASSERT_ERROR(count0==0,"Invalid state: count");
+ `ASSERT_ERROR(errors0==0,"Invalid state: errors");
+ `ASSERT_ERROR(~locked0,"Invalid state: locked");
+ `ASSERT_ERROR(~failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ `TEST_CASE_START("Simple ramp");
+ data0.push_ramp_pkt(100, 64'd0, 64'h1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count0==100,"Invalid state: count");
+ `ASSERT_ERROR(errors0==0,"Invalid state: errors");
+ `ASSERT_ERROR(locked0,"Invalid state: locked");
+ `ASSERT_ERROR(~failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ rst <= 1;
+ @(posedge clk);
+ rst <= 0;
+ repeat (ASYNC_RST_LEN) @(posedge clk);
+
+ `TEST_CASE_START("Multiple ramp iterations");
+ data0.push_ramp_pkt(65536, 64'd0, 64'h1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count0==65536,"Invalid state: count");
+ `ASSERT_ERROR(errors0==0,"Invalid state: errors");
+ `ASSERT_ERROR(locked0,"Invalid state: locked");
+ `ASSERT_ERROR(~failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ #(`NS_PER_TICK*0.3333)
+ rst <= 1;
+ #(`NS_PER_TICK*3.25)
+ rst <= 0;
+ repeat (ASYNC_RST_LEN+1) @(posedge clk);
+
+ `TEST_CASE_START("Simple ramp after async reset");
+ data0.push_ramp_pkt(100, 64'd0, 64'h1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count0==100,"Invalid state: count");
+ `ASSERT_ERROR(errors0==0,"Invalid state: errors");
+ `ASSERT_ERROR(locked0,"Invalid state: locked");
+ `ASSERT_ERROR(~failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ rst <= 1;
+ @(posedge clk);
+ rst <= 0;
+ repeat (ASYNC_RST_LEN) @(posedge clk);
+
+ `TEST_CASE_START("Simple failure");
+ data0.push_ramp_pkt(9, 64'd0, 64'h1);
+ data0.push_ramp_pkt(100, 64'd10, 64'h1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count0==109,"Invalid state: count");
+ `ASSERT_ERROR(errors0==1,"Invalid state: errors");
+ `ASSERT_ERROR(locked0,"Invalid state: locked");
+ `ASSERT_ERROR(failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ `TEST_CASE_START("Late start success");
+ data1.push_ramp_pkt(4096, 64'd0, 64'h1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count1==4096-256,"Invalid state: count");
+ `ASSERT_ERROR(errors1==0,"Invalid state: errors");
+ `ASSERT_ERROR(locked1,"Invalid state: locked");
+ `ASSERT_ERROR(~failed1,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ rst <= 1;
+ @(posedge clk);
+ rst <= 0;
+ repeat (ASYNC_RST_LEN) @(posedge clk);
+
+ `TEST_CASE_START("Late failure overshoot");
+ data1.push_ramp_pkt(4097, 64'd0, 64'h1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count1==4097-256,"Invalid state: count");
+ `ASSERT_ERROR(errors1==1,"Invalid state: errors");
+ `ASSERT_ERROR(locked1,"Invalid state: locked");
+ `ASSERT_ERROR(failed1,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ `TEST_CASE_START("Metastable data in reset");
+ @(posedge clk);
+ rst <= 1;
+ data0.push_word(14'hXXXX, 1'b0);
+ data0.push_word(14'hXXXX, 1'b1);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count0==0,"Invalid state: count");
+ `ASSERT_ERROR(errors0==0,"Invalid state: errors");
+ `ASSERT_ERROR(~locked0,"Invalid state: locked");
+ `ASSERT_ERROR(~failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ `TEST_CASE_START("Metastable data out of reset");
+ rst <= 0;
+ repeat (ASYNC_RST_LEN) @(posedge clk);
+ data0.push_word(14'h0000, 1'b0);
+ data0.push_word(14'h0001, 1'b0);
+ data0.push_word(14'h0002, 1'b0);
+ data0.push_word(14'hXXXX, 1'b0);
+ data0.push_word(14'hXXXX, 1'b0);
+ data0.push_word(14'h0005, 1'b0);
+ repeat (OUTPUT_LATENCY) @(posedge clk);
+ `ASSERT_ERROR(count0==6,"Invalid state: count");
+ `ASSERT_ERROR(errors0==2,"Invalid state: errors");
+ `ASSERT_ERROR(locked0,"Invalid state: locked");
+ `ASSERT_ERROR(failed0,"Invalid state: failed");
+ `TEST_CASE_DONE(1);
+
+ `TEST_BENCH_DONE;
+
+ end
+
+endmodule
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
new file mode 100644
index 000000000..ecb744086
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_dual_mode_tb.v
@@ -0,0 +1,780 @@
+//
+// 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/cat_io_lvds_tb.v b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_tb.v
new file mode 100644
index 000000000..91cd6f938
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/cat_io_lvds/cat_io_lvds_tb.v
@@ -0,0 +1,350 @@
+//
+// Copyright 2016 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: cat_io_lvds_tb
+//
+// Description: Testbench for cat_io_lvds.
+//
+
+`timescale 1ns/1ps
+
+module cat_io_lvds_tb();
+
+ localparam CLK_PERIOD = 10;
+ localparam CLK200_PERIOD = 2.5;
+
+ localparam FRAME_SAMPLE = 0;
+
+ localparam USE_CLOCK_IDELAY = 1;
+ localparam USE_DATA_IDELAY = 1;
+ localparam DATA_IDELAY_MODE = "VAR_LOAD";
+ localparam CLOCK_IDELAY_MODE = "VAR_LOAD";
+ localparam INPUT_CLOCK_DELAY = 0;
+ localparam INPUT_DATA_DELAY = 0;
+ localparam USE_CLOCK_ODELAY = 1;
+ localparam USE_DATA_ODELAY = 1;
+ localparam DATA_ODELAY_MODE = "VAR_LOAD";
+ localparam CLOCK_ODELAY_MODE = "VAR_LOAD";
+ localparam OUTPUT_CLOCK_DELAY = 0;
+ localparam OUTPUT_DATA_DELAY = 0;
+
+ reg [8*19:0] test_status;
+
+ reg clk = 0;
+ reg rx_clk = 0;
+ reg clk200 = 0;
+
+ reg reset;
+ reg mimo;
+ reg [5:0] rx_d;
+ reg rx_frame;
+ reg [7:0] count;
+
+// initial $dumpfile("catcap_ddr_lvds_tb.vcd");
+// initial $dumpvars(0,catcap_ddr_lvds_tb);
+
+ wire [11:0] i0 = {4'hA,count};
+ wire [11:0] q0 = {4'hB,count};
+ wire [11:0] i1 = {4'hC,count};
+ wire [11:0] q1 = {4'hD,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
+ //---------------------------------------------------------------------------
+
+ task BURST;
+ input [31:0] len;
+ input do_mimo;
+ begin
+ count <= 0;
+ 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 <= ~FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= q0[5:0];
+ rx_frame <= ~FRAME_SAMPLE;
+
+ if (do_mimo) begin
+ // Channel 1 sample
+ @(posedge clk);
+ rx_d <= i1[11:6];
+ rx_frame <= FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= q1[11:6];
+ rx_frame <= FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= i1[5:0];
+ rx_frame <= 0;
+ @(posedge clk);
+ rx_d <= q1[5:0];
+ rx_frame <= 0;
+ end else begin
+ if (!FRAME_SAMPLE) begin
+ // When we frame every two samples (one from each channel), in
+ // MIMO mode, we should only grab channel 0. So input garbage on
+ // channel 1 to make sure the data doesn't get used.
+ @(posedge clk);
+ rx_d <= 6'bXXXXXX;
+ rx_frame <= FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= 6'bXXXXXX;
+ rx_frame <= FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= 6'bXXXXXX;
+ rx_frame <= 0;
+ @(posedge clk);
+ rx_d <= 6'bXXXXXX;
+ rx_frame <= 0;
+ end else begin
+ // When every sample is framed, we might sync align to either
+ // channel (no way to tell them apart), so input the channel 1
+ // data, but we should only see one channel's data duplicated on
+ // both outputs.
+ @(posedge clk);
+ rx_d <= i1[11:6];
+ rx_frame <= FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= q1[11:6];
+ rx_frame <= FRAME_SAMPLE;
+ @(posedge clk);
+ rx_d <= i1[5:0];
+ rx_frame <= 0;
+ @(posedge clk);
+ rx_d <= q1[5:0];
+ rx_frame <= 0;
+ end
+ end
+
+ count <= count + 1;
+ end
+ end
+ endtask // BURST
+
+
+ //---------------------------------------------------------------------------
+ // Test Procedure
+ //---------------------------------------------------------------------------
+
+ initial
+ begin
+ // Initial values
+ test_status <= "Reset";
+ reset = 1;
+ mimo = 1;
+ ctrl_in_data_delay = 5'd0;
+ ctrl_in_clk_delay = 5'd8;
+ ctrl_ld_in_data_delay = 1'b0;
+ ctrl_ld_in_clk_delay = 1'b0;
+ ctrl_out_data_delay = 5'd0;
+ ctrl_out_clk_delay = 5'd16;
+ ctrl_ld_out_data_delay = 1'b0;
+ ctrl_ld_out_clk_delay = 1'b0;
+ repeat(10) @(negedge rx_clk);
+ reset = 0;
+ @(negedge rx_clk);
+
+ // Load new input delay values
+ test_status <= "Load input delays";
+ ctrl_ld_in_data_delay = 1'b1;
+ ctrl_ld_in_clk_delay = 1'b1;
+ @(negedge rx_clk);
+ ctrl_ld_in_data_delay = 1'b0;
+ ctrl_ld_in_clk_delay = 1'b0;
+
+ // Load new output delay values
+ test_status <= "Load output delays";
+ ctrl_ld_out_data_delay = 1'b1;
+ ctrl_ld_out_clk_delay = 1'b1;
+ @(negedge rx_clk);
+ ctrl_ld_out_data_delay = 1'b0;
+ ctrl_ld_out_clk_delay = 1'b0;
+
+ // Input data until the Rx circuit aligns
+ test_status <= "Wait align";
+ while (!rx_aligned) begin
+ BURST(1,1);
+ end
+
+ // Input some new samples
+ test_status <= "Burst 1 (MIMO)";
+ BURST(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)";
+ BURST(30, 1);
+
+ // 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
+
+ // Switch to SISO mode
+ test_status <= "Burst 3 (SISO)";
+ BURST(25,0);
+
+ repeat(50) @(negedge rx_clk);
+
+
+ $finish;
+ end
+
+
+ //---------------------------------------------------------------------------
+ // DUT
+ //---------------------------------------------------------------------------
+
+ // Loop the Rx interface of cat_io_lvds back to its Tx interface
+ always @(posedge radio_clk) begin
+ tx_i0 = rx_i0;
+ tx_q0 = rx_q0;
+ tx_i1 = rx_i1;
+ tx_q1 = rx_q1;
+ end
+
+ cat_io_lvds #(
+ .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_i0 (
+ .rst (reset),
+ .clk200 (clk200),
+
+ // Data and frame timing
+ .mimo (mimo),
+ .frame_sample (FRAME_SAMPLE[0]),
+
+ // 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),
+ .radio_clk_2x (),
+ .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_tb
diff --git a/fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.build b/fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.build
new file mode 100755
index 000000000..827ab0628
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.build
@@ -0,0 +1,21 @@
+
+#!/bin/sh
+
+rm -rf isim*
+rm -rf catcap_tb
+rm -rf fuse*
+\
+# --sourcelibdir ../../models \
+
+vlogcomp \
+ --sourcelibext .v \
+ --sourcelibdir ../../coregen \
+ --sourcelibdir ../../control_lib \
+ --sourcelibdir . \
+ --sourcelibdir $XILINX/verilog/src \
+ --sourcelibdir $XILINX/verilog/src/unisims \
+ --work work \
+ catcap_tb.v
+
+
+fuse -o catcap_tb catcap_tb \ No newline at end of file
diff --git a/fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.v b/fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.v
new file mode 100644
index 000000000..f300cc32f
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/catcap/catcap_tb.v
@@ -0,0 +1,115 @@
+//
+// Copyright 2014 Ettus Research LLC
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+`timescale 1ns/1ps
+
+module catcap_tb();
+
+ wire GSR, GTS;
+ glbl glbl( );
+
+ reg clk = 0;
+ reg ddrclk = 0;
+ reg reset = 1;
+
+ always #100 clk = ~clk;
+ always @(negedge clk) ddrclk <= ~ddrclk;
+
+ initial $dumpfile("catcap_tb.vcd");
+ initial $dumpvars(0,catcap_tb);
+
+ wire [11:0] i0 = {4'hA,count};
+ wire [11:0] q0 = {4'hB,count};
+ wire [11:0] i1 = {4'hC,count};
+ wire [11:0] q1 = {4'hD,count};
+
+ reg mimo;
+ reg [11:0] pins;
+ reg frame;
+ reg [7:0] count;
+
+ initial
+ begin
+ #1000 reset = 0;
+ MIMO_BURST(4);
+ MIMO_BURST(5);
+ BURST(4);
+ BURST(5);
+ #2000;
+ $finish;
+ end
+
+ task BURST;
+ input [7:0] len;
+ begin
+ frame <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ mimo <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge ddrclk);
+ count <= 0;
+ repeat(len)
+ begin
+ @(posedge clk);
+ pins <= i0;
+ frame <= 1;
+ @(posedge clk);
+ pins <= q0;
+ frame <= 0;
+ count <= count + 1;
+ end
+ end
+ endtask // BURST
+
+ task MIMO_BURST;
+ input [7:0] len;
+ begin
+ frame <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ mimo <= 1;
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge ddrclk);
+ count <= 0;
+ repeat(len)
+ begin
+ @(posedge clk);
+ pins <= i0;
+ frame <= 1;
+ @(posedge clk);
+ pins <= q0;
+ @(posedge clk);
+ pins <= i1;
+ frame <= 0;
+ @(posedge clk);
+ pins <= q1;
+ count <= count + 1;
+ end
+ @(posedge clk);
+ @(posedge clk);
+ end
+ endtask // MIMO_BURST
+
+ wire rx_clk, rx_strobe;
+ wire [11:0] i0o,i1o,q0o,q1o;
+
+ catcap_ddr_cmos catcap
+ (.data_clk(ddrclk),
+ .reset(reset),
+ .mimo(mimo),
+ .rx_frame(frame),
+ .rx_d(pins),
+ .rx_clk(rx_clk),
+ .rx_strobe(rx_strobe),
+ .i0(i0o),.q0(q0o),
+ .i1(i1o),.q1(q1o));
+
+endmodule // hb_chain_tb
diff --git a/fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.build b/fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.build
new file mode 100755
index 000000000..072495479
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.build
@@ -0,0 +1,21 @@
+
+#!/bin/sh
+
+rm -rf isim*
+rm -rf catgen_tb
+rm -rf fuse*
+\
+# --sourcelibdir ../../models \
+
+vlogcomp \
+ --sourcelibext .v \
+ --sourcelibdir ../../coregen \
+ --sourcelibdir ../../control_lib \
+ --sourcelibdir . \
+ --sourcelibdir $XILINX/verilog/src \
+ --sourcelibdir $XILINX/verilog/src/unisims \
+ --work work \
+ catgen_tb.v
+
+
+fuse -o catgen_tb catgen_tb \ No newline at end of file
diff --git a/fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.v b/fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.v
new file mode 100644
index 000000000..e55f2de32
--- /dev/null
+++ b/fpga/usrp3/lib/sim/io_cap_gen/catgen/catgen_tb.v
@@ -0,0 +1,103 @@
+//
+// Copyright 2014 Ettus Research LLC
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+`timescale 1ns/1ps
+
+module catgen_tb();
+
+ wire GSR, GTS;
+ glbl glbl( );
+
+ reg clk = 0;
+ reg reset = 1;
+ wire ddrclk;
+
+ always #100 clk = ~clk;
+
+ initial $dumpfile("catgen_tb.vcd");
+ initial $dumpvars(0,catgen_tb);
+
+ wire [11:0] pins;
+ wire frame;
+
+ reg mimo;
+ reg [7:0] count;
+ reg tx_strobe;
+
+ wire [11:0] i0 = {4'hA,count};
+ wire [11:0] q0 = {4'hB,count};
+ wire [11:0] i1 = {4'hC,count};
+ wire [11:0] q1 = {4'hD,count};
+
+ initial
+ begin
+ #1000 reset = 0;
+ BURST(4);
+ BURST(5);
+ MIMO_BURST(4);
+ MIMO_BURST(5);
+ #2000;
+ $finish;
+ end
+
+ task BURST;
+ input [7:0] len;
+
+ begin
+ tx_strobe <= 0;
+ mimo <= 0;
+ count <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ repeat(len)
+ begin
+ tx_strobe <= 1;
+ @(posedge clk);
+ count <= count + 1;
+ end
+ tx_strobe <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+ end
+ endtask // BURST
+
+ task MIMO_BURST;
+ input [7:0] len;
+
+ begin
+ tx_strobe <= 0;
+ mimo <= 1;
+ count <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ repeat(len)
+ begin
+ tx_strobe <= 1;
+ @(posedge clk);
+ tx_strobe <= 0;
+ @(posedge clk);
+ count <= count + 1;
+ end
+ tx_strobe <= 0;
+ @(posedge clk);
+ @(posedge clk);
+ @(posedge clk);
+ end
+ endtask // BURST
+
+ catgen_ddr_cmos catgen
+ (.data_clk(ddrclk),
+ .reset(reset),
+ .mimo(mimo),
+ .tx_frame(frame),
+ .tx_d(pins),
+ .tx_clk(clk),
+ .tx_strobe(tx_strobe),
+ .i0(i0),.q0(q0),
+ .i1(i1),.q1(q1));
+
+endmodule // hb_chain_tb