fpga: rfnoc: lib: add AXI FIR filter for >1 SPC
Original-commit: 90ad1fecb62b804f543def6e4c680706d76442b5
This commit is contained in:
@@ -44,4 +44,5 @@ small_hb_int.v \
|
|||||||
srl.v \
|
srl.v \
|
||||||
tx_frontend.v \
|
tx_frontend.v \
|
||||||
variable_delay_line.v \
|
variable_delay_line.v \
|
||||||
|
axi_fir_multisample_filter.sv \
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Parameterized multi-sample FIR filter with AXI-stream interface.
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// For each sample per cycle a separate FIR filter with the given number of coefficients is
|
||||||
|
// instantiated. The filter is implemented as a chain of multiply-accumulate slices.
|
||||||
|
// A shift register is used to store the input samples as long as they are needed.
|
||||||
|
// The FIR filters are fed with the appropriate samples from the shift register.
|
||||||
|
// The indices into the shift registers are calculated at compile time.
|
||||||
|
//
|
||||||
|
// For the most efficient DSP slice inference use these settings:
|
||||||
|
// IN_WIDTH < 25, COEFF_WIDTH < 18, ACCUM_WIDTH < 48
|
||||||
|
//
|
||||||
|
// Parameters (widths are in bits):
|
||||||
|
//
|
||||||
|
// IN_WIDTH - Input width of a single sample
|
||||||
|
// NUM_SPC - Samples per cycle
|
||||||
|
// COEFF_WIDTH - Coefficient width
|
||||||
|
// OUT_WIDTH - Output width of a single sample
|
||||||
|
// NUM_COEFFS - Number of coefficients / taps
|
||||||
|
// CLIP_BITS - If IN_WIDTH != OUT_WIDTH, number of MSBs to drop
|
||||||
|
// ACCUM_WIDTH - Accumulator width
|
||||||
|
// COEFFS_VEC - Vector of NUM_COEFFS values, each of width COEFF_WIDTH to
|
||||||
|
// initialize coeffs. Defaults to an impulse.
|
||||||
|
// RELOADABLE_COEFFS - Enable (1) or disable (0) reloading coefficients at runtime (via reload bus)
|
||||||
|
// 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!
|
||||||
|
|
||||||
|
|
||||||
|
module axi_fir_multisample_filter #(
|
||||||
|
int IN_WIDTH = 16,
|
||||||
|
int NUM_SPC = 4,
|
||||||
|
int COEFF_WIDTH = 16,
|
||||||
|
int OUT_WIDTH = 16,
|
||||||
|
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 RELOADABLE_COEFFS = 1,
|
||||||
|
bit BLANK_OUTPUT = 1,
|
||||||
|
bit USE_EMBEDDED_REGS_COEFFS = 1
|
||||||
|
)(
|
||||||
|
// clocks and control signals
|
||||||
|
input logic clk,
|
||||||
|
input logic reset,
|
||||||
|
input logic clear,
|
||||||
|
|
||||||
|
// AXI stream data input interface
|
||||||
|
input logic [NUM_SPC*IN_WIDTH-1:0] s_axis_data_tdata,
|
||||||
|
input logic s_axis_data_tlast,
|
||||||
|
input logic s_axis_data_tvalid,
|
||||||
|
output logic s_axis_data_tready,
|
||||||
|
|
||||||
|
// AXI stream data output interface
|
||||||
|
output logic [NUM_SPC*OUT_WIDTH-1:0] m_axis_data_tdata,
|
||||||
|
output logic m_axis_data_tlast,
|
||||||
|
output logic m_axis_data_tvalid,
|
||||||
|
input logic m_axis_data_tready,
|
||||||
|
|
||||||
|
// AXI stream coefficient interface
|
||||||
|
input logic [COEFF_WIDTH-1:0] s_axis_reload_tdata,
|
||||||
|
input logic s_axis_reload_tvalid,
|
||||||
|
input logic s_axis_reload_tlast,
|
||||||
|
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
|
||||||
|
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];
|
||||||
|
logic [NUM_SPC-1:0] m_axis_data_tvalid_int;
|
||||||
|
logic [NUM_SPC-1:0] m_axis_data_tready_int;
|
||||||
|
logic [NUM_SPC-1:0] m_axis_data_tlast_int;
|
||||||
|
logic [NUM_SPC-1:0] m_axis_data_tvalid_array;
|
||||||
|
logic [NUM_SPC-1:0] m_axis_data_tlast_array;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Coefficient loading / reloading
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
reg [COEFF_WIDTH-1:0] coeffs[0:NUM_COEFFS-1];
|
||||||
|
reg coeff_load_stb = 1'b1;
|
||||||
|
generate
|
||||||
|
if (RELOADABLE_COEFFS) begin
|
||||||
|
// Use DSP slice registers to hold coefficients. While loading
|
||||||
|
// coefficients, input sample data should be throttled if corrupted
|
||||||
|
// output samples are unacceptable
|
||||||
|
if (USE_EMBEDDED_REGS_COEFFS) begin
|
||||||
|
always @(*) begin
|
||||||
|
coeff_load_stb <= s_axis_reload_tvalid & s_axis_reload_tready;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
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];
|
||||||
|
end
|
||||||
|
// Initialize coefficients at reset
|
||||||
|
coeff_load_stb <= 1'b1;
|
||||||
|
end else begin
|
||||||
|
if (s_axis_reload_tvalid & s_axis_reload_tready) begin
|
||||||
|
// Inverted direction to reload coeff
|
||||||
|
for (int k = NUM_COEFFS-1; k > 0; k = k - 1) begin
|
||||||
|
coeffs[k] <= coeffs[k-1];
|
||||||
|
end
|
||||||
|
coeffs[0] <= s_axis_reload_tdata;
|
||||||
|
end
|
||||||
|
coeff_load_stb <= s_axis_reload_tvalid & s_axis_reload_tready & s_axis_reload_tlast;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// 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];
|
||||||
|
coeff_load_stb <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
assign s_axis_reload_tready = 1'b1;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Multisample FIR Filter
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
reg [IN_WIDTH-1:0] data_shift_reg [0 : SHIFT_REG_WIDTH-1];
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
for (int k= 0; k < SHIFT_REG_WIDTH; k = k + 1) begin
|
||||||
|
data_shift_reg[k] <= 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// s_axis_data_tdata given as x[n-1],x[n-2],...,x[2],x[1],x[0].
|
||||||
|
// data_shift_reg is organized to contain samples in natural order.
|
||||||
|
// data_shift_reg index ... | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
// cycle 0: ....| ? | ? | ? | x0 | x1 | x2 |
|
||||||
|
// cycle 1: ....| x0 | x1 | x2 | x3 | x4 | x5 |
|
||||||
|
// Data from axi data port is stored in reversed order starting from shift register index 0.
|
||||||
|
// For remaining indices samples are shifted by NUM_SPC each cycle.
|
||||||
|
// data_shift_reg works like:
|
||||||
|
// <---- shift by NUM_SPC
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (s_axis_data_tvalid & s_axis_data_tready) begin
|
||||||
|
// Wire input to lower register position
|
||||||
|
for (int k = 0; k < NUM_SPC; k = k + 1) begin
|
||||||
|
automatic int k_flipped = NUM_SPC-k-1;
|
||||||
|
data_shift_reg[k] <= s_axis_data_tdata [k_flipped *IN_WIDTH +: IN_WIDTH ];
|
||||||
|
end
|
||||||
|
// Shift contents by NUM_SPC to the upper position of shift register
|
||||||
|
for (int k = NUM_SPC; k < SHIFT_REG_WIDTH; k = k + 1) begin
|
||||||
|
data_shift_reg[k] <= data_shift_reg[k-NUM_SPC];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Counter to track pipeline fullness
|
||||||
|
reg [$clog2(PIPELINE_DELAY):0] cnt;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (reset | clear) begin
|
||||||
|
cnt <= 0;
|
||||||
|
end else if (s_axis_data_tvalid & s_axis_data_tready) begin
|
||||||
|
if (cnt < PIPELINE_DELAY) begin
|
||||||
|
cnt <= cnt + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// tlast shift register
|
||||||
|
reg [PIPELINE_DELAY-1:0] tlast_shift_reg = 0;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (s_axis_data_tvalid & s_axis_data_tready) begin
|
||||||
|
for (int k = 1; k < PIPELINE_DELAY; k = k + 1) begin
|
||||||
|
tlast_shift_reg[k] <= tlast_shift_reg[k-1];
|
||||||
|
end
|
||||||
|
tlast_shift_reg[0] <= s_axis_data_tlast;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Instantiate NUM_SPC-numbers of DSP-chain
|
||||||
|
generate
|
||||||
|
for (genvar k = 0; k < NUM_SPC; k = k + 1) begin : gen_DSP_chain
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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])
|
||||||
|
);
|
||||||
|
end : gen_slice
|
||||||
|
|
||||||
|
always_comb begin
|
||||||
|
// zero data and valid bit for the ring-in of the pipeline
|
||||||
|
if (BLANK_OUTPUT == 1 && cnt < PIPELINE_DELAY) begin
|
||||||
|
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_tvalid_int[k] = s_axis_data_tvalid;
|
||||||
|
end
|
||||||
|
// tlast is masked the same way during ring-in.
|
||||||
|
// Depending on the blanking mode tlast will be delayed or taken from the input.
|
||||||
|
if (BLANK_OUTPUT == 1) begin
|
||||||
|
if (cnt < PIPELINE_DELAY) begin
|
||||||
|
m_axis_data_tlast_int[k] = 1'b0;
|
||||||
|
end else begin
|
||||||
|
m_axis_data_tlast_int[k] = tlast_shift_reg[PIPELINE_DELAY-1];
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
m_axis_data_tlast_int[k] = s_axis_data_tlast;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
axi_round_and_clip #(
|
||||||
|
.WIDTH_IN(ACCUM_WIDTH),
|
||||||
|
.WIDTH_OUT(OUT_WIDTH),
|
||||||
|
.CLIP_BITS(CLIP_BITS))
|
||||||
|
inst_axi_round_and_clip (
|
||||||
|
.clk(clk),
|
||||||
|
.reset(reset | clear),
|
||||||
|
.i_tdata(m_axis_data_tdata_int[k]),
|
||||||
|
.i_tlast(m_axis_data_tlast_int[k]),
|
||||||
|
.i_tvalid(m_axis_data_tvalid_int[k]),
|
||||||
|
.i_tready(m_axis_data_tready_int[k]), // output
|
||||||
|
.o_tdata(m_axis_data_tdata[k*OUT_WIDTH +: OUT_WIDTH]),
|
||||||
|
.o_tlast(m_axis_data_tlast_array[k]),
|
||||||
|
.o_tvalid(m_axis_data_tvalid_array[k]),
|
||||||
|
.o_tready(m_axis_data_tready) //input
|
||||||
|
);
|
||||||
|
end : gen_DSP_chain
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
assign s_axis_data_tready = m_axis_data_tready_int[0];
|
||||||
|
assign m_axis_data_tvalid = m_axis_data_tvalid_array[0];
|
||||||
|
assign m_axis_data_tlast = m_axis_data_tlast_array[0];
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
#
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Top-of-Makefile
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Define BASE_DIR to point to the "top" dir
|
||||||
|
BASE_DIR = $(abspath ../../../../top)
|
||||||
|
# Include viv_sim_preamble after defining BASE_DIR
|
||||||
|
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Design Specific
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Include makefiles and sources for the DUT and its dependencies
|
||||||
|
|
||||||
|
include $(BASE_DIR)/../lib/rfnoc/Makefile.srcs
|
||||||
|
|
||||||
|
|
||||||
|
DESIGN_SRCS += $(abspath \
|
||||||
|
)
|
||||||
|
|
||||||
|
#$(RFNOC_CORE_SRCS) \
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Testbench Specific
|
||||||
|
#-------------------------------------------------
|
||||||
|
SIM_TOP = axi_fir_multisample_filter_tb_wrapper
|
||||||
|
|
||||||
|
SIM_SRCS = \
|
||||||
|
$(abspath axi_fir_multisample_filter_tb.sv) \
|
||||||
|
$(abspath axi_fir_multisample_filter_tb_wrapper.sv) \
|
||||||
|
|
||||||
|
# MODELSIM_USER_DO = $(abspath wave.do)
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Bottom-of-Makefile
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Include all simulator specific makefiles here
|
||||||
|
# Each should define a unique target to simulate
|
||||||
|
# e.g. xsim, vsim, etc and a common "clean" target
|
||||||
|
include $(BASE_DIR)/../tools/make/viv_simulator.mak
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axi_fir_multisample_filter_tb
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// Testbench for axi_fir_multisample_filter.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// NUM_SPC: How much sample per cycle is set
|
||||||
|
// NUM_COEFFS: Number of coefficients / taps
|
||||||
|
// RELOADABLE_COEFFS - Enable (1) or disable (0) reloading coefficients at runtime (via reload bus)
|
||||||
|
// 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!
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
) ();
|
||||||
|
|
||||||
|
`include "test_exec.svh"
|
||||||
|
|
||||||
|
import PkgTestExec::*;
|
||||||
|
import PkgAxiStreamBfm::*;
|
||||||
|
import PkgRandom::*;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Testbench Configuration
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Local Parameters
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Simulation parameters
|
||||||
|
localparam real AXI_CLK_PER = 10.0; // 100 MHz
|
||||||
|
|
||||||
|
localparam int STALL_PROB = 38; // BFM stall probability, default 38
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
bit axi_clk, axi_rst, axi_clear=0;
|
||||||
|
sim_clock_gen #(.PERIOD(AXI_CLK_PER)) axi_clk_gen (.clk(axi_clk), .rst(axi_rst));
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Bus Functional Models
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Deafult interface data width is 64bits
|
||||||
|
typedef AxiStreamBfm #(AXI_WIDTH)::AxisPacket_t AxisPacket_in;
|
||||||
|
typedef AxiStreamBfm #(AXI_WIDTH)::AxisPacket_t AxisPacket_out;
|
||||||
|
typedef AxiStreamBfm #(COEFF_WIDTH)::AxisPacket_t AxisPacket_reload;
|
||||||
|
|
||||||
|
AxiStreamIf #(AXI_WIDTH) AxisIf_m (axi_clk, axi_rst);
|
||||||
|
AxiStreamIf #(AXI_WIDTH) AxisIf_s (axi_clk, axi_rst);
|
||||||
|
AxiStreamIf #(COEFF_WIDTH) AxisIf_reload (axi_clk, axi_rst);
|
||||||
|
|
||||||
|
// Connect BFM to interface
|
||||||
|
AxiStreamBfm #(AXI_WIDTH) AxisIf_sample_bfm = new(AxisIf_m,AxisIf_s);
|
||||||
|
AxiStreamBfm #(COEFF_WIDTH) AxisIf_coeff_bfm = new(AxisIf_reload, null);
|
||||||
|
|
||||||
|
AxisPacket_in packet_in;
|
||||||
|
AxisPacket_out packet_out;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// DUT
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// DUT Slave (Input) Port Signals
|
||||||
|
logic [NUM_SPC*IN_WIDTH-1:0] s_axis_data_tdata;
|
||||||
|
logic s_axis_data_tlast;
|
||||||
|
logic s_axis_data_tvalid;
|
||||||
|
logic s_axis_data_tready;
|
||||||
|
|
||||||
|
// DUT Master (Output) Port Signals
|
||||||
|
logic [NUM_SPC*OUT_WIDTH-1:0] m_axis_data_tdata;
|
||||||
|
logic m_axis_data_tlast;
|
||||||
|
logic m_axis_data_tvalid;
|
||||||
|
logic m_axis_data_tready;
|
||||||
|
|
||||||
|
// DUT Coefficient Reload (Input) Port Signals
|
||||||
|
logic [COEFF_WIDTH-1:0] s_axis_reload_tdata;
|
||||||
|
logic s_axis_reload_tlast;
|
||||||
|
logic s_axis_reload_tvalid;
|
||||||
|
logic s_axis_reload_tready;
|
||||||
|
|
||||||
|
// Random Coeff
|
||||||
|
logic [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_RANDOM;
|
||||||
|
|
||||||
|
// Link to interface
|
||||||
|
assign s_axis_data_tdata = AxisIf_m.tdata;
|
||||||
|
assign s_axis_data_tlast = AxisIf_m.tlast;
|
||||||
|
assign s_axis_data_tvalid = AxisIf_m.tvalid;
|
||||||
|
assign AxisIf_m.tready = s_axis_data_tready;
|
||||||
|
|
||||||
|
assign AxisIf_s.tdata = m_axis_data_tdata;
|
||||||
|
assign AxisIf_s.tlast = m_axis_data_tlast;
|
||||||
|
assign AxisIf_s.tvalid = m_axis_data_tvalid;
|
||||||
|
assign m_axis_data_tready = AxisIf_s.tready;
|
||||||
|
|
||||||
|
assign s_axis_reload_tdata = AxisIf_reload.tdata;
|
||||||
|
assign s_axis_reload_tlast = AxisIf_reload.tlast;
|
||||||
|
assign s_axis_reload_tvalid = AxisIf_reload.tvalid;
|
||||||
|
assign AxisIf_reload.tready = s_axis_reload_tready;
|
||||||
|
|
||||||
|
// Map the array of AXI to a flat vector for the DUT
|
||||||
|
axi_fir_multisample_filter #(
|
||||||
|
.IN_WIDTH(IN_WIDTH),
|
||||||
|
.NUM_SPC(NUM_SPC),
|
||||||
|
.COEFF_WIDTH(COEFF_WIDTH),
|
||||||
|
.OUT_WIDTH(OUT_WIDTH),
|
||||||
|
.NUM_COEFFS(NUM_COEFFS),
|
||||||
|
.CLIP_BITS(CLIP_BITS),
|
||||||
|
.ACCUM_WIDTH(ACCUM_WIDTH),
|
||||||
|
.COEFFS_VEC(COEFFS_VEC_0),
|
||||||
|
.RELOADABLE_COEFFS(RELOADABLE_COEFFS),
|
||||||
|
.BLANK_OUTPUT(BLANK_OUTPUT),
|
||||||
|
.USE_EMBEDDED_REGS_COEFFS(USE_EMBEDDED_REGS_COEFFS)
|
||||||
|
) axi_fir__multisample_filter_i(
|
||||||
|
.clk(axi_clk),
|
||||||
|
.reset(axi_rst),
|
||||||
|
.clear(axi_clear),
|
||||||
|
.s_axis_data_tdata(s_axis_data_tdata),
|
||||||
|
.s_axis_data_tlast(s_axis_data_tlast),
|
||||||
|
.s_axis_data_tvalid(s_axis_data_tvalid),
|
||||||
|
.s_axis_data_tready(s_axis_data_tready),
|
||||||
|
.m_axis_data_tdata(m_axis_data_tdata),
|
||||||
|
.m_axis_data_tlast(m_axis_data_tlast),
|
||||||
|
.m_axis_data_tvalid(m_axis_data_tvalid),
|
||||||
|
.m_axis_data_tready(m_axis_data_tready),
|
||||||
|
.s_axis_reload_tdata(s_axis_reload_tdata),
|
||||||
|
.s_axis_reload_tvalid(s_axis_reload_tvalid),
|
||||||
|
.s_axis_reload_tlast(s_axis_reload_tlast),
|
||||||
|
.s_axis_reload_tready(s_axis_reload_tready)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Local Functions and Tasks
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Local reset function
|
||||||
|
task reset_axi (input int rst_cyc = 100);
|
||||||
|
axi_clk_gen.clk_wait_f();
|
||||||
|
axi_clk_gen.reset();
|
||||||
|
wait(axi_rst == 0);
|
||||||
|
repeat (rst_cyc) axi_clk_gen.clk_wait_f();
|
||||||
|
endtask : reset_axi
|
||||||
|
|
||||||
|
// Local random coeff generation function
|
||||||
|
function automatic void random_coeff_generation (output [COEFF_WIDTH*NUM_COEFFS-1:0] COEFFS_VEC_RANDOM);
|
||||||
|
logic signed [COEFF_WIDTH-1:0] random_coeff;
|
||||||
|
for (int i = 0; i < NUM_COEFFS; i++) begin
|
||||||
|
// Truncate 32 bits to 16 bits and make it signed
|
||||||
|
random_coeff = Rand #(COEFF_WIDTH)::rand_sbit_range(-2000, 2000);
|
||||||
|
COEFFS_VEC_RANDOM[i*COEFF_WIDTH +: COEFF_WIDTH] = random_coeff;
|
||||||
|
end
|
||||||
|
endfunction:random_coeff_generation
|
||||||
|
|
||||||
|
// Local function to collect single samples
|
||||||
|
// until all the samples collected, add into packet.
|
||||||
|
// Flush : 0 (send valid data)/ 1 (automatically flush)
|
||||||
|
task add_sample (input logic [IN_WIDTH-1:0] sample, input int flush = 0);
|
||||||
|
static logic [AXI_WIDTH-1:0] sample_collected = ('0);
|
||||||
|
static int count_SPC = 0; // NUM_SPC samples as one group
|
||||||
|
|
||||||
|
// append sample
|
||||||
|
sample_collected [count_SPC * IN_WIDTH +: IN_WIDTH] = sample;
|
||||||
|
count_SPC = count_SPC + 1;
|
||||||
|
|
||||||
|
// fill remaining data until vector is complete
|
||||||
|
if (flush == 1) begin
|
||||||
|
for (int i = count_SPC; i<NUM_SPC; i++) begin
|
||||||
|
sample_collected [count_SPC * IN_WIDTH +: IN_WIDTH] = '0;
|
||||||
|
end
|
||||||
|
count_SPC = NUM_SPC;
|
||||||
|
end
|
||||||
|
if (count_SPC == NUM_SPC) begin
|
||||||
|
packet_in.data.push_back(sample_collected);
|
||||||
|
count_SPC = 0;
|
||||||
|
sample_collected = ('0);
|
||||||
|
end
|
||||||
|
endtask: add_sample
|
||||||
|
|
||||||
|
// Local function to flush data inside the filter
|
||||||
|
task flush_axi();
|
||||||
|
// Flush the possible empty place from last pacekt
|
||||||
|
for (int i = 0; i < FLUSH_CYCLE; i++) begin
|
||||||
|
add_sample('0, 1);
|
||||||
|
end
|
||||||
|
AxisIf_sample_bfm.put(packet_in.copy());
|
||||||
|
packet_in.empty();
|
||||||
|
endtask: flush_axi
|
||||||
|
|
||||||
|
// Local task: get one single sample when called.
|
||||||
|
// Automatically grab through AXI_BFM from DUT and pop out
|
||||||
|
task get_sample (output logic [IN_WIDTH-1:0] sample, input logic initialize = 0);
|
||||||
|
static logic [AXI_WIDTH-1:0] sample_collected = ('0);
|
||||||
|
// count up from 0 to NUM_SPC - 1
|
||||||
|
static int count_SPC = 0;
|
||||||
|
// count down from number of elements in the packet to 0
|
||||||
|
static int count_elements = 0;
|
||||||
|
|
||||||
|
// set both counters to end of range
|
||||||
|
if (initialize) begin
|
||||||
|
count_SPC = NUM_SPC-1;
|
||||||
|
count_elements = 0;
|
||||||
|
end
|
||||||
|
// get new vector from packet
|
||||||
|
if (count_SPC == NUM_SPC-1) begin
|
||||||
|
// get a new packet
|
||||||
|
if ( count_elements == 0 ) begin
|
||||||
|
AxisIf_sample_bfm.get(packet_out);
|
||||||
|
count_elements = packet_out.data.size();
|
||||||
|
end
|
||||||
|
sample_collected = packet_out.data.pop_front();
|
||||||
|
count_SPC = 0;
|
||||||
|
count_elements = count_elements - 1;
|
||||||
|
end else begin
|
||||||
|
count_SPC = count_SPC + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
sample = sample_collected [count_SPC*IN_WIDTH +: IN_WIDTH];
|
||||||
|
endtask: get_sample
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Test Process
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
initial begin : tb_main
|
||||||
|
|
||||||
|
string s;
|
||||||
|
$sformat(s, {
|
||||||
|
"tb_axi_fir_multisample_filter SPC= %0d NUM_COEFFS=%0d RELOADABLE=%0d ",
|
||||||
|
"BLANK_OUTPUT=%0d EMBEDDED_REGS=%0d"}, NUM_SPC, NUM_COEFFS,
|
||||||
|
RELOADABLE_COEFFS, BLANK_OUTPUT, USE_EMBEDDED_REGS_COEFFS);
|
||||||
|
|
||||||
|
// stop all clock events for simulation performance
|
||||||
|
axi_clk_gen.kill();
|
||||||
|
|
||||||
|
// Display testbench start message
|
||||||
|
test.start_tb(s);
|
||||||
|
axi_clk_gen.revive();
|
||||||
|
|
||||||
|
// Set stall probability
|
||||||
|
AxisIf_sample_bfm.set_master_stall_prob(STALL_PROB);
|
||||||
|
AxisIf_sample_bfm.set_slave_stall_prob(STALL_PROB);
|
||||||
|
AxisIf_coeff_bfm.set_master_stall_prob(STALL_PROB);
|
||||||
|
AxisIf_coeff_bfm.set_slave_stall_prob(STALL_PROB);
|
||||||
|
|
||||||
|
// Start the BFMs running
|
||||||
|
AxisIf_sample_bfm.run();
|
||||||
|
AxisIf_coeff_bfm.run();
|
||||||
|
|
||||||
|
// initialize variables
|
||||||
|
random_coeff_generation(COEFFS_VEC_RANDOM);
|
||||||
|
packet_in = new();
|
||||||
|
packet_out = new();
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Reset
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test.start_test("Wait for Reset", 10us);
|
||||||
|
reset_axi();
|
||||||
|
test.end_test();
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Initial load of coefficients VEC_0 (If USE_EMBEDDED_REGS_COEFFS enabled)
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
begin
|
||||||
|
automatic AxisPacket_reload packet_reload = new();
|
||||||
|
|
||||||
|
// If using embedded register, coefficients must be preloaded
|
||||||
|
if (USE_EMBEDDED_REGS_COEFFS) begin
|
||||||
|
test.start_test("Initial load of coefficients VEC_0", 10us);
|
||||||
|
// Generate packet which contains new coefficients and enqueue it for transfer
|
||||||
|
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]);
|
||||||
|
end
|
||||||
|
AxisIf_coeff_bfm.put(packet_reload);
|
||||||
|
AxisIf_coeff_bfm.wait_complete();
|
||||||
|
test.end_test();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// Test impulse response with default coefficients
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Sending an impulse should cause the coefficients to be output.
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
begin
|
||||||
|
logic signed [COEFF_WIDTH-1:0] i_coeff, i_samp_int;
|
||||||
|
string s;
|
||||||
|
|
||||||
|
test.start_test("Test impulse response (default coefficients)", 20us);
|
||||||
|
|
||||||
|
packet_in.empty();
|
||||||
|
|
||||||
|
// Send single sample to DUT
|
||||||
|
// Function will automatically packet and send
|
||||||
|
add_sample(16'h7FFF);
|
||||||
|
for (int i = 1; i < NUM_COEFFS; i++) begin
|
||||||
|
add_sample(16'h0000);
|
||||||
|
end
|
||||||
|
// Compensate the unfilled data in last group
|
||||||
|
add_sample('0, 1);
|
||||||
|
AxisIf_sample_bfm.put(packet_in.copy());
|
||||||
|
|
||||||
|
// Enqueue flushing packet and Residue to push the data out
|
||||||
|
packet_in.empty();
|
||||||
|
flush_axi();
|
||||||
|
|
||||||
|
// If BLANK_OUTPUT enabled, internal pipeline fullfilled.
|
||||||
|
// The correct output is supposed to apprear after.
|
||||||
|
// Keep grabbing until the first non_zero output
|
||||||
|
if (BLANK_OUTPUT == 0) begin
|
||||||
|
do begin
|
||||||
|
get_sample(i_samp_int, 0);
|
||||||
|
end while(i_samp_int== ('0));
|
||||||
|
end
|
||||||
|
|
||||||
|
// Correctness check
|
||||||
|
for (int i=0 ; i< NUM_COEFFS; i++) begin
|
||||||
|
if (BLANK_OUTPUT == 0 && i==0) begin
|
||||||
|
i_samp_int = i_samp_int; // verify the first one, which is already grabbed out
|
||||||
|
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]);
|
||||||
|
|
||||||
|
$sformat(
|
||||||
|
s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",
|
||||||
|
i, i_coeff, i_samp_int);
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
(i_samp_int == i_coeff) || (i_samp_int-1 == i_coeff) || (i_samp_int+1 == i_coeff),
|
||||||
|
s);
|
||||||
|
end
|
||||||
|
|
||||||
|
test.end_test();
|
||||||
|
end
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// Load random coefficients
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// If RELOADABLE_COEFFS disabled, skip the rest
|
||||||
|
if (RELOADABLE_COEFFS==1) begin
|
||||||
|
begin
|
||||||
|
automatic AxisPacket_reload packet_reload = new();
|
||||||
|
|
||||||
|
test.start_test("Load random coefficients", 10us);
|
||||||
|
// Generate packet which contains new coefficients and enqueue it for transfer
|
||||||
|
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_RANDOM[COEFF_WIDTH*i +: COEFF_WIDTH]);
|
||||||
|
end
|
||||||
|
AxisIf_coeff_bfm.put(packet_reload);
|
||||||
|
AxisIf_coeff_bfm.wait_complete();
|
||||||
|
|
||||||
|
test.end_test();
|
||||||
|
end
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// Test impulse response with random coefficients
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Sending an impulse should cause the coefficients to be output.
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
begin
|
||||||
|
logic signed [COEFF_WIDTH-1:0] i_coeff, i_samp_int;
|
||||||
|
string s;
|
||||||
|
|
||||||
|
test.start_test("Test impulse response (random coefficients)", 20us);
|
||||||
|
|
||||||
|
packet_in.empty();
|
||||||
|
|
||||||
|
// Send single sample to DUT
|
||||||
|
// Function will automatically packet and send
|
||||||
|
add_sample(16'h7FFF);
|
||||||
|
for (int i = 1; i < NUM_COEFFS; i++) begin
|
||||||
|
add_sample(16'h0000);
|
||||||
|
end
|
||||||
|
// Compensate the unfilled data in last group
|
||||||
|
add_sample('0, 1);
|
||||||
|
AxisIf_sample_bfm.put(packet_in.copy());
|
||||||
|
|
||||||
|
// Enqueue flushing packet and Residue to push the data out
|
||||||
|
packet_in.empty();
|
||||||
|
flush_axi();
|
||||||
|
|
||||||
|
// When BLANK_OUTPUT enabled, no extra ignore needed
|
||||||
|
if (BLANK_OUTPUT == 0) begin
|
||||||
|
do begin
|
||||||
|
get_sample(i_samp_int, 0);
|
||||||
|
end while(i_samp_int== ('0));
|
||||||
|
// Ignore the packet as the result of last flushing
|
||||||
|
end else begin
|
||||||
|
AxisIf_sample_bfm.get(packet_out);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Correctness check
|
||||||
|
for (int i=0 ; i< NUM_COEFFS; i++) begin
|
||||||
|
if (!(BLANK_OUTPUT == 0 && i==0)) begin
|
||||||
|
get_sample(i_samp_int, i == 0); // ask one single sample from output
|
||||||
|
end
|
||||||
|
i_coeff = $signed(COEFFS_VEC_RANDOM[COEFF_WIDTH*i +: COEFF_WIDTH]);
|
||||||
|
|
||||||
|
$sformat(
|
||||||
|
s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",
|
||||||
|
i, i_coeff, i_samp_int);
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
(i_samp_int == i_coeff) || (i_samp_int-1 == i_coeff) || (i_samp_int+1 == i_coeff),
|
||||||
|
s);
|
||||||
|
end
|
||||||
|
|
||||||
|
test.end_test();
|
||||||
|
end
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// Test step response with random coefficients
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
begin
|
||||||
|
logic signed [COEFF_WIDTH-1:0] i_samp_int;
|
||||||
|
string s;
|
||||||
|
int coeff_sum;
|
||||||
|
|
||||||
|
test.start_test("Test step response (random coefficients)", 20us);
|
||||||
|
|
||||||
|
packet_in.empty();
|
||||||
|
|
||||||
|
// Send single sample to DUT
|
||||||
|
// Function will automatically packet and send
|
||||||
|
for (int i = 0; i < NUM_COEFFS; i++) begin
|
||||||
|
add_sample(16'h7FFF);
|
||||||
|
end
|
||||||
|
// Compensate the unfilled data in last group
|
||||||
|
add_sample('0, 1);
|
||||||
|
AxisIf_sample_bfm.put(packet_in.copy());
|
||||||
|
|
||||||
|
// Enqueue flushing packet and Residue to push the data out
|
||||||
|
packet_in.empty();
|
||||||
|
flush_axi();
|
||||||
|
|
||||||
|
// When BLANK_OUTPUT enabled, no extra ignore needed
|
||||||
|
if (BLANK_OUTPUT == 0) begin
|
||||||
|
do begin
|
||||||
|
get_sample(i_samp_int, 0);
|
||||||
|
end while(i_samp_int== ('0));
|
||||||
|
// Ignore the packet as the result of last flushing
|
||||||
|
end else begin
|
||||||
|
AxisIf_sample_bfm.get(packet_out);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Correctness check
|
||||||
|
for (int i=0 ; i< NUM_COEFFS; i++) begin
|
||||||
|
if (BLANK_OUTPUT == 0 && i==0) begin
|
||||||
|
i_samp_int = i_samp_int; // verify the first one, which is already grabbed out
|
||||||
|
end else begin
|
||||||
|
get_sample(i_samp_int, i == 0); // ask one single sample from output
|
||||||
|
end
|
||||||
|
coeff_sum += $signed(COEFFS_VEC_RANDOM[COEFF_WIDTH*i +: COEFF_WIDTH]);
|
||||||
|
|
||||||
|
$sformat(
|
||||||
|
s, "Incorrect I value received on sample %0d! Expected: %0d, Received: %0d",
|
||||||
|
i, coeff_sum, i_samp_int);
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
(i_samp_int == coeff_sum) || (i_samp_int-1 == coeff_sum) || (i_samp_int+1 == coeff_sum),
|
||||||
|
s);
|
||||||
|
end
|
||||||
|
|
||||||
|
test.end_test();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// All done!
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// End the testbench and stop the clock module
|
||||||
|
test.end_tb(0);
|
||||||
|
axi_clk_gen.kill();
|
||||||
|
|
||||||
|
end : tb_main
|
||||||
|
endmodule
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axi_fir_multisample_filter_tb_wrapper
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// testing various configurations of the filter
|
||||||
|
//
|
||||||
|
|
||||||
|
module axi_fir_multisample_filter_tb_wrapper();
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Local Parameters
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
timeunit 1ns / 1ps;
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// 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
|
||||||
Reference in New Issue
Block a user