Initialize test_status for backwards compatability with testbenches that don't call start_tb(). Original-commit: 72295360e65d989dd774deabc4add0a54eb6f97e
299 lines
9.7 KiB
Systemverilog
299 lines
9.7 KiB
Systemverilog
//
|
|
// Copyright 2019 Ettus Research, A National Instruments Company
|
|
//
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
//
|
|
// Module: PkgTestExec
|
|
//
|
|
// Description: This package provides infrastructure for tracking the state of
|
|
// testbench execution and the results of each test.
|
|
//
|
|
|
|
|
|
|
|
package PkgTestExec;
|
|
|
|
timeunit 1ns;
|
|
timeprecision 1ps;
|
|
|
|
typedef enum { SEV_INFO, SEV_WARNING, SEV_ERROR, SEV_FATAL } severity_t;
|
|
|
|
typedef std::process timeout_t;
|
|
|
|
class TestExec;
|
|
|
|
string tb_name; // Name of the testbench
|
|
string current_test; // Name of the current test being run
|
|
int num_started, num_finished; // Number of tests started and finished
|
|
int num_passed; // Number of tests that have passed
|
|
int num_assertions; // Number of assertions checked for the current test
|
|
time start_time, end_time; // Start and end time of the testbench
|
|
bit stop_on_error = 1; // Configuration option to stop when an error occurs
|
|
bit done = 0; // Flag that sets when tb is finished
|
|
logic test_status[$] = { 1'bX }; // Pass/fail of each test where index matches test number
|
|
|
|
timeout_t tb_timeout; // Handle to timeout for the overall testbench
|
|
timeout_t test_timeout; // Handle to timeout for current test
|
|
|
|
semaphore test_sem; // Testbench semaphore
|
|
|
|
|
|
function new();
|
|
$timeformat(-9, 0, " ns", 12);
|
|
this.test_sem = new(1);
|
|
endfunction : new
|
|
|
|
|
|
// Call start_tb() at the start of a testbench to initialize state tracking
|
|
// properties.
|
|
//
|
|
// time_limit: Max simulation time allowed before at timeout occurs.
|
|
// This is a catch-all, in case things go very wrong during
|
|
// simulation and cause it to never end.
|
|
//
|
|
task start_tb(string tb_name, realtime time_limit = 10ms);
|
|
// Get the sempahore, to prevent multiple overlapping instances of the
|
|
// same testbench.
|
|
test_sem.get();
|
|
done = 0;
|
|
$display("========================================================");
|
|
$display("TESTBENCH STARTED: %s", tb_name);
|
|
$display("========================================================");
|
|
this.tb_name = tb_name;
|
|
start_time = $time;
|
|
test_status = { 1'bX }; // There is no test 0, so set it to X
|
|
num_started = 0;
|
|
num_finished = 0;
|
|
num_passed = 0;
|
|
start_timeout(
|
|
tb_timeout,
|
|
time_limit,
|
|
$sformatf("Testbench \"%s\" time limit exceeded", tb_name),
|
|
SEV_FATAL
|
|
);
|
|
endtask : start_tb
|
|
|
|
|
|
// Call end_tb() at the end of a testbench to report final statistics and,
|
|
// optionally, end simulation.
|
|
//
|
|
// finish: Set to 1 (default) to cause $finish() to be called at the
|
|
// end of simulation, cuasing the simulator to close.
|
|
//
|
|
function void end_tb(bit finish = 1);
|
|
assert (num_started == num_finished) else begin
|
|
$fatal(1, "Testbench ended before test completed");
|
|
end
|
|
end_time = $time;
|
|
$display("========================================================");
|
|
$display("TESTBENCH FINISHED: %s", tb_name);
|
|
$display(" - Time elapsed: %0t", end_time - start_time);
|
|
$display(" - Tests Run: %0d", num_finished);
|
|
$display(" - Tests Passed: %0d", num_passed);
|
|
$display(" - Tests Failed: %0d", num_started - num_passed);
|
|
$display("Result: %s",
|
|
(num_started == num_passed) && (num_finished > 0)
|
|
? "PASSED " : "FAILED!!!");
|
|
$display("========================================================");
|
|
|
|
end_timeout(tb_timeout);
|
|
|
|
done = 1;
|
|
if (finish) $finish();
|
|
|
|
// Release the semaphore to allow new instances of the testbench to run
|
|
test_sem.put();
|
|
endfunction : end_tb
|
|
|
|
|
|
// Call at the start of each test with the name of the test.
|
|
//
|
|
// test_name: String name for the test to be started
|
|
//
|
|
task start_test(string test_name, realtime time_limit = 0);
|
|
// Make sure a there isn't already a test running
|
|
assert (num_started == num_finished) else begin
|
|
$fatal(1, "Test started before completing previous test");
|
|
end
|
|
|
|
// Create a timeout for this test
|
|
if (time_limit > 0) begin
|
|
start_timeout(
|
|
test_timeout,
|
|
time_limit,
|
|
$sformatf("Test \"%s\" time limit exceeded", test_name),
|
|
SEV_FATAL
|
|
);
|
|
end
|
|
|
|
current_test = test_name;
|
|
num_started++;
|
|
$display("[TEST CASE %3d] (t = %t) BEGIN: %s...", num_started, $time, test_name);
|
|
test_status.push_back(1); // Set status to 1 (passed) initially
|
|
num_assertions = 0;
|
|
endtask : start_test
|
|
|
|
|
|
// Call end_test() at the end of each test.
|
|
//
|
|
// test_result: Optional value to indicate the overall pass/fail result
|
|
// of the test. Use non-zero for pass, 0 for fail.
|
|
//
|
|
task end_test(int test_result = 1);
|
|
bit passed;
|
|
|
|
assert (num_started == num_finished + 1) else begin
|
|
$fatal(1, "No test running");
|
|
end
|
|
|
|
end_timeout(test_timeout);
|
|
|
|
passed = test_status[num_started] && test_result;
|
|
num_finished++;
|
|
$display("[TEST CASE %3d] (t = %t) DONE... %s",
|
|
num_started, $time, passed ? "Passed" : "FAILED");
|
|
|
|
if (passed) num_passed++;
|
|
|
|
current_test = "";
|
|
endtask : end_test
|
|
|
|
|
|
// Assert the given expression and call $error() if it fails. Simulation
|
|
// will also be stopped (using $stop) if stop_on_error is true.
|
|
//
|
|
// expr: The expression value to be asserted
|
|
// message: String to report if the assertion fails
|
|
//
|
|
function void assert_error(int expr, string message = "");
|
|
num_assertions++;
|
|
assert (expr) else begin
|
|
test_status[num_started] = 0;
|
|
$error(message);
|
|
if (stop_on_error) $stop;
|
|
end
|
|
endfunction : assert_error
|
|
|
|
|
|
// Assert the given expression and call $fatal() if it fails.
|
|
//
|
|
// expr: The expression value to be asserted
|
|
// message: String to report if the assertion fails
|
|
//
|
|
function void assert_fatal(int expr, string message = "");
|
|
num_assertions++;
|
|
assert (expr) else begin
|
|
test_status[num_started] = 0;
|
|
$fatal(1, message);
|
|
end
|
|
endfunction : assert_fatal
|
|
|
|
|
|
// Assert the given expression and call $warning() if it fails.
|
|
//
|
|
// expr: The expression value to be asserted
|
|
// message: String to report if the assertion fails
|
|
//
|
|
function void assert_warning(int expr, string message = "");
|
|
num_assertions++;
|
|
assert (expr) else begin
|
|
$warning(message);
|
|
end
|
|
endfunction : assert_warning
|
|
|
|
|
|
// Assert the given expression and call the appropriate severity or fatal
|
|
// task if the expression fails.
|
|
//
|
|
// expr: The expression value to be asserted
|
|
// message: String to report if the assertion fails
|
|
// severity: Indicates the type of severity task that should be used if
|
|
// the assertion fails ($info, $warning, $error, $fatal).
|
|
// Default value is SEV_ERROR.
|
|
//
|
|
function void assert_sev(
|
|
int expr,
|
|
string message = "",
|
|
severity_t severity = SEV_ERROR
|
|
);
|
|
num_assertions++;
|
|
assert (expr) else begin
|
|
case (severity)
|
|
SEV_INFO: begin
|
|
$info(message);
|
|
end
|
|
SEV_WARNING: begin
|
|
$warning(message);
|
|
end
|
|
SEV_ERROR: begin
|
|
$error(message);
|
|
test_status[num_started] = 0;
|
|
if (stop_on_error) $stop;
|
|
end
|
|
default: begin // SEV_FATAL
|
|
test_status[num_started] = 0;
|
|
$fatal(1, message);
|
|
end
|
|
endcase
|
|
end
|
|
endfunction : assert_sev
|
|
|
|
|
|
// Create a timeout that will expire after the indicated delay, causing an
|
|
// error if the timeout is not canceled before the delay has elapsed.
|
|
//
|
|
// handle: A handle to the timeout process created. Use this
|
|
// handle to end the timeout.
|
|
// timeout_delay: Amount of time to wait before the timeout expires.
|
|
// message: String message to display if the timeout expires.
|
|
// severity: Indicates the type of severity task that should be
|
|
// used if the timeout expires. Default is SEV_ERROR.
|
|
//
|
|
task start_timeout(
|
|
output timeout_t handle,
|
|
input realtime timeout_delay,
|
|
input string message = "Timeout",
|
|
input severity_t severity = SEV_ERROR
|
|
);
|
|
mailbox #(std::process) pid = new(1);
|
|
fork
|
|
begin : timeout
|
|
pid.put(process::self());
|
|
#(timeout_delay);
|
|
assert_sev(0, {"Timeout: ", message}, severity);
|
|
end
|
|
join_none
|
|
#0; // Start the timeout process so we can get its process ID
|
|
|
|
// Return the process ID
|
|
pid.get(handle);
|
|
endtask
|
|
|
|
|
|
// Cancel the timeout with the given handle. This kills the process
|
|
// running the timeout delay.
|
|
//
|
|
// handle: Handle created by start_timeout() for the timeout process.
|
|
//
|
|
function void end_timeout(timeout_t handle);
|
|
if (handle != null)
|
|
if (handle.status != process::FINISHED) handle.kill();
|
|
endfunction;
|
|
|
|
endclass : TestExec
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Test Object
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// This is the default TestExec object instance shared by all instances of
|
|
// the running the testbench.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
TestExec test = new();
|
|
|
|
endpackage : PkgTestExec
|
|
|