Firmware reports that a control register changes by itself. It is written once at boot with 5b, and sometimes reads d8 later. Nobody writes it again; the bus monitor agrees. The firmware team notices it happens after the code that blinks a status LED. The RTL of the register is four lines and looks right. What would make a register change with no write?
The usual hunt starts at the register's write enable and the bus decoder, because those are the only paths into the register in the RTL. They are clean, so the next suspects are glitches, timing and "cosmic rays". All of these miss the paths that are not in the functional RTL: the scan mux on every flop, the test-mode reset and clock muxes, and the pads that are shared between test pins and functional pins.
Test logic gets less review than the function because it is "only for the tester". It is still logic, and in functional mode its rule is simple: every test control is constant and inactive. A bug in this class breaks that rule.
Sub-class | What you see | Where it shows |
|---|---|---|
Scan enable leaks into function | a register holds its old value shifted by N bits (N cycles of shift), new bits from the neighbour flop or scan-in; changes track unrelated pin activity | gate-level simulation of the scan netlist; RTL only where the shift path is modelled; silicon |
Test mode reaches the function | clock gates forced open, memories in bypass, resets or clocks from the test path; often power or behaviour that differs board to board | any simulation that drives the test-mode source; a strap or pad with the wrong default |
Wrong test reset or clock mux | some flops ignore a functional reset, or run on the test clock; a subset of state survives a reset | RTL, when the mux is hand-written glue; otherwise gate level |
Here is the first row as a waveform: functional mode throughout, and software drives a GPIO that shares its pad with scan enable. cfg_good has the correct glue; cfg_bug takes scan enable straight from the pad.
Read the data as a bit pattern, not as a number.
Slot 1: both registers hold 5b, written by software. test_mode is 0 in every slot.
Slots 3 to 5: the GPIO is high, and so is the leaked se_bug. Each cycle cfg_bug moves one bit left: 5b, b6, 6c, d8. A 0 enters from scan-in.
Slot 6: the GPIO goes low and cfg_bug stops changing. It changes only while the pin is high, which is the clue that points away from the bus.
On the debug loop this is Localise followed quickly by Hypothesise: the shape of the wrong value names the sub-class.
Print every test control in the failing functional run. test_mode, every scan enable, test clock and reset selects, memory bypass. In functional mode each must be constant and inactive; any toggle, any 1, or an X is the bug or leads to it.
Read the wrong value as bits. Old value shifted by N bits means a scan shift. A value that survives a functional reset means the reset mux. Behaviour at the wrong rate means a clock mux. A forced-open clock gate shows as activity with no enable.
Trace each test control to its source. A pad, a strap or a test controller register. Who drives it in functional mode, what is its reset value, and is it qualified by test_mode before it fans out?
The block below is an 8-bit configuration register with its test glue. The scan-enable pad doubles as a GPIO in functional mode, and in test mode the flops take their reset from the tester's reset pin. BUG = 1 uses the pad as scan enable without the test_mode qualifier; BUG = 2 has the reset mux's inputs swapped. The bench runs all three in functional mode, blinks the GPIO, applies a functional reset, then checks that the correct glue still shifts in test mode. A small always-on check states the functional-mode rule.
// dft_blk: an 8-bit configuration register as it behaves after scan insertion, with its test glue.
// The scan mux (se ? shift : function) is what insertion adds to every flop; it is modelled here so
// the bug shows in RTL simulation. In a real flow it shows in a simulation of the scan netlist.
// Test glue: the scan-enable pad is shared with a GPIO in functional mode, and in test mode the
// flops take their reset from the tester's reset pin.
// BUG = 0: correct glue.
// BUG = 1: scan enable is taken from the shared pad without the test_mode qualifier.
// BUG = 2: the reset mux's inputs are swapped.
`timescale 1ns/1ps
module dft_blk #(parameter int BUG = 0) (
input logic clk, rst_n, test_mode, se_pad, si, tst_rst_n, wr,
input logic [7:0] wdata,
output logic [7:0] cfg_q,
output logic so
);
logic se, rst_int_n;
assign se = (BUG == 1) ? se_pad : (test_mode && se_pad);
assign rst_int_n = (BUG == 2) ? (test_mode ? rst_n : tst_rst_n)
: (test_mode ? tst_rst_n : rst_n);
always_ff @(posedge clk or negedge rst_int_n)
if (!rst_int_n) cfg_q <= 8'h00;
else if (se) cfg_q <= {cfg_q[6:0], si}; // scan shift: inserted by DFT
else if (wr) cfg_q <= wdata; // function
assign so = cfg_q[7];
endmodule
// dft_mode_check: the functional-mode rule as an always-on check. While test_mode is low, scan
// enable must be low and the flops' reset must be the functional reset.
module dft_mode_check (
input logic clk, test_mode, se, rst_n, rst_int_n
);
int cycle = 0;
string first = "";
always @(posedge clk) begin // the bench changes pins on the falling edge
if (!test_mode && first == "" && (se !== 1'b0 || rst_int_n !== rst_n))
first = $sformatf("cycle %0d: se=%b rst_n=%b rst_int_n=%b with test_mode=0", cycle, se, rst_n, rst_int_n);
cycle++;
end
endmodule
$ iverilog -g2012 -o dft.vvp dft_tb.sv && vvp -n dft.vvp
BUG=0 (correct glue): clean
BUG=1 (scan enable from pad): after 3 GPIO-high cycles cfg_q=d8, expected 5b
BUG=2 (reset mux swapped): after a functional reset cfg_q=5b, expected 00
mode check, BUG=0: clean
mode check, BUG=1: cycle 5: se=1 rst_n=1 rst_int_n=1 with test_mode=0
mode check, BUG=2: cycle 0: se=0 rst_n=0 rst_int_n=1 with test_mode=0
test mode on the correct glue: shifted in a1, chain holds a1
PASS: both test-glue bugs fail in functional mode; the correct glue passes and still shifts
...Verilator 5.048 prints the same eight lines. Read them in pairs.
BUG=1. The data check sees d8 after three GPIO cycles: 5b shifted left by three, exactly the waveform. The mode check fires at cycle 5, the first edge with the pin high, with se=1 in functional mode. That line names the cause; the data line only names the victim.
BUG=2. The data check needs a mid-test reset to notice: the register keeps 5b. The mode check fires at cycle 0, during the power-on reset: rst_n=0 but the flops see rst_int_n=1. The functional reset never reached them, not even at boot. The bench's data checks missed that, because software wrote the register before reading it.
The test-mode line matters for the fix: whatever you change in the glue, the chain must still shift a1 through in test mode.
Here the scan mux is in the RTL so that the bug can be simulated. In a real design scan insertion adds it to the netlist, so the bug shows only at gate level, in the DFT tool's rule checks or on the tester; Part 6 covers moving between those stages. dbglab has no scan logic, so none of its bugs belongs to this class.
Qualify every test control with `test_mode` once, at its source, and fan out the qualified signal: se = test_mode && se_pad. Never use a shared pad directly.
Select test muxes with the static `test_mode`, never with a dynamic scan enable, and check which input is which against the spec: in functional mode, the functional reset and clock.
Give the test controller and any straps a reset value that means functional mode, so a floating or unprogrammed source cannot start the chip in test mode.
A functional-mode assertion in every functional test: while test_mode is low, scan enables are 0 and test muxes pass the functional input. dft_mode_check is that assertion; it found both bugs at their first cycle, one of them during boot.
Drive shared pads in functional tests. A bench that never toggles the GPIO will never see the leak. Toggle every pad that doubles as a test pin.
DFT rule checks and a scan-netlist simulation before sign-off, and a test-mode test that still passes after any glue change, like the a1 shift above.
The review question: in functional mode, what drives this test pin, and can it ever be 1? See the test-mode integration checklist.
A test reset mux (or a reset bypass) that selects the test reset in functional mode, for the flops behind that mux. Print test_mode, the mux select and the reset each flop actually sees around the reset pulse, as the mode check does with rst_n and rst_int_n. BUG=2 showed the same thing: the flops never saw the functional reset, not even at boot.
No. The board change hides the leak on that board only; the GPIO function is lost, and any board or package that drives the pad still shifts the chain. The fix is in the chip: qualify scan enable with test_mode at its source, check that the test-mode shift still works, and keep the functional-mode check in the regression.
dft_tb.sv as shown, 2026-10-18: iverilog -g2012 -o dft.vvp dft_tb.sv && vvp -n dft.vvp (Icarus Verilog 13.0) and verilator --binary --timing --assert -Wno-fatal --top-module dft_tb -Mdir vl dft_tb.sv && ./vl/Vdft_tb (Verilator 5.048) print the same eight lines shown and PASS. The waveform is generated from wave-src/dft-mode-bugs/se-leak.sv with COURSE=dbg node scripts/tools/gen-waves.mjs bug-classes:dft-mode-bugs.
Limits: the scan mux is modelled in RTL; real scan insertion adds it to the netlist, so in a real flow this class shows in gate-level simulation of the scan-inserted netlist, in DFT rule checks, or on the tester. No DFT tool, ATPG or netlist was run. dbglab has no scan logic, so none of its five bugs is in this class.