The structured hang triage in The Chip Hangs: What Do You Do? starts with debug access: halt the CPU, read the fabric status, find the outstanding transaction. If the access port sits behind a gated clock, shares its reset with the logic being debugged, or chains in an order nobody recorded, the first hang is also the first discovery that debug was never integrated. NanoSoC makes the point sharply, because PD_SW, which holds the CPU and the fabric, can be switched off while the always-on peripherals keep running. The path has to keep working across that switch.
The external pins enter a serial chain of test access ports, one per debug domain. The extension has three: tap_aon in the always-on domain (reset and power controller, peripherals), and tap_cpu and tap_sys inside the switchable domain PD_SW. The always-on TAP sits nearest the pins, and every switchable-domain TAP can be bypassed, so powering a domain down shortens the chain instead of breaking it. In a serial TDI-to-TDO chain a link that cannot shift stops everything behind it, so order alone cannot make a failure graceful.
The bypass has to live in the right domain. A bypass register inside a powered-down domain is itself unpowered and cannot shift. What keeps the chain intact is a chain-level bypass: a multiplexer in the always-on domain routes TDI straight to the next link when the domain is off, and the unpowered TAP's TDO is clamped, exactly like any other signal leaving PD_SW. The bypass register of a powered TAP is a different, one-bit thing, used when software deselects that TAP. A single debug access port then translates external transactions into register and memory accesses through the same decode the mission uses.
Three rules keep the path alive. The debug logic resets from power-on and the test reset, never from the functional resets it observes: asserting the system reset must not clear the debugger's breakpoints. The debug clock runs whenever debug is enabled, independent of the CPU and system clock gates, so halting a gated domain does not blind the debugger. And the chain order, the access-port address map and the reset assignment are recorded in the connectivity table the earlier topic owns, checked by the same checker.
// Two pieces of the chain. Simplified: the one-bit bypass register does not
// load 0 in Capture-DR and TDO is not retimed on the falling edge of TCK.
// 1) bypass register of a powered TAP (selected when software deselects it)
module tap_bypass (
input logic tck,
input logic trst_n,
input logic shift_dr,
input logic tdi,
output logic tdo
);
logic bypass;
always_ff @(posedge tck or negedge trst_n) begin
if (!trst_n)
bypass <= 1'b0;
else if (shift_dr)
bypass <= tdi;
end
assign tdo = bypass;
endmodule
// 2) chain-level bypass, placed in the ALWAYS-ON domain around a switchable TAP:
// domain off -> TDI goes straight to the next link, the TAP's TDO is clamped.
module chain_link_bypass (
input logic tdi,
input logic tap_tdo, // from the switchable TAP (undefined when unpowered)
input logic domain_on, // from the power controller, always-on
output logic tdo_next
);
logic tap_tdo_clamped;
assign tap_tdo_clamped = tap_tdo & domain_on; // clamp to 0 when off
assign tdo_next = domain_on ? tap_tdo_clamped : tdi;
endmodule# Chain model: powering a switchable domain down must shorten the chain.
DR_BITS = 32 # illustrative data-register length per TAP
CHAIN = ["tap_aon", "tap_cpu", "tap_sys"] # always-on TAP nearest the pins
DOMAIN = {"tap_aon": "AON", "tap_cpu": "SW", "tap_sys": "SW"}
def chain_len(sw_on, chain_bypass_mux):
n = 0
for tap in CHAIN:
if DOMAIN[tap] == "AON" or sw_on:
n += DR_BITS
elif chain_bypass_mux:
n += 1 # one bypass bit in the always-on mux path
else:
return None # unpowered link: nothing shifts through it
return n
assert chain_len(True, True) == 96
assert chain_len(False, True) == 32 + 1 + 1 # PD_SW off: the chain is shorter, still works
assert chain_len(False, False) is None # bypass inside the dead domain: chain broken
AP_WINDOWS = {"tap_aon": (0x00, 8), "tap_cpu": (0x10, 8), "tap_sys": (0x20, 8)}
def window_problems(w):
bad = [t for t, (b, s) in w.items() if b % s]
spans = sorted(w.values())
bad += ["overlap" for (b1, s1), (b2, _s) in zip(spans, spans[1:]) if b2 < b1 + s1]
return bad
assert window_problems(AP_WINDOWS) == []
assert window_problems(dict(AP_WINDOWS, tap_sys=(0x14, 8))) != [] # overlap and misalignment caught
RESETS = {"tap_aon": ["por_n", "trst_n"], "tap_cpu": ["por_n", "trst_n"],
"tap_sys": ["por_n", "trst_n"]}
FUNCTIONAL = {"rst_sys_n", "rst_cpu_n", "rst_per_n", "pad_rst_n", "sw_rst"}
def reset_problems(r):
return [t for t, srcs in r.items() if FUNCTIONAL & set(srcs)]
assert reset_problems(RESETS) == []
assert reset_problems(dict(RESETS, tap_cpu=["rst_cpu_n"])) == ["tap_cpu"]
print("PASS: chain 96 bits, 34 with PD_SW off; dead-domain bypass, overlap and functional reset are each caught")The chain order drawn and recorded: the always-on TAP first, every switchable-domain TAP bypassable.
The chain-level bypass multiplexer and the TDO clamp for each switchable TAP, placed in the always-on domain and listed with the other clamps.
Debug reset assignment proven independent of every functional reset it must survive, with power-on as an explicit source.
The access-port address map checked with the same alignment and overlap discipline as the mission map.
Test the debug path before any mission test in simulation: enter with the CPU clock gated, with the system in reset, and with PD_SW powered down, and confirm the always-on link still responds through the chain bypass and the chain is shorter by the right number of bits. If the debugger loses the chip exactly when the failure of interest occurs, read the reset assignment first: debug on a functional reset goes blind at the interesting moment by construction. For the TAP basics underneath see JTAG TAP Architecture and for the access port Debug Access Port.
Yes, through the chain bypass: a multiplexer in the always-on domain routes TDI straight to the next link when the domain is off, and the unpowered TAP's TDO is clamped. The bypass register inside a powered-down domain cannot do this, because it is off too.