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.
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.
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.
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:
// snippet
assign sn3 = (fsel == 4'd8) ? fval : rn3; // n3 as its readers see it: forced when fsel picks itfaults: 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/0The 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.
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) |
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.
The coverage number depends on what was divided by what.
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.
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.
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.
`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
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.