The first driver ever written for NanoSoC talks to the DMA, and three of its traps are easy to miss because nothing reports them: LEN and KEY writes vanish while BUSY with no error, a stale ERR holds the interrupt high after DONE is cleared, and writing CTRL with 0x1 clears the interrupt enable as a side effect. A register sweep finds all three in microseconds by reading back everything the register tables promise. Its one job is the sweep method: what to touch, in what order, and what each mismatch means.
A sweep visits every implemented register three times. First reset values: after any reset, read each register and compare against the documented value — DMA CTRL reads 0x0 with bit 0 always zero, STATUS 0, LEN 0, KEY 0, COUNT 0, SBR CTRL 0x4 with NS set, UART BAUDDIV 0xD, CLK_EN 0x3. Second access types: write 1 to each W1C bit and confirm only that bit clears, write to each read-only field and confirm nothing changes, and write LEN and KEY while BUSY to confirm they are silently ignored — the ignored write is correct behaviour, and the sweep documents it instead of tripping on it. Third the traps: start a zero-length job and confirm DONE without BUSY, then clear DONE and ERR with 0x6 and confirm the interrupt drops only when both are gone.
The sweep also has a price, which the plan uses: a DMA or SBR register access is 125 ns from the CPU, a clk_per register (INTC, GPIO, UART, SPI, RSTPWR) is 281.25 ns, so a 40-register sweep of the mixed set is about 7.5 microseconds of simulated time. The sweep order matters because registers have side effects: read STATUS before clearing it, and never sweep CTRL bit 0 (GO) with a real LEN loaded unless the test means to start a job. Unmapped offsets must answer PSLVERR — offsets 0x14 to 0xFF in the DMA slot, the reserved APB slots — and the sweep proves a sample of them rather than assuming the decoder.
# Register sweep against a behavioural model of the DMA registers (register
# map). The sweep is a function of the device, so it can be run on a
# faulty device and must fail. NanoSoC teaching values.
EXPECT_RESET = {0x00: 0x0, 0x04: 0x0, 0x08: 0x0, 0x0C: 0x0, 0x10: 0x0}
class Dma:
def __init__(self, reset_key=0, ignore_busy_len=True):
self.irq_en, self.busy, self.done, self.err = 0, 0, 0, 0
self.len, self.key, self.count = 0, reset_key, 0
self.ignore_busy_len = ignore_busy_len
def read(self, off):
if off == 0x00: return self.irq_en << 1
if off == 0x04: return self.busy | self.done << 1 | self.err << 2
if off == 0x08: return self.len
if off == 0x0C: return self.key
if off == 0x10: return self.count
return "PSLVERR"
def write(self, off, val):
if off == 0x00:
self.irq_en = (val >> 1) & 1 # every write copies bit 1
if val & 1 and not self.busy and self.len:
self.busy, self.done, self.count = 1, 0, 0
elif off == 0x04:
if val & 2: self.done = 0
if val & 4: self.err = 0
elif off == 0x08 and not (self.busy and self.ignore_busy_len):
self.len = val & 0xFFFF
elif off == 0x0C and not self.busy:
self.key = val
def sweep(dut):
bad = [hex(o) for o, want in EXPECT_RESET.items() if dut.read(o) != want]
bad += [hex(o) for o in (0x01, 0x14, 0x20, 0xFF) if dut.read(o) != "PSLVERR"]
dut.write(0x08, 64); dut.write(0x00, 0x3) # LEN, then GO with IRQ_EN
if dut.read(0x04) & 1 != 1: bad.append("busy-after-go")
dut.write(0x08, 8) # LEN write while BUSY must be silently ignored
if dut.read(0x08) != 64: bad.append("len-write-while-busy")
dut.write(0x00, 0x1) # GO with bit 1 clear: IRQ_EN is rewritten to 0
if dut.read(0x00) != 0: bad.append("ctrl-mirror")
return bad
assert sweep(Dma()) == []
assert sweep(Dma(reset_key=1)) == ["0xc"] # a reset-value fault names its register
assert "len-write-while-busy" in sweep(Dma(ignore_busy_len=False))
print("PASS: sweep clean on the model; a wrong KEY reset and an accepted BUSY write are caught")The sweep table lists every register with reset value, access type and side effect, each traced to the register tables.
BUSY-ignored writes and the sticky ERR are asserted as correct behaviour, not flagged as failures.
A sample of unmapped offsets proves PSLVERR, including one alias-image address used only by the sweep.
A reset-value mismatch names its field: check the reset branch of the RTL for that flop, then the synchronised release that should have cleared it. If all values read as zero, suspect the read path rather than the resets — a dead PREADY or a gated clock returns zeros that look like correct resets. If only one block mismatches, check that block's reset net membership before suspecting its logic.
0x6: write 1 to clear to bits 1 and 2, clearing DONE and ERR together whichever outcome occurred. Clearing only DONE leaves a stale ERR set and the interrupt asserted, which is planted bug 9 in its interrupt form.