Files
b210-k7-fpga/lib/rfnoc/blocks/rfnoc_block_fft/fft_packetize.sv
T
Wade Fife d7e5823047 fpga: rfnoc: fft: Add FFT packetize modules
Original-commit: 97ea89fac5c1889e5c0da44349d5f4be3d788965
2025-03-07 12:39:34 -06:00

576 lines
22 KiB
Systemverilog

//
// Copyright 2025 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: fft_packetize
//
// Description:
//
// This module converts the packets from the RFNoC shell's AXI-stream data
// interface into packets to be consumed by the FFT logic. It works in
// collaboration with the fft_depacketize module, which does the reverse.
// Relevant information about the burst and number of FFTs is passed along
// using side-band AXI-stream buses (o_info and o_symbol) to the depacketizer.
//
// This module takes into account any cyclic prefix removal being performed
// by the FFT block to ensure the data is packetized for the FFT logic
// appropriately.
//
// The RFNoC packet from the NoC shell is input onto the i_noc bus. The
// repacketized FFT data, resized to the requested symbol size, is output on
// the o_fft bus.
//
// The cyclic prefix length to be removed for each symbol is input on the
// i_cp_rem bus. A copy of the cyclic prefix length that was input is then
// stored in a FIFO to be output onto the o_cp_rem bus for use by downstream
// FFT logic, where the actual removal is performed. The cyclic prefix to be
// used must be set on the i_cp_rem_tdata input at the time the associated
// symbol begins to be received because it will not wait for i_cp_rem_tvalid.
// In the case where no cyclic prefix was provided by the user, it is assumed
// that the logic driving this input will give it a reasonable default (e.g.,
// a prefix length of 0).
//
// If the last sample of the input burst does not coincide with the end of a
// symbol, then data will be inserted, so that the FFT block does not end a
// burst in the middle of an FFT transfer. This may result in extra data
// being output at the end of a burst, and the values of the final FFT/IFFT
// will be corrupted in an unpredictable way. So, users should always input
// full symbols into the FFT block.
//
// Timestamps are also supported. The timestamp from the first packet of the
// burst input into i_noc is captured by this module and passed to the
// fft_depacketize module via the o_info bus. See the fft_depacketize module
// for details of how timestamps are generated for output packets.
//
// This module also supports multiple synchronized channels by setting
// NUM_CHAN to a number greater than 1. In this case, all the sideband
// signals are assumed to be shared by all channels (tvalid, tready, tkeep,
// tlength, ttimestamp, etc.) but the tdata field is NUM_CHAN times wider to
// accommodate the data from the other channels.
//
// The minimum FFT size supported by this module is the next power of two
// that's greater than or equal to 2*NIPC, due to the pipeline delay of
// calculating when we've reached the end of the packet.
//
// The incoming RFNoC packets and the cyclic prefix length must both be a
// multiple of NIPC. Trailing data (i.e., when TKEEP is not all ones) is only
// allowed on the last transfer of a burst.
//
// Parameters:
//
// ITEM_W : Item size (or sample size) in bits for the FFT/IFFT
// core.
// NIPC : Number of items per clock cycle. Each word is
// NIPC*ITEM_W bits wide. It must be a power of 2.
// NUM_CHAN : Number of parallel channels sharing the sideband
// information.
// EN_CP_REMOVAL : Indicates whether to support cyclic prefix removal.
// MAX_PKT_SIZE_LOG2 : Maximum packet payload size in items, expressed as
// a log base 2. In other words, the maximum packet
// size is 2**PKT_SIZE_LOG items.
// MAX_FFT_SIZE_LOG2 : Maximum FFT size in items, expressed as a log base
// 2. In other words, the maximum FFT size is
// 2**MAX_FFT_SIZE_LOG2 items.
// DATA_FIFO_SIZE_LOG2 : Depth of the internal FIFO that stores output data
// to o_fft, expressed as a log base 2. In other
// words, the FIFO size is 2**DATA_FIFO_SIZE_LOG2
// items for each channel. This can be used to provide
// additional buffering, if needed. Set to -1 to
// remove the FIFO.
// CP_FIFO_SIZE_LOG2 : Depth of the internal FIFO that stores cyclic
// prefix lengths, expressed as a log base 2. In other
// words, the FIFO size is 2**CP_FIFO_SIZE_LOG2
// lengths deep. This FIFO is used to pass i_cp_rem to
// o_cp_rem and must be deep enough to account for the
// maximum number of FFT operations that are in flight
// at one time.
// BURST_FIFO_SIZE_LOG2 : Depth of the internal FIFO that stores burst
// information, expressed as a log base 2. In other
// words, the FIFO size is 2**BURST_FIFO_SIZE_LOG2
// bursts deep. This FIFO stores information about
// each burst and must be deep enough to account for
// the maximum number of bursts that are in flight at
// one time.
// SYMB_FIFO_SIZE_LOG2 : Depth of the internal FIFO that stores symbol
// information, expressed as a log base 2. In other
// words, the FIFO size is 2**SYMB_FIFO_SIZE_LOG2
// symbols deep. This FIFO is used to store
// information about each symbol and must be deep
// enough to account for the maximum number of FFT
// operations that are in flight at one time.
//
`default_nettype none
module fft_packetize
import rfnoc_chdr_utils_pkg::*;
import fft_packetize_pkg::*;
#(
int ITEM_W = 32,
int NIPC = 1,
int NUM_CHAN = 1,
bit EN_CP_REMOVAL = 1'b1,
int MAX_PKT_SIZE_LOG2 = 11,
int MAX_FFT_SIZE_LOG2 = 10,
int DATA_FIFO_SIZE_LOG2 = -1,
int CP_FIFO_SIZE_LOG2 = 5,
int BURST_FIFO_SIZE_LOG2 = 5,
int SYMB_FIFO_SIZE_LOG2 = 5,
// Internal constants
localparam int DATA_W = NUM_CHAN * ITEM_W * NIPC,
localparam int KEEP_W = NIPC,
localparam int PKT_SIZE_W = MAX_PKT_SIZE_LOG2 + 1,
localparam int FFT_SIZE_W = MAX_FFT_SIZE_LOG2 + 1,
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,
input wire [ FFT_SIZE_LOG2_W-1:0] fft_size_log2,
// Input from cyclic prefix removal list
input wire [ CP_LEN_W-1:0] i_cp_rem_tdata,
input wire i_cp_rem_tvalid,
output logic i_cp_rem_tready,
// Output to cyclic prefix removal logic
output logic [ CP_LEN_W-1:0] o_cp_rem_tdata,
output logic o_cp_rem_tvalid,
input wire o_cp_rem_tready,
// Input from NoC Shell
input wire [ DATA_W-1:0] i_noc_tdata,
input wire [ KEEP_W-1:0] i_noc_tkeep,
input wire i_noc_tlast,
input wire i_noc_tvalid,
output logic i_noc_tready,
input wire [CHDR_TIMESTAMP_W-1:0] i_noc_ttimestamp,
input wire i_noc_thas_time,
input wire [ CHDR_LENGTH_W-1:0] i_noc_tlength,
input wire i_noc_teov,
input wire i_noc_teob,
// Output to FFT core
output logic [ DATA_W-1:0] o_fft_tdata,
output logic [ KEEP_W-1:0] o_fft_tkeep,
output logic o_fft_tlast,
output logic o_fft_tvalid,
input wire o_fft_tready,
// Information about each burst (packet size in items, timestamp), going to
// the depacketizer.
output burst_info_t o_burst_tdata,
output logic o_burst_tvalid,
input wire o_burst_tready,
// Information about each symbol (whether it is the last of a burst), going
// to the depacketizer.
output symbol_info_t o_symbol_tdata,
output logic o_symbol_tvalid,
input wire o_symbol_tready
);
// Make sure NIPC is a power of 2
if (NIPC != 2**$clog2(NIPC)) begin : gen_nipc_assertion
$error("NIPC must be a power of 2");
end
// Create a mask to remove unused bits
localparam logic [ CP_LEN_W-1:0] CP_LEN_MASK = $clog2(NIPC);
localparam logic [PKT_SIZE_W-1:0] PKT_SIZE_MASK = $clog2(NIPC);
//---------------------------------------------------------------------------
// FFT Size Register
//---------------------------------------------------------------------------
//
// We assume the fft_size input is set well in advance of any data being
// received and that it does not change during a burst. This means that we
// can tolerate a few cycles of delay on these registers.
//
//---------------------------------------------------------------------------
logic [FFT_SIZE_W-1:0] fft_size;
always_ff @(posedge clk) begin
fft_size <= 1 << fft_size_log2;
end
//---------------------------------------------------------------------------
// State Machine
//---------------------------------------------------------------------------
typedef enum logic [1:0] {
WAIT_BURST_ST,
GET_PREFIX_ST,
PASS_SYMBOL_ST,
FINISH_SYMBOL_ST
} state_t;
state_t state = WAIT_BURST_ST;
// Burst information
logic [CHDR_TIMESTAMP_W-1:0] timestamp;
logic has_time;
logic [ PKT_SIZE_W-1:0] pkt_size; // Packet size in items
logic burst_wr_stb = 1'b0; // Info write strobe
// Symbol size (IFFT data + cyclic prefix)
logic [FFT_SIZE_W-1:0] symbol_size;
logic prefix_wr_stb = 1'b0; // Write strobe
// Symbol information
logic last_symbol; // Last symbol of burst
logic symbol_wr_stb = 1'b0; // Write strobe
// Counter to track how much of current symbol we've output
logic [FFT_SIZE_W-1:0] item_count = NIPC;
// Indicates we're on the last sample/item of the symbol
logic symbol_tlast;
// Data FIFO inputs
logic [DATA_W-1:0] i_fft_tdata;
logic [KEEP_W-1:0] i_fft_tkeep;
logic i_fft_tlast;
logic i_fft_tvalid;
logic i_fft_tready;
// Cyclic prefix removal length FIFO inputs
logic [CP_LEN_W-1:0] cp_rem_fifo_tdata;
logic cp_rem_fifo_tvalid;
logic cp_rem_fifo_tready;
// Burst information FIFO inputs
burst_info_t burst_fifo_tdata;
logic burst_fifo_tvalid;
logic burst_fifo_tready;
// Symbol information FIFO inputs
symbol_info_t symbol_fifo_tdata;
logic symbol_fifo_tvalid;
logic symbol_fifo_tready;
always_ff @(posedge clk) begin
burst_wr_stb <= 1'b0;
prefix_wr_stb <= 1'b0;
symbol_wr_stb <= 1'b0;
unique case (state)
WAIT_BURST_ST : begin
// Grab the packet and FFT size for this burst
item_count <= 2*NIPC; // Account for one cycle of delay, plus one for tlast
symbol_tlast <= 1'b0;
timestamp <= i_noc_ttimestamp;
has_time <= i_noc_thas_time;
pkt_size <= (i_noc_tlength / (ITEM_W/8)) & ~PKT_SIZE_MASK;
// We wait until we have a new packet and the downstream info FIFOs
// have room to accept another entry (CP removal length, burst info,
// and symbol info).
if (i_noc_tvalid && cp_rem_fifo_tready && burst_fifo_tready && symbol_fifo_tready) begin
//synthesis translate_off
assert (i_noc_tlength % (NIPC * ITEM_W/8) == 0) else
$error("fft_packetize: Input packet length is not a multiple of NIPC");
//synthesis translate_on
burst_wr_stb <= 1'b1;
if (EN_CP_REMOVAL) begin
state <= GET_PREFIX_ST;
end else begin
symbol_size <= fft_size;
state <= PASS_SYMBOL_ST;
end
end
end
GET_PREFIX_ST : begin
// Get the cyclic prefix length for this the next symbol. We assume it
// is always valid because it defaults to the desired default value.
item_count <= 2*NIPC;
symbol_tlast <= 1'b0;
// Wait until there's room in the downstream FIFOs for the next symbol
if (symbol_fifo_tready && cp_rem_fifo_tready) begin
// Symbol size only changes when CP removal is enabled
if (EN_CP_REMOVAL) begin
symbol_size <= fft_size + (i_cp_rem_tdata & ~CP_LEN_MASK);
end
prefix_wr_stb <= 1'b1;
state <= PASS_SYMBOL_ST;
end
end
PASS_SYMBOL_ST : begin
// Pass the symbol through
if (i_fft_tvalid && i_fft_tready) begin
item_count <= item_count + NIPC;
symbol_tlast <= (item_count >= symbol_size);
if (symbol_tlast) begin
symbol_wr_stb <= 1'b1;
if (i_noc_tlast && i_noc_teob) begin
// All done! Wait for the next burst.
last_symbol <= 1'b1;
state <= WAIT_BURST_ST;
end else begin
last_symbol <= 1'b0;
// Finished the symbol. Figure out the length of the next one.
state <= GET_PREFIX_ST;
end
end else if (i_noc_tlast && i_noc_teob) begin
// We've reached the end of the burst, but we haven't finished the
// current symbol.
state <= FINISH_SYMBOL_ST;
end else begin
; // Let the next item/sample pass through
end
end
end
FINISH_SYMBOL_ST : begin
// Push through enough data to finish the symbol, so that the
// downstream FFT logic doesn't get left in a bad state.
last_symbol <= 1'b1;
if (i_fft_tvalid && i_fft_tready) begin
item_count <= item_count + NIPC;
symbol_tlast <= (item_count >= symbol_size);
if (symbol_tlast) begin
// All done! Wait for the next burst.
symbol_wr_stb <= 1'b1;
state <= WAIT_BURST_ST;
end
end
end
endcase
if (rst) begin
state <= WAIT_BURST_ST;
prefix_wr_stb <= 1'b0;
timestamp <= 'X;
has_time <= 'X;
pkt_size <= 'X;
burst_wr_stb <= 1'b0;
symbol_size <= 'X;
item_count <= 'X;
symbol_tlast <= 'X;
symbol_wr_stb <= 1'b0;
last_symbol <= 'X;
end
end
//---------------------------------------------------------------------------
// Data Pass-through Logic
//---------------------------------------------------------------------------
always_comb begin
i_fft_tdata = i_noc_tdata;
i_fft_tkeep = {KEEP_W{1'b1}};
i_fft_tlast = symbol_tlast;
unique case (state)
WAIT_BURST_ST : begin
i_fft_tvalid = 1'b0;
i_noc_tready = 1'b0;
end
GET_PREFIX_ST : begin
i_fft_tvalid = 1'b0;
i_noc_tready = 1'b0;
end
PASS_SYMBOL_ST : begin
// Pass the next item/sample through
i_fft_tvalid = i_noc_tvalid;
i_noc_tready = i_fft_tready;
end
FINISH_SYMBOL_ST : begin
// Flush the data through the FFT
i_fft_tvalid = 1'b1;
i_noc_tready = 1'b0;
end
endcase
end
//---------------------------------------------------------------------------
// Output Data FIFO
//---------------------------------------------------------------------------
//
// This FIFO is required to handle the worst-case scenario in which the user
// wants small FFTs and large packets. In the case where the FFT size equals
// the packet size, this FIFO is not required.
//
//---------------------------------------------------------------------------
if (DATA_FIFO_SIZE_LOG2 > -1) begin : gen_output_fifo
axi_fifo #(
.WIDTH(1 + KEEP_W + DATA_W ),
.SIZE (DATA_FIFO_SIZE_LOG2 - $clog2(NIPC))
) axi_fifo_i (
.clk (clk ),
.reset (rst ),
.clear (1'b0 ),
.i_tdata ({i_fft_tlast, i_fft_tkeep, i_fft_tdata}),
.i_tvalid(i_fft_tvalid ),
.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 )
);
end else begin : gen_no_output_fifo
assign o_fft_tdata = i_fft_tdata;
assign o_fft_tkeep = i_fft_tkeep;
assign o_fft_tlast = i_fft_tlast;
assign o_fft_tvalid = i_fft_tvalid;
assign i_fft_tready = o_fft_tready;
end
//---------------------------------------------------------------------------
// Cyclic Prefix Pass-through Logic
//---------------------------------------------------------------------------
if (EN_CP_REMOVAL) begin : gen_cp_rem_pass_through
logic [15:0] cp_rem_fifo_space;
assign i_cp_rem_tready = prefix_wr_stb;
assign cp_rem_fifo_tdata = i_cp_rem_tdata & ~CP_LEN_MASK; // Clear the unused bits
assign cp_rem_fifo_tvalid = prefix_wr_stb;
axi_fifo #(
.WIDTH(CP_LEN_W ),
.SIZE (CP_FIFO_SIZE_LOG2)
) axis_fifo_cp_length (
.clk (clk ),
.reset (rst ),
.clear (1'b0 ),
.i_tdata (cp_rem_fifo_tdata ),
.i_tvalid(cp_rem_fifo_tvalid),
.i_tready(cp_rem_fifo_tready),
.o_tdata (o_cp_rem_tdata ),
.o_tvalid(o_cp_rem_tvalid ),
.o_tready(o_cp_rem_tready ),
.space (cp_rem_fifo_space ),
.occupied( )
);
// The CP removal length FIFO should be large enough to hold all the
// symbols that are in flight. Filling up might be an indication that it's
// sized too small.
//synthesis translate_off
logic cp_rem_fifo_empty_prev = 0;
always_ff @(posedge clk) begin
cp_rem_fifo_empty_prev <= (cp_rem_fifo_space == 0);
if (!cp_rem_fifo_empty_prev && cp_rem_fifo_space == 0) begin
$warning("CP removal FIFO has filled");
end
end
//synthesis translate_on
end else begin : gen_no_cp_rem_pass_through
assign cp_rem_fifo_tready = 1'b1;
assign i_cp_rem_tready = 1'b1;
assign o_cp_rem_tdata = '0;
assign o_cp_rem_tvalid = 1'b0;
end
//---------------------------------------------------------------------------
// Burst Info Logic
//---------------------------------------------------------------------------
logic [15:0] burst_fifo_space;
assign burst_fifo_tdata = '{ timestamp, has_time, pkt_size & ~PKT_SIZE_MASK};
assign burst_fifo_tvalid = burst_wr_stb;
axi_fifo #(
.WIDTH(BURST_INFO_W ),
.SIZE (BURST_FIFO_SIZE_LOG2)
) axis_fifo_burst (
.clk (clk ),
.reset (rst ),
.clear (1'b0 ),
.i_tdata (burst_fifo_tdata ),
.i_tvalid(burst_fifo_tvalid),
.i_tready(burst_fifo_tready),
.o_tdata (o_burst_tdata ),
.o_tvalid(o_burst_tvalid ),
.o_tready(o_burst_tready ),
.space (burst_fifo_space ),
.occupied( )
);
// The burst information FIFO should be large enough to hold all the
// bursts that are in flight. Filling up might be an indication that it's
// sized too small.
//synthesis translate_off
logic burst_fifo_empty_prev = 0;
always_ff @(posedge clk) begin
burst_fifo_empty_prev <= (burst_fifo_space == 0);
if (!burst_fifo_empty_prev && burst_fifo_space == 0) begin
$warning("Burst info FIFO has filled");
end
end
//synthesis translate_on
//---------------------------------------------------------------------------
// Symbol Information FIFO
//---------------------------------------------------------------------------
//
// This FIFO is used to store the information about the the symbols that have
// been input to the FFT block. Each element in the FIFO corresponds to one
// symbol. If the data bit is 0, then the corresponding symbol is not the
// last symbol of the burst. If the data bit is 1, then it is the last symbol
// of the burst.
//
//---------------------------------------------------------------------------
logic [15:0] o_symbol_space;
assign symbol_fifo_tdata.last = last_symbol;
assign symbol_fifo_tvalid = symbol_wr_stb;
axi_fifo #(
.WIDTH(SYMBOL_INFO_W ),
.SIZE (SYMB_FIFO_SIZE_LOG2)
) axis_fifo_symb (
.clk (clk ),
.reset (rst ),
.clear (1'b0 ),
.i_tdata (symbol_fifo_tdata ),
.i_tvalid(symbol_fifo_tvalid),
.i_tready(symbol_fifo_tready),
.o_tdata (o_symbol_tdata ),
.o_tvalid(o_symbol_tvalid ),
.o_tready(o_symbol_tready ),
.space (o_symbol_space ),
.occupied( )
);
// The symbol information FIFO should be large enough to hold all the
// symbols that are in flight. Filling up might be an indication that it's
// sized too small.
//synthesis translate_off
logic symbol_fifo_empty_prev = 0;
always_ff @(posedge clk) begin
symbol_fifo_empty_prev <= (o_symbol_space == 0);
if (!symbol_fifo_empty_prev && o_symbol_space == 0) begin
$warning("Symbol info FIFO has filled");
end
end
//synthesis translate_on
endmodule : fft_packetize
`default_nettype wire