This lesson has one job: teach the three-register dispatch that every NanoSoC driver uses. Eight sources share one CPU line, so the controller must answer three questions in hardware: which inputs are currently asserting, which of those software wants to hear about, and which one comes first. The answers are RAW, ENABLE and PENDING plus the TOP register, and a driver that reads them in the right order handles any combination of simultaneous interrupts without ever touching a source block first.
RAW holds the synchronised input levels, one bit per source. ENABLE is the mask software writes: a 1 lets that input through, and reset clears all eight so nothing interrupts before software is ready. PENDING is the AND of the two, combinational, with no storage of its own: clear an ENABLE bit and the pending bit vanishes in the same cycle; clear the source and it vanishes too. TOP scans the eight pending bits and reports the lowest index with a bit set, or 0x1F when none is pending. Priority is fixed and lower-number-wins: power events on input 0 always beat the DMA on input 1, which beats everything below. The ranking is deliberate, with one line of reasoning per rank: power first because it must never wait, DMA second because a stalled stream costs throughput, UART third because its holding register is only one byte deep.
IRQ | Source | Cleared by | Rank reason |
|---|---|---|---|
0 | Power and PLL events | W1C on PWR_IRQ | Must never wait. |
1 | DMA done or error | W1C DONE or ERR, or clear IRQ_EN | Stream stalls cost throughput. |
2 | Stream bridge done or error | W1C in SBR STATUS | Same stream, second half. |
3 | UART transmit or receive | Touch the DATA register | One byte of holding: overrun risk. |
4 | SPI receive | Read DATA | Same, slower. |
5 | GPIO level | Remove the pin condition | Slow by nature. |
6-7 | Reserved, tied to 0 | n/a | Spares for growth. |
The dispatch loop is four steps: read TOP; if it is 0x1F there is nothing to do; service that source; clear the source's status so its level drops; repeat, because a higher-priority source may have arrived while servicing. Masking is the other half of the contract: a driver that is not ready for an input keeps its ENABLE bit at 0, and the pending bit never forms. The two spare inputs are tied to 0 at the top level, so their RAW bits can never set; any test that sees them set has found a wiring fault, not a new feature.
The script below models the controller's three registers exactly, PENDING as the AND and TOP as the lowest set index, then dispatches a UART-plus-GPIO pile-up while the DMA fires mid-service, exactly the case that rewards reading TOP again instead of assuming one round is enough.
def pending(raw, enable):
# PENDING is exactly RAW & ENABLE; the spares are kept out by tying RAW to 0.
return raw & enable
def top(p):
for i in range(8):
if p & (1 << i):
return i
return 0x1F
RAW_UART, RAW_GPIO, RAW_DMA = 0x08, 0x20, 0x02
raw = RAW_UART | RAW_GPIO # UART and GPIO assert together
enable = 0xFF # everything unmasked
p = pending(raw, enable)
assert p == 0x28
assert top(p) == 3 # UART (3) beats GPIO (5)
raw &= ~RAW_UART # service UART, its level drops
p = pending(raw, enable)
assert top(p) == 5
raw |= RAW_DMA # DMA fires mid-service
p = pending(raw, enable)
assert top(p) == 1 # DMA is dispatched before GPIO on the re-read
raw &= ~(RAW_DMA | RAW_GPIO)
assert top(pending(raw, enable)) == 0x1F
assert pending(0xFF, 0x00) == 0x00 # masking removes everything
assert pending(0x00, 0xFF) == 0x00 # RAW = 0 on the spares: nothing pends
assert pending(0xC0, 0xFF) == 0xC0 # a set spare WOULD pend: only the tie-off keeps it out
print("PASS: dispatch order UART, DMA, GPIO; mask and spares behave")ENABLE resets to zero, and the boot code unmasks each input only after its handler is installed.
Every handler clears its source before returning, and reads TOP again rather than assuming the pile is empty.
The spare inputs have explicit tie-offs at the top level and a connectivity check that flags any use of inputs 6 or 7.
Force each source in turn with the others masked and confirm TOP names it; then unmask all and force pairs to confirm the lower number wins. Write 0x1F-expectation tests for the idle case: TOP must read 0x1F with nothing pending, or the tied-off spares are floating. If the wrong handler runs, suspect the numbering-offset bug: wiring counted with a shared-peripheral base while software counted from zero.
SPI on input 4 beats GPIO on input 5. The handler must clear the source, reading SPI DATA so the level drops, then read TOP again in case a higher-priority source arrived meanwhile.