aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/fft_shift.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/fft_shift.v')
-rw-r--r--fpga/usrp3/lib/rfnoc/fft_shift.v198
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