The chip hangs after fetching its first instructions. That sentence opens the whiteboard triage question, and the structured answer starts at the address: which address hung, who should have answered, and what came back instead. Guessing wastes days; a method that turns every symptom into a location takes minutes. This lesson teaches that method and demonstrates it on two decode bugs planted in the NanoSoC map for exactly this purpose.
Every address symptom falls into one of three classes. A hang means no response arrived: suspect a missing default slave, a bridge that dropped PREADY, a block whose clock was gated off while software accesses it (a clear clock-enable bit has the same symptom), or a reset that left one side of an interface unreleased. The 256-cycle bridge watchdog of NanoSoC converts such a hang into a SLVERR after its timeout, which is a fifth error source to include in the sweep. Wrong data means the wrong slave answered: suspect an overlapping window, a slot boundary off by a nibble, or an alias the software did not know about. An error response means the right machinery declined: place the address on the map, read the response table, and decide whether the software address or the hardware policy is wrong, remembering that a faulted but mapped address answers SLVERR from the fault responder while only true holes answer DECERR from the default slave. The method then runs in five steps: classify the symptom, place the address on the map and predict the responder, reproduce with a single access, bisect with readback to the owning level, and fix that level with a regression check.
Symptom | First suspect | Localising readback | Fix and check |
|---|---|---|---|
Hang, no response | Missing default slave or dropped PREADY | Single read to a known hole: must return, never hang | Add the default slave; sweep every hole |
Wrong data | Overlap, wide window, alias | Write one slave, read back its neighbours | Narrow the select; neighbour-readback test |
Unexpected error | Wrong offset, width wiring, secure state | One register works, the rest fail | Fix the wiring or the state; walk every offset |
The symptom is corrupt DMA output: every word comes out with bit 0 flipped and COUNT correct, because the data was XORed with KEY equal to 1 that nobody wrote. The bisection starts with readback: after touching only the SBR, read back all five DMA registers. KEY has changed while nothing wrote to the DMA, so the write went somewhere it should not. The cause is a slot select drawn one bit too wide: the DMA select used addr[15:13] equal to zero instead of addr[15:12] equal to zero, an 8 KB window covering both slot 0 and slot 1. Writing SBR CTRL at 0x4000_100C drove offset 0x0C into both slaves, and offset 0x0C in the DMA is KEY. The fix narrows the select to the full nibble, and the check that would have caught it writes every SBR register and reads back every DMA register, failing on any cross-talk. Know the whole collision table, because 0x0C is only the one that corrupts data: SBR offset 0x00 (SRC_ADDR) lands on DMA CTRL and silently clears IRQ_EN, offset 0x08 overwrites DMA LEN, and only 0x04 (STATUS written with its clear bits zero) and 0x10 (read-only COUNT) are harmless.
The symptom is a wall of errors with one silent exception: STATUS, LEN and KEY return PSLVERR, and a COUNT read returns the STATUS value with no error at all (offset 0x10 shifted right by two is 0x04). The pattern is the diagnosis: exactly one offset reads correctly, and it is offset zero, which survives any shift. The wrapper wired paddr to apb_paddr bits 9 down to 2, a word index, instead of bits 7 down to 0, a byte offset. Address 0x04 arrives as paddr 0x01, which the DMA correctly rejects, while 0x00 arrives as 0x00 and works. The fix wires paddr to apb_paddr[7:0], restoring the documented alias, and the checks that would have caught it are two: a register walk over every DMA offset through the front door, and a connectivity check that paddr bit i is driven by apb_paddr bit i for all eight bits. Either one fails on the day the bug is introduced.
The debug plan for the section names the three symptom classes and the readback that bisects each.
Every decode bug fix arrives with its regression check: neighbour readback for overlaps, offset walks for wiring, hole sweeps for defaults.
The two planted bugs above are in the verification plan by name, with the exact addresses that expose them.
Keep a debugger attached through the debug access and reproduce each symptom with a single access before changing anything: one write of 1 to 0x4000_100C followed by a DMA KEY readback for the first bug, one read to 0x4000_0004 for the second. Confirm the fix the same way, then run the full sweep: every mapped register, every hole, both security states for the secure rows. If the symptom moves instead of disappearing, two bugs overlap and the method restarts from classification, not from the last fix.
The slot decoder: its DMA select is wider than one slot, reaching into slot 1. Narrow the DMA select to addr[15:12] equal to zero so each 4 KB slot selects exactly one slave.