From 6b19ec030c15da7d89b243ee2224c29c1a042543 Mon Sep 17 00:00:00 2001 From: Wade Fife Date: Fri, 8 Nov 2024 16:15:17 -0600 Subject: [PATCH] fpga: rfnoc: fft: Support multiple samples per cycle This adds the NIPC parameter, which configures support for processing multiple items or samples per clock cycle. With this enabled, the FFT block can process at rates higher than 250 MSPS, such as 500 MSPS and beyond. Original-commit: fc76aa940e121fe1f85a3513f6d90df4667338cf --- .../blocks/rfnoc_block_fft/Makefile.srcs | 4 +- .../blocks/rfnoc_block_fft/cp_removal.sv | 217 +--- lib/rfnoc/blocks/rfnoc_block_fft/fft_core.sv | 1058 ++++++++------- .../rfnoc_block_fft/fft_core_regs_pkg.sv | 35 +- .../blocks/rfnoc_block_fft/fft_depacketize.sv | 14 +- .../blocks/rfnoc_block_fft/fft_packetize.sv | 4 +- .../blocks/rfnoc_block_fft/fft_pipeline.sv | 442 +++++++ .../rfnoc_block_fft/fft_pipeline_wrapper.sv | 310 +++++ .../rfnoc_block_fft/fft_post_processing.sv | 2 +- .../blocks/rfnoc_block_fft/fft_reorder.sv | 7 +- .../blocks/rfnoc_block_fft/noc_shell_fft.v | 4 +- .../blocks/rfnoc_block_fft/rfnoc_block_fft.sv | 172 +-- .../rfnoc_block_fft/rfnoc_block_fft_all_tb.sv | 106 +- .../rfnoc_block_fft/rfnoc_block_fft_tb.sv | 1141 ++++++++++++----- 14 files changed, 2433 insertions(+), 1083 deletions(-) create mode 100644 lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline.sv create mode 100644 lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline_wrapper.sv diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs b/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs index 606f716..24b97f0 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs +++ b/lib/rfnoc/blocks/rfnoc_block_fft/Makefile.srcs @@ -8,14 +8,16 @@ RFNOC_OOT_SRCS += $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/blocks/rfnoc_bl fft_reorder_pkg.sv \ fft_reorder.sv \ fft_post_processing.sv \ -cp_removal.sv \ axis_cp_list.sv \ +cp_removal.sv \ noc_shell_fft.v \ xfft_config_pkg.sv \ fft_core_regs_pkg.sv \ fft_packetize_pkg.sv \ fft_packetize.sv \ fft_depacketize.sv \ +fft_pipeline.sv \ +fft_pipeline_wrapper.sv \ xfft_wrapper.sv \ fft_core.sv \ rfnoc_block_fft.sv \ diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/cp_removal.sv b/lib/rfnoc/blocks/rfnoc_block_fft/cp_removal.sv index c66d84b..5236785 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/cp_removal.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/cp_removal.sv @@ -7,228 +7,109 @@ // // Description: // -// Removes the cyclic prefix from OFDM symbols. A configuration list allows -// for queuing up multiple cyclic prefix lengths, and has an optional repeat -// mode that causes the same list of cyclic prefixes to be reused as new -// symbols arrive. This allows the block to execute a pattern for cases when -// CP lengths change symbol to symbol in a repeating pattern. -// -// There is a two-clock bubble cycle after every symbol due to returning to -// the idle state to load the next config, so this block must be clocked at -// least slightly faster than the sample rate. That is: -// -// Clock rate > Fs * (1 + 2/(CP length + FFT Size)) +// Removes the cyclic prefix from OFDM symbols. This module assumes that each +// AXI-stream input packet is one symbol with a prefix and it will output one +// packet per symbol with the prefix removed. The cyclic prefix length to be +// removed is input on the cp_len AXI-Stream input port, and it must be +// present at the start of each data packet until cp_len_tready is asserted. +// There is a one-cycle bubble at the start of each symbol to register cyclic +// prefix length. // // Parameters: // -// DATA_W : Data/sample AXI-Stream bus width -// USER_W : Width of TUSER on the data/sample AXI-Stream bus -// SYM_LEN_W : Width of the maximum symbol length. The maximum -// supported symbol length is 2**SYM_LEN_W - 1. -// CP_LEN_W : Width of the maximum cyclic prefix length. The maximum -// supported CP length is 2**CP_LEN_W - 1. -// DEFAULT_CP_LEN : Default cyclic prefix length to output -// CP_REPEAT : 1: Cyclic prefix list repeats. 0: Cyclic prefix list -// does not repeat, and the last used prefix length will -// be used once the list is completed. -// MAX_LIST_LOG2 : Log base 2 of the size of the prefix length list -// SET_TLAST : 1: Always set tlast at the end of each symbol. 0: Pass -// through input tlast unchanged. -// -// Signals: -// -// clear_list : Clear the CP removal list -// symbol_len : Symbol/FFT size to use for generating TLAST -// cp_len_t* : AXI-Stream cyclic prefix length list input. Use this to -// write prefix lengths to the list in order. -// cp_list_occupied : Number of items in the cyclic prefix list -// i_t* : AXI-Stream data input on which to do cyclic prefix removal -// o_t* : AXI-Stream data output with cyclic prefix removed +// CP_LEN_W : Width of the maximum cyclic prefix length. The maximum +// supported CP length is 2**CP_LEN_W - 1. +// DATA_W : Data/sample AXI-Stream bus width // `default_nettype none module cp_removal #( - parameter int DATA_W = 32, - parameter int USER_W = 1, - parameter int CP_LEN_W = 16, - parameter int SYM_LEN_W = 17, - parameter int DEFAULT_CP_LEN = 0, - parameter bit CP_REPEAT = 0, - parameter int MAX_LIST_LOG2 = 5, - parameter bit SET_TLAST = 1 + int CP_LEN_W = 12, + int DATA_W = 32 ) ( input wire clk, input wire rst, - input wire clear_list, // Cyclic prefix length input port - input wire [SYM_LEN_W-1:0] symbol_len, input wire [ CP_LEN_W-1:0] cp_len_tdata, input wire cp_len_tvalid, - output wire cp_len_tready, - output wire [ 15:0] cp_list_occupied, + output reg cp_len_tready, // Symbol data stream input input wire [ DATA_W-1:0] i_tdata, - input wire [ USER_W-1:0] i_tuser, input wire i_tlast, input wire i_tvalid, output wire i_tready, - // Symbol data stream output + // Symbol data stream output (one symbol per packet) output wire [ DATA_W-1:0] o_tdata, - output wire [ USER_W-1:0] o_tuser, output wire o_tlast, output wire o_tvalid, input wire o_tready ); `include "usrp_utils.svh" - enum logic [2:0] { S_IDLE, S_CONFIG, S_PREFIX, S_SYMBOL, S_CLEAR } state; + logic [CP_LEN_W-1:0] cp_len_reg; + logic [CP_LEN_W-1:0] count = 1; - logic [CP_LEN_W-1:0] fifo_in_tdata, fifo_out_tdata; - logic fifo_in_tvalid, fifo_out_tvalid; - logic fifo_in_tready, fifo_out_tready; - logic fifo_clear; - - assign fifo_clear = (state == S_CLEAR); - - axi_fifo #( - .WIDTH(CP_LEN_W), - .SIZE (MAX_LIST_LOG2) - ) axi_fifo_config_inst ( - .clk (clk), - .reset (rst), - .clear (fifo_clear), - .i_tdata (fifo_in_tdata), - .i_tvalid(fifo_in_tvalid), - .i_tready(fifo_in_tready), - .o_tdata (fifo_out_tdata), - .o_tvalid(fifo_out_tvalid), - .o_tready(fifo_out_tready), - .space (), - .occupied(cp_list_occupied) - ); - - generate - if (CP_REPEAT == 0) begin - // No config list loopback. New configs can be written at any time. - assign fifo_in_tdata = cp_len_tdata; - assign fifo_in_tvalid = (state == S_CLEAR) ? 1'b0 : cp_len_tvalid; - assign cp_len_tready = (state == S_CLEAR) ? 1'b0 : fifo_in_tready; - assign fifo_out_tready = (state == S_CONFIG); - end else begin - // Config list loopback enabled. Write current config back into config - // FIFO in the S_CONFIG state. New configs can be written in any state - // but S_CONFIG & S_CLEAR. - assign fifo_in_tdata = (state == S_CONFIG) ? fifo_out_tdata : - cp_len_tdata; - assign fifo_in_tvalid = (state == S_CONFIG) ? fifo_out_tvalid : - (state == S_CLEAR) ? 1'b0 : - cp_len_tvalid; - assign cp_len_tready = (state == S_CONFIG) ? 1'b0 : - (state == S_CLEAR) ? 1'b0 : - fifo_in_tready; - assign fifo_out_tready = (state == S_CONFIG); - end - endgenerate - - localparam COUNT_W = `MAX(SYM_LEN_W, CP_LEN_W); - - logic [ CP_LEN_W-1:0] cp_len_reg = DEFAULT_CP_LEN; - logic [SYM_LEN_W-1:0] symbol_len_reg = '0; - logic [ COUNT_W-1:0] count = '0; - logic clear_fifo_hold = 1'b0; + enum logic [1:0] { ST_IDLE, ST_PREFIX, ST_BODY } state; always @(posedge clk) begin - // Latch FIFO clear - if (clear_list) begin - clear_fifo_hold <= 1'b1; - end + cp_len_tready <= 1'b0; - // State machine case (state) - // Wait in idle state until either a configuration list clear is - // requested or we get a new data input. - S_IDLE : begin + // Wait in idle state until we get a new packet and the cyclic prefix + // length. + ST_IDLE : begin count <= 1; - if (clear_fifo_hold) begin - state <= S_CLEAR; - end else if (i_tvalid) begin - // Only update the CP length being used if there's a valid one in the - // list. Otherwise, keep using the previous value. - if (fifo_out_tvalid) begin - cp_len_reg <= fifo_out_tdata; + cp_len_reg <= cp_len_tdata; + if (i_tvalid && cp_len_tvalid) begin + cp_len_tready <= 1'b1; + if (cp_len_tdata > 0) begin + state <= ST_PREFIX; + end else begin + state <= ST_BODY; end - symbol_len_reg <= symbol_len; - state <= S_CONFIG; end end - S_CONFIG : begin - if (cp_len_reg > 0) begin - state <= S_PREFIX; - end else if (symbol_len_reg > 0) begin - state <= S_SYMBOL; - end else begin - state <= S_IDLE; - end - end - S_PREFIX : begin - if (i_tvalid & i_tready) begin + + // Remove the prefix + ST_PREFIX : begin + if (i_tvalid && i_tready) begin count <= count + 1; - if (count >= cp_len_reg) begin + if (count == cp_len_reg) begin count <= 1; - if (symbol_len_reg > 0) begin - state <= S_SYMBOL; - end else begin - state <= S_IDLE; - end + state <= ST_BODY; end end end - S_SYMBOL : begin - if (i_tvalid & i_tready) begin - count <= count + 1; - if (count >= symbol_len_reg) begin - count <= 1; - state <= S_IDLE; + + // Pass through the rest until the end of the packet. + ST_BODY : begin + count <= 1; + if (i_tvalid && i_tready) begin + if(i_tlast) begin + state <= ST_IDLE; end end end - S_CLEAR : begin - clear_fifo_hold <= 1'b0; - cp_len_reg <= DEFAULT_CP_LEN; - state <= S_IDLE; - end - default : state <= S_IDLE; endcase if (rst) begin - clear_fifo_hold <= 1'b0; - cp_len_reg <= DEFAULT_CP_LEN; - count <= 1; - state <= S_IDLE; + state <= ST_IDLE; + count <= 'X; + cp_len_reg <= 'X; + cp_len_tready <= '0; end end - logic new_tlast; - assign new_tlast = (state == S_SYMBOL) & (count >= symbol_len_reg); - assign o_tdata = i_tdata; - assign o_tuser = i_tuser; - assign o_tlast = (SET_TLAST == 0) ? i_tlast : new_tlast; - assign o_tvalid = (state == S_IDLE) ? 1'b0 : - (state == S_PREFIX) ? 1'b0 : - (state == S_SYMBOL) ? i_tvalid : - (state == S_CLEAR) ? 1'b0 : - 1'b0; - assign i_tready = (state == S_IDLE) ? 1'b0 : - (state == S_PREFIX) ? 1'b1 : - (state == S_SYMBOL) ? o_tready : - (state == S_CLEAR) ? 1'b0 : - 1'b0; + assign o_tlast = i_tlast; + assign o_tvalid = (state == ST_BODY ) ? i_tvalid : 1'b0; + assign i_tready = (state == ST_BODY ) ? o_tready : + (state == ST_PREFIX) ? 1'b1 : 1'b0; endmodule : cp_removal diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_core.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_core.sv index 55ef9cf..d0b06e2 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/fft_core.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_core.sv @@ -24,12 +24,21 @@ // // Parameters: // +// NIPC : Number of items/samples per clock cycle on each +// channel. It must be a power of 2. // NUM_CHAN : Number of channels to instantiate on this // fft_core instance. // NUM_CORES : Total number of fft_core instances in the // parent RFNoC block, including this one. +// MAX_PKT_SIZE_LOG2 : Log2 of maximum RFNoC packet size. Actual +// max is 2**MAX_PKT_SIZE_LOG2 items. // MAX_FFT_SIZE_LOG2 : Log2 of maximum configurable FFT size. Actual -// max is 2**MAX_FFT_SIZE_LOG2. +// max is 2**MAX_FFT_SIZE_LOG2 items. +// EN_CP_INSERTION : Controls whether to include the cyclic prefix +// insertion logic. If included, EN_FFT_ORDER must +// be 1. +// EN_CP_REMOVAL : Controls whether to include the cyclic prefix +// removal logic. // MAX_CP_LIST_LEN_INS_LOG2 : Log2 of max length of cyclic prefix insertion // list. Actual max is 2**MAX_CP_LIST_LEN_INS_LOG2. // MAX_CP_LIST_LEN_REM_LOG2 : Log2 of max length of cyclic prefix removal @@ -59,11 +68,15 @@ module fft_core import rfnoc_chdr_utils_pkg::*; import ctrlport_pkg::*; #( + int NIPC = 1, int NUM_CHAN = 1, int NUM_CORES = 1, + int MAX_PKT_SIZE_LOG2 = 14, int MAX_FFT_SIZE_LOG2 = 12, int MAX_CP_LIST_LEN_INS_LOG2 = 5, int MAX_CP_LIST_LEN_REM_LOG2 = 5, + bit EN_CP_REMOVAL = 1, + bit EN_CP_INSERTION = 1, bit CP_INSERTION_REPEAT = 1, bit CP_REMOVAL_REPEAT = 1, bit EN_FFT_BYPASS = 1, @@ -74,43 +87,43 @@ module fft_core // Data width of each FFT channel localparam int ITEM_W = 32, - localparam int DATA_W = ITEM_W, - localparam int KEEP_W = 1 + localparam int DATA_W = NIPC*ITEM_W, + localparam int KEEP_W = NIPC ) ( - input wire ce_clk, - input wire ce_rst, + input wire ce_clk, + input wire ce_rst, // CtrlPort Register Interface - input wire s_ctrlport_req_wr, - input wire s_ctrlport_req_rd, - input wire [ CTRLPORT_ADDR_W-1:0] s_ctrlport_req_addr, - input wire [ CTRLPORT_DATA_W-1:0] s_ctrlport_req_data, - output logic s_ctrlport_resp_ack, - output logic [ CTRLPORT_DATA_W-1:0] s_ctrlport_resp_data, + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [ CTRLPORT_ADDR_W-1:0] s_ctrlport_req_addr, + input wire [ CTRLPORT_DATA_W-1:0] s_ctrlport_req_data, + output logic s_ctrlport_resp_ack, + output logic [ CTRLPORT_DATA_W-1:0] s_ctrlport_resp_data, // Data Input Packets - input wire [ DATA_W*NUM_CHAN-1:0] s_in_axis_tdata, - input wire [ KEEP_W*NUM_CHAN-1:0] s_in_axis_tkeep, - input wire [ NUM_CHAN-1:0] s_in_axis_tlast, - input wire [ NUM_CHAN-1:0] s_in_axis_tvalid, - output logic [ NUM_CHAN-1:0] s_in_axis_tready, - input wire [CHDR_TIMESTAMP_W*NUM_CHAN-1:0] s_in_axis_ttimestamp, - input wire [ NUM_CHAN-1:0] s_in_axis_thas_time, - input wire [ CHDR_LENGTH_W*NUM_CHAN-1:0] s_in_axis_tlength, - input wire [ NUM_CHAN-1:0] s_in_axis_teov, - input wire [ NUM_CHAN-1:0] s_in_axis_teob, + input wire [NUM_CHAN-1:0][ DATA_W-1:0] s_in_axis_tdata, + input wire [NUM_CHAN-1:0][ KEEP_W-1:0] s_in_axis_tkeep, + input wire [NUM_CHAN-1:0][ 0:0] s_in_axis_tlast, + input wire [NUM_CHAN-1:0][ 0:0] s_in_axis_tvalid, + output logic [NUM_CHAN-1:0][ 0:0] s_in_axis_tready, + input wire [NUM_CHAN-1:0][CHDR_TIMESTAMP_W-1:0] s_in_axis_ttimestamp, + input wire [NUM_CHAN-1:0][ 0:0] s_in_axis_thas_time, + input wire [NUM_CHAN-1:0][ CHDR_LENGTH_W-1:0] s_in_axis_tlength, + input wire [NUM_CHAN-1:0][ 0:0] s_in_axis_teov, + input wire [NUM_CHAN-1:0][ 0:0] s_in_axis_teob, // Data Output Packets - output wire [ DATA_W*NUM_CHAN-1:0] m_out_axis_tdata, - output wire [ KEEP_W*NUM_CHAN-1:0] m_out_axis_tkeep, - output wire [ NUM_CHAN-1:0] m_out_axis_tlast, - output wire [ NUM_CHAN-1:0] m_out_axis_tvalid, - input wire [ NUM_CHAN-1:0] m_out_axis_tready, - output wire [CHDR_TIMESTAMP_W*NUM_CHAN-1:0] m_out_axis_ttimestamp, - output wire [ NUM_CHAN-1:0] m_out_axis_thas_time, - output wire [ CHDR_LENGTH_W*NUM_CHAN-1:0] m_out_axis_tlength, - output wire [ NUM_CHAN-1:0] m_out_axis_teov, - output wire [ NUM_CHAN-1:0] m_out_axis_teob + output wire [NUM_CHAN-1:0][ DATA_W-1:0] m_out_axis_tdata, + output wire [NUM_CHAN-1:0][ KEEP_W-1:0] m_out_axis_tkeep, + output wire [NUM_CHAN-1:0][ 0:0] m_out_axis_tlast, + output wire [NUM_CHAN-1:0][ 0:0] m_out_axis_tvalid, + input wire [NUM_CHAN-1:0][ 0:0] m_out_axis_tready, + output wire [NUM_CHAN-1:0][CHDR_TIMESTAMP_W-1:0] m_out_axis_ttimestamp, + output wire [NUM_CHAN-1:0][ 0:0] m_out_axis_thas_time, + output wire [NUM_CHAN-1:0][ CHDR_LENGTH_W-1:0] m_out_axis_tlength, + output wire [NUM_CHAN-1:0][ 0:0] m_out_axis_teov, + output wire [NUM_CHAN-1:0][ 0:0] m_out_axis_teob ); // Import utilities for working with Xilinx FFT block import xfft_config_pkg::*; @@ -118,14 +131,35 @@ module fft_core // Import register descriptions import fft_core_regs_pkg::*; + // Import FFT packetizer definitions + import fft_packetize_pkg::*; + `include "usrp_utils.svh" + //--------------------------------------------------------------------------- + // Check Parameters + //--------------------------------------------------------------------------- + + if (EN_CP_INSERTION && !EN_FFT_ORDER) begin : gen_insertion_assertion + // The cyclic prefix insertion logic is implemented in the FFT reorder + // buffer, so that must also be included. This is done to avoid having two + // separate buffers for storing the current FFT data (one for CP insertion + // and the other for reordering the data). + $error("If EN_CP_INSERTION is enabled then EN_FFT_ORDER must also be enabled"); + end + + if (NIPC != 2**$clog2(NIPC)) begin : gen_nipc_assertion + // NIPC must be a power of 2. This is currently a requirement of the + // packetizer, and also the fact that we store log2 of the NIPC in the + // capabilities register to save bits. + $error("NIPC must be a power of 2"); + end + //--------------------------------------------------------------------------- // FFT Configuration Interface Constants //--------------------------------------------------------------------------- - localparam int MAX_FFT_SIZE = 2**MAX_FFT_SIZE_LOG2; localparam int MAX_CP_LEN_LOG2 = MAX_FFT_SIZE_LOG2; // Calculate the widths needed for the configuration settings of the Xilinx @@ -145,7 +179,6 @@ module fft_core //--------------------------------------------------------------------------- localparam FFT_SIZE_LOG2_W = $clog2(MAX_FFT_SIZE_LOG2+1); - localparam FFT_SIZE_W = MAX_FFT_SIZE_LOG2+1; localparam CP_LEN_W = MAX_CP_LEN_LOG2; localparam int REG_LENGTH_LOG2_WIDTH = FFT_NFFT_W; @@ -153,7 +186,7 @@ module fft_core localparam int REG_CP_INS_LEN_WIDTH = CP_LEN_W; localparam int REG_CP_REM_LEN_WIDTH = CP_LEN_W; - localparam bit [REG_COMPAT_WIDTH-1:0] COMPAT = {16'h03, 16'h00}; + localparam bit [REG_COMPAT_WIDTH-1:0] COMPAT = {16'h03, 16'h01}; localparam bit [REG_CAPABILITIES_WIDTH-1:0] CAPABILITIES = { 8'(MAX_CP_LIST_LEN_INS_LOG2), 8'(MAX_CP_LIST_LEN_REM_LOG2), @@ -161,6 +194,10 @@ module fft_core 8'(MAX_FFT_SIZE_LOG2) }; localparam bit [REG_CAPABILITIES2_WIDTH-1:0] CAPABILITIES2 = { + 4'($clog2(NIPC)), + 2'(0), + 1'(EN_CP_INSERTION), + 1'(EN_CP_REMOVAL), 1'(EN_MAGNITUDE_SQ), 1'(EN_MAGNITUDE), 1'(EN_FFT_ORDER), @@ -172,7 +209,7 @@ module fft_core }; localparam int DEFAULT_FFT_SIZE_LOG2 = MAX_FFT_SIZE_LOG2; - localparam int DEFAULT_FFT_DIRECTION = FFT_INVERSE; + localparam int DEFAULT_FFT_DIRECTION = FFT_FORWARD; localparam int DEFAULT_CP_LEN = 0; logic core_rst; @@ -197,9 +234,6 @@ module fft_core logic [FFT_SIZE_LOG2_W-1:0] fft_size_log2; assign fft_size_log2 = FFT_SIZE_LOG2_W'(reg_fft_size_log2); - logic [FFT_SIZE_W-1:0] fft_size; - assign fft_size = 1 << reg_fft_size_log2[FFT_SIZE_LOG2_W-1:0]; - logic [REG_ADDR_W-1:0] s_ctrlport_req_addr_aligned; assign s_ctrlport_req_addr_aligned = REG_ADDR_W'({s_ctrlport_req_addr[19:2], 2'b0}); @@ -343,65 +377,143 @@ module fft_core //--------------------------------------------------------------------------- - // Packetization + // Cyclic Prefix Insertion List //--------------------------------------------------------------------------- - wire [DATA_W*NUM_CHAN-1:0] user_in_tdata; - wire [ NUM_CHAN-1:0] user_in_teob; - wire [ NUM_CHAN-1:0] user_in_tlast; - wire [ NUM_CHAN-1:0] user_in_tvalid; - wire [ NUM_CHAN-1:0] user_in_tready; - wire [DATA_W*NUM_CHAN-1:0] user_out_tdata; - wire [ NUM_CHAN-1:0] user_out_teob; - wire [ NUM_CHAN-1:0] user_out_teov; - wire [ NUM_CHAN-1:0] user_out_tlast; - wire [ NUM_CHAN-1:0] user_out_tvalid; - wire [ NUM_CHAN-1:0] user_out_tready; + logic [CP_LEN_W-1:0] cp_ins_list_tdata; + logic cp_ins_list_tvalid; + logic cp_ins_list_tready; - for (genvar ch_i = 0; ch_i < NUM_CHAN; ch_i = ch_i + 1) begin : gen_packetize - axis_data_if_packetize #( - .NIPC (1 ), - .ITEM_W (ITEM_W), - .SIDEBAND_FWD_FIFO_SIZE_LOG2(1 ) - ) axis_data_if_packetize_i ( - .clk (ce_clk ), - .reset (core_rst ), - .spp ('0 ), - .s_axis_tdata (`BUS_I(s_in_axis_tdata, DATA_W, ch_i)), - .s_axis_tlast (`BUS_I(s_in_axis_tlast, 1, ch_i)), - .s_axis_tkeep (`BUS_I(s_in_axis_tkeep, KEEP_W, ch_i)), - .s_axis_tvalid (`BUS_I(s_in_axis_tvalid, 1, ch_i)), - .s_axis_tready (`BUS_I(s_in_axis_tready, 1, ch_i)), - .s_axis_ttimestamp (`BUS_I(s_in_axis_ttimestamp, CHDR_TIMESTAMP_W, ch_i)), - .s_axis_thas_time (`BUS_I(s_in_axis_thas_time, 1, ch_i)), - .s_axis_tlength (`BUS_I(s_in_axis_tlength, CHDR_LENGTH_W, ch_i)), - .s_axis_teov (`BUS_I(s_in_axis_teov, 1, ch_i)), - .s_axis_teob (`BUS_I(s_in_axis_teob, 1, ch_i)), - .m_axis_tdata (`BUS_I(m_out_axis_tdata, DATA_W, ch_i)), - .m_axis_tkeep (`BUS_I(m_out_axis_tkeep, KEEP_W, ch_i)), - .m_axis_tlast (`BUS_I(m_out_axis_tlast, 1, ch_i)), - .m_axis_tvalid (`BUS_I(m_out_axis_tvalid, 1, ch_i)), - .m_axis_tready (`BUS_I(m_out_axis_tready, 1, ch_i)), - .m_axis_ttimestamp (`BUS_I(m_out_axis_ttimestamp, CHDR_TIMESTAMP_W, ch_i)), - .m_axis_thas_time (`BUS_I(m_out_axis_thas_time, 1, ch_i)), - .m_axis_tlength (`BUS_I(m_out_axis_tlength, CHDR_LENGTH_W, ch_i)), - .m_axis_teov (`BUS_I(m_out_axis_teov, 1, ch_i)), - .m_axis_teob (`BUS_I(m_out_axis_teob, 1, ch_i)), - .m_axis_user_tdata (`BUS_I(user_in_tdata, DATA_W, ch_i)), - .m_axis_user_tkeep ( ), - .m_axis_user_teob (`BUS_I(user_in_teob, 1, ch_i)), - .m_axis_user_teov ( ), - .m_axis_user_tlast (`BUS_I(user_in_tlast, 1, ch_i)), - .m_axis_user_tvalid(`BUS_I(user_in_tvalid, 1, ch_i)), - .m_axis_user_tready(`BUS_I(user_in_tready, 1, ch_i)), - .s_axis_user_tdata (`BUS_I(user_out_tdata, DATA_W, ch_i)), - .s_axis_user_tkeep ('1 ), - .s_axis_user_teob (`BUS_I(user_out_teob, 1, ch_i)), - .s_axis_user_teov (`BUS_I(user_out_teov, 1, ch_i)), - .s_axis_user_tlast (`BUS_I(user_out_tlast, 1, ch_i)), - .s_axis_user_tvalid(`BUS_I(user_out_tvalid, 1, ch_i)), - .s_axis_user_tready(`BUS_I(user_out_tready, 1, ch_i)) + if (EN_CP_INSERTION) begin : gen_cp_insertion_list + logic [MAX_CP_LIST_LEN_INS_LOG2:0] cp_ins_list_occupied; + assign reg_cp_ins_list_occupied = REG_CP_INS_LIST_OCC_WIDTH'(cp_ins_list_occupied); + + axis_cp_list #( + .ADDR_W (MAX_CP_LIST_LEN_INS_LOG2), + .DATA_W (CP_LEN_W ), + .REPEAT (CP_INSERTION_REPEAT ), + .DEFAULT('0 ) + ) axis_cp_list_ins ( + .clk (ce_clk ), + .rst (core_rst ), + .clear (reg_cp_ins_list_clr ), + .i_tdata (reg_cp_ins_length ), + .i_tvalid(reg_cp_ins_list_load), + .i_tready( ), + .o_tdata (cp_ins_list_tdata ), + .o_tvalid(cp_ins_list_tvalid ), + .o_tready(cp_ins_list_tready ), + .occupied(cp_ins_list_occupied) ); + end else begin : gen_no_cp_insertion_list + assign cp_ins_list_tdata = '0; + assign cp_ins_list_tvalid = '0; + assign reg_cp_ins_list_occupied = '0; + end + + + //--------------------------------------------------------------------------- + // Cyclic Prefix Removal List + //--------------------------------------------------------------------------- + + logic [CP_LEN_W-1:0] cp_rem_list_tdata; + logic cp_rem_list_tvalid; + logic cp_rem_list_tready; + + if (EN_CP_REMOVAL) begin : gen_cp_removal_list + logic [MAX_CP_LIST_LEN_REM_LOG2:0] cp_rem_list_occupied; + assign reg_cp_rem_list_occupied = REG_CP_REM_LIST_OCC_WIDTH'(cp_rem_list_occupied); + + axis_cp_list #( + .ADDR_W (MAX_CP_LIST_LEN_REM_LOG2), + .DATA_W (CP_LEN_W ), + .REPEAT (CP_REMOVAL_REPEAT ), + .DEFAULT('0 ) + ) axis_cp_list_rem ( + .clk (ce_clk ), + .rst (core_rst ), + .clear (reg_cp_rem_list_clr ), + .i_tdata (reg_cp_rem_length ), + .i_tvalid(reg_cp_rem_list_load), + .i_tready( ), + .o_tdata (cp_rem_list_tdata ), + .o_tvalid(cp_rem_list_tvalid ), + .o_tready(cp_rem_list_tready ), + .occupied(cp_rem_list_occupied) + ); + end else begin : gen_no_cp_removal_list + assign cp_rem_list_tdata = '0; + assign cp_rem_list_tvalid = '0; + assign reg_cp_rem_list_occupied = '0; + end + + + //--------------------------------------------------------------------------- + // Bypass Demultiplexer + //--------------------------------------------------------------------------- + // + // This logic splits the data flow, allowing us to select whether to send the + // data through the FFT logic or bypass it entirely. This is useful for + // debugging. + // + //--------------------------------------------------------------------------- + + // Create a constant for the width of TUSER for holding all the NoC shell + // sideband data. + localparam COMBINE_USER_W = CHDR_TIMESTAMP_W + 1 + CHDR_LENGTH_W + 2; + + logic [NUM_CHAN-1:0][COMBINE_USER_W-1:0] s_in_axis_tuser; + + logic [NUM_CHAN-1:0][ DATA_W-1:0] combine_in_tdata; + logic [NUM_CHAN-1:0][COMBINE_USER_W-1:0] combine_in_tuser; + logic [NUM_CHAN-1:0] combine_in_tlast; + logic [NUM_CHAN-1:0] combine_in_tvalid; + logic [NUM_CHAN-1:0] combine_in_tready; + + logic [NUM_CHAN-1:0][ DATA_W-1:0] bypass_tdata; + logic [NUM_CHAN-1:0][COMBINE_USER_W-1:0] bypass_tuser; + logic [NUM_CHAN-1:0] bypass_tlast; + logic [NUM_CHAN-1:0] bypass_tvalid; + logic [NUM_CHAN-1:0] bypass_tready; + + for (genvar ch_i = 0; ch_i < NUM_CHAN; ch_i++) begin : gen_for_bypass_demux + assign s_in_axis_tuser[ch_i] = { + s_in_axis_ttimestamp[ch_i], + s_in_axis_thas_time[ch_i], + s_in_axis_tlength[ch_i], + s_in_axis_teov[ch_i], + s_in_axis_teob[ch_i] + }; + + if (EN_FFT_BYPASS) begin : gen_fft_bypass_demux + axi_demux #( + .WIDTH (COMBINE_USER_W+DATA_W), + .SIZE (2 ), + .PRE_FIFO_SIZE (1 ), + .POST_FIFO_SIZE(1 ) + ) axi_demux_i ( + .clk (ce_clk ), + .reset (core_rst ), + .clear (1'b0 ), + .header ( ), + .dest (reg_fft_bypass ), + .i_tdata ({s_in_axis_tuser [ch_i], s_in_axis_tdata [ch_i]} ), + .i_tlast (s_in_axis_tlast [ch_i] ), + .i_tvalid(s_in_axis_tvalid [ch_i] ), + .i_tready(s_in_axis_tready [ch_i] ), + .o_tdata ({{bypass_tuser [ch_i], bypass_tdata [ch_i]}, + {combine_in_tuser[ch_i], combine_in_tdata [ch_i]}}), + .o_tlast ({bypass_tlast [ch_i], combine_in_tlast [ch_i]} ), + .o_tvalid({bypass_tvalid [ch_i], combine_in_tvalid[ch_i]} ), + .o_tready({bypass_tready [ch_i], combine_in_tready[ch_i]} ) + ); + end else begin : gen_no_fft_bypass_demux + assign combine_in_tdata [ch_i] = s_in_axis_tdata [ch_i]; + assign combine_in_tuser [ch_i] = s_in_axis_tuser [ch_i]; + assign combine_in_tlast [ch_i] = s_in_axis_tlast [ch_i]; + assign combine_in_tvalid[ch_i] = s_in_axis_tvalid [ch_i]; + assign s_in_axis_tready [ch_i] = combine_in_tready[ch_i]; + end end @@ -409,407 +521,292 @@ module fft_core // Combine Streams //--------------------------------------------------------------------------- // - // Combine input streams into one AXI-Stream bus. This lets us run the FFT + // Combine input channels into one AXI-Stream bus. This lets us run the FFT // instances (one per channel) in lock-step by sharing valid/ready signals. - // Also removes the need for multiple instances of FFT configuration logic. + // This allows us to share logic between channels. // //--------------------------------------------------------------------------- - wire [DATA_W*NUM_CHAN-1:0] combine_out_tdata; - wire [ NUM_CHAN-1:0] combine_out_teob; - wire combine_out_tlast; - wire combine_out_tvalid; - wire combine_out_tready; + logic [NUM_CHAN-1:0][ DATA_W-1:0] combine_out_tdata; + logic [NUM_CHAN-1:0][ COMBINE_USER_W-1:0] combine_out_tuser; + logic combine_out_tlast; + logic combine_out_tvalid; + logic combine_out_tready; + logic [CHDR_TIMESTAMP_W-1:0] combine_out_ttimestamp; + logic combine_out_thas_time; + logic [ CHDR_LENGTH_W-1:0] combine_out_tlength; + logic combine_out_teov; + logic combine_out_teob; - axis_combine #( - .SIZE (NUM_CHAN), - .WIDTH (DATA_W), - .USER_WIDTH (1), - .FIFO_SIZE_LOG2 (0)) - axis_combine_inst ( - .clk (ce_clk), - .reset (core_rst), - .s_axis_tdata (user_in_tdata), - .s_axis_tuser (user_in_teob), - .s_axis_tlast (user_in_tlast), - .s_axis_tvalid (user_in_tvalid), - .s_axis_tready (user_in_tready), - .m_axis_tdata (combine_out_tdata), - .m_axis_tuser (combine_out_teob), - .m_axis_tlast (combine_out_tlast), - .m_axis_tvalid (combine_out_tvalid), - .m_axis_tready (combine_out_tready) - ); - - - //--------------------------------------------------------------------------- - // Cyclic Prefix Removal - //--------------------------------------------------------------------------- - // - // This block does the cyclic prefix removal and also sets tlast to ensure - // that the packets going into the FFT block have the expected length. - // - //--------------------------------------------------------------------------- - - logic [DATA_W*NUM_CHAN-1:0] cp_removal_out_tdata; - logic [ NUM_CHAN-1:0] cp_removal_out_teob; - logic [ NUM_CHAN-1:0] cp_removal_out_teov; - - logic cp_removal_out_tlast; - logic cp_removal_out_tvalid; - logic cp_removal_out_tready; - - // Also sets the packet size (i.e. tlast) to the FFT size - cp_removal #( - .DATA_W (NUM_CHAN*DATA_W ), - .USER_W (NUM_CHAN ), - .CP_LEN_W (CP_LEN_W ), - .SYM_LEN_W (FFT_SIZE_W ), - .DEFAULT_CP_LEN(DEFAULT_CP_LEN ), - .CP_REPEAT (CP_REMOVAL_REPEAT ), - .MAX_LIST_LOG2 (MAX_CP_LIST_LEN_REM_LOG2), - .SET_TLAST (1 ) - ) cp_removal_i ( - .clk (ce_clk ), - .rst (core_rst ), - .clear_list (reg_cp_rem_list_clr ), - .symbol_len (fft_size ), - .cp_len_tdata (reg_cp_rem_length ), - .cp_len_tvalid (reg_cp_rem_list_load ), - // No back-pressure needed since block controller checks - // cp_len_fifo_occupied to not overflow FIFO. - .cp_len_tready ( ), - .cp_list_occupied(reg_cp_rem_list_occupied), - .i_tdata (combine_out_tdata ), - .i_tuser (combine_out_teob ), - .i_tlast (combine_out_tlast ), - .i_tvalid (combine_out_tvalid ), - .i_tready (combine_out_tready ), - .o_tdata (cp_removal_out_tdata ), - .o_tuser (cp_removal_out_teob ), - .o_tlast (cp_removal_out_tlast ), - .o_tvalid (cp_removal_out_tvalid ), - .o_tready (cp_removal_out_tready ) - ); - - // We can create a teov from tlast because the packet size is the same as the FFT size - assign cp_removal_out_teov = {NUM_CHAN{cp_removal_out_tlast}}; - - - //--------------------------------------------------------------------------- - // Configuration State Machine - //--------------------------------------------------------------------------- - - logic [ITEM_W*NUM_CHAN-1:0] fft_data_in_tdata; - logic fft_data_in_tlast; - logic fft_data_in_tvalid; - logic [ NUM_CHAN-1:0] fft_data_in_tready; - - // Loads a new configuration at the start of every FFT - enum logic [0:0] { S_FFT_CONFIG, S_FFT_WAIT_FOR_TLAST } fft_config_state = S_FFT_CONFIG; - - always_ff @(posedge ce_clk) begin - case (fft_config_state) - S_FFT_CONFIG: begin - if (fft_data_in_tvalid & fft_data_in_tready[0]) begin - fft_config_state <= S_FFT_WAIT_FOR_TLAST; - end - end - S_FFT_WAIT_FOR_TLAST: begin - if (fft_data_in_tvalid & fft_data_in_tready[0] & fft_data_in_tlast) begin - fft_config_state <= S_FFT_CONFIG; - end - end - endcase - if (core_rst) begin - fft_config_state <= S_FFT_CONFIG; - end + if (NUM_CHAN > 1) begin : gen_combine + axis_combine #( + .SIZE (NUM_CHAN), + .WIDTH (DATA_W), + .USER_WIDTH (COMBINE_USER_W), + .FIFO_SIZE_LOG2 (1) + ) axis_combine_i ( + .clk (ce_clk), + .reset (core_rst), + .s_axis_tdata (combine_in_tdata ), + .s_axis_tuser (combine_in_tuser ), + .s_axis_tlast (combine_in_tlast ), + .s_axis_tvalid (combine_in_tvalid ), + .s_axis_tready (combine_in_tready ), + .m_axis_tdata (combine_out_tdata ), + .m_axis_tuser (combine_out_tuser ), + .m_axis_tlast (combine_out_tlast ), + .m_axis_tvalid (combine_out_tvalid), + .m_axis_tready (combine_out_tready) + ); + end else begin : gen_no_combine + assign combine_out_tdata = combine_in_tdata; + assign combine_out_tuser = combine_in_tuser; + assign combine_out_tlast = combine_in_tlast; + assign combine_out_tvalid = combine_in_tvalid; + assign combine_in_tready = combine_out_tready; end + // We only need the sideband for the first channel, since they're all being + // synchronized. + assign { + combine_out_ttimestamp, + combine_out_thas_time, + combine_out_tlength, + combine_out_teov, + combine_out_teob + } = combine_out_tuser[0][COMBINE_USER_W-1:0]; + //--------------------------------------------------------------------------- - // FFT Configuration + // Convert RFNoC Packets to FFT Packets //--------------------------------------------------------------------------- - logic [FFT_CONFIG_W-1:0] fft_config_tdata; - logic fft_config_tvalid; - logic [ NUM_CHAN-1:0] fft_config_tready; + logic [CP_LEN_W-1:0] cp_ins_fft_tdata; + logic cp_ins_fft_tvalid; + logic cp_ins_fft_tready; - assign fft_config_tdata = build_fft_config( + logic [CP_LEN_W-1:0] cp_rem_fft_tdata; + logic cp_rem_fft_tvalid; + logic cp_rem_fft_tready; + + logic [NUM_CHAN-1:0][DATA_W-1:0] noc_to_fft_tdata; + logic noc_to_fft_tlast; + logic noc_to_fft_tvalid; + logic noc_to_fft_tready; + + burst_info_t burst_tdata; + logic burst_tvalid; + logic burst_tready; + + symbol_info_t symbol_tdata; + logic symbol_tvalid; + logic symbol_tready; + + // Make the symbol FIFO large enough to hold the maximum number of symbols + // that could be in flight, which is a bit more than the maximum packet size + // full of the smallest FFT size (8). But make it at least 32 (SRL FIFO size). + localparam int SYMB_FIFO_SIZE_LOG2 = `MAX(5, $clog2(2**MAX_PKT_SIZE_LOG2 / 8)+1); + + fft_packetize #( + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .NUM_CHAN (NUM_CHAN ), + .EN_CP_REMOVAL (EN_CP_REMOVAL ), + .MAX_PKT_SIZE_LOG2 (MAX_PKT_SIZE_LOG2 ), + .MAX_FFT_SIZE_LOG2 (MAX_FFT_SIZE_LOG2 ), + .DATA_FIFO_SIZE_LOG2 (-1 ), + .CP_FIFO_SIZE_LOG2 (5 ), + .BURST_FIFO_SIZE_LOG2(5 ), + .SYMB_FIFO_SIZE_LOG2 (SYMB_FIFO_SIZE_LOG2) + ) fft_packetize_i ( + .clk (ce_clk ), + .rst (core_rst ), + .fft_size_log2 (fft_size_log2 ), + .i_cp_rem_tdata (cp_rem_list_tdata ), + .i_cp_rem_tvalid (cp_rem_list_tvalid ), + .i_cp_rem_tready (cp_rem_list_tready ), + .o_cp_rem_tdata (cp_rem_fft_tdata ), + .o_cp_rem_tvalid (cp_rem_fft_tvalid ), + .o_cp_rem_tready (cp_rem_fft_tready ), + .i_noc_tdata (combine_out_tdata ), + .i_noc_tkeep ('1 ), + .i_noc_tlast (combine_out_tlast ), + .i_noc_tvalid (combine_out_tvalid ), + .i_noc_tready (combine_out_tready ), + .i_noc_ttimestamp(combine_out_ttimestamp), + .i_noc_thas_time (combine_out_thas_time ), + .i_noc_tlength (combine_out_tlength ), + .i_noc_teov (1'b0 ), + .i_noc_teob (combine_out_teob ), + .o_fft_tdata (noc_to_fft_tdata ), + .o_fft_tkeep ( ), + .o_fft_tlast (noc_to_fft_tlast ), + .o_fft_tvalid (noc_to_fft_tvalid ), + .o_fft_tready (noc_to_fft_tready ), + .o_burst_tdata (burst_tdata ), + .o_burst_tvalid (burst_tvalid ), + .o_burst_tready (burst_tready ), + .o_symbol_tdata (symbol_tdata ), + .o_symbol_tvalid (symbol_tvalid ), + .o_symbol_tready (symbol_tready ) + ); + + + //--------------------------------------------------------------------------- + // FFT Processing + //--------------------------------------------------------------------------- + + logic [NUM_CHAN-1:0][DATA_W-1:0] fft_data_in_tdata; + logic [NUM_CHAN-1:0][ 0:0] fft_data_in_tlast; + logic [NUM_CHAN-1:0][ 0:0] fft_data_in_tvalid; + logic [NUM_CHAN-1:0][ 0:0] fft_data_in_tready; + + logic [NUM_CHAN-1:0][DATA_W-1:0] fft_data_out_tdata; + logic [NUM_CHAN-1:0][ 0:0] fft_data_out_tlast; + logic [NUM_CHAN-1:0][ 0:0] fft_data_out_tvalid; + logic [NUM_CHAN-1:0][ 0:0] fft_data_out_tready; + + logic [NUM_CHAN-1:0] array_cp_rem_fft_tready; + logic [NUM_CHAN-1:0] array_cp_ins_fft_tready; + + logic [FFT_CONFIG_W-1:0] fft_config; + + assign fft_config = build_fft_config( MAX_FFT_SIZE_LOG2, reg_fft_scaling, reg_fft_direction, reg_fft_size_log2 ); - assign fft_config_tvalid = (fft_config_state == S_FFT_CONFIG) ? - fft_data_in_tvalid && fft_data_in_tready : 1'b0; + // All channels should be perfectly synchronized, so use the tready and + // tvalid from channel 0. + assign cp_rem_fft_tready = array_cp_rem_fft_tready[0]; + assign cp_ins_fft_tready = array_cp_ins_fft_tready[0]; + assign noc_to_fft_tready = fft_data_in_tready[0]; + assign fft_data_in_tdata = noc_to_fft_tdata; + assign fft_data_in_tvalid = {NUM_CHAN{noc_to_fft_tvalid}}; + assign fft_data_in_tlast = {NUM_CHAN{noc_to_fft_tlast}}; - //--------------------------------------------------------------------------- - // Sideband Info Bypass - //--------------------------------------------------------------------------- - // - // The Xilinx FFT IP lacks a TUSER signal so this adds one to pass through - // our EOB and EOV signals. - // - //--------------------------------------------------------------------------- - - logic [DATA_W*NUM_CHAN-1:0] fft_data_out_tdata; - logic [ NUM_CHAN-1:0] fft_data_out_tlast; - logic [ NUM_CHAN-1:0] fft_data_out_tvalid; - logic fft_data_out_tready; // One bit shared by all channels - - logic [DATA_W*NUM_CHAN-1:0] split_in_tdata; - logic [ NUM_CHAN-1:0] split_in_teob; - logic [ NUM_CHAN-1:0] split_in_teov; - logic split_in_tlast; - logic split_in_tvalid; - logic split_in_tready; - - // TUSER is EOB and static for the entire packet, so we can use - // PACKET_MODE=2, which is more efficient. - axis_sideband_tuser #( - .WIDTH (NUM_CHAN*DATA_W), - .USER_WIDTH (NUM_CHAN*2 ), - .FIFO_SIZE_LOG2(5 ), - .PACKET_MODE (2 ) - ) axis_sideband_tuser_i ( - .clk (ce_clk ), - .reset (core_rst ), - // Input bus with a TUSER signal - .s_axis_tdata (cp_removal_out_tdata ), - .s_axis_tuser ({cp_removal_out_teob, cp_removal_out_teov}), - .s_axis_tlast (cp_removal_out_tlast ), - .s_axis_tvalid (cp_removal_out_tvalid ), - .s_axis_tready (cp_removal_out_tready ), - // Input bus with TUSER removed, going to our FFT block - .m_axis_mod_tdata (fft_data_in_tdata ), - .m_axis_mod_tlast (fft_data_in_tlast ), - .m_axis_mod_tvalid(fft_data_in_tvalid ), - .m_axis_mod_tready(fft_data_in_tready[0] ), - // Output bus from FFT block - .s_axis_mod_tdata (fft_data_out_tdata ), - .s_axis_mod_tlast (fft_data_out_tlast[0] ), - .s_axis_mod_tvalid(fft_data_out_tvalid[0] ), - .s_axis_mod_tready(fft_data_out_tready ), - // Output bus from FFT block with TUSER added back on - .m_axis_tdata (split_in_tdata ), - .m_axis_tuser ({split_in_teob, split_in_teov} ), - .m_axis_tlast (split_in_tlast ), - .m_axis_tvalid (split_in_tvalid ), - .m_axis_tready (split_in_tready ) - ); - - - //--------------------------------------------------------------------------- - // Cyclic Prefix Insertion List - //--------------------------------------------------------------------------- - - // Output from CP list - logic [CP_LEN_W-1:0] cp_ins_list_tdata; - logic cp_ins_list_tvalid; - logic cp_ins_list_tready; - - logic [MAX_CP_LIST_LEN_INS_LOG2:0] cp_ins_list_occupied; - assign reg_cp_ins_list_occupied = REG_CP_INS_LIST_OCC_WIDTH'(cp_ins_list_occupied); - - axis_cp_list #( - .ADDR_W (MAX_CP_LIST_LEN_INS_LOG2), - .DATA_W (CP_LEN_W ), - .REPEAT (1 ), - .DEFAULT('0 ) - ) axis_cp_list_ins ( - .clk (ce_clk ), - .rst (core_rst ), - .clear (reg_cp_ins_list_clr ), - .i_tdata (reg_cp_ins_length ), - .i_tvalid(reg_cp_ins_list_load), - .i_tready( ), - .o_tdata (cp_ins_list_tdata ), - .o_tvalid(cp_ins_list_tvalid ), - .o_tready(cp_ins_list_tready ), - .occupied(cp_ins_list_occupied) - ); - - logic [DATA_W*NUM_CHAN-1:0] fft_mux_out_tdata; - logic [ NUM_CHAN-1:0] fft_mux_out_tlast; - logic [ NUM_CHAN-1:0] fft_mux_out_tvalid; - logic [ NUM_CHAN-1:0] fft_mux_out_tready; - - logic fft_mux_out_tstart = '1; // Indicates first word transfer of packet - - always_ff @(posedge ce_clk) begin - // Create a register that indicates when the first transfer of a packet - // occurs (analogous to TLAST). - if (fft_mux_out_tvalid[0] && fft_mux_out_tready[0]) begin - fft_mux_out_tstart <= fft_mux_out_tlast[0]; - end - - if (ce_rst) begin - fft_mux_out_tstart <= '1; - end - end - - // Pop off the next list item after the start of each packet. - assign cp_ins_list_tready = - fft_mux_out_tstart && fft_mux_out_tvalid[0] && fft_mux_out_tready[0]; - - - //--------------------------------------------------------------------------- - // FFT Bypass - //--------------------------------------------------------------------------- - - logic [DATA_W*NUM_CHAN-1:0] xfft_in_tdata; - logic [ NUM_CHAN-1:0] xfft_in_tlast; - logic [ NUM_CHAN-1:0] xfft_in_tvalid; - logic [ NUM_CHAN-1:0] xfft_in_tready; - - logic [DATA_W*NUM_CHAN-1:0] xfft_out_tdata; - logic [ NUM_CHAN-1:0] xfft_out_tlast; - logic [ NUM_CHAN-1:0] xfft_out_tvalid; - logic [ NUM_CHAN-1:0] xfft_out_tready; - - logic [DATA_W*NUM_CHAN-1:0] xfft_bypass_in_tdata; - logic [ NUM_CHAN-1:0] xfft_bypass_in_tlast; - logic [ NUM_CHAN-1:0] xfft_bypass_in_tvalid; - logic [ NUM_CHAN-1:0] xfft_bypass_in_tready; - - logic [DATA_W*NUM_CHAN-1:0] xfft_bypass_out_tdata; - logic [ NUM_CHAN-1:0] xfft_bypass_out_tlast; - logic [ NUM_CHAN-1:0] xfft_bypass_out_tvalid; - logic [ NUM_CHAN-1:0] xfft_bypass_out_tready; - - always_comb begin - if (reg_fft_bypass && EN_FFT_BYPASS) begin - // FIFO connections pass through - xfft_bypass_in_tdata = fft_data_in_tdata; - xfft_bypass_in_tlast = {NUM_CHAN{fft_data_in_tlast}}; - xfft_bypass_in_tvalid = {NUM_CHAN{fft_data_in_tvalid}}; - fft_data_in_tready = xfft_bypass_in_tready; - // - fft_mux_out_tdata = xfft_bypass_out_tdata; - fft_mux_out_tlast = xfft_bypass_out_tlast; - fft_mux_out_tvalid = xfft_bypass_out_tvalid; - xfft_bypass_out_tready = {NUM_CHAN{fft_mux_out_tready}}; - - // Tie off bypassed FFT connections - xfft_in_tdata = fft_data_in_tdata; - xfft_in_tlast = {NUM_CHAN{fft_data_in_tlast}}; - xfft_in_tvalid = {NUM_CHAN{1'b0}}; - xfft_out_tready = {NUM_CHAN{1'b1}}; - end else begin - // Tie off bypassed FIFO connections - xfft_bypass_in_tdata = fft_data_in_tdata; - xfft_bypass_in_tlast = {NUM_CHAN{fft_data_in_tlast}}; - xfft_bypass_in_tvalid = {NUM_CHAN{1'b0}}; - xfft_bypass_out_tready = {NUM_CHAN{1'b1}}; - - // FFT connections pass through like normal - xfft_in_tdata = fft_data_in_tdata; - xfft_in_tlast = {NUM_CHAN{fft_data_in_tlast}}; - xfft_in_tvalid = {NUM_CHAN{fft_data_in_tvalid}}; - fft_data_in_tready = xfft_in_tready; - // - fft_mux_out_tdata = xfft_out_tdata; - fft_mux_out_tlast = xfft_out_tlast; - fft_mux_out_tvalid = xfft_out_tvalid; - xfft_out_tready = fft_mux_out_tready; - end - end - - - //--------------------------------------------------------------------------- - // FFT Core - //--------------------------------------------------------------------------- - - logic [NUM_CHAN-1:0] event_frame_started; - logic [NUM_CHAN-1:0] event_frame_tlast_unexpected; - logic [NUM_CHAN-1:0] event_frame_tlast_missing; - - for (genvar fft_i = 0; fft_i < NUM_CHAN; fft_i = fft_i + 1) begin : gen_fft - if (EN_FFT_BYPASS) begin : gen_bypass_fifo - axi_fifo #( - .WIDTH(DATA_W+1 ), - .SIZE (MAX_FFT_SIZE_LOG2) - ) axi_fifo_bypass ( - .clk (ce_clk ), - .reset (core_rst ), - .clear (1'b0 ), - .i_tdata ({ xfft_bypass_in_tlast[fft_i], - xfft_bypass_in_tdata[DATA_W*fft_i +: DATA_W]} ), - .i_tvalid(xfft_bypass_in_tvalid[fft_i] ), - .i_tready(xfft_bypass_in_tready[fft_i] ), - .o_tdata ({ xfft_bypass_out_tlast[fft_i], - xfft_bypass_out_tdata[DATA_W*fft_i +: DATA_W]}), - .o_tvalid(xfft_bypass_out_tvalid[fft_i] ), - .o_tready(xfft_bypass_out_tready[fft_i] ), - .space ( ), - .occupied( ) - ); - end : gen_bypass_fifo - - xfft_wrapper #( - .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2) - ) xfft_wrapper_i ( - .aclk (ce_clk ), - .aresetn (~core_rst ), - .s_axis_config_tdata (fft_config_tdata ), - .s_axis_config_tvalid (fft_config_tvalid ), - .s_axis_config_tready (fft_config_tready[fft_i] ), - .s_axis_data_tdata ({ xfft_in_tdata[32*fft_i +: 16], - xfft_in_tdata[32*fft_i+16 +: 16] } ), - .s_axis_data_tlast (xfft_in_tlast[fft_i] ), - .s_axis_data_tvalid (xfft_in_tvalid[fft_i] ), - .s_axis_data_tready (xfft_in_tready[fft_i] ), - .m_axis_data_tdata ({ xfft_out_tdata[32*fft_i +: 16], - xfft_out_tdata[32*fft_i+16 +: 16] }), - .m_axis_data_tuser ( ), - .m_axis_data_tlast (xfft_out_tlast[fft_i] ), - .m_axis_data_tvalid (xfft_out_tvalid[fft_i] ), - .m_axis_data_tready (xfft_out_tready[fft_i] ), - .m_axis_status_tdata ( ), - .m_axis_status_tvalid ( ), - .m_axis_status_tready (1'b1 ), - .event_frame_started (event_frame_started[fft_i] ), - .event_tlast_unexpected (event_frame_tlast_unexpected[fft_i] ), - .event_tlast_missing (event_frame_tlast_missing[fft_i] ), - .event_fft_overflow (event_fft_overflow[fft_i] ), - .event_status_channel_halt ( ), - .event_data_in_channel_halt ( ), - .event_data_out_channel_halt( ) + for (genvar ch_i = 0; ch_i < NUM_CHAN; ch_i = ch_i + 1) begin : gen_fft + fft_pipeline_wrapper #( + .NIPC (NIPC ), + .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2), + .EN_CONFIG_FIFO (0 ), + .EN_CP_REMOVAL (EN_CP_REMOVAL ), + .EN_CP_INSERTION (EN_CP_INSERTION ), + .EN_FFT_ORDER (EN_FFT_ORDER ), + .EN_MAGNITUDE (EN_MAGNITUDE ), + .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), + .USE_APPROX_MAG (USE_APPROX_MAG ) + ) fft_pipeline_wrapper_i ( + .clk (ce_clk ), + .rst (ce_rst ), + .fft_order (reg_fft_order ), + .magnitude (reg_magnitude ), + .fft_config (fft_config ), + .fft_size_log2 (fft_size_log2 ), + .fft_config_tdata ('0 ), + .fft_config_tvalid (1'b0 ), + .fft_config_tready ( ), + .cp_rem_tdata (cp_rem_fft_tdata ), + .cp_rem_tvalid (cp_rem_fft_tvalid ), + .cp_rem_tready (array_cp_rem_fft_tready[ch_i]), + .cp_ins_tdata (cp_ins_fft_tdata ), + .cp_ins_tvalid (cp_ins_fft_tvalid ), + .cp_ins_tready (array_cp_ins_fft_tready[ch_i]), + .event_fft_overflow(event_fft_overflow [ch_i]), + .i_tdata (fft_data_in_tdata [ch_i]), + .i_tlast (fft_data_in_tlast [ch_i]), + .i_tvalid (fft_data_in_tvalid [ch_i]), + .i_tready (fft_data_in_tready [ch_i]), + .o_tdata (fft_data_out_tdata [ch_i]), + .o_tlast (fft_data_out_tlast [ch_i]), + .o_tvalid (fft_data_out_tvalid [ch_i]), + .o_tready (fft_data_out_tready [ch_i]) ); + end : gen_fft - if (EN_FFT_ORDER || EN_MAGNITUDE || EN_MAGNITUDE_SQ) begin : gen_fft_post_processing - fft_post_processing #( - .EN_FFT_ORDER (EN_FFT_ORDER ), - .EN_MAGNITUDE (EN_MAGNITUDE ), - .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), - .USE_APPROX_MAG (USE_APPROX_MAG ), - .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2) - ) fft_post_processing_i ( - .clk (ce_clk ), - .rst (core_rst ), - .fft_order_sel(reg_fft_order ), - .magnitude_sel(reg_magnitude ), - .fft_size_log2(reg_fft_size_log2[FFT_SIZE_LOG2_W-1:0] ), - .s_axis_tdata (fft_mux_out_tdata [DATA_W*fft_i +: DATA_W] ), - .s_axis_tuser (cp_ins_list_tdata ), - .s_axis_tlast (fft_mux_out_tlast [fft_i] ), - .s_axis_tvalid(fft_mux_out_tvalid[fft_i] ), - .s_axis_tready(fft_mux_out_tready[fft_i] ), - .m_axis_tdata (fft_data_out_tdata [DATA_W*fft_i +: DATA_W]), - .m_axis_tlast (fft_data_out_tlast [fft_i] ), - .m_axis_tvalid(fft_data_out_tvalid[fft_i] ), - .m_axis_tready(fft_data_out_tready ) - ); - end else begin : gen_no_fft_post_processing - assign fft_data_out_tdata = fft_mux_out_tdata; - assign fft_data_out_tlast = fft_mux_out_tlast; - assign fft_data_out_tvalid = fft_mux_out_tvalid; - assign fft_mux_out_tready = {NUM_CHAN{fft_data_out_tready}}; - end - end + + //--------------------------------------------------------------------------- + // Convert FFT Packets to RFNoC packets + //--------------------------------------------------------------------------- + + logic [NUM_CHAN-1:0][DATA_W-1:0] split_in_tdata; + logic split_in_tlast; + logic split_in_tvalid; + logic split_in_tready; + logic [ CHDR_TIMESTAMP_W-1:0] split_in_ttimestamp; + logic split_in_thas_time; + logic [ CHDR_LENGTH_W-1:0] split_in_tlength; + logic split_in_teov; + logic split_in_teob; + + logic [NUM_CHAN-1:0][DATA_W-1:0] fft_to_noc_tdata; + logic fft_to_noc_tlast; + logic fft_to_noc_tvalid; + logic fft_to_noc_tready; + + // All channels should be perfectly synchronized, so use the tready and + // tvalid from channel 0. + assign fft_to_noc_tdata = fft_data_out_tdata; + assign fft_to_noc_tlast = fft_data_out_tlast[0]; + assign fft_to_noc_tvalid = fft_data_out_tvalid[0]; + assign fft_data_out_tready = {NUM_CHAN{fft_to_noc_tready}}; + + // With the current packtize/depacketize design, we must have at least one + // packet worth of buffer between the packetizer and the depacketizer. So, + // this buffer can be removed if it's guaranteed that FFT logic can buffer a + // whole packet. This is the case when the FFT size is always bigger than the + // packet size, or if the reorder buffer is used and twice the FFT size is + // bigger than the packet size. Since we can't know what FFT size and packet + // size the user will use, we assume the worst and add a maximum-packet-sized + // buffer here. + localparam int DATA_FIFO_SIZE_LOG2 = MAX_PKT_SIZE_LOG2; + + fft_depacketize #( + .ITEM_W (ITEM_W ), + .NIPC (NIPC ), + .NUM_CHAN (NUM_CHAN ), + .EN_CP_INSERTION (EN_CP_INSERTION ), + .MAX_PKT_SIZE_LOG2 (MAX_PKT_SIZE_LOG2 ), + .MAX_FFT_SIZE_LOG2 (MAX_FFT_SIZE_LOG2 ), + .DATA_FIFO_SIZE_LOG2(DATA_FIFO_SIZE_LOG2), + .CP_FIFO_SIZE_LOG2 (5 ), + .SYMB_FIFO_SIZE_LOG2(5 ), + .EN_TIME_ALL_PKTS (1 ) + ) fft_depacketize ( + .clk (ce_clk ), + .rst (core_rst ), + .fft_size_log2 (fft_size_log2 ), + .i_burst_tdata (burst_tdata ), + .i_burst_tvalid (burst_tvalid ), + .i_burst_tready (burst_tready ), + .i_symbol_tdata (symbol_tdata ), + .i_symbol_tvalid (symbol_tvalid ), + .i_symbol_tready (symbol_tready ), + .i_cp_ins_tdata (cp_ins_list_tdata ), + .i_cp_ins_tvalid (cp_ins_list_tvalid ), + .i_cp_ins_tready (cp_ins_list_tready ), + .o_cp_ins_tdata (cp_ins_fft_tdata ), + .o_cp_ins_tvalid (cp_ins_fft_tvalid ), + .o_cp_ins_tready (cp_ins_fft_tready ), + .i_fft_tdata (fft_to_noc_tdata ), + .i_fft_tkeep ('1 ), + .i_fft_tlast (fft_to_noc_tlast ), + .i_fft_tvalid (fft_to_noc_tvalid ), + .i_fft_tready (fft_to_noc_tready ), + .o_noc_tdata (split_in_tdata ), + .o_noc_tkeep ( ), + .o_noc_tlast (split_in_tlast ), + .o_noc_tvalid (split_in_tvalid ), + .o_noc_tready (split_in_tready ), + .o_noc_ttimestamp(split_in_ttimestamp), + .o_noc_thas_time (split_in_thas_time ), + .o_noc_tlength (split_in_tlength ), + .o_noc_teov (split_in_teov ), + .o_noc_teob (split_in_teob ) + ); //--------------------------------------------------------------------------- @@ -820,25 +817,104 @@ module fft_core // //--------------------------------------------------------------------------- - // Split back into multiple streams - axis_split_bus #( - .WIDTH (DATA_W ), - .USER_WIDTH(2 ), - .NUM_PORTS (NUM_CHAN) - ) axis_split_bus_i ( - .clk (ce_clk ), - .reset (core_rst ), - .s_axis_tdata (split_in_tdata ), - .s_axis_tuser ({split_in_teob, split_in_teov}), - .s_axis_tlast (split_in_tlast ), - .s_axis_tvalid(split_in_tvalid ), - .s_axis_tready(split_in_tready ), - .m_axis_tdata (user_out_tdata ), - .m_axis_tuser ({user_out_teob, user_out_teov}), - .m_axis_tlast (user_out_tlast ), - .m_axis_tvalid(user_out_tvalid ), - .m_axis_tready(user_out_tready ) - ); + logic [NUM_CHAN-1:0][COMBINE_USER_W-1:0] split_in_tuser; + + logic [NUM_CHAN-1:0][ DATA_W-1:0] split_out_tdata; + logic [NUM_CHAN-1:0][COMBINE_USER_W-1:0] split_out_tuser; + logic [NUM_CHAN-1:0][ 0:0] split_out_tlast; + logic [NUM_CHAN-1:0][ 0:0] split_out_tvalid; + logic [NUM_CHAN-1:0][ 0:0] split_out_tready; + + // We replicate the sideband for the first channel, since they're all + // synchronized. + assign split_in_tuser = { NUM_CHAN { + split_in_ttimestamp, + split_in_thas_time, + split_in_tlength, + split_in_teov, + split_in_teob + }}; + + if (NUM_CHAN > 1) begin : gen_split + // Split back into multiple streams + axis_split_bus #( + .WIDTH (DATA_W ), + .USER_WIDTH(COMBINE_USER_W), + .NUM_PORTS (NUM_CHAN ) + ) axis_split_bus_i ( + .clk (ce_clk ), + .reset (core_rst ), + .s_axis_tdata (split_in_tdata ), + .s_axis_tuser (split_in_tuser ), + .s_axis_tlast (split_in_tlast ), + .s_axis_tvalid(split_in_tvalid ), + .s_axis_tready(split_in_tready ), + .m_axis_tdata (split_out_tdata ), + .m_axis_tuser (split_out_tuser ), + .m_axis_tlast (split_out_tlast ), + .m_axis_tvalid(split_out_tvalid), + .m_axis_tready(split_out_tready) + ); + end else begin : gen_no_split + assign split_out_tdata = split_in_tdata; + assign split_out_tuser = split_in_tuser; + assign split_out_tlast = split_in_tlast; + assign split_out_tvalid = split_in_tvalid; + assign split_in_tready = split_out_tready; + end + + + //--------------------------------------------------------------------------- + // Bypass Multiplexer + //--------------------------------------------------------------------------- + // + // This selects between the FFT logic output path and the bypass path. + // + //--------------------------------------------------------------------------- + + logic [NUM_CHAN-1:0][COMBINE_USER_W-1:0] m_out_axis_tuser; + + for (genvar ch_i = 0; ch_i < NUM_CHAN; ch_i++) begin : gen_for_bypass_mux + if (EN_FFT_BYPASS) begin : gen_fft_bypass_mux + axi_mux #( + .PRIO (1 ), + .WIDTH (COMBINE_USER_W+DATA_W), + .SIZE (2 ), + .PRE_FIFO_SIZE (1 ), + .POST_FIFO_SIZE(1 ) + ) axi_demux_i ( + .clk (ce_clk ), + .reset (core_rst ), + .clear (1'b0 ), + .i_tdata ({{bypass_tuser [ch_i], bypass_tdata [ch_i]}, + {split_out_tuser [ch_i], split_out_tdata [ch_i]}}), + .i_tlast ({bypass_tlast [ch_i], split_out_tlast [ch_i]}), + .i_tvalid({bypass_tvalid [ch_i], split_out_tvalid[ch_i]}), + .i_tready({bypass_tready [ch_i], split_out_tready[ch_i]}), + .o_tdata ({m_out_axis_tuser[ch_i], m_out_axis_tdata[ch_i]}), + .o_tlast (m_out_axis_tlast [ch_i] ), + .o_tvalid(m_out_axis_tvalid[ch_i] ), + .o_tready(m_out_axis_tready[ch_i] ) + ); + end else begin : gen_no_fft_bypass_mux + assign m_out_axis_tdata = split_out_tdata; + assign m_out_axis_tuser = split_out_tuser; + assign m_out_axis_tlast = split_out_tlast; + assign m_out_axis_tvalid = split_out_tvalid; + assign split_out_tready = m_out_axis_tready; + end + + assign m_out_axis_tkeep[ch_i] = '1; + + assign { + m_out_axis_ttimestamp[ch_i], + m_out_axis_thas_time[ch_i], + m_out_axis_tlength[ch_i], + m_out_axis_teov[ch_i], + m_out_axis_teob[ch_i] + } = m_out_axis_tuser[ch_i]; + end : gen_for_bypass_mux + endmodule : fft_core diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_core_regs_pkg.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_core_regs_pkg.sv index 8cfd1fa..9e00928 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/fft_core_regs_pkg.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_core_regs_pkg.sv @@ -80,17 +80,24 @@ package fft_core_regs_pkg; // // Returns information about the post-processing capabilities. // - // [3] : MAGNITUDE_SQ. Indicates whether or not the magnitude-squared - // output capability is present in the core. - // [2] : MAGNITUDE. Indicates whether or not the magnitude output option - // is present in the core. - // [1] : FFT_ORDER. Indicates whether or not the FFT reorder capability is - // present in the core. - // [0] : FFT_BYPASS. Indicates whether or not the FFT bypass capability is - // present in the core. + // [11:8] : Log base 2 of the number of items per clock cycle (NIPC) + // processed by this core. For example, a value of 3 in this field + // means that the NIPC is 2**3 == 8. Packet sizes and cyclic prefix + // lengths must be a multiple of the NIPC value. + // [ 7:6] : Reserved + // [ 5] : CP_INSERTION. Indicates if cyclic-prefix insertion is available. + // [ 4] : CP_REMOVAL. Indicates if cyclic-prefix removal is available. + // [ 3] : MAGNITUDE_SQ. Indicates whether or not the magnitude-squared + // output capability is present in the core. + // [ 2] : MAGNITUDE. Indicates whether or not the magnitude output option + // is present in the core. + // [ 1] : FFT_ORDER. Indicates whether or not the FFT reorder capability is + // present in the core. + // [ 0] : FFT_BYPASS. Indicates whether or not the FFT bypass capability is + // present in the core. // localparam int REG_CAPABILITIES2_ADDR = 'h0C; - localparam int REG_CAPABILITIES2_WIDTH = 4; + localparam int REG_CAPABILITIES2_WIDTH = 12; // REG_RESET (Write-only strobe) // @@ -239,13 +246,17 @@ package fft_core_regs_pkg; // negative frequencies. 0 Hz in the center. // 2 : NATURAL. Positive frequencies are first, followed by negative // frequencies. 0 Hz is on the left. + // 3 : BIT_REVERSE. Like natural, but the bits of the indices are in + // reverse order. For example, for a size 16 FFT, bin 0000 is output + // first, followed by bin 1000, 0100, 1100, 0010, etc. // localparam int REG_ORDER_ADDR = 'h48; localparam int REG_ORDER_WIDTH = 2; // - localparam int FFT_ORDER_NORMAL = 0; - localparam int FFT_ORDER_REVERSE = 1; - localparam int FFT_ORDER_NATURAL = 2; + localparam int FFT_ORDER_NORMAL = 0; + localparam int FFT_ORDER_REVERSE = 1; + localparam int FFT_ORDER_NATURAL = 2; + localparam int FFT_ORDER_BIT_REVERSE = 3; // REG_MAGNITUDE (Read/Write) // diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_depacketize.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_depacketize.sv index 1b40da9..f59a0e8 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/fft_depacketize.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_depacketize.sv @@ -227,7 +227,7 @@ module fft_depacketize symbol_state_t symbol_state = WAIT_SYMBOL_ST; - logic last_symbol; + logic cp_last_symbol; logic prefix_rd_stb = 1'b0; logic [ CP_LEN_W-1:0] cp_len; logic [FFT_SIZE_W-1:0] symbol_size; @@ -258,7 +258,7 @@ module fft_depacketize // wait for their tready signals to be asserted, which on the // axi_fifo indicates that they are not full. i_symbol_tready <= i_symbol_fifo_tready && i_cp_ins_fifo_tready; - last_symbol <= i_symbol_tdata.last; + cp_last_symbol <= i_symbol_tdata.last; cp_len <= i_cp_ins_tdata; if (i_symbol_tvalid && i_symbol_tready) begin i_symbol_tready <= 1'b0; @@ -289,7 +289,7 @@ module fft_depacketize prefix_rd_stb <= 1'b0; i_symbol_tready <= 1'b0; i_symbol_fifo_tvalid <= 1'b0; - last_symbol <= 1'bX; + cp_last_symbol <= 1'bX; cp_len <= 'X; symbol_size <= 'X; end @@ -300,9 +300,7 @@ module fft_depacketize // Symbol Information FIFO //--------------------------------- - logic [15:0] symbol_fifo_space; - - assign i_symbol_fifo_tdata = '{ last_symbol, symbol_size }; + assign i_symbol_fifo_tdata = '{ cp_last_symbol, symbol_size }; axi_fifo #( .WIDTH($bits(symbol_fifo_t)), @@ -317,7 +315,7 @@ module fft_depacketize .o_tdata (o_symbol_fifo_tdata ), .o_tvalid(o_symbol_fifo_tvalid), .o_tready(o_symbol_fifo_tready), - .space (symbol_fifo_space ), + .space ( ), .occupied( ) ); @@ -428,8 +426,6 @@ module fft_depacketize // //--------------------------------------------------------------------------- - localparam int BYTES_PER_ITEM = (ITEM_W/8); - typedef enum logic [2:0] { WAIT_BURST_ST, CALC_ITEMS_ST, diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_packetize.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_packetize.sv index 1aeac92..e7980bf 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/fft_packetize.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_packetize.sv @@ -423,7 +423,9 @@ module fft_packetize .i_tready(i_fft_tready ), .o_tdata ({o_fft_tlast, o_fft_tkeep, o_fft_tdata}), .o_tvalid(o_fft_tvalid ), - .o_tready(o_fft_tready ) + .o_tready(o_fft_tready ), + .space ( ), + .occupied( ) ); end else begin : gen_no_output_fifo assign o_fft_tdata = i_fft_tdata; diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline.sv new file mode 100644 index 0000000..ec7c2a8 --- /dev/null +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline.sv @@ -0,0 +1,442 @@ +// +// Copyright 2024 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: fft_pipeline +// +// Description: +// +// The module contains all FFT processing for a single channel, including +// cyclic prefix removal, cyclic prefix insertion, FFT/IFFT, and logic to +// change the output order of the FFT data. +// +// The data is input on the data input (i_t*) and output on the data output +// (o_t*) ports. There must be one FFT/IFFT per packet, plus cyclic-prefix to +// be removed, if applicable. +// +// The "global FFT settings" are treated as fixed values that won't change +// for the duration of a single FFT/IFFT. These should only be updated when +// everything is idle and there is no data in flight. +// +// The fft_config_t* input contains the per-FFT settings for the Xilinx FFT +// core and you should write once per FFT. +// +// The cp_rem_t* and cp_ins_t* are the cyclic prefix removal and insertion +// lengths. You should write one length per FFT/IFFT. +// +// Parameters: +// +// MAX_FFT_SIZE_LOG2 : Set to the log base 2 of the maximum FFT size to be +// supported. For example, a value of 14 means the +// maximum FFT size is 2**14 = 4096. +// EN_CONFIG_FIFO : When 1, the fft_config_tdata AXI-Stream input is used +// in order to allow a unique configuration per FFT +// operation. If EN_CONFIG_FIFO is 0, then the fft_config +// input is used instead and it is assumed to be static +// for the duration of the FFT operation and must only +// change while the module is idle. +// EN_CP_REMOVAL : Controls whether to include the cyclic prefix removal +// logic. +// EN_CP_INSERTION : Controls whether to include the cyclic prefix +// insertion logic. If included, EN_FFT_ORDER must be 1. +// EN_FFT_ORDER : Set to 1 to add the optional FFT reorder core. Set to +// 0 to remove it and save resources. Removing it also +// disable CP insertion. +// EN_MAGNITUDE : Set to 1 to add the magnitude output calculation core. +// Set to 0 to remove it and save resources. +// EN_MAGNITUDE_SQ : Set to 1 to add the magnitude squared output +// calculation core. Set to 0 to remove it and save +// resources. +// USE_APPROX_MAG : Control which magnitude calculation to use. Set to 1 +// to use a simpler circuit that gives pretty good +// results in order to save resources. Set to 0 to use +// the CORDIC IP to calculate the magnitude. +// + +`default_nettype none + + +module fft_pipeline + import xfft_config_pkg::*; +#( + int MAX_FFT_SIZE_LOG2 = 12, + bit EN_CONFIG_FIFO = 1, + bit EN_CP_REMOVAL = 1, + bit EN_CP_INSERTION = 1, + bit EN_FFT_ORDER = 1, + bit EN_MAGNITUDE = 1, + bit EN_MAGNITUDE_SQ = 1, + bit USE_APPROX_MAG = 1, + + localparam int FFT_CONFIG_W = fft_config_w(MAX_FFT_SIZE_LOG2), + localparam int DATA_W = 32, + localparam int FFT_SIZE_LOG2_W = $clog2(MAX_FFT_SIZE_LOG2+1), + localparam int CP_LEN_W = MAX_FFT_SIZE_LOG2 +) ( + input wire clk, + input wire rst, + + // Global FFT settings + input wire [ 1:0] fft_order, + input wire [ 1:0] magnitude, + input wire [FFT_SIZE_LOG2_W-1:0] fft_size_log2, + input wire [ FFT_CONFIG_W-1:0] fft_config, + + // FFT IP Configuration + input wire [FFT_CONFIG_W-1:0] fft_config_tdata, + input wire fft_config_tvalid, + output wire fft_config_tready, + + // CP Removal Length + input wire [CP_LEN_W-1:0] cp_rem_tdata, + input wire cp_rem_tvalid, + output wire cp_rem_tready, + + // CP Insertion Length + input wire [CP_LEN_W-1:0] cp_ins_tdata, + input wire cp_ins_tvalid, + output reg cp_ins_tready, + + // FFT Event Monitoring + output wire event_fft_overflow, + + // Data Input Packets + input wire [DATA_W-1:0] i_tdata, + input wire i_tlast, + input wire i_tvalid, + output wire i_tready, + + // Data Output Packets + output wire [DATA_W-1:0] o_tdata, + output wire o_tlast, + output wire o_tvalid, + input wire o_tready +); + + //--------------------------------------------------------------------------- + // Input FIFOs + //--------------------------------------------------------------------------- + + logic [FFT_CONFIG_W-1:0] fft_config_fifo_tdata; + logic fft_config_fifo_tvalid; + logic fft_config_fifo_tready; + + logic [ CP_LEN_W-1:0] cp_rem_fifo_tdata; + logic cp_rem_fifo_tvalid; + logic cp_rem_fifo_tready; + + logic [ CP_LEN_W-1:0] cp_ins_fifo_tdata; + logic cp_ins_fifo_tvalid; + logic cp_ins_fifo_tready; + + logic [DATA_W-1:0] fft_fifo_tdata; + logic fft_fifo_tlast; + logic fft_fifo_tvalid; + logic fft_fifo_tready; + + if (EN_CONFIG_FIFO) begin : gen_config_fifo + axi_fifo #( + .WIDTH(FFT_CONFIG_W), + .SIZE (1 ) + ) axi_fifo_fft_config ( + .clk (clk ), + .reset (rst ), + .clear (1'b0 ), + .i_tdata (fft_config_tdata ), + .i_tvalid(fft_config_tvalid ), + .i_tready(fft_config_tready ), + .o_tdata (fft_config_fifo_tdata ), + .o_tvalid(fft_config_fifo_tvalid), + .o_tready(fft_config_fifo_tready), + .space ( ), + .occupied( ) + ); + end else begin : gen_no_config_fifo + assign fft_config_tready = 1'b1; + end + + if (EN_CP_REMOVAL) begin : gen_cp_rem_fifo + axi_fifo #( + .WIDTH(CP_LEN_W), + .SIZE (1 ) + ) axi_fifo_cp_rem ( + .clk (clk ), + .reset (rst ), + .clear (1'b0 ), + .i_tdata (cp_rem_tdata ), + .i_tvalid(cp_rem_tvalid ), + .i_tready(cp_rem_tready ), + .o_tdata (cp_rem_fifo_tdata ), + .o_tvalid(cp_rem_fifo_tvalid), + .o_tready(cp_rem_fifo_tready), + .space ( ), + .occupied( ) + ); + end else begin : gen_no_cp_remo_fifo + assign cp_rem_tready = 1'b1; + assign cp_rem_fifo_tdata = '0; + assign cp_rem_fifo_tvalid = 1'b1; + end + + if (EN_CP_INSERTION) begin : gen_cp_ins_fifo + axi_fifo #( + .WIDTH(CP_LEN_W), + .SIZE (1 ) + ) axi_fifo_cp_ins ( + .clk (clk ), + .reset (rst ), + .clear (1'b0 ), + .i_tdata (cp_ins_tdata ), + .i_tvalid(cp_ins_tvalid ), + .i_tready(cp_ins_tready ), + .o_tdata (cp_ins_fifo_tdata ), + .o_tvalid(cp_ins_fifo_tvalid), + .o_tready(cp_ins_fifo_tready), + .space ( ), + .occupied( ) + ); + end else begin : gen_no_cp_ins_fifo + assign cp_ins_tready = 1'b1; + assign cp_ins_fifo_tdata = '0; + assign cp_ins_fifo_tvalid = 1'b1; + end + + axi_fifo #( + .WIDTH(1+DATA_W), + .SIZE (1 ) + ) axi_fifo_fft ( + .clk (clk ), + .reset (rst ), + .clear (1'b0 ), + .i_tdata ({i_tlast, i_tdata} ), + .i_tvalid(i_tvalid ), + .i_tready(i_tready ), + .o_tdata ({fft_fifo_tlast, fft_fifo_tdata}), + .o_tvalid(fft_fifo_tvalid ), + .o_tready(fft_fifo_tready ), + .space ( ), + .occupied( ) + ); + + + //--------------------------------------------------------------------------- + // Cyclic Prefix Removal + //--------------------------------------------------------------------------- + + logic [31:0] cp_rem_out_tdata; + logic cp_rem_out_tlast; + logic cp_rem_out_tvalid; + logic cp_rem_out_tready; + + if (EN_CP_REMOVAL) begin : gen_cp_removal + cp_removal #( + .CP_LEN_W (CP_LEN_W ), + .DATA_W (DATA_W ) + ) cp_removal_i ( + .clk (clk ), + .rst (rst ), + .cp_len_tdata (cp_rem_fifo_tdata ), + .cp_len_tvalid(cp_rem_fifo_tvalid), + .cp_len_tready(cp_rem_fifo_tready), + .i_tdata (fft_fifo_tdata ), + .i_tlast (fft_fifo_tlast ), + .i_tvalid (fft_fifo_tvalid ), + .i_tready (fft_fifo_tready ), + .o_tdata (cp_rem_out_tdata ), + .o_tlast (cp_rem_out_tlast ), + .o_tvalid (cp_rem_out_tvalid ), + .o_tready (cp_rem_out_tready ) + ); + end else begin : gen_no_cp_removal + assign cp_rem_out_tdata = fft_fifo_tdata; + assign cp_rem_out_tlast = fft_fifo_tlast; + assign cp_rem_out_tvalid = fft_fifo_tvalid; + assign fft_fifo_tready = cp_rem_out_tready; + end + + + //--------------------------------------------------------------------------- + // XFFT Configuration Handling + //--------------------------------------------------------------------------- + + logic [31:0] fft_in_tdata; + logic fft_in_tlast; + logic fft_in_tvalid; + logic fft_in_tready; + + logic [FFT_CONFIG_W-1:0] fft_config_core_tdata; + logic fft_config_core_tvalid; + logic fft_config_core_tready; + + // Create a register that indicates the first word transfer of packet + // (analogous to TLAST). + logic fft_in_tfirst = 1'b1; + + always_ff @(posedge clk) begin + if (rst) begin + fft_in_tfirst <= 1'b1; + end else if (fft_in_tvalid && fft_in_tready) begin + fft_in_tfirst <= fft_in_tlast; + end + end + + always_comb begin + if (EN_CONFIG_FIFO) begin + // In FIFO mode we require one configuration write for each FFT/IFFT + // packet that is input. This mode was used when the XFFT IP handled the + // CP insertion but was no longer needed when the CP insertion was moved + // to the reorder block. We keep it in the design in case we want to use + // a mode that requires this again in the future. + + // Only pass FFT data from cp_rem_out to fft_in when the configuration + // FIFO has a configuration for us. + fft_in_tdata = cp_rem_out_tdata; + fft_in_tlast = cp_rem_out_tlast; + fft_in_tvalid = cp_rem_out_tvalid && fft_config_fifo_tvalid; + cp_rem_out_tready = fft_in_tready && fft_config_fifo_tvalid; + + // Pass configuration from the fft_config_fifo to fft_config_core. Write + // the configuration when the first sample is input into the FFT core and + // pop the configuration off the configuration FIFO when the last sample + // is input into the FFT core. + fft_config_core_tdata = fft_config_fifo_tdata; + fft_config_core_tvalid = fft_in_tvalid && fft_in_tready && fft_in_tfirst; + fft_config_fifo_tready = fft_in_tvalid && fft_in_tready && fft_in_tlast; + + end else begin + // In non-FIFO mode we use whatever configuration value is on the + // fft_config input. + + // Pass FFT data from cp_rem_out to fft_in + fft_in_tdata = cp_rem_out_tdata; + fft_in_tlast = cp_rem_out_tlast; + fft_in_tvalid = cp_rem_out_tvalid; + cp_rem_out_tready = fft_in_tready; + + // Write the configuration when the first sample is input into the FFT + // core. + fft_config_core_tdata = fft_config; + fft_config_core_tvalid = fft_in_tvalid && fft_in_tready && fft_in_tfirst; + fft_config_fifo_tready = 1'b1; + end + end + + //synthesis translate_off + always_ff @(posedge clk) begin + if (fft_config_core_tvalid && !fft_config_core_tready) begin + $error("FFT configuration was not accepted by the XFFT core"); + end + end + //synthesis translate_on + + + //--------------------------------------------------------------------------- + // FFT IP Core + //--------------------------------------------------------------------------- + + logic [31:0] fft_out_tdata; + logic fft_out_tlast; + logic fft_out_tvalid; + logic fft_out_tready; + + logic event_tlast_unexpected; + logic event_tlast_missing; + + xfft_wrapper #( + .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2) + ) xfft_wrapper_i ( + .aclk (clk ), + .aresetn (~rst ), + .s_axis_config_tdata (fft_config_core_tdata ), + .s_axis_config_tvalid (fft_config_core_tvalid ), + .s_axis_config_tready (fft_config_core_tready ), + .s_axis_data_tdata ({ fft_in_tdata[15:0], fft_in_tdata[31:16] } ), + .s_axis_data_tlast (fft_in_tlast ), + .s_axis_data_tvalid (fft_in_tvalid ), + .s_axis_data_tready (fft_in_tready ), + .m_axis_data_tdata ({ fft_out_tdata[15:0], fft_out_tdata[31:16] }), + .m_axis_data_tuser ( ), + .m_axis_data_tlast (fft_out_tlast ), + .m_axis_data_tvalid (fft_out_tvalid ), + .m_axis_data_tready (fft_out_tready ), + .m_axis_status_tdata ( ), + .m_axis_status_tvalid ( ), + .m_axis_status_tready (1'b1 ), + .event_frame_started ( ), + .event_tlast_unexpected (event_tlast_unexpected ), + .event_tlast_missing (event_tlast_missing ), + .event_fft_overflow (event_fft_overflow ), + .event_status_channel_halt ( ), + .event_data_in_channel_halt ( ), + .event_data_out_channel_halt( ) + ); + + //synthesis translate_off + always_ff @(posedge clk) begin + // The packets are not being correctly sized if we get an unexpected or missing TLAST. + assert (event_tlast_unexpected != 1'b1) else $error("FFT TLAST unexpected"); + assert (event_tlast_missing != 1'b1) else $error("FFT TLAST missing"); + // Overflow can occur depending on the scaling settings and input data. + assert (event_fft_overflow != 1'b1) else $warning("FFT overflow"); + end + //synthesis translate_on + + + //--------------------------------------------------------------------------- + // Magnitude and Data Order Post-Processing + //--------------------------------------------------------------------------- + + if (EN_FFT_ORDER || EN_MAGNITUDE || EN_MAGNITUDE_SQ) begin : gen_fft_post_processing + logic [ DATA_W-1:0] pp_in_tdata; + logic [CP_LEN_W-1:0] pp_in_tuser; + logic pp_in_tlast; + logic pp_in_tvalid; + logic pp_in_tready; + + // Only transfer data when both the data and CP FIFOs have their data + // available. + assign pp_in_tdata = fft_out_tdata; + assign pp_in_tuser = cp_ins_fifo_tdata; + assign pp_in_tlast = fft_out_tlast; + assign pp_in_tvalid = fft_out_tvalid && cp_ins_fifo_tvalid; + assign fft_out_tready = pp_in_tready && cp_ins_fifo_tvalid; + + // Pop the CP off the FIFO at the end of the packet + assign cp_ins_fifo_tready = pp_in_tready && pp_in_tvalid && pp_in_tlast; + + fft_post_processing #( + .EN_FFT_ORDER (EN_FFT_ORDER ), + .EN_CP_INSERTION (EN_CP_INSERTION ), + .EN_MAGNITUDE (EN_MAGNITUDE ), + .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), + .USE_APPROX_MAG (USE_APPROX_MAG ), + .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2) + ) fft_post_processing_i ( + .clk (clk ), + .rst (rst ), + .fft_order_sel(fft_order ), + .magnitude_sel(magnitude ), + .fft_size_log2(fft_size_log2), + .s_axis_tdata (pp_in_tdata ), + .s_axis_tuser (pp_in_tuser ), + .s_axis_tlast (pp_in_tlast ), + .s_axis_tvalid(pp_in_tvalid ), + .s_axis_tready(pp_in_tready ), + .m_axis_tdata (o_tdata ), + .m_axis_tlast (o_tlast ), + .m_axis_tvalid(o_tvalid ), + .m_axis_tready(o_tready ) + ); + end else begin : gen_no_fft_post_processing + assign cp_ins_fifo_tready = 1'b1; + assign o_tdata = fft_out_tdata; + assign o_tlast = fft_out_tlast; + assign o_tvalid = fft_out_tvalid; + assign fft_out_tready = o_tready; + end + +endmodule : fft_pipeline + + +`default_nettype wire diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline_wrapper.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline_wrapper.sv new file mode 100644 index 0000000..787902d --- /dev/null +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_pipeline_wrapper.sv @@ -0,0 +1,310 @@ +// +// Copyright 2024 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: fft_pipeline_wrapper +// +// Description: +// +// This module takes a multiple-item-per-cycle data stream and splits the +// processing of packets across multiple FFT instances, each processing a +// single item/sample per cycle. +// +// See fft_pipeline for documentation of the fft_pipeline parameters and +// ports. To avoid duplication, it is not repeated here. +// +// Parameters: +// +// NIPC : Items/samples per cycle on the input/output data port (i_t*, o_t*d) +// + +`default_nettype none + + +module fft_pipeline_wrapper + import xfft_config_pkg::*; +#( + int NIPC = 1, + + // Parameters for fft_pipeline + int MAX_FFT_SIZE_LOG2 = 12, + bit EN_CONFIG_FIFO = 0, + bit EN_CP_REMOVAL = 1, + bit EN_CP_INSERTION = 1, + bit EN_FFT_ORDER = 1, + bit EN_MAGNITUDE = 1, + bit EN_MAGNITUDE_SQ = 1, + bit USE_APPROX_MAG = 1, + + localparam int FFT_CONFIG_W = fft_config_w(MAX_FFT_SIZE_LOG2), + localparam int DATA_W = 32, + localparam int FFT_SIZE_LOG2_W = $clog2(MAX_FFT_SIZE_LOG2+1), + localparam int CP_LEN_W = MAX_FFT_SIZE_LOG2 +) ( + input wire clk, + input wire rst, + + // Global FFT settings + input wire [ 1:0] fft_order, + input wire [ 1:0] magnitude, + input wire [FFT_SIZE_LOG2_W-1:0] fft_size_log2, + input wire [ FFT_CONFIG_W-1:0] fft_config, + + // FFT Configuration + input wire [FFT_CONFIG_W-1:0] fft_config_tdata, + input wire fft_config_tvalid, + output wire fft_config_tready, + + // CP Removal Length + input wire [CP_LEN_W-1:0] cp_rem_tdata, + input wire cp_rem_tvalid, + output wire cp_rem_tready, + + // CP Insertion Length + input wire [CP_LEN_W-1:0] cp_ins_tdata, + input wire cp_ins_tvalid, + output reg cp_ins_tready, + + // FFT Event Monitoring + output wire event_fft_overflow, + + // Data Input Packets + input wire [NIPC*DATA_W-1:0] i_tdata, + input wire i_tlast, + input wire i_tvalid, + output wire i_tready, + + // Data Output Packets + output wire [NIPC*DATA_W-1:0] o_tdata, + output wire o_tlast, + output wire o_tvalid, + input wire o_tready +); + + `include "usrp_utils.svh" + + if (NIPC == 1) begin : gen_one_spc + + fft_pipeline #( + .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2), + .EN_CONFIG_FIFO (EN_CONFIG_FIFO ), + .EN_CP_REMOVAL (EN_CP_REMOVAL ), + .EN_CP_INSERTION (EN_CP_INSERTION ), + .EN_FFT_ORDER (EN_FFT_ORDER ), + .EN_MAGNITUDE (EN_MAGNITUDE ), + .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), + .USE_APPROX_MAG (USE_APPROX_MAG ) + ) fft_pipeline_i ( + .clk (clk ), + .rst (rst ), + .fft_order (fft_order ), + .magnitude (magnitude ), + .fft_config (fft_config ), + .fft_size_log2 (fft_size_log2 ), + .fft_config_tdata (fft_config_tdata ), + .fft_config_tvalid (fft_config_tvalid ), + .fft_config_tready (fft_config_tready ), + .cp_rem_tdata (cp_rem_tdata ), + .cp_rem_tvalid (cp_rem_tvalid ), + .cp_rem_tready (cp_rem_tready ), + .cp_ins_tdata (cp_ins_tdata ), + .cp_ins_tvalid (cp_ins_tvalid ), + .cp_ins_tready (cp_ins_tready ), + .event_fft_overflow(event_fft_overflow), + .i_tdata (i_tdata ), + .i_tlast (i_tlast ), + .i_tvalid (i_tvalid ), + .i_tready (i_tready ), + .o_tdata (o_tdata ), + .o_tlast (o_tlast ), + .o_tvalid (o_tvalid ), + .o_tready (o_tready ) + ); + + end else begin : gen_multi_spc + + logic [FFT_CONFIG_W-1:0] fft_config_split_tdata [NIPC]; + logic fft_config_split_tvalid[NIPC]; + logic fft_config_split_tready[NIPC]; + + logic [CP_LEN_W-1:0] cp_rem_split_tdata [NIPC]; + logic cp_rem_split_tvalid [NIPC]; + logic cp_rem_split_tready [NIPC]; + + logic [CP_LEN_W-1:0] cp_ins_split_tdata [NIPC]; + logic cp_ins_split_tvalid [NIPC]; + logic cp_ins_split_tready [NIPC]; + + logic [DATA_W-1:0] i_split_tdata [NIPC]; + logic i_split_tlast [NIPC]; + logic i_split_tvalid [NIPC]; + logic i_split_tready [NIPC]; + + logic [DATA_W-1:0] o_split_tdata [NIPC]; + logic o_split_tlast [NIPC]; + logic o_split_tvalid [NIPC]; + logic o_split_tready [NIPC]; + + logic [NIPC-1:0] event_fft_overflow_split; + + if (EN_CONFIG_FIFO) begin : gen_config_fifo_split + axis_load_split #( + .IN_DATA_W (FFT_CONFIG_W), + .IN_FIFO_SIZE (1 ), + .OUT_DATA_W (FFT_CONFIG_W), + .OUT_FIFO_SIZE(1 ), + .OUT_NUM_PORTS(NIPC ) + ) axis_load_split_fft_config ( + .clk (clk ), + .rst (rst ), + .i_tdata (fft_config_tdata ), + .i_tuser ('0 ), + .i_tlast (1'b1 ), + .i_tvalid(fft_config_tvalid ), + .i_tready(fft_config_tready ), + .o_tdata (fft_config_split_tdata ), + .o_tuser ( ), + .o_tlast ( ), + .o_tvalid(fft_config_split_tvalid), + .o_tready(fft_config_split_tready) + ); + end else begin : gen_no_config_fifo_split + assign fft_config_tready = 1'b1; + for (genvar idx=0; idx < NIPC; idx++) begin : gen_assign + assign fft_config_split_tvalid[idx] = 1'b0; + end + end + + axis_load_split #( + .IN_DATA_W (CP_LEN_W), + .IN_FIFO_SIZE (1 ), + .OUT_DATA_W (CP_LEN_W), + .OUT_FIFO_SIZE(1 ), + .OUT_NUM_PORTS(NIPC ) + ) axis_load_split_cp_rem ( + .clk (clk ), + .rst (rst ), + .i_tdata (cp_rem_tdata ), + .i_tuser ('0 ), + .i_tlast (1'b1 ), + .i_tvalid(cp_rem_tvalid ), + .i_tready(cp_rem_tready ), + .o_tdata (cp_rem_split_tdata ), + .o_tuser ( ), + .o_tlast ( ), + .o_tvalid(cp_rem_split_tvalid), + .o_tready(cp_rem_split_tready) + ); + + axis_load_split #( + .IN_DATA_W (CP_LEN_W), + .IN_FIFO_SIZE (1 ), + .OUT_DATA_W (CP_LEN_W), + .OUT_FIFO_SIZE(1 ), + .OUT_NUM_PORTS(NIPC ) + ) axis_load_split_cp_ins ( + .clk (clk ), + .rst (rst ), + .i_tdata (cp_ins_tdata ), + .i_tuser ('0 ), + .i_tlast (1'b1 ), + .i_tvalid(cp_ins_tvalid ), + .i_tready(cp_ins_tready ), + .o_tdata (cp_ins_split_tdata ), + .o_tuser ( ), + .o_tlast ( ), + .o_tvalid(cp_ins_split_tvalid), + .o_tready(cp_ins_split_tready) + ); + + axis_load_split #( + .IN_DATA_W (NIPC*DATA_W ), + .IN_FIFO_SIZE (1 ), + .OUT_DATA_W (DATA_W ), + .OUT_FIFO_SIZE(MAX_FFT_SIZE_LOG2), + .OUT_NUM_PORTS(NIPC ), + .USER_W (1 ) + ) axis_load_split_data ( + .clk (clk ), + .rst (rst ), + .i_tdata (i_tdata ), + .i_tuser ('0 ), + .i_tlast (i_tlast ), + .i_tvalid(i_tvalid ), + .i_tready(i_tready ), + .o_tdata (i_split_tdata ), + .o_tuser ( ), + .o_tlast (i_split_tlast ), + .o_tvalid(i_split_tvalid), + .o_tready(i_split_tready) + ); + + for (genvar samp_i = 0; samp_i < NIPC; samp_i++) begin : gen_pipelines + fft_pipeline #( + .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2), + .EN_CONFIG_FIFO (EN_CONFIG_FIFO ), + .EN_CP_REMOVAL (EN_CP_REMOVAL ), + .EN_CP_INSERTION (EN_CP_INSERTION ), + .EN_FFT_ORDER (EN_FFT_ORDER ), + .EN_MAGNITUDE (EN_MAGNITUDE ), + .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), + .USE_APPROX_MAG (USE_APPROX_MAG ) + ) fft_pipeline_i ( + .clk (clk ), + .rst (rst ), + .fft_order (fft_order ), + .magnitude (magnitude ), + .fft_size_log2 (fft_size_log2 ), + .fft_config (fft_config ), + .fft_config_tdata (fft_config_split_tdata [samp_i]), + .fft_config_tvalid (fft_config_split_tvalid [samp_i]), + .fft_config_tready (fft_config_split_tready [samp_i]), + .cp_rem_tdata (cp_rem_split_tdata [samp_i]), + .cp_rem_tvalid (cp_rem_split_tvalid [samp_i]), + .cp_rem_tready (cp_rem_split_tready [samp_i]), + .cp_ins_tdata (cp_ins_split_tdata [samp_i]), + .cp_ins_tvalid (cp_ins_split_tvalid [samp_i]), + .cp_ins_tready (cp_ins_split_tready [samp_i]), + .event_fft_overflow(event_fft_overflow_split[samp_i]), + .i_tdata (i_split_tdata [samp_i]), + .i_tlast (i_split_tlast [samp_i]), + .i_tvalid (i_split_tvalid [samp_i]), + .i_tready (i_split_tready [samp_i]), + .o_tdata (o_split_tdata [samp_i]), + .o_tlast (o_split_tlast [samp_i]), + .o_tvalid (o_split_tvalid [samp_i]), + .o_tready (o_split_tready [samp_i]) + ); + end + + axis_load_merge #( + .IN_DATA_W (DATA_W ), + .IN_FIFO_SIZE (MAX_FFT_SIZE_LOG2), + .IN_NUM_PORTS (NIPC ), + .OUT_DATA_W (NIPC*DATA_W ), + .OUT_FIFO_SIZE(1 ), + .USER_W (1 ) + ) axis_load_merge_data ( + .clk (clk ), + .rst (rst ), + .i_tdata (o_split_tdata ), + .i_tuser ( ), + .i_tlast (o_split_tlast ), + .i_tvalid(o_split_tvalid), + .i_tready(o_split_tready), + .o_tdata (o_tdata ), + .o_tuser ( ), + .o_tlast (o_tlast ), + .o_tvalid(o_tvalid ), + .o_tready(o_tready ) + ); + + assign event_fft_overflow = |event_fft_overflow_split; + + end + +endmodule : fft_pipeline_wrapper + + +`default_nettype wire diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_post_processing.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_post_processing.sv index 39f0da3..99466d9 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/fft_post_processing.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_post_processing.sv @@ -350,7 +350,7 @@ module fft_post_processing #( .WIDTH (32), .SIZE (3), .PRE_FIFO_SIZE (0), - .POST_FIFO_SIZE(0) + .POST_FIFO_SIZE(1) ) axi_demux_i ( .clk (clk), .reset (rst), diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/fft_reorder.sv b/lib/rfnoc/blocks/rfnoc_block_fft/fft_reorder.sv index 0053c57..ea7ef02 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/fft_reorder.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/fft_reorder.sv @@ -393,8 +393,7 @@ module fft_reorder if (EN_CP_INSERTION) begin: gen_cp_ins_fifo logic [CP_LEN_W-1:0] cp_len_tdata; logic cp_len_tvalid; - logic cp_len_tready; - logic i_tvalid; + logic tmp_i_tvalid; logic in_fifo_o_tfirst = '1; // First transfer of packet // Create a register that indicates when the next transfer is the start of @@ -410,7 +409,7 @@ module fft_reorder end // Write the first tuser word of the packet into the CP length FIFO - assign i_tvalid = in_fifo_o_tvalid && in_fifo_o_tready && in_fifo_o_tfirst; + assign tmp_i_tvalid = in_fifo_o_tvalid && in_fifo_o_tready && in_fifo_o_tfirst; // The dual RAM buffer can only hold two FFTs at a time, so we can // guarantee this FIFO has sufficient room and will always be ready by @@ -423,7 +422,7 @@ module fft_reorder .reset (rst), .clear ('0), .i_tdata (in_fifo_o_tuser), - .i_tvalid(i_tvalid), + .i_tvalid(tmp_i_tvalid), .i_tready(), .o_tdata (cp_len_tdata), .o_tvalid(cp_len_tvalid), diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v b/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v index 3d22c16..ed7d56f 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v +++ b/lib/rfnoc/blocks/rfnoc_block_fft/noc_shell_fft.v @@ -272,9 +272,9 @@ module noc_shell_fft #( .NIPC (NIPC), .SYNC_CLKS (0), .INFO_FIFO_SIZE ($clog2(32)), - .PYLD_FIFO_SIZE ($clog2(2**MTU)), + .PYLD_FIFO_SIZE ($clog2(32)), .MTU (MTU), - .SIDEBAND_AT_END (1) + .SIDEBAND_AT_END (0) ) axis_data_to_chdr_out_out ( .axis_chdr_clk (rfnoc_chdr_clk), .axis_chdr_rst (rfnoc_chdr_rst), diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.sv b/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.sv index 4dac8ba..f2d3ace 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft.sv @@ -14,6 +14,8 @@ // THIS_PORTID : Control crossbar port to which this block is connected // CHDR_W : AXIS-CHDR data bus width // MTU : Log2 of maximum transmission unit +// NIPC : Number of samples/items per clock cycle to +// process internally. // NUM_PORTS : Total number of FFT channels // NUM_CORES : Number of individual cores to instantiate. // Setting to 1 means all ports use a shared core @@ -25,6 +27,11 @@ // must be a multiple of NUM_CORES. // MAX_FFT_SIZE_LOG2 : Log2 of maximum configurable FFT size. That is, // the FFT size is exactly 2**fft_size_log2. +// EN_CP_INSERTION : Controls whether to include the cyclic prefix +// insertion logic. If included, EN_FFT_ORDER must +// be 1. +// EN_CP_REMOVAL : Controls whether to include the cyclic prefix +// removal logic. // MAX_CP_LIST_LEN_INS_LOG2 : Log2 of max length of cyclic prefix insertion // list. Actual max is 2**MAX_CP_LIST_LEN_INS_LOG2. // MAX_CP_LIST_LEN_REM_LOG2 : Log2 of max length of cyclic prefix removal @@ -54,16 +61,19 @@ module rfnoc_block_fft #( logic [9:0] THIS_PORTID = 10'd0, int CHDR_W = 64, logic [5:0] MTU = 6'd10, + int NIPC = 1, int NUM_PORTS = 1, int NUM_CORES = 1, - int MAX_FFT_SIZE_LOG2 = 12, + int MAX_FFT_SIZE_LOG2 = 10, + bit EN_CP_REMOVAL = 1, + bit EN_CP_INSERTION = 1, int MAX_CP_LIST_LEN_INS_LOG2 = 5, int MAX_CP_LIST_LEN_REM_LOG2 = 5, bit CP_INSERTION_REPEAT = 1, bit CP_REMOVAL_REPEAT = 1, - bit EN_FFT_BYPASS = 1, + bit EN_FFT_BYPASS = 0, bit EN_FFT_ORDER = 1, - bit EN_MAGNITUDE = 1, + bit EN_MAGNITUDE = 0, bit EN_MAGNITUDE_SQ = 1, bit USE_APPROX_MAG = 1 ) ( @@ -109,6 +119,14 @@ module rfnoc_block_fft #( localparam ITEM_W = 32; + // Calculate the number of channels per core + localparam int NCPC = NUM_PORTS / NUM_CORES; + + // We require each FFT core instance to have the same number of channels + if (NUM_CORES * NCPC != NUM_PORTS) begin : check_num_ports_per_core + $error("NUM_PORTS must be a multiple of NUM_CORES"); + end + //--------------------------------------------------------------------------- // Signal Declarations @@ -124,27 +142,27 @@ module rfnoc_block_fft #( logic ctrlport_resp_ack; logic [CTRLPORT_DATA_W-1:0] ctrlport_resp_data; - logic [ ITEM_W*NUM_PORTS-1:0] in_axis_tdata; - logic [ NUM_PORTS-1:0] in_axis_tkeep; - logic [ NUM_PORTS-1:0] in_axis_tlast; - logic [ NUM_PORTS-1:0] in_axis_tvalid; - logic [ NUM_PORTS-1:0] in_axis_tready; - logic [CHDR_TIMESTAMP_W*NUM_PORTS-1:0] in_axis_ttimestamp; - logic [ NUM_PORTS-1:0] in_axis_thas_time; - logic [ CHDR_LENGTH_W*NUM_PORTS-1:0] in_axis_tlength; - logic [ NUM_PORTS-1:0] in_axis_teov; - logic [ NUM_PORTS-1:0] in_axis_teob; + logic [NUM_CORES-1:0][NCPC-1:0][ ITEM_W*NIPC-1:0] in_axis_tdata; + logic [NUM_CORES-1:0][NCPC-1:0][ NIPC-1:0] in_axis_tkeep; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] in_axis_tlast; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] in_axis_tvalid; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] in_axis_tready; + logic [NUM_CORES-1:0][NCPC-1:0][CHDR_TIMESTAMP_W-1:0] in_axis_ttimestamp; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] in_axis_thas_time; + logic [NUM_CORES-1:0][NCPC-1:0][ CHDR_LENGTH_W-1:0] in_axis_tlength; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] in_axis_teov; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] in_axis_teob; - logic [ ITEM_W*NUM_PORTS-1:0] out_axis_tdata; - logic [ NUM_PORTS-1:0] out_axis_tkeep; - logic [ NUM_PORTS-1:0] out_axis_tlast; - logic [ NUM_PORTS-1:0] out_axis_tvalid; - logic [ NUM_PORTS-1:0] out_axis_tready; - logic [CHDR_TIMESTAMP_W*NUM_PORTS-1:0] out_axis_ttimestamp; - logic [ NUM_PORTS-1:0] out_axis_thas_time; - logic [ CHDR_LENGTH_W*NUM_PORTS-1:0] out_axis_tlength; - logic [ NUM_PORTS-1:0] out_axis_teov; - logic [ NUM_PORTS-1:0] out_axis_teob; + logic [NUM_CORES-1:0][NCPC-1:0][ ITEM_W*NIPC-1:0] out_axis_tdata; + logic [NUM_CORES-1:0][NCPC-1:0][ NIPC-1:0] out_axis_tkeep; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] out_axis_tlast; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] out_axis_tvalid; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] out_axis_tready; + logic [NUM_CORES-1:0][NCPC-1:0][CHDR_TIMESTAMP_W-1:0] out_axis_ttimestamp; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] out_axis_thas_time; + logic [NUM_CORES-1:0][NCPC-1:0][ CHDR_LENGTH_W-1:0] out_axis_tlength; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] out_axis_teov; + logic [NUM_CORES-1:0][NCPC-1:0][ 0:0] out_axis_teob; //--------------------------------------------------------------------------- @@ -155,7 +173,9 @@ module rfnoc_block_fft #( .CHDR_W (CHDR_W), .THIS_PORTID(THIS_PORTID), .MTU (MTU), - .NUM_PORTS (NUM_PORTS) + .NUM_PORTS (NUM_PORTS), + .NIPC (NIPC), + .ITEM_W (ITEM_W) ) noc_shell_fft_i ( //--------------------- // Framework Interface @@ -236,12 +256,12 @@ module rfnoc_block_fft #( // CtrlPort Splitter //--------------------------------------------------------------------------- - wire [ NUM_CORES-1:0] dec_ctrlport_req_wr; - wire [ NUM_CORES-1:0] dec_ctrlport_req_rd; - wire [CTRLPORT_ADDR_W*NUM_CORES-1:0] dec_ctrlport_req_addr; - wire [CTRLPORT_DATA_W*NUM_CORES-1:0] dec_ctrlport_req_data; - wire [ NUM_CORES-1:0] dec_ctrlport_resp_ack; - wire [CTRLPORT_DATA_W*NUM_CORES-1:0] dec_ctrlport_resp_data; + logic [NUM_CORES-1:0][ 0:0] dec_ctrlport_req_wr; + logic [NUM_CORES-1:0][ 0:0] dec_ctrlport_req_rd; + logic [NUM_CORES-1:0][CTRLPORT_ADDR_W-1:0] dec_ctrlport_req_addr; + logic [NUM_CORES-1:0][CTRLPORT_DATA_W-1:0] dec_ctrlport_req_data; + logic [NUM_CORES-1:0][ 0:0] dec_ctrlport_resp_ack; + logic [NUM_CORES-1:0][CTRLPORT_DATA_W-1:0] dec_ctrlport_resp_data; generate if (NUM_CORES > 1) begin : gen_ctrlport_decoder @@ -276,8 +296,7 @@ module rfnoc_block_fft #( end else begin : gen_no_decoder assign dec_ctrlport_req_wr = ctrlport_req_wr; assign dec_ctrlport_req_rd = ctrlport_req_rd; - assign dec_ctrlport_req_addr = {{CTRLPORT_DATA_W-FFT_CORE_ADDR_W{1'b0}}, - ctrlport_req_addr[FFT_CORE_ADDR_W-1:0]}; + assign dec_ctrlport_req_addr = CTRLPORT_ADDR_W'(ctrlport_req_addr[FFT_CORE_ADDR_W-1:0]); assign dec_ctrlport_req_data = ctrlport_req_data; assign ctrlport_resp_ack = dec_ctrlport_resp_ack; assign ctrlport_resp_data = dec_ctrlport_resp_data; @@ -289,59 +308,56 @@ module rfnoc_block_fft #( // FFT Core //--------------------------------------------------------------------------- - // Calculate the number of ports per core - localparam int NPPC = NUM_PORTS / NUM_CORES; + // Convert CHDR MTU to packet size in items + localparam int MAX_PKT_SIZE_LOG2 = $clog2(2**MTU * CHDR_W/ITEM_W); - if (NUM_CORES * NPPC != NUM_PORTS) begin : check_num_ports_per_core - // We require each FFT core instance to have the same number of channels. - ERROR__NUM_PORTS_must_be_a_multiple_of_NUM_CORES(); - end : check_num_ports_per_core - - genvar core_i; - - for (core_i = 0; core_i < NUM_CORES; core_i = core_i+1) begin : gen_fft_cores + for (genvar core_i = 0; core_i < NUM_CORES; core_i = core_i+1) begin : gen_fft_cores fft_core #( - .NUM_CHAN (NPPC), - .NUM_CORES (NUM_CORES), - .MAX_FFT_SIZE_LOG2 (MAX_FFT_SIZE_LOG2), + .NIPC (NIPC ), + .NUM_CHAN (NCPC ), + .NUM_CORES (NUM_CORES ), + .MAX_PKT_SIZE_LOG2 (MAX_PKT_SIZE_LOG2 ), + .MAX_FFT_SIZE_LOG2 (MAX_FFT_SIZE_LOG2 ), + .EN_CP_REMOVAL (EN_CP_REMOVAL ), + .EN_CP_INSERTION (EN_CP_INSERTION ), .MAX_CP_LIST_LEN_INS_LOG2(MAX_CP_LIST_LEN_INS_LOG2), .MAX_CP_LIST_LEN_REM_LOG2(MAX_CP_LIST_LEN_REM_LOG2), - .CP_INSERTION_REPEAT (CP_INSERTION_REPEAT), - .CP_REMOVAL_REPEAT (CP_REMOVAL_REPEAT), - .EN_FFT_BYPASS (EN_FFT_BYPASS), - .EN_FFT_ORDER (EN_FFT_ORDER), - .EN_MAGNITUDE (EN_MAGNITUDE), - .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ), - .USE_APPROX_MAG (USE_APPROX_MAG) + .CP_INSERTION_REPEAT (CP_INSERTION_REPEAT ), + .CP_REMOVAL_REPEAT (CP_REMOVAL_REPEAT ), + .EN_FFT_BYPASS (EN_FFT_BYPASS ), + .EN_FFT_ORDER (EN_FFT_ORDER ), + .EN_MAGNITUDE (EN_MAGNITUDE ), + .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), + .USE_APPROX_MAG (USE_APPROX_MAG ) ) fft_core_i ( .ce_clk (ce_clk), .ce_rst (ce_rst), - .s_ctrlport_req_wr (`BUS_I(dec_ctrlport_req_wr, 1, core_i)), - .s_ctrlport_req_rd (`BUS_I(dec_ctrlport_req_rd, 1, core_i)), - .s_ctrlport_req_addr (`BUS_I(dec_ctrlport_req_addr, CTRLPORT_ADDR_W, core_i)), - .s_ctrlport_req_data (`BUS_I(dec_ctrlport_req_data, CTRLPORT_DATA_W, core_i)), - .s_ctrlport_resp_ack (`BUS_I(dec_ctrlport_resp_ack, 1, core_i)), - .s_ctrlport_resp_data (`BUS_I(dec_ctrlport_resp_data, CTRLPORT_DATA_W, core_i)), - .s_in_axis_tdata (`BUS_I(in_axis_tdata, ITEM_W*NPPC, core_i)), - .s_in_axis_tkeep (`BUS_I(in_axis_tkeep, 1*NPPC, core_i)), - .s_in_axis_tlast (`BUS_I(in_axis_tlast, 1*NPPC, core_i)), - .s_in_axis_tvalid (`BUS_I(in_axis_tvalid, 1*NPPC, core_i)), - .s_in_axis_tready (`BUS_I(in_axis_tready, 1*NPPC, core_i)), - .s_in_axis_ttimestamp (`BUS_I(in_axis_ttimestamp, CHDR_TIMESTAMP_W*NPPC, core_i)), - .s_in_axis_thas_time (`BUS_I(in_axis_thas_time, 1*NPPC, core_i)), - .s_in_axis_tlength (`BUS_I(in_axis_tlength, CHDR_LENGTH_W*NPPC, core_i)), - .s_in_axis_teov (`BUS_I(in_axis_teov, 1*NPPC, core_i)), - .s_in_axis_teob (`BUS_I(in_axis_teob, 1*NPPC, core_i)), - .m_out_axis_tdata (`BUS_I(out_axis_tdata, ITEM_W*NPPC, core_i)), - .m_out_axis_tkeep (`BUS_I(out_axis_tkeep, 1*NPPC, core_i)), - .m_out_axis_tlast (`BUS_I(out_axis_tlast, 1*NPPC, core_i)), - .m_out_axis_tvalid (`BUS_I(out_axis_tvalid, 1*NPPC, core_i)), - .m_out_axis_tready (`BUS_I(out_axis_tready, 1*NPPC, core_i)), - .m_out_axis_ttimestamp(`BUS_I(out_axis_ttimestamp, CHDR_TIMESTAMP_W*NPPC, core_i)), - .m_out_axis_thas_time (`BUS_I(out_axis_thas_time, 1*NPPC, core_i)), - .m_out_axis_tlength (`BUS_I(out_axis_tlength, CHDR_LENGTH_W*NPPC, core_i)), - .m_out_axis_teov (`BUS_I(out_axis_teov, 1*NPPC, core_i)), - .m_out_axis_teob (`BUS_I(out_axis_teob, 1*NPPC, core_i)) + .s_ctrlport_req_wr (dec_ctrlport_req_wr [core_i]), + .s_ctrlport_req_rd (dec_ctrlport_req_rd [core_i]), + .s_ctrlport_req_addr (dec_ctrlport_req_addr [core_i]), + .s_ctrlport_req_data (dec_ctrlport_req_data [core_i]), + .s_ctrlport_resp_ack (dec_ctrlport_resp_ack [core_i]), + .s_ctrlport_resp_data (dec_ctrlport_resp_data[core_i]), + .s_in_axis_tdata (in_axis_tdata [core_i]), + .s_in_axis_tkeep (in_axis_tkeep [core_i]), + .s_in_axis_tlast (in_axis_tlast [core_i]), + .s_in_axis_tvalid (in_axis_tvalid [core_i]), + .s_in_axis_tready (in_axis_tready [core_i]), + .s_in_axis_ttimestamp (in_axis_ttimestamp [core_i]), + .s_in_axis_thas_time (in_axis_thas_time [core_i]), + .s_in_axis_tlength (in_axis_tlength [core_i]), + .s_in_axis_teov (in_axis_teov [core_i]), + .s_in_axis_teob (in_axis_teob [core_i]), + .m_out_axis_tdata (out_axis_tdata [core_i]), + .m_out_axis_tkeep (out_axis_tkeep [core_i]), + .m_out_axis_tlast (out_axis_tlast [core_i]), + .m_out_axis_tvalid (out_axis_tvalid [core_i]), + .m_out_axis_tready (out_axis_tready [core_i]), + .m_out_axis_ttimestamp(out_axis_ttimestamp [core_i]), + .m_out_axis_thas_time (out_axis_thas_time [core_i]), + .m_out_axis_tlength (out_axis_tlength [core_i]), + .m_out_axis_teov (out_axis_teov [core_i]), + .m_out_axis_teob (out_axis_teob [core_i]) ); end : gen_fft_cores diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_all_tb.sv b/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_all_tb.sv index d9c7a54..993c3ec 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_all_tb.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_all_tb.sv @@ -18,35 +18,89 @@ module rfnoc_block_fft_all_tb; // Test Configurations //--------------------------------------------------------------------------- - // Basic tests of multi-ports configurations - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(2), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(10)) tb_0a (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(2), .NUM_CORES(2), .MAX_FFT_SIZE_LOG2(10)) tb_0b (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(4), .NUM_CORES(2), .MAX_FFT_SIZE_LOG2(10)) tb_0c (); + // Basic tests of multi-port configurations + rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(2), .NUM_CORES(1), + .MAX_FFT_SIZE_LOG2(10)) tb_0a (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(2), .NUM_CORES(2), + .MAX_FFT_SIZE_LOG2(10)) tb_0b (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(4), .NUM_CORES(2), + .MAX_FFT_SIZE_LOG2(10)) tb_0c (); // Basic tests of other FFT sizes - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(11)) tb_1a (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(12)) tb_1b (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(13)) tb_1c (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(14)) tb_1d (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(15)) tb_1e (); - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(16)) tb_1f (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .MAX_FFT_SIZE_LOG2(11)) tb_1a (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .MAX_FFT_SIZE_LOG2(12)) tb_1b (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .MAX_FFT_SIZE_LOG2(13)) tb_1c (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .MAX_FFT_SIZE_LOG2(14)) tb_1d (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .MAX_FFT_SIZE_LOG2(15)) tb_1e (); + // Skip the 64k FFT test because it's broken :-( + //rfnoc_block_fft_tb #(.FULL_TEST(0), .MAX_FFT_SIZE_LOG2(16)) tb_1f (); - // Test case where USE_APPROX_MAG = 1 - rfnoc_block_fft_tb #(.FULL_TEST(0), .NUM_PORTS(1), .NUM_CORES(1), .MAX_FFT_SIZE_LOG2(10), - .EN_FFT_BYPASS(1), .EN_MAGNITUDE(1), .USE_APPROX_MAG(1)) tb_2a (); + // Do quick tests with various features disabled to ensure these features get + // disabled and bypassed correctly. The cyclic prefix logic, the magnitude + // and order logic, and the FFT bypass are in separate components. To avoid + // testing every possible permutation, we permute each of these separately. + // + // Test permutations of CP insertion/removal with other features disabled + rfnoc_block_fft_tb #(.FULL_TEST(0), .EN_CP_REMOVAL(0), .EN_CP_INSERTION(1), .EN_MAGNITUDE(0), + .EN_MAGNITUDE_SQ(0), .EN_FFT_ORDER(1), .EN_FFT_BYPASS(0)) tb_2c (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .EN_CP_REMOVAL(1), .EN_CP_INSERTION(0), .EN_MAGNITUDE(0), + .EN_MAGNITUDE_SQ(0), .EN_FFT_ORDER(0), .EN_FFT_BYPASS(1)) tb_2b (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .EN_CP_REMOVAL(1), .EN_CP_INSERTION(1), .EN_MAGNITUDE(0), + .EN_MAGNITUDE_SQ(0), .EN_FFT_ORDER(1), .EN_FFT_BYPASS(0)) tb_2d (); + // + // Test permutations of magnitude with FFT bypass enabled + rfnoc_block_fft_tb #(.FULL_TEST(0), .EN_CP_REMOVAL(0), .EN_CP_INSERTION(0), .EN_MAGNITUDE(0), + .EN_MAGNITUDE_SQ(1), .EN_FFT_ORDER(0), .EN_FFT_BYPASS(1)) tb_2g (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .EN_CP_REMOVAL(0), .EN_CP_INSERTION(0), .EN_MAGNITUDE(1), + .EN_MAGNITUDE_SQ(0), .EN_FFT_ORDER(0), .EN_FFT_BYPASS(0)) tb_2h (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .EN_CP_REMOVAL(0), .EN_CP_INSERTION(0), .EN_MAGNITUDE(1), + .EN_MAGNITUDE_SQ(1), .EN_FFT_ORDER(0), .EN_FFT_BYPASS(1)) tb_2i (); - // Run full suite of tests on 1k FFT configuration - rfnoc_block_fft_tb #( - .FULL_TEST (1 ), - .NUM_PORTS (1 ), - .NUM_CORES (1 ), - .MAX_FFT_SIZE_LOG2 (10), - .MAX_CP_LIST_LEN_INS_LOG2(5 ), - .MAX_CP_LIST_LEN_REM_LOG2(5 ), - .EN_MAGNITUDE_SQ (1 ), - .EN_MAGNITUDE (1 ), - .EN_FFT_BYPASS (1 ), - .USE_APPROX_MAG (0 ) - ) tb_3a (); + // Test SPC of 1, 2, 4, 8 with all features enabled + for (genvar log_spc = 0; log_spc < 3; log_spc++) begin : gen_test_multi_spc + localparam int SPC = 2**log_spc; + rfnoc_block_fft_tb #( + .FULL_TEST (1 ), + .CHDR_W (64*SPC), + .NIPC (SPC ), + .NUM_PORTS (1 ), + .NUM_CORES (1 ), + .MAX_FFT_SIZE_LOG2 (10 ), + .EN_CP_REMOVAL (1 ), + .EN_CP_INSERTION (1 ), + .MAX_CP_LIST_LEN_INS_LOG2(5 ), + .MAX_CP_LIST_LEN_REM_LOG2(5 ), + .EN_MAGNITUDE (1 ), + .EN_MAGNITUDE_SQ (1 ), + .EN_FFT_BYPASS (1 ), + .USE_APPROX_MAG (1 ) + ) tb_3a (); + end : gen_test_multi_spc + + // Test SPC of 1, 2 with extra features disabled + for (genvar log_spc = 0; log_spc < 1; log_spc++) begin : gen_test_features_disabled + localparam int SPC = 2**log_spc; + rfnoc_block_fft_tb #( + .FULL_TEST (1 ), + .CHDR_W (64*SPC), + .NIPC (SPC ), + .NUM_PORTS (1 ), + .NUM_CORES (1 ), + .MAX_FFT_SIZE_LOG2 (10 ), + .EN_CP_REMOVAL (0 ), + .EN_CP_INSERTION (0 ), + .MAX_CP_LIST_LEN_INS_LOG2(5 ), + .MAX_CP_LIST_LEN_REM_LOG2(5 ), + .EN_MAGNITUDE (0 ), + .EN_MAGNITUDE_SQ (0 ), + .EN_FFT_ORDER (0 ), + .EN_FFT_BYPASS (0 ), + .USE_APPROX_MAG (0 ) + ) tb_3b (); + end : gen_test_features_disabled + + // Run quick test on some other multi-SPC configurations + rfnoc_block_fft_tb #(.FULL_TEST(0), .CHDR_W(128), .NIPC(4)) tb_3c (); + rfnoc_block_fft_tb #(.FULL_TEST(0), .CHDR_W(256), .NIPC(8)) tb_3d (); endmodule : rfnoc_block_fft_all_tb diff --git a/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv b/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv index f738f16..1939c69 100644 --- a/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv +++ b/lib/rfnoc/blocks/rfnoc_block_fft/rfnoc_block_fft_tb.sv @@ -12,18 +12,23 @@ module rfnoc_block_fft_tb #( - bit FULL_TEST = 1, - int NUM_PORTS = 1, - int NUM_CORES = 1, - int MAX_FFT_SIZE_LOG2 = 10, - int MAX_CP_LIST_LEN_INS_LOG2 = 5, - int MAX_CP_LIST_LEN_REM_LOG2 = 5, - bit EN_MAGNITUDE_SQ = 1, - bit EN_MAGNITUDE = 1, - bit EN_FFT_ORDER = 1, - bit EN_FFT_BYPASS = 1, - bit USE_APPROX_MAG = 0, - bit VERBOSE = 0 + bit FULL_TEST = 1, + int CHDR_W = 64, + int NIPC = 1, + int NUM_PORTS = 1, + int NUM_CORES = 1, + int MAX_FFT_SIZE_LOG2 = 10, + int MAX_TEST_FFT_SIZE_LOG2 = MAX_FFT_SIZE_LOG2, + bit EN_CP_REMOVAL = 1, + bit EN_CP_INSERTION = 1, + int MAX_CP_LIST_LEN_INS_LOG2 = 5, + int MAX_CP_LIST_LEN_REM_LOG2 = 5, + bit EN_MAGNITUDE = 1, + bit EN_MAGNITUDE_SQ = 1, + bit EN_FFT_ORDER = 1, + bit EN_FFT_BYPASS = 1, + bit USE_APPROX_MAG = 1, + bit VERBOSE = 0 ); `include "test_exec.svh" @@ -57,25 +62,33 @@ module rfnoc_block_fft_tb #( localparam int CP_INSERTION_REPEAT = 1; localparam int CP_REMOVAL_REPEAT = 1; localparam int MAX_FFT_SIZE = 2**MAX_FFT_SIZE_LOG2; + localparam int MAX_TEST_FFT_SIZE = 2**MAX_TEST_FFT_SIZE_LOG2; localparam int MAX_CP_LEN_LOG2 = MAX_FFT_SIZE_LOG2; localparam int MAX_CP_LEN = 2**MAX_CP_LEN_LOG2-1; - localparam int MIN_FFT_SIZE_LOG2 = 3; // Minimum allowed by Xilinx FFT core + localparam int MAX_TEST_CP_LEN = MAX_TEST_FFT_SIZE-NIPC; + localparam int MIN_FFT_SIZE_LOG2 = NIPC < 8 ? 3 : // Minimum allowed by Xilinx FFT core + $clog2(2*NIPC); // The FFT must be at least 2 transfers localparam int MIN_FFT_SIZE = 2**MIN_FFT_SIZE_LOG2; - localparam int FFT_SCALING = fft_scale_default(MAX_FFT_SIZE_LOG2); + localparam bit EN_TIME_ALL_PKTS = 1; + + // Native order used by FFT block when EN_FFT_ORDER is disabled + localparam int NATIVE_FFT_ORDER = BIT_REVERSE; // RFNoC configuration localparam [9:0] THIS_PORTID = 10'h123; - localparam int CHDR_W = 64; // CHDR size in bits - localparam int MTU = 10; // Log2 of max transmission unit in CHDR words localparam int NUM_PORTS_I = NUM_PORTS; localparam int NUM_PORTS_O = NUM_PORTS; - localparam int ITEM_W = 32; // Sample size in bits - localparam int SPP = 64; // Samples per packet. Must be a power of 2 for FFT. - localparam int PKT_SIZE_BYTES = SPP * (ITEM_W/8); - localparam int STALL_PROB = 50; // Default BFM stall probability - localparam real CHDR_CLK_PER = 5.0; // 200 MHz - localparam real CTRL_CLK_PER = 8.0; // 125 MHz - localparam real CE_CLK_PER = 4.0; // 250 MHz + localparam int ITEM_W = 32; // Sample size in bits + localparam int BYTE_MTU = $clog2(8*1024); // 8 KiB packets + localparam int ITEM_MTU = $clog2(2**BYTE_MTU / (ITEM_W/8)); + localparam int CHDR_MTU = $clog2(2**BYTE_MTU / (CHDR_W/8)); + localparam int DEFAULT_SPP = 128; // Default samples per packet + localparam int MAX_SPP = 2**ITEM_MTU; // Max samples per packet + localparam int MIN_SPP = 8; // Min samples per packet + localparam int STALL_PROB = 50; // Default BFM stall probability + localparam real CHDR_CLK_PER = 5.0; // 200 MHz + localparam real CTRL_CLK_PER = 8.0; // 125 MHz + localparam real CE_CLK_PER = 4.0; // 250 MHz //--------------------------------------------------------------------------- @@ -121,7 +134,7 @@ module rfnoc_block_fft_tb #( // Connect block controller to BFMs for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_bfm_input_connections initial begin - blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); + blk_ctrl.connect_master_data_port(i, m_chdr[i], MAX_SPP*(ITEM_W/8)); blk_ctrl.set_master_stall_prob(i, STALL_PROB); end end @@ -166,43 +179,46 @@ module rfnoc_block_fft_tb #( end rfnoc_block_fft #( - .THIS_PORTID (THIS_PORTID), - .CHDR_W (CHDR_W), - .MTU (MTU), - .NUM_PORTS (NUM_PORTS), - .NUM_CORES (NUM_CORES), - .MAX_FFT_SIZE_LOG2 (MAX_FFT_SIZE_LOG2), + .THIS_PORTID (THIS_PORTID ), + .CHDR_W (CHDR_W ), + .MTU (CHDR_MTU ), + .NIPC (NIPC ), + .NUM_PORTS (NUM_PORTS ), + .NUM_CORES (NUM_CORES ), + .MAX_FFT_SIZE_LOG2 (MAX_FFT_SIZE_LOG2 ), + .EN_CP_REMOVAL (EN_CP_REMOVAL ), + .EN_CP_INSERTION (EN_CP_INSERTION ), .MAX_CP_LIST_LEN_INS_LOG2(MAX_CP_LIST_LEN_INS_LOG2), .MAX_CP_LIST_LEN_REM_LOG2(MAX_CP_LIST_LEN_REM_LOG2), - .CP_INSERTION_REPEAT (CP_INSERTION_REPEAT), - .CP_REMOVAL_REPEAT (CP_REMOVAL_REPEAT), - .EN_FFT_BYPASS (EN_FFT_BYPASS), - .EN_FFT_ORDER (EN_FFT_ORDER), - .EN_MAGNITUDE (EN_MAGNITUDE), - .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ), - .USE_APPROX_MAG (USE_APPROX_MAG) + .CP_INSERTION_REPEAT (CP_INSERTION_REPEAT ), + .CP_REMOVAL_REPEAT (CP_REMOVAL_REPEAT ), + .EN_FFT_BYPASS (EN_FFT_BYPASS ), + .EN_FFT_ORDER (EN_FFT_ORDER ), + .EN_MAGNITUDE (EN_MAGNITUDE ), + .EN_MAGNITUDE_SQ (EN_MAGNITUDE_SQ ), + .USE_APPROX_MAG (USE_APPROX_MAG ) ) dut ( - .rfnoc_chdr_clk (rfnoc_chdr_clk), - .rfnoc_ctrl_clk (rfnoc_ctrl_clk), - .ce_clk (ce_clk), - .rfnoc_core_config (backend.cfg), - .rfnoc_core_status (backend.sts), - .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata), - .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast), + .rfnoc_chdr_clk (rfnoc_chdr_clk ), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk ), + .ce_clk (ce_clk ), + .rfnoc_core_config (backend.cfg ), + .rfnoc_core_status (backend.sts ), + .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata ), + .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast ), .s_rfnoc_chdr_tvalid(s_rfnoc_chdr_tvalid), .s_rfnoc_chdr_tready(s_rfnoc_chdr_tready), - .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata), - .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast), + .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata ), + .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast ), .m_rfnoc_chdr_tvalid(m_rfnoc_chdr_tvalid), .m_rfnoc_chdr_tready(m_rfnoc_chdr_tready), - .s_rfnoc_ctrl_tdata (m_ctrl.tdata), - .s_rfnoc_ctrl_tlast (m_ctrl.tlast), - .s_rfnoc_ctrl_tvalid(m_ctrl.tvalid), - .s_rfnoc_ctrl_tready(m_ctrl.tready), - .m_rfnoc_ctrl_tdata (s_ctrl.tdata), - .m_rfnoc_ctrl_tlast (s_ctrl.tlast), - .m_rfnoc_ctrl_tvalid(s_ctrl.tvalid), - .m_rfnoc_ctrl_tready(s_ctrl.tready) + .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 ) ); @@ -245,8 +261,8 @@ module rfnoc_block_fft_tb #( // Configures the FFT core using the given parameters. // // fft_size, : Length of FFT (e.g., 4096 for 4k FFT) - // cp_insertions[] : List of cyclic-prefix insertions to load // cp_removals[] : List of cyclic-prefix removals to load + // cp_insertions[] : List of cyclic-prefix insertions to load // fft_scaling : Scaling value to be passed to FFT core // fft_direction : Direction of FFT (FFT_FORWARD, FFT_INVERSE) // cp_list_clear : When true, cyclic-prefix list will be reset @@ -257,18 +273,45 @@ module rfnoc_block_fft_tb #( // task automatic config_fft ( int fft_size, - int cp_insertions[] = {}, int cp_removals[] = {}, + int cp_insertions[] = {}, int fft_scaling = fft_scale_default($clog2(fft_size)), bit fft_direction = FFT_FORWARD, bit cp_list_clear = 1, bit fft_bypass = 0, - int fft_order_sel = FFT_ORDER_NATURAL, + int fft_order_sel = EN_FFT_ORDER ? NATURAL : NATIVE_FFT_ORDER, int magnitude_sel = 0, int core = 0 ); logic [31:0] fft_size_log2 = $clog2(fft_size); + `ASSERT_ERROR(2**fft_size_log2 == fft_size, + "config_fft(): fft_size must be a power of 2"); + `ASSERT_ERROR(EN_CP_REMOVAL || cp_removals.size() == 0, + "config_fft(): Cyclic prefix removal not allowed when disabled"); + `ASSERT_ERROR(EN_CP_INSERTION || cp_insertions.size() == 0, + "config_fft(): Cyclic prefix removal not allowed when disabled"); + `ASSERT_ERROR(EN_FFT_ORDER || fft_order_sel == NATIVE_FFT_ORDER, + "The FFT output order can't be changed when EN_FFT_ORDER is false"); + + // CP must be a multiple of SPC + foreach (cp_removals[i]) begin + `ASSERT_ERROR(cp_removals[i] % NIPC == 0, + "config_fft(): CP removal length must be a multiple of NIPC"); + `ASSERT_ERROR(cp_insertions[i] < fft_size, + "config_fft(): CP insertion length must be less than FFT size"); + end + foreach (cp_insertions[i]) begin + `ASSERT_ERROR(cp_insertions[i] % NIPC == 0, + "config_fft(): CP insertion length must be a multiple of NIPC"); + `ASSERT_ERROR(cp_insertions[i] < fft_size, + "config_fft(): CP insertion length must be less than FFT size"); + end + + // Restrict testing to MAX_TEST_FFT_SIZE + `ASSERT_ERROR(MIN_FFT_SIZE <= fft_size && fft_size <= MAX_TEST_FFT_SIZE, + "config_fft(): fft_size is outside allowed range"); + if (cp_list_clear) begin if (VERBOSE) $display("config_fft(): Clearing Cyclic Prefix insertion FIFO"); write_reg(REG_CP_INS_LIST_CLR_ADDR, 1'b1, core); @@ -281,16 +324,6 @@ module rfnoc_block_fft_tb #( write_reg(REG_SCALING_ADDR, fft_scaling, core); if (VERBOSE) $display("config_fft(): Setting FFT Direction to %0d", fft_direction); write_reg(REG_DIRECTION_ADDR, fft_direction, core); - foreach (cp_insertions[i]) begin - `ASSERT_ERROR( - cp_insertions[i] < fft_size, - "Cyclic prefix insertion length must be less than FFT size" - ); - if (VERBOSE) $display("config_fft(): Setting Cyclic Prefix (insertion) %0d", - cp_insertions[i]); - write_reg(REG_CP_INS_LEN_ADDR, cp_insertions[i], core); - write_reg(REG_CP_INS_LIST_LOAD_ADDR, 1'b1, core); - end foreach (cp_removals[i]) begin `ASSERT_ERROR( cp_removals[i] < fft_size, @@ -301,6 +334,16 @@ module rfnoc_block_fft_tb #( write_reg(REG_CP_REM_LEN_ADDR, cp_removals[i], core); write_reg(REG_CP_REM_LIST_LOAD_ADDR, 1'b1, core); end + foreach (cp_insertions[i]) begin + `ASSERT_ERROR( + cp_insertions[i] < fft_size, + "Cyclic prefix insertion length must be less than FFT size" + ); + if (VERBOSE) $display("config_fft(): Setting Cyclic Prefix (insertion) %0d", + cp_insertions[i]); + write_reg(REG_CP_INS_LEN_ADDR, cp_insertions[i], core); + write_reg(REG_CP_INS_LIST_LOAD_ADDR, 1'b1, core); + end if (EN_FFT_BYPASS) begin if (VERBOSE) $display("config_fft(): Setting FFT Bypass to %0d", fft_bypass); write_reg(REG_BYPASS_ADDR, fft_bypass, core); @@ -336,7 +379,7 @@ module rfnoc_block_fft_tb #( check_val = write_val & 32'((1 << bit_width)-1); // Mask relevant bits `ASSERT_FATAL( read_val == check_val, - $sformatf("%s register incorrect readback! Expected: %0d, Actual %0d", + $sformatf("%s register incorrect readback! Expected: 0x%X, Actual 0x%X", reg_name, check_val, read_val) ); end @@ -360,7 +403,7 @@ module rfnoc_block_fft_tb #( read_val = read_val & 32'((1 << bit_width)-1); // Mask relevant bits `ASSERT_FATAL( value == read_val, - $sformatf("%s register incorrect readback! Expected: %0d, Actual %0d", + $sformatf("%s register incorrect readback! Expected: 0x%X, Actual 0x%X", reg_name, value, read_val) ); endtask @@ -414,20 +457,22 @@ module rfnoc_block_fft_tb #( // // fft_size : Size of the FFT to test (e.g., 4096 for 4k FFT) // num_ffts : Number of complete FFTs to test - // cp_insertions[] : Cyclic-prefix insertion list to use // cp_removals[] : Cyclic-prefix removal list to use + // cp_insertions[] : Cyclic-prefix insertion list to use // pkt_size : Packet size to use // timed : Test timed (1) or untimed (0) packets // core : Which FFT core to test + // fft_order : Which FFT output order to test // task automatic test_fft_sine( int fft_size, int num_ffts = 1, - int cp_insertions[] = {}, int cp_removals[] = {}, - int pkt_size = fft_size < SPP ? fft_size : SPP, + int cp_insertions[] = {}, + int pkt_size = DEFAULT_SPP, bit timed = 1, - int core = 0 + int core = 0, + int fft_order = EN_FFT_ORDER ? NATURAL : NATIVE_FFT_ORDER ); int first_port = core*NUM_CHAN_PER_CORE; int last_port = (core+1)*NUM_CHAN_PER_CORE - 1; @@ -436,20 +481,27 @@ module rfnoc_block_fft_tb #( $display("test_fft_sine():"); $display(" fft_size: %0d", fft_size); $display(" num_ffts: %0d", num_ffts); - $display(" cp_insertions: %p", cp_insertions); $display(" cp_removals: %p", cp_removals); + $display(" cp_insertions: %p", cp_insertions); $display(" pkt_size: %0d", pkt_size); $display(" timed: %0d", timed); $display(" core: %0d", core); + $display(" fft_order: %0d", fft_order); + end + + if (fft_size > MAX_TEST_FFT_SIZE) begin + `ASSERT_WARNING(0, + "test_fft_sine(): Leaving test because fft_size > MAX_TEST_FFT_SIZE"); + return; end // Having both insertion and removal might work, but that's not a use case // we're supporting. - assert(!(cp_insertions.size() && cp_removals.size())) else - `ASSERT_ERROR(0, "Cannot specify both CP insertion and CP removal"); + `ASSERT_ERROR(!(cp_insertions.size() && cp_removals.size()), + "Cannot specify both CP insertion and CP removal"); - config_fft(fft_size, cp_insertions, cp_removals, fft_scale_default(fft_size), - FFT_FORWARD, 1, .core(core)); + config_fft(fft_size, cp_removals, cp_insertions, fft_scale_default(fft_size), + FFT_FORWARD, 1, .fft_order_sel(fft_order), .core(core)); // Create a thread for the sender and one (or more) for the receiver(s). fork @@ -540,7 +592,7 @@ module rfnoc_block_fft_tb #( end // Check timestamp - if (timed && pkt_count == 0) begin + if (timed && (pkt_count == 0 || EN_TIME_ALL_PKTS)) begin `ASSERT_ERROR( recv_pkt_info.has_time, $sformatf({"test_fft_sine(): recv: PKT %0d: ", @@ -563,10 +615,14 @@ module rfnoc_block_fft_tb #( ); end - if (cp_removals.size() == 0 && cp_insertions.size() == 0 && - recv_data.size() % fft_size == 0) begin - `ASSERT_ERROR(recv_pkt_info.eov, + if (recv_data.size() % fft_size == 0) begin + `ASSERT_WARNING(recv_pkt_info.eov == 1, "test_fft_sine(): recv: EOV is not set on FFT multiple"); + if (VERBOSE && recv_pkt_info.eov) begin + $display("test_fft_sine(): recv: End of vector"); + end end else begin + `ASSERT_WARNING(recv_pkt_info.eov == 0, + "test_fft_sine(): recv: Unexpected EOV was set"); end if (recv_pkt_info.eob) break; @@ -592,11 +648,37 @@ module rfnoc_block_fft_tb #( fft_count, cp_insertion_len, fft_size, total_fft_size); end - // We should see a peak in the FFT region but may see one in - // the cyclic prefix as well if it is long enough. - peak_index = cp_insertion_len + fft_size/4; + // We expect the tone to come through at the frequency Fs/4. + // Here, we figure which bin that is depending on the FFT + // output order. + case (fft_order) + FFT_ORDER_NORMAL: begin + // -fs/2 to fs/2 (or fs/2 to fs then 0 to fs/2) + peak_index = 3*fft_size/4; + end + FFT_ORDER_REVERSE: begin + // fs/2 to -fs/2 (fs/2 to 0 then fs to fs/2) + peak_index = (fft_size-1) - 3*fft_size/4; + end + FFT_ORDER_NATURAL: begin + // 0 to fs + peak_index = fft_size/4; + end + FFT_ORDER_BIT_REVERSE: begin + // Same as natural, but the bits of the index are reversed + peak_index = bit_reverse(fft_size/4, $clog2(fft_size)); + end + endcase + + // Add the offset created by the cyclic prefix. If cyclic + // prefix insertion is enabled, then we may also see the + // frequency tone in the cyclic prefix as well. If + // cp_peak_index is negative, that indicate the cyclic prefix + // wasn't long enough to show it. + peak_index += cp_insertion_len; cp_peak_index = peak_index - fft_size; + // Grab the next FFT from the data for this burst recv_payload = recv_data[samp_count : samp_count+total_fft_size-1]; // Verify the sample values @@ -604,9 +686,7 @@ module rfnoc_block_fft_tb #( foreach (recv_payload[samp_i]) begin if (fft_samp_count == peak_index || fft_samp_count == cp_peak_index) begin bit signed [15:0] real_val, imag_val; - int magnitude; {real_val, imag_val} = recv_payload[samp_i]; - magnitude = $sqrt(real_val**2 + imag_val**2); `ASSERT_ERROR( imag_val == 0, $sformatf({"test_fft_sine(): recv: FFT %0d: ", @@ -711,6 +791,102 @@ module rfnoc_block_fft_tb #( endfunction + // Test a magnitude output selection of the FFT block + // + // mag_sel : Magnitude output selection + // num_ffts : Number of FFTs to send through + // fft_size : Size (in items) of FFT to use during the test + // pkt_size : Packet size (in items) to use during the test + // core : Which FFT core to test + // + task automatic test_magnitude( + int mag_sel, + int num_ffts = 8, + int fft_size = 128, + int pkt_size = 2*fft_size, + int core = 0 + ); + const int num_items = num_ffts*fft_size; + packet_info_t send_pkt_info = '0; + logic [31:0] data_in [$]; + logic [31:0] data_out [NUM_CHAN_PER_CORE][$]; + logic [31:0] data_ref [NUM_CHAN_PER_CORE][$]; + logic [31:0] data_exp [$]; + int ports [] = new [NUM_CHAN_PER_CORE]; + + // Create a list of ports involved in this test + foreach (ports[idx]) ports[idx] = core*NUM_CHAN_PER_CORE + idx; + + `ASSERT_FATAL(EN_MAGNITUDE || EN_MAGNITUDE_SQ, + "Magnitude/Magnitude-squared logic is not enabled in core."); + + `ASSERT_FATAL(!(mag_sel == MAG_SEL_MAG && !EN_MAGNITUDE), + "Magnitude logic is not enabled in core."); + + `ASSERT_FATAL(!(mag_sel == MAG_SEL_MAG_SQ && !EN_MAGNITUDE_SQ), + "Magnitude-squared logic is not enabled in core."); + + if (VERBOSE) begin + $display("test_magnitude():"); + $display(" mag_sel: %0d", mag_sel); + $display(" num_ffts: %0d", num_ffts); + $display(" fft_size: %0d", fft_size); + $display(" pkt_size: %0d", pkt_size); + $display(" core: %0d", core); + end + + // Set the packet size to be sent, in bytes + foreach (ports[idx]) begin + blk_ctrl.set_max_payload_length(ports[idx], pkt_size*4); + end + + // Configure for a normal inverse FFT + config_fft(.fft_size(fft_size), .fft_scaling(0), .fft_direction(FFT_INVERSE), + .magnitude_sel(MAG_SEL_NONE), .core(core)); + + // Generate random data to send + repeat (num_items) data_in.push_back($urandom()); + + // Do an inverse FFT get reference data + send_pkt_info.eob = '1; + foreach (ports[idx]) begin + blk_ctrl.send_packets_items(.port(ports[idx]), .items(data_in), .pkt_info(send_pkt_info)); + end + foreach (ports[idx]) begin + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(data_ref[idx]), .eob(1)); + end + + // Make sure the reference data is the same for all ports + foreach (ports[idx]) begin + `ASSERT_ERROR(data_ref[idx] == data_ref[0], + $sformatf("Reference data for port %0d doesn't match", idx)); + end + + // Configure for an inverse FFT with magnitude output + config_fft(.fft_size(fft_size), .fft_scaling(0), .fft_direction(FFT_INVERSE), + .magnitude_sel(mag_sel), .core(core)); + + // Send and receive the data again, this time with magnitude output + send_pkt_info.eob = '1; + foreach (ports[idx]) begin + blk_ctrl.send_packets_items(.port(ports[idx]), .items(data_in), .pkt_info(send_pkt_info)); + end + foreach (ports[idx]) begin + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(data_out[idx]), .eob(1)); + end + + // Figure out what we should expect from the reference data. + data_exp = calc_magnitude(data_ref[0], mag_sel); + + // Check that what we received matches what we expected, allowing for + // rounding error. + foreach (ports[idx]) begin + `ASSERT_ERROR(approx_equal(data_out[idx], data_exp), + "Magnitude output didn't match expected values"); + end + endtask : test_magnitude + + //--------------------------------------------------------------------------- // Tests //--------------------------------------------------------------------------- @@ -718,7 +894,7 @@ module rfnoc_block_fft_tb #( // Test a sequence of random configurations task automatic test_random( int num_iterations, - int max_fft_size = MAX_FFT_SIZE, + int max_fft_size = MAX_TEST_FFT_SIZE, int min_fft_size = MIN_FFT_SIZE ); test.start_test( @@ -728,40 +904,97 @@ module rfnoc_block_fft_tb #( " max_fft_size: %0d\n", " min_fft_size: %0d"}, num_iterations, max_fft_size, min_fft_size - ), num_iterations*max_fft_size*30ns + ), num_iterations*max_fft_size*50ns ); for (int test_iter = 0; test_iter < num_iterations; test_iter++) begin - bit timed = $urandom_range(0, 1); - int fft_size = 2**$urandom_range($clog2(min_fft_size), $clog2(max_fft_size)); - int num_ffts = $urandom_range(1, 3); - int cp_mode = $urandom_range(0, 2); // 0=None, 1=removal, 2=insertion - bit has_cp = $urandom_range(0, 1); - int cp_list_len = $urandom_range(0, - `MIN(num_ffts-1, `MIN(MAX_CP_LIST_LEN_REM, MAX_CP_LIST_LEN_INS))); + bit timed; + int fft_size; + int num_ffts; + fft_order_t temp; + int fft_order = NATIVE_FFT_ORDER; + int cp_mode; + bit has_cp; + int cp_list_len; int cp_lengths[] = new [cp_list_len]; int pkt_size; + int core; - // We require the packet size to be a multiple of MIN_FFT_SIZE and up to - // the current FFT size in length. - pkt_size = $urandom_range(MIN_FFT_SIZE, `MIN(fft_size, SPP)); - pkt_size = `DIV_CEIL(pkt_size, MIN_FFT_SIZE)*MIN_FFT_SIZE; - - foreach (cp_lengths[i]) begin - cp_lengths[i] = $urandom_range(0, fft_size-1); + // Randomize the parameters of this test iteration + core = $urandom_range(0, NUM_CORES-1); + timed = $urandom_range(0, 1); + num_ffts = $urandom_range(1, 3); + has_cp = $urandom_range(0, 1); + cp_list_len = $urandom_range(0, + `MIN(num_ffts-1, `MIN(MAX_CP_LIST_LEN_REM, MAX_CP_LIST_LEN_INS))); + if (EN_FFT_ORDER) begin + fft_order = $urandom_range(0, temp.num()-1); end - if (cp_mode == 2) begin - // Test with CP insertion - test_fft_sine(fft_size, num_ffts, .timed(timed), - .cp_insertions(cp_lengths), .pkt_size(pkt_size)); - end else if(cp_mode == 1) begin + // Randomly reset the core + if ($urandom_range(0, 1)) user_reset(core); + + // Choose a random FFT size, but avoid some apparent bugs in the Xilinx + // core that we know about. + forever begin + fft_size = 2**$urandom_range($clog2(min_fft_size), $clog2(max_fft_size)); + + // FIXME: With 32k FFT core configured for size 8 FFT, the first FFT + // output is garbled. + if (MAX_FFT_SIZE == 32*1024 && fft_size == 8) begin + `ASSERT_WARNING(0, "Skipping size 8 FFT with 32k FFT core"); + continue; + end + + // FIXME: The 64k FFT doesn't reliably support 64k FFTs. + if (MAX_FFT_SIZE == 64*1024 && fft_size == 64*1024) begin + `ASSERT_WARNING(0, "Skipping size 64k FFT with 64k FFT core"); + continue; + end + + break; + end + + // Choose a random cyclic prefix mode, but make sure it's enabled. + forever begin + cp_mode = $urandom_range(0, 2); // 0=None, 1=removal, 2=insertion + if (cp_mode == 0) break; + if (cp_mode == 1 && EN_CP_REMOVAL) break; + if (cp_mode == 2 && EN_CP_INSERTION) break; + end + + // Choose a random packet size that's a multiple of SPC + if (fft_size >= MAX_SPP) begin + pkt_size = $urandom_range(MIN_SPP, MAX_SPP) / NIPC * NIPC; + end else begin + if ($urandom_range(0,1)) begin + // Choose one that's smaller than the FFT size + pkt_size = $urandom_range(MIN_SPP, fft_size) / NIPC * NIPC; + end else begin + // Choose one that's larger than the FFT size + pkt_size = $urandom_range(fft_size, `MIN(2*fft_size, MAX_SPP)) / NIPC * NIPC; + end + end + + if (cp_mode > 0) foreach (cp_lengths[i]) begin + // CP must be a multiple of SPC + cp_lengths[i] = $urandom_range(0, fft_size-1) / NIPC * NIPC; + end + + if(cp_mode == 1) begin // Test with CP removal test_fft_sine(fft_size, num_ffts, .timed(timed), - .cp_removals(cp_lengths), .pkt_size(pkt_size)); + .cp_removals(cp_lengths), .pkt_size(pkt_size), + .fft_order(fft_order)); + end else if (cp_mode == 2) begin + // Test with CP insertion + test_fft_sine(fft_size, num_ffts, .timed(timed), + .cp_insertions(cp_lengths), .pkt_size(pkt_size), + .fft_order(fft_order)); end else begin // Test without cyclic prefix - test_fft_sine(fft_size, num_ffts, .timed(timed), .pkt_size(pkt_size)); + test_fft_sine(fft_size, num_ffts, .timed(timed), .pkt_size(pkt_size), + .fft_order(fft_order)); end end @@ -769,14 +1002,22 @@ module rfnoc_block_fft_tb #( endtask - task automatic test_fft_config(int fft_size, int pkt_size = SPP); + // Test some OFDM configurations + task automatic test_ofdm_config(int fft_size, int pkt_size = DEFAULT_SPP); int cp_lengths[] = new [14]; test.start_test( - $sformatf("Test fft configuration (fft_size=%0d, pkt_size=%0d)", fft_size, pkt_size), + $sformatf("Test FFT configuration (fft_size=%0d, pkt_size=%0d)", fft_size, pkt_size), 2ms ); + if (fft_size > MAX_TEST_FFT_SIZE) begin + `ASSERT_WARNING(0, + "test_fft_sine(): Leaving test because fft_size > MAX_TEST_FFT_SIZE"); + test.end_test(); + return; + end + if (fft_size == 4096) begin // Assume 122.88 MS/s, 30 kHz subcarrier spacing (mu=1), 28 fft symbols // per 1 ms subframe. @@ -802,20 +1043,19 @@ module rfnoc_block_fft_tb #( int fft_size, int num_ffts = 1, int cp[] = {}, - int pkt_size = fft_size < SPP ? fft_size : SPP, - int port = 0 + int pkt_size = DEFAULT_SPP, + int core = 0 ); logic [31:0] data_in [$]; - logic [31:0] signal [$]; - logic [31:0] data_out [$]; + logic [31:0] signal [NUM_CHAN_PER_CORE][$]; + logic [31:0] data_out [NUM_CHAN_PER_CORE][$]; int fft_scaling = fft_scale_default($clog2(fft_size)); // Get 1/N scaling int ifft_scaling = 0; packet_info_t send_pkt_info = '0; + int ports [] = new [NUM_CHAN_PER_CORE]; - if (NUM_CHAN_PER_CORE > 1) begin - `ASSERT_ERROR(0, "test_loopback() only works with one channel per core."); - return; - end + // Create a list of ports involved in this test + foreach (ports[idx]) ports[idx] = core*NUM_CHAN_PER_CORE + idx; test.start_test( $sformatf({ @@ -824,79 +1064,103 @@ module rfnoc_block_fft_tb #( " num_ffts: %0d\n", " cp: %p\n", " pkt_size: %0d\n", - " port: %0d"}, - fft_size, num_ffts, cp, pkt_size, port + " core: %0d"}, + fft_size, num_ffts, cp, pkt_size, core ), 2ms ); + if (fft_size > MAX_TEST_FFT_SIZE) begin + `ASSERT_WARNING(0, + "test_loopback(): Leaving test because fft_size > MAX_TEST_FFT_SIZE"); + test.end_test(); + return; + end + + `ASSERT_ERROR(EN_CP_REMOVAL && EN_CP_INSERTION, + "test_loopback(): Cyclic prefix insertion and removal must be enabled"); + // For this test, we send one FFT per packet, unless the FFT is larger than // the packet size, which is supported. assert(fft_size % pkt_size == 0) else `ASSERT_ERROR(0, "fft_size must be a multiple of pkt_size"); - // Set the SPP - blk_ctrl.set_max_payload_length(port, pkt_size*4); + // Set the packet size to be sent, in bytes + foreach (ports[idx]) blk_ctrl.set_max_payload_length(ports[idx], pkt_size*4); // Do IFFT to convert the frequency domain signal to a time domain signal // with CP insertion. - config_fft(fft_size, cp, {}, ifft_scaling, FFT_INVERSE, .core(port)); + config_fft(fft_size, {}, cp, ifft_scaling, FFT_INVERSE, .core(core)); repeat(num_ffts) data_in = {data_in, gen_tone(fft_size, 0.25)}; send_pkt_info.eob = '1; - blk_ctrl.send_packets_items(.port(port), .items(data_in), .pkt_info(send_pkt_info)); - blk_ctrl.recv_packets_items(.port(port), .items(signal), .eob(1)); + foreach(ports[idx]) + blk_ctrl.send_packets_items(.port(ports[idx]), .items(data_in), .pkt_info(send_pkt_info)); + foreach(ports[idx]) + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(signal[idx]), .eob(1)); // Now do FFT with CP removal to get back the original symbol - config_fft(fft_size, {}, cp, fft_scaling, FFT_FORWARD, .core(port)); - blk_ctrl.send_packets_items(.port(port), .items(signal), .pkt_info(send_pkt_info)); - blk_ctrl.recv_packets_items(.port(port), .items(data_out), .eob(1)); + config_fft(fft_size, cp, {}, fft_scaling, FFT_FORWARD, .core(core)); + foreach(ports[idx]) + blk_ctrl.send_packets_items(.port(ports[idx]), .items(signal[idx]), .pkt_info(send_pkt_info)); + foreach(ports[idx]) + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(data_out[idx]), .eob(1)); - `ASSERT_ERROR(data_in == data_out, - "Samples sent does not match samples received"); + foreach (ports[idx]) begin + `ASSERT_ERROR(data_in == data_out[idx], $sformatf( + "Samples sent does not match samples received on port %0d", idx)); + end test.end_test(); endtask : test_loopback - // Test max and min FFT size without CP + // Test max and min FFT size without cyclic prefix. Max/min CP insertion is + // in another test. task automatic test_max_min_fft(); - test.start_test("Test min/max values", MAX_FFT_SIZE*30ns); - test_fft_sine(MAX_FFT_SIZE, 2); + test.start_test("Test min/max FFT length", + 1ms + (MIN_FFT_SIZE+MAX_TEST_FFT_SIZE)*50ns); + test_fft_sine(MAX_TEST_FFT_SIZE, 2); test_fft_sine(MIN_FFT_SIZE, 2); test.end_test(); endtask - // Test max CP list length (2**MAX_CP_LIST_LEN_LOG2 - 1). - task automatic test_max_cp_list_len(int fft_size = MIN_FFT_SIZE); + // Test max CP list length. Use a small FFT size by default to shorten the + // test time. + task automatic test_max_cp_list_len(int fft_size = 2**(MIN_FFT_SIZE_LOG2+1)); + int cp_rem_lengths [] = new [MAX_CP_LIST_LEN_INS]; + int cp_ins_lengths [] = new [MAX_CP_LIST_LEN_REM]; + int num_ffts; + test.start_test( - $sformatf("Test maximum CP list size (fft_size = %0d)", fft_size), + $sformatf("Test maximum CP list length (fft_size = %0d)", fft_size), 5ms ); - for (int insert = 0; insert < 2; insert++) begin - int cp_list_len = insert ? MAX_CP_LIST_LEN_INS : MAX_CP_LIST_LEN_REM; - int cp_lengths [] = new [cp_list_len]; - - foreach (cp_lengths[i]) begin - // Keep the insertion length small to limit test length. We'll test the - // max length in another test. - cp_lengths[i] = $urandom_range(0, 1); + if (EN_CP_REMOVAL) begin + foreach (cp_rem_lengths[i]) begin + // CP must be a multiple of SPC + cp_rem_lengths[i] = $urandom_range(NIPC, fft_size-1) / NIPC * NIPC; end - - // Test one FFT more than the CP list length to make sure it repeats - if (insert) begin - test_fft_sine( - .fft_size (fft_size), - .num_ffts (cp_list_len+1), - .cp_insertions(cp_lengths) - ); - end else begin - test_fft_sine( - .fft_size (fft_size), - .num_ffts (cp_list_len+1), - .cp_removals (cp_lengths) - ); + // Test more FFTs than the CP list length to make sure the list repeats + // as expected. + test_fft_sine( + .fft_size (fft_size), + .num_ffts (2*MAX_CP_LIST_LEN_INS), + .cp_removals (cp_rem_lengths) + ); + end + if (EN_CP_INSERTION) begin + foreach (cp_ins_lengths[i]) begin + // CP must be a multiple of SPC + cp_ins_lengths[i] = $urandom_range(NIPC, fft_size-1) / NIPC * NIPC; end + // Test more FFTs than the CP list length to make sure the list repeats + // as expected. + test_fft_sine( + .fft_size (fft_size), + .num_ffts (2*MAX_CP_LIST_LEN_REM), + .cp_insertions(cp_ins_lengths) + ); end test.end_test(); @@ -905,18 +1169,136 @@ module rfnoc_block_fft_tb #( // Test max FFT size with max and min CP insertion and removal lengths task automatic test_max_min_cp_len(); - int cp_lengths [] = {MAX_CP_LEN, 1, 0}; - test.start_test("Test min/max CP lengths", 5ms); + int cp_lengths []; + + // CP lengths must be multiple of SPC + cp_lengths = { + MAX_TEST_CP_LEN-NIPC, // Max + NIPC, // Min plus 1 + 0 // Min + }; + + if (EN_CP_REMOVAL) begin + test.start_test("Test max/min CP removal lengths", 5ms); + test_fft_sine( + .fft_size (MAX_TEST_FFT_SIZE), + .num_ffts (cp_lengths.size()), + .cp_removals(cp_lengths) + ); + test.end_test(); + end + + if (EN_CP_INSERTION) begin + test.start_test("Test max/min CP insertion lengths", 5ms); + test_fft_sine( + .fft_size (MAX_TEST_FFT_SIZE), + .num_ffts (cp_lengths.size()), + .cp_insertions(cp_lengths) + ); + test.end_test(); + end + + endtask + + + // Test maximum packet size, minimum packet size, and worst-case internal + // buffering requirements. In the worst case, we need to be able to buffer + // all the symbols that make up a packet. This is maximized when the packet + // size is at its maximum and the FFT size is at its minimum, with maximum CP + // insertion/removal. + task automatic test_max_min_packet(int core = 0); + int fft_size; + int pkt_size; + int num_pkts; + int num_ffts; + int cp_size; + + test.start_test("Test buffering", 2ms); + + fft_size = MIN_FFT_SIZE; // Use worst-case FFT size (min) + pkt_size = MAX_SPP; // Use worst-case packet size (max) + num_pkts = 2; // Test multiple packets + num_ffts = `DIV_CEIL(num_pkts * pkt_size, fft_size); + // CP must be a multiple of SPC + cp_size = (fft_size-1) / NIPC * NIPC; // Use worst-case CP + + // Test with no CP test_fft_sine( - .fft_size (MAX_FFT_SIZE), - .num_ffts (cp_lengths.size()), - .cp_insertions(cp_lengths) - ); - test_fft_sine( - .fft_size (MAX_FFT_SIZE), - .num_ffts (cp_lengths.size()), - .cp_removals (cp_lengths) + .fft_size (fft_size), + .num_ffts (num_ffts), + .cp_removals (), + .cp_insertions(), + .pkt_size (pkt_size), + .timed (1), + .core (core) ); + + // Test with CP insertion + if (EN_CP_REMOVAL) begin + test_fft_sine( + .fft_size (fft_size), + .num_ffts (num_ffts), + .cp_removals ({cp_size}), + .cp_insertions(), + .pkt_size (pkt_size), + .timed (1), + .core (core) + ); + end + + // Test with CP removal + if (EN_CP_INSERTION) begin + test_fft_sine( + .fft_size (fft_size), + .num_ffts (num_ffts), + .cp_removals (), + .cp_insertions({cp_size}), + .pkt_size (pkt_size), + .timed (1), + .core (core) + ); + end + + // Test with bypass enabled (and CP insertion, if available) + if (EN_FFT_BYPASS) begin + logic [31:0] data_in [$]; + logic [31:0] data_out [NUM_CHAN_PER_CORE][$]; + packet_info_t send_pkt_info = '0; + int ports [] = new [NUM_CHAN_PER_CORE]; + + // Create a list of ports involved in this test + foreach (ports[idx]) ports[idx] = core*NUM_CHAN_PER_CORE + idx; + + config_fft(.fft_size(fft_size), .fft_bypass(1), + .fft_order_sel(BIT_REVERSE), .core(core)); + + // Generate the data to send + repeat(num_ffts * fft_size) data_in.push_back($urandom()); + + // Set the max packet size to be sent, in bytes + foreach (ports[idx]) blk_ctrl.set_max_payload_length(ports[idx], pkt_size*4); + + // Send and receive data + send_pkt_info.eob = '1; + foreach (ports[idx]) begin + blk_ctrl.send_packets_items(.port(ports[idx]), .items(data_in), .pkt_info(send_pkt_info)); + end + foreach (ports[idx]) begin + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(data_out[idx]), .eob(1)); + end + + foreach (ports[idx]) begin + `ASSERT_ERROR(data_out[idx] == data_in, + "Data output didn't match expected values"); + end + end + + // Now test minimum packet size and maximum FFT size + pkt_size = MIN_SPP; + fft_size = MAX_TEST_FFT_SIZE; + num_ffts = 1; + test_fft_sine(fft_size, num_ffts, {}, {}, pkt_size, 1, .core(core)); + test.end_test(); endtask @@ -924,13 +1306,16 @@ module rfnoc_block_fft_tb #( // Test all the registers to ensure that they read/write as expected. Except // the write-only registers which will be exercised during functional tests. task automatic test_registers(); - localparam bit [31:0] exp_compat = {16'd3, 16'd0}; + localparam bit [31:0] exp_compat = {16'd3, 16'd1}; localparam bit [31:0] exp_capabilities = (8'(MAX_CP_LIST_LEN_INS_LOG2) << 24) | (8'(MAX_CP_LIST_LEN_REM_LOG2) << 16) | (8'( MAX_CP_LEN_LOG2) << 8) | (8'( MAX_FFT_SIZE_LOG2) << 0); localparam bit [31:0] exp_capabilities2 = + ($clog2(NIPC) << 8) | + (EN_CP_INSERTION << 5) | + (EN_CP_REMOVAL << 4) | (EN_MAGNITUDE_SQ << 3) | (EN_MAGNITUDE << 2) | (EN_FFT_ORDER << 1) | @@ -945,6 +1330,10 @@ module rfnoc_block_fft_tb #( // Test registers in each core for (int core = 0; core < NUM_CORES; core++) begin if (VERBOSE) $display("test_registers(): Testing core %0d", core); + + // Exercise the user reset, which is write only. + user_reset(core); + // Test read/write registers test_rw_reg("FFT Size", REG_LENGTH_LOG2_ADDR, dut.gen_fft_cores[0].fft_core_i.REG_LENGTH_LOG2_WIDTH, core); @@ -1000,33 +1389,102 @@ module rfnoc_block_fft_tb #( // expected. task automatic test_basic(); test.start_test("Test basic", 2ms); - for (int core = 0; core < NUM_CORES; core++) begin + for (int core = 0; core < NUM_CORES; core++) begin : core_loop + // Test small FFT test_fft_sine( - .fft_size(32), .num_ffts(2), - .cp_insertions({}), + .fft_size(32), + .num_ffts(2*NIPC), .cp_removals({}), + .cp_insertions({}), .pkt_size(32), .timed(1), .core(core) ); - end + // Test maximum FFT size to ensure the correct core is instantiated, but + // skip it if we're going to do the full test later since this test can + // take a long time. + if (!FULL_TEST) begin + test_fft_sine( + .fft_size(MAX_FFT_SIZE), + .num_ffts(1), + .cp_removals({}), + .cp_insertions({}), + .pkt_size(DEFAULT_SPP), + .timed(1), + .core(core) + ); + end + // Test small FFT with CP removal + if (EN_CP_REMOVAL) begin + test_fft_sine( + .fft_size(32), + .num_ffts(2*NIPC), + .cp_removals({NIPC}), + .cp_insertions({}), + .pkt_size(32), + .timed(1), + .core(core) + ); + end + // Test small FFT with CP insertion + if (EN_CP_INSERTION) begin + test_fft_sine( + .fft_size(32), + .num_ffts(2*NIPC), + .cp_removals({}), + .cp_insertions({NIPC}), + .pkt_size(32), + .timed(1), + .core(core) + ); + end + // Test magnitude path + if (EN_MAGNITUDE) begin + test_magnitude(MAG_SEL_MAG, 1, 32, 32, core); + end + // Test magnituded squared path + if (EN_MAGNITUDE_SQ) begin + test_magnitude(MAG_SEL_MAG_SQ, 1, 32, 32, core); + end + end : core_loop test.end_test(); endtask // Test different cyclic-prefix lists with insertion and removal task automatic test_cyclic_prefix(); - automatic int cp_lengths_single[] = '{ 12 }; - automatic int cp_lengths_multi[] = '{ 320, 288, 111, 0, 320 }; + // CP must be a multiple of SPC + automatic int cp_lengths_single[] = NIPC <= 4 ? '{ 12 } : '{ 16 }; + automatic int cp_lengths_multi[] = NIPC == 1 ? '{ 320, 288, 111, 0, 320 } : + '{ 320, 288, 112, 0, 320 }; automatic int cp_lengths_golden[] = '{ 320, 288, 288, 288, 288, 288, 288, 288, 288, 288, 288, 288, 288, 288 }; - test.start_test("Test CP insertion and removal", 2ms); - test_fft_sine(128, cp_lengths_single.size(), .cp_insertions(cp_lengths_single)); - test_fft_sine(512, cp_lengths_multi.size(), .cp_insertions(cp_lengths_multi)); - test_fft_sine(128, cp_lengths_single.size(), .cp_removals(cp_lengths_single)); - test_fft_sine(512, cp_lengths_multi.size(), .cp_removals(cp_lengths_multi)); - test_fft_sine(512, cp_lengths_golden.size(), .cp_removals(cp_lengths_golden)); + if (EN_CP_REMOVAL) begin + test.start_test("Test CP removal", 2ms); + test_fft_sine(128, cp_lengths_single.size(), .cp_removals(cp_lengths_single)); + test_fft_sine(512, cp_lengths_multi.size(), .cp_removals(cp_lengths_multi)); + test_fft_sine(512, cp_lengths_golden.size(), .cp_removals(cp_lengths_golden)); + test.end_test(); + end + if (EN_CP_INSERTION) begin + test.start_test("Test CP insertion", 2ms); + test_fft_sine(128, cp_lengths_single.size(), .cp_insertions(cp_lengths_single)); + test_fft_sine(512, cp_lengths_multi.size(), .cp_insertions(cp_lengths_multi)); + test_fft_sine(512, cp_lengths_golden.size(), .cp_insertions(cp_lengths_golden)); + test.end_test(); + end + endtask + + + // Test the supported FFT data output orders to confirm they controls and + // data path are connected correctly. + task automatic test_fft_order(); + test.start_test("Test FFT output order", 2ms); + test_fft_sine(32, 2, .pkt_size(32), .fft_order(FFT_ORDER_BIT_REVERSE)); + test_fft_sine(32, 2, .pkt_size(32), .fft_order(FFT_ORDER_NATURAL)); + test_fft_sine(32, 2, .pkt_size(32), .fft_order(FFT_ORDER_REVERSE)); + test_fft_sine(32, 2, .pkt_size(32), .fft_order(FFT_ORDER_NORMAL)); test.end_test(); endtask @@ -1069,22 +1527,31 @@ module rfnoc_block_fft_tb #( // Measure the throughput to ensure it's adequate for USRP clock // configurations. For example, with 266.666 MHz CE clock and 250 MHz radio // clock, we need at least 93.75% efficiency. + // + // fft_size : Size of the FFT to test + // num_ffts : Number of FFTs to send during the test + // pkt_size : Packet size to use, in items + // core : Which core to test + // task automatic test_throughput( - int fft_size = 512, - int num_ffts = 8, - int pkt_size = fft_size < SPP ? fft_size : SPP, - int port = 0 + int fft_size = 256, + int num_ffts = 4, + int pkt_size = 512, + int core = 0 ); localparam real CE_CLK_FREQ_MHZ = 1000.0/CE_CLK_PER; - localparam real MIN_RATE = 0.98 * CE_CLK_FREQ_MHZ; + localparam real CHDR_CLK_FREQ_MHZ = 1000.0/CHDR_CLK_PER; + localparam real MIN_RATE_CE = 0.95 * CE_CLK_FREQ_MHZ * NIPC; + localparam real MIN_RATE_CHDR = 0.95 * CHDR_CLK_FREQ_MHZ * CHDR_W/ITEM_W; logic [31:0] data_in [$]; logic [31:0] signal [$]; packet_info_t send_pkt_info = '0; + int ports [] = new [NUM_CHAN_PER_CORE]; + int master_stall_prob [] = new [NUM_CHAN_PER_CORE]; + int slave_stall_prob [] = new [NUM_CHAN_PER_CORE]; - if (NUM_CHAN_PER_CORE > 1) begin - `ASSERT_ERROR(0, "test_throughput() only works with one channel per core. Test skipped."); - return; - end + // Create a list of ports involved in this test + foreach (ports[idx]) ports[idx] = core*NUM_CHAN_PER_CORE + idx; test.start_test( $sformatf({ @@ -1092,22 +1559,26 @@ module rfnoc_block_fft_tb #( " fft_size: %0d\n", " num_ffts: %0d\n", " pkt_size: %0d\n", - " port: %0d"}, - fft_size, num_ffts, pkt_size, port + " core: %0d"}, + fft_size, num_ffts, pkt_size, core ), 2ms ); - // For this test, we send one FFT per packet, unless the FFT is larger than - // the packet size, which is supported. - assert(fft_size % pkt_size == 0) else - `ASSERT_ERROR(0, "fft_size must be a multiple of pkt_size"); + `ASSERT_WARNING(MIN_RATE_CHDR >= MIN_RATE_CE, + "test_throughput: The CHDR rate is limiting the DSP rate."); - // Turn off back pressure in the BFM - blk_ctrl.set_master_stall_prob(port, 0); - blk_ctrl.set_slave_stall_prob(port, 0); + foreach (ports[idx]) begin + // Turn off back pressure in the BFM + master_stall_prob[idx] = blk_ctrl.get_master_stall_prob(ports[idx]); + slave_stall_prob[idx] = blk_ctrl.get_slave_stall_prob(ports[idx]); + blk_ctrl.set_master_stall_prob(ports[idx], 0); + blk_ctrl.set_slave_stall_prob(ports[idx], 0); - blk_ctrl.set_max_payload_length(port, pkt_size*4); - config_fft(.fft_size(fft_size), .core(port)); + // Set the packet size to be sent, in bytes + blk_ctrl.set_max_payload_length(ports[idx], pkt_size*ITEM_W/4); + end + + config_fft(.fft_size(fft_size), .core(core)); repeat(num_ffts) data_in = {data_in, gen_tone(fft_size, 0.25)}; send_pkt_info.eob = '1; fork @@ -1116,90 +1587,169 @@ module rfnoc_block_fft_tb #( real rate; // Send the data twice, once to fill the pipes, the second to measure. repeat (2) begin - blk_ctrl.send_packets_items(.port(port), .items(data_in), .pkt_info(send_pkt_info)); + foreach (ports[idx]) begin + blk_ctrl.send_packets_items(.port(ports[idx]), .items(data_in), + .pkt_info(send_pkt_info)); + end start_time = $realtime; - blk_ctrl.wait_complete(port); + foreach (ports[idx]) begin + blk_ctrl.wait_complete(ports[idx]); + end end elapsed = $realtime - start_time; rate = 1000.0 * num_ffts * fft_size / elapsed; // In MS/s - if (VERBOSE) $display("Throughput is %f MS/s", rate); - `ASSERT_ERROR( - rate >= MIN_RATE, - $sformatf("Calculated throughput (%f MS/s) is below threshold (%f MS/s)", - rate, MIN_RATE) - ); + $display("Measured throughput is %0.1f MS/s (%0.1f%% efficiency)", + rate, 100*rate/(CE_CLK_FREQ_MHZ * NIPC)); + if (MIN_RATE_CE <= MIN_RATE_CHDR) begin + // We're not limited by the CHDR rate, so we should hit the DSP rate + `ASSERT_ERROR( + rate >= MIN_RATE_CE, + $sformatf("Calculated throughput (%f MS/s) is below threshold (%f MS/s)", + rate, MIN_RATE_CE) + ); + end else begin + // We're limited by the CHDR rate, so check that instead + `ASSERT_ERROR( + rate >= MIN_RATE_CHDR, + $sformatf("Calculated throughput (%f MS/s) is below threshold (%f MS/s)", + rate, MIN_RATE_CHDR) + ); + end end begin : receiver - repeat (2) blk_ctrl.recv_packets_items(.port(port), .items(signal), .eob(1)); + repeat (2) begin + foreach (ports[idx]) begin + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(signal), .eob(1)); + end + end end join // Set back pressure back to default - blk_ctrl.set_master_stall_prob(port, STALL_PROB); - blk_ctrl.set_slave_stall_prob(port, STALL_PROB); - - test.end_test(); - endtask - - - // Test the magnitude output option - // - // mag_sel : Magnitude output selection - // port : RFNoC port to test - // - task automatic test_magnitude(int mag_sel, int port = 0); - packet_info_t send_pkt_info = '0; - logic [31:0] data_in [$]; - logic [31:0] data_out [$]; - logic [31:0] data_exp [$]; - localparam FFT_SIZE = 128; - localparam NUM_ITEMS = FFT_SIZE*10; - localparam PKT_SIZE = FFT_SIZE; - - test.start_test($sformatf("Test Magnitude (mag_sel = %0d)", mag_sel), 2ms); - - if (NUM_CHAN_PER_CORE > 1) begin - `ASSERT_ERROR(0, "test_magnitude() only works with one channel per core."); - return; + foreach (ports[idx]) begin + blk_ctrl.set_master_stall_prob(ports[idx], master_stall_prob[idx]); + blk_ctrl.set_slave_stall_prob(ports[idx], slave_stall_prob[idx]); end - `ASSERT_FATAL(EN_MAGNITUDE || EN_MAGNITUDE_SQ, - "Magnitude/Magnitude-squared logic is not enabled in core."); - `ASSERT_FATAL(EN_FFT_BYPASS, - "Magnitude/Magnitude-squared test requires FFT bypass."); - - `ASSERT_FATAL(!(mag_sel == MAG_SEL_MAG && !EN_MAGNITUDE), - "Magnitude logic is not enabled in core."); - - `ASSERT_FATAL(!(mag_sel == MAG_SEL_MAG_SQ && !EN_MAGNITUDE_SQ), - "Magnitude-squared logic is not enabled in core."); - - // Set the packet size in bytes - blk_ctrl.set_max_payload_length(port, PKT_SIZE*4); - - // Configure the FFT block to something sane, but bypass the FFT processing - // stage so we can measure the effect of the post-processing directly. We - // also set the FFT order to match the default input order so that samples - // are not reordered during this test. - config_fft(.fft_size(FFT_SIZE), .fft_bypass(1), .fft_order_sel(BIT_REVERSE), - .magnitude_sel(mag_sel), .core(port)); - - // Generate the data to send - repeat(NUM_ITEMS) data_in.push_back($urandom()); - - // Send and receive data - send_pkt_info.eob = '1; - blk_ctrl.send_packets_items(.port(port), .items(data_in), .pkt_info(send_pkt_info)); - blk_ctrl.recv_packets_items(.port(port), .items(data_out), .eob(1)); - - data_exp = calc_magnitude(data_in, mag_sel); - `ASSERT_ERROR(approx_equal(data_out, data_exp), - "Magnitude output didn't match expected values"); - test.end_test(); endtask + // Test all the supported magnitude output features, as well as the overflow + // register. + task automatic test_magnitude_options( + int num_ffts = 8, + int fft_size = 128, + int pkt_size = 2*fft_size, + int core = 0 + ); + logic [31:0] val; + + // Clear the overflow register, in case it was set previously + read_reg(REG_OVERFLOW_ADDR, val, core); + // Make sure it's cleared + if (val) begin + read_reg(REG_OVERFLOW_ADDR, val, core); + `ASSERT_ERROR(val == 0, "Overflow register did not clear."); + end + + if (EN_MAGNITUDE) begin + const int mag_sel = MAG_SEL_MAG; + test.start_test($sformatf({ + "Test magnitude\n", + " mag_sel: %0d\n", + " num_ffts: %0d\n", + " fft_size: %0d\n", + " pkt_size: %0d\n", + " core: %0d"}, + MAG_SEL_MAG, num_ffts, fft_size, pkt_size, core), 2ms + ); + test_magnitude(MAG_SEL_MAG, num_ffts, fft_size, pkt_size, core); + // We expect some overflow, so make sure that bit was set + read_reg(REG_OVERFLOW_ADDR, val, core); + `ASSERT_ERROR(val != 0, "Overflow register did not set."); + read_reg(REG_OVERFLOW_ADDR, val, core); + `ASSERT_ERROR(val == 0, "Overflow register did not self-clear."); + test.end_test(); + end + if (EN_MAGNITUDE_SQ) begin + const int mag_sel = MAG_SEL_MAG_SQ; + test.start_test($sformatf({ + "Test magnitude\n", + " mag_sel: %0d\n", + " num_ffts: %0d\n", + " fft_size: %0d\n", + " pkt_size: %0d\n", + " core: %0d"}, + MAG_SEL_MAG_SQ, num_ffts, fft_size, pkt_size, core), 2ms + ); + test_magnitude(MAG_SEL_MAG_SQ, num_ffts, fft_size, pkt_size, core); + // We expect some overflow, so make sure that bit was set + read_reg(REG_OVERFLOW_ADDR, val, core); + `ASSERT_ERROR(val != 0, "Overflow register did not set."); + read_reg(REG_OVERFLOW_ADDR, val, core); + `ASSERT_ERROR(val == 0, "Overflow register did not self-clear."); + test.end_test(); + end + endtask : test_magnitude_options + + + task automatic test_bypass( + int num_ffts = 8, + int fft_size = 128, + int pkt_size = 2*fft_size, + int core = 0 + ); + const int num_items = num_ffts*fft_size; + packet_info_t send_pkt_info = '0; + logic [31:0] data_in [$]; + logic [31:0] data_out [NUM_CHAN_PER_CORE][$]; + int ports [] = new [NUM_CHAN_PER_CORE]; + + // Create a list of ports involved in this test + foreach (ports[idx]) ports[idx] = core*NUM_CHAN_PER_CORE + idx; + + `ASSERT_FATAL(EN_FFT_BYPASS, "Bypass logic is not enabled in core."); + + test.start_test($sformatf({ + "Test bypass\n", + " num_ffts: %0d\n", + " fft_size: %0d\n", + " pkt_size: %0d\n", + " core: %0d"}, + num_ffts, fft_size, pkt_size, core), 2ms + ); + + // Set the packet size to be sent, in bytes + foreach (ports[idx]) begin + blk_ctrl.set_max_payload_length(ports[idx], pkt_size*4); + end + + // Configure FFT with bypass. This FFT size shouldn't matter in bypass mode. + config_fft(.fft_size(MAX_FFT_SIZE), .fft_bypass(1), .core(core)); + + // Generate random data to send + repeat (num_items) data_in.push_back($urandom()); + + // Send and receive the data + send_pkt_info.eob = '1; + foreach (ports[idx]) begin + blk_ctrl.send_packets_items(.port(ports[idx]), .items(data_in), .pkt_info(send_pkt_info)); + end + foreach (ports[idx]) begin + blk_ctrl.recv_packets_items(.port(ports[idx]), .items(data_out[idx]), .eob(1)); + end + + // Check that what we received exactly matches what we sent + foreach (ports[idx]) begin + `ASSERT_ERROR(data_out[idx] == data_in, + "Bypass output didn't match expected values"); + end + + test.end_test(); + endtask : test_bypass + + //--------------------------------------------------------------------------- // Main Test Process //--------------------------------------------------------------------------- @@ -1211,20 +1761,27 @@ module rfnoc_block_fft_tb #( tb_name = $sformatf({ "rfnoc_block_fft_tb\n", "\tFULL_TEST = %0d\n", + "\tCHDR_W = %0d\n", + "\tNIPC = %0d\n", "\tNUM_PORTS = %0d\n", "\tNUM_CORES = %0d\n", "\tMAX_FFT_SIZE_LOG2 = %0d\n", + "\tMAX_TEST_FFT_SIZE_LOG2 = %0d\n", + "\tEN_CP_REMOVAL = %0d\n", + "\tEN_CP_INSERTION = %0d\n", "\tMAX_CP_LIST_LEN_INS_LOG2 = %0d\n", "\tMAX_CP_LIST_LEN_REM_LOG2 = %0d\n", - "\tEN_MAGNITUDE_SQ = %0d\n", "\tEN_MAGNITUDE = %0d\n", + "\tEN_MAGNITUDE_SQ = %0d\n", "\tEN_FFT_ORDER = %0d\n", "\tEN_FFT_BYPASS = %0d\n", "\tUSE_APPROX_MAG = %0d\n", "\tVERBOSE = %0d"}, - FULL_TEST, NUM_PORTS, NUM_CORES, MAX_FFT_SIZE_LOG2, - MAX_CP_LIST_LEN_INS_LOG2, MAX_CP_LIST_LEN_REM_LOG2, EN_MAGNITUDE_SQ, - EN_MAGNITUDE, EN_FFT_ORDER, EN_FFT_BYPASS, USE_APPROX_MAG, VERBOSE + + FULL_TEST, CHDR_W, NIPC, NUM_PORTS, NUM_CORES, MAX_FFT_SIZE_LOG2, + MAX_TEST_FFT_SIZE_LOG2, EN_CP_REMOVAL, EN_CP_INSERTION, MAX_CP_LIST_LEN_INS_LOG2, + MAX_CP_LIST_LEN_REM_LOG2, EN_MAGNITUDE, EN_MAGNITUDE_SQ, EN_FFT_ORDER, + EN_FFT_BYPASS, USE_APPROX_MAG, VERBOSE ); // Initialize the test exec object for this testbench @@ -1254,7 +1811,7 @@ module rfnoc_block_fft_tb #( `ASSERT_ERROR(blk_ctrl.get_noc_id() == 32'hFF700002, "Incorrect NOC_ID Value"); `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS_I, "Incorrect NUM_DATA_I Value"); `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS_O, "Incorrect NUM_DATA_O Value"); - `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); + `ASSERT_ERROR(blk_ctrl.get_mtu() == CHDR_MTU, "Incorrect MTU Value"); test.end_test(); //-------------------------------- @@ -1265,26 +1822,30 @@ module rfnoc_block_fft_tb #( test_basic(); if (FULL_TEST) begin - if (NUM_CHAN_PER_CORE == 1) begin - test_loopback(128, 2, '{80, 72, 111, 0, 80}); + if (EN_CP_INSERTION && EN_CP_REMOVAL) begin + test_loopback(128, 2, '{80, 72, 112, 0, 80}); test_loopback(256, 3, '{160, 144, 56, 0, 160}); + if (MAX_TEST_FFT_SIZE >= 4096) test_ofdm_config(4096, 1024); + if (MAX_TEST_FFT_SIZE >= 8192) test_ofdm_config(8192, 1024); end - if (MAX_FFT_SIZE >= 4096) test_fft_config(4096, 1024); - if (MAX_FFT_SIZE >= 8192) test_fft_config(8192, 1024); - test_cyclic_prefix(); + if (EN_CP_INSERTION || EN_CP_REMOVAL) begin + test_cyclic_prefix(); + test_max_min_cp_len(); + test_max_cp_list_len(); + end + if (EN_FFT_ORDER) test_fft_order(); test_throttle(); test_throughput(); + test_max_min_packet(); + test_magnitude_options(); test_max_min_fft(); - test_max_cp_list_len(); - test_max_min_cp_len(); - test_random(.num_iterations(200), .max_fft_size(128), .min_fft_size(MIN_FFT_SIZE)); - end + if (EN_FFT_BYPASS) test_bypass(); - // Always test magnitude as part of the quick tests, so we can quickly test - // different values of USE_APPROX_MAG. - if (NUM_CHAN_PER_CORE == 1) begin - if (EN_MAGNITUDE && EN_FFT_BYPASS) test_magnitude(MAG_SEL_MAG); - if (EN_MAGNITUDE_SQ && EN_FFT_BYPASS) test_magnitude(MAG_SEL_MAG_SQ); + // Do a lot of small tests to get more coverage in less time + test_random(.num_iterations(200), .max_fft_size(`MIN(64, MAX_TEST_FFT_SIZE))); + + // Do some long tests + test_random(.num_iterations(30), .max_fft_size(MAX_TEST_FFT_SIZE)); end //--------------------------------