Thousands of flops in one chain means thousands of shift cycles per pattern; split across chains the same patterns load in a fraction of the time, but chains rarely cross clock domains freely, and they thread through blocks the integrator did not write. NanoSoC has three clock domains, so the chain plan starts from them. Wrapper cells make each block testable alone by capturing its inputs and controlling its outputs; scan clock and reset muxing make the chains shift and capture safely. The arithmetic of shift time belongs to the DFT course; here the integrator's part is the chain plan per domain, the wrappers and the muxing.
Chains are balanced by flop count within each clock domain. A chain that crosses domains needs a lockup latch at the boundary, so the chain count is at least the number of capture-clock domains, three for NanoSoC. Shift time for a pattern is the longest chain divided by the shift-clock rate, plus capture and unload overhead, so a domain with few flops cannot be balanced against a large one without a crossing. Wrapper cells sit on every functional input and output of each tested block: in inward mode they isolate the block from its neighbours so its own patterns run clean, in outward mode they drive the interconnect so wiring between blocks is tested. Scan clocks mux between the functional clock and the test clock at each domain, switching only when both sides are stopped and the controller sequences the change.
Reset follows its own rule. During shift and capture every asynchronous reset is held inactive by a test-controlled source. The reset mux sits ahead of the domain's reset synchroniser, so release stays synchronous to the clock in both modes, and no reset is generated by gating a scan clock.
// Test reset mux: functional or test reset, feeding the domain's reset synchroniser.
// Complete and self-contained. Switching is sequenced by the test
// controller while clocks are stopped; the mux itself only selects, and
// the synchronised release happens after it, in the domain's own clock.
module test_reset_mux (
input logic func_rst_n,
input logic test_rst_n,
input logic test_mode,
output logic rst_n_to_sync // feeds the reset synchroniser, not the flops
);
assign rst_n_to_sync = test_mode ? test_rst_n : func_rst_n;
endmodule# Chain plan per clock domain (illustrative flop counts, 9300 in total).
# Longest chain sets the shift time: cycles per pattern = longest chain.
DOMAINS = { # domain: (flops, chains)
"clk_cpu": (5000, 2), # cpu core and cache, CPU side of the async bridge
"clk_sys": (2400, 1), # xbar, dma, sbr, bridges
"clk_per": (1900, 1), # peripherals, reset and power controller
}
TCK_MHZ = 20.0 # illustrative tester shift clock (not a NanoSoC clock)
total = sum(f for f, _c in DOMAINS.values())
n_chains = sum(c for _f, c in DOMAINS.values())
assert total == 9300 and n_chains == 4
assert n_chains >= len(DOMAINS) # at least one chain per capture domain
lengths = [f / c for f, c in DOMAINS.values()]
longest = max(lengths)
assert longest == 2500.0
ideal = total / n_chains # 2325 if chains could cross domains freely
assert ideal == 2325.0 and longest / ideal > 1.07 # the domain constraint costs 7.5 percent
shift_us = longest / TCK_MHZ
assert abs(shift_us - 125.0) < 0.001
patterns, capture_overhead = 500, 0.05 # illustrative pattern count
tester_s = patterns * shift_us / 1e6 * (1 + capture_overhead)
assert abs(tester_s - 0.0656) < 0.001, tester_s
print("PASS: 4 chains, longest 2500 (ideal 2325); 125 us per pattern; about 66 ms")The chain plan per domain with the longest chain quoted, and the cost of the domain constraint against a free balance.
Wrapper cells on every block boundary with inward and outward modes both verified, not just the block in isolation.
Scan clock and reset muxing reviewed with the switching sequence: stopped clocks, controller-owned, every asynchronous reset held inactive during shift and capture, synchronised release preserved.
Run one pattern through the full chain in simulation and compare the unload bit by bit against the expected: a single stuck bit locates the broken cell, and the chain index plus the bit position names the flop. If capture fails while shift passes, suspect the clock muxing or a reset that never released in the capture domain before blaming the logic. Wrapper-cell modes each get their own test: inward first, then outward, because a cell that cannot isolate produces failures that look exactly like logic bugs.
Integration: inward passing proves the block, so the failing outward path is the wiring or the neighbour's wrapper cell on that path. That isolation of blame is exactly what wrapper cells are for.