aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/sim/io_cap_gen
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-01-23 16:10:22 -0800
committerMartin Braun <martin.braun@ettus.com>2020-01-28 09:35:36 -0800
commitbafa9d95453387814ef25e6b6256ba8db2df612f (patch)
tree39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/lib/sim/io_cap_gen
parent3075b981503002df3115d5f1d0b97d2619ba30f2 (diff)
downloaduhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.gz
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.bz2
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.zip
Merge FPGA repository back into UHD repository
The FPGA codebase was removed from the UHD repository in 2014 to reduce the size of the repository. However, over the last half-decade, the split between the repositories has proven more burdensome than it has been helpful. By merging the FPGA code back, it will be possible to create atomic commits that touch both FPGA and UHD codebases. Continuous integration testing is also simplified by merging the repositories, because it was previously difficult to automatically derive the correct UHD branch when testing a feature branch on the FPGA repository. This commit also updates the license files and paths therein. We are therefore merging the repositories again. Future development for FPGA code will happen in the same repository as the UHD host code and MPM code. == Original Codebase and Rebasing == The original FPGA repository will be hosted for the foreseeable future at its original local location: https://github.com/EttusResearch/fpga/ It can be used for bisecting, reference, and a more detailed history. The final commit from said repository to be merged here is 05003794e2da61cabf64dd278c45685a7abad7ec. This commit is tagged as v4.0.0.0-pre-uhd-merge. If you have changes in the FPGA repository that you want to rebase onto the UHD repository, simply run the following commands: - Create a directory to store patches (this should be an empty directory): mkdir ~/patches - Now make sure that your FPGA codebase is based on the same state as the code that was merged: cd src/fpga # Or wherever your FPGA code is stored git rebase v4.0.0.0-pre-uhd-merge Note: The rebase command may look slightly different depending on what exactly you're trying to rebase. - Create a patch set for your changes versus v4.0.0.0-pre-uhd-merge: git format-patch v4.0.0.0-pre-uhd-merge -o ~/patches Note: Make sure that only patches are stored in your output directory. It should otherwise be empty. Make sure that you picked the correct range of commits, and only commits you wanted to rebase were exported as patch files. - Go to the UHD repository and apply the patches: cd src/uhd # Or wherever your UHD repository is stored git am --directory fpga ~/patches/* rm -rf ~/patches # This is for cleanup == Contributors == The following people have contributed mainly to these files (this list is not complete): Co-authored-by: Alex Williams <alex.williams@ni.com> Co-authored-by: Andrej Rode <andrej.rode@ettus.com> Co-authored-by: Ashish Chaudhari <ashish@ettus.com> Co-authored-by: Ben Hilburn <ben.hilburn@ettus.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Daniel Jepson <daniel.jepson@ni.com> Co-authored-by: Derek Kozel <derek.kozel@ettus.com> Co-authored-by: EJ Kreinar <ej@he360.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Ian Buckley <ian.buckley@gmail.com> Co-authored-by: Jörg Hofrichter <joerg.hofrichter@ni.com> Co-authored-by: Jon Kiser <jon.kiser@ni.com> Co-authored-by: Josh Blum <josh@joshknows.com> Co-authored-by: Jonathon Pendlum <jonathan.pendlum@ettus.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Matt Ettus <matt@ettus.com> Co-authored-by: Michael West <michael.west@ettus.com> Co-authored-by: Moritz Fischer <moritz.fischer@ettus.com> Co-authored-by: Nick Foster <nick@ettus.com> Co-authored-by: Nicolas Cuervo <nicolas.cuervo@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Paul David <paul.david@ettus.com> Co-authored-by: Ryan Marlow <ryan.marlow@ettus.com> Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com> Co-authored-by: Sylvain Munaut <tnt@246tNt.com> Co-authored-by: Trung Tran <trung.tran@ettus.com> Co-authored-by: Vidush Vishwanath <vidush.vishwanath@ettus.com> Co-authored-by: Wade Fife <wade.fife@ettus.com>
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