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
    • Agent Architecture
    • Reusable VIP: Active, Passive and Protocol Agents
    • Error and Fault Injection
      • Sequence-Directed Faults
      • Driver Injection Hooks
      • Callbacks versus Factory Overrides
      • Checking Expected Faults
    • Agent Patterns: Slaves, Reset, Interrupts and Pipelines
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Error and Fault Injection
  1. Topics
  2. UVM Verification
  3. UVM Agents & Protocol VIP
  4. Error and Fault Injection
  5. Checking Expected Faults

Part 6 · Agents & Protocol IP · Intermediate

Checking Expected Faults

Why the data scoreboard passes a truncated job, how to predict the DMA's ERR response from observed traffic, and how to fail missed faults and demote expected errors.

On this page

    dma_truncate_test passes, and its scoreboard line says 4 words matched, 0 mismatched. A new team member is puzzled: the packet was cut in half, so why did the scoreboard not complain? Then a sharper question: suppose the DMA had a bug and ignored the early TLAST. What in this bench would fail? Only the test's own if (status[2] !== 1'b1) line, which lives in one test and nowhere else. Who in a testbench should know that a fault is expected, and how should they find out?

    The idea: a referee watches the foul

    There are two ways for a checker to know a fault is coming. It can be told: the test or sequence sends a "fault planned" flag to the scoreboard by some side channel. Or it can see: it predicts the DUT's error response from the same monitored traffic it already uses to predict data. A football referee who blows the whistle because they saw the foul is trustworthy; one who waits for a coach to tell them a foul happened is not, because the coach's plan and what happened on the pitch can differ.

    The told approach has exactly that weakness. If the injection silently failed (the override was registered late, a callback was never attached), the flag still says "fault planned", the DUT behaves normally, and the scoreboard dutifully reports a missed fault that the DUT never received, or worse, forgives a real error at the wrong moment. The seen approach checks the DUT against what actually reached its pins. Keep the told approach for faults no monitor can observe, such as a memory word corrupted through a backdoor.

    Why the data scoreboard is not surprised

    dma_scoreboard predicts each output word from an input word seen by the input monitor. The truncating driver dropped beats 4 to 7 before the pins, so the monitor saw four beats, the scoreboard made four predictions, the DMA moved four words, and all four matched. That is correct behaviour: the data checker's question is "did the DUT transform what it received correctly?", and the answer was yes. The error response, ERR instead of DONE, is a separate question that the data checker was never asked.

    The picture

    Flow diagram: the APB monitor's LEN and GO writes and the input monitor's beats feed an error predictor; its expectation and the observed STATUS reads feed a status check whose verdict is either counted as tolerated or reported as an error.
    The scoreboard predicts the error response from observed traffic, then checks the STATUS read (and optionally the interrupt) against it.

    Build it: predict ERR from the pins

    The DMA's rule is simple enough to predict exactly: after GO, if an input beat with TLAST arrives before LEN beats, the job ends in ERR; otherwise it ends in DONE. The scoreboard already receives every APB transfer and every input beat, so the extension needs only a little state. It extends dma_scoreboard, which declares its write_* methods virtual precisely so that a derived scoreboard's versions are the ones the analysis imps call.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // snippet: scratch run alongside the DMA bench
    class dma_err_scoreboard extends dma_scoreboard;
      `uvm_component_utils(dma_err_scoreboard)
      int unsigned len_reg, beats_in;
      bit          job_open, err_expected;
      int unsigned n_err_seen, n_done_seen;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      virtual function void write_apb(apb_item t);
        super.write_apb(t);                      // KEY tracking stays in the base class
        if (t.slverr) return;                    // a rejected access changes nothing
        if (t.write && t.addr == LEN) len_reg = t.data[15:0];
        if (t.write && t.addr == CTRL && t.data[0]) begin   // GO: a new job starts
          beats_in = 0; err_expected = 0; job_open = 1;
        end
        if (!t.write && t.addr == STATUS && job_open && !t.rdata[0]) begin
          check_status(t.rdata);                 // first STATUS read after the job ended
          job_open = 0;
        end
      endfunction
    
      virtual function void write_in(axis_item t);
        super.write_in(t);                       // the data prediction is unchanged
        if (!job_open) return;
        beats_in++;
        if (t.last && beats_in < len_reg) err_expected = 1;   // the spec's ERR rule, seen on the pins
      endfunction
    
      function void check_status(bit [31:0] st);
        bit [1:0] exp = err_expected ? 2'b10 : 2'b01;        // {ERR, DONE}
        if (st[2:1] !== exp)
          `uvm_error("SB_STATUS", $sformatf("STATUS=%08h, expected %s", st, err_expected ? "ERR" : "DONE"))
        else if (err_expected) n_err_seen++;
        else n_done_seen++;
      endfunction
    
      function void report_phase(uvm_phase phase);
        super.report_phase(phase);
        `uvm_info("SB_ERR", $sformatf("%0d job(s) ended in ERR as predicted, %0d in DONE as predicted",
                  n_err_seen, n_done_seen), UVM_LOW)
      endfunction
    endclass

    Notice what is not here: no flag from the test, no knowledge of which driver or callback did the cutting. The scoreboard would predict ERR just the same for a short packet from a sequence, a truncating override, a callback, or a DUT upstream in a subsystem that really sent a short packet. A test turns it on with one more type override, dma_scoreboard::type_id::set_type_override(dma_err_scoreboard::get_type()).

    The same structure extends to the interrupt: the expected level after a job is IRQ_EN & (DONE | ERR), so with the interrupt agent connected, the scoreboard can also check that the line rose after the job and fell after the W1C write.

    Watch it run

    A scratch test overrides both the stream driver (truncating) and the scoreboard. Job 1 has LEN = 8 and is cut after beat 3; job 2 has LEN = 3, shorter than the cut, so it runs clean:

    Diagram
    UVM_INFO dma_tb.sv(520) @ 275000: uvm_test_top.env.in_agt.drv [TRUNC] TLAST forced on beat 3
    UVM_INFO dma_tb.sv(411) @ 825000: uvm_test_top.env.sb [SB] 7 words matched, 0 mismatched
    UVM_INFO scratch_tb.sv(368) @ 825000: uvm_test_top.env.sb [SB_ERR] 1 job(s) ended in ERR as predicted, 1 in DONE as predicted
    UVM_ERROR :    0
    • 7 words matched: 4 from the cut job and 3 from the clean one. Data checking is untouched.

    • 1 ERR and 1 DONE, both as predicted: the scoreboard knew which job should fail without being told, and it would have flagged a DUT that reported DONE for job 1 or ERR for job 2.

    Tolerated, surprising, and missed

    Every fault test sorts what happened into three buckets. Counting each one separately is what keeps the bench from forgiving real bugs.

    What the DUT did

    Predicted

    Verdict

    Error response

    Error

    Tolerated: counted, not an error

    Error response

    No error

    Surprising: an unexpected error, test fails

    Normal response

    Error

    Missed fault: a DUT bug in its error detection, test fails

    Normal response

    No error

    Normal compare applies

    Forgive by prediction, never by quota. A scoreboard that tolerates "up to two errors per test" will forgive a real bug in a clean job as long as the planned fault happened to be missed.

    A missed fault often looks like a hang

    On this bench, a DMA that ignored the early TLAST would not report DONE. It would wait for words 5 to 8, which the truncating driver dropped, so no interrupt would ever come and run_job would wait forever. Missed faults in a DUT frequently show up as a hang rather than a wrong status, which is why every fault test needs a bounded run: uvm_root::get().set_timeout(...) in the base test, as dma_timeout_test shows, turns the hang into a PH_TIMEOUT fatal with a time stamp.

    Expected errors from other checkers

    Some injected faults make other checkers complain, correctly: a protocol assertion or a VIP monitor flags the violation you injected on purpose. Do not switch those checkers off for the whole test. Demote exactly the expected message, only while the fault is active, and count it, so the test fails if the expected error did not appear:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: demote one expected error ID and count it
    class expect_err_catcher extends uvm_report_catcher;
      string       id_to_catch;
      int unsigned caught;
      function new(string name = "expect_err_catcher");
        super.new(name);
      endfunction
      function action_e catch();
        if (get_severity() == UVM_ERROR && get_id() == id_to_catch) begin
          set_severity(UVM_INFO);                // expected: keep the message, drop the error
          caught++;
        end
        return THROW;
      endfunction
    endclass
    // In the test, for a monitor whose protocol check reports AXIS_EARLY_LAST:
    //   c.id_to_catch = "AXIS_EARLY_LAST"; uvm_report_cb::add(env.in_agt.mon, c);
    // In check_phase: if (c.caught == 0) `uvm_error("EXPECT", "the injected fault was never flagged")

    What breaks: the fault test that could not fail

    A team had a large fault-injection suite built on the told approach: each fault sequence set a fault_planned bit in a shared object, and the scoreboard skipped every check on a transaction that carried it, data and status alike. After a refactor, the factory override that installed the error-injecting driver was registered one line too late, after the env was built. Every fault test kept passing for a month.

    Nothing was injected, so the DUT behaved normally; and since the scoreboard skipped all checks on planned transactions, a normal response to a planned fault was never examined. The tests could not fail. The redesign did three things this lesson teaches: the scoreboard predicted error responses from observed traffic, so a normal response where the pins showed a fault became an error; a missed fault became its own failure instead of a skipped check; and each fault test asserted that the fault was seen on the pins (a count from the input monitor), not merely planned.

    Interview

    "How does your scoreboard handle expected errors without hiding real bugs?"

    A weak answer: "The test sets a flag and the scoreboard skips checking those transactions." Skipping checks forgives everything, including a DUT that ignored the fault, and a broken injection is invisible.

    A strong answer: "The scoreboard predicts the error response from what the monitors observed, the same way it predicts data: for the DMA, a TLAST before LEN beats after GO means ERR, otherwise DONE, and it checks the STATUS read and the interrupt against that. Data is still compared for every word the DUT accepted. Results go into tolerated, surprising and missed buckets, and only tolerated is forgiven. Fault tests have a timeout because a missed fault often hangs, they check that the fault actually reached the pins, and expected errors from other checkers are demoted by ID with a report catcher that counts them, so a missing expected error fails the test."

    Follow-ups to expect: When is a told flag acceptable? (faults no monitor can see, such as backdoor corruption) and What does a missed fault look like on this DMA? (a hang until the timeout, not a wrong status).

    Check yourself The truncating override is accidentally registered after the env is built. With dma_err_scoreboard in place, what does the run report?

    All eight beats are driven, the input monitor sees no early TLAST, so the scoreboard predicts DONE, and the DMA reports DONE: the scoreboard is satisfied. The test's own ERR check fails, and so would a check that the cut was seen on the pins. Predicting from observation never blames the DUT for a fault it never received; catching a broken injection is the job of the "fault seen on the pins" check.

    Check yourself A job programs LEN = 4 and the packet's TLAST is on beat 4 (the LEN-th beat). What does dma_err_scoreboard predict, and is it right?

    DONE. When the TLAST beat arrives, beats_in is 4, which is not less than len_reg, so err_expected stays 0. That matches the DUT, where last_word takes priority over s_tlast and the job ends with DONE.

    Check yourself Why does the scoreboard check STATUS only on a read with BUSY = 0, and only once per job?

    A read while BUSY is set reflects a job still running, so DONE and ERR are legitimately both clear; checking then would report false errors. Checking only the first read after the job ends ties the check to the job's outcome, and job_open stops later reads (after the W1C clear) from being compared against the old prediction.

    Next: fault injection completes the core agent topics. Agent patterns covers the shapes senior interviews probe next: reactive slaves, reset on the fly, interrupt agents and pipelined drivers.

    Related topics

    • Scoreboard architecture

    • run_test and timeouts

    • Interrupt agents

    • Agent patterns

    Previous

    110 / 280

    Next: Agent Patterns: Slaves, Reset, Interrupts and Pipelines

    Up next

    Up nextAgent Patterns: Slaves, Reset, Interrupts and Pipelines

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Sequence-Directed Faults
    • Driver Injection Hooks