aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio')
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile47
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/Makefile.srcs20
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/noc_shell_radio.v290
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/quarter_rate_downconverter.v138
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_core.v370
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v521
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v417
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio.v546
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv68
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_regs.vh125
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv1382
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/rx_frontend_gen3.v246
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/sim_radio_gen.sv104
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_radio/tx_frontend_gen3.v173
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