diff options
author | Martin Braun <martin.braun@ettus.com> | 2020-01-23 16:10:22 -0800 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2020-01-28 09:35:36 -0800 |
commit | bafa9d95453387814ef25e6b6256ba8db2df612f (patch) | |
tree | 39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio | |
parent | 3075b981503002df3115d5f1d0b97d2619ba30f2 (diff) | |
download | uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.gz uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.bz2 uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.zip |
Merge FPGA repository back into UHD repository
The FPGA codebase was removed from the UHD repository in 2014 to reduce
the size of the repository. However, over the last half-decade, the
split between the repositories has proven more burdensome than it has
been helpful. By merging the FPGA code back, it will be possible to
create atomic commits that touch both FPGA and UHD codebases. Continuous
integration testing is also simplified by merging the repositories,
because it was previously difficult to automatically derive the correct
UHD branch when testing a feature branch on the FPGA repository.
This commit also updates the license files and paths therein.
We are therefore merging the repositories again. Future development for
FPGA code will happen in the same repository as the UHD host code and
MPM code.
== Original Codebase and Rebasing ==
The original FPGA repository will be hosted for the foreseeable future
at its original local location: https://github.com/EttusResearch/fpga/
It can be used for bisecting, reference, and a more detailed history.
The final commit from said repository to be merged here is
05003794e2da61cabf64dd278c45685a7abad7ec. This commit is tagged as
v4.0.0.0-pre-uhd-merge.
If you have changes in the FPGA repository that you want to rebase onto
the UHD repository, simply run the following commands:
- Create a directory to store patches (this should be an empty
directory):
mkdir ~/patches
- Now make sure that your FPGA codebase is based on the same state as
the code that was merged:
cd src/fpga # Or wherever your FPGA code is stored
git rebase v4.0.0.0-pre-uhd-merge
Note: The rebase command may look slightly different depending on what
exactly you're trying to rebase.
- Create a patch set for your changes versus v4.0.0.0-pre-uhd-merge:
git format-patch v4.0.0.0-pre-uhd-merge -o ~/patches
Note: Make sure that only patches are stored in your output directory.
It should otherwise be empty. Make sure that you picked the correct
range of commits, and only commits you wanted to rebase were exported
as patch files.
- Go to the UHD repository and apply the patches:
cd src/uhd # Or wherever your UHD repository is stored
git am --directory fpga ~/patches/*
rm -rf ~/patches # This is for cleanup
== Contributors ==
The following people have contributed mainly to these files (this list
is not complete):
Co-authored-by: Alex Williams <alex.williams@ni.com>
Co-authored-by: Andrej Rode <andrej.rode@ettus.com>
Co-authored-by: Ashish Chaudhari <ashish@ettus.com>
Co-authored-by: Ben Hilburn <ben.hilburn@ettus.com>
Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Co-authored-by: Daniel Jepson <daniel.jepson@ni.com>
Co-authored-by: Derek Kozel <derek.kozel@ettus.com>
Co-authored-by: EJ Kreinar <ej@he360.com>
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com>
Co-authored-by: Ian Buckley <ian.buckley@gmail.com>
Co-authored-by: Jörg Hofrichter <joerg.hofrichter@ni.com>
Co-authored-by: Jon Kiser <jon.kiser@ni.com>
Co-authored-by: Josh Blum <josh@joshknows.com>
Co-authored-by: Jonathon Pendlum <jonathan.pendlum@ettus.com>
Co-authored-by: Martin Braun <martin.braun@ettus.com>
Co-authored-by: Matt Ettus <matt@ettus.com>
Co-authored-by: Michael West <michael.west@ettus.com>
Co-authored-by: Moritz Fischer <moritz.fischer@ettus.com>
Co-authored-by: Nick Foster <nick@ettus.com>
Co-authored-by: Nicolas Cuervo <nicolas.cuervo@ettus.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Paul David <paul.david@ettus.com>
Co-authored-by: Ryan Marlow <ryan.marlow@ettus.com>
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-authored-by: Sylvain Munaut <tnt@246tNt.com>
Co-authored-by: Trung Tran <trung.tran@ettus.com>
Co-authored-by: Vidush Vishwanath <vidush.vishwanath@ettus.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
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 |