aboutsummaryrefslogtreecommitdiffstats
path: root/fpga
diff options
context:
space:
mode:
authorWade Fife <wade.fife@ettus.com>2021-02-13 16:45:43 -0600
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-03 11:26:54 -0500
commit7f36cced81fb05d3cc107a0a0773fcdfc26f8d64 (patch)
tree44a1759c5432ab746e4d12177a541edc7b3c72be /fpga
parentf6f43baa0012a24ed5a8cdf49240e0cb4a1f7d04 (diff)
downloaduhd-7f36cced81fb05d3cc107a0a0773fcdfc26f8d64.tar.gz
uhd-7f36cced81fb05d3cc107a0a0773fcdfc26f8d64.tar.bz2
uhd-7f36cced81fb05d3cc107a0a0773fcdfc26f8d64.zip
fpga: lib: Add 2 to 1 gearbox module
Diffstat (limited to 'fpga')
-rw-r--r--fpga/usrp3/lib/control/Makefile.srcs1
-rw-r--r--fpga/usrp3/lib/control/gearbox_2x1.v180
-rw-r--r--fpga/usrp3/lib/sim/control/gearbox_2x1/Makefile37
-rw-r--r--fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_all_tb.sv19
-rw-r--r--fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_tb.sv280
5 files changed, 517 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/control/Makefile.srcs b/fpga/usrp3/lib/control/Makefile.srcs
index 9a24f44fa..47ea3a53a 100644
--- a/fpga/usrp3/lib/control/Makefile.srcs
+++ b/fpga/usrp3/lib/control/Makefile.srcs
@@ -23,6 +23,7 @@ binary_encoder.v \
db_control.v \
fe_control.v \
filter_bad_sid.v \
+gearbox_2x1.v \
gpio_atr_io.v \
gpio_atr.v \
gray2bin.v \
diff --git a/fpga/usrp3/lib/control/gearbox_2x1.v b/fpga/usrp3/lib/control/gearbox_2x1.v
new file mode 100644
index 000000000..1b3eccc9a
--- /dev/null
+++ b/fpga/usrp3/lib/control/gearbox_2x1.v
@@ -0,0 +1,180 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: gearbox_2x1
+//
+// Description:
+//
+// Moves data between two clock domains at a constant data rate. This module
+// requires that the clocks be related and that there is a 2:1 ratio between
+// the two clocks. Static timing analysis is assumed for the clock domain
+// crossing. The module supports going from a slow clock to a fast clock (2
+// words per 1x clock cycle to 1 word per 2x clock cycle) or from a fast
+// clock to a slow clock (1 word per 1x clock cycle to 2 words per 2x clock
+// cycles) depending on the parameters provided.
+//
+// Note that there are no tready signals, so downstream logic must always be
+// ready.
+//
+// Parameters:
+//
+// WORD_W : Bits per word
+// IN_WORDS : Number of input words per clock cycle
+// OUT_WORDS : Number of output words per clock cycle
+// BIG_ENDIAN : Order in which to input/output words when multiple words per
+// clock cycle are needed. Little endian means the first word is
+// in the least-significant position. Big endian means the first
+// word is in the most-significant position.
+//
+
+
+module gearbox_2x1 #(
+ parameter WORD_W = 8,
+ parameter IN_WORDS = 2,
+ parameter OUT_WORDS = 1,
+ parameter BIG_ENDIAN = 0
+) (
+ input wire i_clk,
+ input wire i_rst,
+ input wire [IN_WORDS*WORD_W-1:0] i_tdata,
+ input wire i_tvalid,
+
+ input wire o_clk,
+ input wire o_rst,
+ output reg [OUT_WORDS*WORD_W-1:0] o_tdata,
+ output reg o_tvalid = 1'b0
+);
+
+ localparam IN_W = WORD_W * IN_WORDS;
+ localparam OUT_W = WORD_W * OUT_WORDS;
+
+ generate
+
+ // Make sure the ratios are supported
+ if (IN_WORDS != 2*OUT_WORDS && OUT_WORDS != 2*IN_WORDS) begin : gen_ERROR
+ IN_WORDS_and_OUT_WORDS_must_have_a_2_to_1_ratio();
+ end
+
+
+ //-------------------------------------------------------------------------
+ // 2 words to 1 word (slow clock to fast clock)
+ //-------------------------------------------------------------------------
+
+ if (IN_WORDS > OUT_WORDS) begin : gen_slow_to_fast
+ reg [IN_W-1:0] i_tdata_reg;
+ reg i_tvalid_reg;
+ reg i_toggle = 1'b0;
+
+ always @(posedge i_clk) begin
+ if (i_rst) begin
+ i_tdata_reg <= 'bX;
+ i_tvalid_reg <= 1'b0;
+ i_toggle <= 1'b0;
+ end else begin
+ i_tdata_reg <= i_tdata;
+ i_tvalid_reg <= i_tvalid;
+ if (i_tvalid) begin
+ i_toggle <= ~i_toggle;
+ end
+ end
+ end
+
+ reg [IN_W-1:0] o_tdata_reg;
+ reg o_tvalid_reg = 1'b0;
+ reg o_toggle;
+ reg o_toggle_dly;
+ reg o_data_sel;
+
+ always @(posedge o_clk) begin
+ if (o_rst) begin
+ o_tdata_reg <= 'bX;
+ o_tvalid <= 1'b0;
+ o_tvalid_reg <= 1'b0;
+ o_toggle <= 1'bX;
+ o_toggle_dly <= 1'bX;
+ o_data_sel <= 1'bX;
+ end else begin
+ // Clock crossing
+ o_tvalid_reg <= i_tvalid_reg;
+ o_toggle <= i_toggle;
+ o_tdata_reg <= i_tdata_reg;
+
+ // Determine which output to select
+ o_toggle_dly <= o_toggle;
+ o_data_sel <= BIG_ENDIAN ^ (o_toggle == o_toggle_dly);
+
+ // Select the correct output for this clock cycle
+ o_tvalid <= o_tvalid_reg;
+ o_tdata <= o_data_sel ?
+ o_tdata_reg[0 +: OUT_W] : o_tdata_reg[IN_W/2 +: OUT_W];
+ end
+ end
+
+
+ //-------------------------------------------------------------------------
+ // 1 word to 2 words (fast clock to slow clock)
+ //-------------------------------------------------------------------------
+
+ end else begin : gen_fast_to_slow
+ reg [IN_W-1:0] i_gear_reg;
+ reg [OUT_W-1:0] i_gear_tdata;
+ reg i_gear_one_word = 1'b0;
+ reg i_gear_tvalid = 1'b0;
+ reg i_gear_tvalid_dly = 1'b0;
+
+ always @(posedge i_clk) begin
+ if (i_rst) begin
+ i_gear_reg <= 'bX;
+ i_gear_tdata <= 'bX;
+ i_gear_one_word <= 1'b0;
+ i_gear_tvalid <= 1'b0;
+ i_gear_tvalid_dly <= 1'b0;
+ end else begin
+ // Default assignments
+ i_gear_tvalid <= 1'b0;
+ i_gear_tvalid_dly <= i_gear_tvalid;
+
+ if (i_tvalid) begin
+ // Track if the gearbox has one word saved
+ i_gear_one_word <= ~i_gear_one_word;
+ i_gear_reg <= i_tdata;
+ if (i_gear_one_word) begin
+ // This is the second word, so output the new word on i_gear_reg_t*
+ i_gear_tdata <= BIG_ENDIAN ?
+ { i_gear_reg, i_tdata } : { i_tdata, i_gear_reg };
+ i_gear_tvalid <= 1'b1;
+ end
+ end
+ end
+ end
+
+ reg [OUT_W-1:0] o_gear_tdata;
+ reg o_gear_tvalid = 1'b0;
+ reg o_gear_tvalid_dly = 1'b0;
+ reg o_tvalid_reg = 1'b0;
+
+ always @(posedge o_clk) begin
+ if (o_rst) begin
+ o_gear_tvalid <= 1'b0;
+ o_gear_tvalid_dly <= 1'b0;
+ o_gear_tdata <= 'bX;
+ o_tvalid <= 1'b0;
+ o_tdata <= 'bX;
+ end else begin
+ // Clock crossing
+ o_gear_tvalid <= i_gear_tvalid;
+ o_gear_tvalid_dly <= i_gear_tvalid_dly;
+ o_gear_tdata <= i_gear_tdata;
+
+ // Control tvalid
+ o_tvalid <= o_gear_tvalid | o_gear_tvalid_dly;
+ o_tdata <= o_gear_tdata;
+ end
+ end
+ end
+
+ endgenerate
+
+endmodule
diff --git a/fpga/usrp3/lib/sim/control/gearbox_2x1/Makefile b/fpga/usrp3/lib/sim/control/gearbox_2x1/Makefile
new file mode 100644
index 000000000..48651a305
--- /dev/null
+++ b/fpga/usrp3/lib/sim/control/gearbox_2x1/Makefile
@@ -0,0 +1,37 @@
+#
+# Copyright 2021 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+
+#-------------------------------------------------
+# Top-of-Makefile
+#-------------------------------------------------
+# Define BASE_DIR to point to the "top" dir.
+BASE_DIR = $(abspath ../../../../top)
+# Include viv_sim_preample after defining BASE_DIR
+include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
+
+#-------------------------------------------------
+# Design Specific
+#-------------------------------------------------
+
+DESIGN_SRCS += $(abspath \
+$(abspath ../../../control/gearbox_2x1.v) \
+)
+
+#-------------------------------------------------
+# Testbench Specific
+#-------------------------------------------------
+SIM_TOP = gearbox_2x1_all_tb
+SIM_SRCS = \
+$(abspath gearbox_2x1_tb.sv) \
+$(abspath gearbox_2x1_all_tb.sv) \
+
+#-------------------------------------------------
+# 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/control/gearbox_2x1/gearbox_2x1_all_tb.sv b/fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_all_tb.sv
new file mode 100644
index 000000000..e8cfd5591
--- /dev/null
+++ b/fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_all_tb.sv
@@ -0,0 +1,19 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: gearbox_2x1_all_tb
+//
+// Description: Top-level testbench for gearbox_2x1, testing different
+// configurations of the module.
+//
+
+module gearbox_2x1_all_tb;
+ gearbox_2x1_tb #(.IN_WORDS(1), .OUT_WORDS(2), .BIG_ENDIAN(0), .PHASE(0)) gearbox_2x1_tb_1();
+ gearbox_2x1_tb #(.IN_WORDS(2), .OUT_WORDS(1), .BIG_ENDIAN(0), .PHASE(0)) gearbox_2x1_tb_2();
+ gearbox_2x1_tb #(.IN_WORDS(2), .OUT_WORDS(4), .BIG_ENDIAN(1), .PHASE(0)) gearbox_2x1_tb_3();
+ gearbox_2x1_tb #(.IN_WORDS(4), .OUT_WORDS(2), .BIG_ENDIAN(1), .PHASE(0)) gearbox_2x1_tb_4();
+ gearbox_2x1_tb #(.IN_WORDS(1), .OUT_WORDS(2), .BIG_ENDIAN(0), .PHASE(1)) gearbox_2x1_tb_5();
+ gearbox_2x1_tb #(.IN_WORDS(2), .OUT_WORDS(1), .BIG_ENDIAN(0), .PHASE(1)) gearbox_2x1_tb_6();
+endmodule : gearbox_2x1_all_tb
diff --git a/fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_tb.sv b/fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_tb.sv
new file mode 100644
index 000000000..22278ce1d
--- /dev/null
+++ b/fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_tb.sv
@@ -0,0 +1,280 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: gearbox_2x1_tb
+//
+// Description: Testbench for gearbox_2x1.
+//
+
+`default_nettype none
+
+
+module gearbox_2x1_tb #(
+ parameter IN_WORDS = 2,
+ parameter OUT_WORDS = 1,
+ parameter BIG_ENDIAN = 0,
+ parameter PHASE = 1
+);
+
+ `include "test_exec.svh"
+
+ import PkgTestExec::*;
+
+ localparam WORD_W = 4;
+
+ // Clock periods
+ localparam real SLOW_CLK_PER_NS = 10.0;
+ localparam real FAST_CLK_PER_NS = SLOW_CLK_PER_NS / 2.0;
+ localparam real CLK_IN_PER_NS = (IN_WORDS > OUT_WORDS) ?
+ SLOW_CLK_PER_NS : FAST_CLK_PER_NS;
+ localparam real CLK_OUT_PER_NS = (OUT_WORDS > IN_WORDS) ?
+ SLOW_CLK_PER_NS : FAST_CLK_PER_NS;
+
+ // Give the faster clock a T/2 phase offset
+ localparam FAST_CLK_PHASE = PHASE * (FAST_CLK_PER_NS / 2.0);
+ localparam real IN_PHASE = (IN_WORDS > OUT_WORDS) ? 0.0 : FAST_CLK_PHASE;
+ localparam real OUT_PHASE = (OUT_WORDS > IN_WORDS) ? 0.0 : FAST_CLK_PHASE;
+
+ // Get gearbox total input and output widths
+ localparam IN_WORD_W = WORD_W * IN_WORDS;
+ localparam OUT_WORD_W = WORD_W * OUT_WORDS;
+
+ // Number of simulation cycles to run for
+ localparam NUM_TEST_CYCLES = 50000;
+
+
+ //---------------------------------------------------------------------------
+ // Clocks and Resets
+ //---------------------------------------------------------------------------
+
+ bit i_clk;
+ bit o_clk;
+
+ bit i_rst;
+ bit o_rst;
+
+ sim_clock_gen #(.PERIOD(CLK_IN_PER_NS), .AUTOSTART(0), .PHASE(IN_PHASE))
+ clk_gen_in (.clk(i_clk), .rst(i_rst));
+ sim_clock_gen #(.PERIOD(CLK_OUT_PER_NS),.AUTOSTART(0), .PHASE(OUT_PHASE))
+ clk_gen_out (.clk(o_clk), .rst(o_rst));
+
+
+ //---------------------------------------------------------------------------
+ // Device Under Test (DUT)
+ //---------------------------------------------------------------------------
+
+ bit [ IN_WORD_W-1:0] i_tdata = { IN_WORD_W { 1'bX }};
+ bit i_tvalid = 0;
+ bit [OUT_WORD_W-1:0] o_tdata;
+ bit o_tvalid;
+
+ gearbox_2x1 #(
+ .WORD_W (WORD_W),
+ .IN_WORDS (IN_WORDS),
+ .OUT_WORDS (OUT_WORDS),
+ .BIG_ENDIAN (BIG_ENDIAN)
+ ) gearbox_2x1_i (
+ .i_clk (i_clk),
+ .i_rst (i_rst),
+ .i_tdata (i_tdata),
+ .i_tvalid (i_tvalid),
+ .o_clk (o_clk),
+ .o_rst (o_rst),
+ .o_tdata (o_tdata),
+ .o_tvalid (o_tvalid)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Input Generator
+ //---------------------------------------------------------------------------
+
+ bit enable_input = 0;
+ longint i_count;
+ mailbox #(bit [IN_WORD_W-1:0]) i_queue = new;
+
+ initial forever begin : gen_input
+ clk_gen_in.clk_wait_r();
+ if (i_rst || !enable_input) begin
+ i_tdata <= { IN_WORD_W { 1'bX }};
+ i_tvalid <= 1'b0;
+ continue;
+ end
+ // Input randomly to the gearbox
+ if ($urandom() % 2 == 0) begin
+ bit [ IN_WORD_W-1:0] next_input;
+ next_input = $urandom();
+ i_count = i_count + 1;
+ i_tdata <= next_input;
+ i_tvalid <= 1'b1;
+ i_queue.put(next_input);
+ end else begin
+ i_tdata <= { IN_WORD_W { 1'bX }};
+ i_tvalid <= 1'b0;
+ end
+ end : gen_input
+
+
+ //---------------------------------------------------------------------------
+ // Packer/Unpacker
+ //---------------------------------------------------------------------------
+ //
+ // Take the data from i_queue and repack it into o_queue with the correct
+ // width and using the correct endianness.
+ //
+ //---------------------------------------------------------------------------
+
+ mailbox #(bit [OUT_WORD_W-1:0]) o_queue = new;
+
+ initial if (IN_WORDS > OUT_WORDS) begin : unpacker
+ bit [IN_WORD_W-1:0] in_word;
+ forever begin
+ i_queue.get(in_word);
+ if (BIG_ENDIAN) begin
+ for (int i = IN_WORDS/OUT_WORDS-1; i >= 0; i--) begin
+ o_queue.put(in_word[OUT_WORD_W*i +: OUT_WORD_W]);
+ end
+ end else begin
+ for (int i = 0; i < IN_WORDS/OUT_WORDS; i++) begin
+ o_queue.put(in_word[OUT_WORD_W*i +: OUT_WORD_W]);
+ end
+ end
+ end
+ end else begin : packer
+ bit [OUT_WORD_W-1:0] out_word;
+ forever begin
+ if (BIG_ENDIAN) begin
+ for (int i = OUT_WORDS/IN_WORDS-1; i >= 0; i--) begin
+ i_queue.get(out_word[IN_WORD_W*i +: IN_WORD_W]);
+ end
+ end else begin
+ for (int i = 0; i < OUT_WORDS/IN_WORDS; i++) begin
+ i_queue.get(out_word[IN_WORD_W*i +: IN_WORD_W]);
+ end
+ end
+ o_queue.put(out_word);
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Output Checker
+ //---------------------------------------------------------------------------
+
+ longint o_count;
+ bit [OUT_WORD_W-1:0] expected, actual;
+
+ initial forever begin : check_output
+ string msg;
+ clk_gen_out.clk_wait_r();
+ if (o_rst) begin
+ continue;
+ end
+ if (o_tvalid) begin
+ o_count++;
+ actual = o_tdata;
+ o_queue.get(expected);
+ msg = $sformatf("Output didn't match expected value! Expected 0x%0X, received 0x%0X.",
+ expected, actual);
+ `ASSERT_ERROR(actual == expected, msg);
+ end
+ end : check_output
+
+
+ //---------------------------------------------------------------------------
+ // Main Test Process
+ //---------------------------------------------------------------------------
+
+ initial begin : tb_main
+ string msg;
+ string tb_name;
+ int min_out_words, max_out_words;
+
+ // Initialize the test exec object for this testbench
+ tb_name = $sformatf( {
+ "gearbox_2x1_tb\n",
+ "IN_WORDS = %01d\n",
+ "OUT_WORDS = %01d\n",
+ "BIG_ENDIAN = %01d\n",
+ "PHASE = %01d" },
+ IN_WORDS, OUT_WORDS, BIG_ENDIAN, PHASE
+ );
+ test.start_tb(tb_name);
+
+ // Don't start the clocks until after start_tb() returns. This ensures that
+ // the clocks aren't toggling while other instances of this testbench are
+ // running, which speeds up simulation time.
+ clk_gen_in.start();
+ clk_gen_out.start();
+
+ //--------------------------------
+ // Reset
+ //--------------------------------
+
+ test.start_test("Reset", 10us);
+ clk_gen_in.reset();
+ clk_gen_out.reset();
+ if (i_rst) @i_rst;
+ if (o_rst) @o_rst;
+ test.end_test();
+
+ //--------------------------------
+ // Test Sequences
+ //--------------------------------
+
+ test.start_test("Random data", 10ms);
+
+ enable_input <= 1;
+
+ // Let it run for a while
+ clk_gen_in.clk_wait_r(NUM_TEST_CYCLES);
+
+ // Stop inputting and wait long enough for any data to propagate through
+ enable_input <= 0;
+ repeat (8*OUT_WORDS) @i_clk;
+ repeat (8*IN_WORDS) @o_clk;
+
+ // Calculate how many words we expect to have received on the output. We
+ // might be in the middle of a word, so it might be less than what was
+ // input.
+ if (OUT_WORDS > IN_WORDS) begin
+ min_out_words = ((i_count * IN_WORDS) / OUT_WORDS) * OUT_WORDS;
+ max_out_words = min_out_words;
+ end else begin
+ min_out_words = (i_count-1) * IN_WORDS;
+ max_out_words = i_count * IN_WORDS;
+ end
+
+ // Make sure the word counts match
+ msg = $sformatf("Word counts don't match. Input %0d, output %0d.",
+ IN_WORDS*i_count, OUT_WORDS*o_count);
+ `ASSERT_ERROR(o_count*OUT_WORDS >= min_out_words &&
+ o_count*OUT_WORDS <= max_out_words , msg);
+ msg = $sformatf("Only %0d words input. Expected about %0d.",
+ i_count*IN_WORDS, 0.5*NUM_TEST_CYCLES*IN_WORDS);
+ `ASSERT_ERROR(i_count > 0.4*NUM_TEST_CYCLES, msg);
+
+ $display("Tested %0d output words", o_count);
+
+ test.end_test();
+
+ //--------------------------------
+ // Finish Up
+ //--------------------------------
+
+ // End the TB, but don't $finish, since we don't want to kill other
+ // instances of this testbench that may be running.
+ test.end_tb(0);
+
+ // Kill the clocks to end this instance of the testbench
+ clk_gen_in.kill();
+ clk_gen_out.kill();
+
+ end : tb_main
+
+endmodule : gearbox_2x1_tb
+
+
+`default_nettype wire