Skip to main content

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:

LevelValue
UVM_NONE0
UVM_LOW100
UVM_MEDIUM200
UVM_HIGH300
UVM_FULL400
UVM_DEBUG500

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:

BitEffect
UVM_DISPLAYPrint to standard output
UVM_LOGWrite to the log file
UVM_COUNTCount toward the report server's quit-count total
UVM_EXITTerminate the simulation immediately
UVM_STOPExecute $stop, dropping into interactive/debug mode
UVM_CALL_HOOKInvoke the registered report-hook callback
UVM_NO_ACTIONTake 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_error count of zero is UVM's own definition of a passing test.
  • Quit count — since UVM_ERROR's default action includes UVM_COUNT, the report server can be configured with set_report_max_quit_count() to end the simulation automatically once a given number of UVM_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 — $display output 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_error automatically fails the test in UVM's built-in reporting summary; a $display saying "ERROR: mismatch" is just text a human has to notice.
  • No verbosity control — $display either 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_fatal message 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.