The DMA bench compiles once and runs six tests. A nightly regression runs each test with fifty seeds, and now the team wants to rerun dma_rand_test with truncated packets without writing a new test class or recompiling a 40-minute SoC build. Every simulator lets you pass plusargs; UVM reads a handful of them and turns them into factory overrides and config_db sets. Knowing exactly when they are applied is what keeps them from silently losing to test code.
The command line is the regression's way to leave notes for the testbench before anyone starts working. UVM collects them at a fixed moment: +UVM_TESTNAME before the test exists, the overrides and config sets in uvm_root's own build_phase, which runs before the test's build_phase. Everything the test does in code happens after the notes are on the desk. That ordering explains every surprise in this lesson.
Plusarg | Form | Becomes |
|---|---|---|
|
| The test class created by |
|
|
|
|
|
|
|
|
|
|
|
|
| various | Timeout, verbosity and tracing; see the run_test and debug lessons. |
The bench's top module calls run_test("dma_smoke_test"). With +UVM_TESTNAME=dma_rand_test, run_test overwrites that name with the plusarg and asks the factory to create dma_rand_test by name. This is why every test needs its own registration macro and why the argument to run_test is only a default.
// snippet: from the course DMA bench
module top;
import uvm_pkg::*;
import dma_pkg::*;
logic clk = 0;
always #5 clk = ~clk; // 100 MHz
rst_if rst (clk);
wire rst_n = rst.rst_n;
initial begin
rst.rst_n = 1'b0;
repeat (4) @(posedge clk);
rst.rst_n <= 1'b1;
end
apb_if apb (clk, rst_n);
axis_if s_ax (clk, rst_n);
axis_if m_ax (clk, rst_n);
irq_if irqi (clk);
// The output side accepts every beat unless a test adds a reactive slave
// (axis_sink_agent), whose driver then drives TREADY.
initial m_ax.tready = 1'b1;
dma_dut dut (
.clk, .rst_n,
.paddr(apb.paddr), .psel(apb.psel), .penable(apb.penable), .pwrite(apb.pwrite),
.pwdata(apb.pwdata), .prdata(apb.prdata), .pready(apb.pready), .pslverr(apb.pslverr),
.s_tdata(s_ax.tdata), .s_tvalid(s_ax.tvalid), .s_tlast(s_ax.tlast), .s_tready(s_ax.tready),
.m_tdata(m_ax.tdata), .m_tvalid(m_ax.tvalid), .m_tlast(m_ax.tlast), .m_tready(m_ax.tready),
.irq(irqi.irq)
);
initial begin
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb.*", "vif", apb);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.in_agt.*", "vif", s_ax);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.out_agt.*", "vif", m_ax);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top", "irq_vif", irqi);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top.env.irq.*", "vif", irqi);
uvm_config_db#(virtual rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
run_test("dma_smoke_test");
end
endmoduleTo rerun the random test with truncated packets, no new class is needed: the truncating driver is already compiled into the bench. A regression line looks like this:
<sim> +UVM_TESTNAME=dma_rand_test +uvm_set_type_override=axis_driver,axis_truncating_driver
<sim> +UVM_TESTNAME=dma_rand_test +uvm_set_inst_override=axis_driver,axis_truncating_driver,uvm_test_top.env.in_agt.drvUVM announces each one with an info message from uvm_root (ID UVM_CMDLINE_PROC for type overrides, INSTOVR for instance overrides), for example Applying type override from the command line: +uvm_set_type_override=axis_driver,axis_truncating_driver. Grep for those lines when a run does not behave as the regression intended.
+uvm_set_config_int and +uvm_set_config_string become config_db sets from the top of the hierarchy, made during build, so they carry the highest build-time precedence (why). The integer form stores a uvm_bitstream_t, which matters for who can read it:
A component that reads with uvm_config_db#(uvm_bitstream_t)::get sees it.
Field-macro auto-config and uvm_agent's is_active lookup accept it (Auto-config).
A plain uvm_config_db#(int)::get does not see it: config_db matches the exact type, and int is not uvm_bitstream_t.
So +uvm_set_config_int=uvm_test_top.env.out_agt,is_active,1 really would make the output agent active (and break the bench, since the DUT drives that port), while +uvm_set_config_int=uvm_test_top,n_jobs,5 does nothing to dma_rand_test, whose n_jobs is a plain member that nothing reads from config_db.
The baseline random run, before any override (random values differ by simulator and seed):
+UVM_TESTNAME=dma_rand_test
UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1
UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
UVM_ERROR : 0With the type override added, expect the UVM_CMDLINE_PROC line at time 0, [TRUNC] TLAST forced on beat 3 lines from uvm_test_top.env.in_agt.drv for every job longer than three words, and [STATUS] errors from dma_rand_test, which expects DONE and now sees ERR. That is the correct outcome: the random test was never written to expect errors, which is why a dedicated dma_truncate_test exists for the passing case.
Keep the plusargs with the result. A failing run must be reproducible from its log. Put the full command line in the regression database, and consider +UVM_DUMP_CMDLINE_ARGS, which makes UVM print the arguments it saw.
Treat malformed overrides as failures. A wrong field count produces a UVM_ERROR from UVM_CMDLINE_PROC (for example, a setting that must specify <requested_type>,<override_type>[,<replace>]). Make sure your pass criteria count it.
Check that an override was used. A misspelled type name is just a string that never matches. Grep the log for the class's own messages, or the topology, not only for the Applying ... line.
Prefer a named test for permanent variations. Plusargs are for exploration and reruns; a variation that runs every night deserves a test class that documents it.
A regression owner added +uvm_set_type_override=axis_driver,axis_slow_driver to every test, to add idle cycles across the board. It worked everywhere except dma_truncate_test, which kept truncating at full speed. The reason is the picture: the command-line override was applied in uvm_root's build_phase; then the test's own build_phase called set_type_override for the same requested type with the default replace = 1, and replaced it. A type override in test code always wins over a command-line type override for the same type.
The fix depends on intent. To force a class over test code, use +uvm_set_inst_override with a path (instance overrides are checked before type overrides), or make the slow behaviour a chained override on top (axis_truncating_driver → axis_slow_trunc_driver). Two more traps worth knowing: a command-line override cannot change which test is built, because the test is created before the overrides are applied (use +UVM_TESTNAME); and parameterized classes cannot be named on the command line at all (why). Anything registered with a name can be: components, and objects too, so +uvm_set_type_override=axis_item,axis_err_item swaps every stream item a sequence creates through the factory, which makes error injection by plusarg possible.
run_test("dma_smoke_test") and the command line has +UVM_TESTNAME=dma_drain_test. Which test runs?dma_drain_test. run_test reads the plusarg and replaces its argument. The string in the top module is only the default when no plusarg is given.
uvm_config_db#(int)::get(this, "", "n_jobs", n_jobs). The regression passes +uvm_set_config_int=uvm_test_top,n_jobs,5. Why does nothing change, and what are two fixes?The plusarg stores a uvm_bitstream_t, and config_db only matches the exact type, so the int get misses. Fix it by reading uvm_config_db#(uvm_bitstream_t) and converting, or by declaring n_jobs with a field macro inside uvm_component_utils_begin/end and letting auto-config apply it during super.build_phase.
in_agt tonight. Which plusarg do you use?+uvm_set_inst_override=axis_driver,<logging driver>,uvm_test_top.env.in_agt.drv. Instance overrides are checked before type overrides, so a test's code type override cannot replace it. (If a test also registers an instance override for that path, the command line's was registered first and wins.) The logging driver must extend axis_driver.
Next: overrides so far replaced components. Overriding items and sequences applies the same machinery to the objects that carry stimulus, where the context argument decides what an instance override can reach.