fpga: rfnoc: Add align_samples testbench
Original-commit: f6f1dec85adcea491104b07cf830e06a61feb595
This commit is contained in:
committed by
Wade Fife
parent
292d31fc52
commit
d451d74448
@@ -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
|
||||
Reference in New Issue
Block a user