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 \
|
||||
tx_frontend.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