diff options
| author | Wade Fife <wade.fife@ettus.com> | 2020-06-18 09:55:58 -0500 | 
|---|---|---|
| committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-07-16 12:15:21 -0500 | 
| commit | b3c74a712a08c54e48713427bf46ea498b8e899b (patch) | |
| tree | 79d45a9e8dfb587579265d2a483257a0266155a4 /fpga/usrp3 | |
| parent | 1ec7380cf708ab335b339f374d1f64826c700154 (diff) | |
| download | uhd-b3c74a712a08c54e48713427bf46ea498b8e899b.tar.gz uhd-b3c74a712a08c54e48713427bf46ea498b8e899b.tar.bz2 uhd-b3c74a712a08c54e48713427bf46ea498b8e899b.zip  | |
fpga: rfnoc: Add RFNoC Moving Average block
Diffstat (limited to 'fpga/usrp3')
8 files changed, 1587 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/Makefile new file mode 100644 index 000000000..4a499e512 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/Makefile @@ -0,0 +1,48 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir. Note: +# UHD_FPGA_DIR must be passed into this Makefile. +BASE_DIR = ../../../../top +# Include viv_sim_preample after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its +# dependencies. +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include $(LIB_IP_DIR)/divide_int24/Makefile.inc +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS)  \ +$(LIB_IP_DIVIDE_INT24_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_moving_avg_tb +SIM_SRCS = \ +$(abspath $(IP_BUILD_DIR)/divide_int24/sim/divide_int24.vhd) \ +$(abspath PkgMovingAverage.sv) \ +$(abspath rfnoc_block_moving_avg_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/rfnoc/blocks/rfnoc_block_moving_avg/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/Makefile.srcs new file mode 100644 index 000000000..ae134b21a --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/Makefile.srcs @@ -0,0 +1,24 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Block Sources +################################################## +# Here, list all the files that are necessary to synthesize this block. Don't +# include testbenches! +# Make sure that the source files are nicely detectable by a regex. Best to put +# one on each line. +# The first argument to addprefix is the current path to this Makefile, so the +# path list is always absolute, regardless of from where we're including or +# calling this file. RFNOC_OOT_SRCS needs to be a simply expanded variable +# (not a recursively expanded variable), and we take care of that in the build +# infrastructure. +RFNOC_OOT_SRCS += $(addprefix $(dir $(abspath $(lastword $(MAKEFILE_LIST)))), \ +noc_shell_moving_avg.v \ +rfnoc_block_moving_avg_regs.vh \ +rfnoc_moving_avg_core.v \ +rfnoc_block_moving_avg.v \ +) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/PkgMovingAverage.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/PkgMovingAverage.sv new file mode 100644 index 000000000..7c60b0084 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/PkgMovingAverage.sv @@ -0,0 +1,72 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: PkgMovingAverage +// +// Description: This package contains the MovingAverage class, which models +// the expected behavior of the moving average RFNoC block. +// + + +package PkgMovingAverage; + +  // This class implements the same moving-average computation as the DUT +  class MovingAverage; + +    local int sum;          // Current running sum +    local int sum_length;   // Number of samples to sum or average +    local int divisor;      // Divisor value to use for averaging +    local int history[$];   // History of previous values + +    function new(); +      // Initialize all values +      set_sum_length(0); +    endfunction : new + +    // Set the number of vales to sum +    function void set_sum_length(int value); +      sum = 0; +      sum_length = value; +      history = {}; +    endfunction : set_sum_length + +    // Set the divisor value +    function void set_divisor(int value); +      divisor = value; +    endfunction : set_divisor + +    // Add a value to the history of values used in the sum computation +    function void add_value(int value); +      history.push_back(value); +      sum += value; + +      // Check if we have a full history +      if (history.size() > sum_length) begin +        sum -= history.pop_front(); +      end +    endfunction : add_value + +    // Return the current running sum +    function int get_sum(); +      return sum; +    endfunction : get_sum + +    // Return the current running average +    function int get_average(); +      int result; + +      // Round to nearest integer, the same way the IP does. +      result = $floor(real'(get_sum())/real'(divisor) + 0.5); + +      // Saturate to 16-bit signed +      if (result > 32767) result = 32767; +      if (result < -32768) result = 32768; + +      return result; +    endfunction : get_average + +  endclass : MovingAverage + +endpackage diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/noc_shell_moving_avg.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/noc_shell_moving_avg.v new file mode 100644 index 000000000..8bd699c58 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/noc_shell_moving_avg.v @@ -0,0 +1,308 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_moving_avg +// +// Description: +// +//   This is a tool-generated NoC-shell for the moving_avg block. +//   See the RFNoC specification for more information about NoC shells. +// +// Parameters: +// +//   THIS_PORTID : Control crossbar port to which this block is connected +//   CHDR_W      : AXIS-CHDR data bus width +//   MTU         : Maximum transmission unit (i.e., maximum packet size in +// + +`default_nettype none + + +module noc_shell_moving_avg #( +  parameter [9:0] THIS_PORTID     = 10'd0, +  parameter       CHDR_W          = 64, +  parameter [5:0] MTU             = 10, +  parameter       NUM_PORTS       = 1 +) ( +  //--------------------- +  // Framework Interface +  //--------------------- + +  // RFNoC Framework Clocks +  input  wire rfnoc_chdr_clk, +  input  wire rfnoc_ctrl_clk, +  input  wire ce_clk, + +  // NoC Shell Generated Resets +  output wire rfnoc_chdr_rst, +  output wire rfnoc_ctrl_rst, +  output wire ce_rst, + +  // RFNoC Backend Interface +  input  wire [511:0]          rfnoc_core_config, +  output wire [511:0]          rfnoc_core_status, + +  // AXIS-CHDR Input Ports (from framework) +  input  wire [(0+NUM_PORTS)*CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire [(0+NUM_PORTS)-1:0]        s_rfnoc_chdr_tlast, +  input  wire [(0+NUM_PORTS)-1:0]        s_rfnoc_chdr_tvalid, +  output wire [(0+NUM_PORTS)-1:0]        s_rfnoc_chdr_tready, +  // AXIS-CHDR Output Ports (to framework) +  output wire [(0+NUM_PORTS)*CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire [(0+NUM_PORTS)-1:0]        m_rfnoc_chdr_tlast, +  output wire [(0+NUM_PORTS)-1:0]        m_rfnoc_chdr_tvalid, +  input  wire [(0+NUM_PORTS)-1:0]        m_rfnoc_chdr_tready, + +  // AXIS-Ctrl Control Input Port (from framework) +  input  wire [31:0]           s_rfnoc_ctrl_tdata, +  input  wire                  s_rfnoc_ctrl_tlast, +  input  wire                  s_rfnoc_ctrl_tvalid, +  output wire                  s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Control Output Port (to framework) +  output wire [31:0]           m_rfnoc_ctrl_tdata, +  output wire                  m_rfnoc_ctrl_tlast, +  output wire                  m_rfnoc_ctrl_tvalid, +  input  wire                  m_rfnoc_ctrl_tready, + +  //--------------------- +  // Client Interface +  //--------------------- + +  // CtrlPort Clock and Reset +  output wire               ctrlport_clk, +  output wire               ctrlport_rst, +  // CtrlPort Master +  output wire               m_ctrlport_req_wr, +  output wire               m_ctrlport_req_rd, +  output wire [19:0]        m_ctrlport_req_addr, +  output wire [31:0]        m_ctrlport_req_data, +  input  wire               m_ctrlport_resp_ack, +  input  wire [31:0]        m_ctrlport_resp_data, + +  // AXI-Stream Payload Context Clock and Reset +  output wire               axis_data_clk, +  output wire               axis_data_rst, +  // Payload Stream to User Logic: in +  output wire [NUM_PORTS*32*1-1:0]   m_in_payload_tdata, +  output wire [NUM_PORTS*1-1:0]      m_in_payload_tkeep, +  output wire [NUM_PORTS-1:0]        m_in_payload_tlast, +  output wire [NUM_PORTS-1:0]        m_in_payload_tvalid, +  input  wire [NUM_PORTS-1:0]        m_in_payload_tready, +  // Context Stream to User Logic: in +  output wire [NUM_PORTS*CHDR_W-1:0] m_in_context_tdata, +  output wire [NUM_PORTS*4-1:0]      m_in_context_tuser, +  output wire [NUM_PORTS-1:0]        m_in_context_tlast, +  output wire [NUM_PORTS-1:0]        m_in_context_tvalid, +  input  wire [NUM_PORTS-1:0]        m_in_context_tready, +  // Payload Stream to User Logic: out +  input  wire [NUM_PORTS*32*1-1:0]   s_out_payload_tdata, +  input  wire [NUM_PORTS*1-1:0]      s_out_payload_tkeep, +  input  wire [NUM_PORTS-1:0]        s_out_payload_tlast, +  input  wire [NUM_PORTS-1:0]        s_out_payload_tvalid, +  output wire [NUM_PORTS-1:0]        s_out_payload_tready, +  // Context Stream to User Logic: out +  input  wire [NUM_PORTS*CHDR_W-1:0] s_out_context_tdata, +  input  wire [NUM_PORTS*4-1:0]      s_out_context_tuser, +  input  wire [NUM_PORTS-1:0]        s_out_context_tlast, +  input  wire [NUM_PORTS-1:0]        s_out_context_tvalid, +  output wire [NUM_PORTS-1:0]        s_out_context_tready +); + +  //--------------------------------------------------------------------------- +  //  Backend Interface +  //--------------------------------------------------------------------------- + +  wire         data_i_flush_en; +  wire [31:0]  data_i_flush_timeout; +  wire [63:0]  data_i_flush_active; +  wire [63:0]  data_i_flush_done; +  wire         data_o_flush_en; +  wire [31:0]  data_o_flush_timeout; +  wire [63:0]  data_o_flush_active; +  wire [63:0]  data_o_flush_done; + +  backend_iface #( +    .NOC_ID        (32'hAAD20000), +    .NUM_DATA_I    (0+NUM_PORTS), +    .NUM_DATA_O    (0+NUM_PORTS), +    .CTRL_FIFOSIZE ($clog2(32)), +    .MTU           (MTU) +  ) backend_iface_i ( +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_chdr_rst       (rfnoc_chdr_rst), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst       (rfnoc_ctrl_rst), +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    .data_i_flush_en      (data_i_flush_en), +    .data_i_flush_timeout (data_i_flush_timeout), +    .data_i_flush_active  (data_i_flush_active), +    .data_i_flush_done    (data_i_flush_done), +    .data_o_flush_en      (data_o_flush_en), +    .data_o_flush_timeout (data_o_flush_timeout), +    .data_o_flush_active  (data_o_flush_active), +    .data_o_flush_done    (data_o_flush_done) +  ); + +  //--------------------------------------------------------------------------- +  //  Reset Generation +  //--------------------------------------------------------------------------- + +  wire ce_rst_pulse; + +  pulse_synchronizer #(.MODE ("POSEDGE")) pulse_synchronizer_ce ( +    .clk_a(rfnoc_chdr_clk), .rst_a(1'b0), .pulse_a (rfnoc_chdr_rst), .busy_a (), +    .clk_b(ce_clk), .pulse_b (ce_rst_pulse) +  ); + +  pulse_stretch_min #(.LENGTH(32)) pulse_stretch_min_ce ( +    .clk(ce_clk), .rst(1'b0), +    .pulse_in(ce_rst_pulse), .pulse_out(ce_rst) +  ); + +  //--------------------------------------------------------------------------- +  //  Control Path +  //--------------------------------------------------------------------------- + +  assign ctrlport_clk = ce_clk; +  assign ctrlport_rst = ce_rst; + +  ctrlport_endpoint #( +    .THIS_PORTID      (THIS_PORTID), +    .SYNC_CLKS        (0), +    .AXIS_CTRL_MST_EN (0), +    .AXIS_CTRL_SLV_EN (1), +    .SLAVE_FIFO_SIZE  ($clog2(32)) +  ) ctrlport_endpoint_i ( +    .rfnoc_ctrl_clk            (rfnoc_ctrl_clk), +    .rfnoc_ctrl_rst            (rfnoc_ctrl_rst), +    .ctrlport_clk              (ctrlport_clk), +    .ctrlport_rst              (ctrlport_rst), +    .s_rfnoc_ctrl_tdata        (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast        (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid       (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready       (s_rfnoc_ctrl_tready), +    .m_rfnoc_ctrl_tdata        (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast        (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid       (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready       (m_rfnoc_ctrl_tready), +    .m_ctrlport_req_wr         (m_ctrlport_req_wr), +    .m_ctrlport_req_rd         (m_ctrlport_req_rd), +    .m_ctrlport_req_addr       (m_ctrlport_req_addr), +    .m_ctrlport_req_data       (m_ctrlport_req_data), +    .m_ctrlport_req_byte_en    (), +    .m_ctrlport_req_has_time   (), +    .m_ctrlport_req_time       (), +    .m_ctrlport_resp_ack       (m_ctrlport_resp_ack), +    .m_ctrlport_resp_status    (2'b0), +    .m_ctrlport_resp_data      (m_ctrlport_resp_data), +    .s_ctrlport_req_wr         (1'b0), +    .s_ctrlport_req_rd         (1'b0), +    .s_ctrlport_req_addr       (20'b0), +    .s_ctrlport_req_portid     (10'b0), +    .s_ctrlport_req_rem_epid   (16'b0), +    .s_ctrlport_req_rem_portid (10'b0), +    .s_ctrlport_req_data       (32'b0), +    .s_ctrlport_req_byte_en    (4'hF), +    .s_ctrlport_req_has_time   (1'b0), +    .s_ctrlport_req_time       (64'b0), +    .s_ctrlport_resp_ack       (), +    .s_ctrlport_resp_status    (), +    .s_ctrlport_resp_data      () +  ); + +  //--------------------------------------------------------------------------- +  //  Data Path +  //--------------------------------------------------------------------------- + +  genvar i; + +  assign axis_data_clk = ce_clk; +  assign axis_data_rst = ce_rst; + +  //--------------------- +  // Input Data Paths +  //--------------------- + +  for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_input_in +    chdr_to_axis_pyld_ctxt #( +      .CHDR_W              (CHDR_W), +      .ITEM_W              (32), +      .NIPC                (1), +      .SYNC_CLKS           (0), +      .CONTEXT_FIFO_SIZE   ($clog2(2)), +      .PAYLOAD_FIFO_SIZE   ($clog2(32)), +      .CONTEXT_PREFETCH_EN (1) +    ) chdr_to_axis_pyld_ctxt_in_in ( +      .axis_chdr_clk         (rfnoc_chdr_clk), +      .axis_chdr_rst         (rfnoc_chdr_rst), +      .axis_data_clk         (axis_data_clk), +      .axis_data_rst         (axis_data_rst), +      .s_axis_chdr_tdata     (s_rfnoc_chdr_tdata[((0+i)*CHDR_W)+:CHDR_W]), +      .s_axis_chdr_tlast     (s_rfnoc_chdr_tlast[0+i]), +      .s_axis_chdr_tvalid    (s_rfnoc_chdr_tvalid[0+i]), +      .s_axis_chdr_tready    (s_rfnoc_chdr_tready[0+i]), +      .m_axis_payload_tdata  (m_in_payload_tdata[(32*1)*i+:(32*1)]), +      .m_axis_payload_tkeep  (m_in_payload_tkeep[1*i+:1]), +      .m_axis_payload_tlast  (m_in_payload_tlast[i]), +      .m_axis_payload_tvalid (m_in_payload_tvalid[i]), +      .m_axis_payload_tready (m_in_payload_tready[i]), +      .m_axis_context_tdata  (m_in_context_tdata[CHDR_W*i+:CHDR_W]), +      .m_axis_context_tuser  (m_in_context_tuser[4*i+:4]), +      .m_axis_context_tlast  (m_in_context_tlast[i]), +      .m_axis_context_tvalid (m_in_context_tvalid[i]), +      .m_axis_context_tready (m_in_context_tready[i]), +      .flush_en              (data_i_flush_en), +      .flush_timeout         (data_i_flush_timeout), +      .flush_active          (data_i_flush_active[0+i]), +      .flush_done            (data_i_flush_done[0+i]) +    ); +  end + +  //--------------------- +  // Output Data Paths +  //--------------------- + +  for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_output_out +    axis_pyld_ctxt_to_chdr #( +      .CHDR_W              (CHDR_W), +      .ITEM_W              (32), +      .NIPC                (1), +      .SYNC_CLKS           (0), +      .CONTEXT_FIFO_SIZE   ($clog2(2)), +      .PAYLOAD_FIFO_SIZE   ($clog2(32)), +      .MTU                 (MTU), +      .CONTEXT_PREFETCH_EN (1) +    ) axis_pyld_ctxt_to_chdr_out_out ( +      .axis_chdr_clk         (rfnoc_chdr_clk), +      .axis_chdr_rst         (rfnoc_chdr_rst), +      .axis_data_clk         (axis_data_clk), +      .axis_data_rst         (axis_data_rst), +      .m_axis_chdr_tdata     (m_rfnoc_chdr_tdata[(0+i)*CHDR_W+:CHDR_W]), +      .m_axis_chdr_tlast     (m_rfnoc_chdr_tlast[0+i]), +      .m_axis_chdr_tvalid    (m_rfnoc_chdr_tvalid[0+i]), +      .m_axis_chdr_tready    (m_rfnoc_chdr_tready[0+i]), +      .s_axis_payload_tdata  (s_out_payload_tdata[(32*1)*i+:(32*1)]), +      .s_axis_payload_tkeep  (s_out_payload_tkeep[1*i+:1]), +      .s_axis_payload_tlast  (s_out_payload_tlast[i]), +      .s_axis_payload_tvalid (s_out_payload_tvalid[i]), +      .s_axis_payload_tready (s_out_payload_tready[i]), +      .s_axis_context_tdata  (s_out_context_tdata[CHDR_W*i+:CHDR_W]), +      .s_axis_context_tuser  (s_out_context_tuser[4*i+:4]), +      .s_axis_context_tlast  (s_out_context_tlast[i]), +      .s_axis_context_tvalid (s_out_context_tvalid[i]), +      .s_axis_context_tready (s_out_context_tready[i]), +      .framer_errors         (), +      .flush_en              (data_o_flush_en), +      .flush_timeout         (data_o_flush_timeout), +      .flush_active          (data_o_flush_active[0+i]), +      .flush_done            (data_o_flush_done[0+i]) +    ); +  end + +endmodule // noc_shell_moving_avg + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg.v new file mode 100644 index 000000000..317189579 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg.v @@ -0,0 +1,281 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_moving_avg +// +// Description: +// +//   Computes the running average of an input data stream. That is, the output +//   of this block is the sum of the SUM_LENGTH most recent input values +//   divided by a DIVISOR value. The I and Q averages are handled separately +//   so that each output consists of the average of the I inputs in the I +//   output position and the average of the Q inputs in the Q output position. +//   SUM_LENGTH and DIVISOR are controlled by by registers. +// +// Parameters: +// +//   THIS_PORTID : Control crossbar port to which this block is connected +//   CHDR_W      : AXIS-CHDR data bus width +//   MTU         : Maximum transmission unit (i.e., maximum packet size in +//                 CHDR words is 2**MTU). +//   NUM_PORTS   : Number of moving-average cores to instantiate. +// + +`default_nettype none + + +module rfnoc_block_moving_avg #( +  parameter [9:0] THIS_PORTID = 10'd0, +  parameter       CHDR_W      = 64, +  parameter [5:0] MTU         = 10, +  parameter       NUM_PORTS   = 1 +) ( +  // RFNoC Framework Clocks and Resets +  input  wire                        rfnoc_chdr_clk, +  input  wire                        rfnoc_ctrl_clk, +  input  wire                        ce_clk, +  // RFNoC Backend Interface +  input  wire [               511:0] rfnoc_core_config, +  output wire [               511:0] rfnoc_core_status, +  // AXIS-CHDR Input Ports (from framework) +  input  wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tlast, +  input  wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, +  output wire [       NUM_PORTS-1:0] s_rfnoc_chdr_tready, +  // AXIS-CHDR Output Ports (to framework) +  output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast, +  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, +  input  wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tready, +  // AXIS-Ctrl Input Port (from framework) +  input  wire [                31:0] s_rfnoc_ctrl_tdata, +  input  wire                        s_rfnoc_ctrl_tlast, +  input  wire                        s_rfnoc_ctrl_tvalid, +  output wire                        s_rfnoc_ctrl_tready, +  // AXIS-Ctrl Output Port (to framework) +  output wire [                31:0] m_rfnoc_ctrl_tdata, +  output wire                        m_rfnoc_ctrl_tlast, +  output wire                        m_rfnoc_ctrl_tvalid, +  input  wire                        m_rfnoc_ctrl_tready +); + +  `include "rfnoc_block_moving_avg_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Signal Declarations +  //--------------------------------------------------------------------------- + +  // CtrlPort Master +  wire                        m_ctrlport_req_wr; +  wire                        m_ctrlport_req_rd; +  wire [19:0]                 m_ctrlport_req_addr; +  wire [31:0]                 m_ctrlport_req_data; +  wire                        m_ctrlport_resp_ack; +  wire [31:0]                 m_ctrlport_resp_data; +  // Payload Stream to User Logic: in +  wire [NUM_PORTS*32*1-1:0]   m_in_payload_tdata; +  wire [NUM_PORTS*1-1:0]      m_in_payload_tkeep; +  wire [NUM_PORTS-1:0]        m_in_payload_tlast; +  wire [NUM_PORTS-1:0]        m_in_payload_tvalid; +  wire [NUM_PORTS-1:0]        m_in_payload_tready; +  // Context Stream to User Logic: in +  wire [NUM_PORTS*CHDR_W-1:0] m_in_context_tdata; +  wire [NUM_PORTS*4-1:0]      m_in_context_tuser; +  wire [NUM_PORTS-1:0]        m_in_context_tlast; +  wire [NUM_PORTS-1:0]        m_in_context_tvalid; +  wire [NUM_PORTS-1:0]        m_in_context_tready; +  // Payload Stream to User Logic: out +  wire [NUM_PORTS*32*1-1:0]   s_out_payload_tdata; +  wire [NUM_PORTS*1-1:0]      s_out_payload_tkeep; +  wire [NUM_PORTS-1:0]        s_out_payload_tlast; +  wire [NUM_PORTS-1:0]        s_out_payload_tvalid; +  wire [NUM_PORTS-1:0]        s_out_payload_tready; +  // Context Stream to User Logic: out +  wire [NUM_PORTS*CHDR_W-1:0] s_out_context_tdata; +  wire [NUM_PORTS*4-1:0]      s_out_context_tuser; +  wire [NUM_PORTS-1:0]        s_out_context_tlast; +  wire [NUM_PORTS-1:0]        s_out_context_tvalid; +  wire [NUM_PORTS-1:0]        s_out_context_tready; + + +  //--------------------------------------------------------------------------- +  // NoC Shell +  //--------------------------------------------------------------------------- + +  wire ce_rst; + +  noc_shell_moving_avg #( +    .CHDR_W      (CHDR_W), +    .THIS_PORTID (THIS_PORTID), +    .MTU         (MTU), +    .NUM_PORTS   (NUM_PORTS) +  ) noc_shell_moving_avg_i ( +    //--------------------- +    // Framework Interface +    //--------------------- + +    // Clock Inputs +    .rfnoc_chdr_clk       (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk       (rfnoc_ctrl_clk), +    .ce_clk               (ce_clk), +    // Reset Outputs +    .rfnoc_chdr_rst       (), +    .rfnoc_ctrl_rst       (), +    .ce_rst               (ce_rst), +    // RFNoC Backend Interface +    .rfnoc_core_config    (rfnoc_core_config), +    .rfnoc_core_status    (rfnoc_core_status), +    // CHDR Input Ports  (from framework) +    .s_rfnoc_chdr_tdata   (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast   (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid  (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready  (s_rfnoc_chdr_tready), +    // CHDR Output Ports (to framework) +    .m_rfnoc_chdr_tdata   (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast   (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid  (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready  (m_rfnoc_chdr_tready), +    // AXIS-Ctrl Input Port (from framework) +    .s_rfnoc_ctrl_tdata   (s_rfnoc_ctrl_tdata), +    .s_rfnoc_ctrl_tlast   (s_rfnoc_ctrl_tlast), +    .s_rfnoc_ctrl_tvalid  (s_rfnoc_ctrl_tvalid), +    .s_rfnoc_ctrl_tready  (s_rfnoc_ctrl_tready), +    // AXIS-Ctrl Output Port (to framework) +    .m_rfnoc_ctrl_tdata   (m_rfnoc_ctrl_tdata), +    .m_rfnoc_ctrl_tlast   (m_rfnoc_ctrl_tlast), +    .m_rfnoc_ctrl_tvalid  (m_rfnoc_ctrl_tvalid), +    .m_rfnoc_ctrl_tready  (m_rfnoc_ctrl_tready), + +    //--------------------- +    // Client Interface +    //--------------------- + +    // CtrlPort Clock and Reset +    .ctrlport_clk         (), +    .ctrlport_rst         (), +    // CtrlPort Master +    .m_ctrlport_req_wr    (m_ctrlport_req_wr), +    .m_ctrlport_req_rd    (m_ctrlport_req_rd), +    .m_ctrlport_req_addr  (m_ctrlport_req_addr), +    .m_ctrlport_req_data  (m_ctrlport_req_data), +    .m_ctrlport_resp_ack  (m_ctrlport_resp_ack), +    .m_ctrlport_resp_data (m_ctrlport_resp_data), + +    // AXI-Stream Payload Context Clock and Reset +    .axis_data_clk        (), +    .axis_data_rst        (), +    // Payload Stream to User Logic: in +    .m_in_payload_tdata   (m_in_payload_tdata), +    .m_in_payload_tkeep   (m_in_payload_tkeep), +    .m_in_payload_tlast   (m_in_payload_tlast), +    .m_in_payload_tvalid  (m_in_payload_tvalid), +    .m_in_payload_tready  (m_in_payload_tready), +    // Context Stream to User Logic: in +    .m_in_context_tdata   (m_in_context_tdata), +    .m_in_context_tuser   (m_in_context_tuser), +    .m_in_context_tlast   (m_in_context_tlast), +    .m_in_context_tvalid  (m_in_context_tvalid), +    .m_in_context_tready  (m_in_context_tready), +    // Payload Stream from User Logic: out +    .s_out_payload_tdata  (s_out_payload_tdata), +    .s_out_payload_tkeep  (s_out_payload_tkeep), +    .s_out_payload_tlast  (s_out_payload_tlast), +    .s_out_payload_tvalid (s_out_payload_tvalid), +    .s_out_payload_tready (s_out_payload_tready), +    // Context Stream from User Logic: out +    .s_out_context_tdata  (s_out_context_tdata), +    .s_out_context_tuser  (s_out_context_tuser), +    .s_out_context_tlast  (s_out_context_tlast), +    .s_out_context_tvalid (s_out_context_tvalid), +    .s_out_context_tready (s_out_context_tready) +  ); + +  // Input packets have the same properties as output packets, so pass through +  // the header information unchanged. +  assign s_out_context_tdata  = m_in_context_tdata; +  assign s_out_context_tuser  = m_in_context_tuser; +  assign s_out_context_tlast  = m_in_context_tlast; +  assign s_out_context_tvalid = m_in_context_tvalid; +  assign m_in_context_tready  = s_out_context_tready; + + +  //--------------------------------------------------------------------------- +  // CtrlPort Splitter +  //--------------------------------------------------------------------------- + +  // Create a CtrlPort bus for each port instance + +  wire [ 1*NUM_PORTS-1:0] ctrlport_req_wr; +  wire [ 1*NUM_PORTS-1:0] ctrlport_req_rd; +  wire [20*NUM_PORTS-1:0] ctrlport_req_addr; +  wire [32*NUM_PORTS-1:0] ctrlport_req_data; +  wire [ 1*NUM_PORTS-1:0] ctrlport_resp_ack; +  wire [32*NUM_PORTS-1:0] ctrlport_resp_data; + +  ctrlport_decoder #( +    .NUM_SLAVES   (NUM_PORTS), +    .BASE_ADDR    (0), +    .SLAVE_ADDR_W (MOVING_AVG_ADDR_W) +  ) ctrlport_decoder_i ( +    .ctrlport_clk            (ce_clk), +    .ctrlport_rst            (ce_rst), +    .s_ctrlport_req_wr       (m_ctrlport_req_wr), +    .s_ctrlport_req_rd       (m_ctrlport_req_rd), +    .s_ctrlport_req_addr     (m_ctrlport_req_addr), +    .s_ctrlport_req_data     (m_ctrlport_req_data), +    .s_ctrlport_req_byte_en  (4'hF), +    .s_ctrlport_req_has_time (1'b0), +    .s_ctrlport_req_time     (64'b0), +    .s_ctrlport_resp_ack     (m_ctrlport_resp_ack), +    .s_ctrlport_resp_status  (), +    .s_ctrlport_resp_data    (m_ctrlport_resp_data), +    .m_ctrlport_req_wr       (ctrlport_req_wr), +    .m_ctrlport_req_rd       (ctrlport_req_rd), +    .m_ctrlport_req_addr     (ctrlport_req_addr), +    .m_ctrlport_req_data     (ctrlport_req_data), +    .m_ctrlport_req_byte_en  (), +    .m_ctrlport_req_has_time (), +    .m_ctrlport_req_time     (), +    .m_ctrlport_resp_ack     (ctrlport_resp_ack), +    .m_ctrlport_resp_status  ({NUM_PORTS{2'b0}}), +    .m_ctrlport_resp_data    (ctrlport_resp_data) +  ); + + +  //--------------------------------------------------------------------------- +  // Port Instances +  //--------------------------------------------------------------------------- + +  genvar port; +  generate +    for (port = 0; port < NUM_PORTS; port = port+1) begin : gen_ports + +      rfnoc_moving_avg_core rfnoc_moving_avg_core_i ( +        .clk                  (ce_clk), +        .rst                  (ce_rst), +        .s_ctrlport_req_wr    (ctrlport_req_wr      [port* 1 +:  1]), +        .s_ctrlport_req_rd    (ctrlport_req_rd      [port* 1 +:  1]), +        .s_ctrlport_req_addr  (ctrlport_req_addr    [port*20 +: 20]), +        .s_ctrlport_req_data  (ctrlport_req_data    [port*32 +: 32]), +        .s_ctrlport_resp_ack  (ctrlport_resp_ack    [port* 1 +:  1]), +        .s_ctrlport_resp_data (ctrlport_resp_data   [port*32 +: 32]), +        .i_tdata              (m_in_payload_tdata   [port*32 +: 32]), +        .i_tlast              (m_in_payload_tlast   [port* 1 +:  1]), +        .i_tvalid             (m_in_payload_tvalid  [port* 1 +:  1]), +        .i_tready             (m_in_payload_tready  [port* 1 +:  1]), +        .o_tdata              (s_out_payload_tdata  [port*32 +: 32]), +        .o_tlast              (s_out_payload_tlast  [port* 1 +:  1]), +        .o_tvalid             (s_out_payload_tvalid [port* 1 +:  1]), +        .o_tready             (s_out_payload_tready [port* 1 +:  1]) +      ); + +    end +  endgenerate + +endmodule // rfnoc_block_moving_avg + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg_regs.vh new file mode 100644 index 000000000..7f45f5ecf --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg_regs.vh @@ -0,0 +1,36 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module:  rfnoc_block_moving_avg_regs (Header) +// +// Description: RFNoC Moving Average block register descriptions +// + +// Address space size, per moving average core. That is, each moving average +// core's address space is separated in the CtrlPort address space by +// 2^MOVING_AVG_ADDR_W bytes. +localparam MOVING_AVG_ADDR_W = 3; + + + +// REG_SUM_LENGTH (R/W) +// +// Number of consecutive input samples for which to accumulate the I and Q +// values. Writing to this register clears the history and resets the +// accumulated sum to 0. +// +localparam REG_SUM_LENGTH = 'h0; +// +localparam REG_SUM_LENGTH_LEN = 8; + + +// REG_DIVISOR (R/W) +// +// Number by which to divide the accumulated sum. This is a signed integer +// value. +// +localparam REG_DIVISOR = 'h4; +// +localparam REG_DIVISOR_LEN = 24; diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg_tb.sv new file mode 100644 index 000000000..4fcf28108 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_block_moving_avg_tb.sv @@ -0,0 +1,491 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_moving_avg_tb +// +// Description: Testbench for the moving_avg RFNoC block. +// + +`default_nettype none + + +module rfnoc_block_moving_avg_tb; + +  `include "test_exec.svh" + +  import PkgTestExec::*; +  import PkgChdrUtils::*; +  import PkgRfnocBlockCtrlBfm::*; +  import PkgRfnocItemUtils::*; + +  import PkgMovingAverage::MovingAverage; + +  `include "rfnoc_block_moving_avg_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Testbench Configuration +  //--------------------------------------------------------------------------- + +  localparam [31:0] NOC_ID          = 32'hAAD20000; +  localparam [ 9:0] THIS_PORTID     = 10'h123; +  localparam int    CHDR_W          = 64;    // CHDR size in bits +  localparam int    MTU             = 10;    // Log2 of max transmission unit in CHDR words +  localparam int    NUM_PORTS       = 2; +  localparam int    NUM_PORTS_I     = NUM_PORTS; +  localparam int    NUM_PORTS_O     = NUM_PORTS; +  localparam int    ITEM_W          = 32;    // Sample size in bits +  localparam int    SPP             = 64;    // Samples per packet +  localparam int    PKT_SIZE_BYTES  = SPP * (ITEM_W/8); +  localparam int    STALL_PROB      = 25;    // Default BFM stall probability +  localparam real   CHDR_CLK_PER    = 5.0;   // 200 MHz +  localparam real   CTRL_CLK_PER    = 8.0;   // 125 MHz +  localparam real   CE_CLK_PER      = 4.0;   // 250 MHz + +  // Divisor data type (signed whole number) +  typedef bit signed [REG_DIVISOR_LEN-1:0] divisor_t; + + +  //--------------------------------------------------------------------------- +  // Clocks and Resets +  //--------------------------------------------------------------------------- + +  bit rfnoc_chdr_clk; +  bit rfnoc_ctrl_clk; +  bit ce_clk; + +  sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); +  sim_clock_gen #(CTRL_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); +  sim_clock_gen #(CE_CLK_PER) ce_clk_gen (.clk(ce_clk), .rst()); + + +  //--------------------------------------------------------------------------- +  // Bus Functional Models +  //--------------------------------------------------------------------------- + +  // Backend Interface +  RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk); + +  // AXIS-Ctrl Interface +  AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0); +  AxiStreamIf #(32) s_ctrl (rfnoc_ctrl_clk, 1'b0); + +  // AXIS-CHDR Interfaces +  AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS_I] (rfnoc_chdr_clk, 1'b0); +  AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS_O] (rfnoc_chdr_clk, 1'b0); + +  // Block Controller BFM +  RfnocBlockCtrlBfm #(CHDR_W, ITEM_W) blk_ctrl = new(backend, m_ctrl, s_ctrl); + +  // CHDR word and item/sample data types +  typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t  chdr_word_t; +  typedef ChdrData #(CHDR_W, ITEM_W)::item_t       item_t; +  typedef ChdrData #(CHDR_W, ITEM_W)::item_queue_t item_queue_t; + +  // Connect block controller to BFMs +  for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_bfm_input_connections +    initial begin +      blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); +      blk_ctrl.set_master_stall_prob(i, STALL_PROB); +    end +  end +  for (genvar i = 0; i < NUM_PORTS_O; i++) begin : gen_bfm_output_connections +    initial begin +      blk_ctrl.connect_slave_data_port(i, s_chdr[i]); +      blk_ctrl.set_slave_stall_prob(i, STALL_PROB); +    end +  end + + +  //--------------------------------------------------------------------------- +  // Device Under Test (DUT) +  //--------------------------------------------------------------------------- + +  // DUT Slave (Input) Port Signals +  logic [CHDR_W*NUM_PORTS_I-1:0] s_rfnoc_chdr_tdata; +  logic [       NUM_PORTS_I-1:0] s_rfnoc_chdr_tlast; +  logic [       NUM_PORTS_I-1:0] s_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS_I-1:0] s_rfnoc_chdr_tready; + +  // DUT Master (Output) Port Signals +  logic [CHDR_W*NUM_PORTS_O-1:0] m_rfnoc_chdr_tdata; +  logic [       NUM_PORTS_O-1:0] m_rfnoc_chdr_tlast; +  logic [       NUM_PORTS_O-1:0] m_rfnoc_chdr_tvalid; +  logic [       NUM_PORTS_O-1:0] m_rfnoc_chdr_tready; + +  // Map the array of BFMs to a flat vector for the DUT connections +  for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_dut_input_connections +    // Connect BFM master to DUT slave port +    assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; +    assign s_rfnoc_chdr_tlast[i]                = m_chdr[i].tlast; +    assign s_rfnoc_chdr_tvalid[i]               = m_chdr[i].tvalid; +    assign m_chdr[i].tready                     = s_rfnoc_chdr_tready[i]; +  end +  for (genvar i = 0; i < NUM_PORTS_O; i++) begin : gen_dut_output_connections +    // Connect BFM slave to DUT master port +    assign s_chdr[i].tdata        = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; +    assign s_chdr[i].tlast        = m_rfnoc_chdr_tlast[i]; +    assign s_chdr[i].tvalid       = m_rfnoc_chdr_tvalid[i]; +    assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; +  end + +  rfnoc_block_moving_avg #( +    .THIS_PORTID         (THIS_PORTID), +    .CHDR_W              (CHDR_W), +    .MTU                 (MTU), +    .NUM_PORTS           (NUM_PORTS) +  ) dut ( +    .rfnoc_chdr_clk      (rfnoc_chdr_clk), +    .rfnoc_ctrl_clk      (rfnoc_ctrl_clk), +    .ce_clk              (ce_clk), +    .rfnoc_core_config   (backend.cfg), +    .rfnoc_core_status   (backend.sts), +    .s_rfnoc_chdr_tdata  (s_rfnoc_chdr_tdata), +    .s_rfnoc_chdr_tlast  (s_rfnoc_chdr_tlast), +    .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid), +    .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready), +    .m_rfnoc_chdr_tdata  (m_rfnoc_chdr_tdata), +    .m_rfnoc_chdr_tlast  (m_rfnoc_chdr_tlast), +    .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid), +    .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready), +    .s_rfnoc_ctrl_tdata  (m_ctrl.tdata), +    .s_rfnoc_ctrl_tlast  (m_ctrl.tlast), +    .s_rfnoc_ctrl_tvalid (m_ctrl.tvalid), +    .s_rfnoc_ctrl_tready (m_ctrl.tready), +    .m_rfnoc_ctrl_tdata  (s_ctrl.tdata), +    .m_rfnoc_ctrl_tlast  (s_ctrl.tlast), +    .m_rfnoc_ctrl_tvalid (s_ctrl.tvalid), +    .m_rfnoc_ctrl_tready (s_ctrl.tready) +  ); + + +  //--------------------------------------------------------------------------- +  // Helper Tasks +  //--------------------------------------------------------------------------- + +  // Write a 32-bit register +  task automatic write_reg(int port, bit [19:0] addr, bit [31:0] value); +    blk_ctrl.reg_write(port * (2**MOVING_AVG_ADDR_W) + addr, value); +  endtask : write_reg + +  // Read a 32-bit register +  task automatic read_reg(int port, bit [19:0] addr, output logic [31:0] value); +    blk_ctrl.reg_read(port * (2**MOVING_AVG_ADDR_W) + addr, value); +  endtask : read_reg + + +  //--------------------------------------------------------------------------- +  // Test Procedures +  //--------------------------------------------------------------------------- + +  // Run a single test on the block using random packet data. +  // +  //  port        : Port number of the block to test +  //  sum_length  : Number of samples to sum +  //  divisor     : Divisor value to use to get the average (may be negative) +  //  packet_len  : Packet size to input and read out, in samples +  //  num_packets : Number of packets to input and check on the output +  //  value       : If set, use this value of sample instead of random data +  // +  task automatic test_moving_avg( +    int port, +    int sum_length, +    int divisor, +    int packet_len, +    int num_packets, +    item_t sample = 'X +  ); +    MovingAverage sum_i = new(), sum_q = new(); +    mailbox #(item_queue_t) packets = new(); + +    $display("Testing: sum_length=%04d, divisor=%08d, packet_len=%04d, num_packets=%04d", +      sum_length, divisor, packet_len, num_packets); + +    write_reg(port, REG_SUM_LENGTH, sum_length); +    write_reg(port, REG_DIVISOR, divisor); + +    sum_i.set_sum_length(sum_length); +    sum_q.set_sum_length(sum_length); +    sum_i.set_divisor(divisor); +    sum_q.set_divisor(divisor); + +    // Generate and enqueue packets for transmission +    for (int packet_count = 0; packet_count < num_packets; packet_count++) begin +      item_t payload[$]; + +      payload = {}; +      for (int sample_count = 0; sample_count < packet_len; sample_count++) begin +        if (sample !== 'X) begin +          payload.push_back(sample); +        end else begin +          payload.push_back($urandom()); +        end +      end +      packets.put(payload); +      blk_ctrl.send_items(port, payload); +    end + +    // Receive and check the results +    for (int packet_count = 0; packet_count < num_packets; packet_count++) begin +      item_t sent[$], received[$]; +      packets.get(sent); + +      // Retrieve the resulting packet +      blk_ctrl.recv_items(port, received); + +      // Check that the packet length matches what was input +      `ASSERT_ERROR( +        sent.size() == received.size(), +        $sformatf("For packet %0d, received length was incorrect", packet_count) +      ); + +      // Check that the payload is correct +      foreach(received[i]) begin +        item_t expected_val, sent_val, received_val; + +        //$display("Sent %09d,%09d", signed'(sent[i][31:16]), signed'(sent[i][15:0])); + +        // Calculate expected result +        sent_val = sent[i]; +        sum_i.add_value(signed'(sent_val[31:16])); +        sum_q.add_value(signed'(sent_val[15: 0])); +        expected_val[31:16] = sum_i.get_average(); +        expected_val[15: 0] = sum_q.get_average(); + +        received_val = received[i]; +        if(received_val != expected_val) begin +          $display("Received %09d,%09d", signed'(received_val[31:16]), signed'(received_val[15:0])); +          $display("Expected %09d,%09d", signed'(expected_val[31:16]), signed'(expected_val[15:0])); +          `ASSERT_ERROR( +            0, +            $sformatf("Unexpected result for packet %0d, sample %0d.", packet_count, i) +          ); +        end +      end +    end + +  endtask : test_moving_avg + + +  // Test the registers for the indicated port. This checks initial values, so +  // it should be run first. +  task automatic test_registers(int port); +    logic [31:0] value; + +    test.start_test("Test registers", 1ms); + +    // Check initial values, to make sure a previous test didn't affect the +    // wrong port. +    read_reg(port, REG_SUM_LENGTH, value); +    `ASSERT_ERROR(value === {REG_SUM_LENGTH_LEN{1'bX}}, +      "REG_SUM_LENGTH initial value didn't match expected value"); + +    read_reg(port, REG_DIVISOR, value); +    `ASSERT_ERROR(value === {REG_DIVISOR_LEN{1'bX}}, +      "REG_DIVISOR_LEN initial value didn't match expected value"); + +    // Test writing 0 to the registers +    write_reg(port, REG_SUM_LENGTH, 0); +    read_reg(port, REG_SUM_LENGTH, value); +    `ASSERT_ERROR(value == 0, "REG_SUM_LENGTH didn't readback correctly"); + +    write_reg(port, REG_DIVISOR, 0); +    read_reg(port, REG_DIVISOR, value); +    `ASSERT_ERROR(value == 0, "REG_DIVISOR didn't readback correctly"); + +    // Test writing the max value to the registers +    write_reg(port, REG_SUM_LENGTH, '1); +    read_reg(port, REG_SUM_LENGTH, value); +    `ASSERT_ERROR(value == {REG_SUM_LENGTH_LEN{1'b1}}, +      "REG_SUM_LENGTH didn't readback correctly"); + +    write_reg(port, REG_DIVISOR, '1); +    read_reg(port, REG_DIVISOR, value); +    `ASSERT_ERROR(value == {REG_DIVISOR_LEN{1'b1}}, +      "REG_DIVISOR didn't readback correctly"); + +    test.end_test(); +  endtask : test_registers + + +  // Run random test and look for anything unexpected +  // +  //   port            : Port number of the block to test +  //   num_iterations  : Number of times to repeat a random test +  //   max_packet_len  : Maximum packet length to use, in samples +  // +  task automatic test_random_config( +    int port, +    int num_iterations  = 100, +    int max_packet_len  = SPP +  ); +    test.start_test("Test random", 10ms); + +    // Repeat the test num_iterations times +    for (int iteration = 0; iteration < num_iterations; iteration++) begin + +      int sum_length; +      int divisor; +      int packet_len; +      int num_packets; + +      // Choose random attributes for this test +      sum_length  = $urandom_range(1, 2**REG_SUM_LENGTH_LEN-1); +      // Limit the divisor so we aren't outputting 0 all the time +      divisor     = divisor_t'($urandom_range(1, 20*sum_length)); +      if ($urandom_range(0,1)) divisor = -divisor; +      packet_len  = $urandom_range(1, SPP); +      num_packets = $urandom_range(3, 8); + +      // Run the test +      test_moving_avg(port, sum_length, divisor, packet_len, num_packets); +    end + +    test.end_test(); +  endtask : test_random_config + + +  // Run some quick basic tests +  task automatic test_basic(int port); +    test.start_test($sformatf("Test basic, port %0d", port), 100us); + +    // Minimum sum and divisor values (Data in should match data out) +    test_moving_avg(.port(port), .sum_length(1), .divisor(1), +      .packet_len(SPP), .num_packets(3)); +    // Input samples negated +    test_moving_avg(.port(port), .sum_length(1), .divisor(-1), +      .packet_len(SPP), .num_packets(3)); +    // Input samples divided by 2 +    test_moving_avg(.port(port), .sum_length(1), .divisor(2), +      .packet_len(SPP), .num_packets(3)); +    // Sum of two samples +    test_moving_avg(.port(port), .sum_length(2), .divisor(1), +      .packet_len(SPP), .num_packets(3)); +    // Average of two samples +    test_moving_avg(.port(port), .sum_length(2), .divisor(2), +      .packet_len(SPP), .num_packets(3)); + +    test.end_test(); +  endtask : test_basic + + +  // Test maximum and minimum of length, divisor, and sample value to check +  // the corner cases of the DUT's computation. +  task automatic test_max_values(int port); +    int max_length, max_divisor, min_divisor; + +    test.start_test("Test max values", 1ms); + +    max_length  =  2**REG_SUM_LENGTH_LEN-1; +    max_divisor =  2**(REG_DIVISOR_LEN-1)-1; +    min_divisor = -2**(REG_DIVISOR_LEN-1); + +    // Send 3*max_length/SPP packets in each case to make sure we fill the +    // history buffer of the DUT. + +    // Maximum allowed sum length +    test_moving_avg(.port(port), .sum_length(max_length), .divisor(max_length), +      .packet_len(SPP), .num_packets(3*max_length/SPP)); +    // Maximum divisor +    test_moving_avg(.port(port), .sum_length(max_length), .divisor(max_divisor), +      .packet_len(SPP), .num_packets(3*max_length/SPP)); +    // Minimum divisor +    test_moving_avg(.port(port), .sum_length(max_length), .divisor(min_divisor), +      .packet_len(SPP), .num_packets(3*max_length/SPP)); + +    // Make sure we don't overflow the internal sum. +    // Maximum sample value (32767) +    test_moving_avg(.port(port), .sum_length(max_length), .divisor(max_length), +      .packet_len(SPP), .num_packets(3*max_length/SPP), .sample(32'h7FFF_7FFF)); +    // Minimum sample value (-32768) +    test_moving_avg(.port(port), .sum_length(max_length), .divisor(max_length), +      .packet_len(SPP), .num_packets(3*max_length/SPP), .sample(32'h8000_8000)); + +    test.end_test(); +  endtask : test_max_values + + +  // Test with slow BFM slave to make sure back-pressure is working correctly. +  task automatic test_back_pressure(int port); +    test.start_test("Test back pressure", 1ms); +    blk_ctrl.set_slave_stall_prob(port, 90); +    test_moving_avg(port, 16, 16, SPP, 20); +    blk_ctrl.set_slave_stall_prob(port, STALL_PROB); +    test.end_test(); +  endtask : test_back_pressure + + +  // Test with slow BFM master to make sure AXI-stream flow control is working +  // correctly. +  task automatic test_underflow(int port); +    test.start_test("Test underflow", 1ms); +    blk_ctrl.set_master_stall_prob(port, 90); +    test_moving_avg(port, 16, 16, SPP, 20); +    blk_ctrl.set_master_stall_prob(port, STALL_PROB); +    test.end_test(); +  endtask : test_underflow + + +  //--------------------------------------------------------------------------- +  // Main Test Process +  //--------------------------------------------------------------------------- + +  initial begin : tb_main +    int port; + +    // Initialize the test exec object for this testbench +    test.start_tb("rfnoc_block_moving_avg_tb"); + +    // Start the BFMs running +    blk_ctrl.run(); + +    //-------------------------------- +    // Reset +    //-------------------------------- + +    test.start_test("Flush block then reset it", 10us); +    blk_ctrl.flush_and_reset(); +    test.end_test(); + +    //-------------------------------- +    // Verify Block Info +    //-------------------------------- + +    test.start_test("Verify Block Info", 2us); +    `ASSERT_ERROR(blk_ctrl.get_noc_id() == NOC_ID, "Incorrect NOC_ID Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS_I, "Incorrect NUM_DATA_I Value"); +    `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS_O, "Incorrect NUM_DATA_O Value"); +    `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); +    test.end_test(); + +    //-------------------------------- +    // Test Sequences +    //-------------------------------- + +    // Run the basic tests on all ports +    for (port = 0; port < NUM_PORTS; port++) begin +      test_registers(port); +      test_basic(port); +    end + +    // Run remaining tests on a single port +    port = 1; +    test_max_values(port); +    test_back_pressure(port); +    test_underflow(port); +    test_random_config(port, 100); + +    //-------------------------------- +    // Finish Up +    //-------------------------------- + +    // Display final statistics and results +    test.end_tb(); +  end : tb_main + +endmodule : rfnoc_block_moving_avg_tb + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_moving_avg_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_moving_avg_core.v new file mode 100644 index 000000000..c6468c379 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_moving_avg/rfnoc_moving_avg_core.v @@ -0,0 +1,327 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_moving_avg_core +// +// Description: +// +//   This module contains the registers and core logic for a single RFNoC +//   Moving Average module instance. +// + + +module rfnoc_moving_avg_core ( +  input wire clk, +  input wire rst, + +  // CtrlPort Slave +  input  wire        s_ctrlport_req_wr, +  input  wire        s_ctrlport_req_rd, +  input  wire [19:0] s_ctrlport_req_addr, +  input  wire [31:0] s_ctrlport_req_data, +  output reg         s_ctrlport_resp_ack, +  output reg  [31:0] s_ctrlport_resp_data, + +  // Input data stream +  input  wire [31:0] i_tdata, +  input  wire        i_tlast, +  input  wire        i_tvalid, +  output wire        i_tready, + +  // Output data stream +  output wire [31:0] o_tdata, +  output wire        o_tlast, +  output wire        o_tvalid, +  input  wire        o_tready +); + +  `include "rfnoc_block_moving_avg_regs.vh" + + +  //--------------------------------------------------------------------------- +  // Registers +  //--------------------------------------------------------------------------- + +  // Number of samples to accumulate +  reg [REG_SUM_LENGTH_LEN-1:0] sum_len_reg; +  reg                          sum_len_reg_changed; + +  // Sum will be divided by this number +  reg [REG_DIVISOR_LEN-1:0] divisor_reg; + +  always @(posedge clk) begin +    if (rst) begin +      sum_len_reg_changed <= 1'b0; +      sum_len_reg         <=  'bX; +      divisor_reg         <=  'bX; +    end else begin +      // Default assignments +      s_ctrlport_resp_ack  <= 1'b0; +      s_ctrlport_resp_data <= 0; +      sum_len_reg_changed  <= 1'b0; + +      // Handle register writes +      if (s_ctrlport_req_wr) begin +        s_ctrlport_resp_ack <= 1; +        case (s_ctrlport_req_addr) +          REG_SUM_LENGTH : begin +            sum_len_reg         <= s_ctrlport_req_data[REG_SUM_LENGTH_LEN-1:0]; +            sum_len_reg_changed <= 1'b1; +          end +          REG_DIVISOR : begin +            divisor_reg <= s_ctrlport_req_data[REG_DIVISOR_LEN-1:0]; +          end +        endcase +      end + +      // Handle register reads +      if (s_ctrlport_req_rd) begin +        s_ctrlport_resp_ack <= 1; +        case (s_ctrlport_req_addr) +          REG_SUM_LENGTH : begin +            s_ctrlport_resp_data[REG_SUM_LENGTH_LEN-1:0] <= sum_len_reg; +          end +          REG_DIVISOR : begin +            s_ctrlport_resp_data[REG_DIVISOR_LEN-1:0] <= divisor_reg; +          end +          default : begin +            s_ctrlport_resp_data <= 32'h0BADC0DE; +          end +        endcase +      end +    end +  end + + +  //--------------------------------------------------------------------------- +  // Moving Average Core Logic +  //--------------------------------------------------------------------------- + +  // I part +  wire [15:0] ipart_tdata; +  wire        ipart_tlast; +  wire        ipart_tvalid; +  wire        ipart_tready; + +  // Q part +  wire [15:0] qpart_tdata; +  wire        qpart_tlast; +  wire        qpart_tvalid; +  wire        qpart_tready; + +  // I sum +  wire [23:0] isum_tdata; +  wire        isum_tlast; +  wire        isum_tvalid; +  wire        isum_tready; + +  // Q sum +  wire [23:0] qsum_tdata; +  wire        qsum_tlast; +  wire        qsum_tvalid; +  wire        qsum_tready; + +  // I average +  wire        [47:0] iavg_uncorrected_tdata; +  wire signed [46:0] iavg_tdata; +  wire               iavg_tlast; +  wire               iavg_tvalid; +  wire               iavg_tready; +  wire        [15:0] iavg_rnd_tdata; +  wire               iavg_rnd_tlast; +  wire               iavg_rnd_tvalid; +  wire               iavg_rnd_tready; +  wire               idivisor_tready; +  wire               idividend_tready; + +  // Q average +  wire        [47:0] qavg_uncorrected_tdata; +  wire signed [46:0] qavg_tdata; +  wire               qavg_tlast; +  wire               qavg_tvalid; +  wire               qavg_tready; +  wire        [15:0] qavg_rnd_tdata; +  wire               qavg_rnd_tlast; +  wire               qavg_rnd_tvalid; +  wire               qavg_rnd_tready; +  wire               qdivisor_tready; +  wire               qdividend_tready; + +  // The core logic below is hard coded for 8-bit sum length and 24-bit +  // divider. So make sure the registers are configured that way. If we want +  // to support longer sums, then the code below needs to be updated. +  generate +    if (REG_SUM_LENGTH_LEN != 8) begin : sum_length_assertion +      SUM_LENGTH_must_be_8_bits(); +    end +    if (REG_DIVISOR_LEN != 24) begin : divisor_length_assertion +      REG_DIVISOR_must_be_24_bits(); +    end +  endgenerate + +  // Split incoming data into I and Q parts +  split_complex #( +    .WIDTH (16) +  ) split_complex_inst ( +    .i_tdata   (i_tdata), +    .i_tlast   (i_tlast), +    .i_tvalid  (i_tvalid), +    .i_tready  (i_tready), +    .oi_tdata  (ipart_tdata), +    .oi_tlast  (ipart_tlast), +    .oi_tvalid (ipart_tvalid), +    .oi_tready (ipart_tready), +    .oq_tdata  (qpart_tdata), +    .oq_tlast  (qpart_tlast), +    .oq_tvalid (qpart_tvalid), +    .oq_tready (qpart_tready), +    .error     () +  ); + +  // Accumulate I values +  moving_sum #( +    .MAX_LEN (255), +    .WIDTH   (16) +  ) moving_isum_inst ( +    .clk      (clk), +    .reset    (rst), +    .clear    (sum_len_reg_changed), +    .len      (sum_len_reg), +    .i_tdata  (ipart_tdata), +    .i_tlast  (ipart_tlast), +    .i_tvalid (ipart_tvalid), +    .i_tready (ipart_tready), +    .o_tdata  (isum_tdata), +    .o_tlast  (isum_tlast), +    .o_tvalid (isum_tvalid), +    .o_tready (isum_tready) +  ); + +  // Accumulate Q values +  moving_sum #( +    .MAX_LEN (255), +    .WIDTH   (16) +  ) moving_qsum_inst ( +    .clk      (clk), +    .reset    (rst), +    .clear    (sum_len_reg_changed), +    .len      (sum_len_reg), +    .i_tdata  (qpart_tdata), +    .i_tlast  (qpart_tlast), +    .i_tvalid (qpart_tvalid), +    .i_tready (qpart_tready), +    .o_tdata  (qsum_tdata), +    .o_tlast  (qsum_tlast), +    .o_tvalid (qsum_tvalid), +    .o_tready (qsum_tready) +  ); + +  // Make sure dividers are ready. The handshake logic here makes the +  // assumption that the divider_int24 instances can always accept a divisor +  // and a dividend on the same clock cycle. That is, as long as we always +  // input them together, we'll never have a situation where the divisor input +  // is ready and the dividend input is not, or vice versa. +  assign isum_tready = idivisor_tready & idividend_tready; +  assign qsum_tready = qdivisor_tready & qdividend_tready; + +  // Divide I part by divisor from register +  divide_int24 divide_i_inst ( +    .aclk                   (clk), +    .aresetn                (~rst), +    .s_axis_divisor_tvalid  (isum_tvalid), +    .s_axis_divisor_tready  (idivisor_tready), +    .s_axis_divisor_tlast   (isum_tlast), +    .s_axis_divisor_tdata   (divisor_reg), +    .s_axis_dividend_tvalid (isum_tvalid), +    .s_axis_dividend_tready (idividend_tready), +    .s_axis_dividend_tlast  (isum_tlast), +    .s_axis_dividend_tdata  (isum_tdata), +    .m_axis_dout_tvalid     (iavg_tvalid), +    .m_axis_dout_tready     (iavg_tready), +    .m_axis_dout_tuser      (), +    .m_axis_dout_tlast      (iavg_tlast), +    .m_axis_dout_tdata      (iavg_uncorrected_tdata) +  ); + +  // Divide Q part by divisor from register +  divide_int24 divide_q_inst ( +    .aclk                   (clk), +    .aresetn                (~rst), +    .s_axis_divisor_tvalid  (qsum_tvalid), +    .s_axis_divisor_tready  (qdivisor_tready), +    .s_axis_divisor_tlast   (qsum_tlast), +    .s_axis_divisor_tdata   (divisor_reg), +    .s_axis_dividend_tvalid (qsum_tvalid), +    .s_axis_dividend_tready (qdividend_tready), +    .s_axis_dividend_tlast  (qsum_tlast), +    .s_axis_dividend_tdata  (qsum_tdata), +    .m_axis_dout_tvalid     (qavg_tvalid), +    .m_axis_dout_tready     (qavg_tready), +    .m_axis_dout_tuser      (), +    .m_axis_dout_tlast      (qavg_tlast), +    .m_axis_dout_tdata      (qavg_uncorrected_tdata) +  ); + +  // Xilinx divider separates integer and fractional parts. Combine into fixed +  // point value Q23.23. +  assign iavg_tdata = $signed({iavg_uncorrected_tdata[47:24],23'd0}) +  +                      $signed(iavg_uncorrected_tdata[23:0]); +  assign qavg_tdata = $signed({qavg_uncorrected_tdata[47:24],23'd0}) +  +                      $signed(qavg_uncorrected_tdata[23:0]); + +  axi_round_and_clip #( +    .WIDTH_IN  (47), +    .WIDTH_OUT (16), +    .CLIP_BITS (8) +  ) axi_round_and_clip_i ( +    .clk      (clk), +    .reset    (rst), +    .i_tdata  (iavg_tdata), +    .i_tlast  (iavg_tlast), +    .i_tvalid (iavg_tvalid), +    .i_tready (iavg_tready), +    .o_tdata  (iavg_rnd_tdata), +    .o_tlast  (iavg_rnd_tlast), +    .o_tvalid (iavg_rnd_tvalid), +    .o_tready (iavg_rnd_tready) +  ); + +  axi_round_and_clip #( +    .WIDTH_IN  (47), +    .WIDTH_OUT (16), +    .CLIP_BITS (8) +  ) axi_round_and_clip_q ( +    .clk      (clk), +    .reset    (rst), +    .i_tdata  (qavg_tdata), +    .i_tlast  (qavg_tlast), +    .i_tvalid (qavg_tvalid), +    .i_tready (qavg_tready), +    .o_tdata  (qavg_rnd_tdata), +    .o_tlast  (qavg_rnd_tlast), +    .o_tvalid (qavg_rnd_tvalid), +    .o_tready (qavg_rnd_tready) +  ); + +  // Concatenate I and Q part again +  join_complex #( +    .WIDTH (16) +  ) join_complex_inst ( +    .ii_tdata  (iavg_rnd_tdata), +    .ii_tlast  (iavg_rnd_tlast), +    .ii_tvalid (iavg_rnd_tvalid), +    .ii_tready (iavg_rnd_tready), +    .iq_tdata  (qavg_rnd_tdata), +    .iq_tlast  (qavg_rnd_tlast), +    .iq_tvalid (qavg_rnd_tvalid), +    .iq_tready (qavg_rnd_tready), +    .o_tdata   (o_tdata), +    .o_tlast   (o_tlast), +    .o_tvalid  (o_tvalid), +    .o_tready  (o_tready), +    .error     () +  ); + +endmodule  | 
