A debugger that works only in RUN quits when the investigation gets interesting: the sleep-wake loop, the boot hang, the powered-down domain. A debug port added to NanoSoC for this section lives by two rules: its pins idle legally with no debugger attached, and its own logic stays alive in SLEEP. The lesson ends the section with the audit that proves every signal pin, the 19 of NanoSoC v1 and the 7 of the extension, keeps its reset promise.
JTAG is the IEEE 1149.1 test access port: four pins, TCK clocking the shift, TMS steering the controller, TDI in and TDO out. The pull directions matter. A TAP controller treats an open TMS and TDI as 1, so pull TMS and TDI up: a missing debugger then holds the controller in Test-Logic-Reset. Pull TCK down so the clock idles low. TDO is off (high impedance) except in the Shift-IR and Shift-DR states. The controller also enters reset after five TCK cycles with TMS high, and here it resets with the always-on peripheral reset, never with the switchable domain's, so a powered-down core does not take the debug path with it. Security gating of the port belongs to the security architecture of this course and is mentioned in words only.
What the port can reach in SLEEP is narrower than it first seems. The TAP lives in the always-on domain, so its IDCODE register still answers when PD_SW is off, and that is the test to run. But the fabric, SRAM1 and the SRAM0 wrapper are in PD_SW, and the APB decoder that connects every register is there too, so a debug access that goes through the bus cannot reach any memory or register in SLEEP. A debug path to the always-on registers would need its own private port, which the extension does not add. After wake, the bus path and memory reads work again.
The audit joins the reset-state table of the pad lessons with the extension rows: 26 rows (the 19 signal pads plus 7; supply pads have no reset state), no driver on, every control input pulled, and the pull directions above. A pin with no row fails the audit; a row with a pending entry fails it louder.
Extension pad | Direction at reset | Pull | Why |
|---|---|---|---|
TCK | Input | Pull-down | Clock idles low. |
TMS, TDI | Input | Pull-up | Open reads 1: the TAP stays in Test-Logic-Reset. |
TDO | Off (high impedance) | None needed; the driver is off outside the shift states | Only drives while shifting. |
TEST_MODE | Input | Pull-down | Mission mode is the default with the pin open. |
BOOT_MODE[1:0] | Input | Pull-down | The default selects the SPI image source. |
The script below builds all 26 rows: the 19 signal pads of NanoSoC v1 from the reset-state table (UART and SPI idle levels, GPIO pull-downs, RESET_N, the crystal) and the 7 extension rows. One function checks the lot: no pad drives at reset, every digital input is pulled in its required direction, TDO is off. It then runs on three broken tables (TMS pulled down, TEST_MODE pulled up, TDO driving at reset), and finally checks what a debug access can reach in SLEEP, with and without a private always-on port.
ROWS = {}
IDLE = {"P0": "up", "P1": "up", "P5": "up"} # UART TX, RX and SPI chip-select idle high
for i in range(16):
ROWS["P%d" % i] = ("input", IDLE.get("P%d" % i, "down"))
ROWS["RESET_N"] = ("input", "up")
ROWS["XTAL_IN"] = ROWS["XTAL_OUT"] = ("analog", "none")
ROWS.update({"TCK": ("input", "down"), "TMS": ("input", "up"), "TDI": ("input", "up"),
"TDO": ("off", "none"), "TEST_MODE": ("input", "down"),
"BOOT0": ("input", "down"), "BOOT1": ("input", "down")})
REQUIRED = {"TCK": "down", "TMS": "up", "TDI": "up", "TEST_MODE": "down", "BOOT0": "down", "BOOT1": "down"}
def check(rows):
assert len(rows) == 26, len(rows)
for pad, (direction, pull) in rows.items():
assert direction in ("input", "off", "analog"), pad + " drives at reset"
if direction == "input":
assert pull in ("up", "down"), pad + " floats"
for pad, pull in REQUIRED.items():
assert rows[pad][1] == pull, pad + " must be pulled " + pull
assert rows["TDO"][0] == "off"
return True
assert check(ROWS)
for pad, bad in (("TMS", ("input", "down")), ("TEST_MODE", ("input", "up")), ("TDO", ("output", "none"))):
try:
check({**ROWS, pad: bad})
raise SystemExit("a bad audit table was accepted")
except AssertionError:
pass
SW = {"CPU", "XBAR", "SRAM1", "DMA", "SBR", "APB_DEC", "SRAM0_WRAPPER"} # PD_SW blocks
AON_REGS = {"RSTPWR", "GPIO", "UART", "SPI", "INTC"} # always-on registers
def reachable_in_sleep(target, private_port=False):
if target == "TAP_IDCODE":
return True # the TAP is in the always-on domain
return private_port and target in AON_REGS # everything else needs the bus, which is in PD_SW
assert reachable_in_sleep("TAP_IDCODE")
assert not any(reachable_in_sleep(t) for t in ("SRAM0_WRAPPER", "SRAM1", "GPIO", "XBAR"))
assert reachable_in_sleep("GPIO", private_port=True) and not reachable_in_sleep("SRAM1", private_port=True)
print("PASS: 26 signal pads audited; TMS and TDI up, TCK down, TDO off; in SLEEP only the TAP answers without a private port")The TAP drawn in the always-on domain with its reset source named.
Pull directions specified per pin for the no-debugger case, with the reason.
A statement of what the port can and cannot reach in SLEEP.
The audit output attached to the tapeout review with zero open rows, regenerated from the pin database after every change.
Verify the debug path with a sleep-wake test on the bench or in emulation: connect the debugger, force SLEEP, and confirm the TAP still answers (the IDCODE reads correctly) while PD_SW is off, then confirm that memory reads work again after wake. Verify the audit by regenerating it from the pin database after every change; a hand-edited audit is a rumour, and the script above is the truth.
The TAP is only read while it is shifting, and the controller drives TDO in exactly the Shift-IR and Shift-DR states; between shifts the line is not sampled. A board pull on TDO is optional tidiness; the pins that need a defined pull are the ones the TAP samples: TCK, TMS and TDI.