A lab without known bugs teaches touring, not integrating. Ten real integration mistakes are injected into the clean lab data, one at a time, each one made possible by a genuine NanoSoC sharp edge: a narrow DMA port behind a wide decoder, a faster sync beating a slower fabric, a reset-safe register default that is not access-safe. Your grade is the fraction you catch with checkers, not with eyes.
Goal: memorise the ten bugs as symptom, root cause and fix, and the rubric weights.
Inputs: the table below; the behaviour of the DMA and SBR registers that each bug abuses.
Steps: for each bug write which stage catches it before reading the stage; after the lab, diff your prediction against the test matrix.
Bug | Symptom | Root cause | Fix | Stage |
|---|---|---|---|---|
1 Address overlap | SBR CTRL write flips DMA data bit 0 | DMA slot decoded as 8 KB, slots 0 and 1 overlap | Decode addr[15:12] per 4 KB slot | 1 |
2 Reset release order | CPU stalls at 0x0000_0000, no RVALID | rst_cpu_n released with rst_sys_n; first fetch lost | CPU last: 8 clk_sys + 2 clk_cpu later | 2 |
3 Mis-wired interrupt | DMA irq high, INTC RAW bit 1 is 0, bit 6 is 1 | irq_dma tied to spare input 6 | Connect to input 1; per-IRQ test | 3 |
4 Width and addressing | Only CTRL reads right; STATUS, LEN, KEY take PSLVERR and a COUNT read silently returns STATUS | paddr fed word addresses, DMA decodes byte offsets: 0x10 shifted right by 2 is 0x04 | Connect apb_paddr[7:0] | 1 |
5 Unsynchronised crossing | Missed interrupt once in thousands of jobs | irq_dma into clk_per with no 2-flop sync | 2-flop synchroniser on every INTC input | 2 |
6 Missing isolation | Spurious irq_cpu in SLEEP; sleep/wake loop | 112 of 114 clamps; interrupts missed | Clamp-0 on all 114 nets before power off | 2 |
7 Secure attribute | SBR STATUS.ERR on first beat from SRAM1 | SBR CTRL.NS resets to 1, SRAM1 secure-only | Secure software clears CTRL.NS | 3 |
8 Stream length mismatch | ERR with COUNT 32, or SBR stuck BUSY | SBR LEN and DMA LEN disagree | One n_words written to both LENs | 3 |
9 Contract traps | Stale KEY; irq never drops; IRQ_EN cleared | Ignored writes while BUSY; sticky ERR; CTRL rewrites IRQ_EN | Wait BUSY=0; clear DONE and ERR; write 0x3 | 3 |
10 Gated clock hang | After CLK_EN = 0 the first access to the idle DMA hangs the bus; only reset recovers | clk_dma stopped, so no PREADY, and the bridge has no timeout | 256-cycle bridge watchdog completes it with PSLVERR | 2 |
Two related traps are not numbered bugs. Placing the SBR source and destination in the same SRAM bank halves the stream rate (stage 1). And the SBR register slot is non-secure, so non-secure software can clear CTRL.NS and reach SRAM1 through the SBR: the secure partition is not closed in NanoSoC v1 (stage 3 and the readiness review).
MAP = [
("BOOTROM", 0x00000000, 0x4000),
("SRAM0", 0x20000000, 0x10000),
("SRAM1", 0x20010000, 0x10000),
("DMA", 0x40000000, 0x1000),
("SBR", 0x40001000, 0x1000),
("UART", 0x40002000, 0x1000),
("SPI", 0x40003000, 0x1000),
("GPIO", 0x40004000, 0x1000),
("INTC", 0x40005000, 0x1000),
("RSTPWR", 0x40006000, 0x1000),
]
SECURE_ONLY = {"SRAM1", "RSTPWR"}
SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
def end_of(base, size):
return base + size - 1
def overlaps(entries=MAP):
bad = []
xs = sorted(entries, key=lambda e: e[1])
for a, b in zip(xs, xs[1:]):
if b[1] < a[1] + a[2]:
bad.append((a[0], b[0]))
return bad
def misaligned(entries=MAP):
return [n for n, b, s in entries if b % s != 0]
def decode(addr, slots=SLOTS):
if addr >> 14 == 0:
return "BOOTROM"
if addr >> 16 == 0x2000:
return "SRAM0"
if addr >> 16 == 0x2001:
return "SRAM1"
if addr >> 16 == 0x4000:
slot = (addr >> 12) & 0xF
return slots[slot] if slot < 7 else "H3"
if addr < 0x20000000:
return "H1"
if addr < 0x40000000:
return "H2"
return "H4"
def response(addr, secure, slots=SLOTS):
t = decode(addr, slots)
if t in ("H1", "H2", "H4"):
return "DECERR"
if t == "H3":
return "SLVERR"
if t in SECURE_ONLY and not secure:
return "SLVERR"
return "OK"
def sustained(peak, beats):
return peak * beats / (beats + 1)
# ---- this lesson starts here ----
bugs = {1: "stage1", 2: "stage2", 3: "stage3", 4: "stage1", 5: "stage2",
6: "stage2", 7: "stage3", 8: "stage3", 9: "stage3", 10: "stage2"}
def covered(b):
return {s: sum(1 for v in b.values() if v == s) for s in ["stage1", "stage2", "stage3"]}
EXPECT = {"stage1": 2, "stage2": 4, "stage3": 4}
assert sorted(bugs) == list(range(1, 11)) and covered(bugs) == EXPECT
assert covered({**bugs, 4: "stage2"}) != EXPECT # a mis-filed bug is visible
assert decode(0x40000000) == "DMA" and decode(0x40001000) == "SBR"
print("PASS: 10 planted bugs filed: stage1 2, stage2 4, stage3 4; stage 4 proves all ten")One PASS line with the per-stage catch counts. Stage 4 files no bug of its own: it runs the matrix that proves stages 1 to 3 caught all ten.
A count failure means a bug was filed under the wrong stage; the table above is the oracle. A decode failure means the engine's map disagrees with your bug-1 notes: slots 0 and 1 are the DMA and SBR windows, and both must decode clean.
Hint 1: group the bugs by signal family, address, reset/clock/power, interrupt/DMA/contract, and the stage names itself. Hint 2: bugs 1 and 4 share the APB decode; bugs 8 and 9 share the LEN and CTRL registers. Hint 3: bug 10 looks like software but is a hardware timeout that is missing. Answer: stage 1 catches 1 and 4, stage 2 catches 2, 5, 6 and 10, stage 3 catches 3, 7, 8 and 9, stage 4 proves all ten.
Grading rubric (illustrative NanoSoC teaching values): 40 percent for checkers that catch their bugs on both good and injected-bad data, 25 percent for correct root-cause statements quoting the exact register or cycle count, 20 percent for the final manifest check passing, 10 percent for the waiver-ageing review with every waiver evidenced, 5 percent for the readiness statement and its stated leftovers. A stage scores only when its checker prints PASS on the clean data and flags the injected fault.
The two interrupt lines irq_dma and irq_sbr: they look like control, not data, so the clamp audit that counts bus widths walks past them.