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
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
    • Simulation for Sign-off
    • Safety, Acceleration and Models
      • Functional Safety: Fault Injection and Diagnostic Coverage
      • Emulation and FPGA Prototyping: When the Simulator Is Too Slow
      • Multi-Language Benches: SystemC, C/C++ and Python
      • Mixed-Signal Verification with UVM
    • Beyond Simulation
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Safety, Acceleration and Models
  1. Topics
  2. UVM Verification
  3. Verification in Industry Practice
  4. Safety, Acceleration and Models
  5. Functional Safety: Fault Injection and Diagnostic Coverage

Part 12 · Industry Practice · Intermediate

Functional Safety: Fault Injection and Diagnostic Coverage

ISO 26262 for DV engineers (ASIL, safety mechanisms, SPFM and LFM), fault models, and a real fault campaign on the DMA bench: 5 of 8 faults caught, why three escaped, and the checks that catch all 8.

On this page

    The DMA is going into a car. The safety manager sends one question: if a flip-flop inside this block breaks in the field, will anything notice? Your regression is green, coverage is close to closed, and you realise you have never measured that. Every test you wrote assumes the hardware works. So you break it on purpose, one fault at a time, and count how often the bench complains. On the course bench the answer is 5 out of 8. What do the three misses mean, and what does a safety standard want from you?

    The idea: assume the silicon breaks, then prove it is caught

    Functional verification asks does the design do what the spec says? Functional safety asks a different question: when the hardware fails at random in the field, does the system stay safe? Random hardware failures are real: a transistor wears out and a node sticks at 0 or 1 (a permanent fault), or a particle strike flips one flip-flop or memory bit for a moment (a transient fault, often called a single-event upset, SEU). No amount of functional testing removes them, because the RTL was never wrong.

    Think of a smoke detector. You do not test it by checking that the kitchen is not on fire; you test it by holding smoke under it. A fault campaign does the same for a chip: inject a fault, run a workload, and record whether something noticed. The "something" is either the testbench (when you want to measure how strong the bench is) or an on-chip safety mechanism (when you want to prove the chip protects itself).

    ISO 26262 in a DV engineer's terms

    ISO 26262 is the automotive functional safety standard; part 5 covers hardware. You do not need to recite it, but interviewers for automotive work expect the vocabulary:

    Term

    What it means for you

    Safety goal

    A top-level requirement from the hazard analysis, such as "the DMA must not deliver corrupted brake data without signalling an error".

    ASIL

    Automotive Safety Integrity Level, A (lowest) to D (highest), set by severity, exposure and controllability of the hazard. QM means no safety requirement beyond normal quality.

    Safety mechanism

    Hardware that detects or corrects faults: parity and ECC on memories and registers, dual-core lockstep (a second core runs the same code and a comparator flags any difference), watchdogs, CRC on buses, duplicated state machines.

    Single-point fault

    A fault in an element with no safety mechanism, which directly violates a safety goal.

    Residual fault

    The part of a covered element's faults that its safety mechanism does not catch.

    Latent fault

    A multiple-point fault that is neither detected nor noticed: typically a broken safety mechanism, harmless until a second fault arrives.

    Diagnostic coverage (DC)

    The fraction of an element's failure rate that its safety mechanism detects or controls.

    Two hardware architectural metrics turn this into numbers. The single-point fault metric (SPFM) is one minus the failure rate of single-point and residual faults, divided by the total failure rate of the safety-related hardware: how well the design is covered against faults that hurt at once. The latent fault metric (LFM) is one minus the latent-fault rate divided by the failure rate that remains after single-point and residual faults are removed: how well the safety mechanisms themselves are checked. ISO 26262-5 gives target values per ASIL:

    ASIL B

    ASIL C

    ASIL D

    SPFM

    ≥ 90%

    ≥ 97%

    ≥ 99%

    LFM

    ≥ 60%

    ≥ 80%

    ≥ 90%

    The failure rates come from the safety analysis (an FMEDA: failure modes, effects and diagnostic analysis), usually done by a safety engineer. The DV engineer's job is the evidence: the fault campaign that shows a claimed DC is real, and the functional tests that show each safety mechanism works (ECC corrects a single-bit error and flags a double, the lockstep comparator fires on a mismatch, the watchdog bites).

    The picture: one fault, one run, one verdict

    Flowchart: a fault list feeds single-fault injection and a workload run; the run is classified as detected if an alarm fired, dangerous if outputs differ without an alarm, or not observed otherwise; all three feed the safety metrics.
    A fault campaign compares each faulty run against a fault-free golden run at two kinds of point: detection points (safety alarms) and observation points (functional outputs). The classification feeds the metrics.

    A fault only counts if the workload activates it (the stuck node would otherwise have held the other value) and propagates it to a point someone checks. Those are the same two words you know from manufacturing test, and they explain most misses below.

    Build it: a real campaign on the DMA

    The DMA has no safety mechanism, so on this bench every detection comes from the testbench itself. That makes the campaign a measure of bench strength: a bench that cannot see a stuck output bit cannot help a safety case either. dma_fault_tb.sv includes the normal bench and replaces its top with one that forces a single stuck-at fault inside dma_dut after reset, chosen by +FAULT=<n>:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // snippet: the fault list in dma_fault_tb.sv (its top module)
    int fault_id;
    initial begin
      if ($value$plusargs("FAULT=%d", fault_id) && fault_id != 0) begin
        @(posedge rst_n);
        case (fault_id)
          1: force dut.key[0]      = 1'b0;   // KEY bit 0 stuck at 0
          2: force dut.m_tdata[31] = 1'b1;   // output data bit 31 stuck at 1
          3: force dut.count[3]    = 1'b0;   // COUNT bit 3 stuck at 0
          4: force dut.irq_en      = 1'b0;   // interrupt enable stuck at 0
          5: force dut.err         = 1'b0;   // ERR flag stuck at 0
          6: force dut.done        = 1'b1;   // DONE flag stuck at 1
          7: force dut.m_tlast     = 1'b0;   // output TLAST stuck at 0
          8: force dut.len[4]      = 1'b0;   // LEN bit 4 stuck at 0
          default: $fatal(1, "unknown fault %0d", fault_id);
        endcase
        $display("FAULT %0d injected", fault_id);
      end
    end

    The top also sets a 50 us UVM timeout, because a fault that hangs the DMA must still end the run. The script builds once, then runs every fault against each test and calls a fault detected if the run ends with any UVM_ERROR or UVM_FATAL:

    terminal
    1
    2
    3
    4
    5
    6
    7
    8
    # excerpt: scripts/tools/uvm-bench/fault-campaign.sh
    for f in $faults; do
      for t in "${tests[@]}"; do
        log=$("$out/obj/Vtop" +UVM_TESTNAME="$t" +FAULT="$f" 2>&1 || true)
        e=$(echo "$log" | sed -n 's/^UVM_ERROR : *//p'); x=$(echo "$log" | sed -n 's/^UVM_FATAL : *//p')
        if [ "${e:-0}" -gt 0 ] || [ "${x:-0}" -gt 0 ]; then r="detected(E${e:-0},F${x:-0})"; else r="MISSED"; fi
      done
    done

    One build, many runs: the same pattern as a regression. A commercial fault simulator does this far more efficiently (many faults per simulation, on a gate-level netlist), but the verdict logic is the same.

    Watch it run: 5 of 8

    Diagram
    $ UVM_HOME=... scripts/tools/uvm-bench/fault-campaign.sh
    fault  dma_rand_test        dma_truncate_test
    1      detected(E11,F0)     MISSED
    2      detected(E8,F0)      detected(E2,F0)
    3      detected(E1,F0)      MISSED
    4      detected(E0,F1)      detected(E0,F1)
    5      MISSED               detected(E0,F1)
    6      MISSED               MISSED
    7      MISSED               MISSED
    8      MISSED               MISSED
    faults detected by at least one test: 5 of 8

    Read the detected rows first, because how a fault was caught matters as much as whether:

    • Fault 2 (output bit 31 stuck at 1) is caught by the scoreboard: every word whose expected bit 31 is 0 mismatches.

    • Fault 3 (COUNT bit 3 stuck at 0) is caught indirectly. On the 10-word job the counter wraps from 7 back to 0, so the DMA never sees its last word arrive; the input packet's TLAST then looks like an early end, the DMA sets ERR, and the test's STATUS check fails: [STATUS] job 0: STATUS=00000004, expected DONE only. Nobody checks COUNT itself.

    • Faults 4 and 5 are caught only as hangs (F1 is the PH_TIMEOUT fatal): no interrupt, so wait (irq) never returns. A hang is a detection, but a slow and uninformative one.

    The per-fault logs of the misses end with a clean scoreboard:

    Diagram
    --- FAULT 6 rand
    UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
    FAULT 6 injected
    UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1
    UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
    UVM_ERROR :    0
    --- FAULT 7 rand        (same jobs, same result)
    UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
    UVM_ERROR :    0
    --- FAULT 1 truncate
    FAULT 1 injected
    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(761) @ 385000: uvm_test_top [TRUNC] DMA reported ERR for the short packet, as expected
    UVM_INFO dma_tb.sv(411) @ 485000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
    UVM_ERROR :    0
    UVM_FATAL :    0

    What the misses mean, one by one

    Fault

    Why it escaped

    Kind of gap

    Fix

    6: DONE stuck at 1

    With IRQ_EN set, the interrupt is high from the CTRL write onward. run_job streams its whole packet before it waits for the interrupt, so an interrupt that comes too early is invisible to it.

    Missing checker (timing)

    Check that the interrupt rises only after the job's last word has left

    7: TLAST stuck at 0

    The output monitor records last, but dma_scoreboard compares data only.

    Missing checker (field)

    Check TLAST on every output word

    8: LEN bit 4 stuck at 0

    Legal lengths are 1–16, and only 16 sets bit 4. The seeds drew 10, 1, 3 and 8.

    Missing stimulus

    Directed LEN=16 jobs (and LEN above 16 once the spec defines it)

    1 in truncate: KEY bit 0

    That test's random KEY happened to have bit 0 = 0, so the stuck-at-0 fault was never activated. dma_rand_test caught it with 11 errors.

    Stimulus-dependent activation

    Already covered by another test; keep both tests in the safety list

    Fault 8 should feel familiar: dma_check_test's coverage report already lists len=16 as a plan hole (see Closing Coverage: The Holes Random Never Hits). A fault campaign and a coverage model looking at the same bench found the same gap from two directions. That agreement is worth saying in an interview: coverage tells you what you did not stimulate; fault injection tells you what you did not check, which coverage cannot.

    Fixing the bench (scratch run)

    A scratch copy of the fault bench adds a job checker and a directed test, and leaves the bench files unchanged. The checker follows the APB writes to learn LEN at each GO, expects TLAST on the job's last word (or on an input word that carried an early TLAST), and two clocks after each interrupt rises checks that the job's last word has already left. The env picks it up through a factory type override of dma_env, enabled by +JOB_CHECK:

    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
    // snippet: scratch dma_fault_fix_tb.sv, the checks added to the bench
    virtual function void write_in(axis_item t);
      n_in++;
      exp_last.push_back(t.last || n_in == job_len);    // TLAST expected here?
    endfunction
    
    virtual function void write_out(axis_item t);
      bit exp;
      n_out++;
      if (exp_last.size() == 0) return;                 // the scoreboard reports unexpected words
      exp = exp_last.pop_front();
      if (t.last !== exp)
        `uvm_error("TLAST", $sformatf("output word %0d of %0d: TLAST=%0b, expected %0b", n_out, job_len, t.last, exp))
      if (exp) job_ended = 1;
    endfunction
    
    task run_phase(uvm_phase phase);
      forever begin
        @(posedge irq_vif.irq);
        repeat (2) @(posedge irq_vif.clk);               // the last word leaves one clock after DONE
        if (!job_ended)
          `uvm_error("DONE_EARLY", $sformatf("interrupt raised with %0d of %0d words out", n_out, job_len))
      end
    endtask
    
    // and a directed test: dma_rand_test with one job of LEN=16 passed through config_db
    class dma_len16_test extends dma_rand_test;
      `uvm_component_utils(dma_len16_test)
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      function void build_phase(uvm_phase phase);
        dma_job_cfg job = dma_job_cfg::type_id::create("job");
        if (!job.randomize() with { len == 16; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
        uvm_config_db#(dma_job_cfg)::set(this, "", "job", job);
        n_jobs = 1;
        super.build_phase(phase);
      endfunction
    endclass

    Without faults, all three tests still pass with the checker on (14, 4 and 16 words matched, 0 errors). The campaign, rerun with the checker on and the new test added:

    Diagram
    scratch run: dma_fault_fix_tb.sv with +JOB_CHECK
    fault  dma_rand_test        dma_truncate_test    dma_len16_test
    1      detected(E11,F0)     MISSED               detected(E17,F0)
    2      detected(E8,F0)      detected(E2,F0)      detected(E10,F0)
    3      detected(E1,F0)      MISSED               detected(E1,F0)
    4      detected(E0,F1)      detected(E0,F1)      detected(E0,F1)
    5      MISSED               detected(E0,F1)      MISSED
    6      detected(E1,F0)      detected(E1,F0)      detected(E1,F0)
    7      detected(E3,F0)      detected(E1,F0)      detected(E1,F0)
    8      MISSED               MISSED               detected(E1,F1)
    faults detected by at least one test: 8 of 8
    
    UVM_ERROR dma_fault_fix_tb.sv(72) @ 175000: uvm_test_top.env.jc [DONE_EARLY] interrupt raised with 0 of 10 words out
    UVM_ERROR dma_fault_fix_tb.sv(63) @ 575000: uvm_test_top.env.jc [TLAST] output word 10 of 10: TLAST=0, expected 1
    UVM_ERROR dma_fault_fix_tb.sv(72) @ 175000: uvm_test_top.env.jc [DONE_EARLY] interrupt raised with 0 of 16 words out
    • Fault 6 now fails at 175 ns, the moment GO is written: the first clear message instead of silence.

    • Fault 8 is caught twice in dma_len16_test: LEN=16 becomes 0, the DMA treats it as a zero-length job and finishes at once (DONE_EARLY), then the packet can never be accepted and the run ends in PH_TIMEOUT. The checker turns a 50 us hang into a 175 ns error with a cause in it.

    • Faults 1 and 5 are still missed by some tests. Detection is per test, so a safety regression keeps the tests that together catch everything, and the campaign tells you which ones those are.

    How a project does this for real

    A production campaign differs from the course script in scale and in what counts as detection. The fault list comes from the gate-level netlist (stuck-at 0 and 1 on every node, plus transient flips on every flip-flop and memory), which runs to millions of faults. Tools collapse equivalent faults, use formal analysis to prove some safe or untestable, and sample the rest. Sampling is statistics, not a shortcut: the sample size follows from the margin and confidence you will claim. A common reference (Leveugle et al., 2009) gives n = N / (1 + e²(N−1) / (t² p(1−p))); with the worst case p = 0.5, a 1% margin (e = 0.01) and 99% confidence (t ≈ 2.58), a large fault list needs about 16,600 injected faults, whatever N is. The safety case states the margin and confidence, and the diagnostic coverage is reported with that interval, not as a bare percentage. A commercial fault simulator runs many faults per simulation against a golden run and classifies each at the declared observation and detection points; the results feed the FMEDA. Those tools cannot be run here, but every major vendor's flow follows these steps.

    A transient fault is modelled differently from a stuck-at. For a flip-flop, force then release one clock later: after a release, a variable keeps the forced value until the design next writes it, which is exactly a bit flip that lasts until the register is reloaded.

    design.sv
    1
    2
    3
    4
    5
    // snippet: a single-event upset on a register bit, held until the design next writes it
    @(posedge clk);
    force dut.key[5] = ~dut.key[5];
    @(posedge clk);
    release dut.key[5];

    What breaks: a campaign that measured the wrong thing

    A team reports 99% detection on a block with ECC. The safety assessor asks how detection was decided: "any UVM_ERROR". The scoreboard caught most faults by comparing data, so the number measures the testbench, not the ECC, and in the field there is no scoreboard. Rerun with detection defined as "the ECC alarm fired before the corrupted data left the block", the number drops sharply: faults in the encoder's output path corrupted data with no alarm. The fix was a design change. Decide what counts as detection before you run, and keep the two uses of fault injection, bench strength and diagnostic coverage, apart in every report.

    Interview

    "How would you verify an ECC safety mechanism and support its diagnostic-coverage claim?"

    A weak answer: "I'd write tests that inject single-bit and double-bit errors and check that it corrects and detects them."

    A strong answer: "Two layers. Functionally, I inject errors at the memory interface and check correction of every single-bit position, detection of double-bit errors, the alarm and syndrome registers, and the interrupt path. Then the fault campaign: a fault list on the netlist of the protected logic and of the ECC itself, a workload that activates and propagates the faults, a golden run, and classification at detection points (the ECC alarm) and observation points (the data outputs). Detected, dangerous-undetected and not-observed faults feed the FMEDA for DC and SPFM, and faults in the ECC itself that stay silent are latent and feed the LFM. I check activation too: a fault the workload never toggles is not evidence."

    Follow-ups: Why is a scoreboard error not a detection in a safety campaign? (there is no scoreboard in the field; only an on-chip mechanism counts). What is a latent fault in an ECC block? (a fault in the checker that leaves data correct but would hide the next error).

    Check yourself A fault campaign reports a stuck-at-1 fault on an unused upper bit of a configuration register as "not observed" in every test. Is that fault safe?

    Maybe, but the campaign has not shown it. "Not observed" means the workload never made the fault visible. If the bit is truly unused, the fault is safe and a formal or structural argument can classify it. If some mode uses it and no test enables that mode, the fault was never activated, and the right answer is more stimulus. Classify unobserved faults with a reason, never by default.

    Check yourself After adding the TLAST check, fault 7 is caught by all three tests. Fault 5 (ERR stuck at 0) is still missed by two of them. Should you add an ERR check to dma_rand_test?

    No check can catch it there: dma_rand_test never creates an error condition, so ERR stuck at 0 is never activated in that test. The fault is caught by dma_truncate_test, which does create an error. The fix for a non-activated fault is stimulus, and here another test already supplies it; the safety regression simply keeps that test.

    Check yourself The campaign result for fault 4 was detected(E0,F1), a 50 us timeout. Why is that a weak detection, and what would make it better?

    It only says the run hung, not why, and it costs the full timeout per fault, which matters when a real campaign has millions of faults. A watchdog or a checker with an expectation ("interrupt within N clocks of the last word") would fail early with a message that names the cause, the same way DONE_EARLY replaced the LEN=16 hang.

    Next: a real campaign runs millions of faults; a full-chip test can take weeks in simulation even without faults. Emulation and FPGA Prototyping is what teams do when the simulator is too slow.

    Related topics

    • Up: Safety, Acceleration and Models

    • False-Pass Playbook

    • Closing Coverage: The Holes Random Never Hits

    Previous

    241 / 280

    Next: Emulation and FPGA Prototyping: When the Simulator Is Too Slow

    Up next

    Up nextEmulation and FPGA Prototyping: When the Simulator Is Too Slow

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Multi-Language Benches: SystemC, C/C++ and Python
    • Mixed-Signal Verification with UVM