diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/blocks')
53 files changed, 14730 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile new file mode 100644 index 000000000..acee50882 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile @@ -0,0 +1,45 @@ +# +# 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_AXI_RAM_FIFO_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_axi_ram_fifo_all_tb + +SIM_SRCS = \ +$(abspath sim_axi_ram.sv) \ +$(abspath rfnoc_block_axi_ram_fifo_tb.sv) \ +$(abspath rfnoc_block_axi_ram_fifo_all_tb.sv) + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile.srcs new file mode 100644 index 000000000..9faa27321 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/Makefile.srcs @@ -0,0 +1,18 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +RFNOC_BLOCK_AXI_RAM_FIFO_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/, \ +noc_shell_axi_ram_fifo.v \ +axi_ram_fifo_regs.vh \ +axi_ram_fifo_regs.v \ +axi_ram_fifo_bist.v \ +axi_ram_fifo_bist_regs.v \ +axi_ram_fifo.v \ +rfnoc_block_axi_ram_fifo.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo.v new file mode 100644 index 000000000..5dd5f5ec4 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo.v @@ -0,0 +1,1228 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo +// +// Description: +// +// Implements a FIFO using a memory-mapped AXI interface as storage. This can +// be connected to any memory-mapped AXI4 bus interface, such as DRAM, SRAM, +// or AXI interconnect IP. The input and output interfaces to the FIFO are +// AXI-Stream. +// +// The logic is designed to buffer up multiple words so that writes and reads +// can be implemented as efficient burst transactions on the AXI4 bus. This +// core never crosses 4 KiB boundaries, per AXI4 rules (a burst must not +// cross a 4 KiB boundary). +// +// The FIFO must be at least 4 KiB in size so that the 4 KiB page boundary +// protection also handles/prevents the FIFO wrap corner case. +// +// Parameters: +// +// MEM_ADDR_W : The width of the byte address to use for the AXI4 memory +// mapped interface. +// +// MEM_DATA_W : The width of the data port to use for the AXI4 memory +// mapped interface. +// +// KEEP_W : Width of tkeep on the AXI-Stream interface. Set to 1 if +// tkeep is not used. +// +// FIFO_ADDR_BASE : Default base address to use for this FIFO. +// +// FIFO_ADDR_MASK : Default byte address mask, which defines which memory +// address bits can be used for the FIFO. For example, an 64 +// KiB memory region, or 2^16 bytes, would require the mask +// 0xFFFF (16 ones). In other words, the mask should be the +// size of the memory region minus 1. +// +// BURST_TIMEOUT : Default number of memory clock cycles to wait for new +// data before performing a short, sub-optimal burst. One +// value per FIFO. +// +// BIST : If true, BIST logic will be included in the build. +// +// CLK_RATE : Frequency of clk in Hz +// +// IN_FIFO_SIZE : The input FIFO size will be 2**IN_FIFO_SIZE in depth. +// +// OUT_FIFO_SIZE : The output FIFO size will be 2**OUT_FIFO_SIZE in depth. +// This must be at least 9 so that there is enough space to +// accept a full AXI4 burst and then accept additional +// bursts while the FIFO is reading out. +// + +module axi_ram_fifo #( + parameter MEM_ADDR_W = 32, + parameter MEM_DATA_W = 64, + parameter KEEP_W = 1, + parameter [MEM_ADDR_W-1:0] FIFO_ADDR_BASE = 'h0, + parameter [MEM_ADDR_W-1:0] FIFO_ADDR_MASK = 'h00FFFFFF, + parameter BURST_TIMEOUT = 256, + parameter BIST = 1, + parameter CLK_RATE = 200e6, + parameter IN_FIFO_SIZE = 11, + parameter OUT_FIFO_SIZE = 10 +) ( + + input clk, + input rst, + + //-------------------------------------------------------------------------- + // CTRL Port + //-------------------------------------------------------------------------- + + 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, + + //-------------------------------------------------------------------------- + // AXI-Stream Interface + //-------------------------------------------------------------------------- + + // FIFO Input + input wire [MEM_DATA_W-1:0] s_tdata, + input wire [ KEEP_W-1:0] s_tkeep, + input wire s_tlast, + input wire s_tvalid, + output wire s_tready, + + // FIFO Output + output wire [MEM_DATA_W-1:0] m_tdata, + output wire [ KEEP_W-1:0] m_tkeep, + output wire m_tlast, + output wire m_tvalid, + input wire m_tready, + + //-------------------------------------------------------------------------- + // AXI4 Memory Interface + //-------------------------------------------------------------------------- + + // AXI Write Address Channel + output wire [ 0:0] m_axi_awid, // Write address ID. This signal is the identification tag for the write address signals. + output wire [ MEM_ADDR_W-1:0] m_axi_awaddr, // Write address. The write address gives the address of the first transfer in a write burst. + output wire [ 7:0] m_axi_awlen, // Burst length. The burst length gives the exact number of transfers in a burst. + output wire [ 2:0] m_axi_awsize, // Burst size. This signal indicates the size of each transfer in the burst. + output wire [ 1:0] m_axi_awburst, // Burst type. The burst type and the size information, determine how the address is calculated. + output wire [ 0:0] m_axi_awlock, // Lock type. Provides additional information about the atomic characteristics of the transfer. + output wire [ 3:0] m_axi_awcache, // Memory type. This signal indicates how transactions are required to progress. + output wire [ 2:0] m_axi_awprot, // Protection type. This signal indicates the privilege and security level of the transaction. + output wire [ 3:0] m_axi_awqos, // Quality of Service, QoS. The QoS identifier sent for each write transaction. + output wire [ 3:0] m_axi_awregion, // Region identifier. Permits a single physical interface on a slave to be re-used. + output wire [ 0:0] m_axi_awuser, // User signal. Optional User-defined signal in the write address channel. + output wire m_axi_awvalid, // Write address valid. This signal indicates that the channel is signaling valid write addr. + input wire m_axi_awready, // Write address ready. This signal indicates that the slave is ready to accept an address. + // + // AXI Write Data Channel + output wire [ MEM_DATA_W-1:0] m_axi_wdata, // Write data + output wire [MEM_DATA_W/8-1:0] m_axi_wstrb, // Write strobes. This signal indicates which byte lanes hold valid data. + output wire m_axi_wlast, // Write last. This signal indicates the last transfer in a write burst. + output wire [ 0:0] m_axi_wuser, // User signal. Optional User-defined signal in the write data channel. + output wire m_axi_wvalid, // Write valid. This signal indicates that valid write data and strobes are available. + input wire m_axi_wready, // Write ready. This signal indicates that the slave can accept the write data. + // + // AXI Write Response Channel + input wire [ 0:0] m_axi_bid, // Response ID tag. This signal is the ID tag of the write response. + input wire [ 1:0] m_axi_bresp, // Write response. This signal indicates the status of the write transaction. + input wire [ 0:0] m_axi_buser, // User signal. Optional User-defined signal in the write response channel. + input wire m_axi_bvalid, // Write response valid. This signal indicates that the channel is signaling a valid response. + output wire m_axi_bready, // Response ready. This signal indicates that the master can accept a write response. + // + // AXI Read Address Channel + output wire [ 0:0] m_axi_arid, // Read address ID. This signal is the identification tag for the read address group of signals. + output wire [ MEM_ADDR_W-1:0] m_axi_araddr, // Read address. The read address gives the address of the first transfer in a read burst. + output wire [ 7:0] m_axi_arlen, // Burst length. This signal indicates the exact number of transfers in a burst. + output wire [ 2:0] m_axi_arsize, // Burst size. This signal indicates the size of each transfer in the burst. + output wire [ 1:0] m_axi_arburst, // Burst type. The burst type and the size information determine how the address for each transfer. + output wire [ 0:0] m_axi_arlock, // Lock type. This signal provides additional information about the atomic characteristics. + output wire [ 3:0] m_axi_arcache, // Memory type. This signal indicates how transactions are required to progress. + output wire [ 2:0] m_axi_arprot, // Protection type. This signal indicates the privilege and security level of the transaction. + output wire [ 3:0] m_axi_arqos, // Quality of Service, QoS. QoS identifier sent for each read transaction. + output wire [ 3:0] m_axi_arregion, // Region identifier. Permits a single physical interface on a slave to be re-used. + output wire [ 0:0] m_axi_aruser, // User signal. Optional User-defined signal in the read address channel. + output wire m_axi_arvalid, // Read address valid. This signal indicates that the channel is signaling valid read addr. + input wire m_axi_arready, // Read address ready. This signal indicates that the slave is ready to accept an address. + // + // AXI Read Data Channel + input wire [ 0:0] m_axi_rid, // Read ID tag. This signal is the identification tag for the read data group of signals. + input wire [ MEM_DATA_W-1:0] m_axi_rdata, // Read data. + input wire [ 1:0] m_axi_rresp, // Read response. This signal indicates the status of the read transfer. + input wire m_axi_rlast, // Read last. This signal indicates the last transfer in a read burst. + input wire [ 0:0] m_axi_ruser, // User signal. Optional User-defined signal in the read data channel. + input wire m_axi_rvalid, // Read valid. This signal indicates that the channel is signaling the required read data. + output wire m_axi_rready // Read ready. This signal indicates that the master can accept the read data and response. +); + + `include "axi_ram_fifo_regs.vh" + + + //--------------------------------------------------------------------------- + // Parameter Checking + //--------------------------------------------------------------------------- + + // The input FIFO size must be at least 9 so that there is enough space to + // hold an entire burst and be able to accept new data while that burst is + // waiting to be ready out. + if (IN_FIFO_SIZE < 9) begin + IN_FIFO_SIZE_must_be_at_least_9(); + end + + // The output FIFO size must be at least 9 so that there is enough space to + // accept a full AXI4 burst (255 words) and then accept additional bursts + // while the FIFO is waiting to be read out. + if (OUT_FIFO_SIZE < 9) begin + OUT_FIFO_SIZE_must_be_at_least_9(); + end + + // The memory must be at least as big as the default FIFO mask + if (2.0**MEM_ADDR_W < FIFO_ADDR_MASK+1) begin + MEM_ADDR_W_must_be_larger_than_size_indicated_by_FIFO_ADDR_MASK(); + end + + // The FIFO memory must be large enough for a full AXI4 burst + 64 words + // that's allocated to allow for read/write reordering. + // TODO: Is the 64-word extra needed? Why 64? + // + // Min size allowed for memory region in bytes + localparam FIFO_MIN_RAM_SIZE = (256+64) * (MEM_DATA_W/8); + // + // Equivalent mask + localparam FIFO_ADDR_MASK_MIN = 2**($clog2(FIFO_MIN_RAM_SIZE))-1; + // + // Check the parameter + if (FIFO_ADDR_MASK < FIFO_ADDR_MASK_MIN) begin + FIFO_ADDR_MASK_must_be_at_least_256_plus_64_words(); + end + + // The 4 KiB page-crossing detection logic assumes that the memory is more + // than 4 kiB in size. This could be fixed in the code, but 8 KiB is already + // pretty small for an external memory. + if (2.0**MEM_ADDR_W < 8192) begin + MEM_ADDR_W_must_be_at_least_8_KiB(); + end + + // Make sure the default burst timeout is not too big for the register + if ($clog2(BURST_TIMEOUT+1) > REG_TIMEOUT_W) begin + BURST_TIMEOUT_must_not_exceed_the_range_of_REG_TIMEOUT_W(); + end + + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Width of the timeout counter + localparam TIMEOUT_W = REG_TIMEOUT_W; + + // Address widths. Each memory byte address can be broken up into the word + // address portion (the upper bits) and the byte address portion (lower + // bits). Although the memory is byte addressable, we only read/write whole + // words. + localparam BYTE_ADDR_W = $clog2(MEM_DATA_W/8); + localparam WORD_ADDR_W = MEM_ADDR_W - BYTE_ADDR_W; + + + //--------------------------------------------------------------------------- + // Functions + //--------------------------------------------------------------------------- + + function automatic integer min(input integer a, b); + min = a < b ? a : b; + endfunction + + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + // Track RAM FIFO state, in number of words + reg [WORD_ADDR_W:0] space; + reg [WORD_ADDR_W:0] occupied; + reg [WORD_ADDR_W:0] occupied_minus_one; // Maintain a -1 version to break critical timing paths + + reg [31:0] out_pkt_count = 0; + + // + // Input Side + // + reg [MEM_DATA_W-1:0] s_tdata_fifo; + reg s_tvalid_fifo; + wire s_tready_fifo; + + wire [MEM_DATA_W-1:0] m_tdata_fifo; + wire m_tvalid_fifo; + reg m_tready_fifo; + + wire [MEM_DATA_W-1:0] s_tdata_i1; + wire [ KEEP_W-1:0] s_tkeep_i1; + wire s_tvalid_i1, s_tready_i1, s_tlast_i1; + + wire [MEM_DATA_W-1:0] s_tdata_i2; + wire s_tvalid_i2, s_tready_i2; + + wire [MEM_DATA_W-1:0] s_tdata_i3; + wire s_tvalid_i3; + reg s_tready_i3; + + wire [MEM_DATA_W-1:0] s_tdata_input; + wire s_tvalid_input, s_tready_input; + + wire [15:0] space_input, occupied_input; + reg [15:0] space_input_reg; + reg suppress_reads; + + // + // Output Side + // + wire [MEM_DATA_W-1:0] m_tdata_output; + wire m_tvalid_output, m_tready_output; + + reg [MEM_DATA_W-1:0] m_tdata_i0; + reg m_tvalid_i0; + wire m_tready_i0; + + wire [MEM_DATA_W-1:0] m_tdata_i1; + wire m_tvalid_i1, m_tready_i1; + + wire [MEM_DATA_W-1:0] m_tdata_i2; + wire m_tvalid_i2, m_tready_i2; + + wire [MEM_DATA_W-1:0] m_tdata_i3; + wire [ KEEP_W-1:0] m_tkeep_i3; + wire m_tvalid_i3, m_tready_i3, m_tlast_i3; + + wire [15:0] space_output; + + + //--------------------------------------------------------------------------- + // Registers + //--------------------------------------------------------------------------- + + wire [ 15:0] set_suppress_threshold; + wire [ TIMEOUT_W-1:0] set_timeout; + wire set_clear = 1'b0; // Clear no longer needed in RFNoC + wire [MEM_ADDR_W-1:0] set_fifo_addr_base; + wire [MEM_ADDR_W-1:0] set_fifo_addr_mask; + + wire s_ctrlport_resp_ack_regs; + wire [31:0] s_ctrlport_resp_data_regs; + + axi_ram_fifo_regs #( + .MEM_ADDR_W (MEM_ADDR_W), + .MEM_DATA_W (MEM_DATA_W), + .FIFO_ADDR_BASE (FIFO_ADDR_BASE), + .FIFO_ADDR_MASK (FIFO_ADDR_MASK), + .FIFO_ADDR_MASK_MIN (FIFO_ADDR_MASK_MIN), + .BIST (BIST), + .IN_FIFO_SIZE (IN_FIFO_SIZE), + .WORD_ADDR_W (WORD_ADDR_W), + .BURST_TIMEOUT (BURST_TIMEOUT), + .TIMEOUT_W (TIMEOUT_W) + ) axi_ram_fifo_regs_i ( + .clk (clk), + .rst (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_resp_ack (s_ctrlport_resp_ack_regs), + .s_ctrlport_resp_data (s_ctrlport_resp_data_regs), + .rb_out_pkt_count (out_pkt_count), + .rb_occupied (occupied), + .set_suppress_threshold (set_suppress_threshold), + .set_timeout (set_timeout), + .set_fifo_addr_base (set_fifo_addr_base), + .set_fifo_addr_mask (set_fifo_addr_mask) + ); + + //synthesis translate_off + // Check the address mask at run-time + always @(set_fifo_addr_mask) begin + if (set_fifo_addr_mask < FIFO_ADDR_MASK_MIN) begin + $display("ERROR: set_fifo_addr_mask was set too small!"); + end + if (2**$clog2(set_fifo_addr_mask)-1 != set_fifo_addr_mask) begin + $display("ERROR: set_fifo_addr_mask must be a power of 2, minus 1!"); + end + end + //synthesis translate_on + + + //--------------------------------------------------------------------------- + // BIST for production testing + //--------------------------------------------------------------------------- + + if (BIST) begin : gen_bist + wire s_ctrlport_resp_ack_bist; + wire [ 31:0] s_ctrlport_resp_data_bist; + wire [MEM_DATA_W-1:0] m_tdata_bist; + wire m_tvalid_bist; + reg m_tready_bist; + reg [MEM_DATA_W-1:0] s_tdata_bist; + reg s_tvalid_bist; + wire s_tready_bist; + + wire bist_running; + + axi_ram_fifo_bist #( + .DATA_W (MEM_DATA_W), + .COUNT_W (48), + .CLK_RATE (CLK_RATE), + .RAND (1) + ) axi_ram_fifo_bist_i ( + .clk (clk), + .rst (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_resp_ack (s_ctrlport_resp_ack_bist), + .s_ctrlport_resp_data (s_ctrlport_resp_data_bist), + .m_tdata (m_tdata_bist), + .m_tvalid (m_tvalid_bist), + .m_tready (m_tready_bist), + .s_tdata (s_tdata_bist), + .s_tvalid (s_tvalid_bist), + .s_tready (s_tready_bist), + .running (bist_running) + ); + + // Use a multiplexer to decide where the data flows, using the BIST when + // ever the BIST is running. + always @(*) begin + if (bist_running) begin + // Insert the BIST logic + s_tdata_fifo = m_tdata_bist; + s_tvalid_fifo = m_tvalid_bist; + m_tready_bist = s_tready_fifo; + // + s_tdata_bist = m_tdata_fifo; + s_tvalid_bist = m_tvalid_fifo; + m_tready_fifo = s_tready_bist; + + // Disable output-logic + s_tready_i3 = 0; + m_tdata_i0 = m_tdata_fifo; + m_tvalid_i0 = 0; + end else begin + // Disable BIST + m_tready_bist = 0; + s_tdata_bist = m_tdata_fifo; + s_tvalid_bist = 0; + + // Bypass BIST + s_tdata_fifo = s_tdata_i3; + s_tvalid_fifo = s_tvalid_i3; + s_tready_i3 = s_tready_fifo; + // + m_tdata_i0 = m_tdata_fifo; + m_tvalid_i0 = m_tvalid_fifo; + m_tready_fifo = m_tready_i0; + end + end + + // Combine register responses + ctrlport_resp_combine #( + .NUM_SLAVES (2) + ) ctrlport_resp_combine_i ( + .ctrlport_clk (clk), + .ctrlport_rst (rst), + .m_ctrlport_resp_ack ({s_ctrlport_resp_ack_bist, s_ctrlport_resp_ack_regs}), + .m_ctrlport_resp_status ({2{2'b00}}), + .m_ctrlport_resp_data ({s_ctrlport_resp_data_bist, s_ctrlport_resp_data_regs}), + .s_ctrlport_resp_ack (s_ctrlport_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (s_ctrlport_resp_data) + ); + + end else begin : gen_no_bist + assign s_ctrlport_resp_ack = s_ctrlport_resp_ack_regs; + assign s_ctrlport_resp_data = s_ctrlport_resp_data_regs; + always @(*) begin + // Bypass the BIST logic + s_tdata_fifo = s_tdata_i3; + s_tvalid_fifo = s_tvalid_i3; + s_tready_i3 = s_tready_fifo; + // + m_tdata_i0 = m_tdata_fifo; + m_tvalid_i0 = m_tvalid_fifo; + m_tready_fifo = m_tready_i0; + // + end + end + + + //--------------------------------------------------------------------------- + // Input Handling and Buffer + //--------------------------------------------------------------------------- + // + // This block embeds TLAST into the data stream using an escape code and + // buffers up input data. + // + //--------------------------------------------------------------------------- + + // Insert flops to improve timing + axi_fifo_flop2 #( + .WIDTH (MEM_DATA_W+1+KEEP_W) + ) input_pipe_i0 ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata ({s_tkeep, s_tlast, s_tdata}), + .i_tvalid (s_tvalid), + .i_tready (s_tready), + // + .o_tdata ({s_tkeep_i1, s_tlast_i1, s_tdata_i1}), + .o_tvalid (s_tvalid_i1), + .o_tready (s_tready_i1), + // + .space (), + .occupied () + ); + + axi_embed_tlast_tkeep #( + .DATA_W (MEM_DATA_W), + .KEEP_W (KEEP_W) + ) axi_embed_tlast_tkeep_i ( + .clk (clk), + .rst (rst | set_clear), + // + .i_tdata (s_tdata_i1), + .i_tkeep (s_tkeep_i1), + .i_tlast (s_tlast_i1), + .i_tvalid (s_tvalid_i1), + .i_tready (s_tready_i1), + // + .o_tdata (s_tdata_i2), + .o_tvalid (s_tvalid_i2), + .o_tready (s_tready_i2) + ); + + // Insert flops to improve timing + axi_fifo_flop2 #( + .WIDTH (MEM_DATA_W) + ) input_pipe_i1 ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata (s_tdata_i2), + .i_tvalid (s_tvalid_i2), + .i_tready (s_tready_i2), + // + .o_tdata (s_tdata_i3), + .o_tvalid (s_tvalid_i3), + .o_tready (s_tready_i3), + // + .space (), + .occupied () + ); + + axi_fifo #( + .WIDTH (MEM_DATA_W), + .SIZE (IN_FIFO_SIZE) + ) input_fifo ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata (s_tdata_fifo), + .i_tvalid (s_tvalid_fifo), + .i_tready (s_tready_fifo), + // + .o_tdata (s_tdata_input), + .o_tvalid (s_tvalid_input), + .o_tready (s_tready_input), + // + .space (space_input), + .occupied (occupied_input) + ); + + + //--------------------------------------------------------------------------- + // Input (Memory Write) Logic + //--------------------------------------------------------------------------- + // + // The input state machine waits for enough entries in input FIFO to trigger + // RAM write burst. A timeout can also trigger a burst so that smaller chunks + // of data are not left to rot in the input FIFO. Also, if enough data is + // present in the input FIFO to complete a burst up to the edge of a 4 KiB + // page then we do a burst up to the 4 KiB boundary. + // + //--------------------------------------------------------------------------- + + // + // Input side declarations + // + localparam [2:0] INPUT_IDLE = 0; + localparam [2:0] INPUT1 = 1; + localparam [2:0] INPUT2 = 2; + localparam [2:0] INPUT3 = 3; + localparam [2:0] INPUT4 = 4; + localparam [2:0] INPUT5 = 5; + localparam [2:0] INPUT6 = 6; + + wire write_ctrl_ready; + + reg [ 2:0] input_state; + reg input_timeout_triggered; + reg input_timeout_reset; + reg [ TIMEOUT_W-1:0] input_timeout_count; + reg [MEM_ADDR_W-1:0] write_addr; + reg write_ctrl_valid; + reg [ 7:0] write_count = 0; + reg [ 8:0] write_count_plus_one = 1; // Maintain a +1 version to break critical timing paths + reg update_write; + + reg [WORD_ADDR_W-1:0] input_page_boundary; + + // + // Input timeout counter. Timeout count only increments when there is some + // data waiting to be written to the RAM. + // + always @(posedge clk) begin + if (rst | set_clear) begin + input_timeout_count <= 0; + input_timeout_triggered <= 0; + end else if (input_timeout_reset) begin + input_timeout_count <= 0; + input_timeout_triggered <= 0; + end else if (input_timeout_count == set_timeout) begin + input_timeout_triggered <= 1; + end else if (input_state == INPUT_IDLE) begin + input_timeout_count <= input_timeout_count + ((occupied_input != 0) ? 1 : 0); + end + end + + // + // Input State Machine + // + always @(posedge clk) + if (rst | set_clear) begin + input_state <= INPUT_IDLE; + write_addr <= set_fifo_addr_base & ~set_fifo_addr_mask; + input_timeout_reset <= 1'b0; + write_ctrl_valid <= 1'b0; + write_count <= 8'd0; + write_count_plus_one <= 9'd1; + update_write <= 1'b0; + end else begin + case (input_state) + // + // INPUT_IDLE. + // To start an input transfer to DRAM need: + // 1) Space in the RAM + // and either + // 2) 256 entries in the input FIFO + // or + // 3) Timeout occurred while waiting for more data, which can only happen + // if there's at least one word in the input FIFO). + // + INPUT_IDLE: begin + write_ctrl_valid <= 1'b0; + update_write <= 1'b0; + input_timeout_reset <= 1'b0; + if (space[WORD_ADDR_W:8] != 'd0) begin // (space > 255): 256 or more slots in the RAM + if (occupied_input[15:8] != 'd0) begin // (occupied_input > 255): 256 or more words in input FIFO + input_state <= INPUT1; + input_timeout_reset <= 1'b1; + + // Calculate the number of entries remaining until next 4 KiB page + // boundary is crossed, minus 1. The units of calculation are + // words. The address is always word aligned. + input_page_boundary <= { write_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } - + write_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; + end else if (input_timeout_triggered) begin // input FIFO timeout waiting for new data. + input_state <= INPUT2; + input_timeout_reset <= 1'b1; + // Calculate the number of entries remaining until next 4 KiB page + // boundary is crossed, minus 1. The units of calculation are + // words. The address is always word-aligned. + input_page_boundary <= { write_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } - + write_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; + end + end + end + // + // INPUT1. + // Caused by input FIFO reaching 256 entries. + // Request write burst of lesser of: + // 1) Entries until page boundary crossed + // 2) 256 + // + INPUT1: begin + // Replicated write logic to break a read timing critical path for + // write_count. + write_count <= input_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ? + input_page_boundary[7:0] : + 255; + write_count_plus_one <= input_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ? + input_page_boundary[7:0] + 1 : + 256; + write_ctrl_valid <= 1'b1; + if (write_ctrl_ready) + input_state <= INPUT4; // Preemptive ACK + else + input_state <= INPUT3; // Wait for ACK + end + // + // INPUT2. + // Caused by timeout of input FIFO (occupied_input must now be 256 or + // less since it was 255 or less in the INPUT_IDLE state; otherwise we + // would have gone to INPUT1). Request write burst of lesser of: + // 1) Entries until page boundary crossed + // 2) Entries in input FIFO + // + INPUT2: begin + // Replicated write logic to break a read timing critical path for + // write_count. + write_count <= input_page_boundary < occupied_input[8:0] - 1 ? + input_page_boundary[7:0] : + occupied_input[8:0] - 1; // Max result of 255 + write_count_plus_one <= input_page_boundary < occupied_input[8:0] - 1 ? + input_page_boundary[7:0] + 1 : + occupied_input[8:0]; + write_ctrl_valid <= 1'b1; + if (write_ctrl_ready) + input_state <= INPUT4; // Preemptive ACK + else + input_state <= INPUT3; // Wait for ACK + end + // + // INPUT3. + // Wait in this state for AXI4 DMA engine to accept transaction. + // + INPUT3: begin + if (write_ctrl_ready) begin + write_ctrl_valid <= 1'b0; + input_state <= INPUT4; // ACK + end else begin + write_ctrl_valid <= 1'b1; + input_state <= INPUT3; // Wait for ACK + end + end + // + // INPUT4. + // Wait here until write_ctrl_ready_deasserts. This is important as the + // next time it asserts we know that a write response was received. + INPUT4: begin + write_ctrl_valid <= 1'b0; + if (!write_ctrl_ready) + input_state <= INPUT5; // Move on + else + input_state <= INPUT4; // Wait for deassert + end + // + // INPUT5. + // Transaction has been accepted by AXI4 DMA engine. Now we wait for the + // re-assertion of write_ctrl_ready which signals that the AXI4 DMA + // engine has received a response for the whole write transaction and we + // assume that this means it is committed to DRAM. We are now free to + // update write_addr pointer and go back to idle state. + // + INPUT5: begin + write_ctrl_valid <= 1'b0; + if (write_ctrl_ready) begin + write_addr <= ((write_addr + (write_count_plus_one << $clog2(MEM_DATA_W/8))) & set_fifo_addr_mask) | (write_addr & ~set_fifo_addr_mask); + input_state <= INPUT6; + update_write <= 1'b1; + end else begin + input_state <= INPUT5; + end + end + // + // INPUT6: + // Need to let space update before looking if there's more to do. + // + INPUT6: begin + input_state <= INPUT_IDLE; + update_write <= 1'b0; + end + + default: + input_state <= INPUT_IDLE; + endcase // case(input_state) + end + + + //--------------------------------------------------------------------------- + // Read Suppression Logic + //--------------------------------------------------------------------------- + // + // Monitor occupied_input to deduce when DRAM FIFO is running short of + // bandwidth and there is a danger of back-pressure passing upstream of the + // DRAM FIFO. In this situation, we suppress read requests to the DRAM FIFO + // so that more bandwidth is available to writes. + // + // However, not reading can actually cause the FIFO to fill up and stall, so + // if the input is stalled, allow switching back to reads. This allows the + // memory to fill up without causing deadlock. + // + //--------------------------------------------------------------------------- + + reg input_idle, input_idle_d1, input_stalled; + + always @(posedge clk) begin + // We consider the input to be stalled when the input state machine is idle + // for 2 or more clock cycles. + input_idle <= (input_state == INPUT_IDLE); + input_idle_d1 <= input_idle; + input_stalled <= input_idle && input_idle_d1; + + space_input_reg <= space_input; + if (space_input_reg < set_suppress_threshold && !input_stalled) + suppress_reads <= 1'b1; + else + suppress_reads <= 1'b0; + end + + + //--------------------------------------------------------------------------- + // Output Handling and Buffer + //--------------------------------------------------------------------------- + // + // This block buffers output data and extracts the TLAS signal that was + // embedded into the data stream. + // + //--------------------------------------------------------------------------- + + // Large FIFO to buffer data read from DRAM. This FIFO must be large enough + // to accept a full burst read. + axi_fifo #( + .WIDTH (MEM_DATA_W), + .SIZE (OUT_FIFO_SIZE) + ) output_fifo ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata (m_tdata_output), + .i_tvalid (m_tvalid_output), + .i_tready (m_tready_output), + // + .o_tdata (m_tdata_fifo), + .o_tvalid (m_tvalid_fifo), + .o_tready (m_tready_fifo), + // + .space (space_output), + .occupied () + ); + + // Place flops right after FIFO to improve timing + axi_fifo_flop2 #( + .WIDTH (MEM_DATA_W) + ) output_pipe_i0 ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata (m_tdata_i0), + .i_tvalid (m_tvalid_i0), + .i_tready (m_tready_i0), + // + .o_tdata (m_tdata_i1), + .o_tvalid (m_tvalid_i1), + .o_tready (m_tready_i1), + // + .space (), + .occupied () + ); + + // Pipeline flop before TLAST extraction logic + axi_fifo_flop2 #( + .WIDTH (MEM_DATA_W) + ) output_pipe_i1 ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata (m_tdata_i1), + .i_tvalid (m_tvalid_i1), + .i_tready (m_tready_i1), + // + .o_tdata (m_tdata_i2), + .o_tvalid (m_tvalid_i2), + .o_tready (m_tready_i2), + // + .space (), + .occupied () + ); + + axi_extract_tlast_tkeep #( + .DATA_W (MEM_DATA_W), + .KEEP_W (KEEP_W) + ) axi_extract_tlast_tkeep_i ( + .clk (clk), + .rst (rst | set_clear), + // + .i_tdata (m_tdata_i2), + .i_tvalid (m_tvalid_i2), + .i_tready (m_tready_i2), + // + .o_tdata (m_tdata_i3), + .o_tkeep (m_tkeep_i3), + .o_tlast (m_tlast_i3), + .o_tvalid (m_tvalid_i3), + .o_tready (m_tready_i3) + ); + + // Pipeline flop after TLAST extraction logic + axi_fifo_flop2 #( + .WIDTH (MEM_DATA_W+1+KEEP_W) + ) output_pipe_i3 ( + .clk (clk), + .reset (rst), + .clear (set_clear), + // + .i_tdata ({m_tkeep_i3, m_tlast_i3, m_tdata_i3}), + .i_tvalid (m_tvalid_i3), + .i_tready (m_tready_i3), + // + .o_tdata ({m_tkeep, m_tlast, m_tdata}), + .o_tvalid (m_tvalid), + .o_tready (m_tready), + // + .space (), + .occupied () + ); + + + //------------------------------------------------------------------------- + // Output (Memory Read) Logic + //------------------------------------------------------------------------- + // + // The output state machine Wait for enough entries in RAM to trigger read + // burst. A timeout can also trigger a burst so that smaller chunks of data + // are not left to rot in the RAM. Also, if enough data is present in the RAM + // to complete a burst up to the edge of a 4 KiB page boundary then we do a + // burst up to the 4 KiB boundary. + // + //--------------------------------------------------------------------------- + + // + // Output side declarations + // + localparam [2:0] OUTPUT_IDLE = 0; + localparam [2:0] OUTPUT1 = 1; + localparam [2:0] OUTPUT2 = 2; + localparam [2:0] OUTPUT3 = 3; + localparam [2:0] OUTPUT4 = 4; + localparam [2:0] OUTPUT5 = 5; + localparam [2:0] OUTPUT6 = 6; + + reg [ 2:0] output_state; + reg output_timeout_triggered; + reg output_timeout_reset; + reg [ TIMEOUT_W-1:0] output_timeout_count; + reg [MEM_ADDR_W-1:0] read_addr; + reg read_ctrl_valid; + wire read_ctrl_ready; + reg [ 7:0] read_count = 0; + reg [ 8:0] read_count_plus_one = 1; // Maintain a +1 version to break critical timing paths + reg update_read; + + reg [WORD_ADDR_W-1:0] output_page_boundary; // Cache in a register to break critical timing paths + + // + // Output Packet Counter + // + always @(posedge clk) begin + if (rst) begin + out_pkt_count <= 0; + end else if (m_tlast & m_tvalid & m_tready) begin + out_pkt_count <= out_pkt_count + 1; + end + end + + // + // Output timeout counter. Timeout count only increments when there is some + // data waiting to be read from the RAM. + // + always @(posedge clk) begin + if (rst | set_clear) begin + output_timeout_count <= 0; + output_timeout_triggered <= 0; + end else if (output_timeout_reset) begin + output_timeout_count <= 0; + output_timeout_triggered <= 0; + end else if (output_timeout_count == set_timeout) begin + output_timeout_triggered <= 1; + end else if (output_state == OUTPUT_IDLE) begin + output_timeout_count <= output_timeout_count + ((occupied != 0) ? 1 : 0); + end + end + + // + // Output State Machine + // + always @(posedge clk) + if (rst | set_clear) begin + output_state <= OUTPUT_IDLE; + read_addr <= set_fifo_addr_base & ~set_fifo_addr_mask; + output_timeout_reset <= 1'b0; + read_ctrl_valid <= 1'b0; + read_count <= 8'd0; + read_count_plus_one <= 9'd1; + update_read <= 1'b0; + end else begin + case (output_state) + // + // OUTPUT_IDLE. + // To start an output transfer from DRAM + // 1) Space in the output FIFO + // and either + // 2) 256 entries in the RAM + // or + // 3) Timeout occurred while waiting for more data, which can only happen + // if there's at least one word in the RAM. + // + OUTPUT_IDLE: begin + read_ctrl_valid <= 1'b0; + update_read <= 1'b0; + output_timeout_reset <= 1'b0; + if (space_output[15:8] != 'd0 && !suppress_reads) begin // (space_output > 255): 256 or more words in the output FIFO + if (occupied[WORD_ADDR_W:8] != 'd0) begin // (occupied > 255): 256 or more words in RAM + output_state <= OUTPUT1; + output_timeout_reset <= 1'b1; + + // Calculate the number of entries remaining until next 4 KiB page + // boundary is crossed, minus 1. The units of calculation are + // words. The address is always word-aligned. + output_page_boundary <= { read_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } - + read_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; + end else if (output_timeout_triggered) begin // output FIFO timeout waiting for new data. + output_state <= OUTPUT2; + output_timeout_reset <= 1'b1; + // Calculate the number of entries remaining until next 4 KiB page + // boundary is crossed, minus 1. The units of calculation are + // words. The address is always word-aligned. + output_page_boundary <= { read_addr[MEM_ADDR_W-1:12], {12-BYTE_ADDR_W{1'b1}} } - + read_addr[MEM_ADDR_W-1 : BYTE_ADDR_W]; + end + end + end + // + // OUTPUT1. + // Caused by RAM FIFO reaching 256 entries. + // Request read burst of lesser of lesser of: + // 1) Entries until page boundary crossed + // 2) 256 + // + OUTPUT1: begin + // Replicated write logic to break a read timing critical path for read_count + read_count <= output_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ? + output_page_boundary[7:0] : + 255; + read_count_plus_one <= output_page_boundary[min(12, WORD_ADDR_W)-1:8] == 0 ? + output_page_boundary[7:0] + 1 : + 256; + read_ctrl_valid <= 1'b1; + if (read_ctrl_ready) + output_state <= OUTPUT4; // Preemptive ACK + else + output_state <= OUTPUT3; // Wait for ACK + end + // + // OUTPUT2. + // Caused by timeout of main FIFO + // Request read burst of lesser of: + // 1) Entries until page boundary crossed + // 2) Entries in main FIFO + // + OUTPUT2: begin + // Replicated write logic to break a read timing critical path for read_count + read_count <= output_page_boundary < occupied_minus_one ? + output_page_boundary[7:0] : + occupied_minus_one[7:0]; + read_count_plus_one <= output_page_boundary < occupied_minus_one ? + output_page_boundary[7:0] + 1 : + occupied[7:0]; + read_ctrl_valid <= 1'b1; + if (read_ctrl_ready) + output_state <= OUTPUT4; // Preemptive ACK + else + output_state <= OUTPUT3; // Wait for ACK + end + // + // OUTPUT3. + // Wait in this state for AXI4 DMA engine to accept transaction. + // + OUTPUT3: begin + if (read_ctrl_ready) begin + read_ctrl_valid <= 1'b0; + output_state <= OUTPUT4; // ACK + end else begin + read_ctrl_valid <= 1'b1; + output_state <= OUTPUT3; // Wait for ACK + end + end + // + // OUTPUT4. + // Wait here until read_ctrl_ready_deasserts. This is important as the + // next time it asserts we know that a read response was received. + OUTPUT4: begin + read_ctrl_valid <= 1'b0; + if (!read_ctrl_ready) + output_state <= OUTPUT5; // Move on + else + output_state <= OUTPUT4; // Wait for deassert + end + // + // OUTPUT5. + // Transaction has been accepted by AXI4 DMA engine. Now we wait for the + // re-assertion of read_ctrl_ready which signals that the AXI4 DMA engine + // has received a last signal and good response for the whole read + // transaction. We are now free to update read_addr pointer and go back + // to idle state. + // + OUTPUT5: begin + read_ctrl_valid <= 1'b0; + if (read_ctrl_ready) begin + read_addr <= ((read_addr + (read_count_plus_one << $clog2(MEM_DATA_W/8))) & set_fifo_addr_mask) | (read_addr & ~set_fifo_addr_mask); + output_state <= OUTPUT6; + update_read <= 1'b1; + end else begin + output_state <= OUTPUT5; + end + end + // + // OUTPUT6. + // Need to get occupied value updated before checking if there's more to do. + // + OUTPUT6: begin + update_read <= 1'b0; + output_state <= OUTPUT_IDLE; + end + + default: + output_state <= OUTPUT_IDLE; + endcase // case(output_state) + end + + + //--------------------------------------------------------------------------- + // Shared Read/Write Logic + //--------------------------------------------------------------------------- + + // + // Count number of words stored in the RAM FIFO. + // + always @(posedge clk) begin + if (rst | set_clear) begin + occupied <= 0; + occupied_minus_one <= -1; + end else begin + occupied <= occupied + (update_write ? write_count_plus_one : 0) - (update_read ? read_count_plus_one : 0); + occupied_minus_one <= occupied_minus_one + (update_write ? write_count_plus_one : 0) - (update_read ? read_count_plus_one : 0); + end + end + + // + // Count amount of space in the RAM FIFO, in words. + // + always @(posedge clk) begin + if (rst | set_clear) begin + // Set to the FIFO size minus 64 words to make allowance for read/write + // reordering in DRAM controller. + // TODO: Is the 64-word extra needed? Why 64? + space <= set_fifo_addr_mask[MEM_ADDR_W-1 -: WORD_ADDR_W] & ~('d63); + end else begin + space <= space - (update_write ? write_count_plus_one : 0) + (update_read ? read_count_plus_one : 0); + end + end + + + //--------------------------------------------------------------------------- + // AXI4 DMA Master + //--------------------------------------------------------------------------- + + axi_dma_master #( + .AWIDTH (MEM_ADDR_W), + .DWIDTH (MEM_DATA_W) + ) axi_dma_master_i ( + .aclk (clk), + .areset (rst | set_clear), + // Write Address + .m_axi_awid (m_axi_awid), + .m_axi_awaddr (m_axi_awaddr), + .m_axi_awlen (m_axi_awlen), + .m_axi_awsize (m_axi_awsize), + .m_axi_awburst (m_axi_awburst), + .m_axi_awvalid (m_axi_awvalid), + .m_axi_awready (m_axi_awready), + .m_axi_awlock (m_axi_awlock), + .m_axi_awcache (m_axi_awcache), + .m_axi_awprot (m_axi_awprot), + .m_axi_awqos (m_axi_awqos), + .m_axi_awregion (m_axi_awregion), + .m_axi_awuser (m_axi_awuser), + // Write Data + .m_axi_wdata (m_axi_wdata), + .m_axi_wstrb (m_axi_wstrb), + .m_axi_wlast (m_axi_wlast), + .m_axi_wvalid (m_axi_wvalid), + .m_axi_wready (m_axi_wready), + .m_axi_wuser (m_axi_wuser), + // Write Response + .m_axi_bid (m_axi_bid), + .m_axi_bresp (m_axi_bresp), + .m_axi_bvalid (m_axi_bvalid), + .m_axi_bready (m_axi_bready), + .m_axi_buser (m_axi_buser), + // Read Address + .m_axi_arid (m_axi_arid), + .m_axi_araddr (m_axi_araddr), + .m_axi_arlen (m_axi_arlen), + .m_axi_arsize (m_axi_arsize), + .m_axi_arburst (m_axi_arburst), + .m_axi_arvalid (m_axi_arvalid), + .m_axi_arready (m_axi_arready), + .m_axi_arlock (m_axi_arlock), + .m_axi_arcache (m_axi_arcache), + .m_axi_arprot (m_axi_arprot), + .m_axi_arqos (m_axi_arqos), + .m_axi_arregion (m_axi_arregion), + .m_axi_aruser (m_axi_aruser), + // Read Data + .m_axi_rid (m_axi_rid), + .m_axi_rdata (m_axi_rdata), + .m_axi_rresp (m_axi_rresp), + .m_axi_rlast (m_axi_rlast), + .m_axi_rvalid (m_axi_rvalid), + .m_axi_rready (m_axi_rready), + .m_axi_ruser (m_axi_ruser), + // + // DMA interface for Write transactions + // + .write_addr (write_addr), // Byte address for start of write transaction (should be 64-bit aligned) + .write_count (write_count), // Count of 64-bit words to write. + .write_ctrl_valid (write_ctrl_valid), + .write_ctrl_ready (write_ctrl_ready), + .write_data (s_tdata_input), + .write_data_valid (s_tvalid_input), + .write_data_ready (s_tready_input), + // + // DMA interface for Read transactions + // + .read_addr (read_addr), // Byte address for start of read transaction (should be 64-bit aligned) + .read_count (read_count), // Count of 64-bit words to read. + .read_ctrl_valid (read_ctrl_valid), + .read_ctrl_ready (read_ctrl_ready), + .read_data (m_tdata_output), + .read_data_valid (m_tvalid_output), + .read_data_ready (m_tready_output), + // + // Debug + // + .debug () + ); + +endmodule + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist.v new file mode 100644 index 000000000..2dd3f99d3 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist.v @@ -0,0 +1,294 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo_bist +// +// Description: +// +// Implements a built-in self test for the RAM FIFO. It can generate random +// or sequential data that it outputs as quickly as possible. The output of +// the RAM is verified to make sure that it matches what was input to the RAM. +// +// Parameters: +// +// DATA_W : The width of the data port to use for the AXI4-Stream interface +// +// COUNT_W : Width of internal counters. This must be wide enough so that +// word, cycle, and and error counters don't overflow during a +// test. +// +// CLK_RATE : The frequency of clk in Hz +// +// RAND : Set to 1 for random data, 0 for sequential data. +// + +module axi_ram_fifo_bist #( + parameter DATA_W = 64, + parameter COUNT_W = 48, + parameter CLK_RATE = 200e6, + parameter RAND = 1 +) ( + input clk, + input rst, + + //-------------------------------------------------------------------------- + // CTRL Port + //-------------------------------------------------------------------------- + + 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, + + //-------------------------------------------------------------------------- + // AXI-Stream Interface + //-------------------------------------------------------------------------- + + // Output to RAM FIFO + output wire [DATA_W-1:0] m_tdata, + output reg m_tvalid, + input wire m_tready, + + // Input from RAM FIFO + input wire [DATA_W-1:0] s_tdata, + input wire s_tvalid, + output wire s_tready, + + //--------------------------------------------------------------------------- + // Status + //--------------------------------------------------------------------------- + + output reg running + +); + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Internal word size to use for data generation. The output word will be a + // multiple of this size. + localparam WORD_W = 32; + + // Random number seed (must not be 0) + localparam [WORD_W-1:0] SEED = 'h012345678; + + // Test data reset value + localparam [WORD_W-1:0] INIT = RAND ? SEED : 0; + + + //--------------------------------------------------------------------------- + // Assertions + //--------------------------------------------------------------------------- + + if (DATA_W % WORD_W != 0) begin + DATA_W_must_be_a_multiple_of_WORD_W(); + end + + // LFSR only supports 8, 16, and 32 bits + if (WORD_W != 32 && WORD_W != 16 && WORD_W != 8) begin + WORD_W_not_supported(); + end + + //--------------------------------------------------------------------------- + // Functions + //--------------------------------------------------------------------------- + + // Linear-feedback Shift Register for random number generation. + function [WORD_W-1:0] lfsr(input [WORD_W-1:0] din); + reg new_bit; + begin + case (WORD_W) + 8 : new_bit = din[7] ^ din[5] ^ din[4] ^ din[3]; + 16 : new_bit = din[15] ^ din[14] ^ din[12] ^ din[3]; + 32 : new_bit = din[31] ^ din[21] ^ din[1] ^ din[0]; + endcase + lfsr = { din[WORD_W-2:0], new_bit }; + end + endfunction + + function [WORD_W-1:0] next(input [WORD_W-1:0] din); + next = RAND ? lfsr(din) : din + 1; + endfunction + + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + reg [COUNT_W-1:0] tx_count; // Number of words transmitted to FIFO + reg [COUNT_W-1:0] rx_count; // Number of words received back from FIFO + reg [COUNT_W-1:0] error_count; // Number of words that show errors + + reg [WORD_W-1:0] tx_data = next(INIT); // Transmitted data word + reg [DATA_W-1:0] rx_data = INIT; // Received data words + reg [WORD_W-1:0] exp_data; // Expected data word + reg rx_valid; // Received word is value (strobe) + + wire [COUNT_W-1:0] num_words; // Number of words to test + reg [COUNT_W-1:0] cycle_count; // Number of clock cycles test has been running for + wire start; // Start test + wire stop; // Stop test + wire clear; // Clear the counters + wire continuous; // Continuous test mode + + + //--------------------------------------------------------------------------- + // Registers + //--------------------------------------------------------------------------- + + axi_ram_fifo_bist_regs #( + .DATA_W (DATA_W), + .COUNT_W (COUNT_W), + .CLK_RATE (CLK_RATE) + ) axi_ram_fifo_bist_regs_i ( + .clk (clk), + .rst (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_resp_ack (s_ctrlport_resp_ack), + .s_ctrlport_resp_data (s_ctrlport_resp_data), + .tx_count (tx_count), + .rx_count (rx_count), + .error_count (error_count), + .cycle_count (cycle_count), + .num_words (num_words), + .start (start), + .stop (stop), + .clear (clear), + .continuous (continuous), + .running (running) + ); + + + //--------------------------------------------------------------------------- + // State Machine + //--------------------------------------------------------------------------- + + localparam ST_IDLE = 0; + localparam ST_ACTIVE = 1; + localparam ST_WAIT_DONE = 2; + + reg [ 1:0] state; + reg [COUNT_W-1:0] num_words_m1; + + always @(posedge clk) begin + if (rst) begin + state <= ST_IDLE; + m_tvalid <= 0; + running <= 0; + end else begin + m_tvalid <= 0; + + case (state) + ST_IDLE : begin + num_words_m1 <= num_words-1; + if (start) begin + running <= 1; + state <= ST_ACTIVE; + end + end + + ST_ACTIVE : begin + if (stop || (tx_count == num_words_m1 && m_tvalid && m_tready && !continuous)) begin + m_tvalid <= 0; + state <= ST_WAIT_DONE; + end else begin + m_tvalid <= 1; + running <= 1; + end + end + + ST_WAIT_DONE : begin + if (rx_count >= tx_count) begin + running <= 0; + state <= ST_IDLE; + end + end + endcase + end + end + + + //--------------------------------------------------------------------------- + // Data Generator + //--------------------------------------------------------------------------- + + reg count_en; + + // Output data is the concatenation of our generated test word. + assign m_tdata = {(DATA_W/WORD_W){ tx_data }}; + + // We were born ready + assign s_tready = 1; + + always @(posedge clk) begin + if (rst) begin + tx_data <= next(INIT); + exp_data <= INIT; + rx_valid <= 0; + tx_count <= 0; + rx_count <= 0; + error_count <= 0; + cycle_count <= 0; + count_en <= 0; + end else begin + // + // Output Data generation + // + if (m_tvalid && m_tready) begin + tx_data <= next(tx_data); + tx_count <= tx_count + 1; + end + + // + // Expected Data Generation + // + if (s_tvalid & s_tready) begin + rx_valid <= 1; + exp_data <= next(exp_data); + rx_count <= rx_count + 1; + rx_data <= s_tdata; + end else begin + rx_valid <= 0; + end + + // + // Data checker + // + if (rx_valid) begin + if (rx_data !== {(DATA_W/WORD_W){exp_data}}) begin + error_count <= error_count + 1; + end + end + + // + // Cycle Counter + // + // Start counting after get the first word back so that we measure + // throughput and not latency. + if (state == ST_IDLE) count_en <= 0; + else if (s_tvalid) count_en <= 1; + + if (count_en) cycle_count <= cycle_count + 1; + + // + // Clear counters upon request + // + if (clear) begin + tx_count <= 0; + rx_count <= 0; + error_count <= 0; + cycle_count <= 0; + end + end + end + +endmodule + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist_regs.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist_regs.v new file mode 100644 index 000000000..c161c10f5 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_bist_regs.v @@ -0,0 +1,206 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo_bist_regs +// +// Description: +// +// Implements the registers for the RAM FIFO BIST logic. +// +// Parameters: +// +// DATA_W : The width of the data port to use for the AXI4-Stream +// interface. +// +// COUNT_W : Width of internal counters. This must be wide enough so that +// word, cycle, and and error counters don't overflow during a +// test. +// +// CLK_RATE : The frequency of clk in Hz +// + +module axi_ram_fifo_bist_regs #( + parameter DATA_W = 64, + parameter COUNT_W = 48, + parameter CLK_RATE = 200e6 +) ( + input clk, + input rst, + + //-------------------------------------------------------------------------- + // CTRL Port + //-------------------------------------------------------------------------- + + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [19:0] s_ctrlport_req_addr, + input wire [31:0] s_ctrlport_req_data, + output reg s_ctrlport_resp_ack, + output reg [31:0] s_ctrlport_resp_data, + + //-------------------------------------------------------------------------- + // Control and Status + //-------------------------------------------------------------------------- + + input wire [COUNT_W-1:0] tx_count, + input wire [COUNT_W-1:0] rx_count, + input wire [COUNT_W-1:0] error_count, + input wire [COUNT_W-1:0] cycle_count, + + output wire [COUNT_W-1:0] num_words, + + output reg start, + output reg stop, + output reg clear, + output reg continuous, + input wire running +); + + `include "axi_ram_fifo_regs.vh" + + localparam BYTES_PER_WORD = DATA_W/8; + localparam WORD_SHIFT = $clog2(BYTES_PER_WORD); + + // Make sure DATA_W is a power of 2, or else the word/byte count conversion + // logic won't be correct. + if (2**$clog2(DATA_W) != DATA_W) begin + DATA_W_must_be_a_power_of_2(); + end + + // The register logic currently assumes that COUNT_W is at least 33 bits. + if (COUNT_W <= 32) begin + COUNT_W_must_be_larger_than_32(); + end + + wire [19:0] word_addr; + wire [63:0] tx_byte_count; + wire [63:0] rx_byte_count; + reg [63:0] num_bytes = 0; + + reg [31:0] tx_byte_count_hi = 0; + reg [31:0] rx_byte_count_hi = 0; + reg [31:0] error_count_hi = 0; + reg [31:0] cycle_count_hi = 0; + + // Only use the word address to simplify address decoding logic + assign word_addr = {s_ctrlport_req_addr[19:2], 2'b00 }; + + // Convert between words and bytes + assign tx_byte_count = tx_count << WORD_SHIFT; + assign rx_byte_count = rx_count << WORD_SHIFT; + assign num_words = num_bytes >> WORD_SHIFT; + + + always @(posedge clk) begin + if (rst) begin + s_ctrlport_resp_ack <= 0; + start <= 0; + stop <= 0; + continuous <= 0; + clear <= 0; + num_bytes <= 0; + end else begin + // Default values + s_ctrlport_resp_ack <= 0; + start <= 0; + stop <= 0; + clear <= 0; + + //----------------------------------------------------------------------- + // Read Logic + //----------------------------------------------------------------------- + + if (s_ctrlport_req_rd) begin + case (word_addr) + REG_BIST_CTRL : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[REG_BIST_RUNNING_POS] <= running; + s_ctrlport_resp_data[REG_BIST_CONT_POS] <= continuous; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_CLK_RATE : begin + s_ctrlport_resp_data <= CLK_RATE; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_NUM_BYTES_LO : begin + s_ctrlport_resp_data <= num_bytes[31:0]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_NUM_BYTES_HI : begin + s_ctrlport_resp_data <= num_bytes[63:32]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_TX_BYTE_COUNT_LO : begin + s_ctrlport_resp_data <= tx_byte_count[31:0]; + tx_byte_count_hi <= tx_byte_count[63:32]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_TX_BYTE_COUNT_HI : begin + s_ctrlport_resp_data <= tx_byte_count_hi; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_RX_BYTE_COUNT_LO : begin + s_ctrlport_resp_data <= rx_byte_count[31:0]; + rx_byte_count_hi[COUNT_W-33:0] <= rx_byte_count[COUNT_W-1:32]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_RX_BYTE_COUNT_HI : begin + s_ctrlport_resp_data <= rx_byte_count_hi; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_ERROR_COUNT_LO : begin + s_ctrlport_resp_data <= error_count[31:0]; + error_count_hi[COUNT_W-33:0] <= error_count[COUNT_W-1:32]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_ERROR_COUNT_HI : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data <= error_count_hi; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_CYCLE_COUNT_LO : begin + s_ctrlport_resp_data <= cycle_count[31:0]; + cycle_count_hi[COUNT_W-33:0] <= cycle_count[COUNT_W-1:32]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_CYCLE_COUNT_HI : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data <= cycle_count_hi; + s_ctrlport_resp_ack <= 1; + end + endcase + end + + + //----------------------------------------------------------------------- + // Write Logic + //----------------------------------------------------------------------- + + if (s_ctrlport_req_wr) begin + case (word_addr) + REG_BIST_CTRL : begin + start <= s_ctrlport_req_data[REG_BIST_START_POS]; + stop <= s_ctrlport_req_data[REG_BIST_STOP_POS]; + clear <= s_ctrlport_req_data[REG_BIST_CLEAR_POS]; + continuous <= s_ctrlport_req_data[REG_BIST_CONT_POS]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_NUM_BYTES_LO : begin + // Update only the word-count portion + num_bytes[31:WORD_SHIFT] <= s_ctrlport_req_data[31:WORD_SHIFT]; + s_ctrlport_resp_ack <= 1; + end + REG_BIST_NUM_BYTES_HI : begin + num_bytes[COUNT_W-1:32] <= s_ctrlport_req_data[COUNT_W-33:0]; + s_ctrlport_resp_ack <= 1; + end + endcase + end + + end + end + +endmodule + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.v new file mode 100644 index 000000000..4496d172d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.v @@ -0,0 +1,207 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo_regs +// +// Description: +// +// Implements the software-accessible registers for the axi_ram_fifo block. +// + + +module axi_ram_fifo_regs #( + parameter MEM_ADDR_W = 32, + parameter MEM_DATA_W = 64, + parameter [MEM_ADDR_W-1:0] FIFO_ADDR_BASE = 'h0, + parameter [MEM_ADDR_W-1:0] FIFO_ADDR_MASK = 'h0000FFFF, + parameter [MEM_ADDR_W-1:0] FIFO_ADDR_MASK_MIN = 'h00000FFF, + parameter BIST = 1, + parameter IN_FIFO_SIZE = 10, + parameter WORD_ADDR_W = 29, + parameter BURST_TIMEOUT = 128, + parameter TIMEOUT_W = 12 +) ( + + input wire clk, + input wire rst, + + //-------------------------------------------------------------------------- + // CTRL Port + //-------------------------------------------------------------------------- + + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [19:0] s_ctrlport_req_addr, + input wire [31:0] s_ctrlport_req_data, + output reg s_ctrlport_resp_ack, + output reg [31:0] s_ctrlport_resp_data, + + //--------------------------------------------------------------------------- + // Register Inputs and Outputs + //--------------------------------------------------------------------------- + + // Read-back Registers + input wire [ 31:0] rb_out_pkt_count, + input wire [WORD_ADDR_W:0] rb_occupied, + + // Settings Registers + output reg [ 15:0] set_suppress_threshold, + output reg [ TIMEOUT_W-1:0] set_timeout, + output reg [MEM_ADDR_W-1:0] set_fifo_addr_base = FIFO_ADDR_BASE, + output reg [MEM_ADDR_W-1:0] set_fifo_addr_mask = FIFO_ADDR_MASK +); + + `include "axi_ram_fifo_regs.vh" + + function automatic integer min(input integer a, b); + min = a < b ? a : b; + endfunction + + function automatic integer max(input integer a, b); + max = a > b ? a : b; + endfunction + + wire [19:0] word_addr; + wire [63:0] reg_fifo_fullness; + reg [31:0] reg_fifo_fullness_hi; + + // Only use the word address to simplify address decoding logic + assign word_addr = {s_ctrlport_req_addr[19:2], 2'b00 }; + + // Convert the "occupied" word count to a 64-bit byte value + assign reg_fifo_fullness = { + {64-MEM_ADDR_W{1'b0}}, // Set unused upper bits to 0 + rb_occupied, + {(MEM_ADDR_W-WORD_ADDR_W){1'b0}} // Set byte offset bits to 0 + }; + + always @(posedge clk) begin + if (rst) begin + s_ctrlport_resp_ack <= 0; + set_suppress_threshold <= 0; + set_timeout <= BURST_TIMEOUT; + set_fifo_addr_base <= FIFO_ADDR_BASE; + set_fifo_addr_mask <= FIFO_ADDR_MASK; + end else begin + s_ctrlport_resp_ack <= 0; + + //----------------------------------------------------------------------- + // Write Logic + //----------------------------------------------------------------------- + + if (s_ctrlport_req_wr) begin + case (word_addr) + REG_FIFO_READ_SUPPRESS : begin + set_suppress_threshold <= s_ctrlport_req_data[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_TIMEOUT : begin + set_timeout[REG_TIMEOUT_W-1:0] <= s_ctrlport_req_data[REG_TIMEOUT_W-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_BASE_LO : begin + set_fifo_addr_base[min(32, MEM_ADDR_W)-1:0] <= s_ctrlport_req_data[min(32, MEM_ADDR_W)-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_BASE_HI : begin + if (MEM_ADDR_W > 32) begin + set_fifo_addr_base[max(32, MEM_ADDR_W-1):32] <= s_ctrlport_req_data[max(0, MEM_ADDR_W-33):0]; + end + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_MASK_LO : begin + // Coerce the lower bits so we are guaranteed to meet the minimum mask size requirement. + set_fifo_addr_mask[min(32, MEM_ADDR_W)-1:0] <= + s_ctrlport_req_data[min(32, MEM_ADDR_W)-1:0] | FIFO_ADDR_MASK_MIN; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_MASK_HI : begin + if (MEM_ADDR_W > 32) begin + set_fifo_addr_mask[max(32, MEM_ADDR_W-1):32] <= s_ctrlport_req_data[max(0, MEM_ADDR_W-33):0]; + end + s_ctrlport_resp_ack <= 1; + end + endcase + end + + + //----------------------------------------------------------------------- + // Read Logic + //----------------------------------------------------------------------- + + if (s_ctrlport_req_rd) begin + case (word_addr) + REG_FIFO_INFO : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[REG_FIFO_MAGIC_POS +: REG_FIFO_MAGIC_W] <= 16'hF1F0; + s_ctrlport_resp_data[REG_FIFO_BIST_PRSNT_POS] <= (BIST != 0); + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_READ_SUPPRESS : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[REG_FIFO_IN_FIFO_SIZE_POS +: REG_FIFO_IN_FIFO_SIZE_W] + <= IN_FIFO_SIZE; + s_ctrlport_resp_data[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W] + <= set_suppress_threshold; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_MEM_SIZE : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[REG_FIFO_DATA_SIZE_POS +: REG_FIFO_DATA_SIZE_W] + <= MEM_DATA_W; + s_ctrlport_resp_data[REG_FIFO_ADDR_SIZE_POS +: REG_FIFO_ADDR_SIZE_W] + <= MEM_ADDR_W; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_TIMEOUT : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[REG_TIMEOUT_W-1:0] <= set_timeout[REG_TIMEOUT_W-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_FULLNESS_LO : begin + s_ctrlport_resp_data <= reg_fifo_fullness[31:0]; + reg_fifo_fullness_hi <= reg_fifo_fullness[63:32]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_FULLNESS_HI : begin + s_ctrlport_resp_data <= reg_fifo_fullness_hi; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_BASE_LO : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[min(32, MEM_ADDR_W)-1:0] <= set_fifo_addr_base[min(32, MEM_ADDR_W)-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_BASE_HI : begin + s_ctrlport_resp_data <= 0; + if (MEM_ADDR_W > 32) begin + s_ctrlport_resp_data[max(0,MEM_ADDR_W-33):0] <= set_fifo_addr_base[max(32, MEM_ADDR_W-1):32]; + end + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_MASK_LO : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data[min(32, MEM_ADDR_W)-1:0] <= set_fifo_addr_mask[min(32, MEM_ADDR_W)-1:0]; + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_ADDR_MASK_HI : begin + s_ctrlport_resp_data <= 0; + if (MEM_ADDR_W > 32) begin + s_ctrlport_resp_data[max(0, MEM_ADDR_W-33):0] <= set_fifo_addr_mask[max(32, MEM_ADDR_W-1):32]; + end + s_ctrlport_resp_ack <= 1; + end + REG_FIFO_PACKET_CNT : begin + s_ctrlport_resp_data <= 0; + s_ctrlport_resp_data <= rb_out_pkt_count; + s_ctrlport_resp_ack <= 1; + end + endcase + end + + end + end + +endmodule
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.vh new file mode 100644 index 000000000..ccb942552 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/axi_ram_fifo_regs.vh @@ -0,0 +1,228 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_ram_fifo_regs (Header) +// +// Description: Header file for axi_ram_fifo_regs. All registers are 32-bit +// words from software's perspective. +// + +// Address space size, per FIFO. That is, each FIFO is separated in the CTRL +// Port address space by 2^FIFO_ADDR_W bytes. +localparam RAM_FIFO_ADDR_W = 7; + + +// REG_FIFO_INFO (R|W) +// +// Contains info/control bits for the FIFO. +// +// [31:16] : Returns the magic number 0xF1F0 (read-only) +// [0] : Indicates if BIST logic is present (read-only) +// +localparam REG_FIFO_INFO = 'h0; +// +localparam REG_FIFO_MAGIC_POS = 16; +localparam REG_FIFO_BIST_PRSNT_POS = 0; +// +localparam REG_FIFO_MAGIC_W = 16; + + +// REG_FIFO_READ_SUPPRESS (R|W) +// +// Controls the read suppression threshold. RAM reads will be disabled whenever +// the amount of free space in the input buffer (in units of RAM words) falls +// below this threshold. This is intended to prevent input buffer overflows +// caused by the RAM being too busy with reads. To disable the read suppression +// feature, set the threshold to 0. In general, the threshold should be set to +// a small value relative to the input FIFO buffer size (the IN_FIFO_SIZE +// field) so that it is only enabled when the input FIFO buffer is close to +// overflowing. +// + +// [31:16] : Address width of input buffer. In other words, the input buffer is +// 2**REG_FIFO_IN_FIFO_SIZE RAM words deep. (read-only) +// [15: 0] : Read suppression threshold, in RAM words (read/write) +// +localparam REG_FIFO_READ_SUPPRESS = 'h4; +// +localparam REG_FIFO_IN_FIFO_SIZE_POS = 16; +localparam REG_FIFO_SUPPRESS_THRESH_POS = 0; +// +localparam REG_FIFO_IN_FIFO_SIZE_W = 16; +localparam REG_FIFO_SUPPRESS_THRESH_W = 16; + + +// REG_FIFO_MEM_SIZE (R) +// +// Returns information about the size of the attached memory. The address size +// allows software to determine what mask and base address values are valid. +// +// [31:16] : Returns the bit width of the RAM word size. +// [15: 0] : Returns the bit width of the RAM byte address size. That is, the +// addressable portion of the attached memory is +// 2**REG_FIFO_ADDR_SIZE bytes. +// +localparam REG_FIFO_MEM_SIZE = 'h8; +// +localparam REG_FIFO_DATA_SIZE_POS = 16; +localparam REG_FIFO_ADDR_SIZE_POS = 0; +// +localparam REG_FIFO_DATA_SIZE_W = 16; +localparam REG_FIFO_ADDR_SIZE_W = 16; + + +// REG_FIFO_TIMEOUT (R/W) +// +// Programs the FIFO timeout, in memory interface clock cycles. For efficiency, +// we want the memory to read and write full bursts. But we also don't want +// smaller amounts of data to be stuck in the FIFO. This timeout determines how +// long we wait for new data before we go ahead and perform a smaller +// read/write. A longer timeout will make more efficient use of the memory, but +// will increase latency. The default value is set by a module parameter. +// +// [31:12] : <Reserved> +// [11: 0] : Timeout +// +localparam REG_FIFO_TIMEOUT = 'hC; +// +localparam REG_TIMEOUT_POS = 0; +localparam REG_TIMEOUT_W = 12; + + +// REG_FIFO_FULLNESS (R) +// +// Returns the fullness of the FIFO in bytes. This is is a 64-bit register in +// which the least-significant 32-bit word must be read first. +// +localparam REG_FIFO_FULLNESS_LO = 'h10; +localparam REG_FIFO_FULLNESS_HI = 'h14; + + +// REG_FIFO_ADDR_BASE (R|W) +// +// Sets the base byte address to use for this FIFO. This should only be updated +// when the FIFO is idle. This should be set to a multiple of +// REG_FIFO_ADDR_MASK+1. Depending on the size of the memory connected, upper +// bits might be ignored. +// +localparam REG_FIFO_ADDR_BASE_LO = 'h18; +localparam REG_FIFO_ADDR_BASE_HI = 'h1C; + + +// REG_FIFO_ADDR_MASK (R|W) +// +// The byte address mask that controls the portion of the memory address that +// is allocated to this FIFO. For example, set to 0xFFFF for a 64 KiB memory. +// +// This should only be updated when the FIFO is idle. It must be equal to a +// power-of-2 minus 1. It should be no smaller than FIFO_ADDR_MASK_MIN, defined +// in axi_ram_fifo.v, otherwise it will be coerced up to that size. +// +// This is is a 64-bit register in which the least-significant 32-bit word must +// be read/written first. Depending on the size of the memory connected, the +// upper bits might be ignored. +// +localparam REG_FIFO_ADDR_MASK_LO = 'h20; +localparam REG_FIFO_ADDR_MASK_HI = 'h24; + + +// REG_FIFO_PACKET_CNT (R) +// +// Returns the number of packets transferred out of the FIFO block. +// +localparam REG_FIFO_PACKET_CNT = 'h28; + + +//----------------------------------------------------------------------------- +// BIST Registers +//----------------------------------------------------------------------------- +// +// Only read these registers if the BIST component is included. +// +//----------------------------------------------------------------------------- + +// REG_BIST_CTRL (R|W) +// +// Control register for the BIST component. +// +// [4] : BIST is running. Changes to 1 after a test is started, then returns to +// 0 when BIST is complete. +// +// [3] : Continuous mode (run until stopped). When set to 1, test will continue +// to run until Stop bit is set. +// +// [2] : Clear the BIST counters (i.e., the TX, RX, cycle, and error counters) +// +// [1] : Stop BIST (strobe). Write a 1 to this bit to stop the test that is +// currently running +// +// [0] : Start BIST (strobe). Write a 1 to this bit to start a test using the +// configured NUM_BYTES and continuous mode setting. +// +localparam REG_BIST_CTRL = 'h30; +// +localparam REG_BIST_RUNNING_POS = 4; +localparam REG_BIST_CONT_POS = 3; +localparam REG_BIST_CLEAR_POS = 2; // Strobe +localparam REG_BIST_STOP_POS = 1; // Strobe +localparam REG_BIST_START_POS = 0; // Strobe + + +// REG_BIST_CLOCK_RATE (R) +// +// Reports the clock rate of the BIST component in Hz. This can be used with +// REG_BIST_CYCLE_COUNT to calculate throughput. +// +localparam REG_BIST_CLK_RATE = 'h34; + + +// REG_BIST_NUM_BYTES (R|W) +// +// Number of bytes to generate for the next BIST run. THis is not used if the +// REG_BIST_CONT_POS bit is set. This register should not be updated while the +// BIST is running. +// +localparam REG_BIST_NUM_BYTES_LO = 'h38; +localparam REG_BIST_NUM_BYTES_HI = 'h3C; + + +// REG_BIST_TX_BYTE_COUNT (R) +// +// Reports the number of bytes transmitted by the BIST component. This should +// always be read least-significant word first to ensure coherency. Once BIST +// is complete, the TX count will equal the RX count. +// +localparam REG_BIST_TX_BYTE_COUNT_LO = 'h40; +localparam REG_BIST_TX_BYTE_COUNT_HI = 'h44; + + +// REG_BIST_RX_BYTE_COUNT (R) +// +// Reports the number of bytes received by the BIST component. This should +// always be read least-significant word first to ensure coherency. Once BIST +// is complete, the TX count will equal the RX count. +// +localparam REG_BIST_RX_BYTE_COUNT_LO = 'h48; +localparam REG_BIST_RX_BYTE_COUNT_HI = 'h4C; + + +// REG_BIST_ERROR_COUNT (R) +// +// Reports the number of words in which the BIST component detected errors. +// This should always be read least-significant word first to ensure coherency. +// +localparam REG_BIST_ERROR_COUNT_LO = 'h50; +localparam REG_BIST_ERROR_COUNT_HI = 'h54; + + +// REG_BIST_CYCLE_COUNT (R) +// +// Reports the number of clock cycles that have elapsed while the BIST was +// running. This can be used to calculate throughput. This should always be +// read least-significant word first to ensure coherency. +// +localparam REG_BIST_CYCLE_COUNT_LO = 'h58; +localparam REG_BIST_CYCLE_COUNT_HI = 'h5C; + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/noc_shell_axi_ram_fifo.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/noc_shell_axi_ram_fifo.v new file mode 100644 index 000000000..fc353595d --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/noc_shell_axi_ram_fifo.v @@ -0,0 +1,319 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_axi_ram_fifo +// +// Description: A NoC Shell for the RFNoC AXI RAM FIFO. This NoC Shell +// implements the control port interface but does nothing to the +// data path other than moving it to the requested clock domain. +// + +`define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) + + +module noc_shell_axi_ram_fifo #( + parameter [31:0] NOC_ID = 32'h0, + parameter [ 9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter DATA_W = 64, + parameter [ 5:0] CTRL_FIFO_SIZE = 0, + parameter [ 0:0] CTRLPORT_MST_EN = 1, + parameter [ 0:0] CTRLPORT_SLV_EN = 1, + parameter [ 5:0] NUM_DATA_I = 1, + parameter [ 5:0] NUM_DATA_O = 1, + parameter [ 5:0] MTU = 10, + parameter SYNC_DATA_CLOCKS = 0 +) ( + //--------------------------------------------------------------------------- + // 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*DATA_W)-1:0] m_axis_tdata, + output wire [(NUM_DATA_I*`MAX(DATA_W/CHDR_W, 1))-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, + + // Input data stream (from user logic) + input wire [ (NUM_DATA_O*DATA_W)-1:0] s_axis_tdata, + input wire [(NUM_DATA_O*`MAX(DATA_W/CHDR_W, 1))-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 +); + + //--------------------------------------------------------------------------- + // 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 + //--------------------------------------------------------------------------- + + // Set WORD_W to the smaller of DATA_W and CHDR_W. This will be our common + // word size between the CHDR and user data ports. + localparam WORD_W = DATA_W < CHDR_W ? DATA_W : CHDR_W; + localparam KEEP_W = `MAX(DATA_W/CHDR_W, 1); + + genvar i; + + for (i = 0; i < NUM_DATA_I; i = i + 1) begin : gen_in + wire [CHDR_W-1:0] temp_in_tdata; + wire temp_in_tlast; + wire temp_in_tvalid; + wire temp_in_tready; + + axis_packet_flush #( + .WIDTH (CHDR_W), + .FLUSH_PARTIAL_PKTS (0), + .TIMEOUT_W (32), + .PIPELINE ("IN") + ) in_packet_flush_i ( + .clk (rfnoc_chdr_clk), + .reset (rfnoc_chdr_rst), + .enable (data_i_flush_en), + .timeout (data_i_flush_timeout), + .flushing (data_i_flush_active[i]), + .done (data_i_flush_done[i]), + .s_axis_tdata (s_rfnoc_chdr_tdata[i*CHDR_W +: CHDR_W]), + .s_axis_tlast (s_rfnoc_chdr_tlast[i]), + .s_axis_tvalid (s_rfnoc_chdr_tvalid[i]), + .s_axis_tready (s_rfnoc_chdr_tready[i]), + .m_axis_tdata (temp_in_tdata), + .m_axis_tlast (temp_in_tlast), + .m_axis_tvalid (temp_in_tvalid), + .m_axis_tready (temp_in_tready) + ); + + axis_width_conv #( + .WORD_W (WORD_W), + .IN_WORDS (CHDR_W/WORD_W), + .OUT_WORDS (DATA_W/WORD_W), + .SYNC_CLKS (SYNC_DATA_CLOCKS), + .PIPELINE ("NONE") + ) in_width_conv_i ( + .s_axis_aclk (rfnoc_chdr_clk), + .s_axis_rst (rfnoc_chdr_rst), + .s_axis_tdata (temp_in_tdata), + .s_axis_tkeep ({CHDR_W/WORD_W{1'b1}}), + .s_axis_tlast (temp_in_tlast), + .s_axis_tvalid (temp_in_tvalid), + .s_axis_tready (temp_in_tready), + .m_axis_aclk (axis_data_clk), + .m_axis_rst (axis_data_rst), + .m_axis_tdata (m_axis_tdata[i*DATA_W +: DATA_W]), + .m_axis_tkeep (m_axis_tkeep[i*KEEP_W +: KEEP_W]), + .m_axis_tlast (m_axis_tlast[i]), + .m_axis_tvalid (m_axis_tvalid[i]), + .m_axis_tready (m_axis_tready[i]) + ); + end + + + for (i = 0; i < NUM_DATA_O; i = i + 1) begin : gen_out + wire [ CHDR_W-1:0] temp_out_tdata; + wire [CHDR_W/WORD_W-1:0] temp_out_tkeep; + wire temp_out_tlast; + wire temp_out_tvalid; + wire temp_out_tready; + + axis_width_conv #( + .WORD_W (WORD_W), + .IN_WORDS (DATA_W/WORD_W), + .OUT_WORDS (CHDR_W/WORD_W), + .SYNC_CLKS (SYNC_DATA_CLOCKS), + .PIPELINE ("NONE") + ) out_width_conv_i ( + .s_axis_aclk (axis_data_clk), + .s_axis_rst (axis_data_rst), + .s_axis_tdata (s_axis_tdata[i*DATA_W +: DATA_W]), + .s_axis_tkeep (s_axis_tkeep[i*KEEP_W +: KEEP_W]), + .s_axis_tlast (s_axis_tlast[i]), + .s_axis_tvalid (s_axis_tvalid[i]), + .s_axis_tready (s_axis_tready[i]), + .m_axis_aclk (rfnoc_chdr_clk), + .m_axis_rst (rfnoc_chdr_rst), + .m_axis_tdata (temp_out_tdata), + .m_axis_tkeep (), + .m_axis_tlast (temp_out_tlast), + .m_axis_tvalid (temp_out_tvalid), + .m_axis_tready (temp_out_tready) + ); + + axis_packet_flush #( + .WIDTH (CHDR_W), + .FLUSH_PARTIAL_PKTS (0), + .TIMEOUT_W (32), + .PIPELINE ("OUT") + ) out_packet_flush_i ( + .clk (rfnoc_chdr_clk), + .reset (rfnoc_chdr_rst), + .enable (data_o_flush_en), + .timeout (data_o_flush_timeout), + .flushing (data_o_flush_active[i]), + .done (data_o_flush_done[i]), + .s_axis_tdata (temp_out_tdata), + .s_axis_tlast (temp_out_tlast), + .s_axis_tvalid (temp_out_tvalid), + .s_axis_tready (temp_out_tready), + .m_axis_tdata (m_rfnoc_chdr_tdata[i*CHDR_W +: CHDR_W]), + .m_axis_tlast (m_rfnoc_chdr_tlast[i]), + .m_axis_tvalid (m_rfnoc_chdr_tvalid[i]), + .m_axis_tready (m_rfnoc_chdr_tready[i]) + ); + + end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo.v new file mode 100644 index 000000000..04d942ce0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo.v @@ -0,0 +1,485 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_axi_ram_fifo +// +// Description: +// +// Implements a FIFO using an AXI memory-mapped interface to an external +// memory. +// +// Parameters: +// +// THIS_PORTID : Control crossbar port to which this block is connected +// +// CHDR_W : CHDR AXI-Stream data bus width +// +// NUM_PORTS : Number of independent FIFOs to support, all sharing the +// same memory. +// +// MTU : Maximum transfer unit (maximum packet size) to support, +// in CHDR_W-sized words. +// +// MEM_DATA_W : Width of the data bus to use for the AXI memory-mapped +// interface. This must be no bigger than CHDR_W and it must +// evenly divide CHDR_W. +// +// MEM_ADDR_W : Width of the byte address to use for RAM addressing. This +// effectively sets the maximum combined size of all FIFOs. +// This must be less than or equal to AWIDTH. +// +// AWIDTH : Width of the address bus for the AXI memory-mapped +// interface. This must be at least as big as MEM_DATA_W. +// +// FIFO_ADDR_BASE : Default base byte address of each FIFO. When NUM_PORTS > +// 1, this should be the concatenation of all the FIFO base +// addresses. These values can be reconfigured by software. +// +// FIFO_ADDR_MASK : Default byte address mask used by each FIFO. It must be +// all ones. The size of the FIFO in bytes will be this +// minus one. These values can be reconfigured by software. +// +// BURST_TIMEOUT : Default number of memory clock cycles to wait for new +// data before performing a short, sub-optimal burst. One +// value per FIFO. +// +// IN_FIFO_SIZE : Size of the input buffer. This is used to mitigate the +// effects of memory write latency, which can be significant +// when the external memory is DRAM. +// +// OUT_FIFO_SIZE : Size of the output buffer. This is used to mitigate the +// effects of memory read latency, which can be significant +// when the external memory is DRAM. +// +// BIST : Includes BIST logic when true. +// +// MEM_CLK_RATE : Frequency of mem_clk in Hz. This is used by BIST for +// throughput calculation. +// + +module rfnoc_block_axi_ram_fifo #( + parameter THIS_PORTID = 0, + parameter CHDR_W = 64, + parameter NUM_PORTS = 1, + parameter MTU = 10, + parameter MEM_DATA_W = CHDR_W, + parameter MEM_ADDR_W = 32, + parameter AWIDTH = 32, + parameter [NUM_PORTS*MEM_ADDR_W-1:0] FIFO_ADDR_BASE = {NUM_PORTS{ {MEM_ADDR_W{1'b0}} }}, + parameter [NUM_PORTS*MEM_ADDR_W-1:0] FIFO_ADDR_MASK = {NUM_PORTS{ {(MEM_ADDR_W-$clog2(NUM_PORTS)){1'b1}} }}, + parameter [ NUM_PORTS*32-1:0] BURST_TIMEOUT = {NUM_PORTS{ 32'd256 }}, + parameter IN_FIFO_SIZE = 11, + parameter OUT_FIFO_SIZE = 11, + parameter BIST = 1, + parameter MEM_CLK_RATE = 200e6 +) ( + //--------------------------------------------------------------------------- + // AXIS CHDR Port + //--------------------------------------------------------------------------- + + input wire rfnoc_chdr_clk, + + // CHDR inputs from framework + input wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, + output wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tready, + + // CHDR outputs to framework + output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, + input wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tready, + + // 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, + + + //--------------------------------------------------------------------------- + // AXI Memory Mapped Interface + //--------------------------------------------------------------------------- + + // AXI Interface Clock and Reset + input wire mem_clk, + input wire axi_rst, + + // AXI Write Address Channel + output wire [ NUM_PORTS*1-1:0] m_axi_awid, // Write address ID. This signal is the identification tag for the write address signals + output wire [ NUM_PORTS*AWIDTH-1:0] m_axi_awaddr, // Write address. The write address gives the address of the first transfer in a write burst + output wire [ NUM_PORTS*8-1:0] m_axi_awlen, // Burst length. The burst length gives the exact number of transfers in a burst. + output wire [ NUM_PORTS*3-1:0] m_axi_awsize, // Burst size. This signal indicates the size of each transfer in the burst. + output wire [ NUM_PORTS*2-1:0] m_axi_awburst, // Burst type. The burst type and the size information, determine how the address is calculated + output wire [ NUM_PORTS*1-1:0] m_axi_awlock, // Lock type. Provides additional information about the atomic characteristics of the transfer. + output wire [ NUM_PORTS*4-1:0] m_axi_awcache, // Memory type. This signal indicates how transactions are required to progress + output wire [ NUM_PORTS*3-1:0] m_axi_awprot, // Protection type. This signal indicates the privilege and security level of the transaction + output wire [ NUM_PORTS*4-1:0] m_axi_awqos, // Quality of Service, QoS. The QoS identifier sent for each write transaction + output wire [ NUM_PORTS*4-1:0] m_axi_awregion, // Region identifier. Permits a single physical interface on a slave to be re-used. + output wire [ NUM_PORTS*1-1:0] m_axi_awuser, // User signal. Optional User-defined signal in the write address channel. + output wire [ NUM_PORTS*1-1:0] m_axi_awvalid, // Write address valid. This signal indicates that the channel is signaling valid write addr + input wire [ NUM_PORTS*1-1:0] m_axi_awready, // Write address ready. This signal indicates that the slave is ready to accept an address + + // AXI Write Data Channel + output wire [ NUM_PORTS*MEM_DATA_W-1:0] m_axi_wdata, // Write data + output wire [NUM_PORTS*MEM_DATA_W/8-1:0] m_axi_wstrb, // Write strobes. This signal indicates which byte lanes hold valid data. + output wire [ NUM_PORTS*1-1:0] m_axi_wlast, // Write last. This signal indicates the last transfer in a write burst + output wire [ NUM_PORTS*1-1:0] m_axi_wuser, // User signal. Optional User-defined signal in the write data channel. + output wire [ NUM_PORTS*1-1:0] m_axi_wvalid, // Write valid. This signal indicates that valid write data and strobes are available. + input wire [ NUM_PORTS*1-1:0] m_axi_wready, // Write ready. This signal indicates that the slave can accept the write data. + + // AXI Write Response Channel + input wire [ NUM_PORTS*1-1:0] m_axi_bid, // Response ID tag. This signal is the ID tag of the write response. + input wire [ NUM_PORTS*2-1:0] m_axi_bresp, // Write response. This signal indicates the status of the write transaction. + input wire [ NUM_PORTS*1-1:0] m_axi_buser, // User signal. Optional User-defined signal in the write response channel. + input wire [ NUM_PORTS*1-1:0] m_axi_bvalid, // Write response valid. This signal indicates that the channel is signaling a valid response + output wire [ NUM_PORTS*1-1:0] m_axi_bready, // Response ready. This signal indicates that the master can accept a write response + + // AXI Read Address Channel + output wire [ NUM_PORTS*1-1:0] m_axi_arid, // Read address ID. This signal is the identification tag for the read address group of signals + output wire [ NUM_PORTS*AWIDTH-1:0] m_axi_araddr, // Read address. The read address gives the address of the first transfer in a read burst + output wire [ NUM_PORTS*8-1:0] m_axi_arlen, // Burst length. This signal indicates the exact number of transfers in a burst. + output wire [ NUM_PORTS*3-1:0] m_axi_arsize, // Burst size. This signal indicates the size of each transfer in the burst. + output wire [ NUM_PORTS*2-1:0] m_axi_arburst, // Burst type. The burst type and the size information determine how the address for each transfer + output wire [ NUM_PORTS*1-1:0] m_axi_arlock, // Lock type. This signal provides additional information about the atomic characteristics + output wire [ NUM_PORTS*4-1:0] m_axi_arcache, // Memory type. This signal indicates how transactions are required to progress + output wire [ NUM_PORTS*3-1:0] m_axi_arprot, // Protection type. This signal indicates the privilege and security level of the transaction + output wire [ NUM_PORTS*4-1:0] m_axi_arqos, // Quality of Service, QoS. QoS identifier sent for each read transaction. + output wire [ NUM_PORTS*4-1:0] m_axi_arregion, // Region identifier. Permits a single physical interface on a slave to be re-used + output wire [ NUM_PORTS*1-1:0] m_axi_aruser, // User signal. Optional User-defined signal in the read address channel. + output wire [ NUM_PORTS*1-1:0] m_axi_arvalid, // Read address valid. This signal indicates that the channel is signaling valid read addr + input wire [ NUM_PORTS*1-1:0] m_axi_arready, // Read address ready. This signal indicates that the slave is ready to accept an address + + // AXI Read Data Channel + input wire [ NUM_PORTS*1-1:0] m_axi_rid, // Read ID tag. This signal is the identification tag for the read data group of signals + input wire [NUM_PORTS*MEM_DATA_W-1:0] m_axi_rdata, // Read data. + input wire [ NUM_PORTS*2-1:0] m_axi_rresp, // Read response. This signal indicates the status of the read transfer + input wire [ NUM_PORTS*1-1:0] m_axi_rlast, // Read last. This signal indicates the last transfer in a read burst. + input wire [ NUM_PORTS*1-1:0] m_axi_ruser, // User signal. Optional User-defined signal in the read data channel. + input wire [ NUM_PORTS*1-1:0] m_axi_rvalid, // Read valid. This signal indicates that the channel is signaling the required read data. + output wire [ NUM_PORTS*1-1:0] m_axi_rready // Read ready. This signal indicates that the master can accept the read data and response +); + + `include "axi_ram_fifo_regs.vh" + + localparam NOC_ID = 'hF1F0_0000; + + // If the memory width is larger than the CHDR width, then we need to use + // tkeep to track which CHDR words are valid as they go through the FIFO. + // Calculate the TKEEP width here. Set to 1 if it's not needed. + localparam KEEP_W = (MEM_DATA_W/CHDR_W) > 1 ? (MEM_DATA_W/CHDR_W) : 1; + + + //--------------------------------------------------------------------------- + // Parameter Checks + //--------------------------------------------------------------------------- + + if (CHDR_W % MEM_DATA_W != 0 && MEM_DATA_W % CHDR_W != 0) + CHDR_W_must_be_a_multiple_of_MEM_DATA_W_or_vice_versa(); + + if (MEM_ADDR_W > AWIDTH) + MEM_ADDR_W_must_be_greater_than_AWIDTH(); + + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + wire rfnoc_chdr_rst; + + wire ctrlport_req_wr; + wire ctrlport_req_rd; + wire [19:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + wire ctrlport_resp_ack; + wire [31:0] ctrlport_resp_data; + + wire [NUM_PORTS*MEM_DATA_W-1:0] m_axis_data_tdata; + wire [ NUM_PORTS*KEEP_W-1:0] m_axis_data_tkeep; + wire [ NUM_PORTS-1:0] m_axis_data_tlast; + wire [ NUM_PORTS-1:0] m_axis_data_tvalid; + wire [ NUM_PORTS-1:0] m_axis_data_tready; + + wire [NUM_PORTS*MEM_DATA_W-1:0] s_axis_data_tdata; + wire [ NUM_PORTS*KEEP_W-1:0] s_axis_data_tkeep; + wire [ NUM_PORTS-1:0] s_axis_data_tlast; + wire [ NUM_PORTS-1:0] s_axis_data_tvalid; + wire [ NUM_PORTS-1:0] s_axis_data_tready; + + noc_shell_axi_ram_fifo #( + .NOC_ID (NOC_ID), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .DATA_W (MEM_DATA_W), + .CTRL_FIFO_SIZE (5), + .CTRLPORT_MST_EN (1), + .CTRLPORT_SLV_EN (0), + .NUM_DATA_I (NUM_PORTS), + .NUM_DATA_O (NUM_PORTS), + .MTU (MTU), + .SYNC_DATA_CLOCKS (0) + ) noc_shell_axi_ram_fifo_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 (mem_clk), + .ctrlport_rst (axi_rst), + .m_ctrlport_req_wr (ctrlport_req_wr), + .m_ctrlport_req_rd (ctrlport_req_rd), + .m_ctrlport_req_addr (ctrlport_req_addr), + .m_ctrlport_req_data (ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (ctrlport_resp_ack), + .m_ctrlport_resp_status (2'b0), + .m_ctrlport_resp_data (ctrlport_resp_data), + .s_ctrlport_req_wr (1'b0), + .s_ctrlport_req_rd (1'b0), + .s_ctrlport_req_addr (20'b0), + .s_ctrlport_req_portid (10'b0), + .s_ctrlport_req_rem_epid (16'b0), + .s_ctrlport_req_rem_portid (10'b0), + .s_ctrlport_req_data (32'b0), + .s_ctrlport_req_byte_en (4'b0), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .axis_data_clk (mem_clk), + .axis_data_rst (axi_rst), + .m_axis_tdata (m_axis_data_tdata), + .m_axis_tkeep (m_axis_data_tkeep), + .m_axis_tlast (m_axis_data_tlast), + .m_axis_tvalid (m_axis_data_tvalid), + .m_axis_tready (m_axis_data_tready), + .s_axis_tdata (s_axis_data_tdata), + .s_axis_tkeep (s_axis_data_tkeep), + .s_axis_tlast (s_axis_data_tlast), + .s_axis_tvalid (s_axis_data_tvalid), + .s_axis_tready (s_axis_data_tready) + ); + + wire rfnoc_chdr_rst_mem_clk; + reg mem_rst_block; + + // Cross the CHDR reset to the mem_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 (mem_clk), + .pulse_b (rfnoc_chdr_rst_mem_clk) + ); + + // Combine the resets in a glitch-free manner + always @(posedge mem_clk) begin + mem_rst_block <= axi_rst | rfnoc_chdr_rst_mem_clk; + end + + + //--------------------------------------------------------------------------- + // CTRL Port Splitter + //--------------------------------------------------------------------------- + + wire [ NUM_PORTS-1:0] m_ctrlport_req_wr; + wire [ NUM_PORTS-1:0] m_ctrlport_req_rd; + wire [20*NUM_PORTS-1:0] m_ctrlport_req_addr; + wire [32*NUM_PORTS-1:0] m_ctrlport_req_data; + wire [ NUM_PORTS-1:0] m_ctrlport_resp_ack; + wire [32*NUM_PORTS-1:0] m_ctrlport_resp_data; + + ctrlport_decoder #( + .NUM_SLAVES (NUM_PORTS), + .BASE_ADDR (0), + .SLAVE_ADDR_W (RAM_FIFO_ADDR_W) + ) ctrlport_splitter_i ( + .ctrlport_clk (mem_clk), + .ctrlport_rst (mem_rst_block), + .s_ctrlport_req_wr (ctrlport_req_wr), + .s_ctrlport_req_rd (ctrlport_req_rd), + .s_ctrlport_req_addr (ctrlport_req_addr), + .s_ctrlport_req_data (ctrlport_req_data), + .s_ctrlport_req_byte_en (4'b1111), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (ctrlport_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (ctrlport_resp_data), + .m_ctrlport_req_wr (m_ctrlport_req_wr), + .m_ctrlport_req_rd (m_ctrlport_req_rd), + .m_ctrlport_req_addr (m_ctrlport_req_addr), + .m_ctrlport_req_data (m_ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (m_ctrlport_resp_ack), + .m_ctrlport_resp_status ({NUM_PORTS*2{1'b0}}), + .m_ctrlport_resp_data (m_ctrlport_resp_data) + ); + + + //--------------------------------------------------------------------------- + // FIFO Instances + //--------------------------------------------------------------------------- + + genvar i; + for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_ram_fifos + + wire [MEM_ADDR_W-1:0] m_axi_awaddr_int; + wire [MEM_ADDR_W-1:0] m_axi_araddr_int; + + // Resize the addresses from MEM_ADDR_W to AWIDTH + assign m_axi_awaddr[(AWIDTH*(i+1))-1:AWIDTH*i] = m_axi_awaddr_int; + assign m_axi_araddr[(AWIDTH*(i+1))-1:AWIDTH*i] = m_axi_araddr_int; + + axi_ram_fifo #( + .MEM_ADDR_W (MEM_ADDR_W), + .MEM_DATA_W (MEM_DATA_W), + .KEEP_W (KEEP_W), + .FIFO_ADDR_BASE (FIFO_ADDR_BASE[MEM_ADDR_W*i +: MEM_ADDR_W]), + .FIFO_ADDR_MASK (FIFO_ADDR_MASK[MEM_ADDR_W*i +: MEM_ADDR_W]), + .BURST_TIMEOUT (BURST_TIMEOUT[32*i +: 32]), + .BIST (BIST), + .CLK_RATE (MEM_CLK_RATE), + .IN_FIFO_SIZE (IN_FIFO_SIZE), + .OUT_FIFO_SIZE (OUT_FIFO_SIZE) + ) axi_ram_fifo_i ( + + .clk(mem_clk), + .rst(mem_rst_block), + + //----------------------------------------------------------------------- + // Control Port + //----------------------------------------------------------------------- + + .s_ctrlport_req_wr (m_ctrlport_req_wr[i]), + .s_ctrlport_req_rd (m_ctrlport_req_rd[i]), + .s_ctrlport_req_addr (m_ctrlport_req_addr[20*i +: 20]), + .s_ctrlport_req_data (m_ctrlport_req_data[32*i +: 32]), + .s_ctrlport_resp_ack (m_ctrlport_resp_ack[i]), + .s_ctrlport_resp_data (m_ctrlport_resp_data[32*i +: 32]), + + //----------------------------------------------------------------------- + // AXI-Stream FIFO Interface + //----------------------------------------------------------------------- + + // AXI-Stream Input + .s_tdata (m_axis_data_tdata[MEM_DATA_W*i +: MEM_DATA_W]), + .s_tkeep (m_axis_data_tkeep[KEEP_W*i +: KEEP_W]), + .s_tlast (m_axis_data_tlast[i]), + .s_tvalid (m_axis_data_tvalid[i]), + .s_tready (m_axis_data_tready[i]), + // + // AXI-Stream Output + .m_tdata (s_axis_data_tdata[MEM_DATA_W*i +: MEM_DATA_W]), + .m_tkeep (s_axis_data_tkeep[KEEP_W*i +: KEEP_W]), + .m_tlast (s_axis_data_tlast[i]), + .m_tvalid (s_axis_data_tvalid[i]), + .m_tready (s_axis_data_tready[i]), + + //----------------------------------------------------------------------- + // AXI4 Memory Interface + //----------------------------------------------------------------------- + + // AXI Write address channel + .m_axi_awid (m_axi_awid[i]), + .m_axi_awaddr (m_axi_awaddr_int), + .m_axi_awlen (m_axi_awlen[(8*(i+1))-1:8*i]), + .m_axi_awsize (m_axi_awsize[(3*(i+1))-1:3*i]), + .m_axi_awburst (m_axi_awburst[(2*(i+1))-1:2*i]), + .m_axi_awlock (m_axi_awlock[i]), + .m_axi_awcache (m_axi_awcache[(4*(i+1))-1:4*i]), + .m_axi_awprot (m_axi_awprot[(3*(i+1))-1:3*i]), + .m_axi_awqos (m_axi_awqos[(4*(i+1))-1:4*i]), + .m_axi_awregion (m_axi_awregion[(4*(i+1))-1:4*i]), + .m_axi_awuser (m_axi_awuser[i]), + .m_axi_awvalid (m_axi_awvalid[i]), + .m_axi_awready (m_axi_awready[i]), + // + // AXI Write data channel. + .m_axi_wdata (m_axi_wdata[(MEM_DATA_W*(i+1))-1:MEM_DATA_W*i]), + .m_axi_wstrb (m_axi_wstrb[((MEM_DATA_W/8)*(i+1))-1:(MEM_DATA_W/8)*i]), + .m_axi_wlast (m_axi_wlast[i]), + .m_axi_wuser (m_axi_wuser[i]), + .m_axi_wvalid (m_axi_wvalid[i]), + .m_axi_wready (m_axi_wready[i]), + // + // AXI Write response channel signals + .m_axi_bid (m_axi_bid[i]), + .m_axi_bresp (m_axi_bresp[(2*(i+1))-1:2*i]), + .m_axi_buser (m_axi_buser[i]), + .m_axi_bvalid (m_axi_bvalid[i]), + .m_axi_bready (m_axi_bready[i]), + // + // AXI Read address channel + .m_axi_arid (m_axi_arid[i]), + .m_axi_araddr (m_axi_araddr_int), + .m_axi_arlen (m_axi_arlen[(8*(i+1))-1:8*i]), + .m_axi_arsize (m_axi_arsize[(3*(i+1))-1:3*i]), + .m_axi_arburst (m_axi_arburst[(2*(i+1))-1:2*i]), + .m_axi_arlock (m_axi_arlock[i]), + .m_axi_arcache (m_axi_arcache[(4*(i+1))-1:4*i]), + .m_axi_arprot (m_axi_arprot[(3*(i+1))-1:3*i]), + .m_axi_arqos (m_axi_arqos[(4*(i+1))-1:4*i]), + .m_axi_arregion (m_axi_arregion[(4*(i+1))-1:4*i]), + .m_axi_aruser (m_axi_aruser[i]), + .m_axi_arvalid (m_axi_arvalid[i]), + .m_axi_arready (m_axi_arready[i]), + // + // AXI Read data channel + .m_axi_rid (m_axi_rid[i]), + .m_axi_rdata (m_axi_rdata[(MEM_DATA_W*(i+1))-1:MEM_DATA_W*i]), + .m_axi_rresp (m_axi_rresp[(2*(i+1))-1:2*i]), + .m_axi_rlast (m_axi_rlast[i]), + .m_axi_ruser (m_axi_ruser[i]), + .m_axi_rvalid (m_axi_rvalid[i]), + .m_axi_rready (m_axi_rready[i]) + ); + + end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_all_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_all_tb.sv new file mode 100644 index 000000000..575c600f9 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_all_tb.sv @@ -0,0 +1,70 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_axi_ram_fifo_all_tb +// +// Description: +// +// This is the testbench for rfnoc_block_axi_ram_fifo that instantiates +// several variations of rfnoc_block_axi_ram_fifo_tb to test different +// configurations. +// + + +module rfnoc_block_axi_ram_fifo_all_tb; + + timeunit 1ns; + timeprecision 1ps; + + import PkgTestExec::*; + + + //--------------------------------------------------------------------------- + // Test Definitions + //--------------------------------------------------------------------------- + + typedef struct { + int CHDR_W; + int NUM_PORTS; + int MEM_DATA_W; + int MEM_ADDR_W; + int FIFO_ADDR_W; + int IN_FIFO_SIZE; + int OUT_FIFO_SIZE; + bit OVERFLOW; + bit BIST; + } test_config_t; + + localparam NUM_TESTS = 4; + + localparam test_config_t test[NUM_TESTS] = '{ + '{CHDR_W: 64, NUM_PORTS: 2, MEM_DATA_W: 64, MEM_ADDR_W: 13, FIFO_ADDR_W: 12, IN_FIFO_SIZE: 9, OUT_FIFO_SIZE: 9, OVERFLOW: 1, BIST: 1 }, + '{CHDR_W: 64, NUM_PORTS: 1, MEM_DATA_W: 128, MEM_ADDR_W: 14, FIFO_ADDR_W: 13, IN_FIFO_SIZE: 9, OUT_FIFO_SIZE: 9, OVERFLOW: 1, BIST: 1 }, + '{CHDR_W: 128, NUM_PORTS: 1, MEM_DATA_W: 64, MEM_ADDR_W: 13, FIFO_ADDR_W: 12, IN_FIFO_SIZE: 9, OUT_FIFO_SIZE: 10, OVERFLOW: 0, BIST: 1 }, + '{CHDR_W: 128, NUM_PORTS: 1, MEM_DATA_W: 128, MEM_ADDR_W: 16, FIFO_ADDR_W: 14, IN_FIFO_SIZE: 12, OUT_FIFO_SIZE: 12, OVERFLOW: 0, BIST: 0 } + }; + + + //--------------------------------------------------------------------------- + // DUT Instances + //--------------------------------------------------------------------------- + + genvar i; + for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config + rfnoc_block_axi_ram_fifo_tb #( + .CHDR_W (test[i].CHDR_W), + .NUM_PORTS (test[i].NUM_PORTS), + .MEM_DATA_W (test[i].MEM_DATA_W), + .MEM_ADDR_W (test[i].MEM_ADDR_W), + .FIFO_ADDR_W (test[i].FIFO_ADDR_W), + .IN_FIFO_SIZE (test[i].IN_FIFO_SIZE), + .OUT_FIFO_SIZE (test[i].OUT_FIFO_SIZE), + .OVERFLOW (test[i].OVERFLOW), + .BIST (test[i].BIST) + ) rfnoc_block_radio_tb_i (); + end : gen_test_config + + +endmodule : rfnoc_block_axi_ram_fifo_all_tb
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_tb.sv new file mode 100644 index 000000000..49e184ce0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/rfnoc_block_axi_ram_fifo_tb.sv @@ -0,0 +1,1114 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_axi_ram_fifo_tb +// +// Description: Testbench for rfnoc_block_axi_ram_fifo +// + + +module rfnoc_block_axi_ram_fifo_tb #( + parameter int CHDR_W = 64, + parameter int NUM_PORTS = 2, + parameter int MEM_DATA_W = 64, + parameter int MEM_ADDR_W = 13, + parameter int FIFO_ADDR_W = 12, + parameter int IN_FIFO_SIZE = 9, + parameter int OUT_FIFO_SIZE = 9, + parameter bit OVERFLOW = 1, + parameter bit BIST = 1 +); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + + `include "axi_ram_fifo_regs.vh" + + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Simulation parameters + localparam int USE_RANDOM = 1; // Use random simulation data (not sequential) + localparam real CHDR_CLK_PER = 5.333333; // CHDR clock rate + localparam real CTRL_CLK_PER = 10.0; // CTRL clock rate + localparam real MEM_CLK_PER = 3.333333; // Memory clock rate + localparam int SPP = 256; // Samples per packet + localparam int PKT_SIZE_BYTES = SPP*4; // Bytes per packet + localparam int STALL_PROB = 25; // BFM stall probability + + // Block configuration + localparam int THIS_PORTID = 'h123; + localparam int MTU = 12; + localparam int NUM_HB = 3; + localparam int CIC_MAX_DECIM = 255; + localparam int BURST_TIMEOUT = 64; + localparam int MEM_CLK_RATE = int'(1.0e9/MEM_CLK_PER); // Frequency in Hz + localparam int AWIDTH = MEM_ADDR_W+1; + + // Put FIFO 0 at the bottom of the memory and FIFO 1 immediately above it. + localparam bit [MEM_ADDR_W-1:0] FIFO_ADDR_BASE_0 = 0; + localparam bit [MEM_ADDR_W-1:0] FIFO_ADDR_BASE_1 = 2**FIFO_ADDR_W; + localparam bit [MEM_ADDR_W-1:0] FIFO_ADDR_MASK = 2**FIFO_ADDR_W-1; + + + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + bit mem_clk, mem_rst; + + // 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(MEM_CLK_PER), .AUOSTART(0)) + mem_clk_gen (.clk(mem_clk), .rst(mem_rst)); + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + 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 = + new(backend, m_ctrl, s_ctrl); + + // Connect block controller to BFMs + for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections + initial begin + blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); + blk_ctrl.connect_slave_data_port(i, s_chdr[i]); + blk_ctrl.set_master_stall_prob(i, STALL_PROB); + blk_ctrl.set_slave_stall_prob(i, STALL_PROB); + end + end + + + //--------------------------------------------------------------------------- + // AXI Memory Model + //--------------------------------------------------------------------------- + + // AXI Write Address Channel + wire [ NUM_PORTS*1-1:0] m_axi_awid; + wire [ NUM_PORTS*AWIDTH-1:0] m_axi_awaddr; + wire [ NUM_PORTS*8-1:0] m_axi_awlen; + wire [ NUM_PORTS*3-1:0] m_axi_awsize; + wire [ NUM_PORTS*2-1:0] m_axi_awburst; + wire [ NUM_PORTS*1-1:0] m_axi_awlock; // Unused master output + wire [ NUM_PORTS*4-1:0] m_axi_awcache; // Unused master output + wire [ NUM_PORTS*3-1:0] m_axi_awprot; // Unused master output + wire [ NUM_PORTS*4-1:0] m_axi_awqos; // Unused master output + wire [ NUM_PORTS*4-1:0] m_axi_awregion; // Unused master output + wire [ NUM_PORTS*1-1:0] m_axi_awuser; // Unused master output + wire [ NUM_PORTS*1-1:0] m_axi_awvalid; + wire [ NUM_PORTS*1-1:0] m_axi_awready; + // AXI Write Data Channel + wire [ NUM_PORTS*MEM_DATA_W-1:0] m_axi_wdata; + wire [NUM_PORTS*MEM_DATA_W/8-1:0] m_axi_wstrb; + wire [ NUM_PORTS*1-1:0] m_axi_wlast; + wire [ NUM_PORTS*1-1:0] m_axi_wuser; // Unused master output + wire [ NUM_PORTS*1-1:0] m_axi_wvalid; + wire [ NUM_PORTS*1-1:0] m_axi_wready; + // AXI Write Response Channel + wire [ NUM_PORTS*1-1:0] m_axi_bid; + wire [ NUM_PORTS*2-1:0] m_axi_bresp; + wire [ NUM_PORTS*1-1:0] m_axi_buser; // Unused master input + wire [ NUM_PORTS*1-1:0] m_axi_bvalid; + wire [ NUM_PORTS*1-1:0] m_axi_bready; + // AXI Read Address Channel + wire [ NUM_PORTS*1-1:0] m_axi_arid; + wire [ NUM_PORTS*AWIDTH-1:0] m_axi_araddr; + wire [ NUM_PORTS*8-1:0] m_axi_arlen; + wire [ NUM_PORTS*3-1:0] m_axi_arsize; + wire [ NUM_PORTS*2-1:0] m_axi_arburst; + wire [ NUM_PORTS*1-1:0] m_axi_arlock; // Unused master output + wire [ NUM_PORTS*4-1:0] m_axi_arcache; // Unused master output + wire [ NUM_PORTS*3-1:0] m_axi_arprot; // Unused master output + wire [ NUM_PORTS*4-1:0] m_axi_arqos; // Unused master output + wire [ NUM_PORTS*4-1:0] m_axi_arregion; // Unused master output + wire [ NUM_PORTS*1-1:0] m_axi_aruser; // Unused master output + wire [ NUM_PORTS*1-1:0] m_axi_arvalid; + wire [ NUM_PORTS*1-1:0] m_axi_arready; + // AXI Read Data Channel + wire [ NUM_PORTS*1-1:0] m_axi_rid; + wire [NUM_PORTS*MEM_DATA_W-1:0] m_axi_rdata; + wire [ NUM_PORTS*2-1:0] m_axi_rresp; + wire [ NUM_PORTS*1-1:0] m_axi_rlast; + wire [ NUM_PORTS*1-1:0] m_axi_ruser; // Unused master input + wire [ NUM_PORTS*1-1:0] m_axi_rvalid; + wire [ NUM_PORTS*1-1:0] m_axi_rready; + + // Unused master input signals + assign m_axi_buser = {NUM_PORTS{1'b0}}; + assign m_axi_ruser = {NUM_PORTS{1'b0}}; + + for (genvar i = 0; i < NUM_PORTS; i = i+1) begin : gen_sim_axi_ram + sim_axi_ram #( + .AWIDTH (AWIDTH), + .DWIDTH (MEM_DATA_W), + .IDWIDTH (1), + .BIG_ENDIAN (0), + .STALL_PROB (STALL_PROB) + ) sim_axi_ram_i ( + .s_aclk (mem_clk), + .s_aresetn (~mem_rst), + .s_axi_awid (m_axi_awid[i]), + .s_axi_awaddr (m_axi_awaddr[i*AWIDTH +: AWIDTH]), + .s_axi_awlen (m_axi_awlen[i*8 +: 8]), + .s_axi_awsize (m_axi_awsize[i*3 +: 3]), + .s_axi_awburst (m_axi_awburst[i*2 +: 2]), + .s_axi_awvalid (m_axi_awvalid[i]), + .s_axi_awready (m_axi_awready[i]), + .s_axi_wdata (m_axi_wdata[i*MEM_DATA_W +: MEM_DATA_W]), + .s_axi_wstrb (m_axi_wstrb[i*(MEM_DATA_W/8) +: (MEM_DATA_W/8)]), + .s_axi_wlast (m_axi_wlast[i]), + .s_axi_wvalid (m_axi_wvalid[i]), + .s_axi_wready (m_axi_wready[i]), + .s_axi_bid (m_axi_bid[i]), + .s_axi_bresp (m_axi_bresp[i*2 +: 2]), + .s_axi_bvalid (m_axi_bvalid[i]), + .s_axi_bready (m_axi_bready[i]), + .s_axi_arid (m_axi_arid[i]), + .s_axi_araddr (m_axi_araddr[i*AWIDTH +: AWIDTH]), + .s_axi_arlen (m_axi_arlen[i*8 +: 8]), + .s_axi_arsize (m_axi_arsize[i*3 +: 3]), + .s_axi_arburst (m_axi_arburst[i*2 +: 2]), + .s_axi_arvalid (m_axi_arvalid[i]), + .s_axi_arready (m_axi_arready[i]), + .s_axi_rid (m_axi_rid[i]), + .s_axi_rdata (m_axi_rdata[i*MEM_DATA_W +: MEM_DATA_W]), + .s_axi_rresp (m_axi_rresp[i*2 +: 2]), + .s_axi_rlast (m_axi_rlast[i]), + .s_axi_rvalid (m_axi_rvalid[i]), + .s_axi_rready (m_axi_rready[i]) + ); + end + + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready; + + logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tready; + + // Map the array of BFMs to a flat vector for the DUT + for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections + // Connect BFM master to DUT slave port + assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; + assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast; + assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid; + assign m_chdr[i].tready = s_rfnoc_chdr_tready[i]; + + // Connect BFM slave to DUT master port + assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; + assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i]; + assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i]; + assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; + end + + rfnoc_block_axi_ram_fifo #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .NUM_PORTS (NUM_PORTS), + .MTU (MTU), + .MEM_DATA_W (MEM_DATA_W), + .MEM_ADDR_W (MEM_ADDR_W), + .AWIDTH (AWIDTH), + .FIFO_ADDR_BASE ({ FIFO_ADDR_BASE_1, FIFO_ADDR_BASE_0 }), + .FIFO_ADDR_MASK ({NUM_PORTS{FIFO_ADDR_MASK}}), + .BURST_TIMEOUT ({NUM_PORTS{BURST_TIMEOUT}}), + .IN_FIFO_SIZE (IN_FIFO_SIZE), + .OUT_FIFO_SIZE (OUT_FIFO_SIZE), + .BIST (BIST), + .MEM_CLK_RATE (MEM_CLK_RATE) + ) rfnoc_block_axi_ram_fifo_i ( + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .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), + .rfnoc_core_config (backend.cfg), + .rfnoc_core_status (backend.sts), + .rfnoc_ctrl_clk (rfnoc_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), + .mem_clk (mem_clk), + .axi_rst (mem_rst), + .m_axi_awid (m_axi_awid), + .m_axi_awaddr (m_axi_awaddr), + .m_axi_awlen (m_axi_awlen), + .m_axi_awsize (m_axi_awsize), + .m_axi_awburst (m_axi_awburst), + .m_axi_awlock (m_axi_awlock), + .m_axi_awcache (m_axi_awcache), + .m_axi_awprot (m_axi_awprot), + .m_axi_awqos (m_axi_awqos), + .m_axi_awregion (m_axi_awregion), + .m_axi_awuser (m_axi_awuser), + .m_axi_awvalid (m_axi_awvalid), + .m_axi_awready (m_axi_awready), + .m_axi_wdata (m_axi_wdata), + .m_axi_wstrb (m_axi_wstrb), + .m_axi_wlast (m_axi_wlast), + .m_axi_wuser (m_axi_wuser), + .m_axi_wvalid (m_axi_wvalid), + .m_axi_wready (m_axi_wready), + .m_axi_bid (m_axi_bid), + .m_axi_bresp (m_axi_bresp), + .m_axi_buser (m_axi_buser), + .m_axi_bvalid (m_axi_bvalid), + .m_axi_bready (m_axi_bready), + .m_axi_arid (m_axi_arid), + .m_axi_araddr (m_axi_araddr), + .m_axi_arlen (m_axi_arlen), + .m_axi_arsize (m_axi_arsize), + .m_axi_arburst (m_axi_arburst), + .m_axi_arlock (m_axi_arlock), + .m_axi_arcache (m_axi_arcache), + .m_axi_arprot (m_axi_arprot), + .m_axi_arqos (m_axi_arqos), + .m_axi_arregion (m_axi_arregion), + .m_axi_aruser (m_axi_aruser), + .m_axi_arvalid (m_axi_arvalid), + .m_axi_arready (m_axi_arready), + .m_axi_rid (m_axi_rid), + .m_axi_rdata (m_axi_rdata), + .m_axi_rresp (m_axi_rresp), + .m_axi_rlast (m_axi_rlast), + .m_axi_ruser (m_axi_ruser), + .m_axi_rvalid (m_axi_rvalid), + .m_axi_rready (m_axi_rready) + ); + + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + task automatic write_reg(int port, bit [19:0] addr, bit [31:0] value); + blk_ctrl.reg_write((2**RAM_FIFO_ADDR_W)*port + addr, value); + endtask : write_reg + + task automatic write_reg_64(int port, bit [19:0] addr, bit [63:0] value); + blk_ctrl.reg_write((2**RAM_FIFO_ADDR_W)*port + addr + 0, value[31: 0]); + blk_ctrl.reg_write((2**RAM_FIFO_ADDR_W)*port + addr + 4, value[63:32]); + endtask : write_reg_64 + + task automatic read_reg(int port, bit [19:0] addr, output logic [63:0] value); + blk_ctrl.reg_read((2**RAM_FIFO_ADDR_W)*port + addr, value[31: 0]); + endtask : read_reg + + task automatic read_reg_64(int port, bit [19:0] addr, output logic [63:0] value); + blk_ctrl.reg_read((2**RAM_FIFO_ADDR_W)*port + addr + 0, value[31: 0]); + blk_ctrl.reg_read((2**RAM_FIFO_ADDR_W)*port + addr + 4, value[63:32]); + endtask : read_reg_64 + + + // Generate a unique sequence of incrementing numbers + task automatic gen_test_data(int num_bytes, output chdr_word_t queue[$]); + int num_chdr_words; + chdr_word_t val64; + + // Calculate the number of chdr_word_t size words + num_chdr_words = int'($ceil(real'(num_bytes) / ($bits(chdr_word_t) / 8))); + + for (int i = 0; i < num_chdr_words; i++) begin + if (USE_RANDOM) begin + val64 = { $urandom(), $urandom() }; // Random data, for more rigorous testing + end else begin + val64 = i; // Sequential data, for easier debugging + end + queue.push_back(val64); + end + endtask : gen_test_data + + + //--------------------------------------------------------------------------- + // Reset + //--------------------------------------------------------------------------- + + task test_reset(); + test.start_test("Wait for Reset", 10us); + mem_clk_gen.reset(); + blk_ctrl.flush_and_reset(); + wait(!mem_rst); + test.end_test(); + endtask : test_reset + + + //--------------------------------------------------------------------------- + // Check NoC ID and Block Info + //--------------------------------------------------------------------------- + + task test_block_info(); + test.start_test("Verify Block Info", 2us); + `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_axi_ram_fifo_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_mtu() == MTU, "Incorrect MTU Value"); + test.end_test(); + endtask : test_block_info + + + //--------------------------------------------------------------------------- + // Check Unused Signals + //--------------------------------------------------------------------------- + + task test_unused(); + test.start_test("Check unused/static signals"); + for (int port = 0; port < NUM_PORTS; port++) begin + `ASSERT_ERROR(m_axi_awlock [port*1 +: 1] == 1'b0, "m_axi_awlock value unexpected"); + `ASSERT_ERROR(m_axi_awcache [port*4 +: 4] == 4'hF, "m_axi_awcache value unexpected"); + `ASSERT_ERROR(m_axi_awprot [port*3 +: 3] == 3'h2, "m_axi_awprot value unexpected"); + `ASSERT_ERROR(m_axi_awqos [port*4 +: 4] == 4'h0, "m_axi_awqos value unexpected"); + `ASSERT_ERROR(m_axi_awregion [port*4 +: 4] == 4'h0, "m_axi_awregion value unexpected"); + `ASSERT_ERROR(m_axi_awuser [port*1 +: 1] == 1'b0, "m_axi_awuser value unexpected"); + // + `ASSERT_ERROR(m_axi_wuser [port*1 +: 1] == 1'b0, "m_axi_wuser value unexpected"); + // + `ASSERT_ERROR(m_axi_arlock [port*1 +: 1] == 1'b0, "m_axi_arlock value unexpected"); + `ASSERT_ERROR(m_axi_arcache [port*4 +: 4] == 4'hF, "m_axi_arcache value unexpected"); + `ASSERT_ERROR(m_axi_arprot [port*3 +: 3] == 3'h2, "m_axi_arprot value unexpected"); + `ASSERT_ERROR(m_axi_arqos [port*4 +: 4] == 4'h0, "m_axi_arqos value unexpected"); + `ASSERT_ERROR(m_axi_arregion [port*4 +: 4] == 4'h0, "m_axi_arregion value unexpected"); + `ASSERT_ERROR(m_axi_aruser [port*1 +: 1] == 1'b0, "m_axi_aruser value unexpected"); + end + test.end_test(); + endtask : test_unused + + + //--------------------------------------------------------------------------- + // Test Registers + //--------------------------------------------------------------------------- + + task test_registers(); + logic [63:0] val64, expected64, temp64; + logic [31:0] val32, expected32, temp32; + + test.start_test("Test registers", 50us); + + for (int port = 0; port < NUM_PORTS; port++) begin + // + // REG_FIFO_INFO + // + expected32 = 0; + expected32[REG_FIFO_MAGIC_POS +: REG_FIFO_MAGIC_W] = 16'hF1F0; + expected32[REG_FIFO_BIST_PRSNT_POS] = (BIST != 0); + read_reg(port, REG_FIFO_INFO, val32); + `ASSERT_ERROR(val32 == expected32, "Initial value for REG_FIFO_INFO is not correct"); + + // + // REG_FIFO_READ_SUPPRESS + // + expected32 = 0; + expected32[REG_FIFO_IN_FIFO_SIZE_POS+:REG_FIFO_IN_FIFO_SIZE_W] = IN_FIFO_SIZE; + expected32[REG_FIFO_SUPPRESS_THRESH_POS+:REG_FIFO_SUPPRESS_THRESH_W] = 0; + read_reg(port, REG_FIFO_READ_SUPPRESS, val32); + `ASSERT_ERROR(val32 == expected32, "Initial value for REG_FIFO_READ_SUPPRESS is not correct"); + + temp32 = expected32; + expected32[REG_FIFO_SUPPRESS_THRESH_POS+:REG_FIFO_SUPPRESS_THRESH_W] = + ~val32[REG_FIFO_SUPPRESS_THRESH_POS+:REG_FIFO_SUPPRESS_THRESH_W]; + write_reg(port, REG_FIFO_READ_SUPPRESS, expected32); + read_reg(port, REG_FIFO_READ_SUPPRESS, val32); + `ASSERT_ERROR(val32 == expected32, "REG_FIFO_READ_SUPPRESS did not update"); + + expected32 = temp32; + write_reg(port, REG_FIFO_READ_SUPPRESS, expected32); + read_reg(port, REG_FIFO_READ_SUPPRESS, val32); + `ASSERT_ERROR(val32 == expected32, "REG_FIFO_READ_SUPPRESS did not reset"); + + // + // REG_FIFO_MEM_SIZE + // + expected32 = 0; + expected32[REG_FIFO_DATA_SIZE_POS +: REG_FIFO_DATA_SIZE_W] = MEM_DATA_W; + expected32[REG_FIFO_ADDR_SIZE_POS +: REG_FIFO_ADDR_SIZE_W] = MEM_ADDR_W; + read_reg(port, REG_FIFO_MEM_SIZE, val32); + `ASSERT_ERROR(val32 == expected32, "Incorrect REG_FIFO_MEM_SIZE value!"); + + // + // REG_FIFO_TIMEOUT + // + expected32 = BURST_TIMEOUT; + read_reg(port, REG_FIFO_TIMEOUT, val32); + `ASSERT_ERROR(val32 == expected32, "Initial value for REG_FIFO_TIMEOUT is not correct"); + + write_reg(port, REG_FIFO_TIMEOUT, {REG_TIMEOUT_W{1'b1}}); + read_reg(port, REG_FIFO_TIMEOUT, val32); + `ASSERT_ERROR(val32 == {REG_TIMEOUT_W{1'b1}}, "REG_FIFO_TIMEOUT did not update"); + + write_reg(port, REG_FIFO_TIMEOUT, expected32); + read_reg(port, REG_FIFO_TIMEOUT, val32); + `ASSERT_ERROR(val32 == expected32, "REG_FIFO_TIMEOUT did not reset"); + + // + // REG_FIFO_FULLNESS + // + read_reg_64(port, REG_FIFO_FULLNESS_LO, val64); + `ASSERT_ERROR(val64 == 0, "Incorrect REG_FIFO_FULLNESS value!"); + + // + // REG_FIFO_ADDR_BASE + // + expected64 = port * 2**FIFO_ADDR_W; + read_reg_64(port, REG_FIFO_ADDR_BASE_LO, val64); + `ASSERT_ERROR(val64 == expected64, "Initial value for REG_FIFO_ADDR_BASE is not correct"); + + write_reg_64(port, REG_FIFO_ADDR_BASE_LO, {MEM_ADDR_W{1'b1}}); + read_reg_64(port, REG_FIFO_ADDR_BASE_LO, val64); + `ASSERT_ERROR(val64 == {MEM_ADDR_W{1'b1}}, "REG_FIFO_ADDR_BASE did not update"); + + write_reg_64(port, REG_FIFO_ADDR_BASE_LO, expected64); + read_reg_64(port, REG_FIFO_ADDR_BASE_LO, val64); + `ASSERT_ERROR(val64 == expected64, "REG_FIFO_ADDR_BASE did not reset"); + + // + // REG_FIFO_ADDR_MASK + // + expected64 = {FIFO_ADDR_W{1'b1}}; + read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); + `ASSERT_ERROR(val64 == expected64, "Initial value for REG_FIFO_ADDR_MASK_LO is not correct"); + + // Set to the max value + write_reg_64(port, REG_FIFO_ADDR_MASK_LO, {MEM_ADDR_W{1'b1}}); + read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); + `ASSERT_ERROR(val64 == {MEM_ADDR_W{1'b1}}, "REG_FIFO_ADDR_MASK_LO did not update"); + + // Set to the min value + write_reg_64(port, REG_FIFO_ADDR_MASK_LO, 0); + read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); + // Coerce to the minimum allowed value + temp64 = rfnoc_block_axi_ram_fifo_i.gen_ram_fifos[0].axi_ram_fifo_i.FIFO_ADDR_MASK_MIN; + `ASSERT_ERROR(val64 == temp64, "REG_FIFO_ADDR_MASK_LO did not update"); + + write_reg_64(port, REG_FIFO_ADDR_MASK_LO, expected64); + read_reg_64(port, REG_FIFO_ADDR_MASK_LO, val64); + `ASSERT_ERROR(val64 == expected64, "REG_FIFO_ADDR_MASK_LO did not reset"); + + // + // REG_FIFO_PACKET_CNT + // + read_reg(port, REG_FIFO_PACKET_CNT, val32); + `ASSERT_ERROR(val32 == 0, "Incorrect REG_FIFO_PACKET_CNT value!"); + + if (BIST) begin + read_reg(port, REG_BIST_CTRL, val32); + `ASSERT_ERROR(val32 == 0, "Initial value for REG_BIST_CTRL is not correct"); + read_reg(port, REG_BIST_CLK_RATE, val32); + `ASSERT_ERROR(val32 == MEM_CLK_RATE, "Initial value for REG_BIST_CLK_RATE is not correct"); + read_reg_64(port, REG_BIST_NUM_BYTES_LO, val64); + `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_NUM_BYTES is not correct"); + read_reg_64(port, REG_BIST_TX_BYTE_COUNT_LO, val64); + `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_TX_BYTE_COUNT is not correct"); + read_reg_64(port, REG_BIST_RX_BYTE_COUNT_LO, val64); + `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_RX_BYTE_COUNT is not correct"); + read_reg_64(port, REG_BIST_ERROR_COUNT_LO, val64); + `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_ERROR_COUNT is not correct"); + read_reg_64(port, REG_BIST_CYCLE_COUNT_LO, val64); + `ASSERT_ERROR(val64 == 0, "Initial value for REG_BIST_CYCLE_COUNT is not correct"); + end + end + + test.end_test(); + endtask : test_registers + + + //--------------------------------------------------------------------------- + // Basic Test + //--------------------------------------------------------------------------- + // + // Push a few packets through each FIFO. + // + //--------------------------------------------------------------------------- + + task test_basic(); + logic [31:0] val32; + + test.start_test("Basic test", NUM_PORTS*20us); + + for (int port = 0; port < NUM_PORTS; port++) begin + chdr_word_t test_data[$]; + logic [63:0] val64; + timeout_t timeout; + + // Generate the test data to send + gen_test_data(PKT_SIZE_BYTES*3, test_data); + + // Queue up the packets to send + blk_ctrl.send_packets(port, test_data); + + // Make sure fullness increases + test.start_timeout(timeout, 4us, + $sformatf("Waiting for fullness to increase on port %0d", port)); + forever begin + read_reg_64(port, REG_FIFO_FULLNESS_LO, val64); + if (val64 != 0) break; + end + test.end_timeout(timeout); + + // Verify the data, one packet at a time + for (int count = 0; count < test_data.size(); ) begin + chdr_word_t recv_data[$]; + int data_bytes; + blk_ctrl.recv(port, recv_data, data_bytes); + + `ASSERT_ERROR( + data_bytes == PKT_SIZE_BYTES, + "Length didn't match expected value" + ); + + for (int i = 0; i < recv_data.size(); i++, count++) begin + if (recv_data[i] != test_data[count]) begin + $display("Expected %X, received %X on port %0d", test_data[count], recv_data[i], port); + end + `ASSERT_ERROR( + recv_data[i] == test_data[count], + "Received data doesn't match expected value" + ); + end + end + + // Make sure the packet count updated + read_reg(port, REG_FIFO_PACKET_CNT, val32); + `ASSERT_ERROR(val32 > 0, "REG_FIFO_PACKET_CNT didn't update"); + end + + test.end_test(); + endtask : test_basic + + + //--------------------------------------------------------------------------- + // Single Byte Test + //--------------------------------------------------------------------------- + + task test_single_byte(); + test.start_test("Single byte test", 20us); + + for (int port = 0; port < NUM_PORTS; port++) begin + chdr_word_t test_data[$]; + chdr_word_t recv_data[$]; + int data_bytes; + + gen_test_data(1, test_data); + + blk_ctrl.send(port, test_data, 1); + blk_ctrl.recv(port, recv_data, data_bytes); + + `ASSERT_ERROR( + data_bytes == 1 && recv_data.size() == CHDR_W/$bits(chdr_word_t), + "Length didn't match expected value" + ); + `ASSERT_ERROR( + recv_data[0][7:0] == test_data[0][7:0], + "Received data doesn't match expected value" + ); + end + + test.end_test(); + endtask : test_single_byte + + + //--------------------------------------------------------------------------- + // Test Overflow + //--------------------------------------------------------------------------- + // + // Fill the FIFO on both ports to make sure if fills correctly and flow + // control works correct at the limits. + // + //--------------------------------------------------------------------------- + + task test_overflow(); + chdr_word_t test_data[NUM_PORTS][$]; + int num_bytes, num_words; + bit [NUM_PORTS-1:0] full_bits; + logic [63:0] val64; + timeout_t timeout; + realtime start_time; + + if (!OVERFLOW) return; + + num_bytes = (MEM_DATA_W/8) * (2**IN_FIFO_SIZE + 2**OUT_FIFO_SIZE) + 2**MEM_ADDR_W; + num_bytes = num_bytes * 3 / 2; + num_words = num_bytes / (CHDR_W/8); + + test.start_test("Overflow test", 10 * num_words * CHDR_CLK_PER); + + // Stall the output of each FIFO, allow unrestricted input + for (int port = 0; port < NUM_PORTS; port++) begin + blk_ctrl.set_master_stall_prob(port, 0); + blk_ctrl.set_slave_stall_prob(port, 100); + end + + // Input more packets into each FIFO than they can fit + for (int port = 0; port < NUM_PORTS; port++) begin + gen_test_data(num_bytes, test_data[port]); + blk_ctrl.send_packets(port, test_data[port]); + end + + // Wait for both inputs to stall + test.start_timeout(timeout, (4 * num_words + 1000) * CHDR_CLK_PER, + $sformatf("Waiting for input to stall")); + full_bits = 0; + forever begin + for (int port = 0; port < NUM_PORTS; port++) begin + full_bits[port] = ~s_rfnoc_chdr_tready[port]; + if (!full_bits[port]) start_time = $realtime; + end + + // Break as soon as all FIFOs have been stalled for 1000 clock cycles + if (full_bits == {NUM_PORTS{1'b1}} && $realtime-start_time > 1000 * CHDR_CLK_PER) break; + #(CHDR_CLK_PER*100); + end + test.end_timeout(timeout); + + // Make sure all the FIFOs filled up + for (int port = 0; port < NUM_PORTS; port++) begin + read_reg_64(port, REG_FIFO_FULLNESS_LO, val64); + // FIFO is full once it comes within 256 words of being full + `ASSERT_ERROR(val64 >= (2**FIFO_ADDR_W / (MEM_DATA_W/8)) - 256, "FIFO not reading full"); + end + + // Restore the input/output rates + for (int port = 0; port < NUM_PORTS; port++) begin + blk_ctrl.set_master_stall_prob(port, STALL_PROB); + blk_ctrl.set_slave_stall_prob(port, STALL_PROB); + end + + // Read out and verify the data + for (int port = 0; port < NUM_PORTS; port++) begin + for (int count = 0; count < test_data[port].size(); ) begin + chdr_word_t recv_data[$]; + int data_bytes; + int expected_length; + blk_ctrl.recv(port, recv_data, data_bytes); + + if (count*($bits(chdr_word_t)/8) + PKT_SIZE_BYTES <= num_bytes) begin + // Should be a full packet + expected_length = PKT_SIZE_BYTES; + end else begin + // Should be a partial packet + expected_length = num_bytes % PKT_SIZE_BYTES; + end + + // Check the length + `ASSERT_ERROR( + data_bytes == expected_length, + "Length didn't match expected value" + ); + + for (int i = 0; i < recv_data.size(); i++, count++) begin + `ASSERT_ERROR( + recv_data[i] == test_data[port][count], + "Received data doesn't match expected value" + ); + end + end + end + + test.end_test(); + endtask : test_overflow + + + //--------------------------------------------------------------------------- + // Test Read Suppression + //--------------------------------------------------------------------------- + + task test_read_suppression(); + chdr_word_t test_data[$]; + logic [31:0] val32, save32; + int port; + + test.start_test("Read suppression test", 100us); + + port = 0; // Only test one port + + // Turn on read suppression with the max threshold to cause it to + // suppress everything. + read_reg(port, REG_FIFO_READ_SUPPRESS, save32); + val32 = save32; + val32[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W] = {REG_FIFO_SUPPRESS_THRESH_W{1'b1}}; + write_reg(port, REG_FIFO_READ_SUPPRESS, val32); + + // Generate the test data to send (send 8 RAM bursts) + gen_test_data(MEM_DATA_W/8 * 256 * 8, test_data); + + // Start sending packets then wait for the input to stall, either because + // we've filled the FIFO or we've input everything. + blk_ctrl.set_master_stall_prob(port, 0); + blk_ctrl.send_packets(port, test_data); + wait (s_rfnoc_chdr_tvalid && s_rfnoc_chdr_tready); + wait (!s_rfnoc_chdr_tvalid || !s_rfnoc_chdr_tready); + + // Make sure nothing made it through + `ASSERT_ERROR(blk_ctrl.num_received(port) == 0, "Read suppression failed"); + + // Turn down the threshold + val32[REG_FIFO_SUPPRESS_THRESH_POS +: REG_FIFO_SUPPRESS_THRESH_W] = {REG_FIFO_SUPPRESS_THRESH_W{1'b0}}; + write_reg(port, REG_FIFO_READ_SUPPRESS, val32); + + blk_ctrl.set_master_stall_prob(port, STALL_PROB); + + // Verify the data, one packet at a time + for (int count = 0; count < test_data.size(); ) begin + chdr_word_t recv_data[$]; + int data_bytes; + blk_ctrl.recv(port, recv_data, data_bytes); + + for (int i = 0; i < recv_data.size(); i++, count++) begin + if (recv_data[i] != test_data[count]) begin + $display("Expected %X, received %X on port %0d", test_data[count], recv_data[i], port); + end + `ASSERT_ERROR( + recv_data[i] == test_data[count], + "Received data doesn't match expected value" + ); + end + end + + // Restore suppression settings + write_reg(port, REG_FIFO_READ_SUPPRESS, save32); + + test.end_test(); + endtask : test_read_suppression + + + //--------------------------------------------------------------------------- + // Random Tests + //--------------------------------------------------------------------------- + // + // Perform a series of random tests with different read/write probabilities + // test unexpected conditions. + // + //--------------------------------------------------------------------------- + + class RandTrans; + chdr_word_t data[$]; + int num_bytes; + endclass; + + task test_random(); + localparam NUM_PACKETS = 256; + + mailbox #(RandTrans) data_queue; + int port; + + test.start_test("Random test", NUM_PACKETS * 2us); + + data_queue = new(); + port = 0; // Just check one port for this test + + // Queue up a bunch of random packets + begin : data_gen + RandTrans trans; + $display("Generating %0d random packets...", NUM_PACKETS); + + for (int packet_count = 0; packet_count < NUM_PACKETS; packet_count++) begin + trans = new(); + trans.num_bytes = $urandom_range(1, PKT_SIZE_BYTES); + gen_test_data(trans.num_bytes, trans.data); + blk_ctrl.send(port, trans.data, trans.num_bytes); + data_queue.put(trans); + end + end + + // Receive and check all the packets + fork + begin : stall_update + // Split the packets into four groups and use different stall + // behavior for each. + // + // 1. Start filling up the FIFO + // 2. Let it run for a while + // 3. Start emptying the FIFO + // 4. Let it run until all the data gets through + // + for (int i = 0; i < 4; i++) begin + case (i) + 0 : begin + $display("Test fast writer, slow reader"); + blk_ctrl.set_master_stall_prob(port, 10); + blk_ctrl.set_slave_stall_prob(port, 80); + end + 1 : begin + $display("Test matched reader/writer"); + blk_ctrl.set_master_stall_prob(port, STALL_PROB); + blk_ctrl.set_slave_stall_prob(port, STALL_PROB); + end + 2 : begin + $display("Test slow writer, fast reader"); + blk_ctrl.set_master_stall_prob(port, 90); + blk_ctrl.set_slave_stall_prob(port, 10); + end + 3 : begin + $display("Test matched reader/writer"); + blk_ctrl.set_master_stall_prob(port, STALL_PROB); + blk_ctrl.set_slave_stall_prob(port, STALL_PROB); + end + endcase + + // Wait for a quarter of the packets to be accepted by the RAM FIFO + blk_ctrl.wait_complete(port, NUM_PACKETS/4); + end + end + begin : data_check + RandTrans trans; + chdr_word_t recv_data[$]; + int num_bytes; + int num_words; + + + for (int packet_count = 0; packet_count < NUM_PACKETS; packet_count++) begin + //$display("Checking packet %0d/%0d...", packet_count, NUM_PACKETS); + + blk_ctrl.recv(port, recv_data, num_bytes); + data_queue.get(trans); + + // Check the length + `ASSERT_ERROR( + num_bytes == trans.num_bytes, + "Length didn't match expected value" + ); + + // If the generated data was an odd number of chdr_word_t words, we + // will get back an extra 0 word at the end. Calculate the correct + // number of words so that we ignore any extra at the end. + num_words = int'($ceil(real'(num_bytes)/($bits(chdr_word_t)/8))); + for (int i = 0; i < num_words; i++) begin + `ASSERT_ERROR( + recv_data[i] == trans.data[i], + "Received data doesn't match expected value" + ); + end + end + end + join + + test.end_test(); + endtask : test_random + + + //--------------------------------------------------------------------------- + // Test Clearing FIFO Block + //--------------------------------------------------------------------------- + + task test_clear(); + test.start_test("FIFO clear test", 100us); + + // TODO: + $warning("Need to write a test flushing and resetting the block!"); + + test.end_test(); + endtask : test_clear + + + //--------------------------------------------------------------------------- + // Test BIST + //--------------------------------------------------------------------------- + + task test_bist(); + logic [31:0] val32; + logic [63:0] val64; + int port; + int num_bytes; + + if (!BIST) return; + + test.start_test("BIST test", 100us); + + port = 0; // Test the first port + num_bytes = 2048; + + // Start a test + write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_CLEAR_POS); + write_reg(port, REG_BIST_NUM_BYTES_LO, num_bytes); + write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_START_POS); + + // Make sure running bit gets set + read_reg(port, REG_BIST_CTRL, val32); + `ASSERT_ERROR(val32[REG_BIST_RUNNING_POS] == 1'b1, "RUNNING bit not set"); + + // Wait for the test to complete + do begin + read_reg(port, REG_BIST_CTRL, val32); + end while(val32[REG_BIST_RUNNING_POS]); + + // Check the results + read_reg_64(port, REG_BIST_TX_BYTE_COUNT_LO, val64); + `ASSERT_ERROR(val64 == num_bytes, "TX_BYTE_COUNT is not correct"); + read_reg_64(port, REG_BIST_RX_BYTE_COUNT_LO, val64); + `ASSERT_ERROR(val64 == num_bytes, "RX_BYTE_COUNT is not correct"); + read_reg_64(port, REG_BIST_ERROR_COUNT_LO, val64); + `ASSERT_ERROR(val64 == 0, "ERROR_COUNT is not zero"); + read_reg_64(port, REG_BIST_CYCLE_COUNT_LO, val64); + `ASSERT_ERROR(val64 > 0, "CYCLE_COUNT did not update"); + + // TODO: + $warning("BIST Continuous mode is NOT being tested"); + $warning("BIST error insertion is NOT being tested (errors might be ignored)"); + + test.end_test(); + endtask : test_bist + + + //--------------------------------------------------------------------------- + // BIST Throughput Test + //--------------------------------------------------------------------------- + // + // This test sanity-checks the values returned by the BIST. If run with the + // other BIST test, it also tests clearing the BIST counters. + // + //--------------------------------------------------------------------------- + + task test_bist_throughput(); + localparam port = 0; // Test the first port + logic [31:0] val32; + logic [63:0] val64; + int num_bytes; + longint rx_byte_count; + longint cycle_count; + real throughput; + real efficiency; + + if (!BIST) return; + + test.start_test("BIST throughput test", 100us); + + num_bytes = 64*1024; + + // Reset the memory probability + gen_sim_axi_ram[port].sim_axi_ram_i.set_stall_prob(0); + + // Start a test + write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_CLEAR_POS); + write_reg(port, REG_BIST_NUM_BYTES_LO, num_bytes); + write_reg(port, REG_BIST_CTRL, 1 << REG_BIST_START_POS); + + // Make sure running bit gets set + read_reg(port, REG_BIST_CTRL, val32); + `ASSERT_ERROR(val32[REG_BIST_RUNNING_POS] == 1'b1, "RUNNING bit not set"); + + // Wait for the test to complete + do begin + read_reg(port, REG_BIST_CTRL, val32); + end while(val32[REG_BIST_RUNNING_POS]); + + // Check the results + read_reg_64(port, REG_BIST_TX_BYTE_COUNT_LO, val64); + `ASSERT_ERROR(val64 == num_bytes, "TX_BYTE_COUNT is not correct"); + read_reg_64(port, REG_BIST_RX_BYTE_COUNT_LO, rx_byte_count); + `ASSERT_ERROR(rx_byte_count == num_bytes, "RX_BYTE_COUNT is not correct"); + read_reg_64(port, REG_BIST_ERROR_COUNT_LO, val64); + `ASSERT_ERROR(val64 == 0, "ERROR_COUNT is not zero"); + read_reg_64(port, REG_BIST_CYCLE_COUNT_LO, cycle_count); + `ASSERT_ERROR(cycle_count > 0, "CYCLE_COUNT did not update"); + + // Throughput = num_bytes / time = num_bytes / (num_cyles * period) + throughput = real'(rx_byte_count) / (real'(cycle_count) / real'(MEM_CLK_RATE)); + + // Efficiency is the actual throughput divided by the theoretical max. We + // use 0.5x in the calculation because we assume that the memory is a + // half-duplex read/write memory running at MEM_CLK_RATE, but we're + // measuring the full-duplex throughput. + efficiency = throughput / (0.5 * real'(MEM_CLK_RATE) * (MEM_DATA_W/8)); + + $display("BIST Throughput: %0.1f MB/s", throughput / 1.0e6); + $display("BIST Efficiency: %0.1f %%", efficiency * 100.0 ); + + `ASSERT_ERROR(efficiency > 0.95, "BIST efficiency was lower than expected"); + + // Restore the memory stall probability + gen_sim_axi_ram[port].sim_axi_ram_i.set_stall_prob(STALL_PROB); + + test.end_test(); + endtask; + + + //--------------------------------------------------------------------------- + // Main Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + const int port = 0; + string tb_name; + + // Generate a string for the name of this instance of the testbench + tb_name = $sformatf( + "rfnoc_block_axi_ram_fifo_tb\nCHDR_W = %0D, NUM_PORTS = %0D, MEM_DATA_W = %0D, MEM_ADDR_W = %0D, FIFO_ADDR_W = %0D, IN_FIFO_SIZE = %0D, OUT_FIFO_SIZE = %0D, OVERFLOW = %0D, BIST = %0D", + CHDR_W, NUM_PORTS, MEM_DATA_W, MEM_ADDR_W, FIFO_ADDR_W, IN_FIFO_SIZE, OUT_FIFO_SIZE, OVERFLOW, BIST + ); + 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(); + mem_clk_gen.start(); + + // Start the BFMs running + blk_ctrl.run(); + + // + // Run test procedures + // + test_reset(); + test_block_info(); + test_unused(); + test_registers(); + test_basic(); + test_single_byte(); + test_overflow(); + test_read_suppression(); + test_random(); + test_clear(); + test_bist(); + test_bist_throughput(); + + // 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(); + mem_clk_gen.kill(); + end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/sim_axi_ram.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/sim_axi_ram.sv new file mode 100644 index 000000000..ee7ff5df8 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_axi_ram_fifo/sim_axi_ram.sv @@ -0,0 +1,637 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: sim_axi_ram +// +// Description: +// +// Simulation model for a basic AXI4 memory mapped memory. A few notes on its +// behavior: +// +// - This model does not reorder requests (regardless of WID/RID). All +// requests are evaluated strictly in order. +// - The only supported response is OKAY +// - This model supports misaligned memory accesses, which cause a +// simulation warning. +// - A reset does not clear the memory contents +// - The memory itself is implemented using an associative array (sparse +// matrix) so that large memories can be supported. +// - This model is half duplex, meaning read and write data transfers won't +// happen at the same time. A new data transfer won't begin until the +// previous one has completed. +// + +module sim_axi_ram #( + parameter AWIDTH = 32, + parameter DWIDTH = 64, + parameter IDWIDTH = 2, + parameter BIG_ENDIAN = 0, + parameter STALL_PROB = 25 +) ( + input logic s_aclk, + input logic s_aresetn, + + // Write Address Channel + input logic [IDWIDTH-1:0] s_axi_awid, + input logic [ AWIDTH-1:0] s_axi_awaddr, + input logic [ 7:0] s_axi_awlen, + input logic [ 2:0] s_axi_awsize, + input logic [ 1:0] s_axi_awburst, + input logic s_axi_awvalid, + output logic s_axi_awready, + + // Write Data Channel + input logic [ DWIDTH-1:0] s_axi_wdata, + input logic [DWIDTH/8-1:0] s_axi_wstrb, + input logic s_axi_wlast, + input logic s_axi_wvalid, + output logic s_axi_wready, + + // Write Response Channel + output logic [IDWIDTH-1:0] s_axi_bid, + output logic [ 1:0] s_axi_bresp, + output logic s_axi_bvalid, + input logic s_axi_bready, + + // Read Address Channel + input logic [IDWIDTH-1:0] s_axi_arid, + input logic [ AWIDTH-1:0] s_axi_araddr, + input logic [ 7:0] s_axi_arlen, + input logic [ 2:0] s_axi_arsize, + input logic [ 1:0] s_axi_arburst, + input logic s_axi_arvalid, + output logic s_axi_arready, + + // Read Data Channel + output logic [ 0:0] s_axi_rid, + output logic [DWIDTH-1:0] s_axi_rdata, + output logic [ 1:0] s_axi_rresp, + output logic s_axi_rlast, + output logic s_axi_rvalid, + input logic s_axi_rready +); + + localparam DEBUG = 0; + + //--------------------------------------------------------------------------- + // Data Types + //--------------------------------------------------------------------------- + + typedef enum logic [1:0] { FIXED, INCR, WRAP } burst_t; + typedef enum logic [1:0] { OKAY, EXOKAY, SLVERR, DECERR } resp_t; + + typedef struct packed { + longint count; // Number of requests to wait for before executing + logic [IDWIDTH-1:0] id; + logic [AWIDTH-1:0] addr; + logic [8:0] len; // Add an extra bit, since actual true length is +1 + logic [7:0] size; // Add extra bits to store size in bytes, instead of clog2(size) + burst_t burst; + } req_t; + + // Make the address type an extra bit wide so that we can detect + // out-of-bounds accesses easily. + typedef bit [AWIDTH:0] addr_t; + + // Data word type + typedef logic [DWIDTH-1:0] data_t; + + // Mask to indicate which bits should be written. + typedef bit [DWIDTH/8-1:0] mask_t; + + + //--------------------------------------------------------------------------- + // Data Structures + //--------------------------------------------------------------------------- + + byte memory [addr_t]; // Byte addressable memory + mailbox #(req_t) read_req = new(); // Read request queue + mailbox #(req_t) write_req = new(); // Write request queue + mailbox #(req_t) write_resp = new(); // Write response queue + + longint req_count; // Number of requests received + longint compl_count; // Number of requests completed + + + //--------------------------------------------------------------------------- + // External Configuration Interface + //--------------------------------------------------------------------------- + + int waddr_stall_prob = STALL_PROB; + int wdata_stall_prob = STALL_PROB; + int wresp_stall_prob = STALL_PROB; + int raddr_stall_prob = STALL_PROB; + int rdata_stall_prob = STALL_PROB; + + // Set ALL stall probabilities to the same value + function void set_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + waddr_stall_prob = probability; + wdata_stall_prob = probability; + wresp_stall_prob = probability; + raddr_stall_prob = probability; + rdata_stall_prob = probability; + endfunction : set_stall_prob + + // Set WRITE stall probabilities to the same value + function void set_write_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + waddr_stall_prob = probability; + wdata_stall_prob = probability; + wresp_stall_prob = probability; + endfunction : set_write_stall_prob + + // Set READ stall probabilities to the same value + function void set_read_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + raddr_stall_prob = probability; + rdata_stall_prob = probability; + endfunction : set_read_stall_prob + + // Set Write Address Channel stall probability + function void set_waddr_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + waddr_stall_prob = probability; + endfunction : set_waddr_stall_prob + + // Set Write Data Channel stall probability + function void set_wdata_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + wdata_stall_prob = probability; + endfunction : set_wdata_stall_prob + + // Set Write Response Channel stall probability + function void set_wresp_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + wresp_stall_prob = probability; + endfunction : set_wresp_stall_prob + + // Set Read Address Channel stall probability + function void set_raddr_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + raddr_stall_prob = probability; + endfunction : set_raddr_stall_prob + + // Set Read Data Channel stall probability + function void set_rdata_stall_prob(int probability); + assert(probability >= 0 && probability <= 100) else begin + $error("Probability must be from 0 to 100"); + end + rdata_stall_prob = probability; + endfunction : set_rdata_stall_prob + + // Get Write Address Channel stall probability + function int get_waddr_stall_prob(); + return waddr_stall_prob; + endfunction : get_waddr_stall_prob + + // Get Write Data Channel stall probability + function int get_wdata_stall_prob(); + return wdata_stall_prob; + endfunction : get_wdata_stall_prob + + // Get Write Response Channel stall probability + function int get_wresp_stall_prob(); + return wresp_stall_prob; + endfunction : get_wresp_stall_prob + + // Get Read Address Channel stall probability + function int get_raddr_stall_prob(); + return raddr_stall_prob; + endfunction : get_raddr_stall_prob + + // Get Read Data Channel stall probability + function int get_rdata_stall_prob(); + return rdata_stall_prob; + endfunction : get_rdata_stall_prob + + + + //--------------------------------------------------------------------------- + // Helper Functions + //--------------------------------------------------------------------------- + + function data_t read_mem(addr_t byte_addr, int num_bytes); + data_t data; + addr_t incr; + + if (BIG_ENDIAN) begin + byte_addr = byte_addr + num_bytes-1; + incr = -1; + end else begin + incr = 1; + end + + for (int i = 0; i < num_bytes; i++) begin + if (byte_addr >= 2**AWIDTH) begin + $fatal(1, "Read extends beyond memory range"); + end + if (memory.exists(byte_addr)) data[i*8 +: 8] = memory[byte_addr]; + else data[i*8 +: 8] = 'X; + byte_addr += incr; + end + + return data; + endfunction : read_mem + + + function void write_mem(addr_t byte_addr, int num_bytes, data_t data, mask_t mask); + addr_t incr; + + if (BIG_ENDIAN) begin + byte_addr = byte_addr + num_bytes-1; + incr = -1; + end else begin + incr = 1; + end + + for (int i = 0; i < num_bytes; i++) begin + if (mask[i]) begin + if (byte_addr >= 2**AWIDTH) begin + $fatal(1, "Write extends beyond memory range"); + end + memory[byte_addr] = data[i*8 +: 8]; + end + byte_addr += incr; + end + endfunction : write_mem + + + //--------------------------------------------------------------------------- + // Write Requests + //--------------------------------------------------------------------------- + + initial begin : write_req_proc + req_t req; + burst_t burst; + + s_axi_awready <= 0; + + forever begin + @(posedge s_aclk); + if (!s_aresetn) continue; + + if (s_axi_awvalid) begin + if (s_axi_awready) begin + req.count = req_count; + req.id = s_axi_awid; + req.addr = s_axi_awaddr; + req.len = s_axi_awlen + 1; // Per AXI4 spec, Burst_length = AxLEN[7:0] + 1 + req.size = 2**s_axi_awsize; // Store as true size in bytes, not clog2(size) + req.burst = burst_t'(s_axi_awburst); + + // Check that the request is valid + assert (!$isunknown(req)) else begin + $fatal(1, "Write request signals are unknown"); + end + assert (s_axi_araddr % (DWIDTH/8) == 0) else begin + $warning("Unaligned memory write"); + end + assert (2**s_axi_awsize <= DWIDTH/8) else begin + $fatal(1, "AWSIZE must not be larger than DWIDTH"); + end + assert ($cast(burst, s_axi_awburst)) else begin + $fatal(1, "Invalid AWBURST value"); + end + + if (DEBUG) begin + $display("WRITE REQ: id=%X, addr=%X, len=%X, size=%X, burst=%s, %t, %m", + req.id, req.addr, req.len, req.size, req.burst.name, $realtime); + end + + req_count++; + write_req.put(req); + end + + // Randomly deassert ready + s_axi_awready <= $urandom_range(99) < waddr_stall_prob ? 0 : 1; + end + end + end : write_req_proc + + + //--------------------------------------------------------------------------- + // Read Requests + //--------------------------------------------------------------------------- + + initial begin : read_req_proc + req_t req; + burst_t burst; + + s_axi_arready <= 0; + + forever begin + @(posedge s_aclk); + if (!s_aresetn) continue; + + if (s_axi_arvalid) begin + if (s_axi_arready) begin + req.count = req_count; + req.id = s_axi_arid; + req.addr = s_axi_araddr; + req.len = s_axi_arlen + 1; // Per AXI4 spec, Burst_length = AxLEN[7:0] + 1 + req.size = 2**s_axi_arsize; // Store as true size in bytes, not clog2(size) + req.burst = burst_t'(s_axi_arburst); + + // Check that the request is valid + assert(!$isunknown(req)) else begin + $fatal(1, "Read request signals are unknown"); + end + assert(s_axi_araddr % (DWIDTH/8) == 0) else begin + $warning("Unaligned memory read"); + end + assert(2**s_axi_arsize <= DWIDTH/8) else begin + $fatal(1, "ARSIZE must not be larger than DWIDTH"); + end + assert ($cast(burst, s_axi_awburst)) else begin + $fatal(1, "Invalid ARBURST value"); + end + + if (DEBUG) begin + $display("READ REQ: id=%X, addr=%X, len=%X, size=%X, burst=%s, %t, %m", + req.id, req.addr, req.len, req.size, req.burst.name, $realtime); + end + + req_count++; + read_req.put(req); + end + + // Randomly deassert ready to cause a stall + s_axi_arready <= $urandom_range(99) < raddr_stall_prob ? 0 : 1; + end + end + end : read_req_proc + + + //--------------------------------------------------------------------------- + // Write Data + //--------------------------------------------------------------------------- + + initial begin : write_data_proc + req_t req; + bit [AWIDTH-1:0] addr; + + forever begin + // Wait for the next write request + s_axi_wready <= 0; + write_req.get(req); + + // Wait for previous requests to complete + while (compl_count < req.count) begin + @(posedge s_aclk); + if (!s_aresetn) break; + end + + // If reset was asserted, clear the request queue and start over + if (!s_aresetn) begin + while(write_req.try_get(req)); + continue; + end + + // Iterate over the number of words in the request + for (int i = 0; i < req.len; ) begin + @(posedge s_aclk); + if (!s_aresetn) break; + + // Check if we have a new data word + if (s_axi_wvalid) begin + if (s_axi_wready) begin + // Check the inputs + if ($isunknown(s_axi_wstrb)) begin + $fatal(1, "WSTRB is unknown"); + end + if ($isunknown(s_axi_wdata)) begin + $warning(1, "WDATA is unknown; data will be changed to zero"); + end + + case (req.burst) + FIXED : begin + addr = req.addr; + end + INCR : begin + // If the address rolls over, we've reached the end of the + // memory and we should stop here. + addr = req.addr + i*req.size; + if (addr < req.addr) break; + end + WRAP : begin + // Allow roll-over + addr = req.addr + i*req.size; + end + endcase + + write_mem(addr, req.size, s_axi_wdata, s_axi_wstrb); + + if (DEBUG) begin + $display("WRITE: count=%3X, ADDR=%X, DATA=%X, SIZE=%X, STRB=%X, %t, %m", + i, addr, s_axi_wdata, req.size, s_axi_wstrb, $realtime); + end + + i++; + end + + // Randomly deassert ready to cause a stall + s_axi_wready <= $urandom_range(99) < wdata_stall_prob ? 0 : 1; + end + end // for + + // If reset was asserted, clear the request queue and start over + if (!s_aresetn) begin + while(write_req.try_get(req)); + continue; + end + + compl_count++; + + // Enqueue write response + write_resp.put(req); + + // Make sure WLAST asserted for the last word. If not we report an error. + // Per the AXI4 standard, "a slave is not required to use the WLAST + // signal" because "a slave can calculate the last write data transfer + // from the burst length AWLEN". + if (s_axi_wlast != 1'b1) begin + $error("WLAST not asserted on last word of burst"); + end + + end // forever + end : write_data_proc + + + //--------------------------------------------------------------------------- + // Write Response + //--------------------------------------------------------------------------- + + initial begin : write_resp_proc + req_t resp; + bit [AWIDTH-1:0] addr; + + forever begin + s_axi_bid <= 'X; + s_axi_bresp <= 'X; + s_axi_bvalid <= 0; + + // Wait for the next write response + write_resp.get(resp); + @(posedge s_aclk); + + // If there's a reset, clear the response queue and start over + if (!s_aresetn) begin + while(write_resp.try_get(resp)); + continue; + end + + // Randomly keep bvalid deasserted for next word to cause a stall + if ($urandom_range(99) < wresp_stall_prob) begin + do begin + @(posedge s_aclk); + if (!s_aresetn) break; + end while ($urandom_range(99) < wresp_stall_prob); + + // If reset was asserted, clear the response queue and start over + if (!s_aresetn) begin + while(write_resp.try_get(resp)); + continue; + end + end + + // Output the next response + s_axi_bid <= resp.id; + s_axi_bresp <= OKAY; + s_axi_bvalid <= 1; + + if (DEBUG) begin + $display("WRITE RESP: ID=%X, %t, %m", resp.id, $realtime); + end + + // Wait for the response to be accepted + do begin + @(posedge s_aclk); + if (!s_aresetn) break; + end while (!s_axi_bready); + + // Output the next response + s_axi_bid <= 'X; + s_axi_bresp <= 'X; + s_axi_bvalid <= 0; + + // If reset was asserted, clear the response queue and start over + if (!s_aresetn) begin + while(write_resp.try_get(resp)); + continue; + end + end // forever + end : write_resp_proc + + + //--------------------------------------------------------------------------- + // Read Data + //--------------------------------------------------------------------------- + + initial begin : read_data_proc + req_t req; + bit [AWIDTH-1:0] addr; + logic [DWIDTH-1:0] data; + + forever begin + s_axi_rid <= 'X; + s_axi_rdata <= 'X; + s_axi_rresp <= 'X; + s_axi_rlast <= 'X; + s_axi_rvalid <= 0; + + // Wait for the next read request + read_req.get(req); + + // Wait for previous requests to complete + do begin + @(posedge s_aclk); + if (!s_aresetn) break; + end while (compl_count < req.count); + + // If reset was asserted, clear the request queue and start over + if (!s_aresetn) begin + while(read_req.try_get(req)); + continue; + end + + for (int i = 0; i < req.len; i++) begin + // Randomly keep rvalid deasserted for next word to cause a stall + if ($urandom_range(99) < rdata_stall_prob) begin + do begin + @(posedge s_aclk); + if (!s_aresetn) break; + end while ($urandom_range(99) < rdata_stall_prob); + if (!s_aresetn) break; + end + + case (req.burst) + FIXED : begin + addr = req.addr; + end + INCR : begin + // If the address rolls over, we've reached the end of the memory + // and we should stop here. + addr = req.addr + i*req.size; + if (addr < req.addr) break; + end + WRAP : begin + // Allow roll-over + addr = req.addr + i*req.size; + end + endcase + + // Read the memory + data = read_mem(addr, req.size); + + // Output the next word + s_axi_rid <= req.id; + s_axi_rdata <= data; + s_axi_rresp <= OKAY; + s_axi_rlast <= (i == req.len-1); + s_axi_rvalid <= 1; + + if (DEBUG) begin + $display("READ: count=%3X, ADDR=%X, DATA=%X, SIZE=%X, %t, %m", i, addr, data, req.size, $realtime); + end + + // Wait for the word to be captured + do begin + @(posedge s_aclk); + if (!s_aresetn) break; + end while (!s_axi_rready); + + s_axi_rid <= 'X; + s_axi_rdata <= 'X; + s_axi_rresp <= 'X; + s_axi_rlast <= 'X; + s_axi_rvalid <= 0; + end // for + + // If reset was asserted, clear the request queue and start over + if (!s_aresetn) begin + while(read_req.try_get(req)); + end + + compl_count++; + + end // forever + end : read_data_proc + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile new file mode 100644 index 000000000..d574c9a01 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile @@ -0,0 +1,68 @@ +# +# 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 + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the LIB_IP_DIR +#include $(LIB_IP_DIR)/axi_fft/Makefile.inc +#include $(LIB_IP_DIR)/complex_to_magphase/Makefile.inc +include $(LIB_IP_DIR)/complex_multiplier_dds/Makefile.inc +include $(LIB_IP_DIR)/dds_sin_cos_lut_only/Makefile.inc +include $(BASE_DIR)/x300/coregen_dsp/Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(LIB_IP_COMPLEX_MULTIPLIER_DDS_SRCS) \ +$(LIB_IP_DDS_SIN_COS_LUT_ONLY_SRCS) \ +$(COREGEN_DSP_SRCS) \ +) + +#------------------------------------------------- +# 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_DDC_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_ddc_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(COREGEN_DSP_SRCS) \ +$(abspath rfnoc_block_ddc_tb.sv) + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile.srcs new file mode 100644 index 000000000..28663f03c --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/Makefile.srcs @@ -0,0 +1,11 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_BLOCK_DDC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_ddc/, \ +noc_shell_ddc.v \ +rfnoc_block_ddc_regs.vh \ +rfnoc_block_ddc.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/noc_shell_ddc.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/noc_shell_ddc.v new file mode 100644 index 000000000..56a13ee0a --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/noc_shell_ddc.v @@ -0,0 +1,291 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_ddc +// +// Description: A NoC Shell for RFNoC. This should eventually be replaced +// by an auto-generated NoC Shell. +// + +module noc_shell_ddc #( + 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 = 6, + 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*16)-1:0] m_axis_tlength, + 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 > PYLD_FIFO_SIZE) ? MTU : PYLD_FIFO_SIZE; + + //--------------------------------------------------------------------------- + // 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_tlength [i*16 +: 16]), + .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_ddc/rfnoc_block_ddc.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc.v new file mode 100644 index 000000000..3162743b6 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc.v @@ -0,0 +1,420 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_ddc +// +// Description: An digital down-converter block for RFNoC. +// +// Parameters: +// +// THIS_PORTID : Control crossbar port to which this block is connected +// CHDR_W : AXIS CHDR interface data width +// NUM_PORTS : Number of DDCs to instantiate +// 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. +// NUM_HB : Number of half-band decimation blocks to include (0-3) +// CIC_MAX_DECIM : Maximum decimation to support in the CIC filter +// + +module rfnoc_block_ddc #( + parameter THIS_PORTID = 0, + parameter CHDR_W = 64, + parameter NUM_PORTS = 2, + parameter MTU = 10, + parameter CTRL_FIFO_SIZE = 6, + parameter NUM_HB = 3, + parameter CIC_MAX_DECIM = 255 +) ( + //--------------------------------------------------------------------------- + // AXIS CHDR Port + //--------------------------------------------------------------------------- + + input wire rfnoc_chdr_clk, + input wire ce_clk, + + // CHDR inputs from framework + input wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, + output wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tready, + + // CHDR outputs to framework + output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, + input wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tready, + + // 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 +); + + // These are the only supported values for now + localparam ITEM_W = 32; + localparam NIPC = 1; + + localparam NOC_ID = 'hDDC0_0000; + + localparam COMPAT_MAJOR = 16'h0; + localparam COMPAT_MINOR = 16'h0; + + `include "rfnoc_block_ddc_regs.vh" + `include "../../core/rfnoc_axis_ctrl_utils.vh" + + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + wire rfnoc_chdr_rst; + + wire ctrlport_req_wr; + wire ctrlport_req_rd; + wire [19:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + wire ctrlport_req_has_time; + wire [63:0] ctrlport_req_time; + wire ctrlport_resp_ack; + wire [31:0] ctrlport_resp_data; + + wire [NUM_PORTS*ITEM_W-1:0] m_axis_data_tdata; + wire [ NUM_PORTS-1:0] m_axis_data_tlast; + wire [ NUM_PORTS-1:0] m_axis_data_tvalid; + wire [ NUM_PORTS-1:0] m_axis_data_tready; + wire [ NUM_PORTS*64-1:0] m_axis_data_ttimestamp; + wire [ NUM_PORTS-1:0] m_axis_data_thas_time; + wire [ 16*NUM_PORTS-1:0] m_axis_data_tlength; + wire [ NUM_PORTS-1:0] m_axis_data_teob; + wire [ NUM_PORTS*128-1:0] m_axis_data_tuser; + + wire [NUM_PORTS*ITEM_W-1:0] s_axis_data_tdata; + wire [ NUM_PORTS-1:0] s_axis_data_tlast; + wire [ NUM_PORTS-1:0] s_axis_data_tvalid; + wire [ NUM_PORTS-1:0] s_axis_data_tready; + wire [ NUM_PORTS*128-1:0] s_axis_data_tuser; + wire [ NUM_PORTS-1:0] s_axis_data_teob; + wire [ NUM_PORTS*64-1:0] s_axis_data_ttimestamp; + wire [ NUM_PORTS-1:0] s_axis_data_thas_time; + + wire ddc_rst; + + // Cross the CHDR reset to the ce_clk domain + synchronizer ddc_rst_sync_i ( + .clk (ce_clk), + .rst (1'b0), + .in (rfnoc_chdr_rst), + .out (ddc_rst) + ); + + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + noc_shell_ddc #( + .NOC_ID (NOC_ID), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .CTRLPORT_SLV_EN (0), + .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_ddc_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 (ce_clk), + .ctrlport_rst (ddc_rst), + .m_ctrlport_req_wr (ctrlport_req_wr), + .m_ctrlport_req_rd (ctrlport_req_rd), + .m_ctrlport_req_addr (ctrlport_req_addr), + .m_ctrlport_req_data (ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (ctrlport_req_has_time), + .m_ctrlport_req_time (ctrlport_req_time), + .m_ctrlport_resp_ack (ctrlport_resp_ack), + .m_ctrlport_resp_status (AXIS_CTRL_STS_OKAY), + .m_ctrlport_resp_data (ctrlport_resp_data), + .s_ctrlport_req_wr (1'b0), + .s_ctrlport_req_rd (1'b0), + .s_ctrlport_req_addr (20'b0), + .s_ctrlport_req_portid (10'b0), + .s_ctrlport_req_rem_epid (16'b0), + .s_ctrlport_req_rem_portid (10'b0), + .s_ctrlport_req_data (32'b0), + .s_ctrlport_req_byte_en (4'b0), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .axis_data_clk (ce_clk), + .axis_data_rst (ddc_rst), + .m_axis_tdata (m_axis_data_tdata), + .m_axis_tkeep (), + .m_axis_tlast (m_axis_data_tlast), + .m_axis_tvalid (m_axis_data_tvalid), + .m_axis_tready (m_axis_data_tready), + .m_axis_ttimestamp (m_axis_data_ttimestamp), + .m_axis_thas_time (m_axis_data_thas_time), + .m_axis_tlength (m_axis_data_tlength), + .m_axis_teov (), + .m_axis_teob (m_axis_data_teob), + .s_axis_tdata (s_axis_data_tdata), + .s_axis_tkeep ({NUM_PORTS*NIPC{1'b1}}), + .s_axis_tlast (s_axis_data_tlast), + .s_axis_tvalid (s_axis_data_tvalid), + .s_axis_tready (s_axis_data_tready), + .s_axis_ttimestamp (s_axis_data_ttimestamp), + .s_axis_thas_time (s_axis_data_thas_time), + .s_axis_teov ({NUM_PORTS{1'b0}}), + .s_axis_teob (s_axis_data_teob) + ); + + + //--------------------------------------------------------------------------- + // Register Translation + //--------------------------------------------------------------------------- + // + // Each DDC block is allocated an address spaces. This block translates CTRL + // port transactions in that space to settings bus. + // + //--------------------------------------------------------------------------- + + wire [ 8*NUM_PORTS-1:0] set_addr; + wire [32*NUM_PORTS-1:0] set_data; + wire [ NUM_PORTS-1:0] set_has_time; + wire [ NUM_PORTS-1:0] set_stb; + wire [64*NUM_PORTS-1:0] set_time; + wire [ 8*NUM_PORTS-1:0] rb_addr; + reg [64*NUM_PORTS-1:0] rb_data; + wire [ NUM_PORTS-1:0] rb_stb; + + ctrlport_to_settings_bus # ( + .NUM_PORTS (NUM_PORTS) + ) ctrlport_to_settings_bus_i ( + .ctrlport_clk (ce_clk), + .ctrlport_rst (ddc_rst), + .s_ctrlport_req_wr (ctrlport_req_wr), + .s_ctrlport_req_rd (ctrlport_req_rd), + .s_ctrlport_req_addr (ctrlport_req_addr), + .s_ctrlport_req_data (ctrlport_req_data), + .s_ctrlport_req_has_time (ctrlport_req_has_time), + .s_ctrlport_req_time (ctrlport_req_time), + .s_ctrlport_resp_ack (ctrlport_resp_ack), + .s_ctrlport_resp_data (ctrlport_resp_data), + .set_data (set_data), + .set_addr (set_addr), + .set_stb (set_stb), + .set_time (set_time), + .set_has_time (set_has_time), + .rb_stb (rb_stb), + .rb_addr (rb_addr), + .rb_data (rb_data)); + + + //--------------------------------------------------------------------------- + // DDC Implementation + //--------------------------------------------------------------------------- + + // Unused signals + wire [ NUM_PORTS-1:0] clear_tx_seqnum = 0; + wire [16*NUM_PORTS-1:0] src_sid = 0; + wire [16*NUM_PORTS-1:0] next_dst_sid = 0; + + localparam MAX_N = CIC_MAX_DECIM * 2 << (NUM_HB-1); + + genvar i; + generate + for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_ddc_chains + wire set_stb_int = set_stb[i]; + wire [7:0] set_addr_int = set_addr[8*i+7:8*i]; + wire [31:0] set_data_int = set_data[32*i+31:32*i]; + wire [63:0] set_time_int = set_time[64*i+63:64*i]; + wire set_has_time_int = set_has_time[i]; + + // Build the expected tuser CHDR header + cvita_hdr_encoder cvita_hdr_encoder_i ( + .pkt_type (2'b0), + .eob (m_axis_data_teob[i]), + .has_time (m_axis_data_thas_time[i]), + .seqnum (12'b0), + .payload_length (m_axis_data_tlength[16*i +: 16]), + .src_sid (16'b0), + .dst_sid (16'b0), + .vita_time (m_axis_data_ttimestamp[64*i +: 64]), + .header (m_axis_data_tuser[128*i+:128]) + ); + + // Extract bit fields from outgoing tuser CHDR header + assign s_axis_data_teob[i] = s_axis_data_tuser[128*i+124 +: 1]; + assign s_axis_data_thas_time[i] = s_axis_data_tuser[128*i+125 +: 1]; + assign s_axis_data_ttimestamp[64*i+:64] = s_axis_data_tuser[128*i+ 0 +: 64]; + + // TODO: Read-back register for number of FIR filter taps + always @(*) begin + case(rb_addr[8*i+7:8*i]) + RB_COMPAT_NUM : rb_data[64*i+63:64*i] <= {COMPAT_MAJOR, COMPAT_MINOR}; + RB_NUM_HB : rb_data[64*i+63:64*i] <= NUM_HB; + RB_CIC_MAX_DECIM : rb_data[64*i+63:64*i] <= CIC_MAX_DECIM; + default : rb_data[64*i+63:64*i] <= 64'h0BADC0DE0BADC0DE; + endcase + end + + //////////////////////////////////////////////////////////// + // + // Timed Commands + // + //////////////////////////////////////////////////////////// + wire [31:0] m_axis_tagged_tdata; + wire m_axis_tagged_tlast; + wire m_axis_tagged_tvalid; + wire m_axis_tagged_tready; + wire [127:0] m_axis_tagged_tuser; + wire m_axis_tagged_tag; + + wire out_set_stb; + wire [7:0] out_set_addr; + wire [31:0] out_set_data; + wire timed_set_stb; + wire [7:0] timed_set_addr; + wire [31:0] timed_set_data; + + wire timed_cmd_fifo_full; + + axi_tag_time #( + .NUM_TAGS(1), + .SR_TAG_ADDRS(SR_FREQ_ADDR)) + axi_tag_time ( + .clk(ce_clk), + .reset(ddc_rst), + .clear(clear_tx_seqnum[i]), + .tick_rate(16'd1), + .timed_cmd_fifo_full(timed_cmd_fifo_full), + .s_axis_data_tdata(m_axis_data_tdata[i*ITEM_W+:ITEM_W]), .s_axis_data_tlast(m_axis_data_tlast[i]), + .s_axis_data_tvalid(m_axis_data_tvalid[i]), .s_axis_data_tready(m_axis_data_tready[i]), + .s_axis_data_tuser(m_axis_data_tuser[128*i+:128]), + .m_axis_data_tdata(m_axis_tagged_tdata), .m_axis_data_tlast(m_axis_tagged_tlast), + .m_axis_data_tvalid(m_axis_tagged_tvalid), .m_axis_data_tready(m_axis_tagged_tready), + .m_axis_data_tuser(m_axis_tagged_tuser), .m_axis_data_tag(m_axis_tagged_tag), + .in_set_stb(set_stb_int), .in_set_addr(set_addr_int), .in_set_data(set_data_int), + .in_set_time(set_time_int), .in_set_has_time(set_has_time_int), + .out_set_stb(out_set_stb), .out_set_addr(out_set_addr), .out_set_data(out_set_data), + .timed_set_stb(timed_set_stb), .timed_set_addr(timed_set_addr), .timed_set_data(timed_set_data)); + + // Hold off reading additional commands if internal FIFO is full + assign rb_stb[i] = ~timed_cmd_fifo_full; + + //////////////////////////////////////////////////////////// + // + // Reduce Rate + // + //////////////////////////////////////////////////////////// + wire [31:0] sample_in_tdata, sample_out_tdata; + wire sample_in_tuser, sample_in_eob; + wire sample_in_tvalid, sample_in_tready, sample_in_tlast; + wire sample_out_tvalid, sample_out_tready; + wire clear_user; + wire nc; + axi_rate_change #( + .WIDTH(33), + .MAX_N(MAX_N), + .MAX_M(1), + .SR_N_ADDR(SR_N_ADDR), + .SR_M_ADDR(SR_M_ADDR), + .SR_CONFIG_ADDR(SR_CONFIG_ADDR)) + axi_rate_change ( + .clk(ce_clk), .reset(ddc_rst), .clear(clear_tx_seqnum[i]), .clear_user(clear_user), + .src_sid(src_sid[16*i+15:16*i]), .dst_sid(next_dst_sid[16*i+15:16*i]), + .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data), + .i_tdata({m_axis_tagged_tag,m_axis_tagged_tdata}), .i_tlast(m_axis_tagged_tlast), + .i_tvalid(m_axis_tagged_tvalid), .i_tready(m_axis_tagged_tready), + .i_tuser(m_axis_tagged_tuser), + .o_tdata({nc,s_axis_data_tdata[i*ITEM_W+:ITEM_W]}), .o_tlast(s_axis_data_tlast[i]), .o_tvalid(s_axis_data_tvalid[i]), + .o_tready(s_axis_data_tready[i]), .o_tuser(s_axis_data_tuser[128*i+:128]), + .m_axis_data_tdata({sample_in_tuser,sample_in_tdata}), .m_axis_data_tlast(sample_in_tlast), + .m_axis_data_tvalid(sample_in_tvalid), .m_axis_data_tready(sample_in_tready), + .s_axis_data_tdata({1'b0,sample_out_tdata}), .s_axis_data_tlast(1'b0), + .s_axis_data_tvalid(sample_out_tvalid), .s_axis_data_tready(sample_out_tready), + .warning_long_throttle(), + .error_extra_outputs(), + .error_drop_pkt_lockup()); + + assign sample_in_eob = m_axis_tagged_tuser[124]; //this should align with last packet output from axi_rate_change + + //////////////////////////////////////////////////////////// + // + // Digital Down Converter + // + //////////////////////////////////////////////////////////// + + ddc #( + .SR_FREQ_ADDR(SR_FREQ_ADDR), + .SR_SCALE_IQ_ADDR(SR_SCALE_IQ_ADDR), + .SR_DECIM_ADDR(SR_DECIM_ADDR), + .SR_MUX_ADDR(SR_MUX_ADDR), + .SR_COEFFS_ADDR(SR_COEFFS_ADDR), + .NUM_HB(NUM_HB), + .CIC_MAX_DECIM(CIC_MAX_DECIM)) + ddc ( + .clk(ce_clk), .reset(ddc_rst), + .clear(clear_user | clear_tx_seqnum[i]), // Use AXI Rate Change's clear user to reset block to initial state after EOB + .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data), + .timed_set_stb(timed_set_stb), .timed_set_addr(timed_set_addr), .timed_set_data(timed_set_data), + .sample_in_tdata(sample_in_tdata), .sample_in_tlast(sample_in_tlast), + .sample_in_tvalid(sample_in_tvalid), .sample_in_tready(sample_in_tready), + .sample_in_tuser(sample_in_tuser), .sample_in_eob(sample_in_eob), + .sample_out_tdata(sample_out_tdata), .sample_out_tlast(), + .sample_out_tvalid(sample_out_tvalid), .sample_out_tready(sample_out_tready) + ); + + end + endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_regs.vh new file mode 100644 index 000000000..bc1bf4c46 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_regs.vh @@ -0,0 +1,27 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_ddc_regs (Header) +// +// Description: Header file for RFNoC DDC functionality. This includes +// register offsets, bitfields and constants for the radio components. +// + +// For now, these offsets match the original DDC +localparam DDC_BASE_ADDR = 'h00; +localparam DDC_ADDR_W = 8; + +localparam RB_COMPAT_NUM = 0; +localparam RB_NUM_HB = 1; +localparam RB_CIC_MAX_DECIM = 2; +localparam SR_N_ADDR = 128; +localparam SR_M_ADDR = 129; +localparam SR_CONFIG_ADDR = 130; +localparam SR_FREQ_ADDR = 132; +localparam SR_SCALE_IQ_ADDR = 133; +localparam SR_DECIM_ADDR = 134; +localparam SR_MUX_ADDR = 135; +localparam SR_COEFFS_ADDR = 136; + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv new file mode 100644 index 000000000..8b0790909 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv @@ -0,0 +1,386 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_ddc_tb +// +// Description: Testbench for rfnoc_block_ddc +// + + +module rfnoc_block_ddc_tb(); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + + `include "rfnoc_block_ddc_regs.vh" + + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Simulation parameters + localparam real CHDR_CLK_PER = 5.0; // CHDR clock rate + localparam real DDC_CLK_PER = 4.0; // DUC IP clock rate + localparam int EXTENDED_TEST = 0; // Perform a longer test + localparam int SPP = 256; // Samples per packet + localparam int PKT_SIZE_BYTES = SPP*4; // Bytes per packet + localparam int STALL_PROB = 25; // BFM stall probability + + // Block configuration + localparam int CHDR_W = 64; + localparam int THIS_PORTID = 'h123; + localparam int MTU = 8; + localparam int NUM_PORTS = 1; + localparam int NUM_HB = 3; + localparam int CIC_MAX_DECIM = 255; + + + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + bit ce_clk; + + sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + sim_clock_gen #(DDC_CLK_PER) ddc_clk_gen (.clk(ce_clk), .rst()); + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + 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 = + new(backend, m_ctrl, s_ctrl); + + // Connect block controller to BFMs + for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections + initial begin + blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); + blk_ctrl.connect_slave_data_port(i, s_chdr[i]); + blk_ctrl.set_master_stall_prob(i, STALL_PROB); + blk_ctrl.set_slave_stall_prob(i, STALL_PROB); + end + end + + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready; + + logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tready; + + // Map the array of BFMs to a flat vector for the DUT + genvar i; + for (i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections + // Connect BFM master to DUT slave port + assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; + assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast; + assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid; + assign m_chdr[i].tready = s_rfnoc_chdr_tready[i]; + + // Connect BFM slave to DUT master port + assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; + assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i]; + assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i]; + assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; + end + + rfnoc_block_ddc #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .NUM_PORTS (NUM_PORTS), + .MTU (MTU), + .NUM_HB (NUM_HB), + .CIC_MAX_DECIM (CIC_MAX_DECIM) + ) rfnoc_block_ddc_i ( + .rfnoc_chdr_clk (backend.chdr_clk), + .ce_clk (ce_clk), + .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), + .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) + ); + + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + // Translate the desired register access to a ctrlport write request. + task automatic write_reg(int port, byte addr, bit [31:0] value); + blk_ctrl.reg_write(256*8*port + addr*8, value); + endtask : write_reg + + + // Translate the desired register access to a ctrlport read request. + task automatic read_user_reg(int port, byte addr, output logic [63:0] value); + blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); + blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); + endtask : read_user_reg + + + task automatic set_decim_rate(int port, input int decim_rate); + logic [7:0] cic_rate; + logic [1:0] hb_enables; + int _decim_rate; + + cic_rate = 8'd0; + hb_enables = 2'b0; + _decim_rate = decim_rate; + + // Calculate which half bands to enable and whatever is left over set the CIC + while ((_decim_rate[0] == 0) && (hb_enables < NUM_HB)) begin + hb_enables += 1'b1; + _decim_rate = _decim_rate >> 1; + end + // CIC rate cannot be set to 0 + cic_rate = (_decim_rate[7:0] == 8'd0) ? 8'd1 : _decim_rate[7:0]; + `ASSERT_ERROR( + hb_enables <= NUM_HB, + "Enabled halfbands may not exceed total number of half bands." + ); + `ASSERT_ERROR( + cic_rate > 0 && cic_rate <= CIC_MAX_DECIM, + "CIC Decimation rate must be positive, not exceed the max cic decimation rate, and cannot equal 0!" + ); + + // Setup DDC + $display("Set decimation to %0d", decim_rate); + $display("- Number of enabled HBs: %0d", hb_enables); + $display("- CIC Rate: %0d", cic_rate); + write_reg(port, SR_N_ADDR, decim_rate); // Set decimation rate in AXI rate change + write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate}); // Enable HBs, set CIC rate + endtask + + + task automatic send_ramp ( + input int unsigned port, + input int unsigned decim_rate, + // (Optional) For testing passing through partial packets + input logic drop_partial_packet = 1'b0, + input int unsigned extra_samples = 0 + ); + set_decim_rate(port, decim_rate); + + // Setup DDC + write_reg(port, SR_CONFIG_ADDR, 32'd1); // Enable clear EOB + write_reg(port, SR_FREQ_ADDR, 32'd0); // Phase increment + write_reg(port, SR_SCALE_IQ_ADDR, (1 << 14)); // Scaling, set to 1 + + // Send a short ramp, should pass through unchanged + fork + begin + chdr_word_t send_payload[$]; + packet_info_t pkt_info; + + pkt_info = 0; + for (int i = 0; i < decim_rate*(PKT_SIZE_BYTES/8 + extra_samples); i++) begin + send_payload.push_back({16'(2*i/decim_rate), 16'(2*i/decim_rate), 16'((2*i+1)/decim_rate), 16'((2*i+1)/decim_rate)}); + end + $display("Send ramp (%0d words)", send_payload.size()); + pkt_info.eob = 1; + blk_ctrl.send_packets(port, send_payload, /*data_bytes*/, /*metadata*/, pkt_info); + blk_ctrl.wait_complete(port); + $display("Send ramp complete"); + end + begin + string s; + logic [63:0] samples, samples_old; + chdr_word_t recv_payload[$], temp_payload[$]; + chdr_word_t metadata[$]; + int data_bytes; + packet_info_t pkt_info; + + $display("Check ramp"); + if (~drop_partial_packet && (extra_samples > 0)) begin + blk_ctrl.recv_adv(port, temp_payload, data_bytes, metadata, pkt_info); + $sformat(s, "Invalid EOB state! Expected %b, Received: %b", 1'b0, pkt_info.eob); + `ASSERT_ERROR(pkt_info.eob == 1'b0, s); + end + $display("Receiving packet"); + blk_ctrl.recv_adv(port, recv_payload, data_bytes, metadata, pkt_info); + $display("Received!"); + $sformat(s, "Invalid EOB state! Expected %b, Received: %b", 1'b1, pkt_info.eob); + `ASSERT_ERROR(pkt_info.eob == 1'b1, s); + recv_payload = {temp_payload, recv_payload}; + if (drop_partial_packet) begin + $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size()); + `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s); + end else begin + $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size() + extra_samples); + `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8 + extra_samples, s); + end + samples = 64'd0; + samples_old = 64'd0; + for (int i = 0; i < PKT_SIZE_BYTES/8; i++) begin + samples = recv_payload[i]; + for (int j = 0; j < 4; j++) begin + // Need to check a range of values due to imperfect gain compensation + $sformat(s, "Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i, + samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4, samples[16*j +: 16]); + `ASSERT_ERROR((samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) && (samples >= samples_old[16*j +: 16]), s); + end + samples_old = samples; + end + $display("Check complete"); + end + join + endtask + + + //--------------------------------------------------------------------------- + // Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + const int port = 0; + test.start_tb("rfnoc_block_ddc_tb"); + + // Start the BFMs running + blk_ctrl.run(); + + + //------------------------------------------------------------------------- + // Reset + //------------------------------------------------------------------------- + + test.start_test("Wait for Reset", 10us); + fork + blk_ctrl.reset_chdr(); + blk_ctrl.reset_ctrl(); + join; + test.end_test(); + + + //------------------------------------------------------------------------- + // Check NoC ID and Block Info + //------------------------------------------------------------------------- + + test.start_test("Verify Block Info", 2us); + `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_ddc_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_mtu() == MTU, "Incorrect MTU Value"); + test.end_test(); + + + //------------------------------------------------------------------------- + // Test read-back regs + //------------------------------------------------------------------------- + + begin + logic [63:0] val64; + test.start_test("Test registers", 10us); + read_user_reg(port, RB_NUM_HB, val64); + `ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value"); + read_user_reg(port, RB_CIC_MAX_DECIM, val64); + `ASSERT_ERROR(val64 == CIC_MAX_DECIM, "Register CIC_MAX_DECIM didn't read back expected value"); + test.end_test(); + end + + + //------------------------------------------------------------------------- + // Test various decimation rates + //------------------------------------------------------------------------- + + begin + test.start_test("Decimate by 1, 2, 3, 4, 6, 8, 12, 13, 16, 24, 40, 255, 2040", 0.5ms); + + $display("Note: This test will take a long time!"); + + // List of rates to catch most issues + send_ramp(port, 1); // HBs enabled: 0, CIC rate: 1 + send_ramp(port, 2); // HBs enabled: 1, CIC rate: 1 + send_ramp(port, 3); // HBs enabled: 0, CIC rate: 3 + send_ramp(port, 4); // HBs enabled: 2, CIC rate: 1 + if (EXTENDED_TEST) send_ramp(port, 6); // HBs enabled: 1, CIC rate: 3 + send_ramp(port, 8); // HBs enabled: 3, CIC rate: 1 + send_ramp(port, 12); // HBs enabled: 2, CIC rate: 3 + send_ramp(port, 13); // HBs enabled: 0, CIC rate: 13 + if (EXTENDED_TEST) send_ramp(port, 16); // HBs enabled: 3, CIC rate: 2 + if (EXTENDED_TEST) send_ramp(port, 24); // HBs enabled: 3, CIC rate: 3 + send_ramp(port, 40); // HBs enabled: 3, CIC rate: 5 + if (EXTENDED_TEST) send_ramp(port, 200); // HBs enabled: 3, CIC rate: 25 + send_ramp(port, 255); // HBs enabled: 0, CIC rate: 255 + if (EXTENDED_TEST) send_ramp(port, 2040); // HBs enabled: 3, CIC rate: 255 + + test.end_test(); + end + + + //------------------------------------------------------------------------- + // Test timed tune + //------------------------------------------------------------------------- + + // This test has not been implemented because the RFNoC FFT has not been + // ported yet. + + + //------------------------------------------------------------------------- + // Test passing through a partial packet + //------------------------------------------------------------------------- + + test.start_test("Pass through partial packet"); + send_ramp(port, 2, 0, 4); + send_ramp(port, 3, 0, 4); + send_ramp(port, 4, 0, 4); + if (EXTENDED_TEST) send_ramp(port, 8, 0, 4); + send_ramp(port, 13, 0, 4); + if (EXTENDED_TEST) send_ramp(port, 24, 0, 4); + test.end_test(); + + + //------------------------------------------------------------------------- + // 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(); + ddc_clk_gen.kill(); + end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile new file mode 100644 index 000000000..6d1da3d60 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile @@ -0,0 +1,67 @@ +# +# 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 + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the LIB_IP_DIR +include $(LIB_IP_DIR)/axi_hb47/Makefile.inc +include $(LIB_IP_DIR)/complex_multiplier_dds/Makefile.inc +include $(LIB_IP_DIR)/dds_sin_cos_lut_only/Makefile.inc +include $(BASE_DIR)/x300/coregen_dsp/Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(LIB_IP_AXI_HB47_SRCS) \ +$(LIB_IP_COMPLEX_MULTIPLIER_DDS_SRCS) \ +$(LIB_IP_DDS_SIN_COS_LUT_ONLY_SRCS) \ +$(COREGEN_DSP_SRCS) \ +) + +#------------------------------------------------- +# 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_DUC_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_duc_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(abspath rfnoc_block_duc_tb.sv) + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile.srcs new file mode 100644 index 000000000..69b6eaece --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/Makefile.srcs @@ -0,0 +1,11 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_BLOCK_DUC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_duc/, \ +../rfnoc_block_ddc/noc_shell_ddc.v \ +rfnoc_block_duc_regs.vh \ +rfnoc_block_duc.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc.v new file mode 100644 index 000000000..400e9d270 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc.v @@ -0,0 +1,387 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_duc +// +// Description: An digital up-converter block for RFNoC. +// +// Parameters: +// +// THIS_PORTID : Control crossbar port to which this block is connected +// CHDR_W : AXIS CHDR interface data width +// NUM_PORTS : Number of DUC signal processing chains +// 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. +// NUM_HB : Number of half-band filter blocks to include (0-3) +// CIC_MAX_INTERP : Maximum interpolation to support in the CIC filter +// + +module rfnoc_block_duc #( + parameter THIS_PORTID = 0, + parameter CHDR_W = 64, + parameter NUM_PORTS = 2, + parameter MTU = 10, + parameter CTRL_FIFO_SIZE = 6, + parameter NUM_HB = 2, + parameter CIC_MAX_INTERP = 128 +) ( + //--------------------------------------------------------------------------- + // AXIS CHDR Port + //--------------------------------------------------------------------------- + + input wire rfnoc_chdr_clk, + input wire ce_clk, + + // CHDR inputs from framework + input wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, + output wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tready, + + // CHDR outputs to framework + output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, + input wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tready, + + // 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 +); + + // These are the only supported values for now + localparam ITEM_W = 32; + localparam NIPC = 1; + + localparam NOC_ID = 'hD0C0_0000; + + localparam COMPAT_MAJOR = 16'h0; + localparam COMPAT_MINOR = 16'h0; + + `include "rfnoc_block_duc_regs.vh" + `include "../../core/rfnoc_axis_ctrl_utils.vh" + + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + wire rfnoc_chdr_rst; + + wire ctrlport_req_wr; + wire ctrlport_req_rd; + wire [19:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + wire ctrlport_req_has_time; + wire [63:0] ctrlport_req_time; + wire ctrlport_resp_ack; + wire [31:0] ctrlport_resp_data; + + wire [NUM_PORTS*ITEM_W-1:0] m_axis_data_tdata; + wire [ NUM_PORTS-1:0] m_axis_data_tlast; + wire [ NUM_PORTS-1:0] m_axis_data_tvalid; + wire [ NUM_PORTS-1:0] m_axis_data_tready; + wire [ NUM_PORTS*64-1:0] m_axis_data_ttimestamp; + wire [ NUM_PORTS-1:0] m_axis_data_thas_time; + wire [ NUM_PORTS*16-1:0] m_axis_data_tlength; + wire [ NUM_PORTS-1:0] m_axis_data_teob; + wire [ NUM_PORTS*128-1:0] m_axis_data_tuser; + + wire [NUM_PORTS*ITEM_W-1:0] s_axis_data_tdata; + wire [ NUM_PORTS-1:0] s_axis_data_tlast; + wire [ NUM_PORTS-1:0] s_axis_data_tvalid; + wire [ NUM_PORTS-1:0] s_axis_data_tready; + wire [ NUM_PORTS*128-1:0] s_axis_data_tuser; + wire [ NUM_PORTS-1:0] s_axis_data_teob; + wire [ NUM_PORTS*64-1:0] s_axis_data_ttimestamp; + wire [ NUM_PORTS-1:0] s_axis_data_thas_time; + + wire duc_rst; + + // Cross the CHDR reset to the ce_clk domain + synchronizer duc_rst_sync_i ( + .clk (ce_clk), + .rst (1'b0), + .in (rfnoc_chdr_rst), + .out (duc_rst) + ); + + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + // TODO: Replace noc_shell_radio with a customized block + noc_shell_ddc #( + .NOC_ID (NOC_ID), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .CTRLPORT_SLV_EN (0), + .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_ddc_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 (ce_clk), + .ctrlport_rst (duc_rst), + .m_ctrlport_req_wr (ctrlport_req_wr), + .m_ctrlport_req_rd (ctrlport_req_rd), + .m_ctrlport_req_addr (ctrlport_req_addr), + .m_ctrlport_req_data (ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (ctrlport_req_has_time), + .m_ctrlport_req_time (ctrlport_req_time), + .m_ctrlport_resp_ack (ctrlport_resp_ack), + .m_ctrlport_resp_status (AXIS_CTRL_STS_OKAY), + .m_ctrlport_resp_data (ctrlport_resp_data), + .s_ctrlport_req_wr (1'b0), + .s_ctrlport_req_rd (1'b0), + .s_ctrlport_req_addr (20'b0), + .s_ctrlport_req_portid (10'b0), + .s_ctrlport_req_rem_epid (16'b0), + .s_ctrlport_req_rem_portid (10'b0), + .s_ctrlport_req_data (32'b0), + .s_ctrlport_req_byte_en (4'b0), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .axis_data_clk (ce_clk), + .axis_data_rst (duc_rst), + .m_axis_tdata (m_axis_data_tdata), + .m_axis_tkeep (), + .m_axis_tlast (m_axis_data_tlast), + .m_axis_tvalid (m_axis_data_tvalid), + .m_axis_tready (m_axis_data_tready), + .m_axis_ttimestamp (m_axis_data_ttimestamp), + .m_axis_thas_time (m_axis_data_thas_time), + .m_axis_tlength (m_axis_data_tlength), + .m_axis_teov (), + .m_axis_teob (m_axis_data_teob), + .s_axis_tdata (s_axis_data_tdata), + .s_axis_tkeep ({NUM_PORTS*NIPC{1'b1}}), + .s_axis_tlast (s_axis_data_tlast), + .s_axis_tvalid (s_axis_data_tvalid), + .s_axis_tready (s_axis_data_tready), + .s_axis_ttimestamp (s_axis_data_ttimestamp), + .s_axis_thas_time (s_axis_data_thas_time), + .s_axis_teov ({NUM_PORTS{1'b0}}), + .s_axis_teob (s_axis_data_teob) + ); + + + //--------------------------------------------------------------------------- + // Register Translation + //--------------------------------------------------------------------------- + // + // Each DUC block is allocated an address spaces. This block translates CTRL + // port transactions in that space to settings bus. + // + //--------------------------------------------------------------------------- + + wire [ 8*NUM_PORTS-1:0] set_addr; + wire [32*NUM_PORTS-1:0] set_data; + wire [ NUM_PORTS-1:0] set_has_time; + wire [ NUM_PORTS-1:0] set_stb; + wire [64*NUM_PORTS-1:0] set_time; + wire [ 8*NUM_PORTS-1:0] rb_addr; + reg [64*NUM_PORTS-1:0] rb_data; + + ctrlport_to_settings_bus # ( + .NUM_PORTS (NUM_PORTS) + ) ctrlport_to_settings_bus_i ( + .ctrlport_clk (ce_clk), + .ctrlport_rst (duc_rst), + .s_ctrlport_req_wr (ctrlport_req_wr), + .s_ctrlport_req_rd (ctrlport_req_rd), + .s_ctrlport_req_addr (ctrlport_req_addr), + .s_ctrlport_req_data (ctrlport_req_data), + .s_ctrlport_req_has_time (ctrlport_req_has_time), + .s_ctrlport_req_time (ctrlport_req_time), + .s_ctrlport_resp_ack (ctrlport_resp_ack), + .s_ctrlport_resp_data (ctrlport_resp_data), + .set_data (set_data), + .set_addr (set_addr), + .set_stb (set_stb), + .set_time (set_time), + .set_has_time (set_has_time), + .rb_stb ({NUM_PORTS{1'b1}}), + .rb_addr (rb_addr), + .rb_data (rb_data)); + + + //--------------------------------------------------------------------------- + // DUC Implementation + //--------------------------------------------------------------------------- + + // Unused signals + wire [ NUM_PORTS-1:0] clear_tx_seqnum = 0; + wire [16*NUM_PORTS-1:0] src_sid = 0; + wire [16*NUM_PORTS-1:0] next_dst_sid = 0; + + localparam MAX_M = CIC_MAX_INTERP * 2<<(NUM_HB-1); + + genvar i; + generate + for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_duc_chains + wire clear_user; + wire clear_duc = clear_tx_seqnum[i] | clear_user; + + wire set_stb_int = set_stb[i]; + wire [7:0] set_addr_int = set_addr[8*i+7:8*i]; + wire [31:0] set_data_int = set_data[32*i+31:32*i]; + wire [63:0] set_time_int = set_time[64*i+63:64*i]; + wire set_has_time_int = set_has_time[i]; + + // Build the expected tuser CHDR header + cvita_hdr_encoder cvita_hdr_encoder_i ( + .pkt_type (2'b0), + .eob (m_axis_data_teob[i]), + .has_time (m_axis_data_thas_time[i]), + .seqnum (12'b0), + .payload_length (m_axis_data_tlength[16*i +: 16]), + .src_sid (16'b0), + .dst_sid (16'b0), + .vita_time (m_axis_data_ttimestamp[64*i +: 64]), + .header (m_axis_data_tuser[128*i+:128]) + ); + + // Extract bit fields from outgoing tuser CHDR header + assign s_axis_data_teob[i] = s_axis_data_tuser[128*i+124 +: 1]; + assign s_axis_data_thas_time[i] = s_axis_data_tuser[128*i+125 +: 1]; + assign s_axis_data_ttimestamp[64*i+:64] = s_axis_data_tuser[128*i+ 0 +: 64]; + + // TODO Readback register for number of FIR filter taps + always @(*) begin + case(rb_addr[i*8+7:i*8]) + RB_COMPAT_NUM : rb_data[i*64+63:i*64] <= {COMPAT_MAJOR, COMPAT_MINOR}; + RB_NUM_HB : rb_data[i*64+63:i*64] <= NUM_HB; + RB_CIC_MAX_INTERP : rb_data[i*64+63:i*64] <= CIC_MAX_INTERP; + default : rb_data[i*64+63:i*64] <= 64'h0BADC0DE0BADC0DE; + endcase + end + + //////////////////////////////////////////////////////////// + // + // Timed CORDIC + // - Implements timed cordic tunes. Placed between AXI Wrapper + // and AXI Rate Change due to it needing access to the + // vita time of the samples. + // + //////////////////////////////////////////////////////////// + wire [31:0] m_axis_rc_tdata; + wire m_axis_rc_tlast; + wire m_axis_rc_tvalid; + wire m_axis_rc_tready; + wire [127:0] m_axis_rc_tuser; + + dds_timed #( + .SR_FREQ_ADDR(SR_FREQ_ADDR), + .SR_SCALE_IQ_ADDR(SR_SCALE_IQ_ADDR)) + dds_timed ( + .clk(ce_clk), .reset(duc_rst), .clear(clear_tx_seqnum[i]), + .timed_cmd_fifo_full(), + .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int), + .set_time(set_time_int), .set_has_time(set_has_time_int), + .i_tdata(m_axis_rc_tdata), .i_tlast(m_axis_rc_tlast), .i_tvalid(m_axis_rc_tvalid), + .i_tready(m_axis_rc_tready), .i_tuser(m_axis_rc_tuser), + .o_tdata(s_axis_data_tdata[ITEM_W*i+:ITEM_W]), .o_tlast(s_axis_data_tlast[i]), .o_tvalid(s_axis_data_tvalid[i]), + .o_tready(s_axis_data_tready[i]), .o_tuser(s_axis_data_tuser[128*i+:128])); + + //////////////////////////////////////////////////////////// + // + // Increase Rate + // + //////////////////////////////////////////////////////////// + wire [31:0] sample_tdata, sample_duc_tdata; + wire sample_tvalid, sample_tready; + wire sample_duc_tvalid, sample_duc_tready; + axi_rate_change #( + .WIDTH(32), + .MAX_N(1), + .MAX_M(MAX_M), + .SR_N_ADDR(SR_N_ADDR), + .SR_M_ADDR(SR_M_ADDR), + .SR_CONFIG_ADDR(SR_CONFIG_ADDR)) + axi_rate_change ( + .clk(ce_clk), .reset(duc_rst), .clear(clear_tx_seqnum[i]), .clear_user(clear_user), + .src_sid(src_sid[16*i+15:16*i]), .dst_sid(next_dst_sid[16*i+15:16*i]), + .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int), + .i_tdata(m_axis_data_tdata[ITEM_W*i+:ITEM_W]), .i_tlast(m_axis_data_tlast[i]), .i_tvalid(m_axis_data_tvalid[i]), + .i_tready(m_axis_data_tready[i]), .i_tuser(m_axis_data_tuser[128*i+:128]), + .o_tdata(m_axis_rc_tdata), .o_tlast(m_axis_rc_tlast), .o_tvalid(m_axis_rc_tvalid), + .o_tready(m_axis_rc_tready), .o_tuser(m_axis_rc_tuser), + .m_axis_data_tdata({sample_tdata}), .m_axis_data_tlast(), .m_axis_data_tvalid(sample_tvalid), + .m_axis_data_tready(sample_tready), + .s_axis_data_tdata(sample_duc_tdata), .s_axis_data_tlast(1'b0), .s_axis_data_tvalid(sample_duc_tvalid), + .s_axis_data_tready(sample_duc_tready), + .warning_long_throttle(), .error_extra_outputs(), .error_drop_pkt_lockup()); + + //////////////////////////////////////////////////////////// + // + // Digital Up Converter + // + //////////////////////////////////////////////////////////// + duc #( + .SR_INTERP_ADDR(SR_INTERP_ADDR), + .NUM_HB(NUM_HB), + .CIC_MAX_INTERP(CIC_MAX_INTERP)) + duc ( + .clk(ce_clk), .reset(duc_rst), .clear(clear_duc), + .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int), + .i_tdata(sample_tdata), .i_tuser(128'b0), .i_tvalid(sample_tvalid), .i_tready(sample_tready), + .o_tdata(sample_duc_tdata), .o_tuser(), .o_tvalid(sample_duc_tvalid), .o_tready(sample_duc_tready)); + + end + endgenerate + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_regs.vh new file mode 100644 index 000000000..fa239857e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_regs.vh @@ -0,0 +1,25 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_duc_regs (Header) +// +// Description: Header file for RFNoC DUC functionality. This includes +// register offsets, bitfields and constants for the radio components. +// + +// For now, these offsets match the original DUC +localparam DUC_BASE_ADDR = 'h00; +localparam DUC_ADDR_W = 8; + +localparam RB_COMPAT_NUM = 0; +localparam RB_NUM_HB = 1; +localparam RB_CIC_MAX_INTERP = 2; +localparam SR_N_ADDR = 128; +localparam SR_M_ADDR = 129; +localparam SR_CONFIG_ADDR = 130; +localparam SR_INTERP_ADDR = 131; +localparam SR_FREQ_ADDR = 132; +localparam SR_SCALE_IQ_ADDR = 133; + diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_tb.sv new file mode 100644 index 000000000..5bca3f03b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_duc/rfnoc_block_duc_tb.sv @@ -0,0 +1,387 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_duc_tb +// +// Description: Testbench for rfnoc_block_duc +// + + +module rfnoc_block_duc_tb(); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + + `include "rfnoc_block_duc_regs.vh" + + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Simulation parameters + localparam real CHDR_CLK_PER = 5.0; // CHDR clock rate + localparam real DUC_CLK_PER = 4.0; // DUC IP clock rate + localparam int EXTENDED_TEST = 0; // Perform a longer test + localparam int SPP = 128; // Samples per packet + localparam int PKT_SIZE_BYTES = SPP*4; // Bytes per packet + localparam int STALL_PROB = 25; // BFM stall probability + + // Block configuration + localparam int CHDR_W = 64; + localparam int THIS_PORTID = 'h123; + localparam int MTU = 8; + localparam int NUM_PORTS = 1; + localparam int NUM_HB = 3; + localparam int CIC_MAX_INTERP = 128; + + + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + + sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + sim_clock_gen #(DUC_CLK_PER) duc_clk_gen (.clk(ce_clk), .rst()); + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + 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 = + new(backend, m_ctrl, s_ctrl); + + // Connect block controller to BFMs + for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections + initial begin + blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); + blk_ctrl.connect_slave_data_port(i, s_chdr[i]); + blk_ctrl.set_master_stall_prob(i, STALL_PROB); + blk_ctrl.set_slave_stall_prob(i, STALL_PROB); + end + end + + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready; + + logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tready; + + // Map the array of BFMs to a flat vector for the DUT + genvar i; + for (i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections + // Connect BFM master to DUT slave port + assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; + assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast; + assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid; + assign m_chdr[i].tready = s_rfnoc_chdr_tready[i]; + + // Connect BFM slave to DUT master port + assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; + assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i]; + assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i]; + assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; + end + + rfnoc_block_duc #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .NUM_PORTS (NUM_PORTS), + .MTU (MTU), + .NUM_HB (NUM_HB), + .CIC_MAX_INTERP (CIC_MAX_INTERP) + ) rfnoc_block_duc_i ( + .rfnoc_chdr_clk (backend.chdr_clk), + .ce_clk (ce_clk), + .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), + .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) + ); + + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + // Translate the desired register access to a ctrlport write request. + task automatic write_reg(int port, byte unsigned addr, bit [31:0] value); + blk_ctrl.reg_write(256*8*port + addr*8, value); + endtask : write_reg + + + // Translate the desired register access to a ctrlport read request. + task automatic read_user_reg(int port, byte unsigned addr, output logic [63:0] value); + blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); + blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); + endtask : read_user_reg + + + // Set the interpolation rate + task automatic set_interp_rate(int port, int interp_rate); + begin + logic [7:0] cic_rate = 8'd0; + logic [7:0] hb_enables = 2'b0; + + int _interp_rate = interp_rate; + + // Calculate which half bands to enable and whatever is left over set the CIC + while ((_interp_rate[0] == 0) && (hb_enables < NUM_HB)) begin + hb_enables += 1'b1; + _interp_rate = _interp_rate >> 1; + end + + // CIC rate cannot be set to 0 + cic_rate = (_interp_rate[7:0] == 8'd0) ? 8'd1 : _interp_rate[7:0]; + `ASSERT_ERROR(hb_enables <= NUM_HB, "Enabled halfbands may not exceed total number of half bands."); + `ASSERT_ERROR(cic_rate > 0 && cic_rate <= CIC_MAX_INTERP, + "CIC Interpolation rate must be positive, not exceed the max cic interpolation rate, and cannot equal 0!"); + + // Setup DUC + $display("Set interpolation to %0d", interp_rate); + $display("- Number of enabled HBs: %0d", hb_enables); + $display("- CIC Rate: %0d", cic_rate); + write_reg(port, SR_M_ADDR, interp_rate); // Set interpolation rate in AXI rate change + write_reg(port, SR_INTERP_ADDR, {hb_enables, cic_rate}); // Enable HBs, set CIC rate + end + endtask + + + // Test sending packets of ones + task automatic send_ones(int port, int interp_rate, bit has_time); + begin + const bit [63:0] start_time = 64'h0123456789ABCDEF; + + set_interp_rate(port, interp_rate); + + // Setup DUC + write_reg(port, SR_CONFIG_ADDR, 32'd1); // Enable clear EOB + write_reg(port, SR_FREQ_ADDR, 32'd0); // CORDIC phase increment + write_reg(port, SR_SCALE_IQ_ADDR, (1 << 14)); // Scaling, set to 1 + + fork + begin + chdr_word_t send_payload[$]; + packet_info_t pkt_info; + + $display("Send ones"); + + // Generate a payload of all ones + send_payload = {}; + for (int i = 0; i < PKT_SIZE_BYTES/8; i++) begin + send_payload.push_back({16'hffff, 16'hffff, 16'hffff, 16'hffff}); + end + + // Send two packets with EOB on the second packet + pkt_info = 0; + pkt_info.has_time = has_time; + pkt_info.timestamp = start_time; + blk_ctrl.send_packets(port, send_payload, /*data_bytes*/, /*metadata*/, pkt_info); + pkt_info.timestamp = start_time + SPP; + pkt_info.eob = 1; + blk_ctrl.send_packets(port, send_payload, /*data_bytes*/, /*metadata*/, pkt_info); + + $display("Send ones complete"); + end + begin + string s; + chdr_word_t samples; + int data_bytes; + chdr_word_t recv_payload[$]; + chdr_word_t metadata[$]; + packet_info_t pkt_info; + + $display("Check incoming samples"); + for (int i = 0; i < 2*interp_rate; i++) begin + blk_ctrl.recv_adv(port, recv_payload, data_bytes, metadata, pkt_info); + + // Check the packet size + $sformat(s, "incorrect (drop) packet size! expected: %0d, actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size()); + `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s); + + // Check the timestamp + if (has_time) begin + bit [63:0] expected_time; + // Calculate what the timestamp should be + expected_time = start_time + i * SPP; + $sformat(s, "Incorrect timestamp: has_time = %0d, timestamp = 0x%0X, expected 0x%0X", + pkt_info.has_time, pkt_info.timestamp, expected_time); + `ASSERT_ERROR(pkt_info.has_time == 1 && pkt_info.timestamp == expected_time, s); + end else begin + `ASSERT_ERROR(pkt_info.has_time == 0, "Packet has timestamp when it shouldn't"); + end + + // Check EOB + if (i == 2*interp_rate-1) begin + `ASSERT_ERROR(pkt_info.eob == 1, "EOB not set on last packet"); + end else begin + `ASSERT_ERROR(pkt_info.eob == 0, + $sformatf("EOB unexpectedly set on packet %0d", i)); + end + + // Check the sample values + samples = 64'd0; + for (int j = 0; j < PKT_SIZE_BYTES/8; j++) begin + samples = recv_payload[j]; + $sformat(s, "Ramp word %0d invalid! Expected a real value, Received: %0d", 2*j, samples); + `ASSERT_ERROR(samples >= 0, s); + end + end + $display("Check complete"); + end + join + end + endtask + + + //--------------------------------------------------------------------------- + // Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + const int port = 0; + test.start_tb("rfnoc_block_duc_tb"); + + // Start the BFMs running + blk_ctrl.run(); + + + //------------------------------------------------------------------------- + // Reset + //------------------------------------------------------------------------- + + test.start_test("Wait for Reset", 10us); + fork + blk_ctrl.reset_chdr(); + blk_ctrl.reset_ctrl(); + join; + test.end_test(); + + + //------------------------------------------------------------------------- + // Check NoC ID and Block Info + //------------------------------------------------------------------------- + + test.start_test("Verify Block Info", 2us); + `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_duc_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_mtu() == MTU, "Incorrect MTU value"); + test.end_test(); + + + //------------------------------------------------------------------------- + // Test read-back regs + //------------------------------------------------------------------------- + + begin + logic [63:0] val64; + test.start_test("Test registers", 10us); + read_user_reg(port, RB_NUM_HB, val64); + `ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value"); + read_user_reg(port, RB_CIC_MAX_INTERP, val64); + `ASSERT_ERROR(val64 ==CIC_MAX_INTERP, "Register RB_CIC_MAX_INTERP didn't read back expected value"); + test.end_test(); + end + + + //------------------------------------------------------------------------- + // Test various interpolation rates (no timestamp) + //------------------------------------------------------------------------- + + begin + test.start_test("Test interpolation rates (with timestamp)", 0.5ms); + + $display("Note: This test will take a long time!"); + send_ones(port, 1, 1); // HBs enabled: 0, CIC rate: 1 + send_ones(port, 2, 1); // HBs enabled: 1, CIC rate: 1 + send_ones(port, 3, 1); // HBs enabled: 0, CIC rate: 3 + send_ones(port, 4, 1); // HBs enabled: 2, CIC rate: 1 + send_ones(port, 6, 1); // HBs enabled: 1, CIC rate: 3 + send_ones(port, 8, 1); // HBs enabled: 2, CIC rate: 2 + send_ones(port, 12, 1); // HBs enabled: 2, CIC rate: 3 + send_ones(port, 13, 1); // HBs enabled: 0, CIC rate: 13 + send_ones(port, 16, 1); // HBs enabled: 2, CIC rate: 3 + send_ones(port, 40, 1); // HBs enabled: 2, CIC rate: 20 + + test.end_test(); + end + + + //------------------------------------------------------------------------- + // Test various interpolation rates (without timestamp) + //------------------------------------------------------------------------- + + begin + test.start_test("Test interpolation rates (no timestamp)", 0.5ms); + + send_ones(port, 1, 0); // HBs enabled: 0, CIC rate: 1 + send_ones(port, 3, 0); // HBs enabled: 0, CIC rate: 3 + + test.end_test(); + end + + + //------------------------------------------------------------------------- + // Test timed tune + //------------------------------------------------------------------------- + + // This test has not been implemented because the RFNoC FFT has not been + // ported yet. + + + //------------------------------------------------------------------------- + // 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(); + duc_clk_gen.kill(); + end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile new file mode 100644 index 000000000..868246fbd --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile @@ -0,0 +1,62 @@ +# +# 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 + +#------------------------------------------------- +# IP Specific +#------------------------------------------------- +# If simulation contains IP, define the IP_DIR and point +# it to the base level IP directory +LIB_IP_DIR = $(BASE_DIR)/../lib/ip + +# Include makefiles and sources for all IP components +# *after* defining the LIB_IP_DIR +include $(LIB_IP_DIR)/axi_fft/Makefile.inc +include $(LIB_IP_DIR)/complex_to_magphase/Makefile.inc + +DESIGN_SRCS += $(abspath \ +$(LIB_IP_AXI_FFT_OUTS) \ +) + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_fft_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(abspath rfnoc_block_fft_tb.sv) + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs new file mode 100644 index 000000000..21ba967f2 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs @@ -0,0 +1,10 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_fft/, \ +noc_shell_fft.v \ +rfnoc_block_fft.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v new file mode 100644 index 000000000..37a60ef31 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v @@ -0,0 +1,294 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_fft +// + +module noc_shell_fft #( + 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 SYNC_CLKS = 0, + 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 = 5, + parameter CTXT_FIFO_SIZE = 5, + 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_payload_tdata, + output wire [ (NUM_DATA_I*NIPC)-1:0] m_axis_payload_tkeep, + output wire [ NUM_DATA_I-1:0] m_axis_payload_tlast, + output wire [ NUM_DATA_I-1:0] m_axis_payload_tvalid, + input wire [ NUM_DATA_I-1:0] m_axis_payload_tready, + + // Input data stream (from user logic) + input wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_payload_tdata, + input wire [ (NUM_DATA_O*NIPC)-1:0] s_axis_payload_tkeep, + input wire [ NUM_DATA_O-1:0] s_axis_payload_tlast, + input wire [ NUM_DATA_O-1:0] s_axis_payload_tvalid, + output wire [ NUM_DATA_O-1:0] s_axis_payload_tready, + + // Output context stream (to user logic) + output wire [(NUM_DATA_I*CHDR_W)-1:0] m_axis_context_tdata, + output wire [ (4*NUM_DATA_I)-1:0] m_axis_context_tuser, + output wire [ NUM_DATA_I-1:0] m_axis_context_tlast, + output wire [ NUM_DATA_I-1:0] m_axis_context_tvalid, + input wire [ NUM_DATA_I-1:0] m_axis_context_tready, + + // Input context stream (from user logic) + input wire [(NUM_DATA_O*CHDR_W)-1:0] s_axis_context_tdata, + input wire [ (4*NUM_DATA_O)-1:0] s_axis_context_tuser, + input wire [ NUM_DATA_O-1:0] s_axis_context_tlast, + input wire [ NUM_DATA_O-1:0] s_axis_context_tvalid, + output wire [ NUM_DATA_O-1:0] s_axis_context_tready +); + + localparam CTRL_FIFO_SIZE = 5; + + + //--------------------------------------------------------------------------- + // 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_pyld_ctxt #( + .CHDR_W (CHDR_W ), + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .SYNC_CLKS (SYNC_CLKS ), + .CONTEXT_FIFO_SIZE (CTXT_FIFO_SIZE), + .PAYLOAD_FIFO_SIZE (PYLD_FIFO_SIZE), + .CONTEXT_PREFETCH_EN (1 ) + ) chdr_to_axis_pyld_ctxt_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_payload_tdata (m_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), + .m_axis_payload_tkeep (m_axis_payload_tkeep [(i*NIPC)+:NIPC] ), + .m_axis_payload_tlast (m_axis_payload_tlast [i] ), + .m_axis_payload_tvalid(m_axis_payload_tvalid[i] ), + .m_axis_payload_tready(m_axis_payload_tready[i] ), + .m_axis_context_tdata (m_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)] ), + .m_axis_context_tuser (m_axis_context_tuser [(i*4)+:4] ), + .m_axis_context_tlast (m_axis_context_tlast [i] ), + .m_axis_context_tvalid(m_axis_context_tvalid[i] ), + .m_axis_context_tready(m_axis_context_tready[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_pyld_ctxt_to_chdr #( + .CHDR_W (CHDR_W ), + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .SYNC_CLKS (SYNC_CLKS ), + .CONTEXT_FIFO_SIZE (CTXT_FIFO_SIZE), + .PAYLOAD_FIFO_SIZE (PYLD_FIFO_SIZE), + .CONTEXT_PREFETCH_EN (1 ), + .MTU (MTU ) + ) axis_pyld_ctxt_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_payload_tdata (s_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), + .s_axis_payload_tkeep (s_axis_payload_tkeep [(i*NIPC)+:NIPC] ), + .s_axis_payload_tlast (s_axis_payload_tlast [i] ), + .s_axis_payload_tvalid(s_axis_payload_tvalid[i] ), + .s_axis_payload_tready(s_axis_payload_tready[i] ), + .s_axis_context_tdata (s_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)] ), + .s_axis_context_tuser (s_axis_context_tuser [(i*4)+:4] ), + .s_axis_context_tlast (s_axis_context_tlast [i] ), + .s_axis_context_tvalid(s_axis_context_tvalid[i] ), + .s_axis_context_tready(s_axis_context_tready[i] ), + .framer_errors ( ), + .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_fft/rfnoc_block_fft.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.v new file mode 100644 index 000000000..76ae37524 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.v @@ -0,0 +1,559 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fft +// +// Description: An FFT block for RFNoC. +// +// Parameters: +// +// THIS_PORTID : Control crossbar port to which this block is connected +// CHDR_W : AXIS CHDR interface data width +// MTU : Maximum transmission unit (i.e., maximum packet size) in +// CHDR words is 2**MTU. +// EN_MAGNITUDE_OUT : CORDIC based magnitude calculation +// EN_MAGNITUDE_APPROX_OUT : Multipler-less, lower resource usage +// EN_MAGNITUDE_SQ_OUT : Magnitude squared +// EN_FFT_SHIFT : Center zero frequency bin +// + +module rfnoc_block_fft #( + parameter THIS_PORTID = 0, + parameter CHDR_W = 64, + parameter MTU = 10, + + parameter EN_MAGNITUDE_OUT = 0, + parameter EN_MAGNITUDE_APPROX_OUT = 1, + parameter EN_MAGNITUDE_SQ_OUT = 1, + parameter EN_FFT_SHIFT = 1 + ) +( + //--------------------------------------------------------------------------- + // AXIS CHDR Port + //--------------------------------------------------------------------------- + + input wire rfnoc_chdr_clk, + input wire ce_clk, + + // CHDR inputs from framework + input wire [CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire s_rfnoc_chdr_tlast, + input wire s_rfnoc_chdr_tvalid, + output wire s_rfnoc_chdr_tready, + + // CHDR outputs to framework + output wire [CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire m_rfnoc_chdr_tlast, + output wire m_rfnoc_chdr_tvalid, + input wire 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 +); + + // These are the only supported values for now + localparam ITEM_W = 32; + localparam NIPC = 1; + + localparam NOC_ID = 32'hFF70_0000; + + `include "../../core/rfnoc_axis_ctrl_utils.vh" + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + wire rfnoc_chdr_rst; + + wire ctrlport_req_wr; + wire ctrlport_req_rd; + wire [19:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + wire ctrlport_req_has_time; + wire [63:0] ctrlport_req_time; + wire ctrlport_resp_ack; + wire [31:0] ctrlport_resp_data; + + wire [ITEM_W-1:0] axis_to_fft_tdata; + wire axis_to_fft_tlast; + wire axis_to_fft_tvalid; + wire axis_to_fft_tready; + + wire [ITEM_W-1:0] axis_from_fft_tdata; + wire axis_from_fft_tlast; + wire axis_from_fft_tvalid; + wire axis_from_fft_tready; + + wire [CHDR_W-1:0] m_axis_context_tdata; + wire [ 3:0] m_axis_context_tuser; + wire [ 0:0] m_axis_context_tlast; + wire [ 0:0] m_axis_context_tvalid; + wire [ 0:0] m_axis_context_tready; + + wire [CHDR_W-1:0] s_axis_context_tdata; + wire [ 3:0] s_axis_context_tuser; + wire [ 0:0] s_axis_context_tlast; + wire [ 0:0] s_axis_context_tvalid; + wire [ 0:0] s_axis_context_tready; + + wire ce_rst; + + // 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 (ce_clk), + .pulse_b (ce_rst) + ); + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + noc_shell_fft #( + .NOC_ID (NOC_ID ), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W ), + .CTRLPORT_SLV_EN(0 ), + .CTRLPORT_MST_EN(1 ), + .SYNC_CLKS (0 ), + .NUM_DATA_I (1 ), + .NUM_DATA_O (1 ), + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .PYLD_FIFO_SIZE (MTU ), + .CTXT_FIFO_SIZE (1 ), + .MTU (MTU ) + ) noc_shell_fft_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 (ce_clk ), + .ctrlport_rst (ce_rst ), + .m_ctrlport_req_wr (ctrlport_req_wr ), + .m_ctrlport_req_rd (ctrlport_req_rd ), + .m_ctrlport_req_addr (ctrlport_req_addr ), + .m_ctrlport_req_data (ctrlport_req_data ), + .m_ctrlport_req_byte_en ( ), + .m_ctrlport_req_has_time (ctrlport_req_has_time), + .m_ctrlport_req_time (ctrlport_req_time ), + .m_ctrlport_resp_ack (ctrlport_resp_ack ), + .m_ctrlport_resp_status (AXIS_CTRL_STS_OKAY ), + .m_ctrlport_resp_data (ctrlport_resp_data ), + .s_ctrlport_req_wr (1'b0 ), + .s_ctrlport_req_rd (1'b0 ), + .s_ctrlport_req_addr (20'b0 ), + .s_ctrlport_req_portid (10'b0 ), + .s_ctrlport_req_rem_epid (16'b0 ), + .s_ctrlport_req_rem_portid(10'b0 ), + .s_ctrlport_req_data (32'b0 ), + .s_ctrlport_req_byte_en (4'b0 ), + .s_ctrlport_req_has_time (1'b0 ), + .s_ctrlport_req_time (64'b0 ), + .s_ctrlport_resp_ack ( ), + .s_ctrlport_resp_status ( ), + .s_ctrlport_resp_data ( ), + .axis_data_clk (ce_clk ), + .axis_data_rst (ce_rst ), + .m_axis_payload_tdata (axis_to_fft_tdata ), + .m_axis_payload_tkeep ( ), + .m_axis_payload_tlast (axis_to_fft_tlast ), + .m_axis_payload_tvalid (axis_to_fft_tvalid ), + .m_axis_payload_tready (axis_to_fft_tready ), + .s_axis_payload_tdata (axis_from_fft_tdata ), + .s_axis_payload_tkeep ({1*NIPC{1'b1}} ), + .s_axis_payload_tlast (axis_from_fft_tlast ), + .s_axis_payload_tvalid (axis_from_fft_tvalid ), + .s_axis_payload_tready (axis_from_fft_tready ), + .m_axis_context_tdata (m_axis_context_tdata ), + .m_axis_context_tuser (m_axis_context_tuser ), + .m_axis_context_tlast (m_axis_context_tlast ), + .m_axis_context_tvalid (m_axis_context_tvalid), + .m_axis_context_tready (m_axis_context_tready), + .s_axis_context_tdata (s_axis_context_tdata ), + .s_axis_context_tuser (s_axis_context_tuser ), + .s_axis_context_tlast (s_axis_context_tlast ), + .s_axis_context_tvalid (s_axis_context_tvalid), + .s_axis_context_tready (s_axis_context_tready) + ); + + // The input packets are the same configuration as the output packets, so + // just use the header information for each incoming to create the header for + // each outgoing packet. This is done by connecting m_axis_context to + // directly to s_axis_context. + assign s_axis_context_tdata = m_axis_context_tdata; + assign s_axis_context_tuser = m_axis_context_tuser; + assign s_axis_context_tlast = m_axis_context_tlast; + assign s_axis_context_tvalid = m_axis_context_tvalid; + assign m_axis_context_tready = s_axis_context_tready; + + wire [ 8-1:0] set_addr; + wire [32-1:0] set_data; + wire set_has_time; + wire set_stb; + wire [ 8-1:0] rb_addr; + reg [64-1:0] rb_data; + + ctrlport_to_settings_bus # ( + .NUM_PORTS (1) + ) ctrlport_to_settings_bus_i ( + .ctrlport_clk (ce_clk), + .ctrlport_rst (ce_rst), + .s_ctrlport_req_wr (ctrlport_req_wr), + .s_ctrlport_req_rd (ctrlport_req_rd), + .s_ctrlport_req_addr (ctrlport_req_addr), + .s_ctrlport_req_data (ctrlport_req_data), + .s_ctrlport_req_has_time (ctrlport_req_has_time), + .s_ctrlport_req_time (ctrlport_req_time), + .s_ctrlport_resp_ack (ctrlport_resp_ack), + .s_ctrlport_resp_data (ctrlport_resp_data), + .set_data (set_data), + .set_addr (set_addr), + .set_stb (set_stb), + .set_time (), + .set_has_time (set_has_time), + .rb_stb (1'b1), + .rb_addr (rb_addr), + .rb_data (rb_data)); + + localparam MAX_FFT_SIZE_LOG2 = 11; + + localparam [31:0] SR_FFT_RESET = 131; + localparam [31:0] SR_FFT_SIZE_LOG2 = 132; + localparam [31:0] SR_MAGNITUDE_OUT = 133; + localparam [31:0] SR_FFT_DIRECTION = 134; + localparam [31:0] SR_FFT_SCALING = 135; + localparam [31:0] SR_FFT_SHIFT_CONFIG = 136; + + // FFT Output + localparam [1:0] COMPLEX_OUT = 0; + localparam [1:0] MAG_OUT = 1; + localparam [1:0] MAG_SQ_OUT = 2; + + // FFT Direction + localparam [0:0] FFT_REVERSE = 0; + localparam [0:0] FFT_FORWARD = 1; + + wire [1:0] magnitude_out; + wire [31:0] fft_data_o_tdata; + wire fft_data_o_tlast; + wire fft_data_o_tvalid; + wire fft_data_o_tready; + wire [15:0] fft_data_o_tuser; + wire [31:0] fft_shift_o_tdata; + wire fft_shift_o_tlast; + wire fft_shift_o_tvalid; + wire fft_shift_o_tready; + wire [31:0] fft_mag_i_tdata, fft_mag_o_tdata, fft_mag_o_tdata_int; + wire fft_mag_i_tlast, fft_mag_o_tlast; + wire fft_mag_i_tvalid, fft_mag_o_tvalid; + wire fft_mag_i_tready, fft_mag_o_tready; + wire [31:0] fft_mag_sq_i_tdata, fft_mag_sq_o_tdata; + wire fft_mag_sq_i_tlast, fft_mag_sq_o_tlast; + wire fft_mag_sq_i_tvalid, fft_mag_sq_o_tvalid; + wire fft_mag_sq_i_tready, fft_mag_sq_o_tready; + wire [31:0] fft_mag_round_i_tdata, fft_mag_round_o_tdata; + wire fft_mag_round_i_tlast, fft_mag_round_o_tlast; + wire fft_mag_round_i_tvalid, fft_mag_round_o_tvalid; + wire fft_mag_round_i_tready, fft_mag_round_o_tready; + + // Settings Registers + wire fft_reset; + setting_reg #( + .my_addr(SR_FFT_RESET), .awidth(8), .width(1)) + sr_fft_reset ( + .clk(ce_clk), .rst(ce_rst), + .strobe(set_stb), .addr(set_addr), .in(set_data), .out(fft_reset), .changed()); + + // Two instances of FFT size register, one for FFT core and one for FFT shift + localparam DEFAULT_FFT_SIZE = 8; // 256 + wire [7:0] fft_size_log2_tdata ,fft_core_size_log2_tdata; + wire fft_size_log2_tvalid, fft_core_size_log2_tvalid, fft_size_log2_tready, fft_core_size_log2_tready; + axi_setting_reg #( + .ADDR(SR_FFT_SIZE_LOG2), .AWIDTH(8), .WIDTH(8), .DATA_AT_RESET(DEFAULT_FFT_SIZE), .VALID_AT_RESET(1)) + sr_fft_size_log2 ( + .clk(ce_clk), .reset(ce_rst), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .o_tdata(fft_size_log2_tdata), .o_tlast(), .o_tvalid(fft_size_log2_tvalid), .o_tready(fft_size_log2_tready)); + + axi_setting_reg #( + .ADDR(SR_FFT_SIZE_LOG2), .AWIDTH(8), .WIDTH(8), .DATA_AT_RESET(DEFAULT_FFT_SIZE), .VALID_AT_RESET(1)) + sr_fft_size_log2_2 ( + .clk(ce_clk), .reset(ce_rst), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .o_tdata(fft_core_size_log2_tdata), .o_tlast(), .o_tvalid(fft_core_size_log2_tvalid), .o_tready(fft_core_size_log2_tready)); + + // Forward = 0, Reverse = 1 + localparam DEFAULT_FFT_DIRECTION = 0; + wire fft_direction_tdata; + wire fft_direction_tvalid, fft_direction_tready; + axi_setting_reg #( + .ADDR(SR_FFT_DIRECTION), .AWIDTH(8), .WIDTH(1), .DATA_AT_RESET(DEFAULT_FFT_DIRECTION), .VALID_AT_RESET(1)) + sr_fft_direction ( + .clk(ce_clk), .reset(ce_rst), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .o_tdata(fft_direction_tdata), .o_tlast(), .o_tvalid(fft_direction_tvalid), .o_tready(fft_direction_tready)); + + localparam [11:0] DEFAULT_FFT_SCALING = 12'b011010101010; // Conservative 1/N scaling + wire [11:0] fft_scaling_tdata; + wire fft_scaling_tvalid, fft_scaling_tready; + axi_setting_reg #( + .ADDR(SR_FFT_SCALING), .AWIDTH(8), .WIDTH(12), .DATA_AT_RESET(DEFAULT_FFT_SCALING), .VALID_AT_RESET(1)) + sr_fft_scaling ( + .clk(ce_clk), .reset(ce_rst), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .o_tdata(fft_scaling_tdata), .o_tlast(), .o_tvalid(fft_scaling_tvalid), .o_tready(fft_scaling_tready)); + + wire [1:0] fft_shift_config_tdata; + wire fft_shift_config_tvalid, fft_shift_config_tready; + axi_setting_reg #( + .ADDR(SR_FFT_SHIFT_CONFIG), .AWIDTH(8), .WIDTH(2)) + sr_fft_shift_config ( + .clk(ce_clk), .reset(ce_rst), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .o_tdata(fft_shift_config_tdata), .o_tlast(), .o_tvalid(fft_shift_config_tvalid), .o_tready(fft_shift_config_tready)); + + // Synchronize writing configuration to the FFT core + reg fft_config_ready; + wire fft_config_write = fft_config_ready & axis_to_fft_tvalid & axis_to_fft_tready; + always @(posedge ce_clk) begin + if (ce_rst | fft_reset) begin + fft_config_ready <= 1'b1; + end else begin + if (fft_config_write) begin + fft_config_ready <= 1'b0; + end else if (axis_to_fft_tlast) begin + fft_config_ready <= 1'b1; + end + end + end + + wire [23:0] fft_config_tdata = {3'd0, fft_scaling_tdata, fft_direction_tdata, fft_core_size_log2_tdata}; + wire fft_config_tvalid = fft_config_write & (fft_scaling_tvalid | fft_direction_tvalid | fft_core_size_log2_tvalid); + wire fft_config_tready; + assign fft_core_size_log2_tready = fft_config_tready & fft_config_write; + assign fft_direction_tready = fft_config_tready & fft_config_write; + assign fft_scaling_tready = fft_config_tready & fft_config_write; + axi_fft inst_axi_fft ( + .aclk(ce_clk), .aresetn(~(fft_reset)), + .s_axis_data_tvalid(axis_to_fft_tvalid), + .s_axis_data_tready(axis_to_fft_tready), + .s_axis_data_tlast(axis_to_fft_tlast), + .s_axis_data_tdata({axis_to_fft_tdata[15:0],axis_to_fft_tdata[31:16]}), + .m_axis_data_tvalid(fft_data_o_tvalid), + .m_axis_data_tready(fft_data_o_tready), + .m_axis_data_tlast(fft_data_o_tlast), + .m_axis_data_tdata({fft_data_o_tdata[15:0],fft_data_o_tdata[31:16]}), + .m_axis_data_tuser(fft_data_o_tuser), // FFT index + .s_axis_config_tdata(fft_config_tdata), + .s_axis_config_tvalid(fft_config_tvalid), + .s_axis_config_tready(fft_config_tready), + .event_frame_started(), + .event_tlast_unexpected(), + .event_tlast_missing(), + .event_status_channel_halt(), + .event_data_in_channel_halt(), + .event_data_out_channel_halt()); + + // Mux control signals + assign fft_shift_o_tready = (magnitude_out == MAG_OUT) ? fft_mag_i_tready : + (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_i_tready : axis_from_fft_tready; + assign fft_mag_i_tvalid = (magnitude_out == MAG_OUT) ? fft_shift_o_tvalid : 1'b0; + assign fft_mag_i_tlast = (magnitude_out == MAG_OUT) ? fft_shift_o_tlast : 1'b0; + assign fft_mag_i_tdata = fft_shift_o_tdata; + assign fft_mag_o_tready = (magnitude_out == MAG_OUT) ? fft_mag_round_i_tready : 1'b0; + assign fft_mag_sq_i_tvalid = (magnitude_out == MAG_SQ_OUT) ? fft_shift_o_tvalid : 1'b0; + assign fft_mag_sq_i_tlast = (magnitude_out == MAG_SQ_OUT) ? fft_shift_o_tlast : 1'b0; + assign fft_mag_sq_i_tdata = fft_shift_o_tdata; + assign fft_mag_sq_o_tready = (magnitude_out == MAG_SQ_OUT) ? fft_mag_round_i_tready : 1'b0; + assign fft_mag_round_i_tvalid = (magnitude_out == MAG_OUT) ? fft_mag_o_tvalid : + (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_o_tvalid : 1'b0; + assign fft_mag_round_i_tlast = (magnitude_out == MAG_OUT) ? fft_mag_o_tlast : + (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_o_tlast : 1'b0; + assign fft_mag_round_i_tdata = (magnitude_out == MAG_OUT) ? fft_mag_o_tdata : fft_mag_sq_o_tdata; + assign fft_mag_round_o_tready = axis_from_fft_tready; + assign axis_from_fft_tvalid = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tvalid : fft_shift_o_tvalid; + assign axis_from_fft_tlast = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tlast : fft_shift_o_tlast; + assign axis_from_fft_tdata = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tdata : fft_shift_o_tdata; + + // Conditionally synth magnitude / magnitude^2 logic + generate + if (EN_MAGNITUDE_OUT | EN_MAGNITUDE_APPROX_OUT | EN_MAGNITUDE_SQ_OUT) begin : generate_magnitude_out + setting_reg #( + .my_addr(SR_MAGNITUDE_OUT), .awidth(8), .width(2)) + sr_magnitude_out ( + .clk(ce_clk), .rst(ce_rst), + .strobe(set_stb), .addr(set_addr), .in(set_data), .out(magnitude_out), .changed()); + end else begin : generate_magnitude_out_else + // Magnitude calculation logic not included, so always bypass + assign magnitude_out = 2'd0; + end + + if (EN_FFT_SHIFT) begin : generate_fft_shift + fft_shift #( + .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2), + .WIDTH(32)) + inst_fft_shift ( + .clk(ce_clk), .reset(ce_rst | fft_reset), + .config_tdata(fft_shift_config_tdata), + .config_tvalid(fft_shift_config_tvalid), + .config_tready(fft_shift_config_tready), + .fft_size_log2_tdata(fft_size_log2_tdata[$clog2(MAX_FFT_SIZE_LOG2)-1:0]), + .fft_size_log2_tvalid(fft_size_log2_tvalid), + .fft_size_log2_tready(fft_size_log2_tready), + .i_tdata(fft_data_o_tdata), + .i_tlast(fft_data_o_tlast), + .i_tvalid(fft_data_o_tvalid), + .i_tready(fft_data_o_tready), + .i_tuser(fft_data_o_tuser[MAX_FFT_SIZE_LOG2-1:0]), + .o_tdata(fft_shift_o_tdata), + .o_tlast(fft_shift_o_tlast), + .o_tvalid(fft_shift_o_tvalid), + .o_tready(fft_shift_o_tready)); + end + else begin : generate_fft_shift_else + assign fft_shift_o_tdata = fft_data_o_tdata; + assign fft_shift_o_tlast = fft_data_o_tlast; + assign fft_shift_o_tvalid = fft_data_o_tvalid; + assign fft_data_o_tready = fft_shift_o_tready; + end + + // More accurate magnitude calculation takes precedence if enabled + if (EN_MAGNITUDE_OUT) begin : generate_complex_to_magphase + complex_to_magphase + inst_complex_to_magphase ( + .aclk(ce_clk), .aresetn(~(ce_rst | fft_reset)), + .s_axis_cartesian_tvalid(fft_mag_i_tvalid), + .s_axis_cartesian_tlast(fft_mag_i_tlast), + .s_axis_cartesian_tready(fft_mag_i_tready), + .s_axis_cartesian_tdata(fft_mag_i_tdata), + .m_axis_dout_tvalid(fft_mag_o_tvalid), + .m_axis_dout_tlast(fft_mag_o_tlast), + .m_axis_dout_tdata(fft_mag_o_tdata_int), + .m_axis_dout_tready(fft_mag_o_tready)); + assign fft_mag_o_tdata = {1'b0, fft_mag_o_tdata_int[15:0], 15'd0}; + end + else if (EN_MAGNITUDE_APPROX_OUT) begin : generate_complex_to_mag_approx + complex_to_mag_approx + inst_complex_to_mag_approx ( + .clk(ce_clk), .reset(ce_rst | fft_reset), .clear(1'b0), + .i_tvalid(fft_mag_i_tvalid), + .i_tlast(fft_mag_i_tlast), + .i_tready(fft_mag_i_tready), + .i_tdata(fft_mag_i_tdata), + .o_tvalid(fft_mag_o_tvalid), + .o_tlast(fft_mag_o_tlast), + .o_tready(fft_mag_o_tready), + .o_tdata(fft_mag_o_tdata_int[15:0])); + assign fft_mag_o_tdata = {1'b0, fft_mag_o_tdata_int[15:0], 15'd0}; + end + else begin : generate_complex_to_mag_approx_else + assign fft_mag_o_tdata = fft_mag_i_tdata; + assign fft_mag_o_tlast = fft_mag_i_tlast; + assign fft_mag_o_tvalid = fft_mag_i_tvalid; + assign fft_mag_i_tready = fft_mag_o_tready; + end + + if (EN_MAGNITUDE_SQ_OUT) begin : generate_complex_to_magsq + complex_to_magsq + inst_complex_to_magsq ( + .clk(ce_clk), .reset(ce_rst | fft_reset), .clear(1'b0), + .i_tvalid(fft_mag_sq_i_tvalid), + .i_tlast(fft_mag_sq_i_tlast), + .i_tready(fft_mag_sq_i_tready), + .i_tdata(fft_mag_sq_i_tdata), + .o_tvalid(fft_mag_sq_o_tvalid), + .o_tlast(fft_mag_sq_o_tlast), + .o_tready(fft_mag_sq_o_tready), + .o_tdata(fft_mag_sq_o_tdata)); + end + else begin : generate_complex_to_magsq_else + assign fft_mag_sq_o_tdata = fft_mag_sq_i_tdata; + assign fft_mag_sq_o_tlast = fft_mag_sq_i_tlast; + assign fft_mag_sq_o_tvalid = fft_mag_sq_i_tvalid; + assign fft_mag_sq_i_tready = fft_mag_sq_o_tready; + end + + // Convert to SC16 + if (EN_MAGNITUDE_OUT | EN_MAGNITUDE_APPROX_OUT | EN_MAGNITUDE_SQ_OUT) begin : generate_axi_round_and_clip + axi_round_and_clip #( + .WIDTH_IN(32), + .WIDTH_OUT(16), + .CLIP_BITS(1)) + inst_axi_round_and_clip ( + .clk(ce_clk), .reset(ce_rst | fft_reset), + .i_tdata(fft_mag_round_i_tdata), + .i_tlast(fft_mag_round_i_tlast), + .i_tvalid(fft_mag_round_i_tvalid), + .i_tready(fft_mag_round_i_tready), + .o_tdata(fft_mag_round_o_tdata[31:16]), + .o_tlast(fft_mag_round_o_tlast), + .o_tvalid(fft_mag_round_o_tvalid), + .o_tready(fft_mag_round_o_tready)); + assign fft_mag_round_o_tdata[15:0] = {16{16'd0}}; + end + else begin : generate_axi_round_and_clip_else + assign fft_mag_round_o_tdata = fft_mag_round_i_tdata; + assign fft_mag_round_o_tlast = fft_mag_round_i_tlast; + assign fft_mag_round_o_tvalid = fft_mag_round_i_tvalid; + assign fft_mag_round_i_tready = fft_mag_round_o_tready; + end + endgenerate + + // Readback registers + always @* + case(rb_addr) + 3'd0 : rb_data <= {63'd0, fft_reset}; + 3'd1 : rb_data <= {62'd0, magnitude_out}; + 3'd2 : rb_data <= {fft_size_log2_tdata}; + 3'd3 : rb_data <= {63'd0, fft_direction_tdata}; + 3'd4 : rb_data <= {52'd0, fft_scaling_tdata}; + 3'd5 : rb_data <= {62'd0, fft_shift_config_tdata}; + default : rb_data <= 64'h0BADC0DE0BADC0DE; + endcase + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv new file mode 100644 index 000000000..bb46e3cc7 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv @@ -0,0 +1,263 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fft_tb +// +// Description: Testbench for rfnoc_block_fft +// + +module rfnoc_block_fft_tb(); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Simulation parameters + localparam real CHDR_CLK_PER = 5.0; // Clock rate + localparam int SPP = 256; // Samples per packet + localparam int PKT_SIZE_BYTES = SPP*4; // Bytes per packet + localparam int STALL_PROB = 25; // BFM stall probability + + // Block configuration + localparam int CHDR_W = 64; + localparam int THIS_PORTID = 'h123; + localparam int MTU = 10; + localparam int NUM_PORTS = 1; + localparam int NUM_HB = 3; + localparam int CIC_MAX_DECIM = 255; + + // FFT specific settings + // FFT settings + localparam [31:0] FFT_SIZE = 256; + localparam [31:0] FFT_SIZE_LOG2 = $clog2(FFT_SIZE); + const logic [31:0] FFT_DIRECTION = DUT.FFT_FORWARD; // Forward + localparam [31:0] FFT_SCALING = 12'b011010101010; // Conservative scaling of 1/N + localparam [31:0] FFT_SHIFT_CONFIG = 0; // Normal FFT shift + localparam FFT_BIN = FFT_SIZE/8 + FFT_SIZE/2; // 1/8 sample rate freq + FFT shift + localparam NUM_ITERATIONS = 10; + + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + + sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + 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 (rfnoc_chdr_clk, 1'b0); + AxiStreamIf #(CHDR_W) s_chdr (rfnoc_chdr_clk, 1'b0); + + // Bus functional model for a software block controller + RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl = + new(backend, m_ctrl, s_ctrl); + + // Connect block controller to BFMs + initial begin + blk_ctrl.connect_master_data_port(0, m_chdr, PKT_SIZE_BYTES); + blk_ctrl.connect_slave_data_port(0, s_chdr); + blk_ctrl.set_master_stall_prob(0, STALL_PROB); + blk_ctrl.set_slave_stall_prob(0, STALL_PROB); + end + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + rfnoc_block_fft #( + .THIS_PORTID (0 ), + .CHDR_W (64 ), + .MTU (MTU), + + .EN_MAGNITUDE_OUT (0 ), + .EN_MAGNITUDE_APPROX_OUT(1 ), + .EN_MAGNITUDE_SQ_OUT (1 ), + .EN_FFT_SHIFT (1 ) + ) DUT ( + .rfnoc_chdr_clk (backend.chdr_clk), + .ce_clk (backend.chdr_clk), + .s_rfnoc_chdr_tdata (m_chdr.tdata ), + .s_rfnoc_chdr_tlast (m_chdr.tlast ), + .s_rfnoc_chdr_tvalid(m_chdr.tvalid ), + .s_rfnoc_chdr_tready(m_chdr.tready ), + + .m_rfnoc_chdr_tdata (s_chdr.tdata ), + .m_rfnoc_chdr_tlast (s_chdr.tlast ), + .m_rfnoc_chdr_tvalid(s_chdr.tvalid ), + .m_rfnoc_chdr_tready(s_chdr.tready ), + + .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 ) + ); + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + // Translate the desired register access to a ctrlport write request. + task automatic write_reg(int port, byte addr, bit [31:0] value); + blk_ctrl.reg_write(256*8*port + addr*8, value); + endtask : write_reg + + // Translate the desired register access to a ctrlport read request. + task automatic read_user_reg(int port, byte addr, output logic [63:0] value); + blk_ctrl.reg_read(256*8*port + addr*8 + 0, value[31: 0]); + blk_ctrl.reg_read(256*8*port + addr*8 + 4, value[63:32]); + endtask : read_user_reg + + //--------------------------------------------------------------------------- + // Test Process + //--------------------------------------------------------------------------- + + task automatic send_sine_wave ( + input int unsigned port + ); + // Send a sine wave + fork + begin + chdr_word_t send_payload[$]; + + for (int n = 0; n < NUM_ITERATIONS; n++) begin + for (int i = 0; i < (FFT_SIZE/8); i++) begin + send_payload.push_back({ 16'h5A82, 16'h5A82, 16'h7FFF, 16'h0000}); + send_payload.push_back({-16'h5A82, 16'h5A82, 16'h0000, 16'h7FFF}); + send_payload.push_back({-16'h5A82,-16'h5A82,-16'h7FFF, 16'h0000}); + send_payload.push_back({ 16'h5A82,-16'h5A82, 16'h0000,-16'h7FFF}); + end + + blk_ctrl.send(port, send_payload); + blk_ctrl.wait_complete(port); + send_payload = {}; + end + end + + begin + string s; + chdr_word_t recv_payload[$], temp_payload[$]; + int data_bytes; + logic [15:0] real_val; + logic [15:0] cplx_val; + + for (int n = 0; n < NUM_ITERATIONS; n++) begin + blk_ctrl.recv(port, recv_payload, data_bytes); + + `ASSERT_ERROR(recv_payload.size * 2 == FFT_SIZE, "received wrong amount of data"); + + for (int k = 0; k < FFT_SIZE/2; k++) begin + chdr_word_t payload_word; + payload_word = recv_payload.pop_front(); + + for (int i = 0; i < 2; i++) begin + {real_val, cplx_val} = payload_word; + payload_word = payload_word[63:32]; + + if (2*k+i == FFT_BIN) begin + // Assert that for the special case of a 1/8th sample rate sine wave input, + // the real part of the corresponding 1/8th sample rate FFT bin should always be greater than 0 and + // the complex part equal to 0. + + `ASSERT_ERROR(real_val > 32'd0, "FFT bin real part is not greater than 0!"); + `ASSERT_ERROR(cplx_val == 32'd0, "FFT bin complex part is not 0!"); + end else begin + // Assert all other FFT bins should be 0 for both complex and real parts + `ASSERT_ERROR(real_val == 32'd0, "FFT bin real part is not 0!"); + `ASSERT_ERROR(cplx_val == 32'd0, "FFT bin complex part is not 0!"); + end + end + end + end + end + join + endtask + + initial begin : tb_main + const int port = 0; + test.start_tb("rfnoc_block_fft_tb"); + + // Start the BFMs running + blk_ctrl.run(); + + //------------------------------------------------------------------------- + // Reset + //------------------------------------------------------------------------- + + test.start_test("Wait for Reset", 10us); + fork + blk_ctrl.reset_chdr(); + blk_ctrl.reset_ctrl(); + join; + test.end_test(); + + + //------------------------------------------------------------------------- + // Check NoC ID and Block Info + //------------------------------------------------------------------------- + + test.start_test("Verify Block Info", 2us); + `ASSERT_ERROR(blk_ctrl.get_noc_id() == DUT.NOC_ID, "Incorrect NOC_ID Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value"); + `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); + test.end_test(); + + //------------------------------------------------------------------------- + // Setup FFT + //------------------------------------------------------------------------- + + test.start_test("Setup FFT", 10us); + write_reg(port, DUT.SR_FFT_SIZE_LOG2, FFT_SIZE_LOG2); + write_reg(port, DUT.SR_FFT_DIRECTION, FFT_DIRECTION); + write_reg(port, DUT.SR_FFT_SCALING, FFT_SCALING); + write_reg(port, DUT.SR_FFT_SHIFT_CONFIG, FFT_SHIFT_CONFIG); + write_reg(port, DUT.SR_MAGNITUDE_OUT, DUT.COMPLEX_OUT); // Enable real/imag out + test.end_test(); + + //-------------------------------------------------------------------------76 + // Test sine wave + //------------------------------------------------------------------------- + + test.start_test("Test sine wave", 20us); + send_sine_wave (port); + test.end_test(); + + //------------------------------------------------------------------------- + // 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(); + end +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile new file mode 100644 index 000000000..7d6d84f82 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile @@ -0,0 +1,46 @@ +# +# 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_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +SIM_TOP = rfnoc_block_fir_filter_tb + +# Add test bench, user design under test, and +# additional user created files +SIM_SRCS = \ +$(abspath rfnoc_block_fir_filter_tb.sv) + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile.srcs new file mode 100644 index 000000000..f8c696096 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/Makefile.srcs @@ -0,0 +1,12 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_fir_filter/, \ +noc_shell_fir_filter.v \ +rfnoc_fir_filter_regs.vh \ +rfnoc_fir_filter_core.v \ +rfnoc_block_fir_filter.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/noc_shell_fir_filter.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/noc_shell_fir_filter.v new file mode 100644 index 000000000..ce9a66fd9 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/noc_shell_fir_filter.v @@ -0,0 +1,297 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_fir_filter +// +// Description: A NoC Shell for RFNoC. This should eventually be replaced +// by an auto-generated NoC Shell. +// + +module noc_shell_fir_filter #( + 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 SYNC_CLKS = 0, + 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 = 5, + parameter CTXT_FIFO_SIZE = 5, + 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_payload_tdata, + output wire [ (NUM_DATA_I*NIPC)-1:0] m_axis_payload_tkeep, + output wire [ NUM_DATA_I-1:0] m_axis_payload_tlast, + output wire [ NUM_DATA_I-1:0] m_axis_payload_tvalid, + input wire [ NUM_DATA_I-1:0] m_axis_payload_tready, + + // Input data stream (from user logic) + input wire [(NUM_DATA_O*ITEM_W*NIPC)-1:0] s_axis_payload_tdata, + input wire [ (NUM_DATA_O*NIPC)-1:0] s_axis_payload_tkeep, + input wire [ NUM_DATA_O-1:0] s_axis_payload_tlast, + input wire [ NUM_DATA_O-1:0] s_axis_payload_tvalid, + output wire [ NUM_DATA_O-1:0] s_axis_payload_tready, + + // Output context stream (to user logic) + output wire [(NUM_DATA_I*CHDR_W)-1:0] m_axis_context_tdata, + output wire [ (4*NUM_DATA_I)-1:0] m_axis_context_tuser, + output wire [ NUM_DATA_I-1:0] m_axis_context_tlast, + output wire [ NUM_DATA_I-1:0] m_axis_context_tvalid, + input wire [ NUM_DATA_I-1:0] m_axis_context_tready, + + // Input context stream (from user logic) + input wire [(NUM_DATA_O*CHDR_W)-1:0] s_axis_context_tdata, + input wire [ (4*NUM_DATA_O)-1:0] s_axis_context_tuser, + input wire [ NUM_DATA_O-1:0] s_axis_context_tlast, + input wire [ NUM_DATA_O-1:0] s_axis_context_tvalid, + output wire [ NUM_DATA_O-1:0] s_axis_context_tready +); + + localparam CTRL_FIFO_SIZE = 5; + + + //--------------------------------------------------------------------------- + // 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_pyld_ctxt #( + .CHDR_W (CHDR_W ), + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .SYNC_CLKS (SYNC_CLKS ), + .CONTEXT_FIFO_SIZE (CTXT_FIFO_SIZE), + .PAYLOAD_FIFO_SIZE (PYLD_FIFO_SIZE), + .CONTEXT_PREFETCH_EN (1 ) + ) chdr_to_axis_pyld_ctxt_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_payload_tdata (m_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), + .m_axis_payload_tkeep (m_axis_payload_tkeep [(i*NIPC)+:NIPC] ), + .m_axis_payload_tlast (m_axis_payload_tlast [i] ), + .m_axis_payload_tvalid(m_axis_payload_tvalid[i] ), + .m_axis_payload_tready(m_axis_payload_tready[i] ), + .m_axis_context_tdata (m_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)] ), + .m_axis_context_tuser (m_axis_context_tuser [(i*4)+:4] ), + .m_axis_context_tlast (m_axis_context_tlast [i] ), + .m_axis_context_tvalid(m_axis_context_tvalid[i] ), + .m_axis_context_tready(m_axis_context_tready[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_pyld_ctxt_to_chdr #( + .CHDR_W (CHDR_W ), + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .SYNC_CLKS (SYNC_CLKS ), + .CONTEXT_FIFO_SIZE (CTXT_FIFO_SIZE), + .PAYLOAD_FIFO_SIZE (PYLD_FIFO_SIZE), + .CONTEXT_PREFETCH_EN (1 ), + .MTU (MTU ) + ) axis_pyld_ctxt_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_payload_tdata (s_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]), + .s_axis_payload_tkeep (s_axis_payload_tkeep [(i*NIPC)+:NIPC] ), + .s_axis_payload_tlast (s_axis_payload_tlast [i] ), + .s_axis_payload_tvalid(s_axis_payload_tvalid[i] ), + .s_axis_payload_tready(s_axis_payload_tready[i] ), + .s_axis_context_tdata (s_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)] ), + .s_axis_context_tuser (s_axis_context_tuser [(i*4)+:4] ), + .s_axis_context_tlast (s_axis_context_tlast [i] ), + .s_axis_context_tvalid(s_axis_context_tvalid[i] ), + .s_axis_context_tready(s_axis_context_tready[i] ), + .framer_errors ( ), + .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_fir_filter/rfnoc_block_fir_filter.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter.v new file mode 100644 index 000000000..f007049cc --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter.v @@ -0,0 +1,343 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// +// Parameterized FIR filter RFNoC block with optional re-loadable +// coefficients. +// +// It has several optimizations for resource utilization such as using half +// the number of DSP slices for symmetric coefficients, skipping coefficients +// that are always set to zero, and using internal DSP slice registers to +// hold coefficients. +// +// For the most efficient DSP slice inference use these settings, set +// COEFF_WIDTH to be less than 18. +// +// Parameters: +// +// COEFF_WIDTH : Coefficient width +// +// NUM_COEFFS : Number of coefficients / filter taps +// +// COEFFS_VEC : Vector of NUM_COEFFS values, each of width +// COEFF_WIDTH, to initialize the filter +// coefficients. Defaults to an impulse. +// +// RELOADABLE_COEFFS : Enable (1) or disable (0) reloading +// coefficients at runtime +// +// SYMMETRIC_COEFFS : Reduce multiplier usage by approximately half +// if coefficients are symmetric +// +// SKIP_ZERO_COEFFS : Reduce multiplier usage by assuming zero valued +// coefficients in DEFAULT_COEFFS are always zero. +// Useful for halfband filters. +// +// USE_EMBEDDED_REGS_COEFFS : Reduce register usage by only using embedded +// registers in DSP slices. Updating taps while +// streaming will cause temporary output +// corruption! +// +// Note: If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at +// least once since COEFFS_VEC is ignored! +// + + +module rfnoc_block_fir_filter #( + // RFNoC Parameters + parameter THIS_PORTID = 0, + parameter CHDR_W = 64, + parameter NUM_PORTS = 2, + parameter MTU = 10, + // FIR Filter Parameters + parameter COEFF_WIDTH = 16, + parameter NUM_COEFFS = 41, + parameter [NUM_COEFFS*COEFF_WIDTH-1:0] COEFFS_VEC = // Make impulse by default + { + {1'b0, {(COEFF_WIDTH-1){1'b1}} }, // Max positive value + {(COEFF_WIDTH*(NUM_COEFFS-1)){1'b0}} // Zero for remaining coefficients + }, + parameter RELOADABLE_COEFFS = 1, + parameter SYMMETRIC_COEFFS = 0, + parameter SKIP_ZERO_COEFFS = 0, + parameter USE_EMBEDDED_REGS_COEFFS = 1 +)( + // Clock to use for signal processing + input wire ce_clk, + + + //--------------------------------------------------------------------------- + // AXIS CHDR Port + //--------------------------------------------------------------------------- + + input wire rfnoc_chdr_clk, + + // CHDR inputs from framework + input wire [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast, + input wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid, + output wire [ NUM_PORTS-1:0] s_rfnoc_chdr_tready, + + // CHDR outputs to framework + output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, + input wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tready, + + // 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 +); + + `include "rfnoc_fir_filter_regs.vh" + + // These are the only supported values for now + localparam ITEM_W = 32; + localparam NIPC = 1; + + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + wire ctrlport_reg_req_wr; + wire ctrlport_reg_req_rd; + wire [19:0] ctrlport_reg_req_addr; + wire [31:0] ctrlport_reg_req_data; + wire ctrlport_reg_resp_ack; + wire [ 1:0] ctrlport_reg_resp_status; + wire [31:0] ctrlport_reg_resp_data; + + wire [(NUM_PORTS*ITEM_W*NIPC)-1:0] axis_to_fir_tdata; + wire [ NUM_PORTS-1:0] axis_to_fir_tlast; + wire [ NUM_PORTS-1:0] axis_to_fir_tvalid; + wire [ NUM_PORTS-1:0] axis_to_fir_tready; + + wire [(NUM_PORTS*ITEM_W*NIPC)-1:0] axis_from_fir_tdata; + wire [ NUM_PORTS-1:0] axis_from_fir_tlast; + wire [ NUM_PORTS-1:0] axis_from_fir_tvalid; + wire [ NUM_PORTS-1:0] axis_from_fir_tready; + + wire [(NUM_PORTS*CHDR_W)-1:0] m_axis_context_tdata; + wire [ (4*NUM_PORTS)-1:0] m_axis_context_tuser; + wire [ NUM_PORTS-1:0] m_axis_context_tlast; + wire [ NUM_PORTS-1:0] m_axis_context_tvalid; + wire [ NUM_PORTS-1:0] m_axis_context_tready; + + wire [(NUM_PORTS*CHDR_W)-1:0] s_axis_context_tdata; + wire [ (4*NUM_PORTS)-1:0] s_axis_context_tuser; + wire [ NUM_PORTS-1:0] s_axis_context_tlast; + wire [ NUM_PORTS-1:0] s_axis_context_tvalid; + wire [ NUM_PORTS-1:0] s_axis_context_tready; + + wire rfnoc_chdr_rst; + wire ce_rst; + + localparam NOC_ID = 32'hF112_0000; + + + // Cross the CHDR reset to the ddc_clk domain + synchronizer ce_rst_sync_i ( + .clk (ce_clk), + .rst (1'b0), + .in (rfnoc_chdr_rst), + .out (ce_rst) + ); + + + noc_shell_fir_filter #( + .NOC_ID (NOC_ID), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .CTRLPORT_SLV_EN (0), + .CTRLPORT_MST_EN (1), + .NUM_DATA_I (NUM_PORTS), + .NUM_DATA_O (NUM_PORTS), + .ITEM_W (ITEM_W), + .NIPC (NIPC), + .PYLD_FIFO_SIZE (5), + .CTXT_FIFO_SIZE (5), + .MTU (MTU) + ) noc_shell_fir_filter_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 (ce_clk), + .ctrlport_rst (ce_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 (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (ctrlport_reg_resp_ack), + .m_ctrlport_resp_status (ctrlport_reg_resp_status), + .m_ctrlport_resp_data (ctrlport_reg_resp_data), + .s_ctrlport_req_wr (1'b0), + .s_ctrlport_req_rd (1'b0), + .s_ctrlport_req_addr (20'b0), + .s_ctrlport_req_portid (10'b0), + .s_ctrlport_req_rem_epid (16'b0), + .s_ctrlport_req_rem_portid (10'b0), + .s_ctrlport_req_data (32'b0), + .s_ctrlport_req_byte_en (4'hF), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (), + .axis_data_clk (ce_clk), + .axis_data_rst (ce_rst), + .m_axis_payload_tdata (axis_to_fir_tdata), + .m_axis_payload_tkeep (), + .m_axis_payload_tlast (axis_to_fir_tlast), + .m_axis_payload_tvalid (axis_to_fir_tvalid), + .m_axis_payload_tready (axis_to_fir_tready), + .s_axis_payload_tdata (axis_from_fir_tdata), + .s_axis_payload_tkeep ({NUM_PORTS*NIPC{1'b1}}), + .s_axis_payload_tlast (axis_from_fir_tlast), + .s_axis_payload_tvalid (axis_from_fir_tvalid), + .s_axis_payload_tready (axis_from_fir_tready), + .m_axis_context_tdata (m_axis_context_tdata), + .m_axis_context_tuser (m_axis_context_tuser), + .m_axis_context_tlast (m_axis_context_tlast), + .m_axis_context_tvalid (m_axis_context_tvalid), + .m_axis_context_tready (m_axis_context_tready), + .s_axis_context_tdata (s_axis_context_tdata), + .s_axis_context_tuser (s_axis_context_tuser), + .s_axis_context_tlast (s_axis_context_tlast), + .s_axis_context_tvalid (s_axis_context_tvalid), + .s_axis_context_tready (s_axis_context_tready) + ); + + + // The input packets are the same configuration as the output packets, so + // just use the header information for each incoming to create the header for + // each outgoing packet. This is done by connecting m_axis_context to + // directly to s_axis_context. + assign s_axis_context_tdata = m_axis_context_tdata; + assign s_axis_context_tuser = m_axis_context_tuser; + assign s_axis_context_tlast = m_axis_context_tlast; + assign s_axis_context_tvalid = m_axis_context_tvalid; + assign m_axis_context_tready = s_axis_context_tready; + + + //--------------------------------------------------------------------------- + // Control Port Address Decoding + //--------------------------------------------------------------------------- + + wire [ NUM_PORTS-1:0] m_ctrlport_req_wr; + wire [ NUM_PORTS-1:0] m_ctrlport_req_rd; + wire [20*NUM_PORTS-1:0] m_ctrlport_req_addr; + wire [32*NUM_PORTS-1:0] m_ctrlport_req_data; + wire [ NUM_PORTS-1:0] m_ctrlport_resp_ack; + wire [32*NUM_PORTS-1:0] m_ctrlport_resp_data; + + ctrlport_decoder #( + .NUM_SLAVES (NUM_PORTS), + .BASE_ADDR (0), + .SLAVE_ADDR_W (FIR_FILTER_ADDR_W) + ) ctrlport_deocder_i ( + .ctrlport_clk (ce_clk), + .ctrlport_rst (ce_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 (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (ctrlport_reg_resp_ack), + .s_ctrlport_resp_status (ctrlport_reg_resp_status), + .s_ctrlport_resp_data (ctrlport_reg_resp_data), + .m_ctrlport_req_wr (m_ctrlport_req_wr), + .m_ctrlport_req_rd (m_ctrlport_req_rd), + .m_ctrlport_req_addr (m_ctrlport_req_addr), + .m_ctrlport_req_data (m_ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (m_ctrlport_resp_ack), + .m_ctrlport_resp_status ({NUM_PORTS{2'b0}}), + .m_ctrlport_resp_data (m_ctrlport_resp_data) + ); + + + //--------------------------------------------------------------------------- + // FIR Core Instances + //--------------------------------------------------------------------------- + + genvar i; + for (i = 0; i < NUM_PORTS; i = i+1) begin : gen_rfnoc_fir_filter_cores + rfnoc_fir_filter_core #( + .DATA_W (ITEM_W*NIPC), + .COEFF_WIDTH (COEFF_WIDTH), + .NUM_COEFFS (NUM_COEFFS), + .COEFFS_VEC (COEFFS_VEC), + .RELOADABLE_COEFFS (RELOADABLE_COEFFS), + .SYMMETRIC_COEFFS (SYMMETRIC_COEFFS), + .SKIP_ZERO_COEFFS (SKIP_ZERO_COEFFS), + .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) + ) rfnoc_fir_filter_core_i ( + .clk (ce_clk), + .rst (ce_rst), + .s_ctrlport_req_wr (m_ctrlport_req_wr[i]), + .s_ctrlport_req_rd (m_ctrlport_req_rd[i]), + .s_ctrlport_req_addr (m_ctrlport_req_addr[20*i +: 20]), + .s_ctrlport_req_data (m_ctrlport_req_data[32*i +: 32]), + .s_ctrlport_resp_ack (m_ctrlport_resp_ack[i]), + .s_ctrlport_resp_data (m_ctrlport_resp_data[32*i +: 32]), + .s_axis_tdata (axis_to_fir_tdata[i*(ITEM_W*NIPC) +: (ITEM_W*NIPC)]), + .s_axis_tlast (axis_to_fir_tlast[i]), + .s_axis_tvalid (axis_to_fir_tvalid[i]), + .s_axis_tready (axis_to_fir_tready[i]), + .m_axis_tdata (axis_from_fir_tdata[i*(ITEM_W*NIPC) +: (ITEM_W*NIPC)]), + .m_axis_tlast (axis_from_fir_tlast[i]), + .m_axis_tvalid (axis_from_fir_tvalid[i]), + .m_axis_tready (axis_from_fir_tready[i]) + ); + end + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter_tb.sv new file mode 100644 index 000000000..28b5493ac --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_block_fir_filter_tb.sv @@ -0,0 +1,524 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fir_filter_tb +// +// Description: Testbench for rfnoc_block_fir_filter +// + + +module rfnoc_block_fir_filter_tb #( + parameter int NUM_PORTS = 2 +); + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + + `include "rfnoc_fir_filter_regs.vh" + + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + + // Simulation parameters + localparam real CHDR_CLK_PER = 6.0; // 166 MHz + localparam real CE_CLK_PER = 5.0; // 200 MHz + localparam int STALL_PROB = 25; // BFM stall probability + + // DUT parameters to test + localparam int CHDR_W = 64; + localparam int THIS_PORTID = 'h123; + localparam int MTU = 8; + // + localparam int NUM_COEFFS = 41; + localparam int COEFF_WIDTH = 16; + localparam int RELOADABLE_COEFFS = 1; + localparam int SYMMETRIC_COEFFS = 1; + localparam int SKIP_ZERO_COEFFS = 1; + localparam int USE_EMBEDDED_REGS_COEFFS = 1; + + localparam logic [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_0 = { + 16'sd158, 16'sd0, 16'sd33, -16'sd0, -16'sd256, + 16'sd553, 16'sd573, -16'sd542, -16'sd1012, 16'sd349, + 16'sd1536, 16'sd123, -16'sd2097, -16'sd1012, 16'sd1633, + 16'sd1608, -16'sd3077, -16'sd5946, 16'sd3370, 16'sd10513, + 16'sd19295, + 16'sd10513, 16'sd3370, -16'sd5946, -16'sd3077, 16'sd1608, + 16'sd1633, -16'sd1012, -16'sd2097, 16'sd123, 16'sd1536, + 16'sd349, -16'sd1012, -16'sd542, 16'sd573, 16'sd553, + -16'sd256, -16'sd0, 16'sd33, 16'sd0, 16'sd158 + }; + + localparam logic [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_1 = { + 16'sd32767, 16'sd0, -16'sd32767, 16'sd0, 16'sd32767, + -16'sd32767, 16'sd32767, -16'sd32767, 16'sd32767, -16'sd32767, + 16'sd32767, 16'sd32767, 16'sd32767, 16'sd32767, 16'sd32767, + -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, + 16'sd32767, + -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, -16'sd32767, + 16'sd32767, 16'sd32767, 16'sd32767, 16'sd32767, 16'sd32767, + -16'sd32767, 16'sd32767, -16'sd32767, 16'sd32767, -16'sd32767, + 16'sd32767, 16'sd0, -16'sd32767, 16'sd0, 16'sd32767 + }; + + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + + sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(CHDR_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + sim_clock_gen #(CE_CLK_PER) ce_clk_gen (.clk(ce_clk), .rst()); + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + 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 = new(backend, m_ctrl, s_ctrl); + + // Connect block controller to BFMs + for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections + initial begin + blk_ctrl.connect_master_data_port(i, m_chdr[i]); + blk_ctrl.connect_slave_data_port(i, s_chdr[i]); + blk_ctrl.set_master_stall_prob(i, STALL_PROB); + blk_ctrl.set_slave_stall_prob(i, STALL_PROB); + end + end + + + //--------------------------------------------------------------------------- + // DUT + //--------------------------------------------------------------------------- + + logic [NUM_PORTS*CHDR_W-1:0] s_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready; + + logic [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid; + logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tready; + + // Map the array of BFMs to a flat vector for the DUT + genvar i; + for (i = 0; i < NUM_PORTS; i++) begin : gen_dut_connections + // Connect BFM master to DUT slave port + assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; + assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast; + assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid; + assign m_chdr[i].tready = s_rfnoc_chdr_tready[i]; + + // Connect BFM slave to DUT master port + assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; + assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i]; + assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i]; + assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; + end + + + rfnoc_block_fir_filter #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .NUM_PORTS (NUM_PORTS), + .MTU (MTU), + .COEFF_WIDTH (COEFF_WIDTH), + .NUM_COEFFS (NUM_COEFFS), + .COEFFS_VEC (COEFFS_VEC_0), + .RELOADABLE_COEFFS (RELOADABLE_COEFFS), + .SYMMETRIC_COEFFS (SYMMETRIC_COEFFS), + .SKIP_ZERO_COEFFS (SKIP_ZERO_COEFFS), + .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) + ) rfnoc_block_fir_filter_i ( + .ce_clk (ce_clk), + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .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), + .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) + ); + + + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + // Translate the desired register access to a ctrlport write request. + task automatic write_reg(int port, byte addr, bit [31:0] value); + blk_ctrl.reg_write(port * (2**FIR_FILTER_ADDR_W) + addr, value); + endtask : write_reg + + // Translate the desired register access to a ctrlport read request. + task automatic read_reg(int port, byte addr, output logic [31:0] value); + blk_ctrl.reg_read(port * (2**FIR_FILTER_ADDR_W), value); + endtask : read_reg + + + + //--------------------------------------------------------------------------- + // Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + // Display testbench start message + test.start_tb("rfnoc_block_fir_filter_tb"); + + // Start the BFMs running + blk_ctrl.run(); + + + //------------------------------------------------------------------------- + // Reset + //------------------------------------------------------------------------- + + test.start_test("Wait for Reset", 10us); + fork + blk_ctrl.reset_chdr(); + blk_ctrl.reset_ctrl(); + join; + test.end_test(); + + + //------------------------------------------------------------------------- + // Check NoC ID and Block Info + //------------------------------------------------------------------------- + + test.start_test("Verify Block Info", 2us); + `ASSERT_ERROR(blk_ctrl.get_noc_id() == rfnoc_block_fir_filter_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_mtu() == MTU, "Incorrect MTU Value"); + test.end_test(); + + + // Test all ports + for (int port = 0; port < NUM_PORTS; port++) begin : port_loop + + //----------------------------------------------------------------------- + // Check filter length + //----------------------------------------------------------------------- + + begin + int num_coeffs, num_coeffs_to_send; + + test.start_test("Check filter length", 20us); + + read_reg(port, REG_FIR_NUM_COEFFS, num_coeffs); + `ASSERT_ERROR(num_coeffs, "Incorrect number of coefficients"); + + // If using symmetric coefficients, send just first half + if (SYMMETRIC_COEFFS) begin + num_coeffs_to_send = num_coeffs/2 + num_coeffs[0]; + end else begin + num_coeffs_to_send = num_coeffs; + end + + // If using embedded register, coefficients must be preloaded + if (USE_EMBEDDED_REGS_COEFFS) begin + int i; + for (i = 0; i < num_coeffs_to_send-1; i++) begin + write_reg(port, REG_FIR_LOAD_COEFF, COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]); + end + write_reg(port, REG_FIR_LOAD_COEFF_LAST, COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]); + end + + test.end_test(); + end + + + //----------------------------------------------------------------------- + // Test impulse response with default coefficients + //----------------------------------------------------------------------- + // + // Sending an impulse should cause the coefficients to be output. + // + //----------------------------------------------------------------------- + + begin + chdr_word_t send_payload[$]; + chdr_word_t recv_payload[$]; + int num_bytes; + logic signed [15:0] i_samp, q_samp, i_coeff, q_coeff; + string s; + + test.start_test("Test impulse response (default coefficients)", 20us); + + // Generate packet containing an impulse and enqueue it for transfer + send_payload = {}; + send_payload.push_back({16'b0, 16'b0, 16'h7FFF, 16'h7FFF}); + for (int i = 0; i < NUM_COEFFS/2; i++) begin + send_payload.push_back(0); + end + blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + + // Enqueue two packets with zeros to push out the impulse from the + // pipeline (one to push out the data and one to overcome some pipeline + // registering). + send_payload = {}; + for (int i = 0; i < NUM_COEFFS/2+1; i++) begin + send_payload.push_back(0); + end + for (int n = 0; n < 2; n++) begin + blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + end + + // Receive the result + blk_ctrl.recv(port, recv_payload, num_bytes); + + // Check the length of the packet + `ASSERT_ERROR( + num_bytes == NUM_COEFFS*4, + "Received packet didn't have expected length" + ); + + for (int i = 0; i < NUM_COEFFS; i++) begin + // Compute the expected sample + i_coeff = $signed(COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]); + q_coeff = i_coeff; + + // Grab the next sample + {i_samp, q_samp} = recv_payload[i/2][i[0]*32 +: 32]; + + // Check I / Q values + $sformat( + s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d", + i, i_coeff, i_samp); + `ASSERT_ERROR( + (i_samp == i_coeff) || (i_samp-1 == i_coeff) || (i_samp+1 == i_coeff), s); + $sformat( + s, "Incorrect Q value received on sample %0d! Expected: %0d, Received: %0d", + i, q_coeff, q_samp); + `ASSERT_ERROR( + (q_samp == q_coeff) || (q_samp-1 == q_coeff) || (q_samp+1 == q_coeff), s); + end + + test.end_test(); + end + + + //----------------------------------------------------------------------- + // Load new coefficients + //----------------------------------------------------------------------- + + begin + int i; + int num_coeffs_to_send; + + // If using symmetric coefficients, send just first half + if (SYMMETRIC_COEFFS) begin + num_coeffs_to_send = NUM_COEFFS/2 + NUM_COEFFS[0]; + end else begin + num_coeffs_to_send = NUM_COEFFS; + end + + test.start_test("Load new coefficients", 20us); + for (i = 0; i < num_coeffs_to_send-1; i++) begin + write_reg(port, REG_FIR_LOAD_COEFF, COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); + end + write_reg(port, REG_FIR_LOAD_COEFF_LAST, COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); + test.end_test(); + end + + + //----------------------------------------------------------------------- + // Test impulse response with new coefficients + //----------------------------------------------------------------------- + // + // Sending an impulse should cause the coefficients to be output. + // + //----------------------------------------------------------------------- + + begin + chdr_word_t send_payload[$]; + chdr_word_t recv_payload[$]; + int num_bytes; + logic signed [15:0] i_samp, q_samp, i_coeff, q_coeff; + string s; + + test.start_test("Test impulse response (loaded coefficients)", 20us); + + // Generate packet containing an impulse and enqueue it for transfer + send_payload = {}; + send_payload.push_back({16'b0, 16'b0, 16'h7FFF, 16'h7FFF}); + for (int i = 0; i < NUM_COEFFS/2; i++) begin + send_payload.push_back(0); + end + blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + + // Enqueue two packets with zeros to push out the impulse from the + // pipeline (one to push out the data and one to overcome some pipeline + // registering). + send_payload = {}; + for (int i = 0; i < NUM_COEFFS/2+1; i++) begin + send_payload.push_back(0); + end + for (int n = 0; n < 2; n++) begin + blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + end + + // Ignore the first two packets (discard the extra data we put in when + // we checked the default coefficients). + blk_ctrl.recv(port, recv_payload, num_bytes); + blk_ctrl.recv(port, recv_payload, num_bytes); + + // Receive the result + blk_ctrl.recv(port, recv_payload, num_bytes); + + // Check the length of the packet + `ASSERT_ERROR( + num_bytes == NUM_COEFFS*4, + "Received packet didn't have expected length" + ); + + for (int i = 0; i < NUM_COEFFS; i++) begin + // Compute the expected sample + i_coeff = $signed(COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); + q_coeff = i_coeff; + + // Grab the next sample + {i_samp, q_samp} = recv_payload[i/2][i[0]*32 +: 32]; + + // Check I / Q values + $sformat( + s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d", + i, i_coeff, i_samp); + `ASSERT_ERROR( + (i_samp == i_coeff) || (i_samp-1 == i_coeff) || (i_samp+1 == i_coeff), s); + $sformat( + s, "Incorrect Q value received on sample %0d! Expected: %0d, Received: %0d", + i, q_coeff, q_samp); + `ASSERT_ERROR( + (q_samp == q_coeff) || (q_samp-1 == q_coeff) || (q_samp+1 == q_coeff), s); + end + + test.end_test(); + end + + + //----------------------------------------------------------------------- + // Test step response + //----------------------------------------------------------------------- + + begin + chdr_word_t send_payload[$]; + chdr_word_t recv_payload[$]; + int num_bytes; + int coeff_sum; + logic signed [15:0] i_samp, q_samp; + string s; + + test.start_test("Test step response", 20us); + + // Generate a step function packet + send_payload = {}; + for (int i = 0; i < NUM_COEFFS/2+1; i++) begin + send_payload.push_back({16'h7FFF,16'h7FFF,16'h7FFF,16'h7FFF}); + end + + // Enqueue step function two times, once to fill up the pipeline and + // another to get the actual response. + for (int n = 0; n < 2; n++) begin + blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + end + + // Enqueue two packets with zeros to push out the impulse from the + // pipeline (one to push out the data and one to overcome some pipeline + // registering). + send_payload = {}; + for (int i = 0; i < NUM_COEFFS/2+1; i++) begin + send_payload.push_back(0); + end + for (int n = 0; n < 2; n++) begin + blk_ctrl.send(port, send_payload, NUM_COEFFS*4); + end + + // Ignore the first two packets (discard the extra data we put in + // during the previous test). + for (int n = 0; n < 3; n++) begin + blk_ctrl.recv(port, recv_payload, num_bytes); + end + + // Receive the result + blk_ctrl.recv(port, recv_payload, num_bytes); + + // Check the length of the packet + `ASSERT_ERROR( + num_bytes == NUM_COEFFS*4, + "Received packet didn't have expected length" + ); + + // Calculate sum of all the coefficients + coeff_sum = 0; + for (int i = 0; i < NUM_COEFFS; i++) begin + coeff_sum += $signed(COEFFS_VEC_1[COEFF_WIDTH*i +: COEFF_WIDTH]); + end + + for (int i = 0; i < NUM_COEFFS; i++) begin + // Grab the next sample + {i_samp, q_samp} = recv_payload[i/2][i[0]*32 +: 32]; + + // Check I / Q values + $sformat( + s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d", + i, coeff_sum, i_samp); + `ASSERT_ERROR( + (i_samp == coeff_sum) || (i_samp-1 == coeff_sum) || (i_samp+1 == coeff_sum), + s + ); + $sformat( + s, "Incorrect Q value received on sample %0d! Expected: %0d, Received: %0d", + i, coeff_sum, q_samp); + `ASSERT_ERROR( + (q_samp == coeff_sum) || (q_samp-1 == coeff_sum) || (q_samp+1 == coeff_sum), + s + ); + end + + test.end_test(); + end + + end : port_loop + + + //------------------------------------------------------------------------- + // All done! + //------------------------------------------------------------------------- + + test.end_tb(1); + + end : tb_main +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_core.v new file mode 100644 index 000000000..774f43761 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_core.v @@ -0,0 +1,228 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// +// Core module for a single instance of an FIR filter, implementing the +// registers and signal processing for a single I/Q filter. It assumes the +// data stream is an IQ pair with I in the upper 32 bits and Q is the lower +// 32 bits. +// +// Parameters: +// +// DATA_W : Width of the input/output data stream to +// process. +// +// BASE_ADDR : Control port base address to which this block +// responds. +// +// COEFF_WIDTH : Coefficient width +// +// NUM_COEFFS : Number of coefficients / filter taps +// +// COEFFS_VEC : Vector of NUM_COEFFS values, each of width +// COEFF_WIDTH, to initialize the filter +// coefficients. Defaults to an impulse. +// +// RELOADABLE_COEFFS : Enable (1) or disable (0) reloading +// coefficients at runtime +// +// SYMMETRIC_COEFFS : Reduce multiplier usage by approximately half +// if coefficients are symmetric +// +// SKIP_ZERO_COEFFS : Reduce multiplier usage by assuming zero valued +// coefficients in DEFAULT_COEFFS are always zero. +// Useful for halfband filters. +// +// USE_EMBEDDED_REGS_COEFFS : Reduce register usage by only using embedded +// registers in DSP slices. Updating taps while +// streaming will cause temporary output +// corruption! +// +// Note: If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at +// least once since COEFFS_VEC is ignored! + + +module rfnoc_fir_filter_core #( + parameter DATA_W = 32, + parameter [19:0] BASE_ADDR = 0, + + // FIR Filter Parameters + parameter COEFF_WIDTH = 16, + parameter NUM_COEFFS = 41, + parameter [NUM_COEFFS*COEFF_WIDTH-1:0] COEFFS_VEC = // Make impulse by default + { + {1'b0, {(COEFF_WIDTH-1){1'b1}} }, // Max positive value + {(COEFF_WIDTH*(NUM_COEFFS-1)){1'b0}} // Zero for remaining coefficients + }, + parameter RELOADABLE_COEFFS = 1, + parameter SYMMETRIC_COEFFS = 0, + parameter SKIP_ZERO_COEFFS = 0, + parameter USE_EMBEDDED_REGS_COEFFS = 1 +) ( + + input wire clk, + input wire rst, + + //--------------------------------------------------------------------------- + // AXIS CTRL Port + //--------------------------------------------------------------------------- + + // Master + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [19:0] s_ctrlport_req_addr, + input wire [31:0] s_ctrlport_req_data, + output reg s_ctrlport_resp_ack, + output reg [31:0] s_ctrlport_resp_data, + + //--------------------------------------------------------------------------- + // Data Interface + //--------------------------------------------------------------------------- + + // Input data stream + input wire [DATA_W-1:0] s_axis_tdata, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + + // Output data stream + output wire [DATA_W-1:0] m_axis_tdata, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready +); + + reg [COEFF_WIDTH-1:0] m_axis_reload_tdata; + reg m_axis_reload_tvalid; + reg m_axis_reload_tlast; + wire m_axis_reload_tready; + + + //--------------------------------------------------------------------------- + // Registers + //--------------------------------------------------------------------------- + + `include "rfnoc_fir_filter_regs.vh" + + // Separate the address into the block and register portions. Ignore the byte + // offset. + wire [20:0] block_addr = s_ctrlport_req_addr[19:FIR_FILTER_ADDR_W]; + wire [19:0] reg_addr = { s_ctrlport_req_addr[FIR_FILTER_ADDR_W:2], 2'b0 }; + + always @(posedge clk) begin + if (rst) begin + s_ctrlport_resp_ack <= 0; + m_axis_reload_tvalid <= 0; + s_ctrlport_resp_data <= {32{1'bX}}; + m_axis_reload_tdata <= {DATA_W{1'bX}}; + m_axis_reload_tlast <= 1'bX; + end else if (block_addr == BASE_ADDR) begin + // Default assignments + s_ctrlport_resp_ack <= 0; + s_ctrlport_resp_data <= 0; + + // Handle write acknowledgments. Don't ack the register write until it + // gets accepted by the FIR filter. + if (m_axis_reload_tvalid && m_axis_reload_tready) begin + m_axis_reload_tvalid <= 1'b0; + s_ctrlport_resp_ack <= 1'b1; + end + + // Handle register writes + if (s_ctrlport_req_wr) begin + if (reg_addr == REG_FIR_LOAD_COEFF) begin + m_axis_reload_tdata <= s_ctrlport_req_data[COEFF_WIDTH-1:0]; + m_axis_reload_tvalid <= 1'b1; + m_axis_reload_tlast <= 1'b0; + end else if (reg_addr == REG_FIR_LOAD_COEFF_LAST) begin + m_axis_reload_tdata <= s_ctrlport_req_data[COEFF_WIDTH-1:0]; + m_axis_reload_tvalid <= 1'b1; + m_axis_reload_tlast <= 1'b1; + end + end + + // Handle register reads + if (s_ctrlport_req_rd) begin + // Ignore the upper bits so the we respond to any port + if (reg_addr == REG_FIR_NUM_COEFFS) begin + s_ctrlport_resp_data <= NUM_COEFFS; + s_ctrlport_resp_ack <= 1; + end + end + end + end + + + //--------------------------------------------------------------------------- + // FIR Filter Instances + //--------------------------------------------------------------------------- + + localparam IN_WIDTH = DATA_W/2; + localparam OUT_WIDTH = DATA_W/2; + + // I + axi_fir_filter #( + .IN_WIDTH (IN_WIDTH), + .COEFF_WIDTH (COEFF_WIDTH), + .OUT_WIDTH (OUT_WIDTH), + .NUM_COEFFS (NUM_COEFFS), + .COEFFS_VEC (COEFFS_VEC), + .RELOADABLE_COEFFS (RELOADABLE_COEFFS), + .BLANK_OUTPUT (1), + // Optional optimizations + .SYMMETRIC_COEFFS (SYMMETRIC_COEFFS), + .SKIP_ZERO_COEFFS (SKIP_ZERO_COEFFS), + .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) + ) inst_axi_fir_filter_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .s_axis_data_tdata (s_axis_tdata[2*IN_WIDTH-1:IN_WIDTH]), + .s_axis_data_tlast (s_axis_tlast), + .s_axis_data_tvalid (s_axis_tvalid), + .s_axis_data_tready (s_axis_tready), + .m_axis_data_tdata (m_axis_tdata[2*OUT_WIDTH-1:OUT_WIDTH]), + .m_axis_data_tlast (m_axis_tlast), + .m_axis_data_tvalid (m_axis_tvalid), + .m_axis_data_tready (m_axis_tready), + .s_axis_reload_tdata (m_axis_reload_tdata), + .s_axis_reload_tlast (m_axis_reload_tlast), + .s_axis_reload_tvalid (m_axis_reload_tvalid), + .s_axis_reload_tready (m_axis_reload_tready) + ); + + // Q + axi_fir_filter #( + .IN_WIDTH (IN_WIDTH), + .COEFF_WIDTH (COEFF_WIDTH), + .OUT_WIDTH (OUT_WIDTH), + .NUM_COEFFS (NUM_COEFFS), + .COEFFS_VEC (COEFFS_VEC), + .RELOADABLE_COEFFS (RELOADABLE_COEFFS), + .BLANK_OUTPUT (1), + // Optional optimizations + .SYMMETRIC_COEFFS (SYMMETRIC_COEFFS), + .SKIP_ZERO_COEFFS (SKIP_ZERO_COEFFS), + .USE_EMBEDDED_REGS_COEFFS (USE_EMBEDDED_REGS_COEFFS) + ) inst_axi_fir_filter_q ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .s_axis_data_tdata (s_axis_tdata[IN_WIDTH-1:0]), + .s_axis_data_tlast (s_axis_tlast), + .s_axis_data_tvalid (s_axis_tvalid), + .s_axis_data_tready (), + .m_axis_data_tdata (m_axis_tdata[OUT_WIDTH-1:0]), + .m_axis_data_tlast (), + .m_axis_data_tvalid (), + .m_axis_data_tready (m_axis_tready), + .s_axis_reload_tdata (m_axis_reload_tdata), + .s_axis_reload_tlast (m_axis_reload_tlast), + .s_axis_reload_tvalid (m_axis_reload_tvalid), + .s_axis_reload_tready () + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_regs.vh new file mode 100644 index 000000000..0e070e3a3 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fir_filter/rfnoc_fir_filter_regs.vh @@ -0,0 +1,51 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: fir_filter_regs (Header) +// +// Description: Header file for rfnoc_block_fir_filter. All registers are +// 32-bit words from software's perspective. +// + +// Address space size, per FIR filter. That is, each filter is separated in the +// CTRL Port address space by 2^FIR_FILTER_ADDR_W bytes. +localparam FIR_FILTER_ADDR_W = 4; + + + +// REG_FIR_NUM_COEFFS (R) +// +// Contains the number of coefficients for the filter. +// +// [31:0] : Returns the number of coefficients (read-only) +// +localparam REG_FIR_NUM_COEFFS = 'h0; + + +// REG_FIR_LOAD_COEFF (R) +// +// Register for inputting the next coefficient to be loaded into the filter. To +// load a new set of filter coefficients, write NUM_COEFFS-1 coefficients to +// this register, then write the last coefficient to REG_FIR_LOAD_COEFF_LAST. +// The width of each coefficient is set by the COEFF_WIDTH parameter on the +// block. +// +// [31:(32-COEFF_WIDTH)] : Reserved +// [COEFF_WIDTH-1:0] : The next coefficient to be loaded +// +localparam REG_FIR_LOAD_COEFF = 'h4; + + +// REG_FIR_LOAD_COEFF_LAST (R) +// +// Register for inputting the last coefficient to be loaded into the filter. To +// load a new set of filter coefficients, write NUM_COEFFS-1 coefficients to +// REG_FIR_LOAD_COEFF, then write the last coefficient to this register. The +// width of each coefficient is set by the COEFF_WIDTH parameter on the block. +// +// [31:(32-COEFF_WIDTH)] : Reserved +// [COEFF_WIDTH-1:0] : The next coefficient to be loaded +// +localparam REG_FIR_LOAD_COEFF_LAST = 'h8;
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile new file mode 100644 index 000000000..30ce14aec --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile @@ -0,0 +1,45 @@ +# +# 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_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_null_src_sink_tb + +SIM_SRCS = \ +$(abspath rfnoc_block_null_src_sink_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_null_src_sink/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile.srcs new file mode 100644 index 000000000..a99bec7db --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/Makefile.srcs @@ -0,0 +1,12 @@ +# +# Copyright 2019 Ettus Research, A National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_block_null_src_sink/, \ +rfnoc_block_null_src_sink.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink.v new file mode 100644 index 000000000..f4f4d7651 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink.v @@ -0,0 +1,338 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_null_src_sink +// Description: +// +// Parameters: +// +// Signals: + +module rfnoc_block_null_src_sink #( + parameter [9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter NIPC = 2, + parameter [5:0] MTU = 10 +)( + // RFNoC Framework Clocks and Resets + input wire rfnoc_chdr_clk, + input wire rfnoc_ctrl_clk, + // RFNoC Backend Interface + input wire [511:0] rfnoc_core_config, + output wire [511:0] rfnoc_core_status, + // 2 CHDR Input Ports (from framework) + input wire [(CHDR_W*2)-1:0] s_rfnoc_chdr_tdata, + input wire [1:0] s_rfnoc_chdr_tlast, + input wire [1:0] s_rfnoc_chdr_tvalid, + output wire [1:0] s_rfnoc_chdr_tready, + // 2 CHDR Output Ports (to framework) + output wire [(CHDR_W*2)-1:0] m_rfnoc_chdr_tdata, + output wire [1:0] m_rfnoc_chdr_tlast, + output wire [1:0] m_rfnoc_chdr_tvalid, + input wire [1:0] m_rfnoc_chdr_tready, + // AXIS-Ctrl Input Port (from framework) + input wire [31:0] s_rfnoc_ctrl_tdata, + input wire s_rfnoc_ctrl_tlast, + input wire s_rfnoc_ctrl_tvalid, + output wire s_rfnoc_ctrl_tready, + // AXIS-Ctrl Output Port (to framework) + output wire [31:0] m_rfnoc_ctrl_tdata, + output wire m_rfnoc_ctrl_tlast, + output wire m_rfnoc_ctrl_tvalid, + input wire m_rfnoc_ctrl_tready +); + + `include "../../core/rfnoc_chdr_utils.vh" + + localparam [19:0] REG_CTRL_STATUS = 20'h00; + localparam [19:0] REG_SRC_LINES_PER_PKT = 20'h04; + localparam [19:0] REG_SRC_BYTES_PER_PKT = 20'h08; + localparam [19:0] REG_SRC_THROTTLE_CYC = 20'h0C; + localparam [19:0] REG_SNK_LINE_CNT_LO = 20'h10; + localparam [19:0] REG_SNK_LINE_CNT_HI = 20'h14; + localparam [19:0] REG_SNK_PKT_CNT_LO = 20'h18; + localparam [19:0] REG_SNK_PKT_CNT_HI = 20'h1C; + localparam [19:0] REG_SRC_LINE_CNT_LO = 20'h20; + localparam [19:0] REG_SRC_LINE_CNT_HI = 20'h24; + localparam [19:0] REG_SRC_PKT_CNT_LO = 20'h28; + localparam [19:0] REG_SRC_PKT_CNT_HI = 20'h2C; + localparam [19:0] REG_LOOP_LINE_CNT_LO = 20'h30; + localparam [19:0] REG_LOOP_LINE_CNT_HI = 20'h34; + localparam [19:0] REG_LOOP_PKT_CNT_LO = 20'h38; + localparam [19:0] REG_LOOP_PKT_CNT_HI = 20'h3C; + + wire rfnoc_chdr_rst; + wire rfnoc_ctrl_rst; + + wire ctrlport_req_wr; + wire ctrlport_req_rd; + wire [19:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + reg ctrlport_resp_ack; + reg [31:0] ctrlport_resp_data; + + wire [(32*NIPC)-1:0] src_pyld_tdata , snk_pyld_tdata , loop_pyld_tdata ; + wire [NIPC-1:0] src_pyld_tkeep , snk_pyld_tkeep , loop_pyld_tkeep ; + wire src_pyld_tlast , snk_pyld_tlast , loop_pyld_tlast ; + wire src_pyld_tvalid, snk_pyld_tvalid, loop_pyld_tvalid; + wire src_pyld_tready, snk_pyld_tready, loop_pyld_tready; + + wire [CHDR_W-1:0] src_ctxt_tdata , snk_ctxt_tdata , loop_ctxt_tdata ; + wire [3:0] src_ctxt_tuser , snk_ctxt_tuser , loop_ctxt_tuser ; + wire src_ctxt_tlast , snk_ctxt_tlast , loop_ctxt_tlast ; + wire src_ctxt_tvalid, snk_ctxt_tvalid, loop_ctxt_tvalid; + wire src_ctxt_tready, snk_ctxt_tready, loop_ctxt_tready; + + // NoC Shell + // --------------------------- + noc_shell_generic_ctrlport_pyld_chdr #( + .NOC_ID (32'h0000_0001), + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .CTRL_FIFOSIZE (5), + .CTRLPORT_SLV_EN (0), + .NUM_DATA_I (2), + .NUM_DATA_O (2), + .ITEM_W (32), + .NIPC (NIPC), + .MTU (MTU), + .CTXT_FIFOSIZE (1), + .PYLD_FIFOSIZE (1) + ) noc_shell_i ( + .rfnoc_chdr_clk (rfnoc_chdr_clk ), + .rfnoc_chdr_rst (rfnoc_chdr_rst ), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk ), + .rfnoc_ctrl_rst (rfnoc_ctrl_rst ), + .rfnoc_core_config (rfnoc_core_config ), + .rfnoc_core_status (rfnoc_core_status ), + .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 ), + .m_ctrlport_req_wr (ctrlport_req_wr ), + .m_ctrlport_req_rd (ctrlport_req_rd ), + .m_ctrlport_req_addr (ctrlport_req_addr ), + .m_ctrlport_req_data (ctrlport_req_data ), + .m_ctrlport_req_byte_en ( ), + .m_ctrlport_req_has_time ( ), + .m_ctrlport_req_time ( ), + .m_ctrlport_resp_ack (ctrlport_resp_ack ), + .m_ctrlport_resp_status (2'd0 ), + .m_ctrlport_resp_data (ctrlport_resp_data ), + .s_ctrlport_req_wr ('h0 ), + .s_ctrlport_req_rd ('h0 ), + .s_ctrlport_req_addr ('h0 ), + .s_ctrlport_req_portid ('h0 ), + .s_ctrlport_req_rem_epid ('h0 ), + .s_ctrlport_req_rem_portid('h0 ), + .s_ctrlport_req_data ('h0 ), + .s_ctrlport_req_byte_en ('h0 ), + .s_ctrlport_req_has_time ('h0 ), + .s_ctrlport_req_time ('h0 ), + .s_ctrlport_resp_ack ( ), + .s_ctrlport_resp_status ( ), + .s_ctrlport_resp_data ( ), + .m_axis_payload_tdata ({loop_pyld_tdata , snk_pyld_tdata }), + .m_axis_payload_tkeep ({loop_pyld_tkeep , snk_pyld_tkeep }), + .m_axis_payload_tlast ({loop_pyld_tlast , snk_pyld_tlast }), + .m_axis_payload_tvalid ({loop_pyld_tvalid, snk_pyld_tvalid}), + .m_axis_payload_tready ({loop_pyld_tready, snk_pyld_tready}), + .m_axis_context_tdata ({loop_ctxt_tdata , snk_ctxt_tdata }), + .m_axis_context_tuser ({loop_ctxt_tuser , snk_ctxt_tuser }), + .m_axis_context_tlast ({loop_ctxt_tlast , snk_ctxt_tlast }), + .m_axis_context_tvalid ({loop_ctxt_tvalid, snk_ctxt_tvalid}), + .m_axis_context_tready ({loop_ctxt_tready, snk_ctxt_tready}), + .s_axis_payload_tdata ({loop_pyld_tdata , src_pyld_tdata }), + .s_axis_payload_tkeep ({loop_pyld_tkeep , src_pyld_tkeep }), + .s_axis_payload_tlast ({loop_pyld_tlast , src_pyld_tlast }), + .s_axis_payload_tvalid ({loop_pyld_tvalid, src_pyld_tvalid}), + .s_axis_payload_tready ({loop_pyld_tready, src_pyld_tready}), + .s_axis_context_tdata ({loop_ctxt_tdata , src_ctxt_tdata }), + .s_axis_context_tuser ({loop_ctxt_tuser , src_ctxt_tuser }), + .s_axis_context_tlast ({loop_ctxt_tlast , src_ctxt_tlast }), + .s_axis_context_tvalid ({loop_ctxt_tvalid, src_ctxt_tvalid}), + .s_axis_context_tready ({loop_ctxt_tready, src_ctxt_tready}) + ); + + // Packet Counters + // --------------------------- + reg reg_clear_cnts = 1'b0; + reg [63:0] snk_line_cnt = 64'd0, snk_pkt_cnt = 64'd0; + reg [63:0] src_line_cnt = 64'd0, src_pkt_cnt = 64'd0; + reg [63:0] loop_line_cnt = 64'd0, loop_pkt_cnt = 64'd0; + + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst | reg_clear_cnts) begin + snk_line_cnt <= 64'd0; + snk_pkt_cnt <= 64'd0; + src_line_cnt <= 64'd0; + src_pkt_cnt <= 64'd0; + loop_line_cnt <= 64'd0; + loop_pkt_cnt <= 64'd0; + end else begin + if (snk_pyld_tvalid & snk_pyld_tready) begin + snk_line_cnt <= snk_line_cnt + 1; + if (snk_pyld_tlast) + snk_pkt_cnt <= snk_pkt_cnt + 1; + end + if (src_pyld_tvalid & src_pyld_tready) begin + src_line_cnt <= src_line_cnt + 1; + if (src_pyld_tlast) + src_pkt_cnt <= src_pkt_cnt + 1; + end + if (loop_pyld_tvalid & loop_pyld_tready) begin + loop_line_cnt <= loop_line_cnt + 1; + if (loop_pyld_tlast) + loop_pkt_cnt <= loop_pkt_cnt + 1; + end + end + end + + // NULL Sink + // --------------------------- + assign snk_pyld_tready = 1'b1; + assign snk_ctxt_tready = 1'b1; + + // NULL Source + // --------------------------- + reg reg_src_en = 1'b0; + reg [11:0] reg_src_lpp = 12'd0; + reg [15:0] reg_src_bpp = 16'd0; + reg [9:0] reg_throttle_cyc = 10'd0; + + localparam [1:0] ST_HDR = 2'd0; + localparam [1:0] ST_PYLD = 2'd1; + localparam [1:0] ST_WAIT = 2'd2; + + reg [1:0] state = ST_HDR; + reg [11:0] lines_left = 12'd0; + reg [9:0] throttle_cntr = 10'd0; + + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst) begin + state <= ST_HDR; + end else begin + case (state) + ST_HDR: begin + if (src_ctxt_tvalid && src_ctxt_tready) begin + state <= ST_PYLD; + lines_left <= reg_src_lpp; + end + end + ST_PYLD: begin + if (src_pyld_tvalid && src_pyld_tready) begin + if (src_pyld_tlast) begin + if (reg_throttle_cyc == 10'd0) begin + state <= ST_HDR; + end else begin + state <= ST_WAIT; + throttle_cntr <= reg_throttle_cyc; + end + end else begin + lines_left <= lines_left - 12'd1; + end + end + end + ST_WAIT: begin + if (throttle_cntr == 10'd0) + state <= ST_HDR; + else + throttle_cntr <= throttle_cntr - 10'd1; + end + default: begin + state <= ST_HDR; + end + endcase + end + end + + assign src_pyld_tdata = {NIPC{{~src_line_cnt[15:0], src_line_cnt[15:0]}}}; + assign src_pyld_tkeep = {NIPC{1'b1}}; + assign src_pyld_tlast = (lines_left == 12'd0); + assign src_pyld_tvalid = (state == ST_PYLD); + + assign src_ctxt_tdata = chdr_build_header( + 6'd0, 1'b0, 1'b0, CHDR_PKT_TYPE_DATA, CHDR_NO_MDATA, src_pkt_cnt[15:0], reg_src_bpp, 16'd0); + assign src_ctxt_tuser = CONTEXT_FIELD_HDR; + assign src_ctxt_tlast = 1'b1; + assign src_ctxt_tvalid = (state == ST_HDR && reg_src_en); + + + // Register Interface + // --------------------------- + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst) begin + ctrlport_resp_ack <= 1'b0; + end else begin + // All transactions finish in 1 cycle + ctrlport_resp_ack <= ctrlport_req_wr | ctrlport_req_rd; + // Handle register writes + if (ctrlport_req_wr) begin + case(ctrlport_req_addr) + REG_CTRL_STATUS: + {reg_src_en, reg_clear_cnts} <= ctrlport_req_data[1:0]; + REG_SRC_LINES_PER_PKT: + reg_src_lpp <= ctrlport_req_data[11:0]; + REG_SRC_BYTES_PER_PKT: + reg_src_bpp <= ctrlport_req_data[15:0]; + REG_SRC_THROTTLE_CYC: + reg_throttle_cyc <= ctrlport_req_data[9:0]; + endcase + end + // Handle register reads + if (ctrlport_req_rd) begin + case(ctrlport_req_addr) + REG_CTRL_STATUS: + ctrlport_resp_data <= {NIPC[7:0], 8'd32, state, 12'h0, reg_src_en, reg_clear_cnts}; + REG_SRC_LINES_PER_PKT: + ctrlport_resp_data <= {20'h0, reg_src_lpp}; + REG_SRC_BYTES_PER_PKT: + ctrlport_resp_data <= {16'h0, reg_src_bpp}; + REG_SRC_THROTTLE_CYC: + ctrlport_resp_data <= {22'h0, reg_throttle_cyc}; + REG_SNK_LINE_CNT_LO: + ctrlport_resp_data <= snk_line_cnt[31:0]; + REG_SNK_LINE_CNT_HI: + ctrlport_resp_data <= snk_line_cnt[63:32]; + REG_SNK_PKT_CNT_LO: + ctrlport_resp_data <= snk_pkt_cnt[31:0]; + REG_SNK_PKT_CNT_HI: + ctrlport_resp_data <= snk_pkt_cnt[63:32]; + REG_SRC_LINE_CNT_LO: + ctrlport_resp_data <= src_line_cnt[31:0]; + REG_SRC_LINE_CNT_HI: + ctrlport_resp_data <= src_line_cnt[63:32]; + REG_SRC_PKT_CNT_LO: + ctrlport_resp_data <= src_pkt_cnt[31:0]; + REG_SRC_PKT_CNT_HI: + ctrlport_resp_data <= src_pkt_cnt[63:32]; + REG_LOOP_LINE_CNT_LO: + ctrlport_resp_data <= loop_line_cnt[31:0]; + REG_LOOP_LINE_CNT_HI: + ctrlport_resp_data <= loop_line_cnt[63:32]; + REG_LOOP_PKT_CNT_LO: + ctrlport_resp_data <= loop_pkt_cnt[31:0]; + REG_LOOP_PKT_CNT_HI: + ctrlport_resp_data <= loop_pkt_cnt[63:32]; + default: + ctrlport_resp_data <= 32'h0; + endcase + end + end + end + +endmodule // rfnoc_block_null_src_sink diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink_tb.sv new file mode 100644 index 000000000..f25e762b3 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_null_src_sink/rfnoc_block_null_src_sink_tb.sv @@ -0,0 +1,268 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_null_src_sink_tb +// + +`default_nettype none + + +module rfnoc_block_null_src_sink_tb; + + // Include macros and time declarations for use with PkgTestExec + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + import PkgRfnocItemUtils::*; + + // Parameters + localparam [9:0] THIS_PORTID = 10'h17; + localparam [15:0] THIS_EPID = 16'hDEAD; + localparam int CHDR_W = 64; + localparam int SPP = 201; + localparam int LPP = ((SPP+1)/2); + localparam int NUM_PKTS = 50; + + localparam int PORT_SRCSNK = 0; + localparam int PORT_LOOP = 1; + + // Clock and Reset Definition + bit rfnoc_chdr_clk; + sim_clock_gen #(2.5) rfnoc_chdr_clk_gen (rfnoc_chdr_clk); // 400 MHz + + // ---------------------------------------- + // Instantiate DUT + // ---------------------------------------- + + // Connections to DUT as interfaces: + RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_chdr_clk); // Required backend iface + AxiStreamIf #(32) m_ctrl (rfnoc_chdr_clk); // Required control iface + AxiStreamIf #(32) s_ctrl (rfnoc_chdr_clk); // Required control iface + AxiStreamIf #(CHDR_W) m0_chdr (rfnoc_chdr_clk); // Optional data iface + AxiStreamIf #(CHDR_W) m1_chdr (rfnoc_chdr_clk); // Optional data iface + AxiStreamIf #(CHDR_W) s0_chdr (rfnoc_chdr_clk); // Optional data iface + AxiStreamIf #(CHDR_W) s1_chdr (rfnoc_chdr_clk); // Optional data iface + + // Bus functional model for a software block controller + RfnocBlockCtrlBfm #(.CHDR_W(CHDR_W)) blk_ctrl; + + // DUT + rfnoc_block_null_src_sink #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .NIPC (2), + .MTU (10) + ) dut ( + .rfnoc_chdr_clk (backend.chdr_clk), + .rfnoc_ctrl_clk (backend.ctrl_clk), + .rfnoc_core_config (backend.slave.cfg), + .rfnoc_core_status (backend.slave.sts), + .s_rfnoc_chdr_tdata ({m1_chdr.slave.tdata , m0_chdr.slave.tdata }), + .s_rfnoc_chdr_tlast ({m1_chdr.slave.tlast , m0_chdr.slave.tlast }), + .s_rfnoc_chdr_tvalid({m1_chdr.slave.tvalid , m0_chdr.slave.tvalid }), + .s_rfnoc_chdr_tready({m1_chdr.slave.tready , m0_chdr.slave.tready }), + .m_rfnoc_chdr_tdata ({s1_chdr.master.tdata , s0_chdr.master.tdata }), + .m_rfnoc_chdr_tlast ({s1_chdr.master.tlast , s0_chdr.master.tlast }), + .m_rfnoc_chdr_tvalid({s1_chdr.master.tvalid, s0_chdr.master.tvalid}), + .m_rfnoc_chdr_tready({s1_chdr.master.tready, s0_chdr.master.tready}), + .s_rfnoc_ctrl_tdata (m_ctrl.slave.tdata ), + .s_rfnoc_ctrl_tlast (m_ctrl.slave.tlast ), + .s_rfnoc_ctrl_tvalid(m_ctrl.slave.tvalid ), + .s_rfnoc_ctrl_tready(m_ctrl.slave.tready ), + .m_rfnoc_ctrl_tdata (s_ctrl.master.tdata ), + .m_rfnoc_ctrl_tlast (s_ctrl.master.tlast ), + .m_rfnoc_ctrl_tvalid(s_ctrl.master.tvalid), + .m_rfnoc_ctrl_tready(s_ctrl.master.tready) + ); + + // ---------------------------------------- + // Test Process + // ---------------------------------------- + + initial begin + // Shared Variables + // ---------------------------------------- + timeout_t timeout; + ctrl_word_t rvalue = 0; + + // Initialize + // ---------------------------------------- + test.start_tb("rfnoc_block_null_src_sink_tb"); + + // Start the stream endpoint BFM + blk_ctrl = new(backend, m_ctrl, s_ctrl); + blk_ctrl.add_master_data_port(m0_chdr); + blk_ctrl.add_slave_data_port(s0_chdr); + blk_ctrl.add_master_data_port(m1_chdr); + blk_ctrl.add_slave_data_port(s1_chdr); + blk_ctrl.run(); + + // Startup block (Software initialization) + // ---------------------------------------- + test.start_test("Flush block then reset it"); + begin + test.start_timeout(timeout, 10us, "Waiting for flush_and_reset"); + #100; //Wait for GSR to deassert + blk_ctrl.flush_and_reset(); + test.end_timeout(timeout); + end + test.end_test(); + + // Run Tests + // ---------------------------------------- + test.start_test("Read Block Info"); + begin + test.start_timeout(timeout, 1us, "Waiting for block info response"); + // Get static block info and validate it + `ASSERT_ERROR(blk_ctrl.get_noc_id() == 1, "Incorrect noc_id Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_i() == 2, "Incorrect num_data_i Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_o() == 2, "Incorrect num_data_o Value"); + `ASSERT_ERROR(blk_ctrl.get_ctrl_fifosize() == 5, "Incorrect ctrl_fifosize Value"); + `ASSERT_ERROR(blk_ctrl.get_mtu() == 10, "Incorrect mtu Value"); + + // Read status register and validate it + blk_ctrl.reg_read(dut.REG_CTRL_STATUS, rvalue); + `ASSERT_ERROR(rvalue[31:24] == 2, "Incorrect NIPC Value"); + `ASSERT_ERROR(rvalue[23:16] == 32, "Incorrect ITEM_W Value"); + test.end_timeout(timeout); + end + test.end_test(); + + test.start_test("Stream Data Through Loopback Port"); + begin + // Send and receive packets + repeat (NUM_PKTS) begin + chdr_word_t rx_data[$]; + int rx_bytes; + automatic ItemDataBuff #(logic[31:0]) tx_dbuff = new, rx_dbuff = new; + for (int i = 0; i < SPP; i++) + tx_dbuff.put($urandom()); + test.start_timeout(timeout, 5us, "Waiting for pkt to loop back"); + blk_ctrl.send(PORT_LOOP, tx_dbuff.to_chdr_payload(), tx_dbuff.get_bytes()); + blk_ctrl.recv(PORT_LOOP, rx_data, rx_bytes); + rx_dbuff.from_chdr_payload(rx_data, rx_bytes); + `ASSERT_ERROR(rx_dbuff.equal(tx_dbuff), "Data mismatch"); + test.end_timeout(timeout); + end + + // Read item and packet counts on loopback port + blk_ctrl.reg_read(dut.REG_LOOP_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == (LPP*NUM_PKTS), "Incorrect REG_LOOP_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_LOOP_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == NUM_PKTS, "Incorrect REG_LOOP_PKT_CNT_LO value"); + + // Read item and packet counts on source port + blk_ctrl.reg_read(dut.REG_SRC_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_PKT_CNT_LO value"); + + // Read item and packet counts on sink port + blk_ctrl.reg_read(dut.REG_SNK_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_SNK_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_PKT_CNT_LO value"); + end + test.end_test(); + + test.start_test("Stream Data To Sink Port"); + begin + // Send packets + repeat (NUM_PKTS) begin + chdr_word_t rx_data[$]; + int rx_bytes; + automatic ItemDataBuff #(logic[31:0]) tx_dbuff = new; + for (int i = 0; i < SPP; i++) + tx_dbuff.put($urandom()); + test.start_timeout(timeout, 5us, "Waiting for pkt to loop back"); + blk_ctrl.send(PORT_SRCSNK, tx_dbuff.to_chdr_payload(), tx_dbuff.get_bytes()); + test.end_timeout(timeout); + end + repeat (NUM_PKTS * SPP * 2) @(posedge rfnoc_chdr_clk); + + // Read item and packet counts on loopback port + blk_ctrl.reg_read(dut.REG_LOOP_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == (LPP*NUM_PKTS), "Incorrect REG_LOOP_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_LOOP_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == NUM_PKTS, "Incorrect REG_LOOP_PKT_CNT_LO value"); + + // Read item and packet counts on source port + blk_ctrl.reg_read(dut.REG_SRC_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_PKT_CNT_LO value"); + + // Read item and packet counts on sink port + blk_ctrl.reg_read(dut.REG_SNK_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == (LPP*NUM_PKTS), "Incorrect REG_SNK_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_SNK_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == NUM_PKTS, "Incorrect REG_SNK_PKT_CNT_LO value"); + end + test.end_test(); + + test.start_test("Stream Data From Source Port"); + begin + // Turn on the source for some time then stop it + blk_ctrl.reg_write(dut.REG_SRC_LINES_PER_PKT, LPP-1); + blk_ctrl.reg_write(dut.REG_SRC_BYTES_PER_PKT, (LPP+1)*8); + blk_ctrl.reg_write(dut.REG_CTRL_STATUS, 2'b10); + repeat ((NUM_PKTS / 10) * LPP) @(posedge rfnoc_chdr_clk); + blk_ctrl.reg_write(dut.REG_CTRL_STATUS, 2'b00); + blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); + repeat (rvalue * LPP * 2) @(posedge rfnoc_chdr_clk); + blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); + + // Gather the accumulated packets and verify contents + for (int p = 0; p < rvalue; p++) begin + chdr_word_t exp_data[$]; + chdr_word_t rx_data[$]; + int rx_bytes; + test.start_timeout(timeout, 5us, "Waiting for pkt to arrive"); + exp_data.delete(); + for (int i = p*LPP; i < (p+1)*LPP; i++) + exp_data.push_back({~i[15:0], i[15:0], ~i[15:0], i[15:0]}); + blk_ctrl.recv(PORT_SRCSNK, rx_data, rx_bytes); + `ASSERT_ERROR(blk_ctrl.compare_data(exp_data, rx_data), "Data mismatch"); + test.end_timeout(timeout); + end + end + test.end_test(); + + test.start_test("Clear Counts"); + begin + test.start_timeout(timeout, 1us, "Waiting for clear and readbacks"); + // Clear + blk_ctrl.reg_write(dut.REG_CTRL_STATUS, 2'b01); + + // Read item and packet counts on loopback port + blk_ctrl.reg_read(dut.REG_LOOP_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_LOOP_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_LOOP_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_LOOP_PKT_CNT_LO value"); + + // Read item and packet counts on source port + blk_ctrl.reg_read(dut.REG_SRC_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_SRC_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SRC_PKT_CNT_LO value"); + + // Read item and packet counts on sink port + blk_ctrl.reg_read(dut.REG_SNK_LINE_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_LINE_CNT_LO value"); + blk_ctrl.reg_read(dut.REG_SNK_PKT_CNT_LO, rvalue); + `ASSERT_ERROR(rvalue == 0, "Incorrect REG_SNK_PKT_CNT_LO value"); + test.end_timeout(timeout); + end + test.end_test(); + + // Finish Up + // ---------------------------------------- + // Display final statistics and results + test.end_tb(); + end + +endmodule 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 |