diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/fft_shift.v')
-rw-r--r-- | fpga/usrp3/lib/rfnoc/fft_shift.v | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/fft_shift.v b/fpga/usrp3/lib/rfnoc/fft_shift.v new file mode 100644 index 000000000..453da8050 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/fft_shift.v @@ -0,0 +1,198 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Arranges FFT output AXI stream packets so zero frequency bin is centered. Expects i_tuser to have FFT index. +// Intended to complement Xilinx Coregen AXI-stream FFT, but should work with any core with similar output. +// Works with natural and bit/digit reversed order. +// +// When using Xilinx FFT core, use bit/digit reversed order (versus natural order) to save resources +// +// Config bits: +// 0: Reverse output so positive frequencies are sent first +// 1: Bypass fft shift + +module fft_shift #( + parameter MAX_FFT_SIZE_LOG2 = 11, + parameter WIDTH = 32) +( + input clk, input reset, + input [1:0] config_tdata, input config_tvalid, output config_tready, + input [$clog2(MAX_FFT_SIZE_LOG2+1)-1:0] fft_size_log2_tdata, input fft_size_log2_tvalid, output fft_size_log2_tready, + input [WIDTH-1:0] i_tdata, input i_tlast, input i_tvalid, output i_tready, input [MAX_FFT_SIZE_LOG2-1:0] i_tuser, + output [WIDTH-1:0] o_tdata, output o_tlast, output o_tvalid, input o_tready +); + + reg ping_pong; + reg loading_pkt; + reg [2:0] reconfig_stall; + reg reverse, bypass; + reg [$clog2(MAX_FFT_SIZE_LOG2+1)-1:0] fft_size_log2_reg; + reg [MAX_FFT_SIZE_LOG2:0] fft_size; + reg [MAX_FFT_SIZE_LOG2-1:0] fft_size_minus_1, fft_shift_mask; + wire [WIDTH-1:0] ping_rd_data, pong_rd_data; + reg [MAX_FFT_SIZE_LOG2-1:0] ping_rd_addr, pong_rd_addr; + // t_user is the FFT index, this XOR is how the natural order FFT output is flipped to + // center the zero frequency bin in the middle. This is essentially adding half the FFT length to + // the write address without carrying, causing the upper half addresses to wrap around to the lower half + // and vice versa. + wire [MAX_FFT_SIZE_LOG2-1:0] ping_wr_addr = fft_shift_mask ^ i_tuser; + wire [MAX_FFT_SIZE_LOG2-1:0] pong_wr_addr = fft_shift_mask ^ i_tuser; + wire ping_wr_en = ping_pong ? i_tvalid & i_tready : 1'b0; + wire pong_wr_en = ping_pong ? 1'b0 : i_tvalid & i_tready; + // Always reads when loading ping/pong RAM so first word falls through. Avoids a bubble state. + wire ping_rd_en = ping_pong ? 1'b1 : o_tvalid & o_tready; + wire pong_rd_en = ping_pong ? o_tvalid & o_tready : 1'b1; + reg ping_loaded, pong_loaded; + // Only fill ping (or pong) RAM if it is empty and fft size has propagated through + assign i_tready = (ping_pong ? ~ping_loaded : ~pong_loaded) & ~reconfig_stall[2]; + reg ping_tlast, pong_tlast; + // Dump data in ping RAM (but only if it has been loaded!) while also loading in pong RAM and vice versa + assign o_tvalid = ping_pong ? pong_loaded : ping_loaded; + assign o_tlast = ping_pong ? pong_tlast : ping_tlast; + assign o_tdata = ping_pong ? pong_rd_data : ping_rd_data; + + // Prevent reconfiguration from occurring except at valid times. If the user violates tvalid rules + // (i.e. deasserts tvalid during the middle of a packet), could cause next output packet to have + // the wrong size. + assign config_tready = ~ping_loaded & ~pong_loaded & ~loading_pkt; + assign fft_size_log2_tready = config_tready; + + ram_2port #( + .DWIDTH(WIDTH), + .AWIDTH(MAX_FFT_SIZE_LOG2)) + ping_ram_2port ( + .clka(clk),.ena(1'b1),.wea(ping_wr_en),.addra(ping_wr_addr),.dia(i_tdata),.doa(), + .clkb(clk),.enb(ping_rd_en),.web(1'b0),.addrb(ping_rd_addr),.dib({WIDTH{1'b0}}),.dob(ping_rd_data)); + + ram_2port #( + .DWIDTH(WIDTH), + .AWIDTH(MAX_FFT_SIZE_LOG2)) + pong_ram_2port ( + .clka(clk),.ena(1'b1),.wea(pong_wr_en),.addra(pong_wr_addr),.dia(i_tdata),.doa(), + .clkb(clk),.enb(pong_rd_en),.web(1'b0),.addrb(pong_rd_addr),.dib({WIDTH{1'b0}}),.dob(pong_rd_data)); + + always @(posedge clk) begin + if (reset) begin + ping_pong <= 1'b1; + ping_loaded <= 1'b0; + pong_loaded <= 1'b0; + ping_rd_addr <= 0; + pong_rd_addr <= 0; + ping_tlast <= 1'b0; + pong_tlast <= 1'b0; + fft_shift_mask <= 0; + fft_size_minus_1 <= 0; + fft_size <= 0; + fft_size_log2_reg <= 0; + bypass <= 1'b0; + reverse <= 1'b0; + reconfig_stall <= 3'd0; + loading_pkt <= 1'b0; + end else begin + fft_size_minus_1 <= fft_size-1; + fft_size <= 1 << fft_size_log2_reg; + // Configure FFT shift mask such that the output order is either + // unaffected (bypass), positive frequencies first (reverse), or + // negative frequencies first + if (bypass) begin + fft_shift_mask <= 'd0; + end else if (reverse) begin + fft_shift_mask <= (fft_size-1) >> 1; + end else begin + fft_shift_mask <= fft_size >> 1; + end + + // Restrict updating + if (config_tready & config_tvalid) begin + reverse <= config_tdata[0]; + bypass <= config_tdata[1]; + reconfig_stall <= 3'b100; + end + // Restrict updating FFT size to valid times + // Also, deassert i_tready until updated fft size has propagated through + if (fft_size_log2_tready & fft_size_log2_tvalid) begin + fft_size_log2_reg <= fft_size_log2_tdata[$clog2(MAX_FFT_SIZE_LOG2)-1:0]; + reconfig_stall <= 3'b111; + end + if (~(config_tready & config_tvalid) & ~(fft_size_log2_tready & fft_size_log2_tvalid)) begin + reconfig_stall[0] <= 1'b0; + reconfig_stall[2:1] <= reconfig_stall[1:0]; + end + + // Used to disable reconfiguration when we are receiving a packet + if (i_tvalid & i_tready & ~i_tlast & ~loading_pkt) begin + loading_pkt <= 1'b1; + end else if (i_tvalid & i_tready & i_tlast & loading_pkt) begin + loading_pkt <= 1'b0; + end + + // Logic to simultaneously load ping RAM and unload pong RAM. Note, write address for ping RAM handled with i_tuser, + // so we only look for i_tlast instead of maintaining a write address counter. + if (ping_pong) begin + // Unload pong RAM + if (pong_loaded & o_tready & o_tvalid) begin + // i.e. pong_rd_addr == fft_size-1, more efficient to use tlast + if (pong_tlast) begin + // Special case: ping RAM loaded before pong RAM emptied + if (ping_loaded | (i_tvalid & i_tready & i_tlast)) begin + ping_pong <= ~ping_pong; + end + pong_tlast <= 1'b0; + pong_loaded <= 1'b0; + pong_rd_addr <= 0; + end else begin + pong_rd_addr <= pong_rd_addr + 1; + end + if (pong_rd_addr == fft_size_minus_1) begin + pong_tlast <= 1'b1; + end + end + // Ping RAM done loading + if (i_tvalid & i_tready & i_tlast) begin + // Value at addr 0 already loaded (see first word fall through and avoiding a bubble state comment above) + ping_rd_addr <= 1; + ping_loaded <= 1'b1; + // We can switch to the pong RAM only if it is empty (or about to be empty) + if (~pong_loaded) begin + ping_pong <= ~ping_pong; + end + end + // Special case: Ping and pong RAM loaded, wait until pong RAM unloaded. + if (ping_loaded & (pong_loaded & o_tvalid & o_tlast)) begin + ping_pong <= ~ping_pong; + end + // Same as above, just ping / pong switched + end else begin + if (ping_loaded & o_tready & o_tvalid) begin + if (ping_tlast) begin + if (pong_loaded | (i_tvalid & i_tready & i_tlast)) begin + ping_pong <= ~ping_pong; + end + ping_tlast <= 1'b0; + ping_loaded <= 1'b0; + ping_rd_addr <= 0; + end else begin + ping_rd_addr <= ping_rd_addr + 1; + end + if (ping_rd_addr == fft_size_minus_1) begin + ping_tlast <= 1'b1; + end + end + if (i_tvalid & i_tready & i_tlast) begin + pong_rd_addr <= 1; + pong_loaded <= 1'b1; + if (~ping_loaded | (ping_loaded & o_tvalid & o_tlast)) begin + ping_pong <= ~ping_pong; + end + end + if (pong_loaded & (ping_loaded & o_tvalid & o_tlast)) begin + ping_pong <= ~ping_pong; + end + end + end + end + +endmodule |