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,378 @@
|
||||
//
|
||||
// Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: fft_post_processing
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// This module contains the optional post-processing stages of an FFT,
|
||||
// including FFT output reordering, magnitude, and magnitude-squared
|
||||
// calculations.
|
||||
//
|
||||
// For the magnitude, the result is clipped to a signed 16-bit result in the
|
||||
// range [0, 0x7FFF]. The result is placed in the real part of the sc16
|
||||
// output (the upper 16 bits) and the imaginary part (the lower 16 bits) is
|
||||
// set to 0.
|
||||
//
|
||||
// For the magnitude squared, it computes (i^2 + q^2) / 0x8000, rounding and
|
||||
// clipping the result to a signed 16-bit value in the range [0, 0x7FFF]. The
|
||||
// division helps to avoid saturation and to put the result in a useful
|
||||
// range. The result is placed in the real part of the sc16 output (the upper
|
||||
// 16 bits) and the imaginary part (the lower 16 bits) is set to 0.
|
||||
//
|
||||
// Note that the I and Q may be swapped in the RFNoC transport adapter, so
|
||||
// this order will likely be reversed by the time it makes it back to a host
|
||||
// computer.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// EN_FFT_ORDER : Set to 1 to add the optional FFT reorder core. Set to
|
||||
// 0 to remove it and save resources.
|
||||
// EN_CP_INSERTION : Controls whether to include the cyclic prefix
|
||||
// insertion logic, which is a subset of EN_FFT_ORDER.
|
||||
// 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 : Controls 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.
|
||||
// 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.
|
||||
//
|
||||
// Signals:
|
||||
//
|
||||
// fft_order_sel : 0 - Normal (0 Hz in the center)
|
||||
// 1 - Reverse (same as normal but in reverse)
|
||||
// 2 - Natural (0 Hz on the left)
|
||||
// magnitude_sel : 0 - Normal complex output (No magnitude calculation)
|
||||
// 1 - Magnitude output
|
||||
// 2 - Magnitude-squared output
|
||||
// fft_size_log2 : Log base-2 of the FFT size. That is, the FFT size is
|
||||
// exactly 2**fft_size_log2. The packet size must match.
|
||||
// s_axis_* : AXI-Stream data input. s_axis_tuser contains the cyclic
|
||||
// prefix length and must be valid on the first transfer of
|
||||
// the packet.
|
||||
|
||||
// m_axis_* : AXI-Stream data output
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module fft_post_processing #(
|
||||
bit EN_FFT_ORDER = 1,
|
||||
bit EN_CP_INSERTION = 1,
|
||||
bit EN_MAGNITUDE = 1,
|
||||
bit EN_MAGNITUDE_SQ = 1,
|
||||
bit USE_APPROX_MAG = 1,
|
||||
int MAX_FFT_SIZE_LOG2 = 12,
|
||||
|
||||
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 [1:0] fft_order_sel,
|
||||
input wire [1:0] magnitude_sel,
|
||||
|
||||
input wire [FFT_SIZE_LOG2_W-1:0] fft_size_log2,
|
||||
|
||||
input wire [ 31:0] s_axis_tdata,
|
||||
input wire [CP_LEN_W-1:0] s_axis_tuser,
|
||||
input wire s_axis_tlast,
|
||||
input wire s_axis_tvalid,
|
||||
output wire s_axis_tready,
|
||||
|
||||
output wire [31:0] m_axis_tdata,
|
||||
output wire m_axis_tlast,
|
||||
output wire m_axis_tvalid,
|
||||
input wire m_axis_tready
|
||||
);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// FFT Reorder
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
import fft_reorder_pkg::*;
|
||||
|
||||
wire [31:0] reorder_tdata;
|
||||
wire reorder_tlast;
|
||||
wire reorder_tvalid;
|
||||
wire reorder_tready;
|
||||
|
||||
if (EN_FFT_ORDER) begin : gen_fft_reorder
|
||||
logic [1:0] old_fft_order_sel;
|
||||
logic [FFT_SIZE_LOG2_W-1:0] old_fft_size_log2;
|
||||
logic fft_cfg_wr;
|
||||
|
||||
// Update the FFT config whenever it changes
|
||||
always_ff @(posedge clk) begin
|
||||
fft_cfg_wr <= 0;
|
||||
if (
|
||||
(old_fft_order_sel != fft_order_sel) ||
|
||||
(old_fft_size_log2 != fft_size_log2)
|
||||
) begin
|
||||
fft_cfg_wr <= 1;
|
||||
end
|
||||
old_fft_order_sel <= fft_order_sel;
|
||||
old_fft_size_log2 <= fft_size_log2;
|
||||
end
|
||||
|
||||
fft_reorder #(
|
||||
.INPUT_ORDER (BIT_REVERSE),
|
||||
.MAX_FFT_LEN_LOG2(MAX_FFT_SIZE_LOG2),
|
||||
.DATA_W (32),
|
||||
.EN_CP_INSERTION (EN_CP_INSERTION)
|
||||
) fft_reorder_i (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.fft_cfg_wr (fft_cfg_wr),
|
||||
.fft_len_log2 (fft_size_log2),
|
||||
.fft_out_order(fft_order_t'(fft_order_sel)),
|
||||
.i_tdata (s_axis_tdata),
|
||||
.i_tuser (s_axis_tuser),
|
||||
.i_tlast (s_axis_tlast),
|
||||
.i_tvalid (s_axis_tvalid),
|
||||
.i_tready (s_axis_tready),
|
||||
.o_tdata (reorder_tdata),
|
||||
.o_tlast (reorder_tlast),
|
||||
.o_tvalid (reorder_tvalid),
|
||||
.o_tready (reorder_tready)
|
||||
);
|
||||
end else begin : gen_no_fft_reorder
|
||||
// Pass the data directly through when reordering is disabled.
|
||||
assign reorder_tdata = s_axis_tdata;
|
||||
assign reorder_tlast = s_axis_tlast;
|
||||
assign reorder_tvalid = s_axis_tvalid;
|
||||
assign s_axis_tready = reorder_tready;
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Demultiplex Magnitude Options
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [31:0] mag_bypass_tdata;
|
||||
wire mag_bypass_tlast;
|
||||
wire mag_bypass_tvalid;
|
||||
wire mag_bypass_tready;
|
||||
|
||||
wire [31:0] mag_in_tdata;
|
||||
wire mag_in_tlast;
|
||||
wire mag_in_tvalid;
|
||||
wire mag_in_tready;
|
||||
|
||||
wire [31:0] mag_sq_in_tdata;
|
||||
wire mag_sq_in_tlast;
|
||||
wire mag_sq_in_tvalid;
|
||||
wire mag_sq_in_tready;
|
||||
|
||||
if (EN_MAGNITUDE || EN_MAGNITUDE_SQ) begin : gen_mag_demux
|
||||
axi_demux #(
|
||||
.WIDTH (32),
|
||||
.SIZE (3),
|
||||
.PRE_FIFO_SIZE (0),
|
||||
.POST_FIFO_SIZE(0)
|
||||
) axi_demux_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.header (),
|
||||
.dest (magnitude_sel),
|
||||
.i_tdata (reorder_tdata),
|
||||
.i_tlast (reorder_tlast),
|
||||
.i_tvalid(reorder_tvalid),
|
||||
.i_tready(reorder_tready),
|
||||
.o_tdata ({mag_sq_in_tdata , mag_in_tdata , mag_bypass_tdata }),
|
||||
.o_tlast ({mag_sq_in_tlast , mag_in_tlast , mag_bypass_tlast }),
|
||||
.o_tvalid({mag_sq_in_tvalid, mag_in_tvalid, mag_bypass_tvalid}),
|
||||
.o_tready({mag_sq_in_tready, mag_in_tready, mag_bypass_tready})
|
||||
);
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Magnitude
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [31:0] mag_out_tdata;
|
||||
wire mag_out_tlast;
|
||||
wire mag_out_tvalid;
|
||||
wire mag_out_tready;
|
||||
|
||||
if (EN_MAGNITUDE) begin : gen_magnitude
|
||||
wire [16:0] round_in_tdata;
|
||||
wire [31:0] round_in_tdata_tmp;
|
||||
wire round_in_tlast;
|
||||
wire round_in_tvalid;
|
||||
wire round_in_tready;
|
||||
|
||||
wire [15:0] mag_out_tdata_tmp;
|
||||
|
||||
if (!USE_APPROX_MAG) begin : gen_cordic
|
||||
wire [47:0] m_axis_dout_tdata;
|
||||
// The CORDIC IP below inputs/outputs its data as signed numbers having 2
|
||||
// whole bits and 15 fractional bits (17 total bits). To be compliant
|
||||
// with AXI, each value is stuffed into a 24-bit vector. On the input, we
|
||||
// resize our sc16 inputs to be 24 bits (the upper 7 bits will be ignored
|
||||
// by the IP). On the output side, we only need the magnitude, which is
|
||||
// in the lower 17 bits. The phase, in the upper bits, is left unused.
|
||||
complex_to_magphase_int17 complex_to_magphase_int17_i (
|
||||
.aclk (clk),
|
||||
.aresetn (~rst),
|
||||
.s_axis_cartesian_tvalid(mag_in_tvalid),
|
||||
.s_axis_cartesian_tlast (mag_in_tlast),
|
||||
.s_axis_cartesian_tready(mag_in_tready),
|
||||
.s_axis_cartesian_tdata ({ 24'(signed'(mag_in_tdata[31:16])),
|
||||
24'(signed'(mag_in_tdata[15:0])) }),
|
||||
.m_axis_dout_tvalid (round_in_tvalid),
|
||||
.m_axis_dout_tlast (round_in_tlast),
|
||||
.m_axis_dout_tdata (m_axis_dout_tdata),
|
||||
.m_axis_dout_tready (round_in_tready)
|
||||
);
|
||||
assign round_in_tdata_tmp = 32'(m_axis_dout_tdata[16:0]);
|
||||
end else if (USE_APPROX_MAG) begin : gen_approx
|
||||
complex_to_mag_approx complex_to_mag_approx_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tvalid(mag_in_tvalid),
|
||||
.i_tlast (mag_in_tlast),
|
||||
.i_tready(mag_in_tready),
|
||||
.i_tdata (mag_in_tdata),
|
||||
.o_tvalid(round_in_tvalid),
|
||||
.o_tlast (round_in_tlast),
|
||||
.o_tready(round_in_tready),
|
||||
.o_tdata (round_in_tdata_tmp[15:0])
|
||||
);
|
||||
assign round_in_tdata_tmp[31:16] = '0;
|
||||
end
|
||||
|
||||
// The magnitude is always positive, so we set the MSB to 0 then clip the
|
||||
// result to a signed 16-bit value.
|
||||
assign round_in_tdata = {1'b0, round_in_tdata_tmp[15:0]};
|
||||
|
||||
axi_round_and_clip #(
|
||||
.WIDTH_IN (17),
|
||||
.WIDTH_OUT(16),
|
||||
.CLIP_BITS(1)
|
||||
) axi_round_and_clip_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.i_tdata (round_in_tdata),
|
||||
.i_tlast (round_in_tlast),
|
||||
.i_tvalid(round_in_tvalid),
|
||||
.i_tready(round_in_tready),
|
||||
.o_tdata (mag_out_tdata_tmp),
|
||||
.o_tlast (mag_out_tlast),
|
||||
.o_tvalid(mag_out_tvalid),
|
||||
.o_tready(mag_out_tready)
|
||||
);
|
||||
|
||||
// Put the resulting magnitude in the "real" part of the output
|
||||
assign mag_out_tdata = {mag_out_tdata_tmp, 16'd0};
|
||||
|
||||
end else begin : gen_no_magnitude
|
||||
assign mag_out_tdata = '0;
|
||||
assign mag_out_tlast = '0;
|
||||
assign mag_out_tvalid = '0;
|
||||
assign s_axis_tready = '1;
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Magnitude Squared
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [31:0] mag_sq_out_tdata;
|
||||
wire mag_sq_out_tlast;
|
||||
wire mag_sq_out_tvalid;
|
||||
wire mag_sq_out_tready;
|
||||
|
||||
if (EN_MAGNITUDE_SQ) begin : gen_magnitude_squared
|
||||
wire [31:0] round_in_tdata;
|
||||
wire round_in_tlast;
|
||||
wire round_in_tvalid;
|
||||
wire round_in_tready;
|
||||
|
||||
wire [15:0] mag_sq_out_tdata_tmp;
|
||||
|
||||
complex_to_magsq complex_to_magsq_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tvalid(mag_sq_in_tvalid),
|
||||
.i_tlast (mag_sq_in_tlast),
|
||||
.i_tready(mag_sq_in_tready),
|
||||
.i_tdata (mag_sq_in_tdata),
|
||||
.o_tvalid(round_in_tvalid),
|
||||
.o_tlast (round_in_tlast),
|
||||
.o_tready(round_in_tready),
|
||||
.o_tdata (round_in_tdata)
|
||||
);
|
||||
|
||||
axi_round_and_clip #(
|
||||
.WIDTH_IN (32),
|
||||
.WIDTH_OUT(16),
|
||||
.CLIP_BITS(1)
|
||||
) axi_round_and_clip_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.i_tdata (round_in_tdata),
|
||||
.i_tlast (round_in_tlast),
|
||||
.i_tvalid(round_in_tvalid),
|
||||
.i_tready(round_in_tready),
|
||||
.o_tdata (mag_sq_out_tdata_tmp),
|
||||
.o_tlast (mag_sq_out_tlast),
|
||||
.o_tvalid(mag_sq_out_tvalid),
|
||||
.o_tready(mag_sq_out_tready)
|
||||
);
|
||||
|
||||
assign mag_sq_out_tdata = {mag_sq_out_tdata_tmp, 16'd0};
|
||||
|
||||
end else begin : gen_no_magnitude_squared
|
||||
assign mag_sq_out_tdata = '0;
|
||||
assign mag_sq_out_tlast = '0;
|
||||
assign mag_sq_out_tvalid = '0;
|
||||
assign mag_sq_in_tready = '1;
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Combine Magnitude Options
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (EN_MAGNITUDE || EN_MAGNITUDE_SQ) begin : gen_mag_mux
|
||||
axi_mux #(
|
||||
.PRIO (1),
|
||||
.WIDTH (32),
|
||||
.SIZE (3),
|
||||
.PRE_FIFO_SIZE (0),
|
||||
.POST_FIFO_SIZE(0)
|
||||
) axi_demux_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata ({mag_sq_out_tdata , mag_out_tdata , mag_bypass_tdata }),
|
||||
.i_tlast ({mag_sq_out_tlast , mag_out_tlast , mag_bypass_tlast }),
|
||||
.i_tvalid({mag_sq_out_tvalid, mag_out_tvalid, mag_bypass_tvalid}),
|
||||
.i_tready({mag_sq_out_tready, mag_out_tready, mag_bypass_tready}),
|
||||
.o_tdata (m_axis_tdata),
|
||||
.o_tlast (m_axis_tlast),
|
||||
.o_tvalid(m_axis_tvalid),
|
||||
.o_tready(m_axis_tready)
|
||||
);
|
||||
end else begin : gen_no_mag_mux
|
||||
assign m_axis_tdata = reorder_tdata;
|
||||
assign m_axis_tlast = reorder_tlast;
|
||||
assign m_axis_tvalid = reorder_tvalid;
|
||||
assign reorder_tready = m_axis_tready;
|
||||
end
|
||||
|
||||
|
||||
endmodule : fft_post_processing
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
Reference in New Issue
Block a user