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