Most of the ten planted NanoSoC bugs pass every block test as written, because each block is correct to its own specification and the failure needs the map, a neighbour, the power intent or the driver. Six of them (3, 4, 5, 6, 7, 8) are invisible to any block test; one (9) is correct block behaviour that becomes a bug only through the driver; and three (1, 2, 10) live inside integration blocks (the decoder, the reset sequencer, the bridge), where a block test would catch them only if someone wrote the cross-check. Nobody does, because what is missing is a map table, a release order or a timeout. The DMA bench cannot see that its interrupt reaches controller input 6, the SRAM model cannot see that software put stack and stream in the same bank, and no APB slave test can see that the CPU released from reset before the fabric. A strategy that merely runs more block tests keeps missing them. Its one job is the level rule: what belongs where, and what only the chip can show.
Three levels, three scopes. Block level proves one block against its specification with its neighbours modelled: the DMA bench proves XOR, LEN handling and the interrupt equation. Subsystem level proves a cluster with real neighbours where interaction matters: the APB segment with its bridge and decoder proves slot decode, wait states and error propagation. Chip level proves the integrated whole with real everything: reset order, the address map as decoded, interrupt wiring end to end, boot to prompt, power modes, and performance against budgets. The level rule follows: run each test at the lowest level that can show its failure, because lower is faster, more controllable and easier to debug; and never assume a higher level repeats a lower one's proof.
What only the chip can show is the complement of every boundary a lower level models away. On NanoSoC that is: the 8-bit DMA address port behind a 12-bit decoder (alias A1) and the byte-offset wiring into it (planted bug 4), the interrupt input index (planted bug 3), the reset release order (planted bug 2), the missing clamps (planted bug 6), the NS reset default against the secure-only SRAM1 (planted bug 7), the two LEN registers that must agree (planted bug 8), the BUSY-write and sticky-ERR contract (planted bug 9, a driver rule), and the clock gated during an access (planted bug 10). Most of these live in the wiring, the map table, the power intent or the driver, which is why the top-level test list exists. Three are different: the slot overlap (planted bug 1) is a decode error inside the APB decoder, the early CPU release (bug 2) is in the reset sequencer, and the missing timeout (bug 10) is in the bridge. A bench for each of those blocks fails them if it checks the whole slot table, the release order or a slave that never answers; the block benches as written check none of those, which is the point of the chip-level entries.
Failure | Visible at block? | First level that shows it |
|---|---|---|
DMA XOR data error | Yes, DMA bench | Block |
Interrupt to wrong input (bug 3) | No, bench models the controller | Chip |
CPU released before the fabric (bug 2) | No, each block has its own reset | Chip |
Byte-offset wiring (bug 4) | No, bench drives paddr directly | APB subsystem (decoder and bridge real) |
NS default vs secure SRAM1 (bug 7) | No, attribute lives in the map | Chip |
SBR/DMA LEN mismatch (bug 8) | No, two blocks plus software | Chip with firmware |
Same-bank slowdown 361.4 to 180.7 MB/s | No, one port per model | Chip performance |
Slot overlap (bug 1), early CPU release (bug 2), no bridge timeout (bug 10) | Only if the block bench checks the whole slot table, the release order, or a slave that never answers | Block, if the cross-check is written; otherwise chip |
Pad pulls, supplies and the simultaneous-switching limit (pad bugs P1 to P3) | No: no pad cell exists in the RTL | Board and silicon only |
# The level rule as a checker. Each failure lists the boundaries it needs to be
# real; the lowest level that instantiates all of them is where it first shows.
# The requirement sets are ILLUSTRATIVE: a teaching model of the planted bugs,
# not a measurement. Changing the test changes the set (see the two bug 1 rows).
LEVELS = ["block", "subsystem", "chip"]
REAL = {
"block": {"own-logic"},
"subsystem": {"own-logic", "apb-decode", "apb-bridge"},
"chip": {"own-logic", "apb-decode", "apb-bridge", "intc-wiring", "reset-tree",
"power-intent", "address-map", "firmware", "shared-sram-port", "clock-gate"},
}
FAILURES = {
"xor data error": {"own-logic"},
"byte-offset wiring (bug 4)": {"apb-decode", "own-logic"},
"cpu released early (bug 2)": {"reset-tree"},
"irq on input 6 (bug 3)": {"intc-wiring"},
"missing clamps (bug 6)": {"power-intent"},
"ns default vs sram1 (bug 7)": {"address-map", "firmware"},
"len mismatch (bug 8)": {"firmware", "own-logic"},
"same-bank slowdown": {"shared-sram-port", "firmware"},
# the same bug moves up a level when the test changes: a decoder bench that
# checks the whole slot table fails at block level; seen only as flipped data
# it needs the decoder, the SBR write and software together
"slot overlap, decoder bench checks the slot table (bug 1)": {"own-logic"},
"slot overlap, seen as flipped data (bug 1)": {"apb-decode", "firmware"},
"bridge bench with a slave that never answers (bug 10)": {"own-logic"},
"clock gated during an access (bug 10)": {"clock-gate", "firmware"},
"pad pull-down on spi_cs_n (pad bug P1)": {"pad-cell"},
}
def first_level(needs):
for level in LEVELS:
if needs <= REAL[level]:
return level
return None
placed = {name: first_level(needs) for name, needs in FAILURES.items()}
assert placed["xor data error"] == "block"
assert placed["byte-offset wiring (bug 4)"] == "subsystem"
chip_only = sorted(n for n, lvl in placed.items() if lvl == "chip")
assert len(chip_only) == 8, chip_only
assert sorted(placed.values(), key=str).count("block") == 3
# the same bug lands on two levels depending on the test that looks for it
assert placed["slot overlap, decoder bench checks the slot table (bug 1)"] == "block"
assert placed["slot overlap, seen as flipped data (bug 1)"] == "chip"
assert placed["bridge bench with a slave that never answers (bug 10)"] == "block"
# the checker can fail: no simulated level instantiates a pad cell, so that failure
# is unplaced and must be handed to the board and silicon plan
unplaced = sorted(n for n, lvl in placed.items() if lvl is None)
assert unplaced == ["pad pull-down on spi_cs_n (pad bug P1)"], unplaced
assert first_level({"analog-supply"}) is None
print("PASS: 3 block, 1 subsystem, 8 chip-level failures; the pad failure is board-only")Every planned test names its level and the failure it shows, with the level rule quoted for any chip-level entry.
No failure is assigned below the first level that can show it, and none is left without a level.
The plan cross-references the owner-course strategy pages instead of restating them, and teaches only the NanoSoC instance.
Audit the plan against a written list of integration failure modes, not against gut feel: list them, strike the ones with no NanoSoC surface, and confirm each survivor names a test at or above its showing level. A mode with no test is the plan's gap; a test with no mode is the plan's waste. Both get fixed in review, before either consumes simulation.
Chip with firmware: the corruption is a LEN mismatch between two blocks plus software, and no lower level instantiates all three together.