The tready signal that was used when EN_MAGNITUDE equals 0 was not correct. This commit allows EN_MAGNITUDE to be disabled. Original-commit: ef449523df61268d4efbc86a12cb098c2b2bf239
379 lines
13 KiB
Systemverilog
379 lines
13 KiB
Systemverilog
//
|
|
// 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 mag_in_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
|