diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/Makefile b/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/Makefile new file mode 100644 index 0000000..fb402e9 --- /dev/null +++ b/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/Makefile @@ -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 diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/align_samples_all_tb.sv b/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/align_samples_all_tb.sv new file mode 100644 index 0000000..d29a634 --- /dev/null +++ b/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/align_samples_all_tb.sv @@ -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 diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/align_samples_tb.sv b/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/align_samples_tb.sv new file mode 100644 index 0000000..a482f37 --- /dev/null +++ b/lib/rfnoc/blocks/rfnoc_block_radio/align_samples_tb/align_samples_tb.sv @@ -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