fpga: rfnoc: Add new FFT block
This replaces the legacy RFNoC FFT block with a new one of the same name that includes a superset of the original features. New features include cyclic prefix insertion or removal and FFT sizes up to 64k. Original-commit: 25fffb006755b2e91f0138eb4af3ab9e16b8690c
This commit is contained in:
committed by
Jörg Hofrichter
parent
0770fd4405
commit
81f150213f
@@ -0,0 +1,623 @@
|
||||
//
|
||||
// Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: fft_reorder
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// This module optionally rearranges the order of FFT bins to put them in the
|
||||
// desired order. It also supports cyclic prefix insertion.
|
||||
//
|
||||
// The input order that this module receives is a parameter that must be
|
||||
// chosen at compile time. The following input orders are supported:
|
||||
//
|
||||
// NATURAL: Positive frequencies are input first, starting with 0 Hz,
|
||||
// followed by negative frequencies. Frequencies are input in
|
||||
// ascending order.
|
||||
// BIT_REVERSE: Like natural, but the bits of the indices are in reverse
|
||||
// order. For example, for a size 16 FFT, bin 0000 is input
|
||||
// first, followed by bin 1000, 0100, 1100, 0010, etc.
|
||||
//
|
||||
// The output order can be chosen at run time. The following output orders
|
||||
// are supported:
|
||||
//
|
||||
// NORMAL: Negative frequencies first, then positive frequencies. 0 Hz
|
||||
// is in the center. Frequencies are output in ascending order.
|
||||
// REVERSE: Reverse order of NORMAL. Positive frequencies first, then
|
||||
// negative frequencies. 0 Hz in the center. Frequencies are
|
||||
// output in descending order.
|
||||
// NATURAL: Positive frequencies are first, starting with 0 Hz,
|
||||
// followed by negative frequencies. Frequencies are output in
|
||||
// ascending order.
|
||||
// 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.
|
||||
//
|
||||
// Typically the FFT IP feeding this module will output data in BIT_REVERSE
|
||||
// order. The FFT IP may have an option to rearrange the data into NATURAL
|
||||
// order but enabling this feature causes a large memory to be added to the
|
||||
// IP to do the reordering. Since we want to also be able to provide NORMAL
|
||||
// order, and we don't want to add a second memory for that reordering, we do
|
||||
// all the reordering here in one memory.
|
||||
//
|
||||
// If the FFT core is outputting in the order you want, then this module
|
||||
// should probably be removed to save RAM and logic.
|
||||
//
|
||||
// The TLAST input/output corresponds to when the FFT input/output ends for a
|
||||
// single FFT-sized sequence of data. i_tlast must be asserted during the
|
||||
// last transfer of the input FFT to reset things for the next FFT input.
|
||||
//
|
||||
// For cyclic prefix insertion, the EN_CP_INSERTION parameter must be true
|
||||
// and i_tuser contains the cyclic prefix size to insert. It must be valid
|
||||
// during the first transfer of the packet. It can be any size from 0 to
|
||||
// 2**MAX_FFT_LEN_LOG2-1.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// IN_FIFO_LOG2 : Log base-2 of the input FIFO size. Set to -1 to remove
|
||||
// the input FIFO. This FIFO is intended as a pipeline
|
||||
// stage to cut the timing path on the input.
|
||||
// OUT_FIFO_LOG2 : Log base-2 of the output FIFO size. This must be set to
|
||||
// at least 3.
|
||||
// INPUT_ORDER : BIT_REVERSE or NATURAL. See fft_reorder_pkg for values.
|
||||
// MAX_FFT_LEN_LOG2 : Ceiling of log base-2 of the maximum FFT size to be
|
||||
// supported.
|
||||
// DATA_W : Data width. Typically 32 for sc16 data type.
|
||||
// EN_CP_INSERTION : Controls whether or not the CP insertion logic is
|
||||
// included.
|
||||
//
|
||||
// Signals:
|
||||
//
|
||||
// i_t* : AXI-Stream data input. Each packet is one FFT to be processed. The
|
||||
// length of the packet must match the FFT size. i_tuser contains the
|
||||
// cyclic prefix size to insert for this packet and must be valid
|
||||
// during the first transfer of the packet.
|
||||
// o_t* : AXI-Stream data output. Each packet is one FFT with optional cyclic
|
||||
// prefix.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module fft_reorder
|
||||
import fft_reorder_pkg::*;
|
||||
#(
|
||||
parameter int IN_FIFO_LOG2 = 1,
|
||||
parameter int OUT_FIFO_LOG2 = 3,
|
||||
parameter fft_order_t INPUT_ORDER = BIT_REVERSE,
|
||||
parameter int MAX_FFT_LEN_LOG2 = 12,
|
||||
parameter int DATA_W = 32,
|
||||
parameter bit EN_CP_INSERTION = 1,
|
||||
|
||||
localparam int FFT_LEN_LOG2_W = $clog2(MAX_FFT_LEN_LOG2+1),
|
||||
localparam int CP_LEN_W = MAX_FFT_LEN_LOG2
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire fft_cfg_wr,
|
||||
input wire [FFT_LEN_LOG2_W-1:0] fft_len_log2,
|
||||
input fft_order_t fft_out_order,
|
||||
|
||||
// Data Input
|
||||
input wire [ DATA_W-1:0] i_tdata,
|
||||
input wire [CP_LEN_W-1:0] i_tuser,
|
||||
input wire i_tlast,
|
||||
input wire i_tvalid,
|
||||
output wire i_tready,
|
||||
|
||||
// Data Output
|
||||
output wire [DATA_W-1:0] o_tdata,
|
||||
output wire o_tlast,
|
||||
output wire o_tvalid,
|
||||
input wire o_tready
|
||||
);
|
||||
|
||||
// These registers track if the current read/write buffers are OK to use
|
||||
logic ok_to_write = 1'b1; // Current write buffer is free for writes
|
||||
logic ok_to_read = 1'b0; // Current read buffer has data to read
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Optional Data Input Pipeline
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic [ DATA_W-1:0] in_fifo_o_tdata;
|
||||
logic [CP_LEN_W-1:0] in_fifo_o_tuser;
|
||||
logic in_fifo_o_tvalid;
|
||||
logic in_fifo_o_tready;
|
||||
logic in_fifo_o_tlast;
|
||||
|
||||
if (IN_FIFO_LOG2 >= 0) begin : gen_in_fifo
|
||||
axi_fifo #(
|
||||
.WIDTH(1 + CP_LEN_W + DATA_W),
|
||||
.SIZE (IN_FIFO_LOG2)
|
||||
) axi_fifo_in (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear ('0),
|
||||
.i_tdata ({i_tlast, i_tuser, i_tdata}),
|
||||
.i_tvalid(i_tvalid),
|
||||
.i_tready(i_tready),
|
||||
.o_tdata ({in_fifo_o_tlast, in_fifo_o_tuser, in_fifo_o_tdata}),
|
||||
.o_tvalid(in_fifo_o_tvalid),
|
||||
.o_tready(in_fifo_o_tready),
|
||||
.space (),
|
||||
.occupied()
|
||||
);
|
||||
end else begin : gen_no_in_fifo
|
||||
assign in_fifo_o_tdata = i_tdata;
|
||||
assign in_fifo_o_tuser = i_tuser;
|
||||
assign in_fifo_o_tlast = i_tlast;
|
||||
assign in_fifo_o_tvalid = i_tvalid;
|
||||
assign i_tready = in_fifo_o_tready;
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Optional Data Output Pipeline
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (OUT_FIFO_LOG2 < 3) begin
|
||||
OUT_FIFO_LOG2_must_be_at_least_3();
|
||||
end
|
||||
|
||||
logic [DATA_W-1:0] out_fifo_i_tdata;
|
||||
logic out_fifo_i_tvalid;
|
||||
logic out_fifo_i_tlast;
|
||||
|
||||
// We use out_fifo_space instead of out_fifo_i_tready to allow extra space
|
||||
// for the RAM output read delay.
|
||||
logic [15:0] out_fifo_space;
|
||||
|
||||
axi_fifo #(
|
||||
.WIDTH(DATA_W+1),
|
||||
.SIZE (OUT_FIFO_LOG2)
|
||||
) axi_fifo_out (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear ('0),
|
||||
.i_tdata ({out_fifo_i_tlast, out_fifo_i_tdata}),
|
||||
.i_tvalid(out_fifo_i_tvalid),
|
||||
.i_tready(),
|
||||
.o_tdata ({o_tlast, o_tdata}),
|
||||
.o_tvalid(o_tvalid),
|
||||
.o_tready(o_tready),
|
||||
.space (out_fifo_space),
|
||||
.occupied()
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Configuration Registers
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Store relevant FFT configuration values in registers for use elsewhere. We
|
||||
// assume that the configuration is set in advance of any operation and is
|
||||
// only changed when the FFT is idle, so we ignore the latency here.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Number of bits needed to represent the maximum FFT size
|
||||
localparam FFT_LEN_W = MAX_FFT_LEN_LOG2+1;
|
||||
|
||||
logic fft_cfg_wr_stb = 1'b0;
|
||||
fft_order_t fft_out_order_reg = NORMAL;
|
||||
logic [FFT_LEN_LOG2_W-1:0] fft_len_log2_reg = MAX_FFT_LEN_LOG2;
|
||||
logic [FFT_LEN_W-1:0] fft_len = 1 << MAX_FFT_LEN_LOG2;
|
||||
logic [FFT_LEN_W-1:0] fft_len_m1 = (1 << MAX_FFT_LEN_LOG2)-1;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if(rst) begin
|
||||
fft_cfg_wr_stb <= 1'b0;
|
||||
fft_out_order_reg <= NORMAL;
|
||||
fft_len_log2_reg <= MAX_FFT_LEN_LOG2;
|
||||
fft_len <= 1 << MAX_FFT_LEN_LOG2;
|
||||
fft_len_m1 <= (1 << MAX_FFT_LEN_LOG2)-1;
|
||||
end else begin
|
||||
fft_cfg_wr_stb <= 1'b0;
|
||||
if (fft_cfg_wr) begin
|
||||
fft_cfg_wr_stb <= 1'b1;
|
||||
fft_out_order_reg <= fft_out_order;
|
||||
fft_len_log2_reg <= fft_len_log2;
|
||||
fft_len <= (1 << fft_len_log2);
|
||||
fft_len_m1 <= (1 << fft_len_log2)-1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// RAM Buffer
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// This RAM stores the data that's being input, writing it the order needed
|
||||
// such that when read out sequentially, it will be in the correct order.
|
||||
//
|
||||
// The RAM is divided into two halves, which we'll call buffers. Each buffer
|
||||
// is used exclusively for read or write, until they switch.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Address width for each buffer. Must be big enough to store the maximum
|
||||
// length FFT.
|
||||
localparam ADDR_W = MAX_FFT_LEN_LOG2;
|
||||
|
||||
// RAM read latency
|
||||
localparam READ_LATENCY = 2;
|
||||
|
||||
logic ram_rd_buffer; // Indicates which buffer is currently used for reads
|
||||
logic ram_wr_buffer; // Indicates which buffer is currently used for writes
|
||||
logic ram_wr_en;
|
||||
logic ram_wr_en_0; // One RAM read enable for each buffer
|
||||
logic ram_wr_en_1;
|
||||
logic [ADDR_W-1:0] ram_wr_addr;
|
||||
logic [DATA_W-1:0] ram_wr_data;
|
||||
logic ram_rd_en;
|
||||
logic [ADDR_W-1:0] ram_rd_addr;
|
||||
logic [DATA_W-1:0] ram_rd_data_raw_0; // One RAM read output for each buffer
|
||||
logic [DATA_W-1:0] ram_rd_data_raw_1;
|
||||
|
||||
ram_2port #(
|
||||
.DWIDTH (DATA_W),
|
||||
.AWIDTH (ADDR_W), // Make the RAM two buffers big
|
||||
.OUT_REG(1)
|
||||
) ram_2port_0 (
|
||||
.clka (clk),
|
||||
.ena ('1),
|
||||
.wea (ram_wr_en_0),
|
||||
.addra(ram_wr_addr),
|
||||
.dia (ram_wr_data),
|
||||
.doa (),
|
||||
.clkb (clk),
|
||||
.enb ('1),
|
||||
.web ('0),
|
||||
.addrb(ram_rd_addr),
|
||||
.dib ('0),
|
||||
.dob (ram_rd_data_raw_0)
|
||||
);
|
||||
|
||||
ram_2port #(
|
||||
.DWIDTH (DATA_W),
|
||||
.AWIDTH (ADDR_W), // Make the RAM two buffers big
|
||||
.OUT_REG(1)
|
||||
) ram_2port_1 (
|
||||
.clka (clk),
|
||||
.ena ('1),
|
||||
.wea (ram_wr_en_1),
|
||||
.addra(ram_wr_addr),
|
||||
.dia (ram_wr_data),
|
||||
.doa (),
|
||||
.clkb (clk),
|
||||
.enb ('1),
|
||||
.web ('0),
|
||||
.addrb(ram_rd_addr),
|
||||
.dib ('0),
|
||||
.dob (ram_rd_data_raw_1)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Write Logic
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Here we write the data into the memory in a carefully controlled order
|
||||
// such that we can read it out in sequential or bit-reversed order to get
|
||||
// the order we want.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic [FFT_LEN_W-1:0] fft_addr_mask;
|
||||
logic [FFT_LEN_W-1:0] wr_count;
|
||||
|
||||
logic ram_wr_last;
|
||||
|
||||
assign ram_wr_data = in_fifo_o_tdata;
|
||||
assign ram_wr_en = in_fifo_o_tvalid && in_fifo_o_tready;
|
||||
assign ram_wr_en_0 = ram_wr_en && (ram_wr_buffer == 1'b0);
|
||||
assign ram_wr_en_1 = ram_wr_en && (ram_wr_buffer == 1'b1);
|
||||
assign in_fifo_o_tready = ok_to_write;
|
||||
assign ram_wr_last = in_fifo_o_tlast;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (fft_cfg_wr_stb || (ram_wr_en && ram_wr_last)) begin
|
||||
if (fft_out_order_reg == NATURAL) begin
|
||||
// Natural to natural. No mask needed to affect the order.
|
||||
fft_addr_mask <= '0;
|
||||
ram_wr_addr <= '0;
|
||||
end else if (fft_out_order_reg == REVERSE) begin
|
||||
// Natural to reverse. Invert all bits except the MSB. Inverting the
|
||||
// lower bits reverses the order. Leaving the MSB unchanged ensures we
|
||||
// output positive frequencies first, then negative frequencies.
|
||||
fft_addr_mask <= fft_len_m1 >> 1; // e.g., 8'b0111_1111
|
||||
ram_wr_addr <= fft_len_m1 >> 1;
|
||||
end else if (fft_out_order_reg == NORMAL) begin
|
||||
// Natural to normal. Invert the MSB, so that we output negative
|
||||
// frequencies first, then positive frequencies.
|
||||
fft_addr_mask <= fft_len >> 1; // e.g., 8'b1000_0000
|
||||
ram_wr_addr <= fft_len >> 1;
|
||||
end else begin // (fft_order_t == BIT_REVERSE)
|
||||
// Natural to bit-reverse. For this we also use natural order, and we
|
||||
// enable/disable the bit-reversal on the read side as needed.
|
||||
fft_addr_mask <= '0;
|
||||
ram_wr_addr <= '0;
|
||||
end
|
||||
end
|
||||
|
||||
if (ram_wr_en) begin
|
||||
wr_count <= wr_count+1;
|
||||
|
||||
if (ram_wr_last) begin
|
||||
// Switch to the other buffer
|
||||
ram_wr_buffer <= ~ram_wr_buffer;
|
||||
wr_count <= '0;
|
||||
end else begin
|
||||
// Calculate the the next write address
|
||||
if (
|
||||
(INPUT_ORDER == BIT_REVERSE && fft_out_order_reg != BIT_REVERSE) ||
|
||||
(INPUT_ORDER == NATURAL && fft_out_order_reg == BIT_REVERSE)
|
||||
) begin : bit_reversed
|
||||
// If the input is bit-reversed and we're not outputting
|
||||
// bit-reversed, then we bit reverse the RAM address to convert from
|
||||
// bit-reversed to natural order. Then apply the mask to that to
|
||||
// convert from natural to the desired output order.
|
||||
ram_wr_addr <= bit_reverse(wr_count+1, fft_len_log2_reg) ^ fft_addr_mask;
|
||||
end else begin : natural
|
||||
// Apply the mask to convert from natural to to the desired output
|
||||
// order.
|
||||
ram_wr_addr <= (wr_count+1) ^ fft_addr_mask;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (rst) begin
|
||||
ram_wr_buffer <= '0;
|
||||
ram_wr_addr <= '0;
|
||||
wr_count <= '0;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// CP Insertion Length FIFO
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Cyclic prefix logic interface signals
|
||||
logic cp_valid; // Indicates the CP FIFO has an output
|
||||
logic cp_non_zero; // Indicates the CP value is > 0
|
||||
logic [ADDR_W-1:0] cp_start_addr; // Indicates the CP RAM start address
|
||||
logic cp_consume; // Control to indicate we've captured the CP length output
|
||||
|
||||
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 in_fifo_o_tfirst = '1; // First transfer of packet
|
||||
|
||||
// Create a register that indicates when the next transfer is the start of
|
||||
// a new packet.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
in_fifo_o_tfirst <= '1;
|
||||
end else begin
|
||||
if (in_fifo_o_tvalid && in_fifo_o_tready) begin
|
||||
in_fifo_o_tfirst <= in_fifo_o_tlast;
|
||||
end
|
||||
end
|
||||
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;
|
||||
|
||||
// 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
|
||||
// setting its size appropriately.
|
||||
axi_fifo #(
|
||||
.WIDTH(CP_LEN_W),
|
||||
.SIZE (1)
|
||||
) axi_fifo_cp_length (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear ('0),
|
||||
.i_tdata (in_fifo_o_tuser),
|
||||
.i_tvalid(i_tvalid),
|
||||
.i_tready(),
|
||||
.o_tdata (cp_len_tdata),
|
||||
.o_tvalid(cp_len_tvalid),
|
||||
.o_tready(cp_consume),
|
||||
.space (),
|
||||
.occupied()
|
||||
);
|
||||
|
||||
// Add a register to calculate the cyclic prefix start read address and
|
||||
// figure out if we need to do a cyclic prefix insertion. The latency of
|
||||
// this register will be much less than the FFT write time.
|
||||
always_ff @(posedge clk) begin
|
||||
cp_valid <= cp_len_tvalid;
|
||||
cp_non_zero <= (cp_len_tdata != 0);
|
||||
cp_start_addr <= fft_len - cp_len_tdata;
|
||||
end
|
||||
end else begin : gen_no_cp_ins_fifo
|
||||
assign cp_valid = '0;
|
||||
assign cp_non_zero = '0;
|
||||
assign cp_start_addr = '0;
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Read Logic
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic [1:0] { READ_CHECK, READ_CP, READ_FFT} read_state_t;
|
||||
read_state_t read_state = EN_CP_INSERTION ? READ_CHECK : READ_FFT;
|
||||
read_state_t read_state_nx;
|
||||
|
||||
logic [ADDR_W-1:0] ram_rd_addr_nx;
|
||||
logic ram_rd_buffer_nx;
|
||||
logic ram_rd_last; // Indicates when ram_rd_en asserts for the last sample
|
||||
logic out_fifo_avail;
|
||||
|
||||
// Delayed versions of read signals to align with read output timing
|
||||
logic [READ_LATENCY-1:0] ram_rd_buffer_del;
|
||||
logic [READ_LATENCY-1:0] ram_rd_en_del;
|
||||
logic [READ_LATENCY-1:0] ram_rd_last_del;
|
||||
|
||||
logic [DATA_W-1:0] ram_rd_data;
|
||||
logic ram_rd_data_valid; // Indicates ram_rd_data has data
|
||||
logic ram_rd_data_last; // Indicates ram_rd_data is the last of the FFT
|
||||
|
||||
assign out_fifo_i_tdata = ram_rd_data;
|
||||
assign out_fifo_i_tvalid = ram_rd_data_valid;
|
||||
assign out_fifo_i_tlast = ram_rd_data_last;
|
||||
|
||||
always_ff @(posedge clk) begin : read_fsm_reg
|
||||
if (rst) begin
|
||||
read_state <= EN_CP_INSERTION ? READ_CHECK : READ_FFT;
|
||||
ram_rd_buffer <= '0;
|
||||
ram_rd_addr <= '0;
|
||||
ram_rd_buffer_del <= '0;
|
||||
ram_rd_en_del <= '0;
|
||||
ram_rd_last_del <= '0;
|
||||
ram_rd_data <= 'X;
|
||||
ram_rd_data_valid <= '0;
|
||||
ram_rd_data_last <= '0;
|
||||
out_fifo_avail <= '0;
|
||||
end else begin
|
||||
read_state <= read_state_nx;
|
||||
ram_rd_buffer <= ram_rd_buffer_nx;
|
||||
ram_rd_addr <= ram_rd_addr_nx;
|
||||
|
||||
// Pipeline the buffer selection, enable, and last to align with RAM output
|
||||
ram_rd_buffer_del <= (ram_rd_buffer_del << 1) | ram_rd_buffer;
|
||||
ram_rd_en_del <= (ram_rd_en_del << 1) | ram_rd_en;
|
||||
ram_rd_last_del <= (ram_rd_last_del << 1) | ram_rd_last;
|
||||
|
||||
// Select the RAM output that was used for the read
|
||||
ram_rd_data <= ram_rd_buffer_del[READ_LATENCY-1] ?
|
||||
ram_rd_data_raw_1 : ram_rd_data_raw_0;
|
||||
ram_rd_data_valid <= ram_rd_en_del[READ_LATENCY-1];
|
||||
ram_rd_data_last <= ram_rd_last_del[READ_LATENCY-1];
|
||||
|
||||
// Ensure there's enough room in the output FIFO to account for the
|
||||
// latency through the read logic.
|
||||
out_fifo_avail <= out_fifo_space > 4;
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin : read_fsm_comb
|
||||
ram_rd_en = '0;
|
||||
ram_rd_last = '0;
|
||||
ram_rd_buffer_nx = ram_rd_buffer;
|
||||
ram_rd_addr_nx = ram_rd_addr;
|
||||
read_state_nx = read_state;
|
||||
cp_consume = '0;
|
||||
|
||||
case (read_state)
|
||||
READ_CHECK : begin
|
||||
// Wait until the next cyclic prefix is available and update the RAM
|
||||
// read address appropriately.
|
||||
if (cp_valid) begin
|
||||
cp_consume = '1;
|
||||
if (cp_non_zero) begin
|
||||
read_state_nx = READ_CP;
|
||||
ram_rd_addr_nx = cp_start_addr;
|
||||
end else begin
|
||||
read_state_nx = READ_FFT;
|
||||
ram_rd_addr_nx = '0;
|
||||
end
|
||||
end
|
||||
end
|
||||
READ_CP : begin
|
||||
// Read out the cyclic prefix
|
||||
ram_rd_en = (ok_to_read && out_fifo_avail);
|
||||
if (ram_rd_en) begin
|
||||
if (ram_rd_addr == fft_len_m1) begin
|
||||
ram_rd_addr_nx = '0;
|
||||
read_state_nx = READ_FFT;
|
||||
end else begin
|
||||
ram_rd_addr_nx = ram_rd_addr + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
default : begin // READ_FFT
|
||||
// Read out the whole FFT
|
||||
ram_rd_en = (ok_to_read && out_fifo_avail);
|
||||
if (ram_rd_en) begin
|
||||
if (ram_rd_addr == fft_len_m1) begin
|
||||
ram_rd_last = '1;
|
||||
ram_rd_addr_nx = '0;
|
||||
ram_rd_buffer_nx = ~ram_rd_buffer;
|
||||
read_state_nx = EN_CP_INSERTION ? READ_CHECK : READ_FFT;
|
||||
end else begin
|
||||
ram_rd_addr_nx = ram_rd_addr + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Read/Write Arbitration Logic
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Here we ensure that we only write when the write buffer is free and that
|
||||
// we only read when the read buffer has an FFT in it. Because we're reading
|
||||
// and writing simultaneously, we swap between the lower and upper parts of
|
||||
// the RAM as data gets written and read out.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (ram_wr_en && ram_rd_en) begin
|
||||
if (ram_wr_last && ram_rd_last) begin
|
||||
// Both buffers are switching on the same cycle
|
||||
ok_to_write <= 1'b1;
|
||||
ok_to_read <= 1'b1;
|
||||
end else if (ram_wr_last) begin
|
||||
// Switching write buffer to the one being used for reads
|
||||
ok_to_write <= 1'b0;
|
||||
end else if (ram_rd_last) begin
|
||||
// Switching read buffer to the one being used for writes
|
||||
ok_to_read <= 1'b0;
|
||||
end
|
||||
end else if (ram_wr_en && ram_wr_last) begin
|
||||
// Write buffer is switching
|
||||
if (ram_wr_buffer == ram_rd_buffer) begin
|
||||
// Write buffer is switching away from the current read buffer
|
||||
ok_to_write <= 1'b1;
|
||||
ok_to_read <= 1'b1;
|
||||
end else begin
|
||||
// Write buffer is switching to the current read buffer
|
||||
ok_to_write <= 1'b0;
|
||||
end
|
||||
end else if (ram_rd_en && ram_rd_last) begin
|
||||
// Read buffer is switching
|
||||
if (ram_wr_buffer == ram_rd_buffer) begin
|
||||
// Read buffer is switching away from the current write buffer
|
||||
ok_to_write <= 1'b1;
|
||||
ok_to_read <= 1'b1;
|
||||
end else begin
|
||||
// Read buffer is switching to the current write buffer
|
||||
ok_to_read <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
//synthesis translate_off
|
||||
if (ram_wr_en && ram_rd_en && (ram_wr_buffer == ram_rd_buffer)) begin
|
||||
$error("Attempt to read and write the same buffer!");
|
||||
end
|
||||
//synthesis translate_on
|
||||
|
||||
if (rst) begin
|
||||
ok_to_write <= 1'b1; // Buffers empty after reset
|
||||
ok_to_read <= 1'b0; // Can't read until we fill the first buffer
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
Reference in New Issue
Block a user