diff options
author | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-08-05 07:32:20 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-08-05 07:47:22 -0500 |
commit | b39021ac2e642c22349183b6bc697daae01da5e9 (patch) | |
tree | 805c837c02d100e19bf49e3cf4a37901bb056436 /fpga/usrp3 | |
parent | 31cade6566383c34f64834d089b025e4b0a274a3 (diff) | |
download | uhd-b39021ac2e642c22349183b6bc697daae01da5e9.tar.gz uhd-b39021ac2e642c22349183b6bc697daae01da5e9.tar.bz2 uhd-b39021ac2e642c22349183b6bc697daae01da5e9.zip |
fpga: rfnoc: Add RFNoC Keep One in N block
Diffstat (limited to 'fpga/usrp3')
7 files changed, 1432 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/Makefile new file mode 100644 index 000000000..17de7c3ae --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/Makefile @@ -0,0 +1,42 @@ +# +# Copyright 2020 Ettus Research, A National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +BASE_DIR = $(abspath ../../../../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 Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_keep_one_in_n_tb +SIM_SRCS = \ +$(abspath rfnoc_block_keep_one_in_n_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_keep_one_in_n/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/Makefile.srcs new file mode 100644 index 000000000..11eb5cd6d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/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)))), \ +rfnoc_block_keep_one_in_n.v \ +rfnoc_keep_one_in_n.v \ +rfnoc_keep_one_in_n_regs.vh \ +noc_shell_keep_one_in_n.v \ +) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/noc_shell_keep_one_in_n.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/noc_shell_keep_one_in_n.v new file mode 100644 index 000000000..78572597e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/noc_shell_keep_one_in_n.v @@ -0,0 +1,304 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_keep_one_in_n +// +// Description: +// +// This is a tool-generated NoC-shell for the keep_one_in_n 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_keep_one_in_n #( + parameter [9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter NUM_PORTS = 1, + parameter [5:0] MTU = 10 +) ( + //--------------------- + // 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 Data Clock and Reset + output wire axis_data_clk, + output wire axis_data_rst, + // Data Stream to User Logic: in + output wire [NUM_PORTS*32*1-1:0] m_in_axis_tdata, + output wire [NUM_PORTS*1-1:0] m_in_axis_tkeep, + output wire [NUM_PORTS-1:0] m_in_axis_tlast, + output wire [NUM_PORTS-1:0] m_in_axis_tvalid, + input wire [NUM_PORTS-1:0] m_in_axis_tready, + output wire [NUM_PORTS*64-1:0] m_in_axis_ttimestamp, + output wire [NUM_PORTS-1:0] m_in_axis_thas_time, + output wire [NUM_PORTS*16-1:0] m_in_axis_tlength, + output wire [NUM_PORTS-1:0] m_in_axis_teov, + output wire [NUM_PORTS-1:0] m_in_axis_teob, + // Data Stream to User Logic: out + input wire [NUM_PORTS*32*1-1:0] s_out_axis_tdata, + input wire [NUM_PORTS*1-1:0] s_out_axis_tkeep, + input wire [NUM_PORTS-1:0] s_out_axis_tlast, + input wire [NUM_PORTS-1:0] s_out_axis_tvalid, + output wire [NUM_PORTS-1:0] s_out_axis_tready, + input wire [NUM_PORTS*64-1:0] s_out_axis_ttimestamp, + input wire [NUM_PORTS-1:0] s_out_axis_thas_time, + input wire [NUM_PORTS*16-1:0] s_out_axis_tlength, + input wire [NUM_PORTS-1:0] s_out_axis_teov, + input wire [NUM_PORTS-1:0] s_out_axis_teob +); + + //--------------------------------------------------------------------------- + // 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'h02460000), + .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_data #( + .CHDR_W (CHDR_W), + .ITEM_W (32), + .NIPC (1), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE ($clog2(32)), + .PYLD_FIFO_SIZE ($clog2(32)) + ) chdr_to_axis_data_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_tdata (m_in_axis_tdata[(32*1)*i+:(32*1)]), + .m_axis_tkeep (m_in_axis_tkeep[1*i+:1]), + .m_axis_tlast (m_in_axis_tlast[i]), + .m_axis_tvalid (m_in_axis_tvalid[i]), + .m_axis_tready (m_in_axis_tready[i]), + .m_axis_ttimestamp (m_in_axis_ttimestamp[64*i+:64]), + .m_axis_thas_time (m_in_axis_thas_time[i]), + .m_axis_tlength (m_in_axis_tlength[16*i+:16]), + .m_axis_teov (m_in_axis_teov[i]), + .m_axis_teob (m_in_axis_teob[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_data_to_chdr #( + .CHDR_W (CHDR_W), + .ITEM_W (32), + .NIPC (1), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE ($clog2(32)), + .PYLD_FIFO_SIZE ($clog2(MTU)), + .MTU (MTU), + .SIDEBAND_AT_END (1) + ) axis_data_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_tdata (s_out_axis_tdata[(32*1)*i+:(32*1)]), + .s_axis_tkeep (s_out_axis_tkeep[1*i+:1]), + .s_axis_tlast (s_out_axis_tlast[i]), + .s_axis_tvalid (s_out_axis_tvalid[i]), + .s_axis_tready (s_out_axis_tready[i]), + .s_axis_ttimestamp (s_out_axis_ttimestamp[64*i+:64]), + .s_axis_thas_time (s_out_axis_thas_time[i]), + .s_axis_tlength (s_out_axis_tlength[16*i+:16]), + .s_axis_teov (s_out_axis_teov[i]), + .s_axis_teob (s_out_axis_teob[i]), + .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_keep_one_in_n + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_block_keep_one_in_n.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_block_keep_one_in_n.v new file mode 100644 index 000000000..27cb21d5b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_block_keep_one_in_n.v @@ -0,0 +1,284 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_keep_one_in_n +// +// Description: +// The Keep One in N block has two modes: sample mode and packet mode. +// In sample mode, the first sample is kept and then N-1 samples are dropped. +// Packet mode is similar to sample mode, except the first packet of samples +// is kept and then N-1 packets are dropped. The packet size is determined +// automatically from tlast. +// +// Parameters: +// +// WIDTH_N : Bit width of N parameter, must be 31 bits or less +// THIS_PORTID : Control crossbar port to which this block is connected +// CHDR_W : AXIS-CHDR data bus width +// NUM_PORTS : Number of block instances +// MTU : Maximum transmission unit (i.e., maximum packet size in +// CHDR words is 2**MTU). +// +`default_nettype none + +module rfnoc_block_keep_one_in_n #( + parameter WIDTH_N = 24, // Must be 31 bits or less + parameter [9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter NUM_PORTS = 1, + parameter [5:0] MTU = 10 +)( + // 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 [CHDR_W*NUM_PORTS-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 [CHDR_W*NUM_PORTS-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 +); + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + // Clocks and Resets + wire ctrlport_clk; + wire ctrlport_rst; + // 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; + // Data Stream to User Logic: in + wire [32*NUM_PORTS-1:0] m_in_axis_tdata; + wire [ NUM_PORTS-1:0] m_in_axis_tlast; + wire [ NUM_PORTS-1:0] m_in_axis_tvalid; + wire [ NUM_PORTS-1:0] m_in_axis_tready; + wire [64*NUM_PORTS-1:0] m_in_axis_ttimestamp; + wire [ NUM_PORTS-1:0] m_in_axis_thas_time; + wire [16*NUM_PORTS-1:0] m_in_axis_tlength; + wire [ NUM_PORTS-1:0] m_in_axis_teov; + wire [ NUM_PORTS-1:0] m_in_axis_teob; + // Data Stream from User Logic: out + wire [32*NUM_PORTS-1:0] s_out_axis_tdata; + wire [ NUM_PORTS-1:0] s_out_axis_tlast; + wire [ NUM_PORTS-1:0] s_out_axis_tvalid; + wire [ NUM_PORTS-1:0] s_out_axis_tready; + wire [64*NUM_PORTS-1:0] s_out_axis_ttimestamp; + wire [ NUM_PORTS-1:0] s_out_axis_thas_time; + wire [ NUM_PORTS-1:0] s_out_axis_teov; + wire [ NUM_PORTS-1:0] s_out_axis_teob; + + wire ce_rst; + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + noc_shell_keep_one_in_n #( + .CHDR_W (CHDR_W), + .THIS_PORTID (THIS_PORTID), + .NUM_PORTS (NUM_PORTS), + .MTU (MTU) + ) noc_shell_keep_one_in_n_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_clk), + .ctrlport_rst (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 (), + // Data Stream to User Logic: in + .m_in_axis_tdata (m_in_axis_tdata), + .m_in_axis_tkeep (), + .m_in_axis_tlast (m_in_axis_tlast), + .m_in_axis_tvalid (m_in_axis_tvalid), + .m_in_axis_tready (m_in_axis_tready), + .m_in_axis_ttimestamp (m_in_axis_ttimestamp), + .m_in_axis_thas_time (m_in_axis_thas_time), + .m_in_axis_tlength (m_in_axis_tlength), + .m_in_axis_teov (m_in_axis_teov), + .m_in_axis_teob (m_in_axis_teob), + // Data Stream from User Logic: out + .s_out_axis_tdata (s_out_axis_tdata), + .s_out_axis_tkeep ({(NUM_PORTS){1'b1}}), + .s_out_axis_tlast (s_out_axis_tlast), + .s_out_axis_tvalid (s_out_axis_tvalid), + .s_out_axis_tready (s_out_axis_tready), + .s_out_axis_ttimestamp (s_out_axis_ttimestamp), + .s_out_axis_thas_time (s_out_axis_thas_time), + .s_out_axis_teov (s_out_axis_teov), + .s_out_axis_teob (s_out_axis_teob) + ); + + wire [ 8*NUM_PORTS-1:0] set_addr; + wire [32*NUM_PORTS-1:0] set_data; + wire [ NUM_PORTS-1:0] set_stb; + wire [ 8*NUM_PORTS-1:0] rb_addr; + reg [64*NUM_PORTS-1:0] rb_data; + wire [ NUM_PORTS-1:0] rb_stb; + + ctrlport_to_settings_bus # ( + .NUM_PORTS (NUM_PORTS) + ) ctrlport_to_settings_bus_i ( + .ctrlport_clk (ctrlport_clk), + .ctrlport_rst (ctrlport_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_has_time (1'b0), + .s_ctrlport_req_time (64'd0), + .s_ctrlport_resp_ack (m_ctrlport_resp_ack), + .s_ctrlport_resp_data (m_ctrlport_resp_data), + .set_data (set_data), + .set_addr (set_addr), + .set_stb (set_stb), + .set_time (), + .set_has_time (), + .rb_stb (rb_stb), + .rb_addr (rb_addr), + .rb_data (rb_data), + .timestamp ()); + + //--------------------------------------------------------------------------- + // User Logic + //--------------------------------------------------------------------------- + + `include "rfnoc_keep_one_in_n_regs.vh" + + wire [REG_N_LEN*NUM_PORTS-1:0] n; + wire [REG_MODE_LEN*NUM_PORTS-1:0] mode; + + genvar i; + for (i = 0; i < NUM_PORTS; i = i+1) begin + + setting_reg #( + .my_addr (REG_N), + .awidth (8), + .width (REG_N_LEN)) + inst_setting_reg_n ( + .clk (ce_clk), + .rst (ce_rst), + .strobe (set_stb[i]), + .addr (set_addr[8*(i+1)-1:8*i]), + .in (set_data[32*(i+1)-1:32*i]), + .out (n[WIDTH_N*(i+1)-1:WIDTH_N*i]), + .changed ()); + + setting_reg #( + .my_addr (REG_MODE), + .awidth (8), + .width (REG_MODE_LEN)) + inst_setting_reg_mode ( + .clk (ce_clk), + .rst (ce_rst), + .strobe (set_stb[i]), + .addr (set_addr[8*(i+1)-1:8*i]), + .in (set_data[32*(i+1)-1:32*i]), + .out (mode[i]), + .changed ()); + + // Readback + assign rb_stb[i] = 1'b1; + always @* + case (rb_addr[8*(i+1)-1:8*i]) + REG_N : rb_data[64*(i+1)-1:64*i] <= {{(64-REG_N_LEN){1'b0}}, n[WIDTH_N*(i+1)-1:WIDTH_N*i]}; + REG_MODE : rb_data[64*(i+1)-1:64*i] <= {{(64-REG_MODE_LEN){1'b0}}, mode[i]}; + REG_WIDTH_N : rb_data[64*(i+1)-1:64*i] <= {{(64-REG_WIDTH_N){1'b0}}, WIDTH_N}; + default : rb_data[64*(i+1)-1:64*i] <= 64'h0BADC0DE0BADC0DE; + endcase + + rfnoc_keep_one_in_n #( + .WIDTH (32), + .WIDTH_N (WIDTH_N)) + inst_rfnoc_keep_one_in_n ( + .clk (ce_clk), + .reset (ce_rst), + .mode (mode[i]), + .n (n[WIDTH_N*(i+1)-1:WIDTH_N*i]), + .s_axis_tdata (m_in_axis_tdata[32*(i+1)-1:32*i]), + .s_axis_tlast (m_in_axis_tlast[i]), + .s_axis_tvalid (m_in_axis_tvalid[i]), + .s_axis_tready (m_in_axis_tready[i]), + .s_axis_ttimestamp (m_in_axis_ttimestamp[64*(i+1)-1:64*i]), + .s_axis_thas_time (m_in_axis_thas_time[i]), + .s_axis_tlength (m_in_axis_tlength[16*(i+1)-1:16*i]), + .s_axis_teov (m_in_axis_teov[i]), + .s_axis_teob (m_in_axis_teob[i]), + .m_axis_tdata (s_out_axis_tdata[32*(i+1)-1:32*i]), + .m_axis_tlast (s_out_axis_tlast[i]), + .m_axis_tvalid (s_out_axis_tvalid[i]), + .m_axis_tready (s_out_axis_tready[i]), + .m_axis_ttimestamp (s_out_axis_ttimestamp[64*(i+1)-1:64*i]), + .m_axis_thas_time (s_out_axis_thas_time[i]), + .m_axis_teov (s_out_axis_teov[i]), + .m_axis_teob (s_out_axis_teob[i])); + end + +endmodule // rfnoc_block_keep_one_in_n + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_block_keep_one_in_n_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_block_keep_one_in_n_tb.sv new file mode 100644 index 000000000..969ff6e69 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_block_keep_one_in_n_tb.sv @@ -0,0 +1,484 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_keep_one_in_n_tb +// +// Description: Testbench for the keep_one_in_n RFNoC block. +// + +`default_nettype none + +module rfnoc_block_keep_one_in_n_tb; + + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + import PkgRfnocItemUtils::*; + + //--------------------------------------------------------------------------- + // Testbench Configuration + //--------------------------------------------------------------------------- + + localparam [ 9:0] THIS_PORTID = 10'h123; + localparam [31:0] NOC_ID = 32'h02460000; + localparam int CHDR_W = 64; + localparam int ITEM_W = 32; + localparam int NUM_PORTS = 2; + localparam int MTU = 13; + localparam int SPP = 64; + localparam int PKT_SIZE_BYTES = SPP * (ITEM_W/8); + localparam int STALL_PROB = 50; // Default BFM stall probability + localparam real CHDR_CLK_PER = 5.0; // 200 MHz + localparam real CTRL_CLK_PER = 25.0; // 40 MHz + localparam real CE_CLK_PER = 5.0; // 200 MHz + + localparam WIDTH_N = 8; + + localparam bit SAMPLE_MODE = 0; + localparam bit PACKET_MODE = 1; + + //--------------------------------------------------------------------------- + // 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] (rfnoc_chdr_clk, 1'b0); + AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (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; + + // Connect block controller to BFMs + for (genvar i = 0; i < NUM_PORTS; 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; 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-1:0] s_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready; + + // DUT Master (Output) Port Signals + logic [CHDR_W*NUM_PORTS-1:0] m_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-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++) 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; 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_keep_one_in_n #( + .WIDTH_N (WIDTH_N), + .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 Logic + //--------------------------------------------------------------------------- + + // Translate the desired register access to a ctrlport write request. + task automatic write_reg(input logic [$clog2(NUM_PORTS)-1:0] port, input ctrl_address_t addr, input logic [31:0] value); + blk_ctrl.reg_write(256*8*port + addr*8, value); + endtask : write_reg + + // Translate the desired register access to a ctrlport read request. + task automatic read_user_reg(input logic [$clog2(NUM_PORTS)-1:0] port, input ctrl_address_t addr, output logic [63:0] value); + blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); + blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); + endtask : read_user_reg + + typedef struct { + item_t samples[$]; + chdr_word_t mdata[$]; + packet_info_t pkt_info; + } test_packet_t; + + // Check if input test packets are identical + function automatic void compare_test_packets(const ref test_packet_t a, b); + string str; + + // Packet payload + $sformat(str, + "Packet payload size incorrect! Expected: %4d, Received: %4d", + a.samples.size(), b.samples.size()); + `ASSERT_ERROR(a.samples.size() == b.samples.size(), str); + + for (int i = 0; i < a.samples.size(); i++) begin + $sformat(str, + "Packet payload word %4d incorrect! Expected: 0x%8X, Received: 0x%8X", + i, a.samples[i], b.samples[i]); + `ASSERT_ERROR(a.samples[i] == b.samples[i], str); + end + + // Packet metadata + $sformat(str, + "Packet metadata size incorrect! Expected: %4d, Received: %4d", + a.mdata.size(), b.mdata.size()); + `ASSERT_ERROR(a.mdata.size() == b.mdata.size(), str); + + for (int i = 0; i < a.mdata.size(); i++) begin + $sformat(str, + "Packet metadata word %04d incorrect! Expected: 0x%8X, Received: 0x%8X", + i, a.mdata[i], b.mdata[i]); + `ASSERT_ERROR(a.mdata[i] == b.mdata[i], str); + end + + // Packet info + $sformat(str, + "Packet info field 'vc' incorrect! Expected: %2d, Received: %2d", + a.pkt_info.vc, b.pkt_info.vc); + `ASSERT_ERROR(a.pkt_info.vc == b.pkt_info.vc, str); + + $sformat(str, + "Packet info field 'eob' incorrect! Expected: %1d, Received: %1d", + a.pkt_info.eob, b.pkt_info.eob); + `ASSERT_ERROR(a.pkt_info.eob == b.pkt_info.eob, str); + + $sformat(str, + "Packet info field 'eov' incorrect! Expected: %1d, Received: %1d", + a.pkt_info.eov, b.pkt_info.eov); + `ASSERT_ERROR(a.pkt_info.eov == b.pkt_info.eov, str); + + $sformat(str, + "Packet info field 'has_time' incorrect! Expected: %1d, Received: %1d", + a.pkt_info.has_time, b.pkt_info.has_time); + `ASSERT_ERROR(a.pkt_info.has_time == b.pkt_info.has_time, str); + + $sformat(str, + "Packet info field 'timestamp' incorrect! Expected: 0x%16X, Received: 0x%16X", + a.pkt_info.timestamp, b.pkt_info.timestamp); + `ASSERT_ERROR(a.pkt_info.timestamp == b.pkt_info.timestamp, str); + + endfunction + + //--------------------------------------------------------------------------- + // Test Tasks + //--------------------------------------------------------------------------- + + task automatic test_keep_one_in_n ( + input bit mode, + input int n, + input int num_packets, + input int port = 0, + input int spp = SPP, + input int stall_prob = STALL_PROB + ); + + mailbox #(test_packet_t) tb_send_packets = new(); + + blk_ctrl.set_master_stall_prob(port, stall_prob); + blk_ctrl.set_slave_stall_prob(port, stall_prob); + + $display("N = %3d, Number of Packets = %3d, Port Number = %1d", n, num_packets, port); + + begin + logic [63:0] readback; + string str; + + write_reg(port, dut.REG_MODE, mode); + read_user_reg(port, dut.REG_MODE, readback); + $sformat(str, + "Mode incorrect! Expected: %1d, Received: %1d", + mode, readback[0]); + `ASSERT_ERROR(readback[0] == mode, str); + + write_reg(port, dut.REG_N, n); + read_user_reg(port, dut.REG_N, readback); + $sformat(str, + "N incorrect! Expected: %5d, Received: %5d", + n, readback); + `ASSERT_ERROR(readback == n, str); + end + + fork + // TX + begin + for (int i = 0; i < num_packets; i++) begin + test_packet_t tb_send_pkt; + + for (int k = 0; k < spp; k++) begin + tb_send_pkt.samples.push_back($urandom()); + end + tb_send_pkt.mdata = {}; + tb_send_pkt.pkt_info = '{ + vc: 0, + eob: (i == num_packets-1), + eov: bit'($urandom()), + has_time: 1'b1, + timestamp: {$urandom(),$urandom()}}; + + blk_ctrl.send_items(port, tb_send_pkt.samples, tb_send_pkt.mdata, tb_send_pkt.pkt_info); + + tb_send_packets.put(tb_send_pkt); + end + end + // RX + begin + int l = 0; + mailbox #(test_packet_t) tb_recv_packets = new(); + int num_packets_expected = int'($ceil(real'(num_packets)/real'(n))); + + for (int i = 0; i < num_packets_expected; i++) begin + test_packet_t tb_recv_pkt; + + blk_ctrl.recv_items_adv(port, tb_recv_pkt.samples, tb_recv_pkt.mdata, tb_recv_pkt.pkt_info); + + tb_recv_packets.put(tb_recv_pkt); + end + + for (int i = 0; i < num_packets; i = i + n) begin + test_packet_t tb_recv_pkt, tb_send_pkt, tb_dropped_pkt; + + // Packet mode keeps first packet, drops n-1 + if (mode) begin + tb_send_packets.get(tb_send_pkt); + for (int k = 0; k < n-1; k++) begin + if (!tb_send_packets.try_get(tb_dropped_pkt)) break; + tb_send_pkt.pkt_info.eob = tb_send_pkt.pkt_info.eob | tb_dropped_pkt.pkt_info.eob; + tb_send_pkt.pkt_info.eov = tb_send_pkt.pkt_info.eov | tb_dropped_pkt.pkt_info.eov; + end + // Sample mode loops through n packets, keeps 1 in n samples + // from all the packets + end else begin + item_t samples_pruned[$]; + + // Peek first packet to grab mdata and packet info + tb_send_packets.peek(tb_send_pkt); + + // Loop through n packets, grabbing 1 in n samples. + for (int k = 0; k < n; k++) begin + + if (!tb_send_packets.try_get(tb_dropped_pkt)) break; + tb_send_pkt.pkt_info.eob = tb_send_pkt.pkt_info.eob | tb_dropped_pkt.pkt_info.eob; + tb_send_pkt.pkt_info.eov = tb_send_pkt.pkt_info.eov | tb_dropped_pkt.pkt_info.eov; + while (l < tb_dropped_pkt.samples.size()) begin + samples_pruned.push_back(tb_dropped_pkt.samples[l]); + l = l + n; + end + // Account for wrap around when dropping samples between packet boundaries + l = l - tb_dropped_pkt.samples.size(); + end + + // Replace packet samples with 1 in n samples of n packets + tb_send_pkt.samples = samples_pruned; + end + + tb_recv_packets.get(tb_recv_pkt); + compare_test_packets(tb_send_pkt, tb_recv_pkt); + end + + begin + string str; + $sformat(str, + "Sent packets queue not empty! Number of extra items: %2d", + tb_send_packets.num); + `ASSERT_ERROR(tb_send_packets.num == 0, str); + $sformat(str, + "Receive packets queue not empty! Number of extra items: %2d", + tb_recv_packets.num); + `ASSERT_ERROR(tb_recv_packets.num == 0, str); + end + end + join + + endtask; + + //--------------------------------------------------------------------------- + // Main Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + + // Initialize the test exec object for this testbench + test.start_tb("rfnoc_block_keep_one_in_n_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, "Incorrect NUM_DATA_I Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value"); + `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); + test.end_test(); + + //-------------------------------- + // Test Sequences + //-------------------------------- + + // Packet mode + test.start_test("Test packet mode", NUM_PORTS*1100us); + for (int port = 0; port < NUM_PORTS; port++) begin + test_keep_one_in_n(PACKET_MODE, 1, 1, port); // mode, n, num_packets, port + test_keep_one_in_n(PACKET_MODE, 1, 2, port); + test_keep_one_in_n(PACKET_MODE, 1, 10, port); + test_keep_one_in_n(PACKET_MODE, 2, 1, port); + test_keep_one_in_n(PACKET_MODE, 2, 2, port); + test_keep_one_in_n(PACKET_MODE, 2, 3, port); + test_keep_one_in_n(PACKET_MODE, 2, 10, port); + test_keep_one_in_n(PACKET_MODE, 2, 51, port); + test_keep_one_in_n(PACKET_MODE, 3, 1, port); + test_keep_one_in_n(PACKET_MODE, 3, 2, port); + test_keep_one_in_n(PACKET_MODE, 3, 3, port); + test_keep_one_in_n(PACKET_MODE, 3, 4, port); + test_keep_one_in_n(PACKET_MODE, 3, 10, port); + test_keep_one_in_n(PACKET_MODE, 3, 53, port); + test_keep_one_in_n(PACKET_MODE, 11, 1, port); + test_keep_one_in_n(PACKET_MODE, 11, 7, port); + test_keep_one_in_n(PACKET_MODE, 11, 10, port); + test_keep_one_in_n(PACKET_MODE, 11, 11, port); + test_keep_one_in_n(PACKET_MODE, 11, 12, port); + test_keep_one_in_n(PACKET_MODE, 11, 13, port); + test_keep_one_in_n(PACKET_MODE, 11, 20, port); + test_keep_one_in_n(PACKET_MODE, 11, 21, port); + test_keep_one_in_n(PACKET_MODE, 11, 32, port); + test_keep_one_in_n(PACKET_MODE, 11, 33, port); + test_keep_one_in_n(PACKET_MODE, 11, 34, port); + test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2**WIDTH_N-2, port); + test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2**WIDTH_N-1, port); + test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2**WIDTH_N , port); + test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-3), port); + test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-2), port); + test_keep_one_in_n(PACKET_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-1), port); + end + test.end_test(); + + // Packet mode + test.start_test("Test sample mode", NUM_PORTS*1100us); + for (int port = 0; port < NUM_PORTS; port++) begin + test_keep_one_in_n(SAMPLE_MODE, 1, 1, port); + test_keep_one_in_n(SAMPLE_MODE, 1, 2, port); + test_keep_one_in_n(SAMPLE_MODE, 1, 10, port); + test_keep_one_in_n(SAMPLE_MODE, 2, 1, port); + test_keep_one_in_n(SAMPLE_MODE, 2, 2, port); + test_keep_one_in_n(SAMPLE_MODE, 2, 3, port); + test_keep_one_in_n(SAMPLE_MODE, 2, 10, port); + test_keep_one_in_n(SAMPLE_MODE, 2, 51, port); + test_keep_one_in_n(SAMPLE_MODE, 3, 1, port); + test_keep_one_in_n(SAMPLE_MODE, 3, 2, port); + test_keep_one_in_n(SAMPLE_MODE, 3, 3, port); + test_keep_one_in_n(SAMPLE_MODE, 3, 4, port); + test_keep_one_in_n(SAMPLE_MODE, 3, 10, port); + test_keep_one_in_n(SAMPLE_MODE, 3, 53, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 1, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 7, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 10, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 11, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 12, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 13, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 20, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 21, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 32, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 33, port); + test_keep_one_in_n(SAMPLE_MODE, 11, 34, port); + test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2**WIDTH_N-2, port); + test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2**WIDTH_N-1, port); + test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2**WIDTH_N , port); + test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-3), port); + test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-2), port); + test_keep_one_in_n(SAMPLE_MODE, 2**WIDTH_N-1, 2*(2**WIDTH_N-1), port); + end + test.end_test(); + + //-------------------------------- + // Finish Up + //-------------------------------- + + // Display final statistics and results + test.end_tb(); + end : tb_main + +endmodule : rfnoc_block_keep_one_in_n_tb + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_keep_one_in_n.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_keep_one_in_n.v new file mode 100644 index 000000000..c41cddda1 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_keep_one_in_n.v @@ -0,0 +1,269 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_keep_one_in_n +// +// Keep one sample and drop N-1 samples. Also, handles timestamp and setting EOB / EOV. +// +// n: Drop n-1 samples or packets +// mode: 0 = operate on samples, 1 = operate on packets that are delineated by tlast +// +`default_nettype none + +module rfnoc_keep_one_in_n #( + parameter WIDTH = 32, + parameter WIDTH_N = 16 +)( + input wire clk, + input wire reset, + input wire mode, + input wire [WIDTH_N-1:0] n, + input wire [WIDTH-1:0] s_axis_tdata, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + input wire [63:0] s_axis_ttimestamp, + input wire s_axis_thas_time, + input wire [15:0] s_axis_tlength, + input wire s_axis_teov, + input wire s_axis_teob, + output wire [WIDTH-1:0] m_axis_tdata, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready, + output wire [63:0] m_axis_ttimestamp, + output wire m_axis_thas_time, + output wire m_axis_teov, + output wire m_axis_teob +); + + reg first_samp = 1'b1; + always @(posedge clk) begin + if (reset) begin + first_samp <= 1'b1; + end else begin + if (s_axis_tvalid & s_axis_tready) begin + first_samp <= s_axis_tlast; + end + end + end + + // Packet mode state machine + // - Keeps first packet which is delinated by tlast + // and drops the following n-1 packets. + // - EOB signifies the end of a stream and causes the + // state machine to stay in or immediately return to the + // "keep" state. + reg state_pkt; + localparam S_PKT_KEEP = 1'd0; + localparam S_PKT_DROP = 1'd1; + + reg [WIDTH_N-1:0] cnt_pkt_n = 2; + + always @(posedge clk) begin + if (reset) begin + cnt_pkt_n <= 2; + state_pkt <= S_PKT_KEEP; + end else begin + case (state_pkt) + S_PKT_KEEP : begin + cnt_pkt_n <= 2; + if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin + // If EOB or n == 1, stay in this state + if (~s_axis_teob & n != 1) begin + state_pkt <= S_PKT_DROP; + end + end + end + S_PKT_DROP : begin + if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin + if (s_axis_teob) begin + state_pkt <= S_PKT_KEEP; + end else begin + cnt_pkt_n <= cnt_pkt_n + 1; + if (cnt_pkt_n >= n) begin + cnt_pkt_n <= 2; + state_pkt <= S_PKT_KEEP; + end + end + end + end + default : state_pkt <= S_PKT_KEEP; + endcase + end + end + + // Sample mode state machine + // - Keeps first sample and drops n-1 samples. + // - EOB also causes this state machine stay in or return + // to the "keep" state. + reg [WIDTH_N-1:0] cnt_samp_n; + + reg state_samp; + localparam S_SAMP_KEEP = 1'd0; + localparam S_SAMP_DROP = 1'd1; + + always @(posedge clk) begin + if (reset) begin + cnt_samp_n <= 2; + state_samp <= S_SAMP_KEEP; + end else begin + case (state_samp) + S_SAMP_KEEP : begin + cnt_samp_n <= 2; + if (s_axis_tvalid & s_axis_tready) begin + // If EOB or n == 1, stay in this state + if (~(s_axis_tlast & s_axis_teob) & n != 1) begin + state_samp <= S_SAMP_DROP; + end + end + end + S_SAMP_DROP : begin + if (s_axis_tvalid & s_axis_tready) begin + if (s_axis_tlast & s_axis_teob) begin + state_samp <= S_SAMP_KEEP; + end else begin + cnt_samp_n <= cnt_samp_n + 1; + if (cnt_samp_n >= n) begin + cnt_samp_n <= 2; + state_samp <= S_SAMP_KEEP; + end + end + end + end + default : state_samp <= S_SAMP_KEEP; + endcase + end + end + + wire keep_sample = mode ? (state_pkt == S_PKT_KEEP) : (state_samp == S_SAMP_KEEP); + + // Output state machine + reg [1:0] state_o; + localparam S_O_FIRST_SAMP = 2'd0; + localparam S_O_OUTPUT = 2'd1; + localparam S_O_LAST_SAMP = 2'd2; + localparam S_O_LAST_SAMP_EOB = 2'd3; + + reg [WIDTH-1:0] sample_reg; + reg [63:0] timestamp_reg; + reg has_time_reg; + reg eov_reg; + reg [15-WIDTH/8:0] length_reg; + reg [15-WIDTH/8:0] length_cnt; + + always @(posedge clk) begin + if (reset) begin + length_cnt <= 2; + sample_reg <= 'd0; + timestamp_reg <= 'd0; + has_time_reg <= 1'b0; + length_reg <= 'd0; + eov_reg <= 1'b0; + state_o <= S_O_FIRST_SAMP; + end else begin + case (state_o) + // Preload the output register sample_reg. This is necessary + // so the state machine can have a sample to output if + // an EOB arrives while dropping samples / packets. If the + // state machine did not have that sample to output when + // an EOB arrives, then the EOB would be dropped because + // you cannot send packets without a payload with RFNoC. + S_O_FIRST_SAMP : begin + length_cnt <= 2; + if (keep_sample & s_axis_tvalid & s_axis_tready) begin + sample_reg <= s_axis_tdata; + timestamp_reg <= s_axis_ttimestamp; + // If this sample isn't the first sample in the input packet, + // then the vita time does not correspond to this sample + // and should be ignored. This situation can happen if the + // packet length is not consistent. + has_time_reg <= s_axis_thas_time & first_samp; + length_reg <= s_axis_tlength[15:$clog2(WIDTH/8)]; + eov_reg <= s_axis_teov; + // First sample is also EOB, so it will be immediately released + if (s_axis_tlast & s_axis_teob) begin + state_o <= S_O_LAST_SAMP_EOB; + // Packet size is 1 sample + end else if (s_axis_tlength[15:$clog2(WIDTH/8)] == 1) begin + state_o <= S_O_LAST_SAMP; + end else begin + state_o <= S_O_OUTPUT; + end + end + end + // Output samples until either we need to either + // set tlast or encounter an EOB + S_O_OUTPUT : begin + if (s_axis_tvalid & s_axis_tready) begin + // Make EOV bit sticky + eov_reg <= eov_reg | s_axis_teov; + if (keep_sample) begin + sample_reg <= s_axis_tdata; + length_cnt <= length_cnt + 1; + end + if (s_axis_tlast & s_axis_teob) begin + state_o <= S_O_LAST_SAMP_EOB; + end else if (keep_sample) begin + // Use length from input packet to set tlast + if (length_cnt >= length_reg) begin + state_o <= S_O_LAST_SAMP; + end + end + end + end + S_O_LAST_SAMP : begin + length_cnt <= 2; + if (s_axis_tvalid & s_axis_tready) begin + if (keep_sample) begin + sample_reg <= s_axis_tdata; + timestamp_reg <= s_axis_ttimestamp; + has_time_reg <= s_axis_thas_time & first_samp; + length_reg <= s_axis_tlength[15:$clog2(WIDTH/8)]; + eov_reg <= s_axis_teov; + end else begin + eov_reg <= eov_reg | s_axis_teov; + end + if (s_axis_tlast & s_axis_teob) begin + state_o <= S_O_LAST_SAMP_EOB; + end else if (keep_sample) begin + if (s_axis_tlength[15:$clog2(WIDTH/8)] > 1) begin + state_o <= S_O_OUTPUT; + end + end + end + end + S_O_LAST_SAMP_EOB : begin + if (s_axis_tready) begin + state_o <= S_O_FIRST_SAMP; + end + end + default : state_o <= S_O_FIRST_SAMP; + endcase + end + end + + assign m_axis_tdata = sample_reg; + assign m_axis_tlast = (state_o == S_O_LAST_SAMP) || (state_o == S_O_LAST_SAMP_EOB); + assign m_axis_ttimestamp = timestamp_reg; + assign m_axis_thas_time = has_time_reg; + assign m_axis_teov = eov_reg; + assign m_axis_teob = (state_o == S_O_LAST_SAMP_EOB); + + assign m_axis_tvalid = (state_o == S_O_FIRST_SAMP) ? 1'b0 : + (state_o == S_O_OUTPUT) ? keep_sample & s_axis_tvalid : + (state_o == S_O_LAST_SAMP) ? keep_sample & s_axis_tvalid : + (state_o == S_O_LAST_SAMP_EOB) ? 1'b1 : + 1'b0; + + assign s_axis_tready = (state_o == S_O_FIRST_SAMP) ? 1'b1 : + (state_o == S_O_OUTPUT) ? m_axis_tready : + (state_o == S_O_LAST_SAMP) ? m_axis_tready : + (state_o == S_O_LAST_SAMP_EOB) ? m_axis_tready : + 1'b0; + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_keep_one_in_n_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_keep_one_in_n_regs.vh new file mode 100644 index 000000000..e3de9a680 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_keep_one_in_n/rfnoc_keep_one_in_n_regs.vh @@ -0,0 +1,25 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_keep_one_in_n_regs +// +// Description: Header file for rfnoc_block_keep_one_in_n_regs. +// + +// Offset in bytes between each ports's bank of registers. This is a hardcoded +// value in ctrlport_to_settings_bus.v put here for documentation purposes only. +localparam REG_BANK_OFFSET = 2**11; // 2048 + +// [WIDTH_N-1:0] : N, drop N-1 samples or packets +localparam REG_N = 0; +localparam REG_N_LEN = WIDTH_N; + +// [0:0] : 0 = Sample Mode, 1 = Packet Mode +localparam REG_MODE = 1; +localparam REG_MODE_LEN = 1; + +// [31:0] : Bit width of N, Read Only +localparam REG_WIDTH_N = 2; +localparam REG_WIDTH_N_LEN = 32; |