fpga: rfnoc: Add align_samples testbench

Original-commit: f6f1dec85adcea491104b07cf830e06a61feb595
This commit is contained in:
Larissa Phillip
2023-01-12 21:42:13 -06:00
committed by Wade Fife
parent 292d31fc52
commit d451d74448
3 changed files with 513 additions and 0 deletions
@@ -0,0 +1,39 @@
#
# Copyright 2022 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
DESIGN_SRCS += $(abspath \
$(abspath ../align_samples.sv) \
)
#-------------------------------------------------
# Testbench Specific
#-------------------------------------------------
SIM_TOP = align_samples_all_tb
SIM_SRCS = \
$(abspath align_samples_tb.sv) \
$(abspath align_samples_all_tb.sv) \
#-------------------------------------------------
# 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,55 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: align_samples_all_tb
//
// Description:
//
// Top-level testbench for align_samples, testing different configurations of
// the module.
//
module align_samples_all_tb;
//---------------------------------------------------------------------------
// Test Definitions
//---------------------------------------------------------------------------
typedef struct {
int SAMP_W;
int SPC;
int USER_W;
bit PIPE_IN;
bit PIPE_OUT;
} test_config_t;
localparam NUM_TESTS = 6;
localparam test_config_t tests[NUM_TESTS] = '{
'{ SAMP_W: 8, SPC: 8, USER_W: 8, PIPE_IN: 0, PIPE_OUT: 0},
'{ SAMP_W: 8, SPC: 4, USER_W: 8, PIPE_IN: 1, PIPE_OUT: 1},
'{ SAMP_W: 8, SPC: 4, USER_W: 8, PIPE_IN: 1, PIPE_OUT: 0},
'{ SAMP_W: 8, SPC: 4, USER_W: 8, PIPE_IN: 0, PIPE_OUT: 1},
'{ SAMP_W: 8, SPC: 2, USER_W: 8, PIPE_IN: 0, PIPE_OUT: 0},
'{ SAMP_W: 16, SPC: 2, USER_W: 16, PIPE_IN: 0, PIPE_OUT: 0}
};
//---------------------------------------------------------------------------
// DUT Instances
//---------------------------------------------------------------------------
genvar i;
for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config
align_samples_tb #(
.SAMP_W (tests[i].SAMP_W ),
.SPC (tests[i].SPC ),
.USER_W (tests[i].USER_W ),
.PIPE_IN (tests[i].PIPE_IN ),
.PIPE_OUT(tests[i].PIPE_OUT)
) align_samples_tb_i ();
end : gen_test_config
endmodule : align_samples_all_tb
@@ -0,0 +1,419 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: align_samples_tb
//
// Description:
//
// Testbench for align_samples.
//
`default_nettype none
module align_samples_tb #(
parameter int SAMP_W = 8,
parameter int SPC = 4,
parameter int USER_W = 8,
parameter bit PIPE_IN = 1,
parameter bit PIPE_OUT = 1
) ();
// Include macros and time declarations for use with PkgTestExec
`include "test_exec.svh"
import PkgTestExec::*;
import PkgRandom::*;
localparam real CLK_PERIOD = 10.0;
localparam bit USE_RANDOM = 1; // Use random vs. sequential data.
localparam int DATA_W = SPC*SAMP_W;
localparam int SHIFT_W = $clog2(DATA_W/SAMP_W);
localparam int NUM_TESTS = 1000;
typedef logic [SPC-1:0][SAMP_W-1:0] data_t;
// Store data and validity of checker
typedef struct packed {
logic valid;
logic [DATA_W-1:0] data;
logic [USER_W-1:0] user;
} v_data_t;
typedef v_data_t queue_t [$];
// Bit to indicate done sending tests
bit done = 0;
// Total number of words input and tested
int in_count = 0;
int out_count = 0;
// Indicate first word of a test
bit first = 0;
bit first_reg = 0;
//---------------------------------------------------------------------------
// Clocks and Resets
//---------------------------------------------------------------------------
bit clk;
bit rst;
sim_clock_gen #(.PERIOD(CLK_PERIOD), .AUTOSTART(0))
clk_gen (.clk(clk), .rst(rst));
//---------------------------------------------------------------------------
// Device Under Test (DUT)
//---------------------------------------------------------------------------
// Input data signals
data_t i_data;
logic [ USER_W-1:0] i_user;
bit [SHIFT_W-1:0] i_shift;
bit i_dir;
bit i_push;
bit i_cfg_en;
// Output data signals
data_t o_data;
logic [USER_W-1:0] o_user;
align_samples #(
.SAMP_W (SAMP_W ),
.SPC (SPC ),
.USER_W (USER_W ),
.PIPE_IN (PIPE_IN ),
.PIPE_OUT(PIPE_OUT)
) align_samples_dut (
.clk (clk ),
.i_data (i_data ),
.i_user (i_user ),
.i_push (i_push ),
.i_dir (i_dir ),
.i_shift (i_shift ),
.i_cfg_en(i_cfg_en),
.o_data (o_data ),
.o_user (o_user )
);
//---------------------------------------------------------------------------
// Tests
//---------------------------------------------------------------------------
// Send test data
task automatic test_data(bit dir, bit [SHIFT_W-1:0] shift, int num_words);
// Counts to assign sequential data
int data_count = 0;
int user_count = 0;
// Sync with rising clock edge
clk_gen.clk_wait_r();
// Set shift and direction only on first rising edge of clk
i_shift <= shift;
i_dir <= dir;
i_cfg_en <= 1;
clk_gen.clk_wait_r();
// Shift and direction will not be read after first rising edge of clk
i_shift <= 1'bX;
i_dir <= 'X;
i_cfg_en <= 0;
// Send number of words based on argument
for (int word_cnt=0; word_cnt < num_words;) begin
// 50% probability of changing push
if ($urandom_range(0,1)) begin
i_push <= 1;
first <= (word_cnt == 0); // Indicate first word of test
word_cnt++; // Increment word_cnt only when i_push is asserted to indicate a word was sent
in_count++;
// Assign random data
if (USE_RANDOM) begin
i_data <= Rand#(DATA_W)::rand_logic();
i_user <= Rand#(USER_W)::rand_bit();
end else begin
// Give the data an incrementing count
i_user <= user_count++;
for(int i=0; i < SPC; i++) begin
i_data[i] <= data_count++;
end
end
end else begin
i_push <= 0;
i_data <= 'X;
i_user <= 'X;
end
clk_gen.clk_wait_r();
// Reset data values at end of test
i_push <= 0;
i_data <= 'X;
i_user <= 'X;
end
endtask: test_data
// Generate test data with random shifting configuration
task automatic run_gen();
bit dir = 0; // Random direction
bit [SHIFT_W-1:0] shift = 0; // Random shift in range 0:SPC-1
int num_words = 0; // Random number of words to send per test
in_count = 0; // Reset input word count
repeat(NUM_TESTS) begin
dir = $urandom_range(0, 1);
shift = $urandom_range(0, SPC-1);
num_words = $urandom_range(1, 5);
test_data(dir, shift, num_words);
end
// Indicate done generating tests
done = 1;
$display("Input %0d words", in_count);
endtask : run_gen
// Check valid output data against expected data
task automatic run_check();
// Queue to hold expected data for checker
queue_t data_q;
// Config signals
bit dir = 0;
bit [SHIFT_W-1:0] shift = 0;
// Data comparison signals
v_data_t val_data; // Struct to hold valid, data and user for queue
v_data_t queue_data; // Struct to hold valid, data and user for queue
data_t exp_data = 'X; // Expected data to compare to output data
logic [USER_W-1:0] exp_user = 'X; // Expected user data to compare to output user data
data_t i_prev_data = 'X; // Carry previous input data
data_t o_prev_data = 'X; // Carry previous output data
logic [USER_W-1:0] i_prev_user = 'X; // Carry previous output user data
logic [USER_W-1:0] o_prev_user = 'X; // Carry previous output user data
// Latency
int push_cnt = 0; // Count number of times i_push is asserted
int val_hold = 0; // Hold data as invalid for corner cases
int latency = 0;
out_count = 0; // Reset checked word count
// Latency based on input and output pipeline parameters
// Add extra cycle of latency for right shift
latency = (PIPE_IN && PIPE_OUT) ? 2 :
(PIPE_IN ^ PIPE_OUT) ? 1 :
0;
// Give initial values to queue
val_data.data = 'X;
val_data.user = 'X;
val_data.valid = 1'b0;
// If additional latency from pipeline, queue additional data
if (latency == 2) begin
data_q.push_back(val_data);
end
while (!done) begin // Ideally check until last value
// Compare output data to expected data only if expected data is valid
if (val_data.valid) begin
data_t act_data; // Actual data output
logic [USER_W-1:0] act_user; // Actual user output
out_count++;
// If no pipeline delay, compare expected data with output data from
// previous clock cycle.
if (latency == 0) begin
act_data = o_prev_data;
act_user = o_prev_user;
end else begin
act_data = o_data;
act_user = o_user;
end
if (dir == 0 && first_reg == 1 && first == 1) begin
// If first word is left shifted, only check data shifted to account
// for ModelSim/Vivado behavior.
for (int val_check = SPC-1; val_check > shift; val_check--) begin
`ASSERT_ERROR(exp_data[val_check] === act_data[val_check],
$sformatf({"Output data sample %0d not equal to expected. ",
"Expected 0x%X Read 0x%X"},
val_check, exp_data[val_check], act_data[val_check]));
end
end else begin
`ASSERT_ERROR(exp_data === act_data,
$sformatf({"Output data not equal to expected data. ",
"Expected 0x%X Read 0x%X"}, exp_data, act_data));
end
`ASSERT_ERROR(exp_user === act_user,
$sformatf({"Output user data not equal to expected user data. ",
"Expected 0x%X Read 0x%X"}, exp_user, act_user));
end
// Check input if config changed and set expected data to invalid
if (i_cfg_en) begin
// Check corner cases
if (i_dir == 0 && dir == 1) begin // Right to Left
val_hold = 1;
end else if (!i_dir && !dir) begin // Left to Left
if (i_shift == 1 && shift == 3) begin // Left 3 to Left 1
val_hold = 1;
end else if (i_shift == 1 && shift == 2) begin // Left 2 to Left 1
val_hold = 1;
end else if (i_shift == 2 && shift == 1) begin // Left 1 to Left 2
val_hold = 1;
end
end else if (i_dir && dir) begin // Right to Right
if (i_shift == 2 && shift == 3) begin // Right 3 to Right 2
val_hold = 1;
end
end else begin
val_hold = 0;
end
// If no pipeline delay, config will not disable expected data
if (latency == 0) begin
if (i_dir && dir) begin // Right to Right
if (i_shift == 2 && shift == 3) begin // Right 3 to Right 2
val_data.valid = 1'b0;
end
end else if (i_dir) begin
val_data.valid = 1'b0;
end
end else begin
val_data.valid = 1'b0;
end
// Update config signals
shift = i_shift;
dir = i_dir;
push_cnt = 0;
end
// Check input if push was asserted
if (i_push) begin
// Calculate new expected data and user
exp_data = (shift == 0) ? i_data :
(dir == 1) ? (i_prev_data >> (shift*SAMP_W)) | (i_data << ((SPC-shift) * SAMP_W)) :
(i_data << (shift*SAMP_W)) | (i_prev_data >> ((SPC-shift) * SAMP_W));
val_data.data = exp_data;
exp_user = i_user;
val_data.user = exp_user;
// Update validity of checker
if (latency == 0) begin
val_data.valid = push_cnt >= (val_hold + dir) ? 1'b1 : 1'b0;
end else begin
val_data.valid = (push_cnt >= val_hold + latency + dir - 1) ? 1'b1 : 1'b0;
end
// Store data to carry
i_prev_data = i_data;
o_prev_data = o_data;
first_reg = first;
i_prev_user = i_user;
o_prev_user = o_user;
// Push new expected data to queue
data_q.push_back(val_data);
// Pop next expected value off queue
queue_data = data_q.pop_front();
exp_data = queue_data.data;
exp_user = queue_data.user;
push_cnt++;
end
clk_gen.clk_wait_r();
end
$display("Verified %0d words on the output", out_count);
endtask : run_check
//---------------------------------------------------------------------------
// Main Test Process
//---------------------------------------------------------------------------
initial begin : tb_main
string tb_name;
tb_name = $sformatf( {
"align_samples_tb\n",
"SAMP_W = %03d\n",
"SPC = %03d\n",
"USER_W = %03d\n",
"PIPE_IN = %03d\n",
"PIPE_OUT = %03d"},
SAMP_W, SPC, USER_W, PIPE_IN, PIPE_OUT
);
test.start_tb(tb_name, 100ms);
// Don't start the clocks until after start_tb() returns. This ensures that
// the clocks aren't toggling while other instances of this testbench are
// running, which speeds up simulation time.
clk_gen.start();
//--------------------------------
// Reset
//--------------------------------
test.start_test("Reset", 1ms);
clk_gen.reset();
if (rst) @rst;
test.end_test();
//--------------------------------
// Test Sequences
//--------------------------------
test.start_test("Generate and check data", 1ms);
fork
run_gen();
run_check();
join
test.end_test();
//--------------------------------
// Finish Up
//--------------------------------
// End the TB, but don't $finish, since we don't want to kill other
// instances of this testbench that may be running.
test.end_tb(0);
// Kill the clocks to end this instance of the testbench
clk_gen.kill();
end : tb_main
endmodule : align_samples_tb
`default_nettype wire