A regression run shows 200 green tests. One of them, a bus-error test, has a scoreboard mismatch printed in its log, as a UVM_WARNING. Another passes because a report catcher, added a year ago to silence noise from a vendor IP, also matches an ID the scoreboard uses. Both tests are green because of how they report, not because of what the DUT did.
This lesson covers the reporting path: what a message's severity, ID and verbosity decide, how catchers intercept messages, and what really makes a test pass or fail.
Each uvm_info, uvm_warning, uvm_error or uvm_fatal call creates a message with a severity, a string ID and, for infos, a verbosity. It passes through the component's report handler, which decides the action (display, log, count, exit and so on) and filters infos by verbosity; then through any report catchers, which may change or swallow it; then to the global report server, which counts it and prints the summary at the end. Like a sorting office: the handler sorts, catchers can redirect, and the server keeps the register.
// snippet
`uvm_info("SB", "matched id=0x42", UVM_HIGH) // printed only at verbosity HIGH or above
`uvm_warning("CFG", "default timeout used") // always printed, never fails the test
`uvm_error("SB_MISMATCH", "expected ff, got 00") // printed and counted: the test fails
`uvm_fatal("NOVIF", "vif not set in config_db") // printed, then the simulation endsSeverity | Filtered by verbosity | Counts toward the quit count | Ends the run | Use for |
|---|---|---|---|---|
UVM_INFO | Yes | No | No | Progress and debug detail |
UVM_WARNING | No | No by default | No | Unexpected but harmless events |
UVM_ERROR | No | Yes | No (unless the quit count is reached) | Check failures |
UVM_FATAL | No | No (it exits at once) | Yes | Setup faults the run cannot survive |
The report server counts every message it receives, of every severity, so get_severity_count(UVM_WARNING) works by default. The UVM_COUNT action decides only which messages count toward the quit count (set_max_quit_count, or +UVM_MAX_QUIT_COUNT=N), after which the run stops.
A message prints when its verbosity is at or below the component's threshold, and verbosity filters only infos. An info at UVM_NONE always prints. Stable, specific IDs are what make selective control possible. Called in the test after the env exists, the _hier forms apply to a component and everything under it; the plain forms affect only the component they are called on.
// snippet: in the test's end_of_elaboration_phase
env.set_report_verbosity_level_hier(UVM_MEDIUM); // the whole env
env.sb.set_report_id_verbosity("SB", UVM_HIGH); // one ID on one component
env.set_report_id_action_hier("VIP_INTERNAL", UVM_NO_ACTION); // silence one ID entirelyTo silence an ID, change its action to UVM_NO_ACTION; lowering its verbosity does nothing for warnings and errors. On the command line, +UVM_VERBOSITY=UVM_HIGH sets the global level and +uvm_set_verbosity=<component>,<id>,<verbosity>,<phase> targets one component and ID.
Two more controls matter in a regression. The UVM_LOG action sends messages to a file handle set with set_report_default_file (or per severity or ID), which is how one noisy component gets its own log without cluttering the main one. And +UVM_MAX_QUIT_COUNT=10,NO caps the run at ten counted errors and marks the limit as not overridable, so a test cannot quietly raise it; a runaway mismatch loop then ends quickly with a readable log instead of a gigabyte of repeats.
The bench uses this exactly once, in dma_zero_len_test, where an empty scoreboard is correct:
// snippet: from dma_zero_len_test in the course DMA bench
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
env.sb.set_report_severity_id_override(UVM_ERROR, "SB_EMPTY", UVM_INFO);
endfunctionOne ID, one component, one test. The same call made globally, or on the env with a _hier form, would turn "this test checked nothing" into an info for every test, and the safety check would be gone.
A catcher is an object whose catch() runs for every message that reaches it. It returns THROW to pass the message on (possibly changed) or CAUGHT to stop it. Catchers exist for third-party IP whose known, harmless errors you cannot fix:
// snippet
class vip_catcher extends uvm_report_catcher;
`uvm_object_utils(vip_catcher)
function new(string name = "vip_catcher");
super.new(name);
endfunction
function action_e catch();
if (get_severity() == UVM_ERROR && get_id() == "VIP_KNOWN_ERR") begin
set_severity(UVM_WARNING); // demote, but keep it visible
set_message({"[DEMOTED] ", get_message()});
end
return THROW; // everything else passes unchanged
endfunction
endclass
// in the test's build_phase: one catcher for every component
// uvm_report_cb::add(null, vip_catcher::type_id::create("vip_c"));UVM prints a report summary but no PASS or FAIL line. A test's report_phase can read the server's counts and print one at UVM_NONE, so no verbosity setting hides it. A UVM_FATAL is the exception: the run ends at once and report_phase never runs. For a verdict line that survives a fatal, print it from report_summarize in a custom report server, or from the pre_abort hook.
// snippet: a test's report_phase
function void report_phase(uvm_phase phase);
uvm_report_server svr = uvm_report_server::get_server();
int n_bad = svr.get_severity_count(UVM_ERROR) + svr.get_severity_count(UVM_FATAL);
super.report_phase(phase);
if (n_bad == 0) `uvm_info("RESULT", "*** TEST PASSED ***", UVM_NONE)
else `uvm_info("RESULT", $sformatf("*** TEST FAILED (%0d) ***", n_bad), UVM_NONE)
endfunction+UVM_TESTNAME=dma_zero_len_test
UVM_INFO dma_tb.sv(407) @ 325000: uvm_test_top.env.sb [SB_EMPTY] no words were compared: the test checked nothing
UVM_INFO dma_tb.sv(411) @ 325000: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0[SB_EMPTY] as UVM_INFO: the scoreboard raised it with uvm_error; the severity override turned it into an info before it reached the server, so it does not count.
The message still prints. A demotion keeps the evidence in the log, which is what makes it reviewable.
UVM_ERROR : 0 is the pass condition regression scripts grep for.
Both stories from the opening are common. The mismatch reported with uvm_warning is counted but never fails anything, because the verdict looks only at errors and fatals; the fix is a rule that every check failure is a uvm_error, enforced in code review. The over-broad catcher is worse, because the log looks clean: the scoreboard's error never reached the server. The fix is exact-ID matching plus a regression that injects a known scoreboard error and expects the test to fail, which proves the reporting path still works.
uvm_warning. What does the regression show?Green. Warnings are counted by the server but are not part of the usual verdict, so the test passes with a live bug in its log. Mismatches must be uvm_error.
dma_zero_len_test call set_report_severity_id_override on env.sb rather than on env with a hierarchical form?Only the scoreboard's SB_EMPTY is expected in this test. A hierarchical override would also demote that ID in any other component that uses it, and a global one would hide empty scoreboards in every test. The narrowest scope that fits the reason is the right one.
UVM_FATAL. Does its report_phase banner print?No. A fatal ends the simulation immediately; report_phase never runs. Only the report summary and anything printed from pre_abort or a custom server's report_summarize appear.
Next: functional coverage measures the plan; Code Coverage and How It Closes with Functional Coverage measures the RTL, and shows how the two close together. After it, the register layer replaces hand-written register traffic with a model that knows every field, including the coverage of which fields were exercised.