System Verilog Unit Testing Framework (SvUTest) is an open source framework for performing rapid sanity testing of lightweight hardware modules in System Verilog. This project is inspired by CppUTest, Google Test and UVM.
- Rapid test case development
- Concurrent regressions
- Consolidation of regression status
- Inbuilt support for interface protocols like valid-ready and credit-data
SvUTest is meant to help RTL designers ensure the basic sanity of small designs whose outputs can be predicted for a given set of inputs. This is not a replacement for UVM based tests.
Let's say we need to test a module that multiplies two floating point numbers:
package floatmul_pkg;
typedef struct packed {
logic sign;
logic [7:0] exponent;
logic [23:0] mantissa;
} float32_t;
endpackage
module floatmul
import floatmul_pkg::*;
(
input logic clk,
input logic rst,
output logic busy,
input logic a_valid,
input float32_t a_data,
output logic a_ready,
input logic b_valid,
input float32_t b_data,
output logic b_ready,
output logic o_valid,
output float32_t o_data,
input logic o_ready
);
// Implementation
a_ready = /* .. */;
b_ready = /* .. */;
o_valid = /* .. */;
o_data = /* .. */
endmoduleWe start by creating a test_top module which has a single interface port of type svutest_test_ctrl_if.target and a single type parameter T_test_case. It instantiates the DUT and calls the run() method of T_Test_case:
module floatmul_test_top
import floatmul_pkg::*;
#(
type T_test_case = bit // Test case
)(
svutest_test_ctrl_if.target tc // Test control from the regress suite
);
// DUT control. Injects clock + rst, and collects test-done
svutest_dut_ctrl_if dc ();
// Input and output interfaces to DUT
svutest_req_payload_rsp_if#(float32_t) a (dc.clk, dc.rst);
svutest_req_payload_rsp_if#(float32_t) b (dc.clk, dc.rst);
svutest_req_payload_rsp_if#(float32_t) o (dc.clk, dc.rst);
logic busy;
// Instantiate the
floatmul u_fmul (
.clk (dc.clk),
.rst (dc.rst),
.busy (busy),
.a_valid (a.req),
.a_data (a.req_payload),
.a_ready (a.rsp),
.b_valid (b.req),
.b_data (b.req_payload),
.b_ready (b.rsp),
.o_valid (o.req),
.o_data (o.req_payload),
.o_ready (o.rsp)
);
// Set this signal once the test is done
always_comb dc.done = ~(a.req | b.req | o.req | busy);
initial begin
// Instantiate the test case, pass the test_ctrl and dut_ctrl ifs,
// along other inputs and outputs. And call run().
T_test_case test = new(tc, dc, a, b, o);
test.run();
end
endmoduleOnce the test top is set up, we need to create a test case base class per dut, derived from svutest_pkg::test_case, that accepts the test_ctrl, dut_ctrl and other interfaces:
class floatmul_utest extends svutest_pkg::test_case;
valid_data_ready_injector#(float32_t) m_a_injector; // Built-in injector for valid-ready protocol (input a)
valid_data_ready_injector#(float32_t) m_b_injector; // Injector for input b
valid_data_ready_extractor#(float32_t) m_o_extractor; // Extractor for output c
function new (
virtual svutest_test_ctrl_if.target vif_test_ctrl, // Test Ctrl
virtual svutest_dut_ctrl_if.driver vif_dut_ctrl, // Dut Ctrl
virtual svutest_req_payload_rsp_if#(float32_t).driver vif_a, // a
virtual svutest_req_payload_rsp_if#(float32_t).driver vif_b, // b
virtual svutest_req_payload_rsp_if#(float32_t).target vif_o, // c
string test_case_name // Test name
);
// Pass test-ctrl, dut_ctrl and test_name to svutest_pkg::test_case::new()
super.new(vif_test_ctrl, vif_dut_ctrl, $sformatf("fmul:%0s", test_case_name));
// Create injectors and extractors
m_a_injector = new(vif_a);
m_b_injector = new(vif_b);
m_o_extractor = new(vif_o);
// And register them with the test case
this.add(m_a_injector);
this.add(m_b_injector);
this.add(m_o_extractor);
endfunction
endclassThe next step is to create a test case class per input-output pattern, derived from the base class:
class floatmul_utest_0_0 extends floatmul_utest;
function new (
virtual svutest_test_ctrl_if.target vif_test_ctrl,
virtual svutest_dut_ctrl_if.driver vif_dut_ctrl,
virtual svutest_req_payload_rsp_if#(float32_t).driver vif_a,
virtual svutest_req_payload_rsp_if#(float32_t).driver vif_b,
virtual svutest_req_payload_rsp_if#(float32_t).target vif_o
);
super.new(vif_test_ctrl, vif_dut_ctrl, vif_a, vif_b, vif_o, "0_0");
endfunction
// Override the populate() virtual function to set up the transcations
// that will be injected per interface. This is a function that is
// run once before actual simulation
function void populate ();
m_a_injector.put('{ sign: 1'b0, exponent: '0, mantissa: '0 });
m_b_injector.put('{ sign: 1'b0, exponent: '0, mantissa: '0 });
endfunction
// Override the check() virtual function to check the number of
// outputs emitted and the expectation of each output
function void check ();
// Get the extractor's internal queue
float32_t queue [$] = m_o_extractor.get_queue();
// Check the number of entries emitted from the output
`SVUTEST_ASSERT_EQ(queue.size(), 1)
// Check the first entry
`SVUTEST_ASSERT_EQ(queue[0], '1)
endfunction
endclassThe last step is to create a top module that instantiates the test cases and runs them:
module regress_top;
import svutest_pkg::*;
import floatmul_utest_pkg::*;
// Create the test_ctrl interface
svutest_test_ctrl_if i_floatmul_test2_0_0 ();
// Instantiate the test_top while passing the test_ctrl interface
floatmul_test_top#(floatmul_test2_0_0) u_floatmul_test2_0_0 (i_floatmul_test2_0_0);
// Another test case, using a macro that does the above in one line
`SVUTEST(floatmul_test_top, floatmul_test2_012_012)
initial begin
// Create a test-list
test_list list = new();
// Add the two test cases to the list by passing the interfaces to add()
list.add(i_floatmul_test2_0_0);
list.add(i_floatmul_test2_012_012);
// Run all tests in the test list
list.run();
end
endmoduleThe run() task of test_list outputs one line of summary per test case, showing the number of assertions that failed in that test case. A complete summary will be printed at the end. Any failure from SVUTEST_ASSERT_EQ macros will get printed in additional lines:
7000 | fmul:0_0> SVUTEST_ASSERT_EQ failed: /usr2/nvettuva/SvUTest/examples/001_floatmul/floatmul_utest_pkg.sv,58: Expected == 0x0, actual == 0x1
7000 | fmul:0_0> COMPLETE. Assertions: 1/2 [FAIL]
15000 | fmul:012_012> SVUTEST_ASSERT_EQ failed: /usr2/nvettuva/SvUTest/examples/001_floatmul/floatmul_utest_pkg.sv,108: Expected == 0x7f000000, actual == 0x0
15000 | fmul:012_012> SVUTEST_ASSERT_EQ failed: /usr2/nvettuva/SvUTest/examples/001_floatmul/floatmul_utest_pkg.sv,110: Expected == 0x7f000000, actual == 0x80000000
15000 | fmul:012_012> SVUTEST_ASSERT_EQ failed: /usr2/nvettuva/SvUTest/examples/001_floatmul/floatmul_utest_pkg.sv,111: Expected == 0x80000000, actual == 0x0
15000 | fmul:012_012> SVUTEST_ASSERT_EQ failed: /usr2/nvettuva/SvUTest/examples/001_floatmul/floatmul_utest_pkg.sv,113: Expected == 0x80000000, actual == 0x81000000
15000 | fmul:012_012> COMPLETE. Assertions: 6/10 [FAIL]
15000 | Status: FAIL | Total: 11, Unresponsive: 0, Timeout: 0, Unchecked: 0, Fail: 2, Pass: 9
SvUTest framework contains 2 source files:
src/svutest_if.sv
src/svutest_pkg.sv
and a few header files defined under src:
src/svutest_defines.svh
...
The source files must be compiled in the order specified above by the user's eda tool like while the header file is typically picked up by providing the include path. A typical invocation from the command line would be:
<tool> src/svutest_if.sv src/svutest_pkg.sv <include_path_flag> src path_to_other_files
See guide.md for more information.
See CONTRIBUTING.md.
- Report an Issue on GitHub
- Open a Discussion on GitHub
- E-mail us for general questions
SvUTest is licensed under the BSD-3-clause License. See LICENSE.txt for the full license text.