Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
    • Callbacks, Command Line, Seeds and Versions
    • Synchronisation and Reporting
      • Events, Barriers and Pools
      • Report Catchers and Waivers
      • Custom Report Servers and the Verdict Line
      • Heartbeats and Watchdogs
      • Transaction Recording
    • Testbenches at Scale
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Synchronisation and Reporting
  1. Topics
  2. UVM Verification
  3. Advanced UVM in Practice
  4. Synchronisation and Reporting
  5. Report Catchers and Waivers

Part 10 · Advanced Topics · Intermediate

Report Catchers and Waivers

Where a catcher sits in a message's path, demote and promote, THROW versus CAUGHT, per-component versus type-wide registration, the bench's DMA-142 waiver, waiver discipline and the false pass.

On this page

    A new revision of a third-party PCIe VIP prints a protocol error that its vendor calls harmless, and 300 tests turn red overnight. Someone adds a report catcher that demotes every error whose ID contains MISMATCH, "until the vendor fixes it". Four months later a real data-path bug ships to silicon. Its scoreboard error had the ID SB_MISMATCH, and every one of them was demoted to an info by a catcher nobody remembered.

    A report catcher is the most powerful reporting tool UVM has, and the most dangerous. This lesson shows exactly where it sits in a message's path, what it can change, how the bench waives one known issue narrowly, and the discipline that keeps a waiver from becoming a false pass.

    The idea: a customs officer who can relabel a parcel

    Every message travels from the component that reported it to the global report server, which counts it. A report catcher is a customs officer on that road. It can open the parcel, read the label (severity, ID, message, the component that sent it), change the label, and then either wave it on (THROW) or confiscate it (CAUGHT). The server only ever sees what the officers let through, labelled the way they left it. That is why a catcher can turn a failing test into a passing one, and why each catcher must be as narrow as a court order.

    Catchers are callbacks: uvm_report_catcher derives from uvm_callback, and you register one on a report object with uvm_report_cb::add(). The object you register it on decides whose messages it sees.

    The picture: the exact order a message travels

    Flowchart: a reported error passes through the component's report handler, which applies severity overrides and looks up the action, then the report hook, then the registered catchers; a caught message is dropped, a thrown message with an action is counted, displayed and logged by the server.
    Read from the 1800.2 reference source (uvm_report_handler::process_report_message, uvm_default_report_server). Overrides act before catchers; catchers act before counting.

    Three consequences follow from that order, and all three were confirmed in a scratch run:

    • A catcher sees the severity after any override. An SB_EMPTY error overridden to info on a component reaches that component's catcher already as UVM_INFO.

    • Changing the severity changes the action. When the message still carries its old severity's default action and the catcher does not call set_action(), the 1800.2 library replaces it with the default action of the new severity. An error demoted to info loses UVM_COUNT; a warning promoted to error gains it.

    • `CAUGHT` makes a message vanish from the log and from the counts. A demotion with THROW leaves evidence; prefer it.

    Build it: the bench's narrow waiver

    dma_waiver_test reproduces a known bench limitation (war story 9): the DMA ignores a KEY write while it is busy, but the scoreboard takes the new key at once, so the remaining words of that job are reported as mismatches. The limitation is tracked as ticket DMA-142. The catcher waives only that:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // snippet: from the course DMA bench
    // A narrow waiver: demotes one message ID whose text matches one pattern, tags
    // it with the tracking ticket, and counts what it waived.
    class dma_waiver_catcher extends uvm_report_catcher;
      string       waive_id;
      string       pattern = "*";
      string       ticket;
      int unsigned waived;
    
      function new(string name = "dma_waiver_catcher");
        super.new(name);
      endfunction
    
      function action_e catch();
        if (get_severity() == UVM_ERROR && get_id() == waive_id && uvm_is_match(pattern, get_message())) begin
          set_severity(UVM_INFO);
          set_message({"[WAIVED ", ticket, "] ", get_message()});
          waived++;
        end
        return THROW;
      endfunction
    endclass
    • One severity, one ID, one pattern. It acts only on UVM_ERROR with ID SB_MISMATCH whose text matches pattern (uvm_is_match is glob matching).

    • It tags the message with the ticket, so the waived lines are still in the log and searchable.

    • It counts what it waived, so the test can report the number.

    • It always returns `THROW`. Nothing disappears.

    The test attaches it to the scoreboard only, after the env exists, and reports the count:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // snippet: from dma_waiver_test in the course DMA bench (dma_tb.sv)
    function void end_of_elaboration_phase(uvm_phase phase);
      super.end_of_elaboration_phase(phase);
      catcher = new("catcher");
      catcher.waive_id = "SB_MISMATCH";
      catcher.ticket   = "DMA-142";
      uvm_report_cb::add(env.sb, catcher);     // this component only
    endfunction
    
    function void report_phase(uvm_phase phase);
      `uvm_info("WAIVER", $sformatf("%0d message(s) waived under %s", catcher.waived, catcher.ticket), UVM_LOW)
    endfunction

    Per component or type-wide: whose messages a catcher sees

    uvm_report_cb::add(env.sb, c) registers the catcher on that one report object. It sees messages reported by env.sb, not by its children: callbacks on a report object are not hierarchical. uvm_report_cb::add(null, c) registers it type-wide, for every report object in the run. The scratch run attached a demoting catcher to a component p and a promoting catcher type-wide:

    design.sv
    1
    2
    3
    4
    5
    6
    // snippet: scratch run (sync_tb.sv), in the test's end_of_elaboration_phase
    demote_catcher  d  = new("d");    // demotes VIP_X errors to warnings
    promote_catcher pr = new("pr");   // promotes PROTO_TIMEOUT warnings to errors
    uvm_report_cb::add(p, d);         // p only, not p.c
    uvm_report_cb::add(null, pr);     // every report object
    p.set_report_severity_id_override(UVM_ERROR, "SB_EMPTY", UVM_INFO);
    Diagram
    scratch run: +UVM_TESTNAME=catcher_test
    UVM_ERROR sync_tb.sv(152) @ 10000: uvm_test_top.p.c [VIP_X] child error
    UVM_WARNING sync_tb.sv(167) @ 20000: uvm_test_top.p [VIP_X] [DEMOTED] parent error
    UVM_ERROR sync_tb.sv(168) @ 20000: uvm_test_top.p [PROTO_TIMEOUT] [PROMOTED] no response in 64 cycles
    UVM_INFO sync_tb.sv(169) @ 20000: uvm_test_top.p [SB_EMPTY] overridden then caught
    --- UVM Report catcher Summary ---
    Number of demoted UVM_ERROR reports  :    1
    Number of caught UVM_ERROR reports   :    0
    • The child's VIP_X error was not demoted: the catcher on p never saw it.

    • Promotion is the underused direction. Many VIPs report protocol violations as warnings, which never fail a test; a type-wide promoting catcher for exactly those IDs makes them count.

    • The library prints a catcher summary of demoted and caught messages. A regression that reads it can flag runs where catchers did unexpected work.

    Note

    In UVM 1.2 the same API applies: uvm_report_cb::add(obj, c) with obj = null for type-wide, catch() returning THROW or CAUGHT, and the protected setters set_severity, set_id, set_message, set_verbosity and set_action. Use callback_mode(0) on a catcher to switch it off without removing it.

    Waiver discipline: five fields, no exceptions

    A waiver is a promise that a known message is harmless, for a reason, for a while. Write all five parts of that promise into the code:

    Field

    In the bench

    Why

    Narrow ID

    waive_id = "SB_MISMATCH"

    An exact ID, never a substring or a glob over IDs.

    Narrow text

    pattern (default *, and dma_waiver_test leaves it there)

    Narrow it to the known signature, for example the address range of the known issue. The demo is deliberately wide: every SB_MISMATCH from env.sb is waived, which is acceptable only because the test exists to show the mechanism.

    One component

    uvm_report_cb::add(env.sb, ...)

    Never type-wide for a demotion.

    Ticket

    ticket = "DMA-142", tagged into the message

    The waived line is searchable and someone owns closing it.

    Expiry and expected count

    not in the bench; add them

    A waiver must end, and must fail loudly when it waives too much or nothing at all.

    The last row is what turns a catcher from a muzzle into a contract. A derived waiver can check both at the end of the run:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: a waiver with an expected count and an expiry
    class dma_waiver_rule extends dma_waiver_catcher;
      int unsigned max_waived  = 8;     // the known issue never produces more
      int unsigned expires_rev = 7;     // RTL revision that fixes DMA-142
      function new(string name = "dma_waiver_rule");
        super.new(name);
      endfunction
      // Called from the test's report_phase.
      function void audit();
        int unsigned rtl_rev;
        if ($value$plusargs("RTL_REV=%d", rtl_rev) && rtl_rev >= expires_rev)
          `uvm_error("WAIVER_EXPIRED", $sformatf("%s should be fixed in rev %0d; remove the waiver", ticket, expires_rev))
        if (waived > max_waived)
          `uvm_error("WAIVER_EXCESS", $sformatf("%s waived %0d messages, more than the known %0d", ticket, waived, max_waived))
        if (waived == 0)
          `uvm_warning("WAIVER_UNUSED", $sformatf("%s waived nothing in this test", ticket))
      endfunction
    endclass

    Large teams often move waivers out of the simulation entirely, into the regression tool, which matches failure signatures against a reviewed waiver list after the run. That keeps every message intact in the log. In-simulation catchers remain the right tool for a VIP's known noise, where the message must not count at all.

    Watch it run

    Diagram
    +UVM_TESTNAME=dma_waiver_test
    UVM_INFO dma_tb.sv(396) @ 375000: uvm_test_top.env.sb [SB_MISMATCH] [WAIVED DMA-142] expected 4bce257b, got b4312584
    UVM_INFO dma_tb.sv(396) @ 395000: uvm_test_top.env.sb [SB_MISMATCH] [WAIVED DMA-142] expected 98357889, got 67ca7876
    UVM_INFO dma_tb.sv(396) @ 435000: uvm_test_top.env.sb [SB_MISMATCH] [WAIVED DMA-142] expected 089dd98a, got f762d975
    UVM_INFO dma_tb.sv(396) @ 475000: uvm_test_top.env.sb [SB_MISMATCH] [WAIVED DMA-142] expected 2d9cf6a9, got d263f656
    UVM_INFO dma_tb.sv(396) @ 525000: uvm_test_top.env.sb [SB_MISMATCH] [WAIVED DMA-142] expected 511e14a1, got aee1145e
    UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 3 words matched, 5 mismatched
    UVM_INFO dma_tb.sv(1899) @ 655000: uvm_test_top [WAIVER] 5 message(s) waived under DMA-142
    UVM_ERROR :    0
    UVM_FATAL :    0
    • Five SB_MISMATCH lines, each tagged [WAIVED DMA-142], all at UVM_INFO. The evidence is in the log; the server did not count them as errors.

    • "3 words matched, 5 mismatched" is the scoreboard's own report and still tells the truth.

    • "5 message(s) waived under DMA-142" is the count a reviewer compares with the ticket. If it ever says 6, something new is being waived.

    What breaks: the false pass you build yourself

    The bench's dma_false_pass_test is the opening story in miniature. It is the early-end bug (the test drops its objection before any data moves), after someone "quietened" the scoreboard's end-of-test errors with two unconditional severity overrides. The scoreboard says plainly that it checked nothing, and the verdict says PASS:

    Diagram
    +UVM_TESTNAME=dma_false_pass_test
    UVM_INFO dma_tb.sv(407) @ 155000: uvm_test_top.env.sb [SB_EMPTY] no words were compared: the test checked nothing
    UVM_INFO dma_tb.sv(411) @ 155000: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
    UVM_ERROR :    0
    UVM_FATAL :    0
    TEST VERDICT: PASS (UVM_ERROR=0, UVM_FATAL=0)

    No catcher was even needed: a severity override is a catcher with no conditions. The fix is not to ban demotions but to make every one of them carry the five fields above, and to keep SB_EMPTY-style "I checked nothing" errors off every waiver list. The full diagnosis of this failure, and how to detect it in a regression, is in the false pass playbook.

    Interview

    "A VIP reports a known, harmless error. How do you stop it failing your tests?"

    A weak answer: "Add a report catcher that changes it to a warning." Which messages, on which components, until when?

    A strong answer: "First I check it really is harmless and get it tracked. Then a catcher that matches the exact ID and the known text pattern, registered on the VIP's component, not type-wide, that demotes rather than catches, tags the message with the ticket, and counts. In report_phase the test checks the count against the known number and errors if the waiver expired with the fix revision. Severity overrides and catchers act before the server counts, so a broad one can turn real failures green; every waiver goes through review like code."

    Expect the follow-ups: Does a catcher on the env see its agents' messages? (no; callbacks on a report object are not hierarchical, use type-wide with a component check, or add it per component) and What happens to the action when you demote an error? (unless set_action() was called, it becomes the default action of the new severity, so UVM_COUNT goes away).

    Check yourself A catcher registered with uvm_report_cb::add(env, c) is supposed to demote an error from env.agt.mon. It does nothing. Why, and what are the two fixes?

    Catchers registered on a report object see only that object's own messages, not its children's. Either register it on env.agt.mon, or register it type-wide (null) and check get_client().get_full_name() inside catch() so it still acts on one component only.

    Check yourself A catcher returns CAUGHT for a VIP's known error. Six months later a regression on a new VIP revision is green, yet the bug the error reported is real. What made it invisible, and what would have kept the evidence?

    CAUGHT removes the message from the log and from every count, so nothing showed it was still firing. A demotion with THROW and a ticket tag keeps the line in the log, and an expected-count audit would have flagged the change.

    Check yourself Why can a catcher not rescue an SB_EMPTY error that a severity override already turned into an info?

    Severity overrides are applied by the component's report handler before the message reaches any catcher. The catcher sees an info; the error no longer exists anywhere in the path.

    Next: catchers decide what each message means. The report server decides what the whole run means, and it must say so even when the run dies: Custom Report Servers and the Verdict Line.

    Related topics

    • Up: Synchronisation and Reporting

    • Reporting and Verbosity Control

    • Callback Types and Hooks

    • False pass playbook

    Previous

    175 / 280

    Next: Custom Report Servers and the Verdict Line

    Up next

    Up nextCustom Report Servers and the Verdict Line

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Heartbeats and Watchdogs
    • Transaction Recording