Skip to content

UVM

Report Server

The central engine that processes every report — the uvm_report_server that receives reports after the catchers, applies actions per severity (display, log, count, exit), tallies the error and fatal counts that decide pass/fail, bounds a flood with max_quit_count, formats and routes messages, and is customizable to tailor the whole testbench's reporting — and why the count, not the log, is the verdict.

Reporting System · Module 24 · Page 24.4

The Engineering Problem

Every report — after passing the catchers (Module 24.3) — flows into one place: the report server, the central engine that decides what happens to it. The server is where the testbench's reporting behavior is actually realized: it applies actions per severity (print it, log it, count it, or exit on it), tallies the error and fatal counts, formats the message, routes it to stdout and files, and — cruciallydetermines pass/fail from those counts. This centrality makes the server powerful and its configuration consequential. The count the server keeps is the authoritative verdict: a regression reads the server's error/fatal count, not the log, to decide pass/fail. So if the server's action mapping is altered — say, UVM_ERROR is configured to display but not count — then errors appear in the log yet the count stays 0 and the test passes, a baffling contradiction between what you see (errors) and what the regression reports (pass). The problem this chapter solves is the report server: how it processes every report (actions per severity), how it decides pass/fail (the counts, max_quit_count), how it formats and routes, how it's customized, and why the count — not the log — is the verdict.

The report server (uvm_report_server) is the single, global engine that processes every report after the catchers. For each report, it applies the actions configured for that severity: UVM_DISPLAY (print to stdout), UVM_LOG (write to a file), UVM_COUNT (increment the count toward pass/fail and max_quit_count), UVM_EXIT/UVM_STOP (end or pause the simulation — UVM_FATAL exits), plus UVM_NO_ACTION, UVM_CALL_HOOK. The severity→actions mapping is configurable (set_severity_action, set_id_action). The server tallies the UVM_ERROR and UVM_FATAL countsthese counts decide pass/fail (0 = pass) — and max_quit_count bounds a flood (quit after N errors). It formats each message (severity, time, component, id, text) and routes it to file handles. The server is replaceable (uvm_report_server::set_server) — a custom server tailors the whole testbench's reporting (format, counting, the end-of-run report_summarize tally). The governing truth: the server's count is the single source of truth for pass/fail — everything upstream (catchers, verbosity) affects what the server counts, but the count is the verdict, so a green result with errors in the log means the action mapping was altered. This chapter is the report server: actions per severity, the pass/fail counts, formatting/routing, customization, and the count-is-the-verdict principle.

What is the report server — how does it process every report (actions per severity: display, log, count, exit), how does it decide pass/fail from the error and fatal counts (with max_quit_count), how does it format and route messages, how is it customized, and why is the count, not the log, the verdict?

Motivation — why the server, and its counts, are central

The report server is where reporting becomes a result — so understanding it (and trusting its counts) is essential. The reasons:

  • It's the single terminus of every report. All reports — from every component, after the catchers — converge on one server. So the server is where the testbench-wide reporting behavior is realized: one place decides what happens to every message.
  • The counts are the verdict. The server tallies UVM_ERROR/UVM_FATAL, and the regression reads those counts for pass/fail. The countnot the log — is authoritative. Everything about whether a test passes funnels through the server's count.
  • Actions per severity shape behavior. How a severity is handled (display, log, count, exit) is configurable at the server — and changing it changes the whole testbench's response to that severity. A miconfigured action (errors not counted) silently breaks pass/fail.
  • max_quit_count bounds the failure mode. A flood of errors could run forever; max_quit_count quits after N, bounding the damage — but set wrong (too low), it masks later errors. The server governs this.
  • Customizing it tailors all reporting. Replacing the server lets you change the format, the summary, the integration with external tools — uniformly, for all reports. The server is the one knob for testbench-wide reporting.

The motivation, in one line: the report server is the single terminus of every report and the keeper of the pass/fail counts — so it's where reporting becomes a result: its actions-per-severity shape the whole testbench's response, its counts are the authoritative verdict (not the log), max_quit_count bounds a flood, and customizing it tailors all reporting — making understanding and correctly configuring the server (especially keeping errors counted) essential.

Mental Model

Hold the report server as the control room at the end of the message road — every report arrives, gets stamped by severity, and the official scoreboard the control room keeps is the result, not the chatter in the log:

Every report message travels the road, past the catcher checkpoints, and arrives at a central control room — the report server. The control room is where each message's fate is decided. It stamps the message by severity and applies the actions for that stamp: post it on the display board (display), file it in the logbook (log), mark it on the official scoreboard (count), or sound the evacuation alarm and end the mission (exit, for fatals). It keeps the official scoreboard — the running tally of errors and fatals — and that scoreboard is what decides whether the mission passed: zero on the board is a pass. There's a circuit breaker too: if errors pile up past a set limit, the control room ends the run rather than letting it flood forever. The control room formats every announcement uniformly and routes it to the board and the logbook. And it's re-staffable — you can install a custom control room that tallies, formats, or summarizes differently, changing how the whole testbench reports. The one thing to internalize: the scoreboard, not the chatter in the logbook, is the result. A logbook full of error entries while the scoreboard reads zero doesn't mean no errors happened — it means the control room was configured to log those errors without marking them on the scoreboard. The official count is the verdict; if it contradicts the log, the control room's stamping rules were changed. Picture every report arriving, past the catcher checkpoints (24.3), at a central control room — the report server. The control room decides each message's fate: it stamps by severity and applies the actionspost it on the display board (display), file it in the logbook (log), mark it on the official scoreboard (count), or sound the evacuation and end the mission (exit, for fatals). It keeps the official scoreboard — the running tally of errors and fatals — and that scoreboard decides pass/fail (zero = pass). There's a circuit breaker: if errors pile past a limit (max_quit_count), the control room ends the run rather than flood forever. It formats every announcement uniformly and routes it to the board and logbook. And it's re-staffable — a custom control room tallies/formats/summarizes differently, changing how the whole testbench reports. The one thing to internalize: the scoreboard, not the chatter in the logbook, is the result. A logbook full of error entries while the scoreboard reads zero doesn't mean no errors — it means the control room was configured to log without marking them on the scoreboard. The official count is the verdict; if it contradicts the log, the stamping rules were changed.

So the report server is the control room at the end of the message road: every report arrives (past the catchers), gets stamped by severity and the actions applied (display/log/count/exit), and the control room keeps the official scoreboard (the error/fatal counts) that decides pass/fail — with a circuit breaker (max_quit_count) and uniform formatting/routing, and re-staffable (custom server) to tailor all reporting. The scoreboard (count) is the verdict, not the logbook (log) — a green result with errors in the log means the action mapping was altered (logged but not counted). Trust the server's count as the verdict; a log full of errors with a passing count means the stamping rules were changed.

Visual Explanation — the report server engine

The defining picture is the engine: the server receives thrown reports, applies actions per severity, tallies counts, and formats/routes — with the counts deciding pass/fail.

Report server: receive, apply actions per severity, format/route, tally counts deciding pass/failReceives every report (after catchers)the single, global engine all thrown reports converge on — from every componentthe single, global engine all thrown reports converge on — from every componentApplies actions per severityUVM_DISPLAY (stdout), UVM_LOG (file), UVM_COUNT (tally), UVM_EXIT (end sim) — configurable per severity/idUVM_DISPLAY (stdout), UVM_LOG (file), UVM_COUNT (tally), UVM_EXIT (end sim) — configurable per severity/idFormats and routescomposes the message (severity, time, component, id, text) and routes it to the file handlescomposes the message (severity, time, component, id, text) and routes it to the file handlesTallies counts → pass/fail (the verdict)error/fatal counts decide pass/fail (0 = pass); max_quit_count bounds a flood — the count, not the log, is the resulterror/fatal counts decide pass/fail (0 = pass); max_quit_count bounds a flood — the count, not the log, is the result
Figure 1 — the report server processes every report. After the catchers, each report enters the server, which applies the actions configured for its severity: display to stdout, log to a file, count toward the tally, or exit the simulation. The server formats the message and routes it to the file handles. It tallies the error and fatal counts, and those counts — not the log — decide pass/fail, with max_quit_count bounding a flood. The server is the single engine where every report's fate is decided and the pass/fail verdict is kept.

The figure shows the report server engine. It receives every report (the brand-colored top — after the catchers; the single, global engine all thrown reports converge on). It applies actions per severity (the warning-colored layer — UVM_DISPLAY (stdout), UVM_LOG (file), UVM_COUNT (tally), UVM_EXIT (end sim) — configurable per severity/id). It formats and routes (composes the message — severity, time, component, id, text — and routes to file handles). And it tallies counts → pass/fail (the success-colorederror/fatal counts decide pass/fail (0 = pass); max_quit_count bounds a floodthe count, not the log, is the result). The crucial reading is the server's two outputs: the visible output (formatted messages, displayed and logged) and the authoritative output (the counts that decide pass/fail). These are separate: a report can be displayed/logged (visible) without being counted (the UVM_COUNT action omitted), or counted without being displayed. The warning-colored actions layer is highlighted because it's where the two outputs are decided (warning because misconfiguring it — e.g. logging without counting an error — breaks the correspondence between what you see and what's counted). The success-colored count layer is the verdict — and it's derived from the UVM_COUNT action being applied to errors/fatals. So the server's pass/fail depends on two things: the report reaching the server (not caught/demoted upstream — 24.3) and the count action being applied to its severity. The diagram is the server engine: receive (after catchers) → apply actions per severity → format/route + tally counts → pass/fail — with the counts (not the log) the verdict, governed by the action mapping. The server applies actions per severity, formats and routes, and tallies the counts that decide pass/fail — the count, not the log, is the verdict.

RTL / Simulation Perspective — actions, counts, and customization

In code, the server's behavior is actions per severity, the counts, and customization. The example shows configuring actions, the count/max_quit, and a custom server.

report server: actions per severity, the pass/fail counts, max_quit, and customization
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === ACTIONS PER SEVERITY: how the server handles each severity (configurable) ===
// defaults (typical): INFO → DISPLAY|LOG ; WARNING → DISPLAY|LOG ; ERROR → DISPLAY|LOG|COUNT ;
//                     FATAL → DISPLAY|LOG|EXIT
// reconfigure (e.g. also count warnings as failures for a strict run):
uvm_report_server::get_server().set_severity_action(... );   // (via report handler API)
// per-ID action: make a specific id NOT count (a targeted, documented relaxation)
//   set_id_action("BENIGN_ID", UVM_DISPLAY | UVM_LOG);       // display+log but no COUNT (scoped!)
 
// === THE PASS/FAIL COUNTS: the server tallies errors/fatals → the verdict ===
uvm_report_server svr = uvm_report_server::get_server();
int n_err = svr.get_severity_count(UVM_ERROR);   // the COUNT is read by the regression for pass/fail
int n_fat = svr.get_severity_count(UVM_FATAL);
// max_quit_count: quit after N errors to bound a flood (not run forever)
svr.set_max_quit_count(50);
 
// === END OF RUN: the server summarizes the tally ===
//   report_summarize() prints "UVM_ERROR : N   UVM_FATAL : M ..." → the official scoreboard
 
// === CUSTOMIZE: replace the server to tailor formatting/counting/summary testbench-wide ===
class my_report_server extends uvm_default_report_server;
  virtual function string compose_report_message(uvm_report_message m, string fmt="");
    return $sformatf("[%0t] %s", $time, super.compose_report_message(m, fmt));  // custom format
  endfunction
endclass
// uvm_report_server::set_server(my_report_server::type_id::create());  // tailors ALL reports
 
// ✗ DANGER: strip UVM_COUNT from UVM_ERROR globally → errors LOG but don't count → false PASS (DebugLab)
//   set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_LOG);   // ← no COUNT → test passes with errors!

The code shows the server's behavior. Actions per severity: the defaults (INFO/WARNING → DISPLAY|LOG, ERROR → DISPLAY|LOG|COUNT, FATAL → DISPLAY|LOG|EXIT) are configurable (set_severity_action, set_id_action) — e.g. a per-id relaxation set_id_action("BENIGN_ID", UVM_DISPLAY | UVM_LOG) (display+log but no COUNT, scoped to one id). The pass/fail counts: the server tallies errors/fatals (get_severity_count(UVM_ERROR)) — the COUNT is read by the regression for pass/fail — and set_max_quit_count(50) quits after N errors (bounding a flood). End of run: report_summarize() prints the official scoreboard (UVM_ERROR : N ...). Customize: a my_report_server extends uvm_default_report_server overrides compose_report_message (custom format), set via set_servertailoring ALL reports. The danger (commented) is stripping UVM_COUNT from UVM_ERROR globally (set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_LOG)) → errors LOG but don't countfalse PASS (the DebugLab). The shape to carry: the server's behavior is the actions per severity (display/log/count/exit — configurable), the counts it tallies (the pass/fail verdict, with max_quit_count bounding floods), and its customizability (replace the server to tailor format/counting/summary). The crucial line is the UVM_COUNT action: it's what makes an error count — and stripping it (globally) makes errors visible but uncounted, a false pass. A per-id relaxation (the BENIGN_ID example) is the scoped, legitimate version (like a narrow catcher, 24.3); a global strip is the dangerous version. The custom server is the testbench-wide reporting knob — one place to change format/summary for all reports. Configure actions per severity (keep COUNT on errors), read the counts as the verdict, bound floods with max_quit, and customize the server to tailor all reporting.

Verification Perspective — actions per severity

The server's behavior is defined by the severity→actions mapping. Seeing what each severity does — and which action makes it count — clarifies how reporting becomes pass/fail.

Severity to actions: info/warning display+log, error display+log+count, fatal display+log+exitdisplay + logdisplay + log (nocount by default)display + log +COUNTdisplay + log + EXITUVM_INFOinformationalDisplay + Lognever countsUVM_WARNINGnotedDisplay + Logno count by defaultUVM_ERRORa failureDisplay + Log + COUNTCOUNT makes it fail (max_quitbounds)UVM_FATALabortDisplay + Log + EXITends the sim, counts12
Figure 2 — the actions the server applies per severity. Info: display and log — informational, never counts. Warning: display and log — noted but, by default, doesn't fail the test. Error: display, log, and count — the count is what makes an error fail the test, and what max_quit_count bounds. Fatal: display, log, and exit — it ends the simulation immediately and counts. The UVM_COUNT action is the pivot: it is what turns a report into a pass/fail-affecting event. Stripping count from error makes errors visible but non-failing; adding count to warning makes warnings fail.

The figure shows the actions per severity. UVM_INFOdisplay and loginformational, never counts. UVM_WARNINGdisplay and lognoted but, by default, doesn't fail the test (no count). UVM_ERRORdisplay, log, and COUNTthe count is what makes an error fail the test, and what max_quit_count bounds. UVM_FATALdisplay, log, and EXIT — it ends the simulation immediately and counts. The verification insight is that UVM_COUNT is the pivot: it's what turns a report into a pass/fail-affecting event. Info and warning are visible (displayed/logged) but don't count (by default), so they don't fail the test; error and fatal count, so they do. This is why the severity matters — not (just) for the log appearance, but for whether it counts (the UVM_COUNT action attached to error/fatal). And because the mapping is configurable, you can change this: adding count to warning (set_severity_action(UVM_WARNING, ... | UVM_COUNT)) makes warnings fail (a stricter run); stripping count from error makes errors visible but non-failing (the dangerous DebugLab). The warning-colored error/fatal (with their count/exit actions) are the pass/fail-affecting severities; the brand/success-colored info/warning are visible-but-not-failing (by default). The practical takeaway: the UVM_COUNT action on error/fatal is what makes the testbench fail on them — it's the link between a report and the verdict, so that action must stay attached to errors/fatals (or they stop failing). The diagram is the severity→actions mapping: info/warning (display+log, no count), error (display+log+COUNT → fails), fatal (display+log+EXIT → aborts) — with UVM_COUNT the pivot that makes a report count. UVM_COUNT is the pivot — it turns a report into a pass/fail event; it must stay on errors and fatals.

Runtime / Execution Flow — the counts deciding pass/fail

At run time, the server accumulates the counts and derives pass/fail — with max_quit_count bounding a flood and fatal exiting immediately. The flow shows the pass/fail derivation.

Counts decide pass/fail: increment on counted errors/fatals, max_quit bounds, summarizereport arrives → counted error increments the error count (fatal increments and exits) → error count reaches max_quit_count? quit → end of run: 0 errors and 0 fatals = pass, else fail (regression reads the count)report arrives → counted error increments the error count (fatal increments and exits) → error count reaches max_quit_count? quit → end of run: 0 errors and 0 fatals = pass, else fail (regression reads the count)1Counted report arrivesthe server increments the error count on each counted error, thefatal count on each fatal.2Fatal exits; max_quit boundsa fatal exits the sim immediately; if the error count reachesmax_quit_count, the server quits the run.3End of run: summarize the tallyreport_summarize() prints the error/fatal counts — the officialscoreboard of the run.4Counts decide pass/fail0 errors and 0 fatals = pass; any counted error/fatal = fail — theregression reads the count, not the log.
Figure 3 — how the server's counts decide pass/fail. As reports arrive, the server increments the error count on each counted error and the fatal count on each fatal. A fatal also exits the simulation immediately. If the error count reaches max_quit_count, the server quits the run to bound a flood rather than continue forever. At the end of the run, the server summarizes: zero errors and zero fatals is a pass; any counted error or fatal is a fail. The regression reads these counts — not the log — as the pass/fail verdict.

The flow shows how the server's counts decide pass/fail. Arrive (step 1): the server increments the error count on each counted error, the fatal count on each fatal. Exit/bound (step 2): a fatal exits the sim immediately; if the error count reaches max_quit_count, the server quits the run (bounding a flood). Summarize (step 3): at end of run, report_summarize() prints the error/fatal counts — the official scoreboard. Verdict (step 4): 0 errors and 0 fatals = pass; any counted error/fatal = failthe regression reads the count, not the log. The runtime insight is that pass/fail is purely a function of the server's counts — and those counts are what the regression reads (typically by parsing report_summarize's output, or querying get_severity_count). So the server's count is the single, authoritative pass/fail signal. Two behaviors are worth internalizing. First, fatal exits immediately — a UVM_FATAL doesn't just count; it ends the simulation right there (it's unrecoverable), so the run stops at the first fatal. Second, max_quit_count bounds the error floodwithout it, a broken DUT spewing thousands of errors would run to completion (slow, log-flooding); with it, the run quits after N errors (you've seen enough to know it failed). The summarize (step 3) is the end-of-run tally — the official count printed for the regression to read. The brand/warning-colored arrive/exit feed the success-colored summarize, yielding the default-colored verdict. The crucial point (step 4) is the regression reads the count, not the log — so the count is what matters, and anything that changes the count (a catcher demoting, an action stripping UVM_COUNT) changes the verdict regardless of what's in the log. The flow is the pass/fail derivation: counted reports → increment (fatal exits, max_quit bounds) → summarize → 0 = pass, else fail (regression reads the count). The server's error/fatal counts are the pass/fail verdict — fatals exit immediately, max_quit bounds a flood, and the regression reads the count, not the log.

Waveform Perspective — counts accumulating to the verdict

The pass/fail derivation is visible on a timeline: the error count increments on each counted error, a fatal exits immediately, and the final count is the verdict. The waveform shows counts accumulating and a fatal aborting.

The error count increments on counted errors; a fatal exits immediately — the final count is the verdict

12 cycles
The error count increments on counted errors; a fatal exits immediately — the final count is the verdictcounted UVM_ERROR → err_count → 1 (the running pass/fail tally)counted UVM_ERROR → er…UVM_INFO → displayed/logged but NOT counted → err_count unchangedUVM_INFO → displayed/l…second counted error → err_count → 2 → the test will fail (nonzero)second counted error →…UVM_FATAL → server EXITS the sim immediately (sim_exit) — run ends hereUVM_FATAL → server EXI…clkreport--ERR--INFO--ERR----FATAL------err_count011112222222fatalsim_exitt0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — the server's counts deciding pass/fail. Reports arrive over the run. Each counted UVM_ERROR increments the error count (err_count steps up). The count is the running tally that decides pass/fail. If a UVM_FATAL arrives (fatal), the server exits the simulation immediately (sim_exit) — the run ends there. Otherwise, at end of run, the final err_count is the verdict: nonzero means fail. Here two errors are counted (err_count reaches 2), so the test fails — the regression reads this count, not the log. A demoted or uncounted error would not increment err_count, silently changing the verdict.

The waveform shows the server's counts deciding pass/fail. Reports arrive over the run. Each counted UVM_ERROR increments the error count (err_count steps up). The count is the running tally that decides pass/fail. A UVM_INFO is displayed/logged but not counted (err_count unchanged). A second counted error takes err_count to 2 (the test will fail). When a UVM_FATAL arrives (fatal), the server exits the simulation immediately (sim_exit) — the run ends there. The crucial reading is the count as the running verdict: err_count increments only on counted errors (not on INFO, which is visible but uncounted), and the final err_count (here 2) is the verdictnonzero means fail. The fatal shows the immediate exitUVM_FATAL doesn't just count; it ends the sim at that cycle (sim_exit high from the fatal onward). The picture to carry is that pass/fail is the error/fatal count — and the count is what the regression reads, not the log. A demoted or uncounted error (a catcher demoting it — 24.3, or the action mapping stripping UVM_COUNT — the DebugLab) would not increment err_count, silently changing the verdict — the error would be in the log (displayed) but not in the count (the verdict). So the err_count is the authoritative signal, and the correspondence between "errors in the log" and "err_count nonzero" holds only if the UVM_COUNT action is applied to errors. Reading the waveform this way — is the error count incrementing on the errors, and does the final count match the log's errors? — is reading the server's verdict. The err_count stepping up on counted errors (not INFO), and the fatal exiting immediately is the signature of the server deriving pass/fail from the countsthe count, not the log, is the result. The error count increments on counted errors and is the verdict; a fatal exits immediately — and an uncounted error silently changes the result without changing the log.

DebugLab — the passing regression whose log was full of errors

A green regression with UVM_ERRORs printed throughout the log — errors logged but not counted

Symptom

A regression reported PASSUVM_ERROR : 0 in the summary, green result — and the block moved toward sign-off. But an engineer reading the log (for an unrelated reason) noticed something jarring: the log was full of UVM_ERROR messages — real ones, including a UVM_ERROR: data mismatch from the scoreboard. Dozens of them, clearly printed, UVM_ERROR severity — yet the summary said UVM_ERROR : 0 and the regression called it PASS. The contradiction was stark: the log showed errors, the verdict said zero. Tracing it, the team found that an environment base class called set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_LOG) in start_of_simulation — a leftover from an early bring-up hack to "not fail on errors yet" — which had stripped the UVM_COUNT action from UVM_ERROR globally. So every UVM_ERROR was displayed and logged (hence visible in the log) but not counted — the error count stayed 0, the regression passed, and the real scoreboard errors were invisible to the verdict.

Root cause

A global set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_LOG) had stripped UVM_COUNT from UVM_ERROR, so errors were logged but not counted — and because pass/fail is the count, not the log, the test passed despite errors visible in the log:

why a regression passed with UVM_ERRORs all over the log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ STRIP UVM_COUNT from UVM_ERROR globally (a leftover bring-up hack):
  // in an env base class's start_of_simulation:
  set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_LOG);   // ← no UVM_COUNT!
  // every UVM_ERROR is DISPLAYED + LOGGED (visible in the log) but NOT counted
  // → err_count stays 0 → report_summarize prints "UVM_ERROR : 0" → regression reads 0 → PASS
  // real scoreboard errors are in the log but invisible to the verdict → bug ships
 
✓ KEEP UVM_COUNT on UVM_ERROR (the default) — the count is the verdict:
  // remove the hack; UVM_ERROR defaults to DISPLAY | LOG | COUNT → errors count → test FAILS on them
  // if you must relax ONE benign id, scope it narrowly (like a catcher, 24.3):
  set_id_action("BENIGN_ID", UVM_DISPLAY | UVM_LOG);       // per-ID, documented — not global!
  // and TRUST the count: a green result with errors in the log means the action mapping was altered

This is the stripped-count bug — the server-level analog of the over-broad catcher (24.3), and a direct violation of the count-is-the-verdict principle. A global set_severity_action(UVM_ERROR, UVM_DISPLAY | UVM_LOG) — a leftover bring-up hack to "not fail on errors yet" — had stripped the UVM_COUNT action from UVM_ERROR. So every UVM_ERROR was displayed and logged (so it appeared in the log) but not counted (so err_count stayed 0). The regression reads the count (UVM_ERROR : 0) — not the log — so it passed, while real scoreboard errors sat in the log, invisible to the verdict. The baffling contradictionerrors in the log, zero in the summary — is the signature of a stripped count action: the reports reached the server and were logged, but the UVM_COUNT action that makes them count was removed, so they don't fail the test. The deep reason is the separation of the server's outputs (Figure 1): a report can be displayed/logged (visible) without being counted (the verdict) — and stripping UVM_COUNT breaks the correspondence between "error in the log" and "error in the count". The fix is to keep UVM_COUNT on UVM_ERROR (the defaultDISPLAY | LOG | COUNT): remove the hack, so errors count and the test fails on them. If you must relax one benign id, scope it narrowly (set_id_action("BENIGN_ID", UVM_DISPLAY | UVM_LOG)per-id, documentedlike a narrow catcher, not global). The general lesson, and the chapter's thesis: the error/fatal count — not the log — is the pass/fail verdict, and the count only increments if the UVM_COUNT action is applied to the severity — so stripping UVM_COUNT from UVM_ERROR makes errors appear in the log but not count, and the test passes with errors visible, a contradiction between the log and the result; keep UVM_COUNT on errors and fatals (the default), scope any relaxation narrowly to a specific, documented id (never globally), and trust the server's count as the verdicta green result with errors in the log means the action mapping was altered, so the count, not the chatter in the log, is the result. Errors in the log with a passing count don't mean the errors are benign — they mean the action that makes errors count was removed; keep UVM_COUNT on errors and trust the count.

Diagnosis

The tell is a passing result with UVM_ERROR messages in the log. Diagnose a stripped count action:

  1. Compare the log's errors to the summary count. Errors printed in the log while the summary says UVM_ERROR : 0 means errors were logged but not counted.
  2. Search for set_severity_action on UVM_ERROR. A global action that omits UVM_COUNT strips counting from all errors; check start_of_simulation and base classes.
  3. Check for leftover bring-up hacks. A relaxation added to not fail early can outlive its reason and silently disable the verdict.
  4. Verify the default action is intact. UVM_ERROR should be DISPLAY | LOG | COUNT; if COUNT is missing, errors don't fail the test.
Prevention

Keep errors counted and trust the count:

  1. Never strip UVM_COUNT from UVM_ERROR or UVM_FATAL globally. The count is the verdict; removing it makes errors non-failing while still visible.
  2. Scope any relaxation to a specific id, documented. Use set_id_action for one benign id, like a narrow catcher, not a global severity action.
  3. Remove bring-up hacks before regression. A not-fail-yet relaxation must be reverted; audit start_of_simulation and base classes for leftover action changes.
  4. Trust the server's count as the verdict. A green result with errors in the log is a red flag that the action mapping was altered — investigate the actions, not the log.

The one-sentence lesson: the error/fatal count, not the log, is the pass/fail verdict, and the count only increments if the UVM_COUNT action is applied to the severity, so stripping UVM_COUNT from UVM_ERROR makes errors appear in the log but not count — the test passes with errors visible — so keep UVM_COUNT on errors and fatals, scope any relaxation narrowly to a documented id, and trust the count, because a green result with errors in the log means the action mapping was altered.

Common Mistakes

  • Stripping UVM_COUNT from UVM_ERROR globally. Errors then log but don't count, so the test passes with errors visible; keep the default DISPLAY|LOG|COUNT.
  • Trusting the log over the count. The count is the verdict; a green result with errors in the log means the action mapping was altered, not that the errors are benign.
  • Leaving bring-up relaxations in place. A not-fail-yet action stripped from errors can outlive its reason and silently disable pass/fail; revert it before regression.
  • Relaxing globally instead of per-id. Make a single benign id non-counting with set_id_action, narrowly and documented, not a global severity change.
  • Missetting max_quit_count. Too low quits early and masks later errors; too high lets a flood run forever — set it to bound the flood while seeing enough.
  • Forgetting fatal exits immediately. A UVM_FATAL ends the simulation at that point; don't expect code after a fatal to run.

Senior Design Review Notes

Interview Insights

The report server, uvm_report_server, is the single global engine that processes every report after the catchers — it applies the actions configured for each report's severity, formats and routes the message, and tallies the error and fatal counts that decide pass/fail. Every report from every component, after passing through the catchers, converges on this one server, so it's where the testbench-wide reporting behavior is realized. For each report, it applies the actions for that severity. The standard actions are UVM_DISPLAY, print to stdout; UVM_LOG, write to a file; UVM_COUNT, increment the tally toward pass/fail and max_quit_count; and UVM_EXIT or UVM_STOP, end or pause the simulation, which is what UVM_FATAL does. The severity-to-actions mapping is configurable via set_severity_action and set_id_action. The server formats each message — severity, time, component, id, text — and routes it to the file handles. Critically, it tallies the UVM_ERROR and UVM_FATAL counts, and those counts decide pass/fail: zero errors and zero fatals is a pass, any counted error or fatal is a fail. It also has max_quit_count, which quits the run after a set number of errors to bound a flood rather than run forever. At the end of the run, report_summarize prints the official tally. And the server is replaceable via set_server, so you can install a custom server to tailor the formatting, counting, or summary behavior for the whole testbench. The mental model is a control room at the end of the message road: every report arrives, gets stamped by severity and the actions applied — posted on the display board, filed in the logbook, marked on the scoreboard, or sounding the evacuation — and the official scoreboard, the error and fatal counts, decides whether the mission passed. The single most important thing to internalize is that the scoreboard, the count, not the chatter in the logbook, is the result. So the report server is the central engine where every report's fate is decided and the authoritative pass/fail verdict is kept.

Because the regression reads the server's error and fatal counts to decide pass/fail, and a report can be displayed and logged without being counted, so the log and the count can diverge — the count is authoritative. The server applies actions per severity, and these actions are independent: a report can be displayed to stdout and logged to a file by the display and log actions, while the count action — which is what increments the error tally — is separate. By default UVM_ERROR has all three: display, log, and count. But if the action mapping is changed so UVM_ERROR has only display and log, then errors are still printed and logged — visible in the log — but not counted, so the error count stays zero. The regression reads the count, via report_summarize or get_severity_count, not by grepping the log, so it sees zero errors and reports pass, even though the log is full of UVM_ERROR messages. This is the classic contradiction: errors in the log, zero in the summary, a green result. The deeper point is that pass/fail is purely a function of the server's counts, and the count only increments when the count action is applied. So anything that prevents an error from being counted — a catcher demoting it to a warning, or a stripped count action — silently changes the verdict regardless of what's in the log. That's why the count is the verdict and the log is not: the log is what was displayed, which is informational, while the count is what was tallied, which is the decision. The practical discipline is to keep UVM_COUNT on errors and fatals, to read the server's count for pass/fail rather than the log, and to treat a green result with errors in the log as a red flag that the action mapping was altered, not as evidence that the errors are benign. A common real failure is a bring-up hack that stripped count from errors to not fail early, left in place, so the regression passes with real errors visible in the log. So you trust the count, keep the count action intact, and investigate any divergence between log and count as an altered action mapping.

The actions are UVM_DISPLAY, UVM_LOG, UVM_COUNT, UVM_EXIT, UVM_STOP, UVM_NO_ACTION, and UVM_CALL_HOOK, and each determines part of what happens to a report. UVM_DISPLAY prints the report to standard output, so it appears on the console. UVM_LOG writes the report to a file, via the file handles configured, so it's captured in a log. UVM_COUNT increments the count for that severity — this is the pivotal one, because it's what makes a report contribute to the pass/fail tally and to max_quit_count; an error that isn't counted doesn't fail the test. UVM_EXIT ends the simulation immediately — this is what UVM_FATAL does, terminating the run at that point because a fatal is unrecoverable. UVM_STOP pauses the simulation, dropping into an interactive prompt, used for debugging. UVM_NO_ACTION does nothing — the report is effectively ignored. And UVM_CALL_HOOK invokes a callback hook for custom handling. Actions are combined as a bitmask, so a severity has a set of actions. The defaults are typically: UVM_INFO and UVM_WARNING get display and log — visible but not counted, so they don't fail the test; UVM_ERROR gets display, log, and count — so errors are visible and counted, which makes them fail; and UVM_FATAL gets display, log, and exit — so a fatal is visible, counted, and ends the simulation. The mapping is configurable per severity with set_severity_action and per id with set_id_action, so you can, for example, add count to warnings to make a stricter run that fails on warnings, or relax a specific benign id to not count. The key insight is that UVM_COUNT is the pivot that turns a report into a pass/fail-affecting event. Display and log control visibility; count controls whether it fails the test; exit controls whether it ends the run. So when you reason about a report's effect, you separate visibility — display and log — from the verdict — count — from termination — exit. And you keep count on errors and fatals, because that's the action that links a report to the pass/fail result.

max_quit_count is a server setting that quits the simulation after a specified number of counted errors, and it matters because it bounds a failure flood — stopping a broken run rather than letting it churn out thousands of errors and run to completion. When a DUT or testbench is badly broken, it can generate a huge number of errors — every transaction mismatching, a protocol completely violated. Without a bound, the simulation would keep running, counting error after error, flooding the log and consuming compute, even though you already know after a handful of errors that the test has failed. max_quit_count puts a ceiling on this: once the error count reaches the limit, the server quits the run. You've seen enough to know it failed, and you stop wasting time. The default is often a small number like five or ten, and you set it with set_max_quit_count. Why it matters in both directions: set too low, it can quit so early that you miss later errors that might be more informative for debugging — sometimes the first error cascades into many, and the root-cause error isn't the first one, so quitting at one error can hide useful context. Set too high or unbounded, a flood runs forever, wasting compute and making logs unwieldy. So you tune it to bound the flood while still capturing enough errors to debug — often you want to see a representative set of failures, not just the first, but not thousands. It interacts with the count: only counted errors increment toward max_quit_count, so an uncounted error, demoted or with count stripped, doesn't contribute, which is another reason to keep errors counted. And it's distinct from fatal: a fatal exits immediately regardless of count, because it's unrecoverable, while max_quit_count is about bounding an accumulation of recoverable errors. So max_quit_count is the circuit breaker for error floods — it stops a clearly-failing run after enough errors to confirm and debug the failure, balancing not-too-early against not-running-forever.

You customize the report server by extending uvm_default_report_server and installing it with set_server, and you do it to tailor the formatting, counting, summary, or routing of reports uniformly across the whole testbench. Because the server is the single engine all reports flow through, customizing it changes how every report is handled, which is exactly what you want for testbench-wide reporting policies. The common customizations are several. Message format: you override compose_report_message to change how messages are formatted — adding a timestamp prefix, changing the field order, adding context like a test name or a transaction id, or matching a corporate log format. Summary: you override the end-of-run summarize to print a custom report — perhaps a structured summary, additional statistics, or output in a machine-readable format for a regression dashboard. Counting or categorization: you might track additional categories of messages, or integrate the counts with an external tracking system. Routing: you might send messages to an external logging service, a database, or a structured event stream, in addition to or instead of the default file handles. You install your custom server by creating it and calling uvm_report_server::set_server, typically early, so all reports use it. Why you do this rather than per-component configuration is uniformity and central control: a reporting policy that should apply to the whole testbench — a format, a summary, an integration — belongs in one place, the server, rather than scattered. It's the one knob for testbench-wide reporting behavior. The caution is that because it's central and powerful, a custom server must preserve the essential semantics — most importantly, it must keep counting errors and fatals correctly, because the count is the verdict. A custom server that broke the counting or the summary could make the regression misread pass/fail. So you customize for format, summary, and integration freely, but you preserve the count semantics, and you test that pass/fail still works correctly. So customizing the report server is how you implement a uniform, testbench-wide reporting policy — format, summary, routing — in the single central engine, while being careful to keep the authoritative count intact.

Exercises

  1. Trace the verdict. Describe how a UVM_ERROR becomes a test failure, from emission through the server's count.
  2. Spot the stripped count. Given a passing regression with UVM_ERRORs in the log, name the likely cause and where to look.
  3. Relax narrowly. Show how to make one benign id non-counting without affecting other errors.
  4. Tune max_quit. Explain the trade-off in setting max_quit_count too low versus too high.

Summary

  • The report server (uvm_report_server) is the single, global engine that processes every report after the catchers — applying the actions configured per severity: UVM_DISPLAY (stdout), UVM_LOG (file), UVM_COUNT (tally), UVM_EXIT/UVM_STOP (end/pause; UVM_FATAL exits) — formatting and routing each message.
  • It tallies the error and fatal counts that decide pass/fail (0 = pass), with max_quit_count bounding a flood (quit after N errors) and report_summarize() printing the official tally; the severity→actions mapping is configurable (set_severity_action, set_id_action).
  • The server is replaceable (set_server) — a custom server tailors the whole testbench's reporting (format, counting, summary, routing) uniformly, while it must preserve the count semantics.
  • The governing truth: the server's error/fatal count — not the log — is the pass/fail verdict; the count only increments if UVM_COUNT is applied, so a report can be logged without being counted — a green result with errors in the log means the action mapping was altered (e.g. UVM_COUNT stripped from UVM_ERROR).
  • The durable rule of thumb: the report server processes every report by its severity's actions and keeps the error/fatal counts that are the pass/fail verdict — so keep UVM_COUNT on errors and fatals (the default), trust the count rather than the log (a green result with errors in the log means the action mapping was altered), scope any relaxation narrowly to a documented id with set_id_action rather than a global severity change, set max_quit_count to bound a flood sensibly, and when customizing the server preserve the count semantics, because the count, not the chatter in the log, is the result.

Next — UVM Logging to File: with the server understood, the next chapter covers where its output goes — logging to file. How the UVM_LOG action routes messages to file handles, opening and managing log files, splitting logs per component or severity, the relationship between the console and the file logs, and the conventions for log management that keep a large regression's output organized and searchable.