diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio')
14 files changed, 4447 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile new file mode 100644 index 000000000..63d6f1851 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile @@ -0,0 +1,47 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir +BASE_DIR = $(abspath ../../../../top) +# Include viv_sim_preamble 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_BLOCK_RADIO_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_radio_all_tb + +SIM_SRCS = \ +$(abspath sim_radio_gen.sv) \ +$(abspath rfnoc_block_radio_tb.sv) \ +$(abspath rfnoc_block_radio_all_tb.sv) + +# MODELSIM_USER_DO = $(abspath wave.do) + +#------------------------------------------------- +# 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_radio/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile.srcs new file mode 100644 index 000000000..84dd01541 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile.srcs @@ -0,0 +1,20 @@ +# +# Copyright 2018 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +RFNOC_BLOCK_RADIO_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_radio/, \ +rfnoc_block_radio_regs.vh \ +radio_rx_core.v \ +radio_tx_core.v \ +radio_core.v \ +noc_shell_radio.v \ +rfnoc_block_radio.v \ +rx_frontend_gen3.v \ +tx_frontend_gen3.v \ +quarter_rate_downconverter.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v new file mode 100644 index 000000000..32ab32b63 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v @@ -0,0 +1,290 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_radio +// +// Description: A NoC Shell for RFNoC. This should eventually be replaced +// by an auto-generated NoC Shell. +// + +module noc_shell_radio #( + parameter [31:0] NOC_ID = 32'h0, + parameter [ 9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter [ 0:0] CTRLPORT_SLV_EN = 1, + parameter [ 0:0] CTRLPORT_MST_EN = 1, + parameter [ 5:0] CTRL_FIFO_SIZE = 9, + parameter [ 5:0] NUM_DATA_I = 1, + parameter [ 5:0] NUM_DATA_O = 1, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter PYLD_FIFO_SIZE = 10, + parameter MTU = 10 +)( + //--------------------------------------------------------------------------- + // Framework Interface + //--------------------------------------------------------------------------- + + // RFNoC Framework Clocks and Resets + input wire rfnoc_chdr_clk, + output wire rfnoc_chdr_rst, + input wire rfnoc_ctrl_clk, + output wire rfnoc_ctrl_rst, + // RFNoC Backend Interface + input wire [ 511:0] rfnoc_core_config, + output wire [ 511:0] rfnoc_core_status, + // CHDR Input Ports (from framework) + input wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata, + input wire [ NUM_DATA_I-1:0] s_rfnoc_chdr_tlast, + input wire [ NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid, + output wire [ NUM_DATA_I-1:0] s_rfnoc_chdr_tready, + // CHDR Output Ports (to framework) + output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata, + output wire [ NUM_DATA_O-1:0] m_rfnoc_chdr_tlast, + output wire [ NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid, + input wire [ NUM_DATA_O-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, + + //--------------------------------------------------------------------------- + // Client Control Port Interface + //--------------------------------------------------------------------------- + + // Clock + input wire ctrlport_clk, + input wire ctrlport_rst, + // 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, + output wire [ 3:0] m_ctrlport_req_byte_en, + output wire m_ctrlport_req_has_time, + output wire [63:0] m_ctrlport_req_time, + input wire m_ctrlport_resp_ack, + input wire [ 1:0] m_ctrlport_resp_status, + input wire [31:0] m_ctrlport_resp_data, + // Slave + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [19:0] s_ctrlport_req_addr, + input wire [ 9:0] s_ctrlport_req_portid, + input wire [15:0] s_ctrlport_req_rem_epid, + input wire [ 9:0] s_ctrlport_req_rem_portid, + input wire [31:0] s_ctrlport_req_data, + input wire [ 3:0] s_ctrlport_req_byte_en, + input wire s_ctrlport_req_has_time, + input wire [63:0] s_ctrlport_req_time, + output wire s_ctrlport_resp_ack, + output wire [ 1:0] s_ctrlport_resp_status, + output wire [31:0] s_ctrlport_resp_data, + + //--------------------------------------------------------------------------- + // Client Data Interface + //--------------------------------------------------------------------------- + + // Clock + input wire axis_data_clk, + input wire axis_data_rst, + + // Output data stream (to user logic) + output wire [(NUM_DATA_I*ITEM_W*NIPC)-1:0] m_axis_tdata, + output wire [ (NUM_DATA_I*NIPC)-1:0] m_axis_tkeep, + output wire [ NUM_DATA_I-1:0] m_axis_tlast, + output wire [ NUM_DATA_I-1:0] m_axis_tvalid, + input wire [ NUM_DATA_I-1:0] m_axis_tready, + // Sideband information + output wire [ (NUM_DATA_I*64)-1:0] m_axis_ttimestamp, + output wire [ NUM_DATA_I-1:0] m_axis_thas_time, + output wire [ NUM_DATA_I-1:0] m_axis_teov, + output wire [ NUM_DATA_I-1:0] m_axis_teob, + + // Input data stream (from user logic) + input wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_tdata, + input wire [ (NUM_DATA_O*NIPC)-1:0] s_axis_tkeep, + input wire [ NUM_DATA_O-1:0] s_axis_tlast, + input wire [ NUM_DATA_O-1:0] s_axis_tvalid, + output wire [ NUM_DATA_O-1:0] s_axis_tready, + // Sideband info (sampled on the first cycle of the packet) + input wire [ (NUM_DATA_O*64)-1:0] s_axis_ttimestamp, + input wire [ NUM_DATA_O-1:0] s_axis_thas_time, + input wire [ NUM_DATA_O-1:0] s_axis_teov, + input wire [ NUM_DATA_O-1:0] s_axis_teob +); + + localparam SNK_INFO_FIFO_SIZE = 4; + localparam SNK_PYLD_FIFO_SIZE = PYLD_FIFO_SIZE; + localparam SRC_INFO_FIFO_SIZE = 4; + localparam SRC_PYLD_FIFO_SIZE = MTU; + + //--------------------------------------------------------------------------- + // 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 (NOC_ID), + .NUM_DATA_I (NUM_DATA_I), + .NUM_DATA_O (NUM_DATA_O), + .CTRL_FIFOSIZE (CTRL_FIFO_SIZE), + .MTU (MTU) + ) backend_iface_i ( + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .rfnoc_core_config (rfnoc_core_config), + .rfnoc_core_status (rfnoc_core_status), + .rfnoc_chdr_rst (rfnoc_chdr_rst), + .rfnoc_ctrl_rst (rfnoc_ctrl_rst), + .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) + ); + + //--------------------------------------------------------------------------- + // Control Path + //--------------------------------------------------------------------------- + + ctrlport_endpoint #( + .THIS_PORTID (THIS_PORTID ), + .SYNC_CLKS (0 ), + .AXIS_CTRL_MST_EN (CTRLPORT_SLV_EN), + .AXIS_CTRL_SLV_EN (CTRLPORT_MST_EN), + .SLAVE_FIFO_SIZE (CTRL_FIFO_SIZE ) + ) ctrlport_ep_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_byte_en ), + .m_ctrlport_req_has_time (m_ctrlport_req_has_time ), + .m_ctrlport_req_time (m_ctrlport_req_time ), + .m_ctrlport_resp_ack (m_ctrlport_resp_ack ), + .m_ctrlport_resp_status (m_ctrlport_resp_status ), + .m_ctrlport_resp_data (m_ctrlport_resp_data ), + .s_ctrlport_req_wr (s_ctrlport_req_wr ), + .s_ctrlport_req_rd (s_ctrlport_req_rd ), + .s_ctrlport_req_addr (s_ctrlport_req_addr ), + .s_ctrlport_req_portid (s_ctrlport_req_portid ), + .s_ctrlport_req_rem_epid (s_ctrlport_req_rem_epid ), + .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), + .s_ctrlport_req_data (s_ctrlport_req_data ), + .s_ctrlport_req_byte_en (s_ctrlport_req_byte_en ), + .s_ctrlport_req_has_time (s_ctrlport_req_has_time ), + .s_ctrlport_req_time (s_ctrlport_req_time ), + .s_ctrlport_resp_ack (s_ctrlport_resp_ack ), + .s_ctrlport_resp_status (s_ctrlport_resp_status ), + .s_ctrlport_resp_data (s_ctrlport_resp_data ) + ); + + //--------------------------------------------------------------------------- + // Data Path + //--------------------------------------------------------------------------- + + genvar i; + generate + + for (i = 0; i < NUM_DATA_I; i = i + 1) begin: chdr_to_data + chdr_to_axis_data #( + .CHDR_W (CHDR_W), + .ITEM_W (ITEM_W), + .NIPC (NIPC), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE (SNK_INFO_FIFO_SIZE), + .PYLD_FIFO_SIZE (SNK_PYLD_FIFO_SIZE) + ) chdr_to_axis_data_i ( + .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 [(i*CHDR_W)+:CHDR_W]), + .s_axis_chdr_tlast (s_rfnoc_chdr_tlast [i]), + .s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid [i]), + .s_axis_chdr_tready (s_rfnoc_chdr_tready [i]), + .m_axis_tdata (m_axis_tdata [i*ITEM_W*NIPC +: ITEM_W*NIPC]), + .m_axis_tkeep (m_axis_tkeep [i*NIPC +: NIPC]), + .m_axis_tlast (m_axis_tlast [i]), + .m_axis_tvalid (m_axis_tvalid [i]), + .m_axis_tready (m_axis_tready [i]), + .m_axis_ttimestamp (m_axis_ttimestamp [i*64 +: 64]), + .m_axis_thas_time (m_axis_thas_time [i]), + .m_axis_tlength (), + .m_axis_teov (m_axis_teov [i]), + .m_axis_teob (m_axis_teob [i]), + .flush_en (data_i_flush_en), + .flush_timeout (data_i_flush_timeout), + .flush_active (data_i_flush_active [i]), + .flush_done (data_i_flush_done [i]) + ); + end + + for (i = 0; i < NUM_DATA_O; i = i + 1) begin: data_to_chdr + axis_data_to_chdr #( + .CHDR_W (CHDR_W), + .ITEM_W (ITEM_W), + .NIPC (NIPC), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE (4), + .PYLD_FIFO_SIZE (SRC_INFO_FIFO_SIZE), + .MTU (SRC_PYLD_FIFO_SIZE) + ) axis_data_to_chdr_i ( + .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 [i*CHDR_W +: CHDR_W]), + .m_axis_chdr_tlast (m_rfnoc_chdr_tlast [i]), + .m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid [i]), + .m_axis_chdr_tready (m_rfnoc_chdr_tready [i]), + .s_axis_tdata (s_axis_tdata [i*ITEM_W*NIPC +: ITEM_W*NIPC]), + .s_axis_tkeep (s_axis_tkeep [i*NIPC +: NIPC]), + .s_axis_tlast (s_axis_tlast [i]), + .s_axis_tvalid (s_axis_tvalid [i]), + .s_axis_tready (s_axis_tready [i]), + .s_axis_ttimestamp (s_axis_ttimestamp [i*64 +: 64]), + .s_axis_thas_time (s_axis_thas_time [i]), + .s_axis_teov (s_axis_teov [i]), + .s_axis_teob (s_axis_teob [i]), + .flush_en (data_o_flush_en), + .flush_timeout (data_o_flush_timeout), + .flush_active (data_o_flush_active [i]), + .flush_done (data_o_flush_done [i]) + ); + end + endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v new file mode 100644 index 000000000..ded9a8c0b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v @@ -0,0 +1,138 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// mixer with 90 degree angles, i.e., multiplying the input signal with 1, i, -1, -i: + +// Let S(t) = I(t) + i*Q(t) be the input signal based on inputs i_in and q_in +// Multiplying with (1,i,-1,-i) then becomes: +// S(t) * 1 = I(t) + i*Q(t) +// S(t) * i = -Q(t) + i*I(t) +// S(t) * -1 = -I(t) - i*Q(t) +// S(t) * -i = Q(t) - i*I(t) + +// To control the direction of rotation, the dirctn input is used +// When set to 0, the phase is increased with pi/2 every sample, i.e., rotating counter clock wise +// When set to 1, the phase is increased with -pi/2 every sample, i.e., rotating clock wise + +// the input is the concatenation of the i and q signal: {i_in, q_in} + +module quarter_rate_downconverter #( + parameter WIDTH=24 +)( + input clk, + input reset, + input phase_sync, + + input [2*WIDTH-1:0] i_tdata, + input i_tlast, + input i_tvalid, + output i_tready, + + output [2*WIDTH-1:0] o_tdata, + output o_tlast, + output o_tvalid, + input o_tready, + + input dirctn +); + + // temporary signals for i and q after rotation + reg [WIDTH-1:0] tmp_i = {WIDTH{1'b0}}; + reg [WIDTH-1:0] tmp_q = {WIDTH{1'b0}}; + + // State machine types and reg + localparam S0=0, S1=1, S2=2, S3=3; + reg[1:0] cur_state; + + // split input into i and q signal + wire[WIDTH-1:0] i_in, q_in; + assign i_in = i_tdata[2*WIDTH-1:WIDTH]; + assign q_in = i_tdata[WIDTH-1:0]; + + // The state machine doing the rotations among states + always @(posedge clk) begin + if(reset || phase_sync) begin + cur_state <= S0; + end else begin + case (cur_state) + S0: begin + if(i_tvalid == 1'b1 && i_tready == 1'b1) + if(dirctn == 1'b0) + cur_state <= S1; + else + cur_state <= S3; + else + cur_state <= S0; + end + S1: begin + if(i_tvalid == 1'b1 && i_tready == 1'b1) + if(dirctn == 1'b0) + cur_state <= S2; + else + cur_state <= S0; + else + cur_state <= S1; + end + S2: begin + if(i_tvalid == 1'b1 && i_tready == 1'b1) + if(dirctn == 1'b0) + cur_state <= S3; + else + cur_state <= S1; + else + cur_state <= S2; + end + S3: begin + if(i_tvalid == 1'b1 && i_tready == 1'b1) + if(dirctn == 1'b0) + cur_state <= S0; + else + cur_state <= S2; + else + cur_state <= S3; + end + endcase + end + end + + // Multiplication of input IQ signal with (1,i,-1,-i): + always @(*) begin + case (cur_state) + S0: begin + // S(t) * 1 = I(t) + iQ(t): + tmp_i = i_in; + tmp_q = q_in; + end + S1: begin + // S(t) * i = -Q(t) + iI(t): + tmp_i = -q_in; + tmp_q = i_in; + end + S2: begin + // S(t) * -1 = -I(t) - iQ(t): + tmp_i = -i_in; + tmp_q = -q_in; + end + S3: begin + // S(t) * -i = Q(t) - iI(t): + tmp_i = q_in; + tmp_q = -i_in; + end + default: begin + tmp_i = i_in; + tmp_q = q_in; + end + endcase + end + + // Flop for valid and ready signals and shortening of comb. paths. + axi_fifo #(.WIDTH(2*WIDTH + 1), .SIZE(1)) flop ( + .clk(clk), .reset(reset), .clear(1'b0), + .i_tdata({i_tlast, tmp_i, tmp_q}), .i_tvalid(i_tvalid), .i_tready(i_tready), + .o_tdata({o_tlast, o_tdata}), .o_tvalid(o_tvalid), .o_tready(o_tready), + .occupied(), .space()); + +endmodule // quarter_rate_downconverter diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v new file mode 100644 index 000000000..9456fc398 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v @@ -0,0 +1,370 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: radio_core +// +// Description: +// +// A radio core for RFNoC. This core contains all logic in the radio clock +// domain for interfacing to a single RX/TX radio. It includes registers shared +// by both Rx and Tx logic and instantiates Rx and Tx interface cores. +// +// Parameters: +// +// BASE_ADDR : Base address for this radio block instance +// SAMP_W : Width of a radio sample +// NSPC : Number of radio samples per radio clock cycle +// + + +module radio_core #( + parameter SAMP_W = 32, + parameter NSPC = 1 +) ( + input wire radio_clk, + input wire radio_rst, + + + //--------------------------------------------------------------------------- + // Control Interface + //--------------------------------------------------------------------------- + + // 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 wire s_ctrlport_resp_ack, + output wire [31:0] s_ctrlport_resp_data, + + // Master + output wire m_ctrlport_req_wr, + output wire [19:0] m_ctrlport_req_addr, + output wire [ 9:0] m_ctrlport_req_portid, + output wire [15:0] m_ctrlport_req_rem_epid, + output wire [ 9:0] m_ctrlport_req_rem_portid, + output wire [31:0] m_ctrlport_req_data, + output wire m_ctrlport_req_has_time, + output wire [63:0] m_ctrlport_req_time, + input wire m_ctrlport_resp_ack, + + + //--------------------------------------------------------------------------- + // Data Interface + //--------------------------------------------------------------------------- + + // Tx Radio Data Stream + input wire [(SAMP_W*NSPC)-1:0] s_axis_tdata, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + // Sideband info + input wire [ 63:0] s_axis_ttimestamp, + input wire s_axis_thas_time, + input wire s_axis_teob, + + // Rx Radio Data Stream + output wire [(SAMP_W*NSPC)-1:0] m_axis_tdata, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready, + // Sideband info + output wire [ 63:0] m_axis_ttimestamp, + output wire m_axis_thas_time, + output wire m_axis_teob, + + + //--------------------------------------------------------------------------- + // Radio Interface + //--------------------------------------------------------------------------- + + input wire [63:0] radio_time, + + // Radio Rx Interface + input wire [SAMP_W*NSPC-1:0] radio_rx_data, + input wire radio_rx_stb, + output wire radio_rx_running, + + // Radio Tx Interface + output wire [SAMP_W*NSPC-1:0] radio_tx_data, + input wire radio_tx_stb, + output wire radio_tx_running +); + + `include "rfnoc_block_radio_regs.vh" + + + //--------------------------------------------------------------------------- + // Split Control Port Interface + //--------------------------------------------------------------------------- + // + // This block splits the single slave interface of the radio core into + // multiple interfaces, one for each subcomponent. The responses from each + // subcomponent are merged into a single response and sent back out the slave + // interface. + // + //--------------------------------------------------------------------------- + + // Registers shared by Rx and Tx + wire ctrlport_general_req_wr; + wire ctrlport_general_req_rd; + wire [19:0] ctrlport_general_req_addr; + wire [31:0] ctrlport_general_req_data; + reg ctrlport_general_resp_ack = 1'b0; + reg [31:0] ctrlport_general_resp_data = 0; + + // Tx core registers + wire ctrlport_tx_req_wr; + wire ctrlport_tx_req_rd; + wire [19:0] ctrlport_tx_req_addr; + wire [31:0] ctrlport_tx_req_data; + wire ctrlport_tx_resp_ack; + wire [31:0] ctrlport_tx_resp_data; + + // Rx core registers + wire ctrlport_rx_req_wr; + wire ctrlport_rx_req_rd; + wire [19:0] ctrlport_rx_req_addr; + wire [31:0] ctrlport_rx_req_data; + wire ctrlport_rx_resp_ack; + wire [31:0] ctrlport_rx_resp_data; + + ctrlport_splitter #( + .NUM_SLAVES (3) + ) ctrlport_decoder_i ( + .ctrlport_clk (radio_clk), + .ctrlport_rst (radio_rst), + .s_ctrlport_req_wr (s_ctrlport_req_wr), + .s_ctrlport_req_rd (s_ctrlport_req_rd), + .s_ctrlport_req_addr (s_ctrlport_req_addr), + .s_ctrlport_req_data (s_ctrlport_req_data), + .s_ctrlport_req_byte_en (4'b0), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (s_ctrlport_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (s_ctrlport_resp_data), + .m_ctrlport_req_wr ({ctrlport_general_req_wr, + ctrlport_tx_req_wr, + ctrlport_rx_req_wr}), + .m_ctrlport_req_rd ({ctrlport_general_req_rd, + ctrlport_tx_req_rd, + ctrlport_rx_req_rd}), + .m_ctrlport_req_addr ({ctrlport_general_req_addr, + ctrlport_tx_req_addr, + ctrlport_rx_req_addr}), + .m_ctrlport_req_data ({ctrlport_general_req_data, + ctrlport_tx_req_data, + ctrlport_rx_req_data}), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack ({ctrlport_general_resp_ack, + ctrlport_tx_resp_ack, + ctrlport_rx_resp_ack}), + .m_ctrlport_resp_status (6'b0), + .m_ctrlport_resp_data ({ctrlport_general_resp_data, + ctrlport_tx_resp_data, + ctrlport_rx_resp_data}) + ); + + + //--------------------------------------------------------------------------- + // Merge Control Port Interfaces + //--------------------------------------------------------------------------- + // + // This block merges the master control port interfaces of the Rx and Tx + // cores into a single master control port interface. Both the Rx and Tx + // cores support error reporting by writing to a control port interface. This + // block arbitrates the requests between the Rx and Tx cores. Rx and Tx only + // support writes for error reporting, not reads. Time and byte enables are + // also not needed. Hence, several ports are unconnected. + // + //--------------------------------------------------------------------------- + + // Tx and Rx error reporting signals + wire ctrlport_err_tx_req_wr, ctrlport_err_rx_req_wr; + wire [19:0] ctrlport_err_tx_req_addr, ctrlport_err_rx_req_addr; + wire [31:0] ctrlport_err_tx_req_data, ctrlport_err_rx_req_data; + wire ctrlport_err_tx_req_has_time, ctrlport_err_rx_req_has_time; + wire [63:0] ctrlport_err_tx_req_time, ctrlport_err_rx_req_time; + wire [ 9:0] ctrlport_err_tx_req_portid, ctrlport_err_rx_req_portid; + wire [15:0] ctrlport_err_tx_req_rem_epid, ctrlport_err_rx_req_rem_epid; + wire [ 9:0] ctrlport_err_tx_req_rem_portid, ctrlport_err_rx_req_rem_portid; + wire ctrlport_err_tx_resp_ack, ctrlport_err_rx_resp_ack; + + + ctrlport_combiner #( + .NUM_MASTERS (2), + .PRIORITY (0) + ) ctrlport_req_combine_i ( + .ctrlport_clk (radio_clk), + .ctrlport_rst (radio_rst), + .s_ctrlport_req_wr ({ctrlport_err_tx_req_wr, ctrlport_err_rx_req_wr}), + .s_ctrlport_req_rd (2'b0), + .s_ctrlport_req_addr ({ctrlport_err_tx_req_addr, ctrlport_err_rx_req_addr}), + .s_ctrlport_req_portid ({ctrlport_err_tx_req_portid, ctrlport_err_rx_req_portid}), + .s_ctrlport_req_rem_epid ({ctrlport_err_tx_req_rem_epid, ctrlport_err_rx_req_rem_epid}), + .s_ctrlport_req_rem_portid ({ctrlport_err_tx_req_rem_portid, ctrlport_err_rx_req_rem_portid}), + .s_ctrlport_req_data ({ctrlport_err_tx_req_data, ctrlport_err_rx_req_data}), + .s_ctrlport_req_byte_en (8'hFF), + .s_ctrlport_req_has_time ({ctrlport_err_tx_req_has_time, ctrlport_err_rx_req_has_time}), + .s_ctrlport_req_time ({ctrlport_err_tx_req_time, ctrlport_err_rx_req_time}), + .s_ctrlport_resp_ack ({ctrlport_err_tx_resp_ack, ctrlport_err_rx_resp_ack}), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .m_ctrlport_req_wr (m_ctrlport_req_wr), + .m_ctrlport_req_rd (), + .m_ctrlport_req_addr (m_ctrlport_req_addr), + .m_ctrlport_req_portid (m_ctrlport_req_portid), + .m_ctrlport_req_rem_epid (m_ctrlport_req_rem_epid), + .m_ctrlport_req_rem_portid (m_ctrlport_req_rem_portid), + .m_ctrlport_req_data (m_ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (m_ctrlport_req_has_time), + .m_ctrlport_req_time (m_ctrlport_req_time), + .m_ctrlport_resp_ack (m_ctrlport_resp_ack), + .m_ctrlport_resp_status (2'b0), + .m_ctrlport_resp_data (0) + ); + + + //--------------------------------------------------------------------------- + // General Registers + //--------------------------------------------------------------------------- + // + // These are registers that apply to both Rx and Tx and are shared by both. + // + //--------------------------------------------------------------------------- + + reg reg_loopback_en = 1'b0; + + always @(posedge radio_clk) begin + if (radio_rst) begin + ctrlport_general_resp_ack <= 0; + ctrlport_general_resp_data <= 0; + reg_loopback_en <= 0; + end else begin + // Default assignments + ctrlport_general_resp_ack <= 0; + ctrlport_general_resp_data <= 0; + + // Handle register writes + if (ctrlport_general_req_wr) begin + case (ctrlport_general_req_addr) + REG_LOOPBACK_EN: begin + reg_loopback_en <= ctrlport_general_req_data[0]; + ctrlport_general_resp_ack <= 1; + end + endcase + end + + // Handle register reads + if (ctrlport_general_req_rd) begin + case (ctrlport_general_req_addr) + REG_LOOPBACK_EN: begin + ctrlport_general_resp_data <= 0; + ctrlport_general_resp_data[0] <= reg_loopback_en; + ctrlport_general_resp_ack <= 1; + end + REG_RADIO_WIDTH: begin + ctrlport_general_resp_data <= { SAMP_W[15:0], NSPC[15:0] }; + ctrlport_general_resp_ack <= 1; + end + endcase + end + end + end + + + //--------------------------------------------------------------------------- + // Tx to Rx Loopback + //--------------------------------------------------------------------------- + + wire [SAMP_W*NSPC-1:0] radio_rx_data_mux; + wire radio_rx_stb_mux; + + assign radio_rx_data_mux = reg_loopback_en ? radio_tx_data : radio_rx_data; + assign radio_rx_stb_mux = reg_loopback_en ? radio_tx_stb : radio_rx_stb; + + + //--------------------------------------------------------------------------- + // Tx Core + //--------------------------------------------------------------------------- + + radio_tx_core #( + .SAMP_W (SAMP_W), + .NSPC (NSPC) + ) radio_tx_core_i ( + .radio_clk (radio_clk), + .radio_rst (radio_rst), + .s_ctrlport_req_wr (ctrlport_tx_req_wr), + .s_ctrlport_req_rd (ctrlport_tx_req_rd), + .s_ctrlport_req_addr (ctrlport_tx_req_addr), + .s_ctrlport_req_data (ctrlport_tx_req_data), + .s_ctrlport_resp_ack (ctrlport_tx_resp_ack), + .s_ctrlport_resp_data (ctrlport_tx_resp_data), + .m_ctrlport_req_wr (ctrlport_err_tx_req_wr), + .m_ctrlport_req_addr (ctrlport_err_tx_req_addr), + .m_ctrlport_req_data (ctrlport_err_tx_req_data), + .m_ctrlport_req_has_time (ctrlport_err_tx_req_has_time), + .m_ctrlport_req_time (ctrlport_err_tx_req_time), + .m_ctrlport_req_portid (ctrlport_err_tx_req_portid), + .m_ctrlport_req_rem_epid (ctrlport_err_tx_req_rem_epid), + .m_ctrlport_req_rem_portid (ctrlport_err_tx_req_rem_portid), + .m_ctrlport_resp_ack (ctrlport_err_tx_resp_ack), + .radio_time (radio_time), + .radio_tx_data (radio_tx_data), + .radio_tx_stb (radio_tx_stb), + .radio_tx_running (radio_tx_running), + .s_axis_tdata (s_axis_tdata), + .s_axis_tlast (s_axis_tlast), + .s_axis_tvalid (s_axis_tvalid), + .s_axis_tready (s_axis_tready), + .s_axis_ttimestamp (s_axis_ttimestamp), + .s_axis_thas_time (s_axis_thas_time), + .s_axis_teob (s_axis_teob) + ); + + + //--------------------------------------------------------------------------- + // Rx Core + //--------------------------------------------------------------------------- + + radio_rx_core #( + .SAMP_W (SAMP_W), + .NSPC (NSPC) + ) radio_rx_core_i ( + .radio_clk (radio_clk), + .radio_rst (radio_rst), + .s_ctrlport_req_wr (ctrlport_rx_req_wr), + .s_ctrlport_req_rd (ctrlport_rx_req_rd), + .s_ctrlport_req_addr (ctrlport_rx_req_addr), + .s_ctrlport_req_data (ctrlport_rx_req_data), + .s_ctrlport_resp_ack (ctrlport_rx_resp_ack), + .s_ctrlport_resp_data (ctrlport_rx_resp_data), + .m_ctrlport_req_wr (ctrlport_err_rx_req_wr), + .m_ctrlport_req_addr (ctrlport_err_rx_req_addr), + .m_ctrlport_req_data (ctrlport_err_rx_req_data), + .m_ctrlport_req_has_time (ctrlport_err_rx_req_has_time), + .m_ctrlport_req_time (ctrlport_err_rx_req_time), + .m_ctrlport_req_portid (ctrlport_err_rx_req_portid), + .m_ctrlport_req_rem_epid (ctrlport_err_rx_req_rem_epid), + .m_ctrlport_req_rem_portid (ctrlport_err_rx_req_rem_portid), + .m_ctrlport_resp_ack (ctrlport_err_rx_resp_ack), + .radio_time (radio_time), + .radio_rx_data (radio_rx_data_mux), + .radio_rx_stb (radio_rx_stb_mux), + .radio_rx_running (radio_rx_running), + .m_axis_tdata (m_axis_tdata), + .m_axis_tlast (m_axis_tlast), + .m_axis_tvalid (m_axis_tvalid), + .m_axis_tready (m_axis_tready), + .m_axis_ttimestamp (m_axis_ttimestamp), + .m_axis_thas_time (m_axis_thas_time), + .m_axis_teob (m_axis_teob) + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v new file mode 100644 index 000000000..ee7774fd7 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v @@ -0,0 +1,521 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: radio_rx_core +// +// Description: +// +// This module contains the core Rx radio acquisition logic. It retrieves +// sample data from the radio interface, as indicated by the radio's strobe +// signal, and outputs the data via AXI-Stream. +// +// The receiver is operated by writing a time (optionally) to the +// REG_RX_CMD_TIME_* registers and a number of words (optionally) to +// REG_RX_CMD_NUM_WORDS_* registers followed by writing a command word to +// REG_RX_CMD. The command word indicates whether it is a finite ("num samps +// and done") or continuous acquisition and whether or not the acquisition +// should start at the time indicated byREG_RX_CMD_TIME_*. A stop command will +// stop any acquisition that's waiting to start or is in progress. +// +// The REG_RX_MAX_WORDS_PER_PKT and REG_RX_ERR_* registers should be +// initialized prior to the first acquisition. +// +// Parameters: +// +// SAMP_W : Width of a radio sample +// NSPC : Number of radio samples per radio clock cycle +// +`default_nettype none + + +module radio_rx_core #( + parameter SAMP_W = 32, + parameter NSPC = 1 +) ( + input wire radio_clk, + input wire radio_rst, + + + //--------------------------------------------------------------------------- + // Control Interface + //--------------------------------------------------------------------------- + + // Slave (Register Reads and Writes) + 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 = 1'b0, + output reg [31:0] s_ctrlport_resp_data, + + // Master (Error Reporting) + output reg m_ctrlport_req_wr = 1'b0, + output reg [19:0] m_ctrlport_req_addr, + output reg [31:0] m_ctrlport_req_data, + output wire m_ctrlport_req_has_time, + output reg [63:0] m_ctrlport_req_time, + output wire [ 9:0] m_ctrlport_req_portid, + output wire [15:0] m_ctrlport_req_rem_epid, + output wire [ 9:0] m_ctrlport_req_rem_portid, + input wire m_ctrlport_resp_ack, + + + //--------------------------------------------------------------------------- + // Radio Interface + //--------------------------------------------------------------------------- + + input wire [63:0] radio_time, + + input wire [SAMP_W*NSPC-1:0] radio_rx_data, + input wire radio_rx_stb, + + // Status indicator (true when receiving) + output wire radio_rx_running, + + + //--------------------------------------------------------------------------- + // AXI-Stream Data Output + //--------------------------------------------------------------------------- + + output wire [SAMP_W*NSPC-1:0] m_axis_tdata, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready, + // Sideband info + output wire [ 63:0] m_axis_ttimestamp, + output wire m_axis_thas_time, + output wire m_axis_teob +); + + `include "rfnoc_block_radio_regs.vh" + `include "../../core/rfnoc_chdr_utils.vh" + + localparam NUM_WORDS_LEN = RX_CMD_NUM_WORDS_LEN; + + + //--------------------------------------------------------------------------- + // Register Read/Write Logic + //--------------------------------------------------------------------------- + + reg reg_cmd_valid = 0; // Indicates when the CMD_FIFO has been written + reg [ RX_CMD_LEN-1:0] reg_cmd_word = 0; // Command to execute + reg [NUM_WORDS_LEN-1:0] reg_cmd_num_words = 0; // Number of words for the command + reg [ 63:0] reg_cmd_time = 0; // Time for the command + reg reg_cmd_timed = 0; // Indicates if this is a timed command + reg [ 31:0] reg_max_pkt_len = 64; // Maximum words per packet + reg [ 9:0] reg_error_portid = 0; // Port ID to use for error reporting + reg [ 15:0] reg_error_rem_epid = 0; // Remote EPID to use for error reporting + reg [ 9:0] reg_error_rem_portid = 0; // Remote port ID to use for error reporting + reg [ 19:0] reg_error_addr = 0; // Address to use for error reporting + reg reg_has_time = 1; // Whether or not to use timestamps on data + + wire [15:0] cmd_fifo_space; // Empty space in the command FIFO + reg cmd_stop = 0; // Indicates a full stop request + wire cmd_stop_ack; // Acknowledgment that a stop has completed + reg clear_fifo = 0; // Signal to clear the command FIFO + + assign m_axis_thas_time = reg_has_time; + + always @(posedge radio_clk) begin + if (radio_rst) begin + s_ctrlport_resp_ack <= 0; + reg_cmd_valid <= 0; + reg_cmd_word <= 0; + reg_cmd_num_words <= 0; + reg_cmd_time <= 0; + reg_cmd_timed <= 0; + reg_max_pkt_len <= 64; + reg_error_portid <= 0; + reg_error_rem_epid <= 0; + reg_error_rem_portid <= 0; + reg_error_addr <= 0; + reg_has_time <= 1; + clear_fifo <= 0; + cmd_stop <= 0; + end else begin + // Default assignments + s_ctrlport_resp_ack <= 0; + s_ctrlport_resp_data <= 0; + reg_cmd_valid <= 0; + clear_fifo <= 0; + + // Clear stop register when we enter the STOP state + if (cmd_stop_ack) cmd_stop <= 1'b0; + + // Handle register writes + if (s_ctrlport_req_wr) begin + case (s_ctrlport_req_addr) + REG_RX_CMD: begin + // All commands go into the command FIFO except STOP + reg_cmd_valid <= (s_ctrlport_req_data[RX_CMD_LEN-1:0] != RX_CMD_STOP); + reg_cmd_word <= s_ctrlport_req_data[RX_CMD_LEN-1:0]; + reg_cmd_timed <= s_ctrlport_req_data[RX_CMD_TIMED_POS]; + s_ctrlport_resp_ack <= 1; + + // cmd_stop must remain asserted until it has completed + if (!cmd_stop || cmd_stop_ack) begin + cmd_stop <= (s_ctrlport_req_data[RX_CMD_LEN-1:0] == RX_CMD_STOP); + end + clear_fifo <= (s_ctrlport_req_data[RX_CMD_LEN-1:0] == RX_CMD_STOP); + end + REG_RX_CMD_NUM_WORDS_LO: begin + reg_cmd_num_words[31:0] <= s_ctrlport_req_data; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_NUM_WORDS_HI: begin + reg_cmd_num_words[NUM_WORDS_LEN-1:32] <= s_ctrlport_req_data[NUM_WORDS_LEN-32-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_TIME_LO: begin + reg_cmd_time[31:0] <= s_ctrlport_req_data; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_TIME_HI: begin + reg_cmd_time[63:32] <= s_ctrlport_req_data; + s_ctrlport_resp_ack <= 1; + end + REG_RX_MAX_WORDS_PER_PKT: begin + reg_max_pkt_len <= s_ctrlport_req_data; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_PORT: begin + reg_error_portid <= s_ctrlport_req_data[9:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_REM_PORT: begin + reg_error_rem_portid <= s_ctrlport_req_data[9:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_REM_EPID: begin + reg_error_rem_epid <= s_ctrlport_req_data[15:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_ADDR: begin + reg_error_addr <= s_ctrlport_req_data[19:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_HAS_TIME: begin + reg_has_time <= s_ctrlport_req_data[0:0]; + s_ctrlport_resp_ack <= 1; + end + endcase + end + + // Handle register reads + if (s_ctrlport_req_rd) begin + case (s_ctrlport_req_addr) + REG_RX_STATUS: begin + s_ctrlport_resp_data[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] + <= cmd_fifo_space[CMD_FIFO_SPACE_LEN-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD: begin + s_ctrlport_resp_data[RX_CMD_LEN-1:0] <= reg_cmd_word; + s_ctrlport_resp_data[RX_CMD_TIMED_POS] <= reg_cmd_timed; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_NUM_WORDS_LO: begin + s_ctrlport_resp_data <= reg_cmd_num_words[31:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_NUM_WORDS_HI: begin + s_ctrlport_resp_data[NUM_WORDS_LEN-32-1:0] <= reg_cmd_num_words[NUM_WORDS_LEN-1:32]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_TIME_LO: begin + s_ctrlport_resp_data <= reg_cmd_time[31:0]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_CMD_TIME_HI: begin + s_ctrlport_resp_data <= reg_cmd_time[63:32]; + s_ctrlport_resp_ack <= 1; + end + REG_RX_MAX_WORDS_PER_PKT: begin + s_ctrlport_resp_data <= reg_max_pkt_len; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_PORT: begin + s_ctrlport_resp_data[9:0] <= reg_error_portid; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_REM_PORT: begin + s_ctrlport_resp_data[9:0] <= reg_error_rem_portid; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_REM_EPID: begin + s_ctrlport_resp_data[15:0] <= reg_error_rem_epid; + s_ctrlport_resp_ack <= 1; + end + REG_RX_ERR_ADDR: begin + s_ctrlport_resp_data[19:0] <= reg_error_addr; + s_ctrlport_resp_ack <= 1; + end + REG_RX_DATA: begin + s_ctrlport_resp_data <= radio_rx_data; + s_ctrlport_resp_ack <= 1; + end + REG_RX_HAS_TIME: begin + s_ctrlport_resp_data[0] <= reg_has_time; + s_ctrlport_resp_ack <= 1; + end + endcase + end + + end + end + + + //--------------------------------------------------------------------------- + // Command Queue + //--------------------------------------------------------------------------- + + wire [ 63:0] cmd_time; // Time for next start of command + wire cmd_timed; // Command is timed (use cmd_time) + wire [NUM_WORDS_LEN-1:0] cmd_num_words; // Number of words for next command + wire cmd_continuous; // Command is continuous (ignore cmd_num_words) + wire cmd_valid; // cmd_* is a valid command + wire cmd_done; // Command has completed and can be popped from FIFO + + axi_fifo #( + .WIDTH (64 + 1 + NUM_WORDS_LEN + 1), + .SIZE (5) // Ideally, this size will lead to an SRL-based FIFO + ) cmd_fifo ( + .clk (radio_clk), + .reset (radio_rst), + .clear (clear_fifo), + .i_tdata ({ reg_cmd_time, reg_cmd_timed, reg_cmd_num_words, (reg_cmd_word == RX_CMD_CONTINUOUS) }), + .i_tvalid (reg_cmd_valid), + .i_tready (), + .o_tdata ({ cmd_time, cmd_timed, cmd_num_words, cmd_continuous }), + .o_tvalid (cmd_valid), + .o_tready (cmd_done), + .space (cmd_fifo_space), + .occupied () + ); + + + //--------------------------------------------------------------------------- + // Receiver State Machine + //--------------------------------------------------------------------------- + + // FSM state values + localparam ST_IDLE = 0; + localparam ST_TIME_CHECK = 1; + localparam ST_RUNNING = 2; + localparam ST_STOP = 3; + localparam ST_REPORT_ERR = 4; + localparam ST_REPORT_ERR_WAIT = 5; + + reg [ 2:0] state = ST_IDLE; // Current state + reg [NUM_WORDS_LEN-1:0] words_left; // Words left in current command + reg [ 31:0] words_left_pkt; // Words left in current packet + reg first_word = 1'b1; // Next word is first in packet + reg [ 15:0] seq_num = 0; // Sequence number (packet count) + reg [ 63:0] error_time; // Time at which overflow occurred + reg [ERR_RX_CODE_W-1:0] error_code; // Error code register + + // Output FIFO signals + wire [ 15:0] out_fifo_space; + reg [SAMP_W*NSPC-1:0] out_fifo_tdata; + reg out_fifo_tlast; + reg out_fifo_tvalid = 1'b0; + reg [ 63:0] out_fifo_timestamp; + reg out_fifo_teob; + reg out_fifo_almost_full; + + reg [63:0] radio_time_low_samp, radio_time_hi_samp; + reg time_now, time_past; + + // All ctrlport requests have a time + assign m_ctrlport_req_has_time = 1'b1; + + // Acknowledge STOP requests and pop the command FIFO in the STOP state + assign cmd_stop_ack = (state == ST_STOP); + assign cmd_done = (state == ST_STOP); + + always @(posedge radio_clk) begin + if (radio_rst) begin + state <= ST_IDLE; + out_fifo_tvalid <= 1'b0; + seq_num <= 'd0; + m_ctrlport_req_wr <= 1'b0; + first_word <= 1'b1; + end else begin + // Default assignments + out_fifo_tvalid <= 1'b0; + out_fifo_tlast <= 1'b0; + out_fifo_teob <= 1'b0; + m_ctrlport_req_wr <= 1'b0; + + if (radio_rx_stb) begin + // Get the time for the low sample and the high sample of the radio + // word (needed when NISPC > 1). Compensate for the delay required to + // check the time by adding 3 clock cycles worth of samples. + radio_time_low_samp <= (radio_time + 3*NSPC); + radio_time_hi_samp <= (radio_time + 3*NSPC + (NSPC-1)); + + // Register the time comparisons so they don't become the critical path + time_now <= (cmd_time >= radio_time_low_samp && + cmd_time <= radio_time_hi_samp); + time_past <= (cmd_time < radio_time_low_samp); + end + + case (state) + ST_IDLE : begin + // Wait for a new command to arrive and allow a cycle for the time + // comparisons to update. + if (cmd_valid && radio_rx_stb) begin + state <= ST_TIME_CHECK; + end else if (cmd_stop) begin + state <= ST_STOP; + end + first_word <= 1'b1; + end + + ST_TIME_CHECK : begin + if (cmd_stop) begin + // Nothing to do but stop (timed STOP commands are not supported) + state <= ST_STOP; + end else if (cmd_timed && time_past && radio_rx_stb) begin + // Got this command later than its execution time + //synthesis translate_off + $display("WARNING: radio_rx_core: Late command error"); + //synthesis translate_on + error_code <= ERR_RX_LATE_CMD; + error_time <= radio_time; + state <= ST_REPORT_ERR; + end else if (!cmd_timed || (time_now && radio_rx_stb)) begin + // Either it's time to run this command or it should run + // immediately. + words_left <= cmd_num_words; + words_left_pkt <= reg_max_pkt_len; + state <= ST_RUNNING; + end + end + + ST_RUNNING : begin + if (radio_rx_stb) begin + // Output the next word + out_fifo_tvalid <= 1'b1; + out_fifo_tdata <= radio_rx_data; + if (first_word) begin + out_fifo_timestamp <= radio_time; + first_word <= 1'b0; + end + + // Update word counters + words_left <= words_left - 1; + words_left_pkt <= words_left_pkt - 1; + + if ((words_left == 1 && !cmd_continuous) || cmd_stop) begin + // This command has finished, or we've been asked to stop. + state <= ST_STOP; + out_fifo_tlast <= 1'b1; + out_fifo_teob <= 1'b1; + first_word <= 1'b1; + end else if (words_left_pkt == 1) begin + // We've finished building a packet + seq_num <= seq_num + 1; + words_left_pkt <= reg_max_pkt_len; + out_fifo_tlast <= 1'b1; + first_word <= 1'b1; + end + + // Check for overflow. Note that we've left enough room in the + // output FIFO so that we can end the packet cleanly. + if (out_fifo_almost_full) begin + // End the command and terminate packet early + //synthesis translate_off + $display("WARNING: radio_rx_core: Overrun error"); + //synthesis translate_on + out_fifo_tlast <= 1'b1; + out_fifo_teob <= 1'b1; + seq_num <= seq_num + 1; + error_time <= radio_time; + error_code <= ERR_RX_OVERRUN; + state <= ST_REPORT_ERR; + end + + end + end + + ST_STOP : begin + // This single-cycle state allows time for STOP to be acknowledged + // and for the command FIFO to be popped. + state <= ST_IDLE; + end + + ST_REPORT_ERR : begin + // Setup write of error code + m_ctrlport_req_wr <= 1'b1; + m_ctrlport_req_data <= 0; + m_ctrlport_req_data[ERR_RX_CODE_W-1:0] <= error_code; + m_ctrlport_req_addr <= reg_error_addr; + m_ctrlport_req_time <= error_time; + state <= ST_REPORT_ERR_WAIT; + end + + ST_REPORT_ERR_WAIT : begin + // Wait for write of error code and timestamp to complete + if (m_ctrlport_resp_ack) begin + state <= ST_STOP; + end + end + + default : state <= ST_IDLE; + endcase + end + end + + + assign radio_rx_running = (state == ST_RUNNING); // We're actively acquiring + + // Directly connect the port ID, remote port ID, and remote EPID since they + // are only used for error reporting. + assign m_ctrlport_req_portid = reg_error_portid; + assign m_ctrlport_req_rem_epid = reg_error_rem_epid; + assign m_ctrlport_req_rem_portid = reg_error_rem_portid; + + + //--------------------------------------------------------------------------- + // Output FIFO + //--------------------------------------------------------------------------- + // + // Here we buffer output samples and monitor FIFO fullness to be able to + // detect overflows. + // + //--------------------------------------------------------------------------- + + axi_fifo #( + .WIDTH (1+64+1+SAMP_W*NSPC), + .SIZE (5) // Ideally, this size will lead to an SRL-based FIFO + ) output_fifo ( + .clk (radio_clk), + .reset (radio_rst), + .clear (1'b0), + .i_tdata ({out_fifo_teob, out_fifo_timestamp, out_fifo_tlast, out_fifo_tdata}), + .i_tvalid (out_fifo_tvalid), + .i_tready (), + .o_tdata ({m_axis_teob, m_axis_ttimestamp, m_axis_tlast, m_axis_tdata}), + .o_tvalid (m_axis_tvalid), + .o_tready (m_axis_tready), + .space (out_fifo_space), + .occupied () + ); + + // Create a register to indicate if the output FIFO is about to overflow + always @(posedge radio_clk) begin + if (radio_rst) begin + out_fifo_almost_full <= 1'b0; + end else begin + out_fifo_almost_full <= (out_fifo_space < 5); + end + end + + +endmodule + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v new file mode 100644 index 000000000..d40db5122 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v @@ -0,0 +1,417 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: radio_tx_core +// +// Description: +// +// This module contains the core Tx radio data-path logic. It receives samples +// over AXI-Stream that it then sends to the radio interface coincident with a +// strobe signal that must be provided by the radio interface. +// +// There are no registers for starting or stopping the transmitter. It is +// operated simply by providing data packets via its AXI-Stream data interface. +// The end-of-burst (EOB) signal is used to indicate when the transmitter is +// allowed to stop transmitting. Packet timestamps can be used to indicate when +// transmission should start. +// +// Care must be taken to provide data to the transmitter at a rate that is +// faster than the radio needs it so that underflows do not occur. Similarly, +// timed packets must be delivered before the timestamp expires. If a packet +// arrives late, then it will be dropped and the error will be reported via the +// CTRL port interface. +// +// Parameters: +// +// SAMP_W : Width of a radio sample +// NSPC : Number of radio samples per radio clock cycle +// + + +module radio_tx_core #( + parameter SAMP_W = 32, + parameter NSPC = 1 +) ( + input wire radio_clk, + input wire radio_rst, + + + //--------------------------------------------------------------------------- + // Control Interface + //--------------------------------------------------------------------------- + + // Slave (Register Reads and Writes) + 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 = 1'b0, + output reg [31:0] s_ctrlport_resp_data, + + // Master (Error Reporting) + output reg m_ctrlport_req_wr = 1'b0, + output reg [19:0] m_ctrlport_req_addr, + output reg [31:0] m_ctrlport_req_data, + output wire m_ctrlport_req_has_time, + output reg [63:0] m_ctrlport_req_time, + output wire [ 9:0] m_ctrlport_req_portid, + output wire [15:0] m_ctrlport_req_rem_epid, + output wire [ 9:0] m_ctrlport_req_rem_portid, + input wire m_ctrlport_resp_ack, + + + //--------------------------------------------------------------------------- + // Radio Interface + //--------------------------------------------------------------------------- + + input wire [63:0] radio_time, + + output wire [SAMP_W*NSPC-1:0] radio_tx_data, + input wire radio_tx_stb, + + // Status indicator (true when transmitting) + output wire radio_tx_running, + + + //--------------------------------------------------------------------------- + // AXI-Stream Data Input + //--------------------------------------------------------------------------- + + input wire [SAMP_W*NSPC-1:0] s_axis_tdata, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + // Sideband info + input wire [ 63:0] s_axis_ttimestamp, + input wire s_axis_thas_time, + input wire s_axis_teob +); + + `include "rfnoc_block_radio_regs.vh" + `include "../../core/rfnoc_chdr_utils.vh" + + + //--------------------------------------------------------------------------- + // Register Read/Write Logic + //--------------------------------------------------------------------------- + + reg [SAMP_W-1:0] reg_idle_value = 0; // Value to output when transmitter is idle + reg [ 9:0] reg_error_portid = 0; // Port ID to use for error reporting + reg [ 15:0] reg_error_rem_epid = 0; // Remote EPID to use for error reporting + reg [ 9:0] reg_error_rem_portid = 0; // Remote port ID to use for error reporting + reg [ 19:0] reg_error_addr = 0; // Address to use for error reporting + + reg [TX_ERR_POLICY_LEN-1:0] reg_policy = TX_ERR_POLICY_PACKET; + + always @(posedge radio_clk) begin + if (radio_rst) begin + s_ctrlport_resp_ack <= 0; + reg_idle_value <= 0; + reg_error_portid <= 0; + reg_error_rem_epid <= 0; + reg_error_rem_portid <= 0; + reg_error_addr <= 0; + reg_policy <= TX_ERR_POLICY_PACKET; + end else begin + // Default assignments + s_ctrlport_resp_ack <= 0; + s_ctrlport_resp_data <= 0; + + // Handle register writes + if (s_ctrlport_req_wr) begin + case (s_ctrlport_req_addr) + REG_TX_IDLE_VALUE: begin + reg_idle_value <= s_ctrlport_req_data[SAMP_W-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERROR_POLICY: begin + // Only allow valid configurations + case (s_ctrlport_req_data[TX_ERR_POLICY_LEN-1:0]) + TX_ERR_POLICY_PACKET : reg_policy <= TX_ERR_POLICY_PACKET; + TX_ERR_POLICY_BURST : reg_policy <= TX_ERR_POLICY_BURST; + default : reg_policy <= TX_ERR_POLICY_PACKET; + endcase + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_PORT: begin + reg_error_portid <= s_ctrlport_req_data[9:0]; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_REM_PORT: begin + reg_error_rem_portid <= s_ctrlport_req_data[9:0]; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_REM_EPID: begin + reg_error_rem_epid <= s_ctrlport_req_data[15:0]; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_ADDR: begin + reg_error_addr <= s_ctrlport_req_data[19:0]; + s_ctrlport_resp_ack <= 1; + end + endcase + end + + // Handle register reads + if (s_ctrlport_req_rd) begin + case (s_ctrlport_req_addr) + REG_TX_IDLE_VALUE: begin + s_ctrlport_resp_data[SAMP_W-1:0] <= reg_idle_value; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERROR_POLICY: begin + s_ctrlport_resp_data[TX_ERR_POLICY_LEN-1:0] <= reg_policy; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_PORT: begin + s_ctrlport_resp_data[9:0] <= reg_error_portid; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_REM_PORT: begin + s_ctrlport_resp_data[9:0] <= reg_error_rem_portid; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_REM_EPID: begin + s_ctrlport_resp_data[15:0] <= reg_error_rem_epid; + s_ctrlport_resp_ack <= 1; + end + REG_TX_ERR_ADDR: begin + s_ctrlport_resp_data[19:0] <= reg_error_addr; + s_ctrlport_resp_ack <= 1; + end + endcase + end + end + end + + + //--------------------------------------------------------------------------- + // Transmitter State Machine + //--------------------------------------------------------------------------- + + // FSM state values + localparam ST_IDLE = 0; + localparam ST_TIME_CHECK = 1; + localparam ST_TRANSMIT = 2; + localparam ST_POLICY_WAIT = 3; + + reg [1:0] state = ST_IDLE; + + reg sop = 1'b1; // Start of packet + + reg [ERR_TX_CODE_W-1:0] new_error_code; + reg [ 63:0] new_error_time; + reg new_error_valid = 1'b0; + + reg time_now, time_past; + + + always @(posedge radio_clk) begin + if (radio_rst) begin + state <= ST_IDLE; + sop <= 1'b1; + new_error_valid <= 1'b0; + end else begin + new_error_valid <= 1'b0; + + // Register time comparisons so they don't become the critical path + time_now <= (radio_time == s_axis_ttimestamp); + time_past <= (radio_time > s_axis_ttimestamp); + + // Track if the next word will be the start of a packet (sop) + if (s_axis_tvalid && s_axis_tready) begin + sop <= s_axis_tlast; + end + + case (state) + ST_IDLE : begin + // Wait for a new packet to arrive and allow a cycle for the time + // comparisons to update. + if (s_axis_tvalid) begin + state <= ST_TIME_CHECK; + end + end + + ST_TIME_CHECK : begin + if (!s_axis_thas_time || time_now) begin + // We have a new packet without a timestamp, or a new packet + // whose time has arrived. + state <= ST_TRANSMIT; + end else if (time_past) begin + // We have a new packet with a timestamp, but the time has passed. + //synthesis translate off + $display("WARNING: radio_tx_core: Late data error"); + //synthesis translate_on + new_error_code <= ERR_TX_LATE_DATA; + new_error_time <= radio_time; + new_error_valid <= 1'b1; + state <= ST_POLICY_WAIT; + end + end + + ST_TRANSMIT : begin + if (radio_tx_stb) begin + if (!s_axis_tvalid) begin + // The radio strobed for new data but we don't have any to give + //synthesis translate off + $display("WARNING: radio_tx_core: Underrun error"); + //synthesis translate_on + new_error_code <= ERR_TX_UNDERRUN; + new_error_time <= radio_time; + new_error_valid <= 1'b1; + state <= ST_POLICY_WAIT; + end else if (s_axis_tlast && s_axis_teob) begin + // We're done with this burst of packets, so acknowledge EOB and + // go back to idle. + new_error_code <= ERR_TX_EOB_ACK; + new_error_time <= radio_time; + new_error_valid <= 1'b1; + state <= ST_IDLE; + end + end + end + + ST_POLICY_WAIT : begin + // If we came here from ST_TIME_CHECK or ST_TRANSMIT and we're in the + // middle of a packet then we just wait until we reach the end of the + // packet. + if (s_axis_tvalid && s_axis_tlast) begin + // We're either at the end of a packet or between packets + if (reg_policy == TX_ERR_POLICY_PACKET || + (reg_policy == TX_ERR_POLICY_BURST && s_axis_teob)) begin + state <= ST_IDLE; + end + + // If we came from ST_TRANSMIT and we happen to already be between + // packets (i.e., we underflowed while waiting for the next packet). + end else if (!s_axis_tvalid && sop) begin + if (reg_policy == TX_ERR_POLICY_PACKET) state <= ST_IDLE; + end + end + + default : state <= ST_IDLE; + endcase + end + end + + + // Output the current sample whenever we're transmitting and the sample is + // valid. Otherwise, output the idle value. + assign radio_tx_data = (s_axis_tvalid && state == ST_TRANSMIT) ? + s_axis_tdata : + {NSPC{reg_idle_value[SAMP_W-1:0]}}; + + // Read packet in the transmit state or dump it in the error state + assign s_axis_tready = (radio_tx_stb && (state == ST_TRANSMIT)) || + (state == ST_POLICY_WAIT); + + // Indicate whether Tx interface is actively transmitting + assign radio_tx_running = (state == ST_TRANSMIT); + + + //--------------------------------------------------------------------------- + // Error FIFO + //--------------------------------------------------------------------------- + // + // This FIFO queues up errors in case we get multiple errors in a row faster + // than they can be reported. If the FIFO fills then new errors will be + // ignored. + // + //--------------------------------------------------------------------------- + + // Error information + wire [ERR_TX_CODE_W-1:0] next_error_code; + wire [ 63:0] next_error_time; + wire next_error_valid; + reg next_error_ready = 1'b0; + + wire new_error_ready; + + axi_fifo_short #( + .WIDTH (64 + ERR_TX_CODE_W) + ) error_fifo ( + .clk (radio_clk), + .reset (radio_rst), + .clear (1'b0), + .i_tdata ({new_error_time, new_error_code}), + .i_tvalid (new_error_valid & new_error_ready), // Mask with ready to prevent FIFO corruption + .i_tready (new_error_ready), + .o_tdata ({next_error_time, next_error_code}), + .o_tvalid (next_error_valid), + .o_tready (next_error_ready), + .space (), + .occupied () + ); + + //synthesis translate_off + // Output a message if the error FIFO overflows + always @(posedge radio_clk) begin + if (new_error_valid && !new_error_ready) begin + $display("WARNING: Tx error report dropped!"); + end + end + //synthesis translate_on + + + //--------------------------------------------------------------------------- + // Error Reporting State Machine + //--------------------------------------------------------------------------- + // + // This state machine reports errors that have been queued up in the error + // FIFO. + // + //--------------------------------------------------------------------------- + + localparam ST_ERR_IDLE = 0; + localparam ST_ERR_CODE = 1; + + reg [0:0] err_state = ST_ERR_IDLE; + + // All ctrlport requests have a time + assign m_ctrlport_req_has_time = 1'b1; + + always @(posedge radio_clk) begin + if (radio_rst) begin + m_ctrlport_req_wr <= 1'b0; + err_state <= ST_ERR_IDLE; + next_error_ready <= 1'b0; + end else begin + m_ctrlport_req_wr <= 1'b0; + next_error_ready <= 1'b0; + + case (err_state) + ST_ERR_IDLE : begin + if (next_error_valid) begin + // Setup write of error code + m_ctrlport_req_wr <= 1'b1; + m_ctrlport_req_addr <= reg_error_addr; + m_ctrlport_req_data <= {{(32-ERR_TX_CODE_W){1'b0}}, next_error_code}; + m_ctrlport_req_time <= next_error_time; + next_error_ready <= 1'b1; + err_state <= ST_ERR_CODE; + end + end + + ST_ERR_CODE : begin + // Wait for write of error code and timestamp + if (m_ctrlport_resp_ack) begin + err_state <= ST_ERR_IDLE; + end + end + + default : err_state <= ST_ERR_IDLE; + endcase + end + end + + + // Directly connect the port ID, remote port ID, remote EPID since they are + // only used for error reporting. + assign m_ctrlport_req_portid = reg_error_portid; + assign m_ctrlport_req_rem_epid = reg_error_rem_epid; + assign m_ctrlport_req_rem_portid = reg_error_rem_portid; + + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v new file mode 100644 index 000000000..a97b141c0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v @@ -0,0 +1,546 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio +// +// Description: This is the top-level file for the RFNoC radio block. +// +// Parameters: +// +// THIS_PORTID : CTRL port ID to which this block is connected +// CHDR_W : CHDR AXI-Stream data bus width +// NIPC : Number of radio samples per radio clock cycle +// ITEM_W : Radio sample width +// NUM_PORTS : Number of radio channels (RX/TX pairs) +// MTU : Maximum transmission unit (i.e., maximum packet size) +// in CHDR words is 2**MTU. +// CTRL_FIFO_SIZE : Size of the Control Port slave FIFO. This affects the +// number of outstanding commands that can be pending. +// PERIPH_BASE_ADDR : CTRL port peripheral window base address +// PERIPH_ADDR_W : CTRL port peripheral address space = 2**PERIPH_ADDR_W +// + + +module rfnoc_block_radio #( + parameter THIS_PORTID = 0, + parameter CHDR_W = 64, + parameter NIPC = 1, + parameter ITEM_W = 32, + parameter NUM_PORTS = 2, + parameter MTU = 10, + parameter CTRL_FIFO_SIZE = 9, + parameter PERIPH_BASE_ADDR = 20'h80000, + parameter PERIPH_ADDR_W = 19 +) ( + //--------------------------------------------------------------------------- + // AXIS CHDR Port + //--------------------------------------------------------------------------- + + input wire rfnoc_chdr_clk, + + // CHDR inputs 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, + + // CHDR outputs 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, + + // Backend interface + input wire [511:0] rfnoc_core_config, + output wire [511:0] rfnoc_core_status, + + + //--------------------------------------------------------------------------- + // AXIS CTRL Port + //--------------------------------------------------------------------------- + + input wire rfnoc_ctrl_clk, + + // CTRL port requests 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, + + // CTRL port requests 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, + + + //--------------------------------------------------------------------------- + // CTRL Port Peripheral Interface + //--------------------------------------------------------------------------- + + 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, + output wire [ 3:0] m_ctrlport_req_byte_en, + output wire m_ctrlport_req_has_time, + output wire [63:0] m_ctrlport_req_time, + input wire m_ctrlport_resp_ack, + input wire [ 1:0] m_ctrlport_resp_status, + input wire [31:0] m_ctrlport_resp_data, + + + //--------------------------------------------------------------------------- + // Radio Interface + //--------------------------------------------------------------------------- + + input wire radio_clk, + + // Timekeeper interface + input wire [63:0] radio_time, + + // Radio Rx interface + input wire [(ITEM_W*NIPC)*NUM_PORTS-1:0] radio_rx_data, + input wire [ NUM_PORTS-1:0] radio_rx_stb, + output wire [ NUM_PORTS-1:0] radio_rx_running, + + // Radio Tx interface + output wire [(ITEM_W*NIPC)*NUM_PORTS-1:0] radio_tx_data, + input wire [ NUM_PORTS-1:0] radio_tx_stb, + output wire [ NUM_PORTS-1:0] radio_tx_running +); + + `include "rfnoc_block_radio_regs.vh" + `include "../../core/rfnoc_axis_ctrl_utils.vh" + + localparam NOC_ID = 32'h12AD1000; + localparam RADIO_W = NIPC*ITEM_W; + + + // Radio Tx data stream + wire [RADIO_W*NUM_PORTS-1:0] axis_tx_tdata; + wire [ NUM_PORTS-1:0] axis_tx_tlast; + wire [ NUM_PORTS-1:0] axis_tx_tvalid; + wire [ NUM_PORTS-1:0] axis_tx_tready; + wire [ 64*NUM_PORTS-1:0] axis_tx_ttimestamp; + wire [ NUM_PORTS-1:0] axis_tx_thas_time; + wire [ NUM_PORTS-1:0] axis_tx_teob; + + // Radio Rx data stream + wire [RADIO_W*NUM_PORTS-1:0] axis_rx_tdata; + wire [ NUM_PORTS-1:0] axis_rx_tlast; + wire [ NUM_PORTS-1:0] axis_rx_tvalid; + wire [ NUM_PORTS-1:0] axis_rx_tready; + wire [ 64*NUM_PORTS-1:0] axis_rx_ttimestamp; + wire [ NUM_PORTS-1:0] axis_rx_thas_time; + wire [ NUM_PORTS-1:0] axis_rx_teob; + + // Control port signals used for register access (NoC shell masters user logic) + wire ctrlport_reg_req_wr; + wire ctrlport_reg_req_rd; + wire [19:0] ctrlport_reg_req_addr; + wire ctrlport_reg_has_time; + wire [63:0] ctrlport_reg_time; + wire [31:0] ctrlport_reg_req_data; + wire [31:0] ctrlport_reg_resp_data; + wire ctrlport_reg_resp_ack; + + // Control port signals used for error reporting (user logic masters to NoC shell) + wire ctrlport_err_req_wr; + wire [19:0] ctrlport_err_req_addr; + wire [ 9:0] ctrlport_err_req_portid; + wire [15:0] ctrlport_err_req_rem_epid; + wire [ 9:0] ctrlport_err_req_rem_portid; + wire [31:0] ctrlport_err_req_data; + wire ctrlport_err_req_has_time; + wire [63:0] ctrlport_err_req_time; + wire ctrlport_err_resp_ack; + + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + wire rfnoc_chdr_rst; + wire radio_rst; + + noc_shell_radio #( + .NOC_ID (NOC_ID), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .CTRLPORT_SLV_EN (1), + .CTRLPORT_MST_EN (1), + .CTRL_FIFO_SIZE (CTRL_FIFO_SIZE), + .NUM_DATA_I (NUM_PORTS), + .NUM_DATA_O (NUM_PORTS), + .ITEM_W (ITEM_W), + .NIPC (NIPC), + .PYLD_FIFO_SIZE (MTU), + .MTU (MTU) + ) noc_shell_radio_i ( + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .rfnoc_chdr_rst (rfnoc_chdr_rst), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .rfnoc_ctrl_rst (), + .rfnoc_core_config (rfnoc_core_config), + .rfnoc_core_status (rfnoc_core_status), + .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 (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), + .ctrlport_clk (radio_clk), + .ctrlport_rst (radio_rst), + .m_ctrlport_req_wr (ctrlport_reg_req_wr), + .m_ctrlport_req_rd (ctrlport_reg_req_rd), + .m_ctrlport_req_addr (ctrlport_reg_req_addr), + .m_ctrlport_req_data (ctrlport_reg_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (ctrlport_reg_has_time), + .m_ctrlport_req_time (ctrlport_reg_time), + .m_ctrlport_resp_ack (ctrlport_reg_resp_ack), + .m_ctrlport_resp_status (AXIS_CTRL_STS_OKAY), + .m_ctrlport_resp_data (ctrlport_reg_resp_data), + .s_ctrlport_req_wr (ctrlport_err_req_wr), + .s_ctrlport_req_rd (1'b0), + .s_ctrlport_req_addr (ctrlport_err_req_addr), + .s_ctrlport_req_portid (ctrlport_err_req_portid), + .s_ctrlport_req_rem_epid (ctrlport_err_req_rem_epid), + .s_ctrlport_req_rem_portid (ctrlport_err_req_rem_portid), + .s_ctrlport_req_data (ctrlport_err_req_data), + .s_ctrlport_req_byte_en (4'hF), + .s_ctrlport_req_has_time (ctrlport_err_req_has_time), + .s_ctrlport_req_time (ctrlport_err_req_time), + .s_ctrlport_resp_ack (ctrlport_err_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .axis_data_clk (radio_clk), + .axis_data_rst (radio_rst), + .m_axis_tdata (axis_tx_tdata), + .m_axis_tkeep (), // Radio only transmits full words + .m_axis_tlast (axis_tx_tlast), + .m_axis_tvalid (axis_tx_tvalid), + .m_axis_tready (axis_tx_tready), + .m_axis_ttimestamp (axis_tx_ttimestamp), + .m_axis_thas_time (axis_tx_thas_time), + .m_axis_teov (), + .m_axis_teob (axis_tx_teob), + .s_axis_tdata (axis_rx_tdata), + .s_axis_tkeep ({NUM_PORTS*NIPC{1'b1}}), // Radio only receives full words + .s_axis_tlast (axis_rx_tlast), + .s_axis_tvalid (axis_rx_tvalid), + .s_axis_tready (axis_rx_tready), + .s_axis_ttimestamp (axis_rx_ttimestamp), + .s_axis_thas_time (axis_rx_thas_time), + .s_axis_teov ({NUM_PORTS{1'b0}}), + .s_axis_teob (axis_rx_teob) + ); + + // Cross the CHDR reset to the radio_clk domain + pulse_synchronizer #( + .MODE ("POSEDGE") + ) ctrl_rst_sync_i ( + .clk_a (rfnoc_chdr_clk), + .rst_a (1'b0), + .pulse_a (rfnoc_chdr_rst), + .busy_a (), + .clk_b (radio_clk), + .pulse_b (radio_rst) + ); + + + //--------------------------------------------------------------------------- + // Decode Control Port Addresses + //--------------------------------------------------------------------------- + // + // This block splits the NoC shell's single master control port interface + // into three masters, connected to the shared registers, radio cores, and + // the external CTRL port peripheral interface. The responses from each of + // these are merged into a single response and sent back to the NoC shell. + // + //--------------------------------------------------------------------------- + + wire ctrlport_shared_req_wr; + wire ctrlport_shared_req_rd; + wire [19:0] ctrlport_shared_req_addr; + wire [31:0] ctrlport_shared_req_data; + wire [ 3:0] ctrlport_shared_req_byte_en; + wire ctrlport_shared_req_has_time; + wire [63:0] ctrlport_shared_req_time; + reg ctrlport_shared_resp_ack = 1'b0; + reg [31:0] ctrlport_shared_resp_data = 0; + + wire ctrlport_core_req_wr; + wire ctrlport_core_req_rd; + wire [19:0] ctrlport_core_req_addr; + wire [31:0] ctrlport_core_req_data; + wire [ 3:0] ctrlport_core_req_byte_en; + wire ctrlport_core_req_has_time; + wire [63:0] ctrlport_core_req_time; + wire ctrlport_core_resp_ack; + wire [31:0] ctrlport_core_resp_data; + + ctrlport_decoder_param #( + .NUM_SLAVES (3), + .PORT_BASE ({PERIPH_BASE_ADDR, RADIO_BASE_ADDR, SHARED_BASE_ADDR}), + .PORT_ADDR_W({PERIPH_ADDR_W, RADIO_ADDR_W + $clog2(NUM_PORTS), SHARED_ADDR_W}) + ) ctrlport_decoder_param_i ( + .ctrlport_clk (radio_clk), + .ctrlport_rst (radio_rst), + .s_ctrlport_req_wr (ctrlport_reg_req_wr), + .s_ctrlport_req_rd (ctrlport_reg_req_rd), + .s_ctrlport_req_addr (ctrlport_reg_req_addr), + .s_ctrlport_req_data (ctrlport_reg_req_data), + .s_ctrlport_req_byte_en (4'b0), + .s_ctrlport_req_has_time (ctrlport_reg_has_time), + .s_ctrlport_req_time (ctrlport_reg_time), + .s_ctrlport_resp_ack (ctrlport_reg_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (ctrlport_reg_resp_data), + .m_ctrlport_req_wr ({m_ctrlport_req_wr, + ctrlport_core_req_wr, + ctrlport_shared_req_wr}), + .m_ctrlport_req_rd ({m_ctrlport_req_rd, + ctrlport_core_req_rd, + ctrlport_shared_req_rd}), + .m_ctrlport_req_addr ({m_ctrlport_req_addr, + ctrlport_core_req_addr, + ctrlport_shared_req_addr}), + .m_ctrlport_req_data ({m_ctrlport_req_data, + ctrlport_core_req_data, + ctrlport_shared_req_data}), + .m_ctrlport_req_byte_en ({m_ctrlport_req_byte_en, + ctrlport_core_req_byte_en, + ctrlport_shared_req_byte_en}), + .m_ctrlport_req_has_time ({m_ctrlport_req_has_time, + ctrlport_core_req_has_time, + ctrlport_shared_req_has_time}), + .m_ctrlport_req_time ({m_ctrlport_req_time, + ctrlport_core_req_time, + ctrlport_shared_req_time}), + .m_ctrlport_resp_ack ({m_ctrlport_resp_ack, + ctrlport_core_resp_ack, + ctrlport_shared_resp_ack}), + .m_ctrlport_resp_status ({m_ctrlport_resp_status, + 2'b00, + 2'b00}), + .m_ctrlport_resp_data ({m_ctrlport_resp_data, + ctrlport_core_resp_data, + ctrlport_shared_resp_data + }) + ); + + + //--------------------------------------------------------------------------- + // Split Radio Control Port Interfaces + //--------------------------------------------------------------------------- + + wire [ NUM_PORTS-1:0] ctrlport_radios_req_wr; + wire [ NUM_PORTS-1:0] ctrlport_radios_req_rd; + wire [20*NUM_PORTS-1:0] ctrlport_radios_req_addr; + wire [32*NUM_PORTS-1:0] ctrlport_radios_req_data; + wire [ NUM_PORTS-1:0] ctrlport_radios_resp_ack; + wire [32*NUM_PORTS-1:0] ctrlport_radios_resp_data; + + ctrlport_decoder #( + .NUM_SLAVES (NUM_PORTS), + .BASE_ADDR (0), + .SLAVE_ADDR_W (RADIO_ADDR_W) + ) ctrlport_decoder_i ( + .ctrlport_clk (radio_clk), + .ctrlport_rst (radio_rst), + .s_ctrlport_req_wr (ctrlport_core_req_wr), + .s_ctrlport_req_rd (ctrlport_core_req_rd), + .s_ctrlport_req_addr (ctrlport_core_req_addr), + .s_ctrlport_req_data (ctrlport_core_req_data), + .s_ctrlport_req_byte_en (4'b0), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (ctrlport_core_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (ctrlport_core_resp_data), + .m_ctrlport_req_wr (ctrlport_radios_req_wr), + .m_ctrlport_req_rd (ctrlport_radios_req_rd), + .m_ctrlport_req_addr (ctrlport_radios_req_addr), + .m_ctrlport_req_data (ctrlport_radios_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (ctrlport_radios_resp_ack), + .m_ctrlport_resp_status ({NUM_PORTS{2'b00}}), + .m_ctrlport_resp_data (ctrlport_radios_resp_data) + ); + + + //--------------------------------------------------------------------------- + // Merge Control Port Interfaces + //--------------------------------------------------------------------------- + // + // This block merges the master control port interfaces of all radio_cores + // into a single master for the NoC shell. + // + //--------------------------------------------------------------------------- + + wire [ NUM_PORTS-1:0] ctrlport_err_radio_req_wr; + wire [20*NUM_PORTS-1:0] ctrlport_err_radio_req_addr; + wire [10*NUM_PORTS-1:0] ctrlport_err_radio_req_portid; + wire [16*NUM_PORTS-1:0] ctrlport_err_radio_req_rem_epid; + wire [10*NUM_PORTS-1:0] ctrlport_err_radio_req_rem_portid; + wire [32*NUM_PORTS-1:0] ctrlport_err_radio_req_data; + wire [ NUM_PORTS-1:0] ctrlport_err_radio_req_has_time; + wire [64*NUM_PORTS-1:0] ctrlport_err_radio_req_time; + wire [ NUM_PORTS-1:0] ctrlport_err_radio_resp_ack; + + ctrlport_combiner #( + .NUM_MASTERS (NUM_PORTS), + .PRIORITY (0) + ) ctrlport_combiner_i ( + .ctrlport_clk (radio_clk), + .ctrlport_rst (radio_rst), + .s_ctrlport_req_wr (ctrlport_err_radio_req_wr), + .s_ctrlport_req_rd ({NUM_PORTS{1'b0}}), + .s_ctrlport_req_addr (ctrlport_err_radio_req_addr), + .s_ctrlport_req_portid (ctrlport_err_radio_req_portid), + .s_ctrlport_req_rem_epid (ctrlport_err_radio_req_rem_epid), + .s_ctrlport_req_rem_portid (ctrlport_err_radio_req_rem_portid), + .s_ctrlport_req_data (ctrlport_err_radio_req_data), + .s_ctrlport_req_byte_en ({4*NUM_PORTS{1'b1}}), + .s_ctrlport_req_has_time (ctrlport_err_radio_req_has_time), + .s_ctrlport_req_time (ctrlport_err_radio_req_time), + .s_ctrlport_resp_ack (ctrlport_err_radio_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .m_ctrlport_req_wr (ctrlport_err_req_wr), + .m_ctrlport_req_rd (), + .m_ctrlport_req_addr (ctrlport_err_req_addr), + .m_ctrlport_req_portid (ctrlport_err_req_portid), + .m_ctrlport_req_rem_epid (ctrlport_err_req_rem_epid), + .m_ctrlport_req_rem_portid (ctrlport_err_req_rem_portid), + .m_ctrlport_req_data (ctrlport_err_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (ctrlport_err_req_has_time), + .m_ctrlport_req_time (ctrlport_err_req_time), + .m_ctrlport_resp_ack (ctrlport_err_resp_ack), + .m_ctrlport_resp_status (2'b0), + .m_ctrlport_resp_data (32'b0) + ); + + + //--------------------------------------------------------------------------- + // Shared Registers + //--------------------------------------------------------------------------- + // + // These registers are shared by all radio channels. + // + //--------------------------------------------------------------------------- + + localparam [15:0] compat_major = 16'd0; + localparam [15:0] compat_minor = 16'd0; + + always @(posedge radio_clk) begin + if (radio_rst) begin + ctrlport_shared_resp_ack <= 0; + ctrlport_shared_resp_data <= 0; + end else begin + // Default assignments + ctrlport_shared_resp_ack <= 0; + ctrlport_shared_resp_data <= 0; + + // Handle register reads + if (ctrlport_shared_req_rd) begin + case (ctrlport_shared_req_addr) + REG_COMPAT_NUM: begin + ctrlport_shared_resp_ack <= 1; + ctrlport_shared_resp_data <= { compat_major, compat_minor }; + end + endcase + end + end + end + + + //--------------------------------------------------------------------------- + // Radio Cores + //--------------------------------------------------------------------------- + // + // This generate block instantiates one radio core for each channel that is + // requested by NUM_PORTS. + // + //--------------------------------------------------------------------------- + + genvar i; + generate + for (i = 0; i < NUM_PORTS; i = i+1) begin : radio_core_gen + + // The radio core contains all the logic related to a single radio channel. + radio_core #( + .SAMP_W (ITEM_W), + .NSPC (NIPC) + ) radio_core_i ( + .radio_clk (radio_clk), + .radio_rst (radio_rst), + + // Slave Control Port (Register Access) + .s_ctrlport_req_wr (ctrlport_radios_req_wr[i]), + .s_ctrlport_req_rd (ctrlport_radios_req_rd[i]), + .s_ctrlport_req_addr (ctrlport_radios_req_addr[i*20 +: 20]), + .s_ctrlport_req_data (ctrlport_radios_req_data[i*32 +: 32]), + .s_ctrlport_resp_ack (ctrlport_radios_resp_ack[i]), + .s_ctrlport_resp_data (ctrlport_radios_resp_data[i*32 +: 32]), + + // Master Control Port (Error Reporting) + .m_ctrlport_req_wr (ctrlport_err_radio_req_wr[i]), + .m_ctrlport_req_addr (ctrlport_err_radio_req_addr[i*20 +: 20]), + .m_ctrlport_req_portid (ctrlport_err_radio_req_portid[i*10 +: 10]), + .m_ctrlport_req_rem_epid (ctrlport_err_radio_req_rem_epid[i*16 +: 16]), + .m_ctrlport_req_rem_portid (ctrlport_err_radio_req_rem_portid[i*10 +: 10]), + .m_ctrlport_req_data (ctrlport_err_radio_req_data[i*32 +: 32]), + .m_ctrlport_req_has_time (ctrlport_err_radio_req_has_time[i]), + .m_ctrlport_req_time (ctrlport_err_radio_req_time[i*64 +: 64]), + .m_ctrlport_resp_ack (ctrlport_err_radio_resp_ack[i]), + + // Tx Data Stream + .s_axis_tdata (axis_tx_tdata[RADIO_W*i +: RADIO_W]), + .s_axis_tlast (axis_tx_tlast[i]), + .s_axis_tvalid (axis_tx_tvalid[i]), + .s_axis_tready (axis_tx_tready[i]), + // Sideband Info + .s_axis_ttimestamp (axis_tx_ttimestamp[i*64 +: 64]), + .s_axis_thas_time (axis_tx_thas_time[i]), + .s_axis_teob (axis_tx_teob[i]), + + // Rx Data Stream + .m_axis_tdata (axis_rx_tdata[RADIO_W*i +: RADIO_W]), + .m_axis_tlast (axis_rx_tlast[i]), + .m_axis_tvalid (axis_rx_tvalid[i]), + .m_axis_tready (axis_rx_tready[i]), + // Sideband Info + .m_axis_ttimestamp (axis_rx_ttimestamp[i*64 +: 64]), + .m_axis_thas_time (axis_rx_thas_time[i]), + .m_axis_teob (axis_rx_teob[i]), + + // Radio Data + .radio_time (radio_time), + .radio_rx_data (radio_rx_data[(RADIO_W)*i +: (RADIO_W)]), + .radio_rx_stb (radio_rx_stb[i]), + .radio_rx_running (radio_rx_running[i]), + .radio_tx_data (radio_tx_data[(RADIO_W)*i +: (RADIO_W)]), + .radio_tx_stb (radio_tx_stb[i]), + .radio_tx_running (radio_tx_running[i]) + ); + end + endgenerate +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv new file mode 100644 index 000000000..ea99692bb --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv @@ -0,0 +1,68 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio_all_tb +// +// Description: This is the testbench for rfnoc_block_radio that instantiates +// several variations of rfnoc_block_radio_tb to test different configurations. +// + + +module rfnoc_block_radio_all_tb; + + timeunit 1ns; + timeprecision 1ps; + + import PkgTestExec::*; + + + //--------------------------------------------------------------------------- + // Test Definitions + //--------------------------------------------------------------------------- + + typedef struct { + int CHDR_W; + int ITEM_W; + int NIPC; + int NUM_PORTS; + int STALL_PROB; + int STB_PROB; + bit TEST_REGS; + } test_config_t; + + localparam NUM_TESTS = 9; + + localparam test_config_t test[NUM_TESTS] = '{ + '{CHDR_W: 64, ITEM_W: 16, NIPC: 1, NUM_PORTS: 3, STALL_PROB: 10, STB_PROB: 100, TEST_REGS: 1 }, + '{CHDR_W: 64, ITEM_W: 16, NIPC: 1, NUM_PORTS: 2, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 1 }, + '{CHDR_W: 64, ITEM_W: 16, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, + '{CHDR_W: 64, ITEM_W: 32, NIPC: 1, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, + '{CHDR_W: 64, ITEM_W: 32, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 10, STB_PROB: 80, TEST_REGS: 0 }, + '{CHDR_W: 128, ITEM_W: 32, NIPC: 1, NUM_PORTS: 3, STALL_PROB: 10, STB_PROB: 100, TEST_REGS: 1 }, + '{CHDR_W: 128, ITEM_W: 32, NIPC: 1, NUM_PORTS: 2, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, + '{CHDR_W: 128, ITEM_W: 32, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, + '{CHDR_W: 128, ITEM_W: 32, NIPC: 4, NUM_PORTS: 1, STALL_PROB: 10, STB_PROB: 80, TEST_REGS: 0 } + }; + + + //--------------------------------------------------------------------------- + // DUT Instances + //--------------------------------------------------------------------------- + + genvar i; + for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config + rfnoc_block_radio_tb #( + .CHDR_W (test[i].CHDR_W ), + .ITEM_W (test[i].ITEM_W ), + .NIPC (test[i].NIPC ), + .NUM_PORTS (test[i].NUM_PORTS ), + .STALL_PROB (test[i].STALL_PROB), + .STB_PROB (test[i].STB_PROB ), + .TEST_REGS (test[i].TEST_REGS ) + ) rfnoc_block_radio_tb_i (); + end : gen_test_config + + +endmodule : rfnoc_block_radio_all_tb diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh new file mode 100644 index 000000000..41f9a144e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh @@ -0,0 +1,125 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio_regs (Header) +// +// Description: Header file for RFNoC radio functionality. This includes +// register offsets, bitfields and constants for the radio components. +// + + +//----------------------------------------------------------------------------- +// Shared Register Offsets (One Set Per Radio NoC Block) +//----------------------------------------------------------------------------- + +localparam SHARED_BASE_ADDR = 20'h00; // Base address for shared radio registers +localparam SHARED_ADDR_W = 4; // Address space size for shared registers + +localparam REG_COMPAT_NUM = 'h00; // Compatibility number register offset + + +//----------------------------------------------------------------------------- +// Radio Core Register Offsets (One Set Per Radio Port) +//----------------------------------------------------------------------------- +// +// These registers are replicated depending on the number of radio channels +// requested. They start at BASE_ADDR_RADIO and repeat every RADIO_ADDR_SPACE +// bytes. +// +// WARNING: All registers larger than a single 32-bit word must be read and +// written least significant word first to guarantee coherency. +// +//----------------------------------------------------------------------------- + +localparam RADIO_BASE_ADDR = 20'h1000; // Base address of first radio. Choose a + // nice big power of 2 so we can just pass + // the lower bits to the radio cores. +localparam RADIO_ADDR_W = 7; // Address space size per radio + +// General Radio Registers +localparam REG_LOOPBACK_EN = 'h00; // Loopback enable (connect Tx output to Rx input) +localparam REG_RADIO_WIDTH = 'h04; // Upper 16 bits is sample width, lower 16 bits is NSPC + +// RX Control Registers +localparam REG_RX_STATUS = 'h10; // Status of Rx radio +localparam REG_RX_CMD = 'h14; // The next radio command to execute +localparam REG_RX_CMD_NUM_WORDS_LO = 'h18; // Number of radio words for the next command (low word) +localparam REG_RX_CMD_NUM_WORDS_HI = 'h1C; // Number of radio words for the next command (high word) +localparam REG_RX_CMD_TIME_LO = 'h20; // Time for the next command (low word) +localparam REG_RX_CMD_TIME_HI = 'h24; // Time for the next command (high word) +localparam REG_RX_MAX_WORDS_PER_PKT = 'h28; // Maximum packet length to build from Rx data +localparam REG_RX_ERR_PORT = 'h2C; // Port ID for error reporting +localparam REG_RX_ERR_REM_PORT = 'h30; // Remote port ID for error reporting +localparam REG_RX_ERR_REM_EPID = 'h34; // Remote EPID (endpoint ID) for error reporting +localparam REG_RX_ERR_ADDR = 'h38; // Offset to write error code to +localparam REG_RX_DATA = 'h3C; // Read the current Rx output of the radio +localparam REG_RX_HAS_TIME = 'h70; // Controls whether or not a channel has timestamps + +// TX Control Registers +localparam REG_TX_IDLE_VALUE = 'h40; // Value to output when transmitter is idle +localparam REG_TX_ERROR_POLICY = 'h44; // Tx error policy +localparam REG_TX_ERR_PORT = 'h48; // Port ID for error reporting +localparam REG_TX_ERR_REM_PORT = 'h4C; // Remote port ID for error reporting +localparam REG_TX_ERR_REM_EPID = 'h50; // Remote EPID (endpoint ID) for error reporting +localparam REG_TX_ERR_ADDR = 'h54; // Offset to write error code to + + +//----------------------------------------------------------------------------- +// Register Bit Fields +//----------------------------------------------------------------------------- + +// REG_RX_CMD bit fields +localparam RX_CMD_POS = 0; // Location of the command bit field +localparam RX_CMD_LEN = 2; // Bit length of the command bit field +localparam RX_CMD_TIMED_POS = 31; // Location of the bit indicating if this is + // a timed command or not. + +// REG_RX_CMD_NUM_WORDS_HI/LO length field +localparam RX_CMD_NUM_WORDS_LEN = 48; // Number of bits that are used in the 64-bit + // NUM_WORDS register (must be in range [33:64]). + +// REG_RX_STATUS bit fields +localparam CMD_FIFO_SPACE_POS = 0; // Indicates if radio is busy executing a command. +localparam CMD_FIFO_SPACE_LEN = 6; // Length of the FIFO_SPACE field +localparam CMD_FIFO_SPACE_MAX = 32; // Size of command FIFO + +// REG_TX_ERROR_POLICY bit fields +localparam TX_ERR_POLICY_LEN = 2; // Length of error policy bit field + + +//----------------------------------------------------------------------------- +// Rx Radio Commands +//----------------------------------------------------------------------------- + +localparam [RX_CMD_LEN-1:0] RX_CMD_STOP = 0; // Stop acquiring at end of next packet +localparam [RX_CMD_LEN-1:0] RX_CMD_FINITE = 1; // Acquire NUM_SAMPS then stop +localparam [RX_CMD_LEN-1:0] RX_CMD_CONTINUOUS = 2; // Acquire until stopped + + +//----------------------------------------------------------------------------- +// Tx Error Policies +//----------------------------------------------------------------------------- + +localparam TX_ERR_POLICY_PACKET = 1; // Wait for end of packet after error +localparam TX_ERR_POLICY_BURST = 2; // Wait for end of burst after error + + +//----------------------------------------------------------------------------- +// Error Codes +//----------------------------------------------------------------------------- + +// Rx Error Codes +localparam ERR_RX_CODE_W = 2; // Bit width of error code values +// +localparam ERR_RX_LATE_CMD = 1; // Late command (arrived after indicated time) +localparam ERR_RX_OVERRUN = 2; // FIFO overflow + + +// Tx Error Codes +localparam ERR_TX_CODE_W = 2; // Bit width of error code values +// +localparam ERR_TX_UNDERRUN = 1; // Data underflow (data not available when needed) +localparam ERR_TX_LATE_DATA = 2; // Late data (arrived after indicated time) +localparam ERR_TX_EOB_ACK = 3; // Acknowledge end-of-burst (this is not an error) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv new file mode 100644 index 000000000..706e0f185 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv @@ -0,0 +1,1382 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_radio_tb +// +// Description: This is the testbench for rfnoc_block_radio. +// + + +module rfnoc_block_radio_tb #( + parameter int CHDR_W = 128, // CHDR bus width + parameter int ITEM_W = 32, // Sample width + parameter int NIPC = 2, // Number of samples per radio clock cycle + parameter int NUM_PORTS = 2, // Number of radio channels + parameter int STALL_PROB = 25, // Probability of AXI BFM stall + parameter int STB_PROB = 80, // Probability of radio STB asserting + parameter bit TEST_REGS = 1 // Do register tests +); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + import PkgAxisCtrlBfm::*; + import PkgChdrBfm::*; + import PkgRfnocItemUtils::*; + + // Pull in radio register offsets and constants + `include "rfnoc_block_radio_regs.vh" + + + // Simulation Parameters + localparam logic [ 9:0] THIS_PORTID = 10'h17; + localparam logic [15:0] THIS_EPID = 16'hDEAD; + localparam int MTU = 8; + localparam int RADIO_W = NIPC * ITEM_W; // Radio word size + localparam int SPP = 64; // Samples per packet + localparam int WPP = SPP*ITEM_W/RADIO_W; // Radio words per packet + localparam int CHDR_CLK_PER = 5; // rfnoc_chdr_clk period in ns + localparam int CTRL_CLK_PER = 25; // rfnoc_ctrl_clk period in ns + localparam int RADIO_CLK_PER = 10; // radio_clk_per period in ns + + // Amount of time to wait for a packet to be fully acquired + localparam realtime MAX_PKT_WAIT = 4*WPP*(RADIO_CLK_PER+CTRL_CLK_PER)*1ns; + + // Error reporting values to use + localparam bit [ 9:0] TX_ERR_DST_PORT = 10'h2B5; + localparam bit [ 9:0] TX_ERR_REM_DST_PORT = 10'h14C; + localparam bit [15:0] TX_ERR_REM_DST_EPID = 16'hA18E; + localparam bit [19:0] TX_ERR_ADDRESS = 20'hA31D3; + + + + //--------------------------------------------------------------------------- + // Clocks and Resets + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + bit radio_clk; + + // Don't start the clocks automatically (AUTOSTART=0), since we expect + // multiple instances of this testbench to run in sequence. They will be + // started before the first test. + sim_clock_gen #(.PERIOD(CHDR_CLK_PER), .AUTOSTART(0)) + rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0)) + rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + sim_clock_gen #(.PERIOD(RADIO_CLK_PER), .AUTOSTART(0)) + radio_clk_gen (.clk(radio_clk), .rst()); + + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + // Connections to DUT as interfaces: + RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk); + AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0); + AxiStreamIf #(32) s_ctrl (rfnoc_ctrl_clk, 1'b0); + AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0); + + // Bus functional model for a software block controller + RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl; + + + + //--------------------------------------------------------------------------- + // Radio Data Model + //--------------------------------------------------------------------------- + + bit [NUM_PORTS*RADIO_W-1:0] radio_rx_data; + bit [ NUM_PORTS-1:0] radio_rx_stb; + + bit [63:0] radio_time; + bit radio_pps; + + // Radio data generation + sim_radio_gen #( + .NSPC (NIPC), + .SAMP_W (ITEM_W), + .NUM_CHANNELS (NUM_PORTS), + .STB_PROB (STB_PROB), + .INCREMENT (NIPC), + .PPS_PERIOD (NIPC * 250) + ) radio_gen ( + .radio_clk (radio_clk), + .radio_rst (1'b0), + .radio_rx_data (radio_rx_data), + .radio_rx_stb (radio_rx_stb), + .radio_time (radio_time), + .radio_pps (radio_pps) + ); + + + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + logic [NUM_PORTS-1:0] radio_rx_running; + + logic [NUM_PORTS*RADIO_W-1:0] radio_tx_data; + logic [ NUM_PORTS-1:0] radio_tx_stb; + logic [ NUM_PORTS-1:0] radio_tx_running; + + logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata_flat; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast_flat; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid_flat; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready_flat; + + logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata_flat; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast_flat; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid_flat; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tready_flat; + + semaphore port_sem = new(0); + + // Use the same strobe for both Rx and Tx + assign radio_tx_stb = radio_rx_stb; + + + // Flatten the data stream arrays into concatenated vectors + genvar i; + for (i = 0; i < NUM_PORTS; i++) begin : gen_radio_connections + assign s_rfnoc_chdr_tdata_flat[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; + assign s_rfnoc_chdr_tlast_flat[i] = m_chdr[i].tlast; + assign s_rfnoc_chdr_tvalid_flat[i] = m_chdr[i].tvalid; + assign m_chdr[i].tready = s_rfnoc_chdr_tready_flat[i]; + + assign s_chdr[i].tdata = m_rfnoc_chdr_tdata_flat[CHDR_W*i+:CHDR_W]; + assign s_chdr[i].tlast = m_rfnoc_chdr_tlast_flat[i]; + assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid_flat[i]; + assign m_rfnoc_chdr_tready_flat[i] = s_chdr[i].tready; + + // Connect each interface to the BFM. This is done in a generate block + // since the interface indices must be constant in SystemVerilog :( + initial begin + // Get the port number (plus 1) from the semaphore. This will block until + // the semaphore is incremented to this port number (plus 1). + port_sem.get(i+1); + // Connect the master and slave interfaces to the BFM + void'(blk_ctrl.add_master_data_port(m_chdr[i])); + void'(blk_ctrl.add_slave_data_port(s_chdr[i])); + // Put the port number to communicate that we're done + port_sem.put(i+1); + end + end + + + rfnoc_block_radio #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .NIPC (NIPC), + .ITEM_W (ITEM_W), + .NUM_PORTS (NUM_PORTS), + .MTU (MTU) + ) rfnoc_block_radio_i ( + .rfnoc_chdr_clk (backend.chdr_clk), + .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata_flat), + .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast_flat), + .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid_flat), + .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready_flat), + .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata_flat), + .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast_flat), + .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid_flat), + .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready_flat), + .rfnoc_core_config (backend.cfg), + .rfnoc_core_status (backend.sts), + .rfnoc_ctrl_clk (backend.ctrl_clk), + .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), + .m_ctrlport_req_wr (), + .m_ctrlport_req_rd (), + .m_ctrlport_req_addr (), + .m_ctrlport_req_data (), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (1'b0), + .m_ctrlport_resp_status (2'b0), + .m_ctrlport_resp_data (32'b0), + .radio_clk (radio_clk), + .radio_time (radio_time), + .radio_rx_data (radio_rx_data), + .radio_rx_stb (radio_rx_stb), + .radio_rx_running (radio_rx_running), + .radio_tx_data (radio_tx_data), + .radio_tx_stb (radio_tx_stb), + .radio_tx_running (radio_tx_running) + ); + + + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + // Read a 32-bit register at offset "addr" from shared radio registers + task automatic read_shared(logic [19:0] addr, output logic [31:0] data); + addr = addr + SHARED_BASE_ADDR; + blk_ctrl.reg_read(addr, data); + endtask : read_shared + + // Write a 32-bit register at offset "addr" in shared radio registers + task automatic write_shared(logic [19:0] addr, logic [31:0] data); + addr = addr + SHARED_BASE_ADDR; + blk_ctrl.reg_write(addr, data); + endtask : write_shared + + // Read a 32-bit register at offset "addr" from radio "radio_num" + task automatic read_radio(int radio_num, logic [19:0] addr, output logic [31:0] data); + addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); + blk_ctrl.reg_read(addr, data); + endtask : read_radio + + // Read a 64-bit register at offset "addr" from radio "radio_num" + task automatic read_radio_64(int radio_num, logic [19:0] addr, output logic [63:0] data); + addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); + blk_ctrl.reg_read(addr, data[31:0]); + blk_ctrl.reg_read(addr+4, data[63:32]); + endtask : read_radio_64 + + // Write a 32-bit register at offset "addr" in radio "radio_num" + task automatic write_radio(int radio_num, logic [19:0] addr, logic [31:0] data); + addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); + blk_ctrl.reg_write(addr, data); + endtask : write_radio + + // Write a 64-bit register at offset "addr" in radio "radio_num" + task automatic write_radio_64(int radio_num, logic [19:0] addr, logic [63:0] data); + addr = addr + RADIO_BASE_ADDR + (radio_num * 2**RADIO_ADDR_W); + blk_ctrl.reg_write(addr, data[31:0]); + blk_ctrl.reg_write(addr+4, data[63:32]); + endtask : write_radio_64 + + + // Start an Rx acquisition + task automatic start_rx ( + int radio_num, // Radio channel to use + bit [63:0] num_words = 0 // Number of radio words + ); + logic [31:0] cmd; + + if (num_words == 0) begin + // Do a continuous acquisition + $display("Radio %0d: Start RX, continuous receive", radio_num); + cmd = RX_CMD_CONTINUOUS; + end else begin + // Do a finite acquisition (num samps and done) + $display("Radio %0d: Start RX, receive %0d words", radio_num, num_words); + write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words); + cmd = RX_CMD_FINITE; + end + + // Write command to radio + write_radio(radio_num, REG_RX_CMD, cmd); + endtask : start_rx + + + // Start an Rx acquisition at a specific time + task automatic start_rx_timed ( + int radio_num, // Radio channel to use + bit [63:0] num_words = 0, // Number of radio words + bit [63:0] start_time + ); + logic [31:0] cmd; + + if (num_words == 0) begin + // Do a continuous acquisition + $display("Radio %0d: Start RX, continuous receive (timed)", radio_num); + cmd = RX_CMD_CONTINUOUS; + end else begin + // Do a finite acquisition (num samps and done) + $display("Radio %0d: Start RX, receive %0d words (timed)", radio_num, num_words); + write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words); + cmd = RX_CMD_FINITE; + end + + // Mark that this is a timed command + cmd[RX_CMD_TIMED_POS] = 1'b1; + + // Set start time for command + write_radio_64(radio_num, REG_RX_CMD_TIME_LO, start_time); + + // Write command to radio + write_radio(radio_num, REG_RX_CMD, cmd); + endtask : start_rx_timed + + + // Send the Rx stop command to the indicated radio channel + task automatic stop_rx(int radio_num); + $display("Radio %0d: Stop RX", radio_num); + write_radio(radio_num, REG_RX_CMD, RX_CMD_STOP); + endtask : stop_rx + + + // Receive num_words from the indicated radio channel and verify that it's + // sequential and contiguous data aligned on packet boundaries. + task automatic check_rx( + int radio_num, // Radio to receive from and check + int num_words // Number of radio words to expect + ); + int sample_count; // Counter to track number of samples generated + bit [ITEM_W-1:0] sample_val; // Value of the next sample + chdr_word_t data[$]; // Array of data for the received packet + int num_samples; // Number of samples to send + int byte_length; // Number of data bytes in next packet + int expected_length; // Expected byte length of the next packet + int valid_words; // Number of valid chdr_word_t in next packet + + num_samples = num_words * NIPC; + + sample_count = 0; + while (sample_count < num_samples) begin + // Fetch the next packet + blk_ctrl.recv(radio_num, data, byte_length); + + // Take the first sample as a starting count for the remaining samples + if (sample_count == 0) begin + sample_val = data[0][ITEM_W-1:0]; + end + + // Calculate expected length in bytes + if (num_samples - sample_count >= SPP) begin + // Expecting a full packet + expected_length = SPP*ITEM_W/8; + end else begin + // Expecting partial packet + expected_length = (num_samples - sample_count) * ITEM_W/8; + end + + // Check that the length matches our expectation + `ASSERT_ERROR( + byte_length == expected_length, + "Received packet didn't have expected length." + ); + + // Loop over the packet, one chdr_word_t at a time + valid_words = int'($ceil(real'(byte_length) / ($bits(chdr_word_t)/8))); + for (int i = 0; i < valid_words; i++) begin + // Check each sample of the next chdr_word_t value + for (int sub_sample = 0; sub_sample < $bits(chdr_word_t)/ITEM_W; sub_sample++) begin + chdr_word_t word; + word = data[i][ITEM_W*sub_sample +: ITEM_W]; // Work around Vivado 2018.3 issue + `ASSERT_ERROR( + word == sample_val, + $sformatf( + "Sample %0d (0x%X) didn't match expected value (0x%X)", + sample_count, data[i][ITEM_W*sub_sample +: ITEM_W], sample_val + ) + ); + sample_val++; + sample_count++; + + // Check if the word is only partially full + if (sample_count >= num_samples) break; + end + end + end + endtask : check_rx + + + // Send num_words to the indicated radio for transmission at the given time. + task automatic start_tx_timed ( + int radio_num, // Radio channel to transmit on + bit [63:0] num_words, // Number of radio words to transmit + logic [63:0] start_time = 'X, // Time at which to begin transmit + bit [ITEM_W-1:0] start_val = 1, // Initial sample value + bit eob = 1 // Set EOB flag at the end + ); + int sample_count; // Counter to track number of samples generated + bit [ITEM_W-1:0] sample_val; // Value of the next sample + chdr_word_t data[$]; // Array of data for the packet + int num_samples; // Number of samples to send + int byte_length; // Number of bytes for next packet + chdr_word_t chdr_word; // Next word to send to BFM + packet_info_t pkt_info = 0; // Flags/timestamp for next packet + + $display("Radio %0d: Start TX, send %0d words", radio_num, num_words); + + num_samples = num_words * NIPC; + + if (!$isunknown(start_time)) pkt_info.has_time = 1; + + sample_val = start_val; + sample_count = 0; + while (sample_count < num_samples) begin + // Calculate timestamp for this packet + if (pkt_info.has_time) begin + pkt_info.timestamp = start_time + sample_count; + end + + // Clear the payload + data = {}; + + // Loop until we've built up a packet + forever begin + // Generate the next word + for (int sub_sample = 0; sub_sample < $bits(chdr_word_t)/ITEM_W; sub_sample++) begin + chdr_word[ITEM_W*sub_sample +: ITEM_W] = sample_val; + sample_val++; + sample_count++; + end + + // Add next word to the queue + data.push_back(chdr_word); + + // Send the packet if we're at a packet boundary + if (sample_count % SPP == 0) begin + pkt_info.eob = (sample_count == num_samples && eob) ? 1 : 0; + byte_length = SPP * ITEM_W/8; + blk_ctrl.send(radio_num, data, byte_length, {}, pkt_info); + break; + end else if (sample_count >= num_samples) begin + pkt_info.eob = eob; + byte_length = (sample_count % SPP) * ITEM_W/8; + blk_ctrl.send(radio_num, data, byte_length, {}, pkt_info); + break; + end + end + end + endtask : start_tx_timed + + + // Send num_words to the indicated radio for transmission. + task automatic start_tx ( + int radio_num, // Radio channel to transmit on + bit [63:0] num_words, // Number of radio words to transmit + bit [ITEM_W-1:0] start_val = 1, // Initial sample value + bit eob = 1 // Set EOB flag at the end + ); + // Passing 'X tells the underlying BFM to not insert a timestamp + start_tx_timed(radio_num, num_words, 'X, start_val, eob); + endtask : start_tx + + + // Verify the output of a packet, expecting it at a specific time + task automatic check_tx_timed ( + int radio_num, // Radio channel to transmit on + bit [63:0] num_words, // Number of radio words to expect + logic [63:0] start_time = 'X, // Expected start time + bit [ITEM_W-1:0] start_val = 1 // Initial sample value + ); + int sample_val; // Expected value of next sample + + sample_val = start_val; + + // Wait for the packet to start + wait(radio_tx_data[radio_num*RADIO_W +: ITEM_W] == start_val); + + // Check the time + if (!$isunknown(start_time)) begin + `ASSERT_ERROR( + radio_time - start_time <= NIPC*2, + $sformatf("Packet transmitted at radio time 0x%0X but expected 0x%0X", radio_time, start_time) + ); + end + + // Verify output one word at a time + for (int word_count = 0; word_count < num_words; word_count++) begin + // Wait for the next radio word to be output + do begin + @(posedge radio_clk); + end while (radio_tx_stb[radio_num] == 0); + + // Check each sample of the radio word + for (int sub_sample = 0; sub_sample < NIPC; sub_sample++) begin + `ASSERT_ERROR( + radio_tx_data[radio_num*RADIO_W + ITEM_W*sub_sample +: ITEM_W] == sample_val, + "Radio output doesn't match expected value" + ); + sample_val++; + end + end + endtask : check_tx_timed + + + // Verify the output of a packet + task automatic check_tx ( + int radio_num, // Radio to transmit on + bit [63:0] num_words, // Number of radio words to expect + bit [ITEM_W-1:0] start_val = 1 // Initial sample value + ); + check_tx_timed(radio_num, num_words, 'X, start_val); + endtask : check_tx + + + // When we expect and error, this task will check that control packets were + // received and that they have the expected values. + task check_error (int error); + AxisCtrlPacket ctrl_packet; + chdr_word_t word; + + // Get error code + blk_ctrl.get_ctrl_bfm().get_ctrl(ctrl_packet); + word = ctrl_packet.data[0]; // Work around Vivado 2018.3 issue + `ASSERT_ERROR( + word == error && + ctrl_packet.op_word.op_code == CTRL_OP_WRITE && + ctrl_packet.op_word.address == TX_ERR_ADDRESS && + ctrl_packet.header.is_ack == 1'b0 && + ctrl_packet.header.has_time == 1'b1 && + ctrl_packet.header.num_data == 1 && + ctrl_packet.header.dst_port == TX_ERR_DST_PORT && + ctrl_packet.header.rem_dst_port == TX_ERR_REM_DST_PORT && + ctrl_packet.header.rem_dst_epid == TX_ERR_REM_DST_EPID, + "Unexpected error code response"); + + // Send acknowledgment + ctrl_packet.header = 0; + ctrl_packet.header.is_ack = 1; + blk_ctrl.get_ctrl_bfm().put_ctrl(ctrl_packet); + endtask : check_error + + + + //--------------------------------------------------------------------------- + // Test Procedures + //--------------------------------------------------------------------------- + + task automatic test_block_info(); + test.start_test("Verify Block Info", 2us); + + // Get static block info and validate it + `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_radio_i.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_ctrl_fifosize() == rfnoc_block_radio_i.noc_shell_radio_i.CTRL_FIFO_SIZE, + "Incorrect ctrl_fifosize Value"); + `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect mtu Value"); + + test.end_test(); + endtask : test_block_info + + + + task automatic test_shared_registers(); + logic [31:0] val; + test.start_test("Shared Registers", 10us); + + // Compatibility number + read_shared(REG_COMPAT_NUM, val); + `ASSERT_ERROR( + val == { + rfnoc_block_radio_i.compat_major, + rfnoc_block_radio_i.compat_minor + }, + "REG_COMPAT_NUM didn't read correctly" + ); + test.end_test(); + endtask : test_shared_registers + + + + task automatic test_general_registers(int radio_num); + logic [31:0] val; + test.start_test("General Registers", 10us); + + // Test loopback enable register (read/write) + read_radio(radio_num, REG_LOOPBACK_EN, val); + `ASSERT_ERROR(val == 0, "Initial value of REG_LOOPBACK_EN is incorrect"); + write_radio(radio_num, REG_LOOPBACK_EN, 32'hFFFFFFFF); + read_radio(radio_num, REG_LOOPBACK_EN, val); + `ASSERT_ERROR(val == 1, "REG_LOOPBACK_EN didn't update correctly"); + write_radio(radio_num, REG_LOOPBACK_EN, 0); + + // Read ITEM_W and NIPC (read only) + read_radio(radio_num, REG_RADIO_WIDTH, val); + `ASSERT_ERROR(val[15:0] == NIPC, "Value of NIPC register is incorrect"); + `ASSERT_ERROR(val[31:16] == ITEM_W, "Value of ITEM_W register is incorrect"); + + test.end_test(); + endtask : test_general_registers + + + + task test_rx_registers(int radio_num); + logic [63:0] val, temp, expected; + localparam int num_words_len = RX_CMD_NUM_WORDS_LEN; + + test.start_test("Rx Registers", 50us); + + // REG_RX_CMD_STATUS (read only) + expected = CMD_FIFO_SPACE_MAX; + read_radio(radio_num, REG_RX_STATUS, val); + `ASSERT_ERROR(val == expected, "REG_RX_STATUS not initially CMD_FIFO_SPACE_MAX"); + + // REG_RX_CMD (read/write). Test a bogus timed stop command just to check + // read/write of the register. + expected = 0; + expected[RX_CMD_POS +: RX_CMD_LEN] = RX_CMD_STOP; + expected[RX_CMD_TIMED_POS] = 1'b1; + write_radio(radio_num, REG_RX_CMD, expected); + read_radio(radio_num, REG_RX_CMD, val); + `ASSERT_ERROR(val == expected, "REG_RX_CMD didn't update correctly"); + + // REG_RX_CMD_NUM_WORDS (read/write) + read_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, val); + `ASSERT_ERROR(val == 0, "REG_RX_CMD_NUM_WORDS not initially 0"); + expected = 64'hFEDCBA9876543210; + write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, expected); + read_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, val); + `ASSERT_ERROR( + val == expected[num_words_len-1:0], + "REG_RX_CMD_NUM_WORDS didn't update correctly" + ); + + // REG_RX_CMD_TIME (read/write) + read_radio_64(radio_num, REG_RX_CMD_TIME_LO, val); + `ASSERT_ERROR(val == 0, "REG_RX_CMD_TIME not initially 0"); + expected = 64'hBEADFEED0123F1FE; + write_radio_64(radio_num, REG_RX_CMD_TIME_LO, expected); + read_radio_64(radio_num, REG_RX_CMD_TIME_LO, val); + `ASSERT_ERROR(val == expected, "REG_RX_CMD_TIME didn't update correctly"); + + // REG_RX_MAX_WORDS_PER_PKT (read/write) + read_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, val); + `ASSERT_ERROR(val == 64, "REG_RX_MAX_WORDS_PER_PKT not initially 64"); + expected = 32'hABBEC001; + write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, expected); + read_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, val); + `ASSERT_ERROR(val == expected, "REG_RX_MAX_WORDS_PER_PKT didn't update correctly"); + + // REG_RX_ERR_PORT (read/write) + read_radio(radio_num, REG_RX_ERR_PORT, val); + `ASSERT_ERROR(val == 0, "REG_RX_ERR_PORT not initially 0"); + expected = $urandom() & 32'h000001FF; + write_radio(radio_num, REG_RX_ERR_PORT, expected); + read_radio(radio_num, REG_RX_ERR_PORT, val); + `ASSERT_ERROR(val == expected, "REG_RX_ERR_PORT didn't update correctly"); + + // REG_RX_ERR_REM_PORT (read/write) + read_radio(radio_num, REG_RX_ERR_REM_PORT, val); + `ASSERT_ERROR(val == 0, "REG_RX_ERR_REM_PORT not initially 0"); + expected = $urandom() & 32'h000001FF; + write_radio(radio_num, REG_RX_ERR_REM_PORT, expected); + read_radio(radio_num, REG_RX_ERR_REM_PORT, val); + `ASSERT_ERROR(val == expected, "REG_RX_ERR_REM_PORT didn't update correctly"); + + // REG_RX_ERR_REM_EPID (read/write) + read_radio(radio_num, REG_RX_ERR_REM_EPID, val); + `ASSERT_ERROR(val == 0, "REG_RX_ERR_REM_EPID not initially 0"); + expected = $urandom() & 32'h0000FFFF; + write_radio(radio_num, REG_RX_ERR_REM_EPID, expected); + read_radio(radio_num, REG_RX_ERR_REM_EPID, val); + `ASSERT_ERROR(val == expected, "REG_RX_ERR_REM_EPID didn't update correctly"); + + // REG_RX_ERR_ADDR (read/write) + read_radio(radio_num, REG_RX_ERR_ADDR, val); + `ASSERT_ERROR(val == 0, "REG_RX_ERR_ADDR not initially 0"); + expected = $urandom() & 32'h000FFFFF; + write_radio(radio_num, REG_RX_ERR_ADDR, expected); + read_radio(radio_num, REG_RX_ERR_ADDR, val); + `ASSERT_ERROR(val == expected, "REG_RX_ERR_ADDR didn't update correctly"); + + // REG_RX_DATA (read-only) + temp = radio_tx_data[RADIO_W*radio_num +: RADIO_W]; + read_radio(radio_num, REG_RX_DATA, val); + `ASSERT_ERROR( + radio_rx_data[RADIO_W*radio_num +: RADIO_W] >= val && val >= temp, + "REG_RX_DATA wasn't in the expected range"); + read_radio(radio_num, REG_RX_DATA, temp); + `ASSERT_ERROR(temp != val, "REG_RX_DATA didn't update"); + + test.end_test(); + endtask : test_rx_registers + + + + task automatic test_tx_registers(int radio_num); + logic [31:0] val, expected; + + test.start_test("Tx Registers", 50us); + + // REG_TX_IDLE_VALUE (read/write) + read_radio(radio_num, REG_TX_IDLE_VALUE, val); + `ASSERT_ERROR(val == 0, "REG_TX_IDLE_VALUE not initially 0"); + expected = $urandom() & {ITEM_W{1'b1}}; + write_radio(radio_num, REG_TX_IDLE_VALUE, expected); + read_radio(radio_num, REG_TX_IDLE_VALUE, val); + `ASSERT_ERROR(val == expected, "REG_TX_IDLE_VALUE didn't update correctly"); + + // REG_TX_ERROR_POLICY (read/write) + read_radio(radio_num, REG_TX_ERROR_POLICY, val); + expected = TX_ERR_POLICY_PACKET; + `ASSERT_ERROR(val == expected, "REG_TX_ERROR_POLICY not initially 'PACKET'"); + expected = TX_ERR_POLICY_BURST; + write_radio(radio_num, REG_TX_ERROR_POLICY, expected); + read_radio(radio_num, REG_TX_ERROR_POLICY, val); + `ASSERT_ERROR(val == expected, "REG_TX_ERROR_POLICY didn't update to 'BURST'"); + expected = TX_ERR_POLICY_PACKET; + write_radio(radio_num, REG_TX_ERROR_POLICY, 32'h03); // Try to set both bits! + read_radio(radio_num, REG_TX_ERROR_POLICY, val); + `ASSERT_ERROR(val == expected, "REG_TX_ERROR_POLICY didn't revert to 'PACKET'"); + + // REG_TX_ERR_PORT (read/write) + read_radio(radio_num, REG_TX_ERR_PORT, val); + `ASSERT_ERROR(val == 0, "REG_TX_ERR_PORT not initially 0"); + expected = $urandom() & 32'h000001FF; + write_radio(radio_num, REG_TX_ERR_PORT, expected); + read_radio(radio_num, REG_TX_ERR_PORT, val); + `ASSERT_ERROR(val == expected, "REG_TX_ERR_PORT didn't update correctly"); + + // REG_TX_ERR_REM_PORT (read/write) + read_radio(radio_num, REG_TX_ERR_REM_PORT, val); + `ASSERT_ERROR(val == 0, "REG_TX_ERR_REM_PORT not initially 0"); + expected = $urandom() & 32'h000001FF; + write_radio(radio_num, REG_TX_ERR_REM_PORT, expected); + read_radio(radio_num, REG_TX_ERR_REM_PORT, val); + `ASSERT_ERROR(val == expected, "REG_TX_ERR_REM_PORT didn't update correctly"); + + // REG_TX_ERR_REM_EPID (read/write) + read_radio(radio_num, REG_TX_ERR_REM_EPID, val); + `ASSERT_ERROR(val == 0, "REG_TX_ERR_REM_EPID not initially 0"); + expected = $urandom() & 32'h0000FFFF; + write_radio(radio_num, REG_TX_ERR_REM_EPID, expected); + read_radio(radio_num, REG_TX_ERR_REM_EPID, val); + `ASSERT_ERROR(val == expected, "REG_TX_ERR_REM_EPID didn't update correctly"); + + // REG_TX_ERR_ADDR (read/write) + read_radio(radio_num, REG_TX_ERR_ADDR, val); + `ASSERT_ERROR(val == 0, "REG_TX_ERR_ADDR not initially 0"); + expected = $urandom() & 32'h000FFFFF; + write_radio(radio_num, REG_TX_ERR_ADDR, expected); + read_radio(radio_num, REG_TX_ERR_ADDR, val); + `ASSERT_ERROR(val == expected, "REG_TX_ERR_ADDR didn't update correctly"); + + test.end_test(); + endtask : test_tx_registers + + + + task automatic test_rx(int radio_num); + + //--------------------- + // Finite Acquisitions + //--------------------- + + test.start_test("Rx (finite)", 50us); + + // Set packet length + write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); + + // Grab and verify a partial packet + start_rx(radio_num, WPP/2); + check_rx(radio_num, WPP/2); + + // Grab a minimally-sized packet + start_rx(radio_num, 1); + check_rx(radio_num, 1); + + // Grab and verify several packets + start_rx(radio_num, WPP*15/2); + check_rx(radio_num, WPP*15/2); + + // Wait long enough to receive another packet and then make sure we didn't + // receive anything. That is, make sure Rx actually stopped. + #MAX_PKT_WAIT; + `ASSERT_ERROR( + blk_ctrl.num_received(radio_num) == 0, + "Received more packets than expected" + ); + + test.end_test(); + + + //------------------------- + // Continuous Acquisitions + //------------------------- + + test.start_test("Rx (continuous)", 100us); + + start_rx(radio_num); + + // Grab and verify several packets + check_rx(radio_num, WPP*7); + stop_rx(radio_num); + + // Grab and discard any remaining packets + do begin + while (blk_ctrl.num_received(radio_num) != 0) begin + ChdrPacket #(CHDR_W) chdr_packet; + blk_ctrl.get_chdr(radio_num, chdr_packet); + end + #MAX_PKT_WAIT; + end while (blk_ctrl.num_received(radio_num) != 0); + + test.end_test(); + + + //-------------------------- + // Finite Timed Acquisition + //-------------------------- + + begin + ChdrPacket #(CHDR_W) chdr_packet; + chdr_word_t expected_time; + + test.start_test("Rx (finite, timed)", 100us); + + // Send Rx command with time in the future + expected_time = radio_time + 2000; + start_rx_timed(radio_num, WPP, expected_time); + + // Take a peak at the timestamp in the received packet to check it + blk_ctrl.peek_chdr(radio_num, chdr_packet); + `ASSERT_ERROR( + chdr_packet.timestamp == expected_time, + "Received packet didn't have expected timestamp" + ); + + // Verify the packet data + check_rx(radio_num, WPP); + test.end_test(); + end + + + //------------------------------ + // Continuous Timed Acquisition + //------------------------------ + + begin + ChdrPacket #(CHDR_W) chdr_packet; + chdr_word_t expected_time; + + test.start_test("Rx (continuous, timed)", 100us); + + // Send Rx command with time in the future + expected_time = radio_time + 2000; + start_rx_timed(radio_num, 0, expected_time); + + // Take a peak at the timestamp in the received packet to check it + blk_ctrl.peek_chdr(radio_num, chdr_packet); + `ASSERT_ERROR( + chdr_packet.timestamp == expected_time, + "Received packet didn't have expected timestamp" + ); + + // Verify a few packets + check_rx(radio_num, WPP*3); + stop_rx(radio_num); + + // Grab and discard any remaining packets + do begin + while (blk_ctrl.num_received(radio_num) != 0) begin + ChdrPacket #(CHDR_W) chdr_packet; + blk_ctrl.get_chdr(radio_num, chdr_packet); + end + #(MAX_PKT_WAIT); + end while (blk_ctrl.num_received(radio_num) != 0); + + test.end_test(); + end + + + //------------- + // Rx Overflow + //------------- + begin + logic [31:0] val; + + test.start_test("Rx (now, overflow)", 200us); + + // Configure the error reporting registers + write_radio(radio_num, REG_RX_ERR_PORT, TX_ERR_DST_PORT); + write_radio(radio_num, REG_RX_ERR_REM_PORT, TX_ERR_REM_DST_PORT); + write_radio(radio_num, REG_RX_ERR_REM_EPID, TX_ERR_REM_DST_EPID); + write_radio(radio_num, REG_RX_ERR_ADDR, TX_ERR_ADDRESS); + + // Stall the BFM to force a backup of data + blk_ctrl.set_slave_stall_prob(radio_num, 100); + + // Acquire continuously until we get an error + start_rx(radio_num); + + // Check that we're acquiring + read_radio(radio_num, REG_RX_STATUS, val); + `ASSERT_ERROR( + val[CMD_FIFO_SPACE_POS +: CMD_FIFO_SPACE_LEN] != CMD_FIFO_SPACE_MAX, + "Rx radio reports that it is not busy" + ); + + // Verify that we receive an error + check_error(ERR_RX_OVERRUN); + + // Restore the BFM stall probability + blk_ctrl.set_slave_stall_prob(radio_num, STALL_PROB); + + // Verify that Rx stopped + read_radio(radio_num, REG_RX_STATUS, val); + `ASSERT_ERROR( + val[CMD_FIFO_SPACE_POS +: CMD_FIFO_SPACE_LEN] == CMD_FIFO_SPACE_MAX, + "Rx radio reports that it is still busy after overflow" + ); + + // Discard any packets we received. Rx should eventually stop + // automatically after an overflow. + do begin + while (blk_ctrl.num_received(radio_num) != 0) begin + ChdrPacket #(CHDR_W) chdr_packet; + blk_ctrl.get_chdr(radio_num, chdr_packet); + end + #(MAX_PKT_WAIT); + end while (blk_ctrl.num_received(radio_num) != 0); + + test.end_test(); + end + + + //-------------- + // Late Command + //-------------- + + test.start_test("Rx (timed, late)", 100us); + + start_rx_timed(radio_num, WPP, radio_time); + check_error(ERR_RX_LATE_CMD); + + // Late command should be ignored. Make sure we didn't receive any packets. + begin + ChdrPacket #(CHDR_W) chdr_packet; + #(MAX_PKT_WAIT); + `ASSERT_ERROR( + blk_ctrl.num_received(radio_num) == 0, + "Packets received for late Rx command" + ); + + // Discard any remaining packets + while (blk_ctrl.num_received(radio_num)) blk_ctrl.get_chdr(radio_num, chdr_packet); + end + + test.end_test(); + + + //--------------- + // Command Queue + //--------------- + + test.start_test("Rx (queue multiple commands)"); + + begin + logic [31:0] expected, val; + + // Send one continuous command and verify the queue fullness + start_rx(radio_num); + expected = CMD_FIFO_SPACE_MAX-1; + read_radio(radio_num, REG_RX_STATUS, val); + `ASSERT_ERROR( + val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, + "CMD_FIFO_SPACE did not decrement" + ); + + // Fill the command FIFO, going one over + for (int i = 0; i < CMD_FIFO_SPACE_MAX; i++) begin + start_rx(radio_num, WPP); + end + expected = 0; + read_radio(radio_num, REG_RX_STATUS, val); + `ASSERT_ERROR( + val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, + "CMD_FIFO_SPACE did not reach 0" + ); + + // Issue stop command and verify that the FIFO empties + stop_rx(radio_num); + expected = CMD_FIFO_SPACE_MAX; + read_radio(radio_num, REG_RX_STATUS, val); + `ASSERT_ERROR( + val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, + "CMD_FIFO_SPACE did not return to max" + ); + + // Grab and discard any remaining packets + do begin + while (blk_ctrl.num_received(radio_num) != 0) begin + ChdrPacket #(CHDR_W) chdr_packet; + blk_ctrl.get_chdr(radio_num, chdr_packet); + end + #MAX_PKT_WAIT; + end while (blk_ctrl.num_received(radio_num) != 0); + + // Queue several long commands back-to-back and make sure they all + // complete. The lengths are unique to ensure we execute the right + // commands in the expected order. + for (int i = 0; i < 3; i++) start_rx(radio_num, WPP*20+i); + for (int i = 0; i < 3; i++) check_rx(radio_num, WPP*20+i); + + // Make sure we don't get any more data + do begin + while (blk_ctrl.num_received(radio_num) != 0) begin + `ASSERT_ERROR(0, "Received unexpected packets"); + end + #MAX_PKT_WAIT; + end while (blk_ctrl.num_received(radio_num) != 0); + end + + test.end_test(); + + endtask : test_rx + + + + task automatic test_tx(int radio_num); + logic [RADIO_W-1:0] radio_data; + enum { WAIT_FOR_EOP, WAIT_FOR_EOB } policy; + + //------- + // Setup + //------- + + test.start_test("Tx Init", 50us); + + // Configure the error reporting registers + write_radio(radio_num, REG_TX_ERR_PORT, TX_ERR_DST_PORT); + write_radio(radio_num, REG_TX_ERR_REM_PORT, TX_ERR_REM_DST_PORT); + write_radio(radio_num, REG_TX_ERR_REM_EPID, TX_ERR_REM_DST_EPID); + write_radio(radio_num, REG_TX_ERR_ADDR, TX_ERR_ADDRESS); + + test.end_test(); + + + //--------------- + // Test Tx (now) + //--------------- + + test.start_test("Tx (now)", 50us); + + // Grab and verify a partial packet + start_tx(radio_num, WPP*3/4); + check_tx(radio_num, WPP*3/4); + check_error(ERR_TX_EOB_ACK); + + // Grab and verify multiple packets + start_tx(radio_num, WPP*3/2); + check_tx(radio_num, WPP*3/2); + check_error(ERR_TX_EOB_ACK); + + // Test a minimally-sized packet + start_tx(radio_num, 1); + check_tx(radio_num, 1); + check_error(ERR_TX_EOB_ACK); + + test.end_test(); + + + //--------------------- + // Test Tx (underflow) + //--------------------- + + test.start_test("Tx (now, underflow)", 50us); + + // Send some bursts without EOB + start_tx(radio_num, WPP*3/4, 1, 0); // Skip EOB + check_tx(radio_num, WPP*3/4); + check_error(ERR_TX_UNDERRUN); + + start_tx(radio_num, WPP*2, 1, 0); // Skip EOB + check_tx(radio_num, WPP*2); + check_error(ERR_TX_UNDERRUN); + + test.end_test(); + + + //----------------- + // Test Tx (timed) + //----------------- + + test.start_test("Tx (timed)", 50us); + + // Grab and verify a partial packet + start_tx_timed(radio_num, WPP*3/4, radio_time + 200); + check_tx_timed(radio_num, WPP*3/4, radio_time + 200); + check_error(ERR_TX_EOB_ACK); + + // Grab and verify whole packets + start_tx_timed(radio_num, WPP*2, radio_time + 200); + check_tx_timed(radio_num, WPP*2, radio_time + 200); + check_error(ERR_TX_EOB_ACK); + + test.end_test(); + + + //----------------- + // Test Tx (timed, underflow) + //----------------- + + test.start_test("Tx (timed, underflow)", 50us); + + // Send some bursts without EOB + start_tx_timed(radio_num, WPP*3/4, radio_time + 200, 1, 0); + check_tx_timed(radio_num, WPP*3/4, radio_time + 200); + check_error(ERR_TX_UNDERRUN); + + start_tx_timed(radio_num, WPP*2, radio_time + 200, 1, 0); + check_tx_timed(radio_num, WPP*2, radio_time + 200); + check_error(ERR_TX_UNDERRUN); + + test.end_test(); + + + //--------------------------- + // Test Tx (timed, late) + //--------------------------- + + test.start_test("Tx (timed, late)", 50us); + + // Test each error policy + policy = policy.first(); + do begin + // Set the policy + if (policy == WAIT_FOR_EOP) begin + write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_PACKET); + end else if (policy == WAIT_FOR_EOB) begin + write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_BURST); + end + +// Commenting out the fork code for now due to Vivado 2018.3 bug. +// radio_data = radio_tx_data[radio_num]; +// fork : tx_fork + // In this branch of the fork, we send the packets + repeat (2) begin + // Send late packets with random start value + start_tx_timed(radio_num, WPP*3, 0, $urandom()); + + if (policy == WAIT_FOR_EOP) begin + // We should get three errors, one for each packet + repeat (3) check_error(ERR_TX_LATE_DATA); + end else if (policy == WAIT_FOR_EOB) begin + // We should get one error for the entire burst + check_error(ERR_TX_LATE_DATA); + end + end + +// // The packets sent in the above branch of the fork should be +// // dropped. In this branch of the fork we make sure that the Tx +// // output doesn't change. +// begin +// forever begin +// @(posedge radio_clk) +// `ASSERT_ERROR( +// radio_data === radio_tx_data[radio_num], +// "Radio Tx output changed when late Tx packet should have been ignored" +// ); +// end +// end +// join_any +// +// // Stop checking the output +// disable tx_fork; + + policy = policy.next(); + end while (policy != policy.first()); + + // Make sure good transmissions can go through now. + start_tx_timed(radio_num, WPP, radio_time + 200); + check_tx_timed(radio_num, WPP, radio_time + 200); + check_error(ERR_TX_EOB_ACK); + + test.end_test(); + + endtask : test_tx + + + + // Test internal loopback and idle value + task automatic test_loopback_and_idle(int radio_num); + int byte_length; + chdr_word_t data[$]; + bit [ITEM_W-1:0] idle; + + //---------------------------- + // Use IDLE value to loopback + //---------------------------- + + test.start_test("Idle Loopback", 50us); + + // Turn on loopback + write_radio(radio_num, REG_LOOPBACK_EN, 1); + + // This test ensures we get the Tx output on Rx and not the TB's simulated + // radio data. It also tests updating the idle value. Run the test twice to + // make sure the IDLE value updates. + repeat (2) begin + // Set idle value + idle = $urandom(); + write_radio(radio_num, REG_TX_IDLE_VALUE, idle); + + // Grab a radio word and check that it equals the IDLE value + write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, 1); + write_radio(radio_num, REG_RX_CMD, RX_CMD_FINITE); + blk_ctrl.recv(radio_num, data, byte_length); + + // Check the length + `ASSERT_ERROR(byte_length == RADIO_W/8, "Didn't receive expected length"); + + // Check the payload + foreach (data[i]) begin + chdr_word_t word; + word = data[i]; // Work around Vivado 2018.3 issue + `ASSERT_ERROR( + word == {$bits(chdr_word_t)/ITEM_W{idle}}, + "Loopback data didn't match expected" + ); + end + end + + test.end_test(); + + + //--------------------- + // Loopback Tx packets + //--------------------- + + test.start_test("Tx Loopback", 50us); + + // This test ensures that loopback isn't reordering words or anything else + // unexpected. + + // Configure the Tx error reporting registers + write_radio(radio_num, REG_TX_ERR_PORT, TX_ERR_DST_PORT); + write_radio(radio_num, REG_TX_ERR_REM_PORT, TX_ERR_REM_DST_PORT); + write_radio(radio_num, REG_TX_ERR_REM_EPID, TX_ERR_REM_DST_EPID); + write_radio(radio_num, REG_TX_ERR_ADDR, TX_ERR_ADDRESS); + + // Set packet length + write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); + + // Loopback a few packets, back-to-back. This code has a race condition + // since there's a delay between when we start Tx and when Rx starts, due + // to how long it takes to write the Rx registers. Therefore, we transmit a + // lot more packets than we receive to ensure we're still transmitting by + // the time we receive. + start_tx(radio_num, WPP*16); + start_rx(radio_num, WPP*2); + + // Check the results + check_rx(radio_num, WPP*2); + check_error(ERR_TX_EOB_ACK); + + // Turn off loopback + write_radio(radio_num, REG_LOOPBACK_EN, 0); + + test.end_test(); + endtask : test_loopback_and_idle; + + + + //--------------------------------------------------------------------------- + // Test Process + //--------------------------------------------------------------------------- + + timeout_t timeout; + + initial begin : main + string tb_name; + + //------------------------------------------------------------------------- + // Initialization + //------------------------------------------------------------------------- + + // Generate a string for the name of this instance of the testbench + tb_name = $sformatf( + "rfnoc_block_radio_tb\nCHDR_W = %0D, ITEM_W = %0D, NIPC = %0D, NUM_PORTS = %0D, STALL_PROB = %0D, STB_PROB = %0D, TEST_REGS = %0D", + CHDR_W, ITEM_W, NIPC, NUM_PORTS, STALL_PROB, STB_PROB, TEST_REGS + ); + + test.start_tb(tb_name); + + // Don't start the clocks until after start_tb() returns. This ensures that + // the clocks aren't toggling while other instances of this testbench are + // running, which speeds up simulation time. + rfnoc_chdr_clk_gen.start(); + rfnoc_ctrl_clk_gen.start(); + radio_clk_gen.start(); + + // Setup and start the stream endpoint BFM + blk_ctrl = new(backend, m_ctrl, s_ctrl); + for (int i = 0; i < NUM_PORTS; i++) begin + // I'd love to do this: + // void'(blk_ctrl.add_master_data_port(m_chdr[i])); + // void'(blk_ctrl.add_slave_data_port(s_chdr[i])); + // But interface indices must be constant. So instead, we use a semaphore + // to trigger port initialization and control the order of initialization + // in the generate block gen_radio_connections. + + // Put the port number in the semaphore to cause its initializer to run + port_sem.put(i+1); + // Delay to allow gen_radio_connections to run + #0; + // Get the port number again to know when it's done + port_sem.get(i+1); + + // Set the CHDR BFM stall probability + blk_ctrl.set_master_stall_prob(i, STALL_PROB); + blk_ctrl.set_slave_stall_prob(i, STALL_PROB); + end + blk_ctrl.run(); + + + //------------------------------------------------------------------------- + // Reset + //------------------------------------------------------------------------- + + test.start_test("Flush block then reset it", 10us); + blk_ctrl.flush_and_reset(); + test.end_test(); + + + //------------------------------------------------------------------------- + // Test Sequences + //------------------------------------------------------------------------- + + // Run register tests first, since they check that initial values are + // correct. + + test_block_info(); + if (TEST_REGS) test_shared_registers(); + + for (int radio_num = 0; radio_num < NUM_PORTS; radio_num++) begin + $display("************************************************************"); + $display("Testing Radio Channel %0d", radio_num); + $display("************************************************************"); + if (TEST_REGS) begin + test_general_registers(radio_num); + test_rx_registers(radio_num); + test_tx_registers(radio_num); + end + test_rx(radio_num); + test_tx(radio_num); + test_loopback_and_idle(radio_num); + end + + + //------------------------------------------------------------------------- + // Finish + //------------------------------------------------------------------------- + + // End the TB, but don't $finish, since we don't want to kill other + // instances of this testbench that may be running. + test.end_tb(0); + + // Kill the clocks to end this instance of the testbench + rfnoc_chdr_clk_gen.kill(); + rfnoc_ctrl_clk_gen.kill(); + radio_clk_gen.kill(); + + end : main + +endmodule : rfnoc_block_radio_tb diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v new file mode 100644 index 000000000..54529136b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v @@ -0,0 +1,246 @@ +// +// Copyright 2015 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module rx_frontend_gen3 #( + parameter SR_MAG_CORRECTION = 0, + parameter SR_PHASE_CORRECTION = 1, + parameter SR_OFFSET_I = 2, + parameter SR_OFFSET_Q = 3, + parameter SR_IQ_MAPPING = 4, + parameter SR_HET_PHASE_INCR = 5, + parameter BYPASS_DC_OFFSET_CORR = 0, + parameter BYPASS_IQ_COMP = 0, + parameter BYPASS_REALMODE_DSP = 0, + parameter DEVICE = "7SERIES" +)( + input clk, input reset, input sync_in, + input set_stb, input [7:0] set_addr, input [31:0] set_data, + input adc_stb, input [15:0] adc_i, input [15:0] adc_q, + output rx_stb, output [15:0] rx_i, output [15:0] rx_q +); + + wire realmode; + wire swap_iq; + wire invert_i; + wire invert_q; + wire realmode_decim; + wire bypass_all; + wire [1:0] iq_map_reserved; + wire [17:0] mag_corr, phase_corr; + wire phase_dir; + wire phase_sync; + + reg [23:0] adc_i_mux, adc_q_mux; + reg adc_mux_stb; + wire [23:0] adc_i_ofs, adc_q_ofs, adc_i_comp, adc_q_comp; + reg [23:0] adc_i_ofs_dly, adc_q_ofs_dly; + wire adc_ofs_stb, adc_comp_stb; + reg [1:0] adc_ofs_stb_dly; + wire [23:0] adc_i_dsp, adc_q_dsp; + wire adc_dsp_stb; + wire [35:0] corr_i, corr_q; + wire [15:0] rx_i_out, rx_q_out; + + /******************************************************** + ** Settings Bus Registers + ********************************************************/ + setting_reg #(.my_addr(SR_MAG_CORRECTION),.width(18)) sr_mag_corr ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(mag_corr),.changed()); + + setting_reg #(.my_addr(SR_PHASE_CORRECTION),.width(18)) sr_phase_corr ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(phase_corr),.changed()); + + setting_reg #(.my_addr(SR_IQ_MAPPING), .width(8)) sr_mux_sel ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out({bypass_all,iq_map_reserved,realmode_decim,invert_i,invert_q,realmode,swap_iq}),.changed()); + + // Setting reg: 1 bit to set phase direction: default to 0: + // direction bit == 0: the phase is increased by pi/2 (counter clockwise) + // direction bit == 1: the phase is increased by -pi/2 (clockwise) + setting_reg #(.my_addr(SR_HET_PHASE_INCR), .width(1)) sr_phase_dir ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(phase_dir),.changed(phase_sync)); + + /******************************************************** + ** IQ Mapping (swapping, inversion, real-mode) + ********************************************************/ + // MUX so we can do realmode signals on either input + always @(posedge clk) begin + if (swap_iq) begin + adc_i_mux[23:8] <= invert_q ? ~adc_q : adc_q; + adc_q_mux[23:8] <= realmode ? 16'd0 : invert_i ? ~adc_i : adc_i; + end else begin + adc_i_mux[23:8] <= invert_i ? ~adc_i : adc_i; + adc_q_mux[23:8] <= realmode ? 16'd0 : invert_q ? ~adc_q : adc_q; + end + adc_mux_stb <= adc_stb; + adc_i_mux[7:0] <= 8'd0; + adc_q_mux[7:0] <= 8'd0; + end + + /******************************************************** + ** DC offset Correction + ********************************************************/ + generate + if (BYPASS_DC_OFFSET_CORR == 0) begin + + rx_dcoffset #(.WIDTH(24),.ADDR(SR_OFFSET_I)) rx_dcoffset_i ( + .clk(clk),.rst(reset),.set_stb(set_stb),.set_addr(set_addr),.set_data(set_data), + .in_stb(adc_mux_stb),.in(adc_i_mux), + .out_stb(adc_ofs_stb),.out(adc_i_ofs)); + rx_dcoffset #(.WIDTH(24),.ADDR(SR_OFFSET_Q)) rx_dcoffset_q ( + .clk(clk),.rst(reset),.set_stb(set_stb),.set_addr(set_addr),.set_data(set_data), + .in_stb(adc_mux_stb),.in(adc_q_mux), + .out_stb(),.out(adc_q_ofs)); + + end else begin + assign adc_ofs_stb = adc_mux_stb; + assign adc_i_ofs = adc_i_mux; + assign adc_q_ofs = adc_q_mux; + end + endgenerate + + /******************************************************** + ** IQ Imbalance Compensation + ********************************************************/ + generate + if (BYPASS_IQ_COMP == 0) begin + + mult_add_clip #( + .WIDTH_A(18), + .BIN_PT_A(17), + .WIDTH_B(18), + .BIN_PT_B(17), + .WIDTH_C(24), + .BIN_PT_C(23), + .WIDTH_O(24), + .BIN_PT_O(23), + .LATENCY(2) + ) mult_i ( + .clk(clk), + .reset(reset), + .CE(1'b1), + .A(adc_i_ofs[23:6]), + .B(mag_corr), + .C(adc_i_ofs), + .O(adc_i_comp) + ); + + mult_add_clip #( + .WIDTH_A(18), + .BIN_PT_A(17), + .WIDTH_B(18), + .BIN_PT_B(17), + .WIDTH_C(24), + .BIN_PT_C(23), + .WIDTH_O(24), + .BIN_PT_O(23), + .LATENCY(2) + ) mult_q ( + .clk(clk), + .reset(reset), + .CE(1'b1), + .A(adc_i_ofs[23:6]), + .B(phase_corr), + .C(adc_q_ofs), + .O(adc_q_comp) + ); + + // Delay to match path latencies + always @(posedge clk) begin + if (reset) begin + adc_ofs_stb_dly <= 2'b0; + end else begin + adc_ofs_stb_dly <= {adc_ofs_stb_dly[0], adc_ofs_stb}; + end + end + + assign adc_comp_stb = adc_ofs_stb_dly[1]; + + end else begin + assign adc_comp_stb = adc_ofs_stb; + assign adc_i_comp = adc_i_ofs; + assign adc_q_comp = adc_q_ofs; + end + endgenerate + + /******************************************************** + ** Realmode DSP: + * - Heterodyne frequency translation + * - Realmode decimation (by 2) + ********************************************************/ + generate + if (BYPASS_REALMODE_DSP == 0) begin + + wire [24:0] adc_i_dsp_cout, adc_q_dsp_cout; + wire [23:0] adc_i_cclip, adc_q_cclip; + wire [23:0] adc_i_hb, adc_q_hb; + wire [23:0] adc_i_dec, adc_q_dec; + wire adc_dsp_cout_stb; + wire adc_cclip_stb; + wire adc_hb_stb; + + wire valid_hbf0; + wire valid_hbf1; + wire valid_dec0; + wire valid_dec1; + + // 90 degree mixer + quarter_rate_downconverter #(.WIDTH(24)) qr_dc_i( + .clk(clk), .reset(reset || sync_in), .phase_sync(phase_sync), + .i_tdata({adc_i_comp, adc_q_comp}), .i_tlast(1'b1), .i_tvalid(adc_comp_stb), .i_tready(), + .o_tdata({adc_i_dsp_cout, adc_q_dsp_cout}), .o_tlast(), .o_tvalid(adc_dsp_cout_stb), .o_tready(1'b1), + .dirctn(phase_dir)); + + // Double FIR and decimator block + localparam HB_COEFS = {-18'd62, 18'd0, 18'd194, 18'd0, -18'd440, 18'd0, 18'd855, 18'd0, -18'd1505, 18'd0, 18'd2478, 18'd0, + -18'd3900, 18'd0, 18'd5990, 18'd0, -18'd9187, 18'd0, 18'd14632, 18'd0, -18'd26536, 18'd0, 18'd83009, 18'd131071, 18'd83009, + 18'd0, -18'd26536, 18'd0, 18'd14632, 18'd0, -18'd9187, 18'd0, 18'd5990, 18'd0, -18'd3900, 18'd0, 18'd2478, 18'd0, -18'd1505, + 18'd0, 18'd855, 18'd0, -18'd440, 18'd0, 18'd194, 18'd0, -18'd62}; + + axi_fir_filter_dec #( + .WIDTH(24), + .COEFF_WIDTH(18), + .NUM_COEFFS(47), + .COEFFS_VEC(HB_COEFS), + .BLANK_OUTPUT(0) + ) ffd0 ( + .clk(clk), .reset(reset || sync_in), + + .i_tdata({adc_i_dsp_cout, adc_q_dsp_cout}), + .i_tlast(1'b1), + .i_tvalid(adc_dsp_cout_stb), + .i_tready(), + + .o_tdata({adc_i_dec, adc_q_dec}), + .o_tlast(), + .o_tvalid(adc_hb_stb), + .o_tready(1'b1)); + + assign adc_dsp_stb = realmode_decim ? adc_hb_stb : adc_comp_stb; + assign adc_i_dsp = realmode_decim ? adc_i_dec : adc_i_comp; + assign adc_q_dsp = realmode_decim ? adc_q_dec : adc_q_comp; + + end else begin + assign adc_dsp_stb = adc_comp_stb; + assign adc_i_dsp = adc_i_comp; + assign adc_q_dsp = adc_q_comp; + end + endgenerate + + // Round to short complex (sc16) + round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_i ( + .clk(clk),.reset(reset), .in(adc_i_dsp),.strobe_in(adc_dsp_stb), .out(rx_i_out), .strobe_out(rx_stb)); + round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_q ( + .clk(clk),.reset(reset), .in(adc_q_dsp),.strobe_in(adc_dsp_stb), .out(rx_q_out), .strobe_out()); + + assign rx_i = bypass_all ? adc_i : rx_i_out; + assign rx_q = bypass_all ? adc_q : rx_q_out; + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv new file mode 100644 index 000000000..a6f827f8f --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv @@ -0,0 +1,104 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: sim_radio_gen +// +// Description: Generate radio data for simulation purposes. The strobe pattern +// is random, which is not like a normal radio but covers every possibility. +// The data pattern is an incrementing sequence of samples, with each channel +// starting at a different value to differentiate them. Strobe and time are +// common between channels. +// + +module sim_radio_gen #( + parameter int NSPC = 1, // Number of samples per clock cycle + parameter int SAMP_W = 32, // Length of each radio sample + parameter int NUM_CHANNELS = 1, // Number of radio RX ports + parameter int STB_PROB = 50, // Probability of STB being asserted on each clock cycle + parameter int INCREMENT = 2, // Amount by which to increment + parameter int PPS_PERIOD = 50 // Period of the PPS output +) ( + input bit radio_clk, + input bit radio_rst, + output bit [NUM_CHANNELS*SAMP_W*NSPC-1:0] radio_rx_data, + output bit [ NUM_CHANNELS-1:0] radio_rx_stb, + output bit [ 63:0] radio_time, + output bit radio_pps +); + + localparam int RADIO_W = SAMP_W*NSPC; + typedef bit [RADIO_W-1:0] radio_t; // Radio output word + typedef bit [SAMP_W-1:0] sample_t; // Single sample + + initial assert (PPS_PERIOD % INCREMENT == 0) else + $fatal(1, "PPS_PERIOD must be a multiple of INCREMENT"); + + + // Generate an initial value all radio channels + function radio_t [NUM_CHANNELS-1:0] radio_init(); + radio_t [NUM_CHANNELS-1:0] ret_val; + + for (int n = 0; n < NUM_CHANNELS; n++) begin + sample_t sample; + + // Calculate the value of first sample in this radio channel + sample = sample_t'((2.0 ** SAMP_W) / NUM_CHANNELS * n); + + // Calculate the value of subsequent samples in the channel + for (int s = 0; s < NSPC; s++) begin + ret_val[n][s*SAMP_W +: SAMP_W] = sample + s; + end + end + + return ret_val; + endfunction : radio_init + + + //--------------------------------------------------------------------------- + // Radio Data Generation + //--------------------------------------------------------------------------- + + radio_t [NUM_CHANNELS-1:0] data = radio_init(); + + assign radio_rx_data = data; + + always @(posedge radio_clk) begin : radio_data_count_reg + if (radio_rst) begin + data <= radio_init(); + radio_rx_stb <= '0; + end else begin + radio_rx_stb <= '0; + if ($urandom_range(100) < STB_PROB) begin + for (int n = 0; n < NUM_CHANNELS; n++) begin + for (int s = 0; s < NSPC; s++) begin + data[n][s*SAMP_W +: SAMP_W] <= data[n][s*SAMP_W +: SAMP_W] + NSPC; + end + end + radio_rx_stb <= '1; + end + end + end : radio_data_count_reg + + + //--------------------------------------------------------------------------- + // Radio Time + //--------------------------------------------------------------------------- + + always @(posedge radio_clk) begin + if (radio_rst) begin + radio_time <= 64'b0; + radio_pps <= 1'b0; + end else begin + radio_pps <= 1'b0; + if (radio_rx_stb[0]) begin + radio_time <= radio_time + INCREMENT; + if (radio_time % PPS_PERIOD == 0 && radio_time != 0) begin + radio_pps <= 1'b1; + end + end + end + end + +endmodule : sim_radio_gen
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v new file mode 100644 index 000000000..f5435787d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v @@ -0,0 +1,173 @@ +// +// Copyright 2015 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module tx_frontend_gen3 #( + parameter SR_OFFSET_I = 0, + parameter SR_OFFSET_Q = 1, + parameter SR_MAG_CORRECTION = 2, + parameter SR_PHASE_CORRECTION = 3, + parameter SR_MUX = 4, + parameter BYPASS_DC_OFFSET_CORR = 0, + parameter BYPASS_IQ_COMP = 0, + parameter DEVICE = "7SERIES" +)( + input clk, input reset, + input set_stb, input [7:0] set_addr, input [31:0] set_data, + input tx_stb, input [15:0] tx_i, input [15:0] tx_q, + output reg dac_stb, output reg [15:0] dac_i, output reg [15:0] dac_q +); + + wire [23:0] i_dco, q_dco; + wire [7:0] mux_ctrl; + wire [17:0] mag_corr, phase_corr; + + wire [35:0] corr_i, corr_q; + reg [1:0] tx_stb_dly; + reg [23:0] tx_i_dly, tx_q_dly; + wire tx_comp_stb, tx_ofs_stb; + wire [23:0] tx_i_comp, tx_q_comp, tx_i_ofs, tx_q_ofs; + wire tx_round_stb; + wire [15:0] tx_i_round, tx_q_round; + + /******************************************************** + ** Settings Registers + ********************************************************/ + setting_reg #(.my_addr(SR_OFFSET_I), .width(24)) sr_i_dc_offset ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(i_dco),.changed()); + + setting_reg #(.my_addr(SR_OFFSET_Q), .width(24)) sr_q_dc_offset ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(q_dco),.changed()); + + setting_reg #(.my_addr(SR_MAG_CORRECTION),.width(18)) sr_mag_corr ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(mag_corr),.changed()); + + setting_reg #(.my_addr(SR_PHASE_CORRECTION),.width(18)) sr_phase_corr ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(phase_corr),.changed()); + + setting_reg #(.my_addr(SR_MUX), .width(8), .at_reset(8'h10)) sr_mux_ctrl ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(mux_ctrl),.changed()); + + /******************************************************** + ** DSP + ********************************************************/ + // I/Q compensation with option to bypass + generate + if (BYPASS_IQ_COMP == 0) begin + + mult_add_clip #( + .WIDTH_A(16), + .BIN_PT_A(15), + .WIDTH_B(18), + .BIN_PT_B(17), + .WIDTH_C(16), + .BIN_PT_C(15), + .WIDTH_O(24), + .BIN_PT_O(23), + .LATENCY(2) + ) mult_i ( + .clk(clk), + .reset(reset), + .CE(1'b1), + .A(tx_i), + .B(mag_corr), + .C(tx_i), + .O(tx_i_comp) + ); + + mult_add_clip #( + .WIDTH_A(16), + .BIN_PT_A(15), + .WIDTH_B(18), + .BIN_PT_B(17), + .WIDTH_C(16), + .BIN_PT_C(15), + .WIDTH_O(24), + .BIN_PT_O(23), + .LATENCY(2) + ) mult_q ( + .clk(clk), + .reset(reset), + .CE(1'b1), + .A(tx_i), + .B(phase_corr), + .C(tx_q), + .O(tx_q_comp) + ); + + // Delay to match path latencies + always @(posedge clk) begin + if (reset) begin + tx_stb_dly <= 2'b0; + end else begin + tx_stb_dly <= {tx_stb_dly[0], tx_stb}; + end + end + + assign tx_comp_stb = tx_stb_dly[1]; + + end else begin + assign tx_comp_stb = tx_stb; + assign tx_i_comp = {tx_i,8'd0}; + assign tx_q_comp = {tx_q,8'd0}; + end + endgenerate + + // DC offset correction + generate + if (BYPASS_DC_OFFSET_CORR == 0) begin + add2_and_clip_reg #(.WIDTH(24)) add_dco_i ( + .clk(clk), .rst(reset), .in1(i_dco), .in2(tx_i_comp), .strobe_in(tx_comp_stb), .sum(tx_i_ofs), .strobe_out(tx_ofs_stb)); + add2_and_clip_reg #(.WIDTH(24)) add_dco_q ( + .clk(clk), .rst(reset), .in1(q_dco), .in2(tx_q_comp), .strobe_in(tx_comp_stb), .sum(tx_q_ofs), .strobe_out()); + end else begin + assign tx_ofs_stb = tx_comp_stb; + assign tx_i_ofs = tx_i_comp; + assign tx_q_ofs = tx_q_comp; + end + endgenerate + + // Round to short complex (sc16) + round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_i ( + .clk(clk),.reset(reset), .in(tx_i_ofs),.strobe_in(tx_ofs_stb), .out(tx_i_round), .strobe_out(tx_round_stb)); + round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_q ( + .clk(clk),.reset(reset), .in(tx_q_ofs),.strobe_in(tx_ofs_stb), .out(tx_q_round), .strobe_out()); + + // Mux + // Muxing logic matches that in tx_frontend.v, and what tx_frontend_core_200.cpp expects. + // + // mux_ctrl ! 0+0 ! 0+16 ! 1+0 ! 1+16 + // =========!======!======!======!======== + // DAC_I ! tx_i ! tx_i ! tx_q ! tx_q + // DAC_Q ! tx_i ! tx_q ! tx_i ! tx_q + // + // Most daughterboards will thus use 0x01 or 0x10 as the mux_ctrl value. + always @(posedge clk) begin + if (reset) begin + dac_stb <= 1'b0; + dac_i <= 16'd0; + dac_q <= 16'd0; + end else begin + dac_stb <= tx_round_stb; + case(mux_ctrl[3:0]) + 0 : dac_i <= tx_i_round; + 1 : dac_i <= tx_q_round; + default : dac_i <= 0; + endcase + case(mux_ctrl[7:4]) + 0 : dac_q <= tx_i_round; + 1 : dac_q <= tx_q_round; + default : dac_q <= 0; + endcase + end + end + +endmodule |