Reporting & Messaging
Every code example so far has used `uvm_info or `uvm_fatal instead of $display. That's not a style preference — UVM's reporting macros solve real problems plain $display doesn't, and in a testbench with dozens of components logging concurrently, the difference shows up immediately.
The four severities
`uvm_info("DRV", "Starting transaction", UVM_MEDIUM)
`uvm_warning("DRV", "Retrying after a NACK")
`uvm_error("SB", "Data mismatch: expected != actual")
`uvm_fatal("CFG", "Virtual interface not set")
`uvm_info— informational, expected during normal operation. Doesn't affect pass/fail.`uvm_warning— something unexpected but recoverable happened. Doesn't stop the test, but shows up in the end-of-test summary.`uvm_error— something is wrong. The test keeps running (so later errors in the same test are still caught), but the test is marked failed.`uvm_fatal— unrecoverable. Ends the simulation immediately, since continuing would only produce noise.
That severity is exactly why uvm_config_db#(virtual fifo_if)::get() failing (see Resource DB & Config DB) belongs behind `uvm_fatal and not `uvm_error: a driver with no virtual interface can't drive anything meaningful, so there's nothing to be gained by continuing.
Every message carries an ID and a severity
The first argument to every macro — "DRV", "SB", "CFG" above — is an arbitrary ID string, conventionally naming the component or subsystem that raised it. Combined with severity, this is what makes filtering possible: a scoreboard failure and a driver's routine info message never look alike, even if a hundred components are logging every clock cycle.
Verbosity: controlling the noise
`uvm_info's third argument (UVM_MEDIUM above) is a verbosity level, with exact underlying numeric values worth knowing since they determine filtering behavior directly:
| Level | Value |
|---|---|
UVM_NONE | 0 |
UVM_LOW | 100 |
UVM_MEDIUM | 200 |
UVM_HIGH | 300 |
UVM_FULL | 400 |
UVM_DEBUG | 500 |
A message only prints if its verbosity value is at or below the component's configured verbosity threshold — which defaults to UVM_MEDIUM (200) — so raising the threshold to UVM_HIGH shows UVM_HIGH, UVM_MEDIUM, and UVM_LOW messages, but not UVM_FULL or UVM_DEBUG:
// From the command line, no code changes needed:
// +UVM_VERBOSITY=UVM_HIGH
// Or from within a test, scoped to one component:
env.agent.driver.set_report_verbosity_level(UVM_HIGH);
This is why detailed, per-transaction `uvm_info calls (like the scoreboard's in The Scoreboard) are safe to leave in permanently at UVM_MEDIUM or UVM_HIGH — they stay silent during routine regression runs and become available instantly, with no recompile, the moment someone needs to debug that specific component.
Macros vs. the underlying functions
`uvm_info/ `uvm_warning/ `uvm_error/ `uvm_fatal are macros wrapping four plain functions — uvm_report_info(), uvm_report_warning(), uvm_report_error(), uvm_report_fatal() — that can technically be called directly instead:
uvm_report_info("DRV", "Starting transaction", UVM_MEDIUM);
The macros are preferred in practice for one concrete reason: they automatically capture `__FILE__ and `__LINE__ at the call site, so every logged message can be traced straight back to the exact line that produced it — the underlying functions don't do this on their own. That file/line annotation can be turned off globally, with no code changes, via +UVM_REPORT_DISABLE_FILE_LINE on the command line — useful for trimming log noise once a testbench is stable and messages no longer need tracing back to source on every run.
Overriding severity and action
Every message's severity and its action (what actually happens when it fires) can both be overridden, per component, without touching the call site:
// Downgrade every uvm_error from this component to a warning:
env.scoreboard.set_report_severity_override(UVM_ERROR, UVM_WARNING);
// Change what a specific ID does, regardless of severity:
env.driver.set_report_id_action("DRV", UVM_DISPLAY | UVM_COUNT);
The action argument is a bitwise OR of:
| Bit | Effect |
|---|---|
UVM_DISPLAY | Print to standard output |
UVM_LOG | Write to the log file |
UVM_COUNT | Count toward the report server's quit-count total |
UVM_EXIT | Terminate the simulation immediately |
UVM_STOP | Execute $stop, dropping into interactive/debug mode |
UVM_CALL_HOOK | Invoke the registered report-hook callback |
UVM_NO_ACTION | Take no action at all — the message is fully suppressed |
Each severity ships with a default action even before any override: UVM_INFO and UVM_WARNING default to UVM_DISPLAY; UVM_ERROR defaults to UVM_DISPLAY | UVM_COUNT; UVM_FATAL defaults to UVM_DISPLAY | UVM_EXIT. That default action is exactly what makes a `uvm_fatal end the simulation and a `uvm_error merely get counted — the severities themselves don't halt anything; their default actions do.
set_report_severity_override() and set_report_id_action() (like set_report_verbosity_level() earlier) are called on a component handle, so the override is scoped to that component and anything below it in the hierarchy — not global — unless called on uvm_top/via uvm_root to apply it testbench-wide.
Per-ID verbosity and the report server
Verbosity can also be overridden per message ID rather than per component, so one noisy ID can be dialed up without raising verbosity for everything else the component logs:
env.agent.driver.set_report_id_verbosity("DRV_TXN", UVM_HIGH);
Every message ultimately funnels through a single uvm_report_server (obtained via the static uvm_report_server::get_server()), which tallies how many messages of each severity have fired. Two of its jobs matter in everyday debugging:
report_summarize()— prints the end-of-test tally (counts per severity, per ID) that appears in every UVM log's final lines; this is the report a CI script greps to decide pass/fail, since a`uvm_errorcount of zero is UVM's own definition of a passing test.- Quit count — since
UVM_ERROR's default action includesUVM_COUNT, the report server can be configured withset_report_max_quit_count()to end the simulation automatically once a given number ofUVM_COUNT-actioned messages have fired, instead of letting an error-flooding bug run to the end of a long test uselessly.
Why not just $display
- No severity —
$displayoutput can't be distinguished as info vs. warning vs. error by any tool; UVM's macros make severity a first-class, machine-readable property of the message. - No automatic pass/fail impact — a
`uvm_errorautomatically fails the test in UVM's built-in reporting summary; a$displaysaying "ERROR: mismatch" is just text a human has to notice. - No verbosity control —
$displayeither always prints or you comment it out; there's no equivalent of dialing one component's verbosity up without a recompile. - No consistent formatting — every
`uvm_info/`uvm_warning/`uvm_error/`uvm_fatalmessage is automatically prefixed with severity, ID, time, and the reporting component's full hierarchical path, which is often the fastest way to find which one of a dozen identical agents just complained.
What's next
Reporting covers messages; the next page covers the other two uvm_object utility policies first introduced in Object Utility Methods — configurable print()/compare() behavior via uvm_printer and uvm_comparer policy objects.