Skip to content

UVM

Report Catchers

Intercepting and modifying report messages before the report server processes them — the uvm_report_catcher callback on the report path, its actions (catch/suppress, throw/pass, change severity to promote or demote, modify or count), the legitimate uses (documented demotions, escalations, noise suppression), and the cardinal danger that an over-broad demotion or suppression masks real errors.

Reporting System · Module 24 · Page 24.3

The Engineering Problem

A third-party VIP emits an UVM_ERROR you know is benign — a spurious complaint that fails your test even though nothing is wrong. You can't (and shouldn't) modify the VIP's source to change the severity. Or your spec says a condition that a component reports as a warning is actually fatal for your design — you need it to fail. Or the log is flooded with a known-noise message that buries the real signal. In all these cases, you need to intervene on the reportchange its severity, suppress it, or modify itwithout touching the emitting component. The mechanism is the report catcher: a callback on the report path that intercepts every message before the report server processes it (prints, counts, decides pass/fail), and can change it. But this power is double-edged: a catcher that demotes an error to a warning or suppresses it removes it from the pass/fail count — and if that error was actually a real bug, the test passes while the bug ships. The problem this chapter solves is report catchers: what they are (a callback intercepting reports), what they can do (suppress, pass, change severity, modify), their legitimate uses, and the cardinal danger that an over-broad demotion or suppression masks real errors.

A report catcher (uvm_report_catcher) is a callback on the report path that intercepts every report message before the report server processes it and can modify or act on it. Its catch() method inspects the report and returns one of two dispositions: THROW (pass it through to the server — the default) or CAUGHT (suppress it — dropped, not printed, not counted). Within catch(), it can change the severity (set_severitypromote a warning to an error to fail on a known-bad condition, or demote an error to a warning to waive a known-benign one), modify the report (set_message, set_id), or count/track it. It is built on the callback mechanism (Module 22) — the report server calls out to registered catchers — so it's registered (globally or targeted by id/component) and non-intrusive (the emitting component is untouched). The legitimate uses: demote a documented, known-benign error (a justified waiver), promote a warning your spec deems fatal, suppress known noise, count events. The cardinal danger: demoting or suppressing a real error masks a bug — so catchers must be narrowly scoped (exact id + component), justified, and documentedexactly like a coverage waiver (Module 19.7), because an over-broad catcher swallows the real errors it wasn't meant to touch. This chapter is report catchers: the interception mechanism, the actions, the uses, and the masking danger.

What is a report catcher — how does it intercept and modify reports before the report server processes them, what can it do (suppress, pass, change severity, modify), what are its legitimate uses, and why does an over-broad demotion or suppression mask real errors?

Motivation — why intervene on reports at all

Sometimes the report is wrong for your context — too severe, too noisy, or under-rated — and you can't fix the emitter. The reasons report catchers exist:

  • You can't modify the emitting component. A third-party VIP, a shared component, or a sealed IP emits a report you need to handle differently — but editing its source is off-limits (breaks reuse, Module 22.1). A catcher lets you intervene on the report non-intrusively.
  • Severity is context-dependent. A warning in general might be fatal for your spec (promote it); an error from a known-benign condition might be acceptable for your setup (demote it, with justification). The emitter can't know your context; the catcher adjusts severity for it.
  • Known noise buries the signal. A frequent, benign message floods the log and hides the real failures. Suppressing it (CAUGHT) cleans the log so the real signal is visible.
  • Reports carry information worth counting. A component's reports can mark events (a protocol transition, a retry); a catcher can count or act on them without the emitter providing a separate hook.
  • But it's a waiver mechanism — dangerous if misused. Demoting or suppressing an error is a waiver of that failure — and like any waiver, it can hide a real bug if over-broad or unjustified. The power to change severity demands the discipline to scope and justify it.

The motivation, in one line: you intervene on reports because severity is context-dependent (a warning fatal for your spec, an error benign for your setup) and you can't modify the emitter (third-party/sealed), so a catcher adjusts severity, suppresses noise, or counts events non-intrusively — but because demoting/suppressing an error is a waiver of that failure, the power demands discipline: narrow scope and justification, or it masks real bugs.

Mental Model

Hold a report catcher as a checkpoint inspector on the message road — every report passes through, and the inspector can let it through, confiscate it, re-stamp its severity, or edit it:

Every report message travels a road from the component that emitted it to the report server that processes it — prints it, counts it, and decides pass/fail. A report catcher is a checkpoint inspector standing on that road, and every message passes through the checkpoint before reaching the server. The inspector has four powers. Let it through (throw): wave the message on unchanged — the default. Confiscate it (caught): take the message off the road entirely, so it never reaches the server — not printed, not counted, gone. Re-stamp its severity: change an error stamp to a warning (demote) or a warning to an error (promote), so the server treats it differently. And edit it: rewrite the message text or id. These powers are legitimate and useful: you can confiscate a known-noise message to keep the road clear, re-stamp a third-party VIP's spurious error down to a warning so it doesn't fail your run (with a documented note explaining why), or re-stamp a warning up to an error because your spec says it's critical. But the inspector is also a point of enormous danger, because confiscating or downgrading a message removes it from the count that decides pass/fail — and if the inspector is briefed too broadly ("downgrade all errors of this severity," "confiscate anything from this area"), it will also downgrade or confiscate real errors it was never meant to touch, and those failures vanish silently while the run reports clean. So the inspector must be briefed narrowly — act only on this exact message id, from this exact component, for this documented reason — or it becomes a censor that hides the failures it wasn't supposed to see. Picture every report message traveling a road from the emitting component to the report server (which prints, counts, decides pass/fail). A report catcher is a checkpoint inspector on that road — every message passes through before reaching the server. The inspector has four powers: let it through (THROW — wave it on, the default); confiscate it (CAUGHT — take it off the road, so it never reaches the server — not printed, not counted, gone); re-stamp its severity (error→warning demote, warning→error promote); and edit it (rewrite the message/id). These are legitimate: confiscate known-noise to keep the road clear, re-stamp a VIP's spurious error down (with a documented note), or re-stamp a warning up because your spec says critical. But the inspector is a point of enormous danger: confiscating or downgrading a message removes it from the pass/fail count — and if briefed too broadly ("downgrade all errors of this severity"), it also downgrades real errors it was never meant to touch, and those failures vanish silently while the run reports clean. So the inspector must be briefed narrowlyonly this exact id, from this exact component, for this documented reason — or it becomes a censor that hides the failures it wasn't supposed to see.

So a report catcher is a checkpoint inspector on the message road: every report passes through, and the inspector can let it through (THROW), confiscate it (CAUGHT/suppress), re-stamp its severity (promote/demote), or edit it. Legitimate for documented waivers (demote known-benign, with a note), escalations (promote a spec-critical warning), and noise suppression — but dangerous because confiscating/downgrading removes a message from the pass/fail count, so an over-broad inspector hides real failures. Brief the inspector narrowly (exact id + component + documented reason), or it censors the very failures it wasn't meant to touch. Intercept reports to waive, escalate, or clean — but brief the catcher narrowly, or it masks the real errors it was never meant to touch.

Visual Explanation — the catcher on the report path

The defining picture is the interception: the report passes through the catcher before the report server processes it.

Report path: emitter, report catchers (intercept), report server (print/count/pass-fail)Component emits a reportuvm_info / uvm_warning / uvm_error / uvm_fatal — a message with severity, id, and textuvm_info / uvm_warning / uvm_error / uvm_fatal — a message with severity, id, and textReport catchers intercept (catch())each registered catcher inspects and either THROWs (pass on, maybe modified) or CAUGHTs (suppress) — before the servereach registered catcher inspects and either THROWs (pass on, maybe modified) or CAUGHTs (suppress) — before the serverReport server processes thrown reportsprints, routes to logs, and counts toward pass/fail — only reports that were thrown reach hereprints, routes to logs, and counts toward pass/fail — only reports that were thrown reach herePass/fail counterror/fatal counts decide the test result — a caught or demoted error never reaches the counterror/fatal counts decide the test result — a caught or demoted error never reaches the count
Figure 1 — the report catcher intercepts on the report path before the server. A component emits a report. Before the report server processes it — printing it, counting it toward pass/fail — the report passes through any registered report catchers. Each catcher's catch() inspects the report and either throws it (passes it on, possibly modified) or catches it (suppresses it). Only thrown reports reach the server. So the catcher sits between the emitter and the server, with the power to modify or remove a report before it's counted — which is exactly why an over-broad catcher can silently drop real errors before they ever reach the pass/fail count.

The figure shows the catcher intercepting on the report path. A component emits a report (the brand-colored top — uvm_info/uvm_warning/uvm_error/uvm_fatal, with severity, id, text). Before the report server processes it, the report passes through report catchers (the warning-colored layer — each catch() inspects and either THROWs (pass on, maybe modified) or CAUGHTs (suppress)). Only thrown reports reach the server, which prints, routes to logs, and counts toward pass/fail — feeding the pass/fail count (error/fatal counts decide the result). The crucial reading is the position of the catcher — between the emitter and the server — and what that position means: the catcher sees every report before it's processed, with the power to modify or remove it before it's counted. This is why the catcher is powerful: it can change a report's severity (so the server counts it differently) or suppress it (so the server never sees it). And it's why it's dangerous: a caught or demoted error never reaches the pass/fail count — so an over-broad catcher can silently drop real errors before they'd fail the test. The warning-colored catcher layer is highlighted because it's where the interception (and the risk) lives: it's a gate on the pass/fail-determining path. The success-colored server and count are downstream — they only see what the catcher let through. So the report server's verdict (pass/fail) is only as trustworthy as the catchers are narrow — a broad catcher upstream can manufacture a passing result by swallowing errors. The diagram is the report path: emit → catchers (intercept: throw/catch/modify) → server (print/count) → pass/fail — with the catcher a gate that every report passes through before being counted. The catcher intercepts every report before the server counts it — modifying or removing it ahead of the pass/fail decision.

RTL / Simulation Perspective — writing a catcher and its actions

In code, a catcher is a class with a catch() method returning THROW/CAUGHT, registered on the report path. The example shows a narrowly-scoped demotion, a promotion, suppression, and the registration.

a report catcher: narrowly-scoped demote, promote, suppress — and registration
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === A REPORT CATCHER: intercept reports, act in catch() ===
class my_catcher extends uvm_report_catcher;
  virtual function action_e catch();
    // (1) DEMOTE a SPECIFIC known-benign error to a warning — NARROWLY scoped (exact id + component)
    if (get_severity() == UVM_ERROR && get_id() == "VIP_SPURIOUS" &&
        get_context() == "uvm_test_top.env.tpz_vip") begin
      set_severity(UVM_WARNING);                 // documented waiver: VIP-TICKET-123, known-benign
      return THROW;                              // pass it on (now as a warning)
    end
    // (2) PROMOTE a warning your spec deems fatal — exact id
    if (get_severity() == UVM_WARNING && get_id() == "OVERFLOW_NEAR") begin
      set_severity(UVM_ERROR); return THROW;     // your spec: this must fail
    end
    // (3) SUPPRESS known noise — exact id
    if (get_id() == "CHATTY_DEBUG") return CAUGHT;   // dropped: not printed, not counted
    // (4) everything else: pass through unchanged
    return THROW;
  endfunction
endclass
 
// === REGISTER the catcher on the report path (built on the callback mechanism, Module 22) ===
my_catcher cat = new();
uvm_report_cb::add(null, cat);     // global; or add(specific_component, cat) to TARGET it
 
// ✗ DANGER: a BROAD demote/suppress masks REAL errors (DebugLab):
//   if (get_severity() == UVM_ERROR) set_severity(UVM_WARNING);  // ← demotes ALL errors → bug ships!

The code shows a report catcher and its actions. The catch() method inspects each report (get_severity(), get_id(), get_context()) and acts: (1) Demote a specific known-benign error to a warning — narrowly scoped (exact id "VIP_SPURIOUS" and exact component "...tpz_vip"), with a documented waiver note (VIP-TICKET-123), returning THROW (pass it on, now as a warning). (2) Promote a warning your spec deems fatal (exact id "OVERFLOW_NEAR") to an error. (3) Suppress known noise (exact id "CHATTY_DEBUG") — return CAUGHT (dropped: not printed, not counted). (4) Everything elsereturn THROW (pass through unchanged). The catcher is registered on the report path (uvm_report_cb::add(null, cat)global; or add(specific_component, cat) to target it), built on the callback mechanism (Module 22). The danger (commented) is a broad demote — if (get_severity() == UVM_ERROR) set_severity(UVM_WARNING) — which demotes all errors → a real bug ships (the DebugLab). The shape to carry: a catcher's catch() matches narrowly (exact id and component) and acts (demote/promote/suppress/modify), returning THROW (pass, maybe modified) or CAUGHT (suppress). The narrow matching (1) is what makes a demotion safe — it only affects the one known-benign report, with a documented reason — versus the broad matching (the danger) that swallows all errors. The promotion (2) and suppression (3) are similarly scoped by exact id. The registration (global vs targeted) also scopes the catcher's reachadd(specific_component, ...) limits it to one component's reports. The demote should carry justification (the comment VIP-TICKET-123 stands for a documented waiver). Match narrowly by exact id and component, act (demote/promote/suppress) with justification, and never demote broadly.

Verification Perspective — the catcher's actions and the legitimate-versus-dangerous line

The catcher's power is four actions — and the line between legitimate and dangerous use runs through demote/suppress. Seeing the actions and the line clarifies responsible use.

Catcher actions: throw/modify/promote safe; demote/suppress are waivers, safe only if narrowthrow, modify,promotedemote, suppressstricter or cosmetic → safestricter orcosmetic →…narrow + documented→ legitimate waiverbroad → masks real errorsbroad →masks real…Catcher actionsfour powersThrow / modify / promotepass, rewrite, escalateDemote / suppressremoves from pass/fail countSafestricter or cosmeticNarrow + documentedlegitimate waiverBroadmasks real errors12
Figure 2 — the catcher's actions, and the legitimate-versus-dangerous line. Throw passes a report on unchanged — always safe. Modify rewrites the text or id — cosmetic, safe. Promote raises severity (warning to error) — safe, it makes the test stricter. The danger runs through demote (error to warning) and suppress (catch): both remove a report from the pass/fail count, so they are legitimate only when narrowly scoped to a documented, known-benign report — and dangerous when broad, because they then mask real errors. Promote and modify are safe; demote and suppress are waivers that must be justified and narrow.

The figure shows the catcher's actions and the legitimate-versus-dangerous line. Throw / modify / promote (the success-coloredsafe): throw passes a report on unchanged (always safe); modify rewrites the text or id (cosmetic, safe); promote raises severity (warning→error) — safe, it makes the test stricter. Demote / suppress (the warning-coloredthe waiver actions): both remove a report from the pass/fail countlegitimate only when narrowly scoped to a documented, known-benign report, and dangerous when broad (they then mask real errors). The verification insight is that not all four actions carry the same risk — and the risk runs through demote/suppress. Throw (no change) and modify (cosmetic) can't hide a failure. Promote can't hide a failure either — it adds strictness (more things fail), which is safe (at worst, it over-fails, which you'd notice). But demote (error→warning) and suppress (CAUGHT) remove an error from the pass/fail count — so they're waivers of failures, and like any waiver (Module 19.7), they're legitimate only when narrow and documented (this exact known-benign report, for this reason) and dangerous when broad (they also waive real errors). The success-colored safe actions (throw/modify/promote) and the narrow+documented demote/suppress are legitimate; the default-colored broad demote/suppress masks real errors. The practical rule: promote freely (stricter is safe), modify cosmetically, but treat every demote and suppress as a waiverscope it narrowly (exact id + component), justify it, document it, and never apply it broadly. The diagram is the risk map: throw/modify/promote → safe; demote/suppress → a waiver, safe only if narrow+documented, dangerous if broad — the line between responsible and masking use. Promote and modify are safe; demote and suppress are waivers — narrow and document them, or they mask real errors.

Runtime / Execution Flow — a report passing through the catcher

At run time, each report flows through the catcher's catch(), which decides its fate — pass, modify, demote/promote, or suppress — before the server counts it. The flow shows it.

Report through catch(): match criteria, act or throw, server counts thrown reportsreport emitted → enters catch() → matches the catcher's criteria? → if match: act (demote/promote/suppress/modify); if not: throw unchanged → thrown reports reach the server's count, caught are droppedreport emitted → enters catch() → matches the catcher's criteria? → if match: act (demote/promote/suppress/modify); if not: throw unchanged → thrown reports reach the server's count, caught are dropped1Report emitteda component emits a report with a severity, id, and context.2catch() matches the criteriais this the exact id and component this catcher acts on? — thenarrow match decides whether it's touched.3If match: act; if not: throw unchangedon a match, demote/promote/suppress/modify; on no match, throw itthrough untouched.4Server counts thrown reportsthrown reports (maybe modified) reach the server's pass/fail count;caught reports are dropped before it.
Figure 3 — a report passing through the catcher's catch(). A report is emitted with a severity and id. It enters the catcher's catch(), which matches it against the catcher's criteria — is this the specific id and component the catcher acts on? If it matches, the catcher acts: demote, promote, suppress, or modify. If it doesn't match, the catcher throws it unchanged. A thrown report (possibly modified) continues to the report server, which counts it toward pass/fail; a caught report is dropped before the count. The match decides whether the catcher touches the report at all, which is why a narrow match is what keeps the catcher from touching reports it shouldn't.

The flow shows a report passing through the catcher. Emit (step 1): a component emits a report with a severity, id, and context. Match (step 2): catch() matches it against the catcher's criteriais this the exact id and component this catcher acts on? — the narrow match decides whether it's touched. Act (step 3): on a match, demote/promote/suppress/modify; on no match, throw it through untouched. Count (step 4): thrown reports (maybe modified) reach the server's pass/fail count; caught reports are dropped before it. The runtime insight is that the match is the gate — it determines whether the catcher touches the report at all. A narrow match (exact id + component) means the catcher acts on only the intended reports and throws everything else unchanged — so it can't affect unrelated reports. A broad match (by severity alone, or a loose id) means the catcher acts on many reports — including ones it wasn't meant to touch. So the match scope is what makes a catcher safe or dangerous: narrowsurgical (only the known-benign report); broadindiscriminate (any matching error, real or not). This is why the catch() logic must match narrowly — it's the guard that limits the catcher's reach. The every-report-flows-through design (step 1→2) means every report is subject to the catcher's match, so a broad match has broad reach. The brand-colored emit/match feed the success-colored act-or-throw, and thrown reports reach the default-colored server count (caught are dropped). The flow is the catch() decision: emit → match (narrow?) → act (if match) or throw (if not) → server counts thrown — and the match scope is the safety control. Every report flows through the catcher's match; a narrow match touches only the intended reports, a broad one reaches reports it shouldn't.

Waveform Perspective — a narrowly-scoped catcher demoting one error id

The catcher's effect is visible on the report stream: a matching error (the one id the catcher targets) is demoted (so the error count doesn't increment), while a non-matching error passes through (and does count). The waveform shows the narrow demotion.

A narrow catcher demotes only its target error id; a non-matching error still counts

12 cycles
A narrow catcher demotes only its target error id; a non-matching error still countsVIP_SPURIOUS error arrives → catcher MATCHES → demoted to WARN → err_count stays 0VIP_SPURIOUS error arr…a different (REAL) error arrives → no match → passes as ERR → err_count → 1a different (REAL) err…narrow scope: the benign error is waived, the real error still countsnarrow scope: the beni…err_count=1 → the test FAILS on the real error (the catcher didn't touch it)err_count=1 → the test…clkreport_id--VIP------REAL------------catcher_matchsev_out--WARN------ERR------------err_count000001111111t0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — a narrowly-scoped catcher in action. Reports flow by. The catcher targets only error id VIP_SPURIOUS. When that report arrives (report_id VIP), it matches (catcher_match), is demoted to a warning (sev_out WARN), so the error count does not increment — the known-benign error is waived. When a different error arrives (report_id REAL), it does not match, passes through unchanged (sev_out ERR), and the error count increments — the real error still fails the test. The narrow match is what lets the catcher waive the one benign error while leaving the real one to fail.

The waveform shows a narrowly-scoped catcher in action. Reports flow by. The catcher targets only error id VIP_SPURIOUS. When that report arrives (report_id=VIP), it matches (catcher_match), is demoted to a warning (sev_out=WARN), so the error count does not increment — the known-benign error is waived. When a different error arrives (report_id=REAL), it does not match, passes through unchanged (sev_out=ERR), and the error count increments (err_count→1) — the real error still fails the test. The crucial reading is the selectivity of the narrow catcher: it acts on its one target (VIP_SPURIOUS → demoted, waived) and leaves everything else untouched (REAL → passes as ERR, counts, fails). The error count tells the story: it stays 0 through the benign (demoted) error and increments on the real one — so the test fails on the real error, exactly as it should, while the benign one is waived. This is the signature of a responsible catcher: it waives the one documented benign report and does not touch the real failures. The contrast (the DebugLab) is a broad catcher that demotes REAL too (because it matched by severity alone) — which would leave err_count=0 and the test passing despite the real bug. The picture to carry is that the narrow match is what separates waiving the benign from masking the real: the catcher touches only its target, so the real error survives to fail. Reading the waveform this way — does the catcher demote only its target, leaving real errors to count? — is seeing a responsible catcher. The benign error demoted (count stays 0) while the real error passes (count increments, test fails) is the signature of a narrow, documented catcher doing its job without masking anything. A narrow catcher demotes only its target; the real error it doesn't match still counts and fails the test — which is exactly the point.

DebugLab — the broad catcher that demoted a real bug to a warning

A green regression hiding a real error that a too-broad report catcher demoted to a warning

Symptom

A regression went green0 errors, all tests passing — and the block was signed off. In silicon, a real bug surfaced: a data-corruption error that the DUT monitor had, in fact, reported during the regression — as a UVM_ERRORevery run. Yet the regression had reported 0 errors. Investigating, the team found a report catcher that a developer had added months earlier to demote a known-benign UVM_ERROR from a flaky third-party VIP — so it wouldn't fail the regression. But the catcher had been scoped too broadly: instead of matching the VIP's specific id and component, it matched by severity aloneif (get_severity() == UVM_ERROR) set_severity(UVM_WARNING). So it demoted every UVM_ERROR in the entire testbench to a warning — including the DUT monitor's real data-corruption error. The real error became a warning, the error count stayed 0, the regression passed, and the bug shippedmasked by a catcher meant to waive an unrelated, benign VIP message.

Root cause

The catcher demoted by severity alone (get_severity() == UVM_ERROR) instead of matching the specific known-benign report (exact id + component), so it demoted every error in the testbench — including real ones — removing them from the pass/fail count and masking the bug:

why a regression reported 0 errors while a real error fired every run
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ BROAD demote — matches ALL errors by severity alone:
  virtual function action_e catch();
    if (get_severity() == UVM_ERROR) begin    // ← matches EVERY UVM_ERROR, from ANY component!
      set_severity(UVM_WARNING); return THROW; //   the intent was ONE benign VIP error...
    end                                         //   ...but this demotes the DUT's REAL error too
    return THROW;
  endfunction
  // → every real UVM_ERROR becomes a warning → err_count stays 0 → regression PASSES → bug SHIPS
 
✓ NARROW demote — match the SPECIFIC known-benign report (exact id + component), documented:
  virtual function action_e catch();
    if (get_severity() == UVM_ERROR && get_id() == "VIP_SPURIOUS" &&     // EXACT id
        get_context() == "uvm_test_top.env.tpz_vip") begin               // EXACT component
      set_severity(UVM_WARNING); return THROW;   // waiver: VIP-TICKET-123, known-benign, documented
    end
    return THROW;   // ALL other errors — including the DUT's real one — pass through and COUNT
  endfunction

This is the over-broad-catcher bug — the cardinal report-catcher failure, and the masking danger the chapter warns against. A developer added a catcher to demote a known-benign UVM_ERROR from a flaky third-party VIP — a legitimate waiver in intent. But the catcher matched by severity alone (get_severity() == UVM_ERROR) instead of the VIP's specific id and component — so it demoted every UVM_ERROR in the entire testbench, including the DUT monitor's real data-corruption error. The real error became a warning, was removed from the error count, and the regression passed (0 errors) while the bug shipped. The deep reason is that a catcher's match scope determines its reach, and a broad match (by severity) has broad reach (all errors) — so a catcher intended to waive one benign report actually waived every error, masking the real failures. This is exactly the waiver-integrity failure (Module 19.7) for reports: a demotion is a waiver of a failure, and an over-broad waiver hides the failures it wasn't meant to touch. The fix is to match the specific known-benign reportexact id ("VIP_SPURIOUS") and exact component ("...tpz_vip") — with a documented justification (VIP-TICKET-123), so the catcher demotes only the one benign report and all other errors — including the DUT's real one — pass through and count. The general lesson, and the chapter's thesis: a report catcher that demotes or suppresses too broadly masks real errors — a demotion/suppression is a waiver of a failure, and matching broadly (by severity alone, or a loose id) waives every matching report, including the real errors it wasn't meant to touch, so they don't fail the test and the bug ships; therefore scope every catcher narrowly (exact id + exact component), justify and document each demotion/suppression as a waiver, and never demote or suppress broadly — because a catcher is a waiver mechanism, and an over-broad waiver silently swallows the very failures it should have left to fail. Demote one error by id and component with a documented reason; demote all errors by severity, and you ship the bug the catcher swallowed.

Diagnosis

The tell is a green regression where a real error was actually reported (as a warning). Diagnose an over-broad catcher:

  1. Search the warning log for what should be errors. A real failure appearing as a warning, when it should be an error, points at a demoting catcher.
  2. Audit the report catchers' match scope. A catcher matching by severity alone, or a loose id, affects far more than intended — check for exact id and component matches.
  3. Map demoted/suppressed messages to their catchers. Trace which catcher touched a given report and whether its scope was meant to include it.
  4. Treat every demote/suppress as a waiver to review. A catcher that removes errors from the count is a waiver; confirm each is narrow, justified, and documented.
Prevention

Scope catchers narrowly and document them:

  1. Match the exact id and component. A demote or suppress must target the specific known-benign report, not a broad severity or loose id.
  2. Justify and document every demotion/suppression. Treat it as a waiver — a documented reason and ticket, reviewed, so it's accountable.
  3. Never demote or suppress by severity alone. Matching all errors of a severity demotes real errors; always add the id and component.
  4. Review catchers periodically. A catcher added for one waiver can outlive its reason; audit that each is still narrow, justified, and needed.

The one-sentence lesson: a report catcher that demotes or suppresses too broadly masks real errors — a demotion is a waiver of a failure, so matching by severity alone or a loose id waives every matching report including the real errors it wasn't meant to touch, removing them from the pass/fail count so the bug ships, so scope every catcher narrowly to the exact id and component, justify and document each as a waiver, and never demote or suppress broadly.

Common Mistakes

  • Demoting or suppressing by severity alone. Matching all errors of a severity waives real errors too; match the exact id and component.
  • Treating a demotion as harmless. A demote or suppress is a waiver of a failure; an unjustified or broad one masks bugs — justify and document each.
  • Over-broad registration. A global catcher with a loose match touches reports across the testbench; target by component and match narrowly.
  • Forgetting catchers outlive their reason. A catcher added for one waiver can keep masking after the underlying issue is fixed; review and remove stale catchers.
  • Promoting where a demote was meant (or vice versa). Promote raises severity (stricter, safe); demote lowers it (a waiver, risky) — use the right direction deliberately.
  • Suppressing real signal as noise. A suppress meant for noise can drop meaningful messages if the id is loose; confirm the suppressed id is genuinely benign noise.

Senior Design Review Notes

Interview Insights

A report catcher, uvm_report_catcher, is a callback on the report path that intercepts every report message before the report server processes it, and can modify or act on it. The report server is what prints reports, routes them to logs, counts errors and fatals, and decides pass/fail. A report catcher sits between the emitting component and the server, so every report passes through it first. Its catch method inspects the report — its severity, id, context — and returns one of two dispositions: THROW, which passes the report on to the server, possibly modified, the default; or CAUGHT, which suppresses the report entirely, so it's dropped, not printed and not counted. Within catch, it can do several things. Change the severity with set_severity — promote, raising a warning to an error to make the test fail on a condition your spec deems critical, or demote, lowering an error to a warning to waive a known-benign condition. Modify the report with set_message or set_id, rewriting the text or id. And count or track reports, acting on them. It's built on the callback mechanism, so it's registered on the report path — globally, or targeted to a specific component — and it's non-intrusive, meaning the emitting component is untouched, which is why it's useful when you can't or shouldn't modify a third-party VIP or sealed IP. The legitimate uses are demoting a documented known-benign error so it doesn't fail your run, promoting a warning your spec considers fatal, suppressing known noise to keep the log clean, and counting events. The mental model is a checkpoint inspector on the message road: every message passes through, and the inspector can let it through, confiscate it, re-stamp its severity, or edit it. But the catcher is also dangerous, because demoting or suppressing removes a report from the pass/fail count, so if scoped too broadly it masks real errors — which is why catchers must be narrowly scoped and justified. So a report catcher is a powerful, non-intrusive interception point on the report path with the power to change how reports are handled before they're counted.

Because demoting an error to a warning or suppressing it removes that report from the pass/fail count, so if it was a real error, the test passes while the bug ships — a demote or suppress is effectively a waiver of a failure. The report server decides pass/fail from the error and fatal counts. When a catcher demotes a UVM_ERROR to a UVM_WARNING, the report no longer counts as an error, so it doesn't contribute to failing the test. When a catcher suppresses a report with CAUGHT, it's dropped entirely — not printed, not counted. Both remove the report from the count that determines whether the test passes. That's fine if the report was genuinely benign — a spurious error from a flaky third-party VIP that you've investigated and documented as harmless. But it's catastrophic if the report was a real error, because demoting or suppressing it makes a real failure invisible: the regression reports zero errors and passes, and the bug ships. The danger is amplified by scope. A catcher intended to waive one specific benign report can, if matched too broadly — by severity alone, like demoting every UVM_ERROR, or a loose id — also demote or suppress real, unrelated errors it was never meant to touch. The classic disaster is a catcher added to demote one flaky VIP error that matches by severity, so it demotes every error in the testbench, including a real data-corruption error from the DUT monitor, and that real error becomes a warning, the count stays zero, the regression passes green, and the bug ships, masked by a catcher meant to waive something else entirely. This is exactly the waiver-integrity problem from coverage closure, applied to reports: a demotion is a waiver, and an over-broad waiver hides the failures it wasn't meant to touch. So the discipline is to treat every demote and suppress as a waiver — scope it narrowly to the exact id and component, justify and document it like any waiver, and never apply it by severity alone. Promote and modify are safe because promote only makes the test stricter and modify is cosmetic, but demote and suppress remove failures from the count, which is why they demand narrow scope and justification.

You scope it narrowly by matching the exact id and the exact component of the specific known-benign report, rather than by severity alone or a loose id, so the catcher acts only on the one report it's meant to waive and throws everything else unchanged. The match in the catch method is the gate that determines the catcher's reach. If you match broadly — for example, if get_severity equals UVM_ERROR — the catcher acts on every error in the testbench, from any component, which means it demotes or suppresses real errors along with the intended one. If you match narrowly — get_severity equals UVM_ERROR and get_id equals the specific benign id and get_context equals the specific component path — the catcher acts only on that exact report, and every other error, including real ones, passes through and counts. So the narrow match is what separates waiving the benign from masking the real. Concretely, for a flaky VIP error, you match the VIP's specific error id and its component context, not just the severity. Registration also scopes the reach: registering the catcher globally with a loose match touches reports across the testbench, while targeting it to a specific component limits it. Beyond the match, you document and justify each demotion or suppression as a waiver — a reason, a ticket, reviewed — because a catcher that removes errors from the count is exactly like a coverage waiver and should be just as accountable. You also review catchers periodically, because a catcher added for one waiver can outlive its reason — the underlying VIP bug gets fixed, but the catcher keeps demoting, potentially masking new real errors. And you prefer promote and modify, which are safe, and use demote and suppress sparingly and tightly. The waveform intuition is that a narrow catcher demotes only its target id, so the error count stays zero through the benign error but increments on a real one, and the test fails on the real error as it should. So the rule is: exact id plus exact component, documented justification, periodic review, and never demote or suppress by severity alone — that's what keeps a catcher a precise waiver rather than a blanket that swallows real failures.

Promoting a report — raising its severity, like a warning to an error — is appropriate when your specific context or spec considers a condition more serious than the emitting component rated it, and it's safer than demoting because promoting makes the test stricter, which can only cause more things to fail, never hide a failure. Components emit reports at a severity that reflects a general judgment, but severity is context-dependent. A condition a reusable component flags as a warning might be unacceptable for your particular design — your spec says it must never happen, so it should fail the test. You can't necessarily modify the component, so you use a catcher to promote that warning to an error for your run. Examples are a near-overflow warning your spec treats as a hard error, a protocol deviation a generic VIP warns about but your strict profile forbids, or a corner condition your design must never hit. Promoting makes the test fail on these, enforcing your stricter requirements. Why it's safer than demoting comes down to the direction of risk. Demoting lowers severity, removing a report from the error count — it waives a failure, so if you're wrong about it being benign, or you scope it too broadly, you hide a real bug and ship it. Promoting raises severity, adding to the error count — it creates a failure. The worst case of an over-broad promote is that you fail tests you didn't mean to, which is annoying but visible — you'll see the failures and investigate, and you can't accidentally ship a bug because you over-failed. The worst case of an over-broad demote is silent masking — a real error vanishes from the count and the bug ships undetected. So promoting is fail-safe: errors of judgment surface as visible over-failures you'll notice and fix. Demoting is fail-dangerous: errors of judgment surface as invisible under-failures you won't notice until silicon. That asymmetry is why the disciplines focus on demote and suppress — they need narrow scope and justification — while promote and cosmetic modify are considered safe. You still scope a promote sensibly to the right condition, but it doesn't carry the masking risk, because making the test stricter can't hide a failure.

A report catcher is built on the UVM callback mechanism — it's a callback on the report path that the report server calls out to — and a demotion or suppression by a catcher is functionally a waiver of a failure, so it carries the same integrity requirements as a coverage waiver. On the callback side, the report path is instrumented with a callback hook: when a report is emitted, before the server processes it, the mechanism calls the registered report catchers' catch methods, exactly like any callback call-out. So a report catcher is a specific application of the general callback pattern from the callbacks module — the report server is the component exposing the hook, the catcher is the callback class implementing catch, and you register it via the callback pool, globally or targeted. This is why catchers are non-intrusive and composable: the emitting component is untouched, and multiple catchers can be registered, each inspecting the report in turn. It also means the callback reuse and runtime-customization principles apply — you can add and remove catchers, scope them, and you should keep them self-contained. On the waiver side, the connection is conceptual but important. The coverage closure module established that a waiver is a deliberate, documented decision to exclude something from a pass/fail requirement, legitimate only when narrow, justified, reviewed, and tracked, and dangerous when it silently hides real failures. A report catcher that demotes an error to a warning or suppresses it does exactly that — it excludes a failure from the pass/fail count. So it is a waiver, and it must meet the same bar: narrowly scoped to the specific known-benign report by exact id and component, justified with a documented reason and ticket, reviewed, and periodically audited so it doesn't outlive its reason. The over-broad catcher that demotes all errors by severity is the report-layer version of an unjustified blanket waiver that hides real failures. So the two connections are: mechanically, a report catcher is a callback on the report path, inheriting the callback module's patterns; and in terms of integrity, a demote or suppress is a waiver, inheriting the closure module's discipline that waivers must be narrow, documented, and accountable, or they mask the very failures they should leave to fail.

Exercises

  1. Write a narrow demote. Write a catch() that demotes only a specific VIP error by id and component, with everything else passing through.
  2. Spot the over-broad catcher. Given a catch() matching by severity alone, explain what it masks and how to narrow it.
  3. Promote a warning. Write a catch() that promotes a spec-critical warning to an error, and explain why promotion is safe.
  4. Justify a waiver. Describe what documentation a demotion catcher should carry to be a legitimate, accountable waiver.

Summary

  • A report catcher (uvm_report_catcher) is a callback on the report path that intercepts every report before the report server processes it (prints, counts, decides pass/fail) and can modify or act on itcatch() returns THROW (pass through) or CAUGHT (suppress).
  • Within catch(), it can change severity (set_severitypromote a warning to an error, or demote an error to a warning), modify the message/id, or count/track; it is built on the callback mechanism (Module 22), registered globally or targeted, and non-intrusive.
  • Legitimate uses: demote a documented, known-benign error (a justified waiver), promote a warning your spec deems fatal, suppress known noise, count events — promote and modify are safe (stricter/cosmetic); demote and suppress are waivers of failures.
  • The cardinal danger: demoting or suppressing too broadly (by severity alone or a loose id) removes real errors from the pass/fail count, so the test passes while the bug ships — masking failures the catcher wasn't meant to touch.
  • The durable rule of thumb: a report catcher intercepts reports before the server counts them, so it can demote, suppress, promote, or modify — but a demote or suppress is a waiver of a failure, so scope every catcher narrowly to the exact id and component, justify and document each like a coverage waiver, prefer safe promotions and cosmetic modifications, review catchers so they don't outlive their reason, and never demote or suppress by severity alone, because an over-broad catcher silently swallows the real errors it should have left to fail.

Next — Report Server: with catchers in hand, the next chapter examines the engine they plug into — the report server. How the central report server receives every report after the catchers, applies actions per severity (display, log, count, exit), tallies the error and fatal counts that decide pass/fail, formats and routes messages, and how customizing the report server tailors the whole reporting behavior of the testbench.