diff options
| author | Wade Fife <wade.fife@ettus.com> | 2021-02-13 16:45:43 -0600 | 
|---|---|---|
| committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-03 11:26:54 -0500 | 
| commit | 7f36cced81fb05d3cc107a0a0773fcdfc26f8d64 (patch) | |
| tree | 44a1759c5432ab746e4d12177a541edc7b3c72be /fpga/usrp3/lib | |
| parent | f6f43baa0012a24ed5a8cdf49240e0cb4a1f7d04 (diff) | |
| download | uhd-7f36cced81fb05d3cc107a0a0773fcdfc26f8d64.tar.gz uhd-7f36cced81fb05d3cc107a0a0773fcdfc26f8d64.tar.bz2 uhd-7f36cced81fb05d3cc107a0a0773fcdfc26f8d64.zip | |
fpga: lib: Add 2 to 1 gearbox module
Diffstat (limited to 'fpga/usrp3/lib')
| -rw-r--r-- | fpga/usrp3/lib/control/Makefile.srcs | 1 | ||||
| -rw-r--r-- | fpga/usrp3/lib/control/gearbox_2x1.v | 180 | ||||
| -rw-r--r-- | fpga/usrp3/lib/sim/control/gearbox_2x1/Makefile | 37 | ||||
| -rw-r--r-- | fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_all_tb.sv | 19 | ||||
| -rw-r--r-- | fpga/usrp3/lib/sim/control/gearbox_2x1/gearbox_2x1_tb.sv | 280 | 
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 | 
