Reports that support a verdict
Read and control UVM severity, verbosity and actions, expect an injected error without hiding real ones, and end a test with a verdict that silence cannot fake.
- Lesson
- U16.1
- Time
- 25 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
An error-injection test corrupts one FIFO read on purpose. To keep it green, someone adds a report catcher that demotes every UVM_ERROR to information. Months later a real bug leaves two values unread at the end of that test. The scoreboard reports it as an error, the catcher demotes that too, and the test stays green for weeks.
| Time | Report as issued | After the catch-all catcher |
|---|---|---|
| 20 ns | UVM_ERROR [DATA] read 2: got 21, expected 20 (injected) | UVM_INFO: correct, this one was expected |
| 40 ns | UVM_ERROR [DATA] read 4: got 41, expected 40 (injected) | UVM_INFO: correct, this one was expected |
| End of test | UVM_ERROR [PENDING] 2 values never read (a real bug) | UVM_INFO: the bug disappears |
| Summary | UVM_ERROR : 3 | UVM_ERROR : 0, and the test is reported as passing |
A catcher that matches on severity alone cannot tell the expected errors from the real one.
Severity and verbosity answer different questions
Every UVM report has a severity: info, warning, error or fatal. Severity says what kind of event it is. An info message is a progress note, and an error means a check failed. A fatal error ends the test at once.
Info messages also carry a verbosity, from UVM_NONE up through UVM_LOW, UVM_MEDIUM, UVM_HIGH, UVM_FULL and UVM_DEBUG. Each component has a verbosity setting, UVM_MEDIUM by default. An info message prints only if its verbosity is at or below that setting, so UVM_HIGH detail stays hidden unless you ask for it.
The macros issue warnings, errors and fatals at UVM_NONE, so the verbosity setting never hides them. A filtered info message is dropped before it reaches the report server, and it is not even counted. Lowering verbosity makes a log shorter; it can never turn a failing test into a passing one.
What a report does: actions
Once a report gets through, its action decides what happens. The action is a set of flags, and each severity has a default set.
| Severity | Default action | Effect |
|---|---|---|
UVM_INFO | UVM_DISPLAY | Printed |
UVM_WARNING | UVM_DISPLAY | Printed |
UVM_ERROR | UVM_DISPLAY | UVM_COUNT | Printed, and counts toward the quit limit |
UVM_FATAL | UVM_DISPLAY | UVM_EXIT | Printed, then the test ends |
Default actions in uvm-core 2020.3.1. Every report whose action is not UVM_NO_ACTION also adds one to its severity count and its ID count.
UVM_COUNT is easy to misread. It feeds the quit count, which ends the test after a set number of errors. The limit is 0 by default, which means no limit. The severity count is a different counter: it grows for every report that is executed, whatever its action. Strip UVM_COUNT from errors and they still show up in the error total.
You can change reports per component and per ID. set_report_verbosity_level changes one component's filter, and the _hier versions apply it to a whole subtree. set_report_severity_id_override(UVM_ERROR, "RETRY", UVM_WARNING) turns one ID's errors into warnings. set_report_id_action changes what one ID does. Each of these tools can hide a real failure, so keep them scoped to an ID you understand.
The report server keeps the score
All reports end up in one report server, uvm_report_server::get_server(). It keeps a count per severity and a count per ID. At the end of the run it prints both, in the report summary. The summary is the first thing to read in a failing log.
Read the counts by ID first. Forty DATA errors after one FLAGS error usually means one root cause: the flags went wrong, and every later read followed. Fix the first error in time, then rerun, before reading anything after it.
A quit limit helps with that kind of flood. uvm_report_server::get_server().set_max_quit_count(10) ends the test after the tenth counted error, so a broken run stops early with a short log. While a limit is set, the summary adds a Quit count line, such as 2 of 10 for a run with two counted errors.
get_severity_count(UVM_ERROR) and get_id_count("DATA") let a test read those numbers itself. A verdict built on them belongs in report_phase, after check_phase has run the end-of-test checks, such as the scoreboard's check for values never read.
Expecting an error on purpose
An error-injection test breaks something on purpose and expects the bench to complain. If the complaint is a UVM_ERROR, the run must not fail because of it. A report catcher handles this case: a callback object that sees each report before it is executed.
A catcher extends uvm_report_catcher and overrides catch(). It reads the report with get_severity(), get_id() and get_message(). It may change it with set_severity() or set_message(). It returns THROW to pass the report on, or CAUGHT to drop it without printing or counting it.
Three choices keep a catcher honest. Match narrowly: the severity and the ID, and attach it only to the component that reports the fault. Demote rather than drop: an expected error demoted to info still prints, and the catcher summary at the end counts demoted reports. Count: the test knows how many faults it injected, and the verdict requires that exact number.
Catches every error anywhere
function action_e catch();
if (get_severity() == UVM_ERROR)
set_severity(UVM_INFO);
return THROW;
endfunction
// installed with uvm_report_cb::add(null, c)
Demotes the real PENDING error too. Nothing checks that the injected faults were reported.
One ID, one component, a known count
function action_e catch();
if (get_severity() == UVM_ERROR && get_id() == "DATA") begin
seen++;
set_severity(UVM_INFO);
end
return THROW;
endfunction
// installed with uvm_report_cb::add(chk, c)
Only the checker's DATA errors change. The test then requires seen to equal the faults it injected.
Where you attach the catcher matters. uvm_report_cb::add(obj, c) covers reports issued by obj alone, not by its children. A catcher added to the environment never sees the scoreboard inside it. add(null, c) covers every reporter in the bench. add_by_name("*.sb", c, root) covers every component whose full name matches the pattern.
Keep the catcher only as long as the fault lasts. When the injected window ends, remove it with uvm_report_cb::delete(obj, c). A real DATA error later in the same test then fails the run as it should.
Zero errors can mean zero checks
A summary with UVM_ERROR : 0 says that no check failed. It does not say that any check ran. A driver that never got an item, or a monitor wired to the wrong interface, also produces zero errors.
Both runs report no errors. Only the activity counts show that the second one checked nothing.
So a verdict needs positive evidence as well as an empty error count. The example prints TEST PASSED only when the checker compared at least one read, the catcher saw exactly the injected faults, and no error or fatal was counted. U16.3 extends the same idea to a full ledger of writes, reads and canceled values.
Try it
The two injected faults appear as UVM_INFO lines starting with expected:, and the catcher summary counts 2 demoted errors. The two correct reads print nothing, because their messages are UVM_HIGH and the default setting is UVM_MEDIUM.
Remove the got ^= 8'h01; line and run again: the catcher sees 0 errors, so the verdict reports VERDICT instead of passing. Then attach the catcher to this instead of chk and watch the two DATA errors reach the summary.
UVM_INFO main.sv(13) @ 20: uvm_test_top.chk [DATA] expected: read 2: got 21, expected 20 UVM_INFO main.sv(13) @ 40: uvm_test_top.chk [DATA] expected: read 4: got 41, expected 40 checked 4 reads, injected 2 faults, saw 2 expected DATA errors TEST PASSED Number of demoted UVM_ERROR reports : 2 UVM_ERROR : 0Check yourself
Remember
- Severity says what happened; verbosity only filters info messages, and filtered messages are not counted.
- Errors default to display plus
UVM_COUNT, which feeds the quit limit (0, no limit, by default); the error total counts every executed error. - A report catcher sees the reports of the object it is attached to, not of that object's children.
- Expect an injected error narrowly: by severity, ID and component, demoted rather than dropped, and counted.
- A pass needs positive evidence, such as reads checked, as well as zero errors and fatals.