dsp: cic: Add FIFO in CIC interpolation to allow tready bypass

This commit is contained in:
Max Köhler 2026-09-02 12:32:28 +02:00 committed by mkoop-ni
parent 5ea031a946
commit d394ae626a
7 changed files with 154 additions and 37 deletions

View File

@ -33,10 +33,6 @@
// Parameters:
//
// SPC : Number of samples per clock cycle (power of 2, >= 1).
// OUT_WIDTH : Output sample width in bits (I + Q packed, each OUT_WIDTH/2 bits).
// data_out.tdata must be SPC * OUT_WIDTH bits wide.
// IN_WIDTH : Input sample width in bits (I + Q packed, each IN_WIDTH/2 bits).
// data_in.tdata must be SPC * IN_WIDTH bits wide.
// MAX_RATE : Maximum rate factor (decimation or interpolation ratio).
// Sizes the compile-time shift lookup table.
// ORDER : CIC filter order (number of integrator/comb stages).
@ -57,8 +53,6 @@
module cic_barrel_shift #(
parameter int SPC = 4,
parameter int OUT_WIDTH = 32,
parameter int IN_WIDTH = 128,
parameter int MAX_RATE = 255,
parameter int ORDER = 4
)(
@ -72,9 +66,11 @@ module cic_barrel_shift #(
// Data out: SPC samples, each OUT_WIDTH bits wide ({I, Q} each OUT_WIDTH/2 bits)
AxiStreamIf.master data_out
);
import cic_utils_pkg::*;
localparam int MAX_SHIFT = IN_WIDTH/2 - OUT_WIDTH/2;
localparam int SHIFT_W = $clog2(MAX_SHIFT + 1);
localparam int IN_WIDTH = data_in.DATA_WIDTH / SPC;
localparam int OUT_WIDTH = data_out.DATA_WIDTH / SPC;
localparam int SHIFT_W = barrel_shift_width(IN_WIDTH, OUT_WIDTH);
localparam int IN_COMP_W = IN_WIDTH/2;
localparam int OUT_COMP_W = OUT_WIDTH/2;
// One extra LSB so the bit dropped by the shift is available for rounding.

View File

@ -217,8 +217,6 @@ module cic_filter_decim #(
// Barrel-shift each I/Q component and extract SAMP_W/2 output bits.
cic_barrel_shift #(
.SPC (SPC),
.IN_WIDTH (ACCUM_W),
.OUT_WIDTH (SAMP_W),
.MAX_RATE (MAX_DECIM),
.ORDER (ORDER)
) cic_barrel_shift_i (

View File

@ -25,21 +25,31 @@
// │ └───────────┬────────────┘ │
// └──────────────┼────────────────┘
// │
// ┌────────────────────────┐
// interp_factor─┤ Interpolator (×R) │
// └───────────┬────────────┘
// │
// ┌──────────────┼────────────────┐
// │ ┌────────────────────────┐ │
// │ │ Integrator 0 │ │
// │ └───────────┬────────────┘ │
// │ │ │ ORDER stages
// │ ··· │ (full output rate)
// │ │ │
// │ ┌────────────────────────┐ │
// │ │ Integrator ORDER-1 │ │
// │ └───────────┬────────────┘ │
// └──────────────┼────────────────┘
// ┌────────────────────────┐ tready
// interp_factor─┤ Interpolator (×R) │<──────────────┐
// └───────────┬────────────┘ │
// │ │
// ┌──────────────┼────────────────┐ │
// │ ┌────────────────────────┐ │ │
// │ │ Integrator 0 │ │ │
// │ └────────────────────────┘ │ │
// │ │ │ ORDER │
// │ ··· │ stages │
// │ │ │ │
// │ ┌────────────────────────┐ │ │
// │ │ Integrator ORDER-1 │ │ │
// │ └────────────────────────┘ │ │
// └──────────────┼────────────────┘ │
// │ │
// v │
// ┌───────────────────────────────┐ │
// │ Barrel shift │ │
// └──────────────┬────────────────┘ │
// │ │
// v │
// ┌───────────────────────────────┐ │
// │ Output FIFO │───────────┘
// └───────────────────────────────┘
// │
// v
// data_out
@ -99,6 +109,8 @@ module cic_filter_interp #(
AxiStreamIf.slave data_in,
AxiStreamIf.master data_out
);
`include "../axi4s_sv/axi4s.vh"
//--------------------------------------------------------------------------
// Local parameters
//--------------------------------------------------------------------------
@ -124,6 +136,7 @@ module cic_filter_interp #(
// The interpolator operates at COMB_W bits. Its output is sign-extended to
// ACCUM_W bits before entering the integrator chain.
AxiStreamIf #(SPC * COMB_W) interp_bus (.clk(clk), .rst(rst));
AxiStreamIf #(SPC * ACCUM_W) interp_bus_extended (.clk(clk), .rst(rst));
// Integrator path: two buses connecting the interpolator to the integrator
// chain and carrying the chain output.
@ -131,6 +144,10 @@ module cic_filter_interp #(
// integ_bus[1] carries the output of the ORDER-stage integrator chain.
AxiStreamIf #(SPC * ACCUM_W) integ_bus [2] (.clk(clk), .rst(rst));
// Barrel Shift output bus
AxiStreamIf #(data_out.DATA_WIDTH) barrel_shift_out (.clk(clk), .rst(rst));
AxiStreamIf #(data_out.DATA_WIDTH) output_fifo_in (.clk(clk), .rst(rst));
//---------------------------------------------------------------------------
// Register configuration parameters
//---------------------------------------------------------------------------
@ -223,9 +240,9 @@ module cic_filter_interp #(
// stages require ACCUM_W bits to accommodate their accumulated growth.
//--------------------------------------------------------------------------
assign integ_bus[0].tvalid = interp_bus.tvalid;
assign integ_bus[0].tlast = interp_bus.tlast;
assign interp_bus.tready = integ_bus[0].tready;
assign interp_bus_extended.tvalid = interp_bus.tvalid;
assign interp_bus_extended.tlast = interp_bus.tlast;
assign interp_bus.tready = interp_bus_extended.tready;
for (genvar samp_idx = 0; samp_idx < SPC; samp_idx++) begin : gen_interp_ext
sign_extend #(
@ -233,14 +250,14 @@ module cic_filter_interp #(
.bits_out(ACCUM_W/2)
) sign_ext_q (
.in (interp_bus.tdata[COMB_W*samp_idx +: COMB_W/2]),
.out(integ_bus[0].tdata[ACCUM_W*samp_idx +: ACCUM_W/2])
.out(interp_bus_extended.tdata[ACCUM_W*samp_idx +: ACCUM_W/2])
);
sign_extend #(
.bits_in (COMB_W/2),
.bits_out(ACCUM_W/2)
) sign_ext_i (
.in (interp_bus.tdata[COMB_W*samp_idx + COMB_W/2 +: COMB_W/2]),
.out(integ_bus[0].tdata[ACCUM_W*samp_idx + ACCUM_W/2 +: ACCUM_W/2])
.out(interp_bus_extended.tdata[ACCUM_W*samp_idx + ACCUM_W/2 +: ACCUM_W/2])
);
end : gen_interp_ext
@ -248,6 +265,32 @@ module cic_filter_interp #(
//---------------------------------------------------------------------------
// Integrator chain: ORDER stages at full output rate
//---------------------------------------------------------------------------
// Starting here until the output FIFO the data flow is controlled by the
// output FIFO backpressure.
logic fifo_ready;
logic input_blocked;
// Align input locking with AXI transfer.
always_ff @(posedge clk) begin
if (rst || clear) begin
input_blocked <= 1'b0;
end else begin
if (fifo_ready) begin
input_blocked <= 1'b0;
end else if (interp_bus_extended.tvalid && interp_bus_extended.tready) begin
input_blocked <= 1'b1;
end
end
end
always_comb begin
`AXI4S_ASSIGN(integ_bus[0], interp_bus_extended)
// overwrite handshake signal to block input when FIFO is full
interp_bus_extended.tready = integ_bus[0].tready & ~input_blocked;
integ_bus[0].tvalid = interp_bus_extended.tvalid & ~input_blocked;
end
cic_integrator #(
.SAMP_W(ACCUM_W),
.SPC (SPC),
@ -264,8 +307,6 @@ module cic_filter_interp #(
// Barrel-shift each I/Q component and extract SAMP_W/2 output bits.
cic_barrel_shift #(
.SPC (SPC),
.IN_WIDTH (ACCUM_W),
.OUT_WIDTH (SAMP_W),
.MAX_RATE (MAX_INTERP),
.ORDER (ORDER-1) // An Nth order CIC interpolation filter has a Gain of
// R^(N-1) at DC, so we shift by ceil(log2(R^(N-1))) bits.
@ -275,7 +316,63 @@ module cic_filter_interp #(
.clear (clear),
.rate_factor (interp_factor_reg),
.data_in (integ_bus[1]),
.data_out (data_out)
.data_out (barrel_shift_out)
);
//---------------------------------------------------------------------------
// Output FIFO to absorb latency of bypassed modules
//---------------------------------------------------------------------------
import cic_utils_pkg::*;
localparam int LATENCY = integrator_latency(SPC, ORDER)
+ barrel_shift_latency(ACCUM_W, SAMP_W)
+ 1 // fifo_ready register
+ 1 // input_blocked register
+ 1; // output FIFO output register offset for space calculation
// assign the barrel shift output to the FIFO input, and always ready to
// accept data. The FIFO will backpressure the integrator chain when it is full.
always_comb begin
`AXI4S_ASSIGN(output_fifo_in, barrel_shift_out)
barrel_shift_out.tready = 1'b1;
end
// Allow the integrator chain to continue processing as long as the FIFO has
// enough space for the entire latency of the bypassed modules.
logic [15:0] fifo_space;
always_ff @(posedge clk) begin
if (rst || clear) begin
fifo_ready <= 1'b0;
end else begin
fifo_ready <= (fifo_space > LATENCY);
end
end
// FIFO depth is the next power of two at or above twice the calculated
// latency, ensuring sufficient buffering for constant data flow downstream
// after stalls.
localparam int FIFO_WIDTH = data_out.DATA_WIDTH + 1;
logic [FIFO_WIDTH-1:0] fifo_i_tdata;
logic [FIFO_WIDTH-1:0] fifo_o_tdata;
assign fifo_i_tdata = {output_fifo_in.tlast, output_fifo_in.tdata};
assign {data_out.tlast, data_out.tdata} = fifo_o_tdata;
axi_fifo #(
.WIDTH(FIFO_WIDTH),
.SIZE ($clog2(2*LATENCY))
) output_fifo (
.clk (clk),
.reset (rst | clear),
.clear (1'b0),
.i_tdata (fifo_i_tdata),
.i_tvalid (output_fifo_in.tvalid),
.i_tready (output_fifo_in.tready),
.o_tdata (fifo_o_tdata),
.o_tvalid (data_out.tvalid),
.o_tready (data_out.tready),
.space (fifo_space),
.occupied ()
);
endmodule : cic_filter_interp

View File

@ -70,6 +70,8 @@ module cic_integrator #(
AxiStreamIf.slave data_in,
AxiStreamIf.master data_out
);
import cic_utils_pkg::*;
// compile time check
if (SAMP_W % 2 != 0) begin
$error("cic_integrator: SAMP_W must be a multiple of 2");
@ -82,7 +84,7 @@ module cic_integrator #(
// final prefix_accum : 2 cycles
// de-staircase : SPC-1 cycles
// Total = ORDER*(SPC+1) + SPC + 1 cycles.
localparam int CIC_LATENCY = ORDER * (SPC + 2-1) + SPC + 1;
localparam int CIC_LATENCY = integrator_dsp_latency(SPC, ORDER);
// OUT_FIFO_SIZE: The pipeline is gated by fifo_in_ready so it stalls in
// lock-step with data_in.tready; the FIFO only needs to absorb the one
// word in transit between the pipeline output register and the FIFO input.

View File

@ -308,6 +308,8 @@ module cic_interpolator #(
//---------------------------------------------------------------------------
// Output Register
//---------------------------------------------------------------------------
logic [SPC*SAMP_W:0] fifo_out;
axi_fifo #(
.WIDTH (SPC*SAMP_W + 1 ),
.SIZE (1 )
@ -318,13 +320,15 @@ module cic_interpolator #(
.i_tdata ({tlast_pipe[1], gen_tdata}),
.i_tvalid (tvalid_pipe[1] ),
.i_tready (gen_tready ),
.o_tdata ({data_out.tlast, data_out.tdata}),
.o_tdata (fifo_out ),
.o_tvalid (data_out.tvalid ),
.o_tready (data_out.tready ),
.space ( ),
.occupied ( )
);
assign {data_out.tlast, data_out.tdata} = fifo_out;
endmodule : cic_interpolator
`default_nettype wire

View File

@ -96,8 +96,6 @@ module cic_barrel_shift_tb #(
cic_barrel_shift #(
.SPC (SPC),
.OUT_WIDTH(OUT_WIDTH),
.IN_WIDTH (IN_WIDTH),
.MAX_RATE (MAX_RATE),
.ORDER (ORDER)
) dut (

View File

@ -12,6 +12,28 @@
package cic_utils_pkg;
function automatic int barrel_shift_width(
input int in_width,
input int out_width
);
return $clog2(in_width/2 - out_width/2 + 1);
endfunction
// The latency of the barrel shift stage is the number of stages in the barrel
// shifter plus one cycle for the output register.
function automatic int barrel_shift_latency(input int in_width, input int out_width);
return barrel_shift_width(in_width, out_width) + 1;
endfunction
function automatic int integrator_dsp_latency(input int spc, input int order);
return order * (spc + 1) + spc + 1;
endfunction
// The latency of the integrator is the DSP latency plus one cycle for the output register.
function automatic int integrator_latency(input int spc, input int order);
return integrator_dsp_latency(spc, order) + 1;
endfunction
class cic_utils #(
parameter int SPC = 8, // Number of samples processed per clock cycle
parameter int SAMP_W = 48 // Input/output data width