Files
Wade Fife af2aeccdc8 fpga: rfnoc: Add Vector IIR RFNoC block
Original-commit: a40f2a4a5d04aad3ef3e222033fbacc521233782
2020-05-19 14:22:55 -05:00

188 lines
7.3 KiB
Verilog

//
// Copyright 2018 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: vector_iir
// Description:
// This module implements an IIR filter with a variable length delay line.
// Transfer Function: beta
// H(z) = ------------------
// 1 - alpha*z^-delay
// where:
// - beta is the feedforward tap
// - alpha is the feedback tap
// - delay (aka vector_len) is the feedback tap delay
//
// Parameters:
// - MAX_VECTOR_LEN: Maximum value for delay (vector_len)
// - IN_W: Input sample width for a real sample which includes the sign bit.
// The actual input of the module will be 2*IN_W because it handles
// complex data.
// - OUT_W: Output sample width for a real sample which includes the sign bit.
// The actual output of the module will be 2*OUT_W because it handles
// complex data.
// - ALPHA_W: Width of the alpha parameter (signed)
// - BETA_W: Width of the beta parameter (signed)
// - FEEDBACK_W: Number of bits in the feedback delay line (optimal = 25)
// - ACCUM_HEADROOM: Number of bits of headroom in the feedback accumulator
// Signals:
// - i_* : Input sample stream (AXI-Stream)
// - o_* : Output sample stream (AXI-Stream)
// - set_*: Static settings
//
module vector_iir #(
parameter MAX_VECTOR_LEN = 1023,
parameter IN_W = 16,
parameter OUT_W = 16,
parameter ALPHA_W = 16,
parameter BETA_W = 16,
parameter FEEDBACK_W = 25,
parameter ACCUM_HEADROOM = 4
)(
input wire clk,
input wire reset,
input wire [$clog2(MAX_VECTOR_LEN+1)-1:0] set_vector_len,
input wire [BETA_W-1:0] set_beta,
input wire [ALPHA_W-1:0] set_alpha,
input wire [IN_W*2-1:0] i_tdata,
input wire i_tlast,
input wire i_tvalid,
output wire i_tready,
output wire [OUT_W*2-1:0] o_tdata,
output wire o_tlast,
output wire o_tvalid,
input wire o_tready
);
// There are four registers between the input and output
// - Input pipeline (in_X_reg)
// - Feedforward product (ff_prod_X_reg)
// - Feedback sum (fb_sum_X_reg)
// - Output pipeline (dsp_data_out)
localparam IN_TO_OUT_LATENCY = 4;
// The feedback path has 3 cycles of delay
// - Feedback sum (fb_sum_X_reg)
// - variable_delay_line (2 cyc)
// - Scaled feedback (fb_sum_scaled_X_reg)
localparam MIN_FB_DELAY = 4;
// Pipeline settings for timing
reg [$clog2(MAX_VECTOR_LEN-MIN_FB_DELAY)-1:0] reg_fb_delay;
reg signed [ BETA_W-1:0] reg_beta;
reg signed [ ALPHA_W-1:0] reg_alpha;
always @(posedge clk) begin
reg_fb_delay <= set_vector_len - MIN_FB_DELAY - 1; //Adjust for pipeline delay
reg_beta <= set_beta;
reg_alpha <= set_alpha;
end
//-----------------------------------------------------------
// AXI-Stream wrapper
//-----------------------------------------------------------
wire [(IN_W*2)-1:0] dsp_data_in;
reg [(OUT_W*2)-1:0] dsp_data_out = 0;
wire [IN_TO_OUT_LATENCY-1:0] chain_en;
// We are implementing an N-cycle DSP operation without AXI-Stream handshaking.
// Use an axis_shift_register and the associated strobes to drive clock enables
// on the DSP regs to ensure that data/valid/last sync up.
axis_shift_register #(
.WIDTH(IN_W*2), .NSPC(1), .LATENCY(IN_TO_OUT_LATENCY),
.SIDEBAND_DATAPATH(1), .PIPELINE("NONE")
) axis_shreg_i (
.clk(clk), .reset(reset),
.s_axis_tdata(i_tdata), .s_axis_tkeep(1'b1), .s_axis_tlast(i_tlast),
.s_axis_tvalid(i_tvalid), .s_axis_tready(i_tready),
.m_axis_tdata(o_tdata), .m_axis_tkeep(), .m_axis_tlast(o_tlast),
.m_axis_tvalid(o_tvalid), .m_axis_tready(o_tready),
.stage_stb(chain_en), .stage_eop(),
.m_sideband_data(dsp_data_in), .m_sideband_keep(),
.s_sideband_data(dsp_data_out)
);
//-----------------------------------------------------------
// DSP datapath
//-----------------------------------------------------------
localparam FF_PROD_W = IN_W + BETA_W - 1;
localparam FB_PROD_W = FEEDBACK_W + ALPHA_W - 1;
reg signed [IN_W-1:0] in_i_reg = 0, in_q_reg = 0;
reg signed [FF_PROD_W-1:0] ff_prod_i_reg = 0, ff_prod_q_reg = 0;
reg signed [FB_PROD_W-1:0] fb_sum_i_reg = 0, fb_sum_q_reg = 0;
wire signed [FB_PROD_W-1:0] fb_trunc_i, fb_trunc_q;
wire signed [FEEDBACK_W-1:0] fb_sum_del_i, fb_sum_del_q;
reg signed [FB_PROD_W-1:0] fb_sum_scaled_i_reg = 0, fb_sum_scaled_q_reg = 0;
wire signed [OUT_W-1:0] out_i_rnd, out_q_rnd;
always @(posedge clk) begin
if (reset) begin
{in_i_reg, in_q_reg} <= 0;
ff_prod_i_reg <= 0;
ff_prod_q_reg <= 0;
fb_sum_i_reg <= 0;
fb_sum_q_reg <= 0;
fb_sum_scaled_i_reg <= 0;
fb_sum_scaled_q_reg <= 0;
dsp_data_out <= 0;
end else begin
if (chain_en[0]) begin
// Input pipeline register
{in_i_reg, in_q_reg} <= dsp_data_in;
end
if (chain_en[1]) begin
// Feedforward product (x[n] * beta)
ff_prod_i_reg <= in_i_reg * reg_beta;
ff_prod_q_reg <= in_q_reg * reg_beta;
// Compute scaled, delayed feedback (y[n-D] * alpha)
fb_sum_scaled_i_reg <= fb_sum_del_i * reg_alpha;
fb_sum_scaled_q_reg <= fb_sum_del_q * reg_alpha;
end
if (chain_en[2]) begin
// Sum of feedforward product and scaled, delayed feedback
// y[n] = (alpha * y[n-D]) + (x[n] * beta)
fb_sum_i_reg <= fb_sum_scaled_i_reg + (ff_prod_i_reg <<< (FB_PROD_W - FF_PROD_W - ACCUM_HEADROOM));
fb_sum_q_reg <= fb_sum_scaled_q_reg + (ff_prod_q_reg <<< (FB_PROD_W - FF_PROD_W - ACCUM_HEADROOM));
end
if (chain_en[3]) begin
// Output pipeline register
dsp_data_out <= {out_i_rnd, out_q_rnd};
end
end
end
// Truncate feedback to the requested FEEDBACK_W
assign fb_trunc_i = (fb_sum_i_reg >>> (FB_PROD_W - FEEDBACK_W));
assign fb_trunc_q = (fb_sum_q_reg >>> (FB_PROD_W - FEEDBACK_W));
// A variable delay line will be used to store the feedback
// This delay line stores "reg_fb_delay" worth of samples which
// allows each element in the vector to have it's own independent state
variable_delay_line #(
.WIDTH(FEEDBACK_W * 2), .DEPTH(MAX_VECTOR_LEN - MIN_FB_DELAY),
.DYNAMIC_DELAY(1), .DEFAULT_DATA(0), .OUT_REG(1)
) delay_line_inst (
.clk(clk), .clk_en(chain_en[1]), .reset(reset),
.stb_in(1'b1),
.data_in({fb_trunc_i[FEEDBACK_W-1:0], fb_trunc_q[FEEDBACK_W-1:0]}),
.delay(reg_fb_delay),
.data_out({fb_sum_del_i, fb_sum_del_q})
);
// Round the accumulator output to produce the final output
round #(
.bits_in(FB_PROD_W-ACCUM_HEADROOM), .bits_out(OUT_W)
) out_round_i_inst (
.in(fb_sum_i_reg[FB_PROD_W-ACCUM_HEADROOM-1:0]), .out(out_i_rnd), .err()
);
round #(
.bits_in(FB_PROD_W-ACCUM_HEADROOM), .bits_out(OUT_W)
) out_round_q_inst (
.in(fb_sum_q_reg[FB_PROD_W-ACCUM_HEADROOM-1:0]), .out(out_q_rnd), .err()
);
endmodule // vector_iir