fpga: lib: optimizations for zero/symmetric coefficients for static FIR

Optimizations aim to reduce number of DSP slices:
For static FIR filter generation (RELOADABLE_COEFFS = 0):
- skip fir_filter_slice instantiation for zero coefficient FIR taps.
  Positiion of zero taps derived from COEFFS_VEC.
  (cherry pick from Max prototype)
- use fir_filter_slice preadder input to jointly process symmetric
  FIR filter taps. Saves almost half the number of fir_filter_slices.
  Symmetry automatically inferred from COEFFS_VEC.

TB Changes:
TB has been extended to validate optimizations for zero/symmetric
coefficients depending on the provided filter coefficients.
- axi_fir_multisample_filter_tb.sv: changed interface to pass
  FIR filter coefficients from outside.
- axi_fir_multisample_filter_tb_wrapper.sv: splitted into
  separate generate loop to test general behavior and specific
  optimizations
This commit is contained in:
Andre Zoch 2026-01-27 12:55:02 +01:00 committed by Jörg Hofrichter
parent 125e3284c3
commit 734f260534
3 changed files with 332 additions and 93 deletions

View File

@ -31,10 +31,15 @@
// BLANK_OUTPUT - Disable (1) or enable (0) output when initially filling internal pipeline
// USE_EMBEDDED_REGS_COEFFS - Reduce register usage by only using embedded registers in DSP slices.
// Updating taps while streaming will cause temporary output corruption!
//
// Notes:
// - If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at least once as COEFFS_VEC is ignored!
// - If using RELOADABLE_COEFFS, coefficients must be written in reverse order!
// - Optimizations: For static coefficients (RELOADABLE_COEFFS = 0), zero and symmetric coefficient
// optimizations are automatically enabled to reduce DSP slice utilization.
// Symmetric coefficients save ~50% of DSP slices by reusing computations.
// Zero coefficients are skipped, saving DSP slices proportionally
// (e.g., halfband filters save ~50%).
module axi_fir_multisample_filter #(
int IN_WIDTH = 16,
@ -44,8 +49,7 @@ module axi_fir_multisample_filter #(
int NUM_COEFFS = 41,
int CLIP_BITS = $clog2(NUM_COEFFS),
int ACCUM_WIDTH = IN_WIDTH+COEFF_WIDTH+$clog2(NUM_COEFFS)-1,
bit [NUM_COEFFS*COEFF_WIDTH-1:0] COEFFS_VEC =
{{1'b0,{(COEFF_WIDTH-1){1'b1}}},{(COEFF_WIDTH*(NUM_COEFFS-1)){1'b0}}},
bit [COEFF_WIDTH-1:0] COEFFS_VEC [NUM_COEFFS] = '{0: (1 << (COEFF_WIDTH-1)) - 1, default: 0},
bit RELOADABLE_COEFFS = 1,
bit BLANK_OUTPUT = 1,
bit USE_EMBEDDED_REGS_COEFFS = 1
@ -74,7 +78,42 @@ module axi_fir_multisample_filter #(
output logic s_axis_reload_tready
);
localparam int PIPELINE_DELAY = NUM_COEFFS + 5; // +4 pipeline depth in fir_filter_slice.v, +1 of shift register
// define function to calculate the number of zero coefficients up to the
// given index
function int count_zeros_up_to_index (
input int index
);
int count;
begin
count = 0;
for (int i = 0; i < index; i = i + 1) begin
if (COEFFS_VEC[i] == '0) begin
count = count + 1;
end
end
return count;
end
endfunction
// function to check coefficient symmetry
function bit check_coeffs_symmetric();
begin
check_coeffs_symmetric = 1'b1;
for (int i = 0; i < NUM_COEFFS/2; i = i + 1) begin
if (COEFFS_VEC[i] != COEFFS_VEC[NUM_COEFFS-1-i]) begin
check_coeffs_symmetric = 1'b0;
end
end
return check_coeffs_symmetric;
end
endfunction
localparam bit OPTIMIZE_FOR_ZERO_COEFFS = (RELOADABLE_COEFFS == 0);
localparam bit OPTIMIZE_FOR_SYMMETRIC_COEFFS = (RELOADABLE_COEFFS == 0)
? check_coeffs_symmetric() : 0;
localparam int NUM_SLICES = OPTIMIZE_FOR_SYMMETRIC_COEFFS
? NUM_COEFFS/2 + NUM_COEFFS[0] : NUM_COEFFS;
localparam int PIPELINE_DELAY = NUM_SLICES + 5 // +4 pipeline depth in fir_filter_slice.v, +1 of shift register
- (OPTIMIZE_FOR_ZERO_COEFFS ? count_zeros_up_to_index(NUM_SLICES) : 0);
localparam int SHIFT_REG_WIDTH = (NUM_SPC + 1) * NUM_COEFFS; // length of shift register
logic [ACCUM_WIDTH-1:0] m_axis_data_tdata_int [NUM_SPC-1:0];
@ -105,7 +144,7 @@ module axi_fir_multisample_filter #(
always @(posedge clk) begin
if (reset | clear) begin
for (int k = 0; k < NUM_COEFFS; k = k + 1) begin
coeffs[k] <= COEFFS_VEC[COEFF_WIDTH*k +: COEFF_WIDTH];
coeffs[k] <= COEFFS_VEC[k];
end
// Initialize coefficients at reset
coeff_load_stb <= 1'b1;
@ -125,7 +164,7 @@ module axi_fir_multisample_filter #(
// Coefficients are static
initial begin
for (int k = NUM_COEFFS-1; k >= 0; k = k - 1) begin
coeffs[k] <= COEFFS_VEC[COEFF_WIDTH*k +: COEFF_WIDTH];
coeffs[k] <= COEFFS_VEC[k];
coeff_load_stb <= 1'b1;
end
end
@ -201,39 +240,64 @@ module axi_fir_multisample_filter #(
// K_FLIPPED: refer to the documentation above of data_shift_reg
localparam int K_FLIPPED = NUM_SPC-k-1;
wire [ACCUM_WIDTH-1:0] sample_accum [0 : NUM_COEFFS]; // [0:NUM_COEFFS] to make the
wire [COEFF_WIDTH-1:0] coeff_forward [0 : NUM_COEFFS]; // generate loop easier to read
logic [ACCUM_WIDTH-1:0] sample_accum [0 : NUM_SLICES]; // [0:NUM_COEFFS] to make the
logic [COEFF_WIDTH-1:0] coeff_forward [0 : NUM_SLICES]; // generate loop easier to read
assign sample_accum[0] = 0;
assign coeff_forward[0] = s_axis_reload_tdata;
// Build up FIR filter with multiply-accumulate slices (fir_filter_slice).
// Now generate the slices for each chain
for (genvar j = 0; j < NUM_COEFFS ; j = j + 1) begin : gen_slice
fir_filter_slice #(
.IN_WIDTH(IN_WIDTH),
.COEFF_WIDTH(COEFF_WIDTH),
.ACCUM_WIDTH(ACCUM_WIDTH),
.OUT_WIDTH(ACCUM_WIDTH))
fir_filter_slice (
.clk(clk),
.reset(reset),
.clear(clear),
.sample_in_stb(s_axis_data_tvalid & s_axis_data_tready),
// NUM_SPC is added j times due to the pipeline delay.
// +1 for selecting the next index for the FIR result calculation
// K_FLIPPED is the offset into data_shift_reg
.sample_in_a(data_shift_reg[ j*(NUM_SPC+1) + K_FLIPPED ]),
// sample_in_b is used to implement symmetric coefficients, always 0 if SYMMETRIC_COEFFS = 0
.sample_in_b('0), // symmetric disabled, thus empty
.sample_forward(),
// For proper coeffient loading, coeff_forward must be shifted in backwards. coeffs[] is already backwards
.coeff_in(((USE_EMBEDDED_REGS_COEFFS == 1) && (RELOADABLE_COEFFS == 1)) ? coeff_forward[j] : coeffs[j]),
.coeff_forward(coeff_forward[j+1]),
.coeff_load_stb(coeff_load_stb),
.sample_accum(sample_accum[j]),
.sample_out(sample_accum[j+1])
);
for (genvar j = 0; j < NUM_SLICES ; j = j + 1) begin : gen_slice
// skip zero coefficients: always for static coeffs
if (OPTIMIZE_FOR_ZERO_COEFFS && COEFFS_VEC[j] == '0) begin
assign sample_accum[j+1] = sample_accum[j];
always_ff @(posedge clk) begin
if (reset | clear) begin
coeff_forward[j+1] <= '0;
end else if (coeff_load_stb) begin
coeff_forward[j+1] <= coeff_forward[j];
end
end
end else begin
localparam int GAP = OPTIMIZE_FOR_ZERO_COEFFS ? count_zeros_up_to_index(j) : 0;
localparam bit APPLY_SYMMETRY = (OPTIMIZE_FOR_SYMMETRIC_COEFFS == 1) // enabled?
&& !((NUM_COEFFS[0] == 1) && (j == NUM_SLICES-1)); // not the center tap for odd NUM_COEFFS
fir_filter_slice #(
.IN_WIDTH(IN_WIDTH),
.COEFF_WIDTH(COEFF_WIDTH),
.ACCUM_WIDTH(ACCUM_WIDTH),
.OUT_WIDTH(ACCUM_WIDTH))
fir_filter_slice (
.clk(clk),
.reset(reset),
.clear(clear),
.sample_in_stb(s_axis_data_tvalid & s_axis_data_tready),
// NUM_SPC is added j times due to the pipeline delay.
// +1 for selecting the next index for the FIR result calculation
// K_FLIPPED is the offset into data_shift_reg.
// If zero tap filter slices are skipped, we need to pick samples
// GAP cycles earlier to keep the correct alignment which corresponds
// to subtracting GAP*NUM_SPC from the buffer index.
.sample_in_a(data_shift_reg[ (j)*(NUM_SPC+1) - GAP*NUM_SPC + K_FLIPPED ]),
// sample_in_b is used to implement symmetric coefficients
// If not symmetric or middle tap of odd number of coeffs, tie to zero
// otherwise select the mirrored sample from the shift register.
// Mirror index is NUM_COEFFS-1-j, since sample_in_b inside fir_filter_slice
// has one cycle delay less that sample_in_a, we need to take it one cycle
// later such that both values are in-sync. Thus we add another NUM_SPC to the index.
.sample_in_b(APPLY_SYMMETRY ? data_shift_reg[ (NUM_COEFFS-1-j) + (j-GAP+1)*NUM_SPC + K_FLIPPED ] : '0),
.sample_forward(),
// For proper coeffient loading, coeff_forward must be shifted in backwards. coeffs[] is already backwards
.coeff_in(((USE_EMBEDDED_REGS_COEFFS == 1) && (RELOADABLE_COEFFS == 1)) ? coeff_forward[j] : coeffs[j]),
.coeff_forward(coeff_forward[j+1]),
.coeff_load_stb(coeff_load_stb),
.sample_accum(sample_accum[j]),
// Connect the accumulator chain from previous to next slice instance
.sample_out(sample_accum[j+1])
);
end
end : gen_slice
always_comb begin
@ -242,7 +306,7 @@ module axi_fir_multisample_filter #(
m_axis_data_tdata_int[k] = '0;
m_axis_data_tvalid_int[k] = '0;
end else begin
m_axis_data_tdata_int[k] = sample_accum[NUM_COEFFS];
m_axis_data_tdata_int[k] = sample_accum[NUM_SLICES];
m_axis_data_tvalid_int[k] = s_axis_data_tvalid;
end
// tlast is masked the same way during ring-in.

View File

@ -16,14 +16,18 @@
// BLANK_OUTPUT - Disable (1) or enable (0) output when initially filling internal pipeline
// USE_EMBEDDED_REGS_COEFFS - Reduce register usage by only using embedded registers in DSP slices.
// Updating taps while streaming will cause temporary output corruption!
//
// COEFF_WIDTH - Width of filter coefficients
// COEFFS_VEC - Vector of filter coefficients
module axi_fir_multisample_filter_tb #(
parameter NUM_SPC = 8,
parameter NUM_COEFFS = 41,
parameter RELOADABLE_COEFFS = 1,
parameter BLANK_OUTPUT = 1,
parameter USE_EMBEDDED_REGS_COEFFS = 1
parameter int NUM_SPC = 8,
parameter int NUM_COEFFS = 41,
parameter bit RELOADABLE_COEFFS = 1,
parameter bit BLANK_OUTPUT = 1,
parameter bit USE_EMBEDDED_REGS_COEFFS = 1,
parameter int COEFF_WIDTH = 16,
parameter bit [COEFF_WIDTH-1:0] COEFFS_VEC [NUM_COEFFS] =
'{0: (1 << (COEFF_WIDTH-1)) - 1, default: 0}
) ();
`include "test_exec.svh"
@ -46,29 +50,13 @@ module axi_fir_multisample_filter_tb #(
// DUT parameters to test
localparam int IN_WIDTH = 16;
localparam int AXI_WIDTH = IN_WIDTH * NUM_SPC;
localparam int COEFF_WIDTH = 16;
localparam int OUT_WIDTH = 16;
localparam int CLIP_BITS = $clog2(NUM_COEFFS);
localparam int ACCUM_WIDTH = IN_WIDTH + COEFF_WIDTH + CLIP_BITS - 1;
localparam int PIPELINE_DELAY = NUM_COEFFS + 5;
// How many groups of multisampled-input needed
localparam int IN_GROUP_NUM = $ceil(NUM_COEFFS*1.0/NUM_SPC);
// +10 is pipeline compensation
localparam int FLUSH_CYCLE = ((NUM_SPC < NUM_COEFFS) ? NUM_COEFFS : NUM_SPC ) +10;
localparam logic [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_0 = {
16'sd158, 16'sd0, 16'sd33, -16'sd0, -16'sd256,
16'sd553, 16'sd573, -16'sd542, -16'sd1012, 16'sd349,
16'sd1536, 16'sd123, -16'sd2097, -16'sd1012, 16'sd1633,
16'sd1608, -16'sd3077, -16'sd5946, 16'sd3370, 16'sd10513,
-16'sd19295, // 16'sd19295, change to negative to avoid clipping
16'sd10513, 16'sd3370, -16'sd5946, -16'sd3077, 16'sd1608,
16'sd1633, -16'sd1012, -16'sd2097, 16'sd123, 16'sd1536,
16'sd349, -16'sd1012, -16'sd542, 16'sd573, 16'sd553,
-16'sd256, -16'sd0, 16'sd33, 16'sd0, 16'sd158
};
//---------------------------------------------------------------------------
// Clocks and Resets
//---------------------------------------------------------------------------
@ -146,7 +134,7 @@ module axi_fir_multisample_filter_tb #(
.NUM_COEFFS(NUM_COEFFS),
.CLIP_BITS(CLIP_BITS),
.ACCUM_WIDTH(ACCUM_WIDTH),
.COEFFS_VEC(COEFFS_VEC_0),
.COEFFS_VEC(COEFFS_VEC),
.RELOADABLE_COEFFS(RELOADABLE_COEFFS),
.BLANK_OUTPUT(BLANK_OUTPUT),
.USE_EMBEDDED_REGS_COEFFS(USE_EMBEDDED_REGS_COEFFS)
@ -314,7 +302,7 @@ module axi_fir_multisample_filter_tb #(
packet_reload.empty();
// Reload must send data in reverse direction
for (int i= NUM_COEFFS-1 ; i>=0; i--) begin
packet_reload.data.push_back(COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]);
packet_reload.data.push_back(COEFFS_VEC[i]);
end
AxisIf_coeff_bfm.put(packet_reload);
AxisIf_coeff_bfm.wait_complete();
@ -368,7 +356,7 @@ module axi_fir_multisample_filter_tb #(
end else begin
get_sample(i_samp_int, i == 0); // ask one single sample from output
end
i_coeff = $signed(COEFFS_VEC_0[COEFF_WIDTH*i +: COEFF_WIDTH]);
i_coeff = $signed(COEFFS_VEC[i]);
$sformat(
s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",

View File

@ -7,43 +7,230 @@
//
// Description:
//
// testing various configurations of the filter
// Top-level testbench wrapper for axi_fir_multisample_filter. This module
// runs multiple test configurations to verify:
// - Base functionality with various NUM_SPC, NUM_COEFFS, and feature combinations
// (RELOADABLE_COEFFS, BLANK_OUTPUT, USE_EMBEDDED_REGS_COEFFS)
// - Symmetric coefficient optimization with odd tap counts (non-reloadable only)
// - Symmetric coefficient optimization with even tap counts (non-reloadable only)
// - Zero coefficient skipping optimization combined with symmetry (non-reloadable only)
//
// Each test configuration uses nested generate loops to create comprehensive
// parameter coverage across different filter configurations.
//
module axi_fir_multisample_filter_tb_wrapper();
// Common Parameters
`include "test_exec.svh"
localparam int COEFF_WIDTH = 16;
localparam integer NUM_SPC_TEST[2:0] = {2 , 4 , 8};
localparam integer NUM_COEFFS_TEST[2:0] = {5 , 17 , 41};
localparam integer PARA_TEST[1:0] = {0 , 1};
// General tests:
// - General tests covering all combinations of NUM_SPC, NUM_COEFFS,
// RELOADABLE_COEFFS, BLANK_OUTPUT, and USE_EMBEDDED_REGS_COEFFS parameters.
//
// Test parameters:
// - NUM_SPC: 2, 4, 8 samples per cycle
// - NUM_COEFFS: 5, 17, 41 taps
// - RELOADABLE_COEFFS: 0, 1
// - BLANK_OUTPUT: 0, 1
// - USE_EMBEDDED_REGS_COEFFS: 0, 1
generate
//---------------------------------------------------------------------------
// Local parameters
//---------------------------------------------------------------------------
localparam bit [COEFF_WIDTH-1:0] MAX_COEFF_ARRAY [41] = '{
16'sd158, 16'sd0, 16'sd33, -16'sd0, -16'sd256,
16'sd553, 16'sd573, -16'sd542, -16'sd1012, 16'sd349,
16'sd1536, 16'sd123, -16'sd2097, -16'sd1012, 16'sd1633,
16'sd1608, -16'sd3077, -16'sd5946, 16'sd3370, 16'sd10513,
-16'sd19295, // 16'sd19295, change to negative to avoid clipping
16'sd10513, 16'sd3370, -16'sd5946, -16'sd3077, 16'sd1608,
16'sd1633, -16'sd1012, -16'sd2097, 16'sd123, 16'sd1536,
16'sd349, -16'sd1012, -16'sd542, 16'sd573, 16'sd553,
-16'sd256, -16'sd0, 16'sd33, 16'sd0, 16'sd158
};
//---------------------------------------------------------------------------
// Nested test cases
//---------------------------------------------------------------------------
// Full coverage nested test loop
for (genvar i = 0; i < $size(NUM_SPC_TEST); i = i + 1) begin : test_NUM_SPC
for (genvar j = 0; j < $size(NUM_COEFFS_TEST); j = j + 1) begin : test_NUM_COEFFS
for (genvar k = 0; k < $size(PARA_TEST); k = k + 1) begin : test_RELOADABLE
for (genvar m = 0; m < $size(PARA_TEST); m = m + 1) begin : test_BLANK
for (genvar n = 0; n < $size(PARA_TEST); n = n + 1) begin : test_EMBEDDED
axi_fir_multisample_filter_tb #(
.NUM_SPC(NUM_SPC_TEST[i]),
.NUM_COEFFS(NUM_COEFFS_TEST[j]),
.RELOADABLE_COEFFS(PARA_TEST[k]),
.BLANK_OUTPUT(PARA_TEST[m]),
.USE_EMBEDDED_REGS_COEFFS(PARA_TEST[n]),
.COEFF_WIDTH(COEFF_WIDTH),
.COEFFS_VEC(MAX_COEFF_ARRAY[0 +: NUM_COEFFS_TEST[j]])
) tb_i ();
end : test_EMBEDDED
end : test_BLANK
end : test_RELOADABLE
end : test_NUM_COEFFS
end : test_NUM_SPC
endgenerate
//---------------------------------------------------------------------------
// Local Parameters
//---------------------------------------------------------------------------
timeunit 1ns / 1ps;
//
// Tests for symmetric coefficient optimization with odd tap counts.
// - Uses symmetric coefficient arrays to enable OPTIMIZE_FOR_SYMMETRIC_COEFFS.
// - Only non-reloadable configurations (RELOADABLE_COEFFS=0) are tested.
//
// Test parameters:
// - NUM_SPC: 2, 4, 8 samples per cycle
// - NUM_COEFFS: 5, 17, 41 taps (all odd)
// - BLANK_OUTPUT: 0, 1
// - RELOADABLE_COEFFS: 0 (fixed)
// - USE_EMBEDDED_REGS_COEFFS: 0 (fixed)
//
generate
//---------------------------------------------------------------------------
// Local parameters
//---------------------------------------------------------------------------
localparam bit [COEFF_WIDTH-1:0] MAX_COEFF_ARRAY_SYMMETRIC_ODD [41] = '{
16'sd158, 16'sd158, 16'sd33, -16'sd256, -16'sd256,
16'sd553, 16'sd573, -16'sd542, -16'sd1012, 16'sd349,
16'sd1536, 16'sd123, -16'sd2097, -16'sd1012, 16'sd1633,
16'sd1608, -16'sd3077, -16'sd5946, 16'sd3370, 16'sd10513,
-16'sd19295, // 16'sd19295, change to negative to avoid clipping
16'sd10513, 16'sd3370, -16'sd5946, -16'sd3077, 16'sd1608,
16'sd1633, -16'sd1012, -16'sd2097, 16'sd123, 16'sd1536,
16'sd349, -16'sd1012, -16'sd542, 16'sd573, 16'sd553,
-16'sd256, -16'sd256, 16'sd33, 16'sd158, 16'sd158
};
//---------------------------------------------------------------------------
// Nested test cases
//---------------------------------------------------------------------------
for (genvar i = 0; i < $size(NUM_SPC_TEST); i = i + 1) begin : test_NUM_SPC_SYM
for (genvar j = 0; j < $size(NUM_COEFFS_TEST); j = j + 1) begin : test_NUM_COEFFS_SYM
for (genvar m = 0; m < $size(PARA_TEST); m = m + 1) begin : test_BLANK_SYM
// ... extract symmetric coefficients for odd number of taps
localparam bit [COEFF_WIDTH-1:0] COEFFS_SYMM_ODD [NUM_COEFFS_TEST[j]] =
MAX_COEFF_ARRAY_SYMMETRIC_ODD[($size(MAX_COEFF_ARRAY_SYMMETRIC_ODD)-NUM_COEFFS_TEST[j])/2 +: NUM_COEFFS_TEST[j]];
// ... instantiate testbench
axi_fir_multisample_filter_tb #(
.NUM_SPC(NUM_SPC_TEST[i]),
.NUM_COEFFS(NUM_COEFFS_TEST[j]),
.RELOADABLE_COEFFS(0),
.BLANK_OUTPUT(PARA_TEST[m]),
.USE_EMBEDDED_REGS_COEFFS(0),
.COEFF_WIDTH(COEFF_WIDTH),
.COEFFS_VEC(COEFFS_SYMM_ODD)
) tb_sym_i ();
end : test_BLANK_SYM
end : test_NUM_COEFFS_SYM
end : test_NUM_SPC_SYM
endgenerate
localparam integer NUM_SPC_TEST[2:0] = {2 , 4 , 8};
localparam integer NUM_COEFFS_TEST[2:0] = {5 , 17 , 41};
localparam integer PARA_TEST[1:0] = {0 , 1};
//
// Tests for symmetric coefficient optimization with even tap counts.
// - Uses symmetric coefficient arrays to enable OPTIMIZE_FOR_SYMMETRIC_COEFFS.
// - Only non-reloadable configurations (RELOADABLE_COEFFS=0) are tested.
//
// Test parameters:
// - NUM_SPC: 2, 4, 8 samples per cycle
// - NUM_COEFFS: 6, 18, 40 taps (all even)
// - BLANK_OUTPUT: 0, 1
// - RELOADABLE_COEFFS: 0 (fixed)
// - USE_EMBEDDED_REGS_COEFFS: 0 (fixed)
//
generate
//---------------------------------------------------------------------------
// Local parameters
//---------------------------------------------------------------------------
localparam integer SYM_EVEN_NUM_COEFFS_TEST[2:0] = {6, 18, 40};
// ... symmetric coefficients with even number of taps
localparam bit [COEFF_WIDTH-1:0] MAX_COEFF_ARRAY_SYMMETRIC_EVEN [40] = '{
16'sd158, 16'sd158, 16'sd33, -16'sd256, -16'sd256,
16'sd553, 16'sd573, -16'sd542, -16'sd1012, 16'sd349,
16'sd1536, 16'sd123, -16'sd2097, -16'sd1012, 16'sd1633,
16'sd1608, -16'sd3077, -16'sd5946, 16'sd3370, 16'sd10513,
16'sd10513, 16'sd3370, -16'sd5946, -16'sd3077, 16'sd1608,
16'sd1633, -16'sd1012, -16'sd2097, 16'sd123, 16'sd1536,
16'sd349, -16'sd1012, -16'sd542, 16'sd573, 16'sd553,
-16'sd256, -16'sd256, 16'sd33, 16'sd158, 16'sd158
};
//---------------------------------------------------------------------------
// Nested test cases
//---------------------------------------------------------------------------
for (genvar i = 0; i < $size(NUM_SPC_TEST); i = i + 1) begin : test_NUM_SPC_SYM_EVEN
for (genvar j = 0; j < $size(SYM_EVEN_NUM_COEFFS_TEST); j = j + 1) begin : test_NUM_COEFFS_SYM_EVEN
for (genvar m = 0; m < $size(PARA_TEST); m = m + 1) begin : test_BLANK_SYM_EVEN
// ... extract symmetric coefficients for even number of taps
localparam bit [COEFF_WIDTH-1:0] COEFFS_SYMM_EVEN [SYM_EVEN_NUM_COEFFS_TEST[j]] =
MAX_COEFF_ARRAY_SYMMETRIC_EVEN[($size(MAX_COEFF_ARRAY_SYMMETRIC_EVEN)-SYM_EVEN_NUM_COEFFS_TEST[j])/2 +: SYM_EVEN_NUM_COEFFS_TEST[j]];
// ... instantiate testbench
axi_fir_multisample_filter_tb #(
.NUM_SPC(NUM_SPC_TEST[i]),
.NUM_COEFFS(SYM_EVEN_NUM_COEFFS_TEST[j]),
.RELOADABLE_COEFFS(0),
.BLANK_OUTPUT(PARA_TEST[m]),
.USE_EMBEDDED_REGS_COEFFS(0),
.COEFF_WIDTH(COEFF_WIDTH),
.COEFFS_VEC(COEFFS_SYMM_EVEN)
) tb_sym_even_i ();
end : test_BLANK_SYM_EVEN
end : test_NUM_COEFFS_SYM_EVEN
end : test_NUM_SPC_SYM_EVEN
endgenerate
//---------------------------------------------------------------------------
// Nested test cases
//---------------------------------------------------------------------------
// Full coverage nested test loop
generate
for (genvar i = 0; i < $size(NUM_SPC_TEST); i = i + 1) begin : test_NUM_SPC
for (genvar j = 0; j < $size(NUM_COEFFS_TEST); j = j + 1) begin : test_NUM_COEFFS
for (genvar k = 0; k < $size(PARA_TEST); k = k + 1) begin : test_RELOADABLE
for (genvar m = 0; m < $size(PARA_TEST); m = m + 1) begin : test_BLANK
for (genvar n = 0; n < $size(PARA_TEST); n = n + 1) begin : test_EMBEDDED
axi_fir_multisample_filter_tb #(
.NUM_SPC(NUM_SPC_TEST[i]),
.NUM_COEFFS(NUM_COEFFS_TEST[j]),
.RELOADABLE_COEFFS(PARA_TEST[k]),
.BLANK_OUTPUT(PARA_TEST[m]),
.USE_EMBEDDED_REGS_COEFFS(PARA_TEST[n])
) tb_i ();
end : test_EMBEDDED
end : test_BLANK
end : test_RELOADABLE
end : test_NUM_COEFFS
end : test_NUM_SPC
endgenerate
endmodule
//
// Tests for combined symmetric and zero coefficient optimizations.
// - Uses symmetric coefficient arrays with every 2nd coefficient set to zero
// to enable both OPTIMIZE_FOR_SYMMETRIC_COEFFS and OPTIMIZE_FOR_ZERO_COEFFS.
// - Only non-reloadable configurations (RELOADABLE_COEFFS=0) are tested.
//
// Test parameters:
// - NUM_SPC: 1, 2, 3, 4, 8, 16 samples per cycle
// - NUM_COEFFS: 5, 17, 41 taps (all odd)
// - BLANK_OUTPUT: 0, 1
// - RELOADABLE_COEFFS: 0 (fixed)
// - USE_EMBEDDED_REGS_COEFFS: 0 (fixed)
//
generate
//---------------------------------------------------------------------------
// Local parameters
//---------------------------------------------------------------------------
localparam integer SYM_ZEROS_NUM_SPC_TEST[3:0] = {1, 2, 4, 8};
// ... every 2nd coefficient is zero for testing zero-coefficient skipping optimization
localparam bit [COEFF_WIDTH-1:0] MAX_COEFF_ARRAY_SYMMETRIC_ZEROS [41] = '{
16'sd158, 16'sd0, 16'sd33, 16'sd0, -16'sd256,
16'sd0, 16'sd573, 16'sd0, -16'sd1012, 16'sd0,
16'sd1536, 16'sd0, -16'sd2097, 16'sd0, 16'sd1633,
16'sd0, -16'sd3077, 16'sd0, 16'sd3370, 16'sd0,
-16'sd19295, // center coefficient (odd number of taps)
16'sd0, 16'sd3370, 16'sd0, -16'sd3077, 16'sd0,
16'sd1633, 16'sd0, -16'sd2097, 16'sd0, 16'sd1536,
16'sd0, -16'sd1012, 16'sd0, 16'sd573, 16'sd0,
-16'sd256, 16'sd0, 16'sd33, 16'sd0, 16'sd158
};
//---------------------------------------------------------------------------
// Nested test cases
//---------------------------------------------------------------------------
for (genvar i = 0; i < $size(SYM_ZEROS_NUM_SPC_TEST); i = i + 1) begin : test_NUM_SPC_SYM_ZEROS
for (genvar j = 0; j < $size(NUM_COEFFS_TEST); j = j + 1) begin : test_NUM_COEFFS_SYM_ZEROS
for (genvar m = 0; m < $size(PARA_TEST); m = m + 1) begin : test_BLANK_SYM_ZEROS
// ... extract symmetric coefficients for odd number of taps
localparam bit [COEFF_WIDTH-1:0] COEFFS_SYMM_ODD [NUM_COEFFS_TEST[j]] =
MAX_COEFF_ARRAY_SYMMETRIC_ZEROS[($size(MAX_COEFF_ARRAY_SYMMETRIC_ZEROS)-NUM_COEFFS_TEST[j])/2 +: NUM_COEFFS_TEST[j]];
// ... instantiate testbench
axi_fir_multisample_filter_tb #(
.NUM_SPC(SYM_ZEROS_NUM_SPC_TEST[i]),
.NUM_COEFFS(NUM_COEFFS_TEST[j]),
.RELOADABLE_COEFFS(0),
.BLANK_OUTPUT(PARA_TEST[m]),
.USE_EMBEDDED_REGS_COEFFS(0),
.COEFF_WIDTH(COEFF_WIDTH),
.COEFFS_VEC(COEFFS_SYMM_ODD)
) tb_sym_odd_zeros_i ();
end : test_BLANK_SYM_ZEROS
end : test_NUM_COEFFS_SYM_ZEROS
end : test_NUM_SPC_SYM_ZEROS
endgenerate
endmodule : axi_fir_multisample_filter_tb_wrapper