What happens when you start your simulation with a UVM test bench? Where should you put the uvm_config_db::set() calls to send the virtual interface to the test class? Are there potential race conditions? And what happens when your test is over?
Let’s Get This Started
module hdl_top; logic clk, rst; tx_ifc tx_if (clk, reset); // Instantiate the tx_ifc interface rx_ifc rx_if (clk, reset); // Instantiate the rx_ifc interface dut i0 (tx_if, rx_if); initial begin uvm_config_db#(virtual tx_ifc)::set(null, "uvm_test_top", "tx_if", tx_if); uvm_config_db#(virtual rx_ifc)::set(null, "uvm_test_top", "rx_if", rx_if); end endmodule
Over in the test module, you start the UVM code.
module hvl_top; import uvm_pkg::*; import tx_pkg::*; // tx definitions import rx_pkg::*; // rx definitions initial run_test(); // Start UVM endmodule
You can also put the uvm_config_db::set() calls in the testbench module. Just make sure they are before the call to run_test(), or in a separate initial block. The final argument to set() is the value of the virtual interface, which is a hierarchical name.
initial begin uvm_config_db#(virtual tx_ifc)::set(null, "uvm_test_top", "tx_if", hdl_top.tx_if); uvm_config_db#(virtual rx_ifc)::set(null, "uvm_test_top", "rx_if", hdl_top.rx_if); run_test(); // Start UVM end
Going the Distance
If there are two initial blocks, is there a race condition? Could your test class call uvm_config_db::get() before the set() call? Good news – the UVM developers anticipated this problem and added a #0 delay before your test code starts. As a result, all the initial and always blocks across the simulation set the uvm_config_db entries before the test class tries to get them.
Here is a little more detail. The UVM global method run_test() constructs a uvm_root object and calls its run_test() method. That gets the test name from the +UVM_TESTNAME command line switch and constructs the test object. It then starts the UVM phases, which includes a #0 delay, ensuring all your initial blocks have woken up.
The run_test() method never returns, so any code after it will never execute.
$display("This will never print");
Extra, Extra, Extra
In my opinion, the run_test() method should have been called uvm_run_test(), but I’m a few decades late for that battle.
The method has an argument for the test name – don’t use it. UVM has the great concept of including all tests in one simulation image, so you don’t need to recompile between tests. The only time you might want to give a default test name is for a trivial example with a single test.
After UVM ends your test, it calls $finish(). If you need to run some code at the end of the test, put it in a final_phase() function. That way, your code runs with other testbench OOP code.
In UVM, call uvm_config_db::set() to send the virtual interface to the test class. Put these calls in an initial block. This are typically in the RTL module. If they are in the testbench module, put them before the call to run_test(), or in a separate initial block.
You can learn more about these topics, including Oriented Programming with the Siemens SystemVerilog for Verification course. It is offered in instructor-led format by our industry expert instructors, or in a self-paced, on-demand format. It can also be tailored to address your specific design goals and show you how to set up an environment for reuse for additional designs. Also, you can now earn a digital badge/level 1 certificate by taking our Advanced Topics Badging Exam. This will enable you to showcase your knowledge of this topic by displaying the badge on your social media and email signature.