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 | |
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')
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 |