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

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
    • Low-Power RTL Hub
    • DFT and Scan Hub
      • Why scan exists
      • The scan chain at RTL abstraction
      • Test-mode integration checklist
      • Controllability and observability in practice
      • Fault Coverage by Hand
    • Physical Awareness Hub
    • Waveform Debug Hub
    • STA and ECO Hub
    • RTL-to-Implementation Closure
    • DFT-Ready RTL
    • Capstone: Resilient Streaming Peripheral
    • Principal Design Review
    • Senior RTL Interview Questions & Model Answers
  • Glossary and Notation
Loading lesson

‹DFT and Scan Hub
  1. Topics
  2. RTL Design
  3. Senior & Interview
  4. DFT and Scan Hub
  5. Fault Coverage by Hand

Part 9 · Senior · Senior

Fault Coverage by Hand

List every stuck-at fault of a nine-gate circuit, simulate each one, and see why coverage stops short of 100%.

On this page

    Why this matters

    A coverage figure is a count of detected faults over a fault list, and both halves can mislead. In a nine-gate circuit all 16 patterns stop at 25 of 26 faults, because one gate is redundant and its fault can never be detected.

    After this lesson you can: list the stuck-at faults of a small circuit, decide whether a pattern detects one, compute fault and test coverage from a simulation, and explain why a redundant fault is untestable.

    Fault coverage is a count you can do by hand

    Two test patterns, all zeros and all ones, detect 12 of the 26 stuck-at faults in the nine-gate circuit below: 46.2%. Two more patterns aimed at the misses reach 88.5%. All 16 possible inputs stop at 96.2%, because one gate in the circuit can never be tested.

    Why scan exists explains why a tester needs access to internal nets. This lesson computes the number that access is for: list the faults, simulate each one, count.

    The nine-gate circuit with every net named: g4 adds the consensus term n3 = b and c, which never changes y

    Read it left to right: the four vertical rails are the inputs, and every wire carries the net name used in the fault list.

    • y uses a to choose between b and c: y = a&b | ~a&c. Gate g4 adds b&c on top.

    • z = (c ^ d) & ~(b & d) is ordinary logic with no redundancy.

    • 13 nets (4 inputs and 9 gate outputs), each stuck at 0 or at 1, give 26 single stuck-at faults.

    What detected means

    A stuck-at fault models a net held at 0 (sa0) or at 1 (sa1) whatever drives it. A pattern detects the fault when the faulty circuit's outputs differ from the good circuit's outputs for that pattern. Two things must happen: the pattern drives the net to the opposite value (activation), and the difference reaches y or z (propagation).

    Take n1 sa0. Activation needs n1 = 1, so a = b = 1. Propagation through the two OR gates needs n2 = 0 and n3 = 0, so c = 0. Pattern abcd = 1101 does both: the good y is 1 and the faulty y is 0.

    Running the fault simulation

    The method is brute force, and it is what a fault simulator does, only faster: make one faulty copy of the circuit per fault, apply the same patterns to it and to the good circuit, and record the first pattern where the outputs differ. A short script wrote 26 faulty netlists, each with one net replaced by a constant, and simulated each one against three pattern lists. In the bench at the end, one line per net does the same job:

    design.v
    // snippet
    assign sn3 = (fsel == 4'd8) ? fval : rn3;   // n3 as its readers see it: forced when fsel picks it
    Diagram
    faults: 26 (13 nets x stuck-at-0 and stuck-at-1), one Icarus run per fault per pattern list
    patterns 0000 1111: 12/26 detected = 46.2%
    patterns 0000 1111 0010 1101: 23/26 detected = 88.5%
    all 16 patterns: 25/26 detected = 96.2%
    missed by 2 patterns: a/0 a/1 b/1 c/0 na/0 na/1 n1/0 n2/0 n3/0 n4/0 n5/0 n6/0 n6/1 z/0
    missed by 4 patterns: b/1 na/1 n3/0
    missed by all 16: n3/0

    The same run, with the patterns applied in the order 0000, 1111, 0010, 1101 and then the other twelve in counting order, gives the first pattern (abcd) that detects each fault:

    Net

    sa0 first detected by

    sa1 first detected by

    a

    1101

    0010

    b

    1111

    0001

    c

    0010

    0000

    d

    1111

    0000

    na

    0010

    1010

    n1

    1101

    0000

    n2

    0010

    0000

    n3

    never

    0000

    n4

    0010

    0000

    y

    1111

    0000

    n5

    0010

    0000

    n6

    0010

    1101

    z

    0010

    0000

    Read down a column to see which pattern earns its place:

    • 0000 sets y = 0 and z = 0, so it shows sa1 faults that pull y or z up: nine of them. 1111 adds only three. All-same patterns leave more than half the list untested.

    • 0010 activates the ~a&c path and z, and adds eight faults. 1101 adds three: a sa0, n1 sa0 and n6 sa1.

    • After four patterns three faults remain. The full set finds b sa1 (0001) and na sa1 (1010); n3 sa0 says never.

    Why n3 stuck-at-0 can never be detected

    To detect n3 sa0, a pattern must set n3 = 1 (b = c = 1) and let it reach y, which needs the other OR input n4 = 0. But with b = c = 1, n4 = a&b | ~a&c is 1 for either value of a. The fault is activated but always masked, so no pattern detects it. The gate is redundant: removing it does not change y.

    Synthesis agrees. Run through Yosys, the y cone becomes a single mux, assign y = a ? b : c;, and the b&c gate is gone. Redundant logic survives in real designs when it is kept on purpose, such as a consensus term that removes a static hazard, or when it hides in a cone the optimiser cannot see through.

    So one run gives two honest numbers:

    Metric

    Formula

    This circuit

    Fault coverage

    detected / all faults

    25 / 26 = 96.2%

    Test coverage

    detected / (all faults - faults proven untestable)

    25 / 25 = 100%

    Redundancy is one of four common reasons a fault stays undetected:

    Cause

    Example

    What helps

    Redundant logic

    n3 sa0 here

    Remove the logic, or record the fault as untestable after review

    Constant or tied net

    A mode pin tied to 0 cannot show its sa0

    Make the mode controllable in test

    Unobservable net

    A net that only feeds a flop outside every scan chain

    Scan the flop or add an observation point

    X source

    An uninitialised RAM output reaching a capture flop

    Bypass or initialise the source, or mask that response bit (a mask is lost observation)

    Sequential circuits: scan makes the same method work

    In a sequential block most nets sit between flops, so a tester can neither set them nor see them. Scan turns every flop into a pseudo-input during shift and a pseudo-output at capture, so the logic between two ranks of flops becomes a combinational circuit like the one above. Shift a pattern in, capture once, shift the response out.

    The scan chain at RTL abstraction builds that round, and its stepper lets you inject a stuck-at fault and watch whether the capture shows it. Switching the stepper's logic to and-pair hides the same fault for that pattern: the pattern dependence in the table above, on a chain.

    Warning

    Limits: production test-pattern generation goes further than this count.

    • Fault collapsing merges equivalent faults (an AND input sa0 and its output sa0), so real lists are shorter than nets x 2; this lesson also leaves out fanout-branch faults.

    • Transition faults model slow-to-rise and slow-to-fall nets and need two patterns, launch and capture, at speed.

    • Compression feeds many short chains from an on-chip decompressor and folds the responses in a compactor, cutting test time and data.

    • X-masking blocks response bits that could be X before compaction; every masked bit is lost observation.

    Common mistakes

    The coverage number depends on what was divided by what.

    Common pitfall

    Reporting 100% after the fault list was trimmed. Test coverage divides by the faults left after untestable ones are removed, so dropping a fault raises the number without testing anything. Here n3 sa0 was proven redundant by hand. A fault marked untestable because the tool gave up, or because a mode pin was tied, needs the same review, and the report should show fault coverage and test coverage side by side.

    Check yourself A fifth pattern is added after 0000, 1111, 0010 and 1101. What is the best fault coverage it can reach, and which pattern gets there?

    25 of 26 (96.2%). Pattern 1010 detects both remaining testable faults, b sa1 and na sa1. Nothing detects n3 sa0, so no fifth pattern does better.

    Check yourself All 16 patterns have been applied and fault coverage is 96.2%. Can more patterns raise it?

    No. There are only 16 input combinations and the remaining fault is redundant. The number changes only if the gate is removed or the fault is reviewed and excluded, which changes test coverage, not fault coverage.

    Testbench

    The circuit with a fault-select input, the good copy beside it, and a loop over all 26 faults and 16 patterns. It checks the coverage after 2, 4 and 16 patterns against the numbers above and checks that n3 sa0 is never detected.

    circ_fi.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
    `timescale 1ns/1ps
    // The nine-gate circuit with a fault-injection port. fsel picks one of the 13 nets
    // (1 = a ... 13 = z) and holds it at fval; fsel = 0 is the fault-free circuit.
    module circ_fi (
      input  wire       a, b, c, d,
      input  wire [3:0] fsel,
      input  wire       fval,
      output wire       y, z
    );
      wire ra, rb, rc, rd, rna, rn1, rn2, rn3, rn4, ry, rn5, rn6, rz;   // driver values
      wire sa, sb, sc, sd, sna, sn1, sn2, sn3, sn4, sy, sn5, sn6, sz;   // values the fanout sees
      assign {ra, rb, rc, rd} = {a, b, c, d};
      not  g1 (rna, sa);
      and  g2 (rn1, sa, sb);
      and  g3 (rn2, sna, sc);
      and  g4 (rn3, sb, sc);        // consensus term: redundant
      or   g5 (rn4, sn1, sn2);
      or   g6 (ry,  sn4, sn3);
      xor  g7 (rn5, sc, sd);
      nand g8 (rn6, sb, sd);
      and  g9 (rz,  sn5, sn6);
      assign sa  = (fsel == 4'd1)  ? fval : ra;
      assign sb  = (fsel == 4'd2)  ? fval : rb;
      assign sc  = (fsel == 4'd3)  ? fval : rc;
      assign sd  = (fsel == 4'd4)  ? fval : rd;
      assign sna = (fsel == 4'd5)  ? fval : rna;
      assign sn1 = (fsel == 4'd6)  ? fval : rn1;
      assign sn2 = (fsel == 4'd7)  ? fval : rn2;
      assign sn3 = (fsel == 4'd8)  ? fval : rn3;
      assign sn4 = (fsel == 4'd9)  ? fval : rn4;
      assign sy  = (fsel == 4'd10) ? fval : ry;
      assign sn5 = (fsel == 4'd11) ? fval : rn5;
      assign sn6 = (fsel == 4'd12) ? fval : rn6;
      assign sz  = (fsel == 4'd13) ? fval : rz;
      assign {y, z} = {sy, sz};
    endmodule
    

    Remember

    • A pattern detects a fault only when it activates the net to the opposite value and the difference propagates to an observed output.

    • Fault coverage divides by every fault; test coverage divides by the faults left after untestable ones are removed. Report both.

    • Scan turns flops into pseudo-inputs and pseudo-outputs, so the logic between flops can be tested like a combinational circuit.

    Rule of thumb: never accept a coverage number without the fault list it was divided by and a reviewed reason for every excluded fault.

    How this was checked

    Tools: Icarus Verilog 13.0, Verilator 5.048 and Yosys 0.69+post (git sha1 143eb14f9), run on 2026-10-17. Fault simulation: a Python script (kept out of the course) wrote one faulty netlist per fault, each with one of 13 nets replaced by a constant, and ran iverilog -g2005 tb.v circ.v faulty.v; vvp -n for each fault against three pattern lists: python3 faultsim.py 0000,1111,0010,1101.

    Result: 26 faults; 12/26 (46.2%) after 0000 1111, 23/26 (88.5%) after adding 0010 1101, 25/26 (96.2%) after all 16; only n3 sa0 never detected. The first-detecting-pattern table and the missed lists are copied from the same run. A second run with 1010 as a fifth pattern showed b sa1 and na sa1 first detected by 1010. The lesson bench, which uses a fault-select input instead of separate netlists, gives the same 12, 23 and 25 in Icarus and Verilator.

    Synthesis remark: yosys -p "read_verilog circ.v; synth -top circ" maps the circuit to 4 cells, with y written as assign y = a ? b : c, so the b&c gate does not survive.

    Scope and limits: single stuck-at faults on stems only (inputs and gate outputs), no fanout-branch faults, no fault collapsing, exhaustive patterns on a 4-input circuit. Production ATPG uses collapsed fault lists, transition faults, compression and X-masking.

    Keep going

    • Code lab: Scan chain stub

    • Interview: run a mock interview on this topic

    Related topics

    • Why scan exists

    • The scan chain at RTL abstraction

    • Scan-ready coding

    Previous

    282 / 350

    Next: Physical Awareness Hub

    Up next

    Up nextPhysical Awareness Hub

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why scan exists
    • The scan chain at RTL abstraction
    • Test-mode integration checklist