Only a read of DMA CTRL returns the right register. STATUS, LEN and KEY take PSLVERR, and a COUNT read silently returns STATUS with no error at all. The register map is correct and the slot select is correct, yet the block is unusable. The integrator fed the DMA's 8-bit paddr with word addresses apb_paddr[9:2], but the DMA decodes byte offsets, so offset 4 arrives as 1, which is unmapped, while offset 0x10 arrives as 0x04, which is STATUS. Address conventions differ per IP, and the checker for this lesson models the wiring from the bus address to the port.
Goal: the decode expressions, a response code for every hole, and the DMA port wired to the right address bits.
Inputs: the crossbar rule, the APB slot rule, the DMA's byte-offset map.
Steps: derive the selects; probe each hole in the checker; inject the word-address wiring and confirm only CTRL is reached correctly and COUNT lands on STATUS; restore.
The crossbar compares addr[31:14] == 0 for ROM, addr[31:16] == 0x2000 and 0x2001 for the SRAMs, and addr[31:16] == 0x4000 for the APB bridge; anything else falls to the default slave. Inside the window the APB decoder uses addr[15:12] as the slot, 0 to 6 mapped, 7 to 15 reserved. Holes H1, H2 and H4 reach no slave, so the default slave answers DECERR (2'b11) within 2 clk_sys cycles; hole H3 reaches the APB decoder's default slave, which answers PSLVERR, turned into AXI SLVERR (2'b10). Secure-only SRAM1 and RSTPWR return SLVERR to non-secure accesses. The DMA takes apb_paddr[7:0] as byte offsets; offsets 0x14 to 0xFF inside each 256-byte image still give PSLVERR.
# not-run: format fragment, not executable here
# crossbar selects apb slot select
addr[31:14] == 0 -> ROM addr[15:12] == 0 -> DMA ... == 6 -> RSTPWR
addr[31:16] == 0x2000 -> SRAM0 == 7..15 -> default slave (PSLVERR)
addr[31:16] == 0x2001 -> SRAM1 H1/H2/H4 -> fabric default slave (DECERR)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 ----
assert decode(0x00000000) == "BOOTROM" and decode(0x2000FFFF) == "SRAM0"
assert decode(0x20010000) == "SRAM1" and decode(0x40006000) == "RSTPWR"
assert decode(0x00004000) == "H1" and decode(0x20020000) == "H2"
assert decode(0x40007000) == "H3" and decode(0x40010000) == "H4"
assert response(0x00004000, True) == "DECERR" and response(0x40007000, True) == "SLVERR"
assert response(0x40010000, True) == "DECERR"
assert response(0x20010000, False) == "SLVERR" and response(0x20010000, True) == "OK"
REG = {0x00: "CTRL", 0x04: "STATUS", 0x08: "LEN", 0x0C: "KEY", 0x10: "COUNT"}
def dma_read(bus_addr, lo, hi):
# the bus address has 12 bits; the DMA port paddr has 8 and sees bits [hi:lo] of it
if hi - lo != 7:
return "WIDTH" # a width mismatch is a lint error, never a silent truncation
return REG.get(((bus_addr & 0xFFF) >> lo) & 0xFF, "PSLVERR")
def reached(lo, hi):
return [dma_read(0x40000000 + o, lo, hi) for o in sorted(REG)]
assert reached(0, 7) == ["CTRL", "STATUS", "LEN", "KEY", "COUNT"] # apb_paddr[7:0]: correct
assert reached(2, 9) == ["CTRL", "PSLVERR", "PSLVERR", "PSLVERR", "STATUS"] # apb_paddr[9:2]: bug 4
assert reached(4, 11) == ["CTRL", "CTRL", "CTRL", "CTRL", "PSLVERR"] # apb_paddr[11:4]: worse
assert reached(0, 9)[0] == "WIDTH"
assert dma_read(0x40000104, 0, 7) == "STATUS" and dma_read(0x40000114, 0, 7) == "PSLVERR" # the alias, and offsets past 0x10
print("PASS: decode and codes agree; word-address wiring reaches 1 register right, 3 take PSLVERR, COUNT returns STATUS")One PASS line. The three reached lists are the bug-4 demonstration: the correct connection gives all five registers; the word-address connection leaves CTRL right, STATUS, LEN and KEY on PSLVERR, and COUNT silently reading STATUS because 0x10 shifted right by 2 is 0x04. The assert on a 10-bit slice shows the width lint refusing a mismatch.
A DECERR/SLVERR swap means the hole addressing was guessed: H3 is inside the APB window and answers SLVERR, the rest DECERR. A secure-column failure means AxPROT[1] was read backwards: 0 is secure, 1 is non-secure. A wrong list from the correct connection means the slice bounds were misread: the port wants bits [7:0] of the bus address, not a word address.
Hint 1: list which offsets the RTL case statement actually matches: 0x00, 04, 08, 0C, 10. Hint 2: shift each right by two and look the result up in the same table. Hint 3: 0 lands on CTRL correctly, and 0x10 lands on 4, which is STATUS, not COUNT, so a COUNT read returns the wrong register with no error. Answer: connect apb_paddr[7:0]; treat port-width mismatches as lint errors and keep a per-IP address-convention table in the integration spec.
A wild pointer or a bad linker script gets a clean DECERR instead of a hang, turning a freeze into a diagnosable fault.