This lesson has one job: teach the bridge that makes the streaming DMA useful and the agreement its two halves must keep. The DMA cannot touch memory, so the bridge reads source words over AXI, pushes them into the DMA input stream, collects the output stream and writes it back. The two blocks each have their own LEN register, programmed separately, and nothing in hardware keeps them equal. When they disagree, one of two failures follows, both easy to miss: an error flag with a short count, or a DONE with a bridge that never finishes.
The bridge holds SRC_ADDR and DST_ADDR with reset values pointing at SRAM0 and SRAM1, a 16-bit LEN matching the DMA width on purpose, CTRL with START, IRQ_EN and an NS bit that resets to 1, non-secure, and STATUS with BUSY, DONE and ERR for AXI error responses. Programming mirrors the DMA: addresses, LEN, then CTRL with START. The NS reset default is a deliberate teaching trap: a job that touches secure-only SRAM1 with NS still 1 returns SLVERR on the first beat, the bridge reports ERR, the DMA starves with COUNT at 0 and stays BUSY. That holds on either side of the copy: DST_ADDR resets to SRAM1 too, so even the very first job started from reset values writes to SRAM1 and fails on the write side. Any job that touches SRAM1, as source or destination, needs NS cleared first, and clearing NS is a job for secure software; the reset-safe default stays non-secure so an unprogrammed bridge cannot reach secure memory by accident.
Length agreement is the standing rule: software keeps one n_words variable and writes it to both LEN registers. Case A, bridge 32 with DMA 64: the bridge ends its read after 32 words and its output tlast arrives early, which the DMA treats as an early end, setting ERR with COUNT at 32. Case B, bridge 64 with DMA 32: the DMA finishes after 32 words, stops accepting, and the bridge's read engine stalls forever with SBR BUSY never clearing. Case A fails loudly, case B hangs quietly, and both are fixed the same way: one variable, two writes, and a post-DONE check that COUNT equals the bridge length.
A DMA job of N words costs N input beats plus 8 clocks of pipeline fill and drain: read address, SRAM latency, the DMA output register and the write path. The script below prices the boot ROM's 64-word self-test job, an 8 KB copy of the same size as the boot image (the boot flow itself loads that image over SPI with the CPU, not with the DMA), and the single-bank slowdown that halves the rate when source and destination share one SRAM port.
T_SYS = 1 / 96e6
def job_time(n_words):
return (n_words + 8) * T_SYS # N beats + 8 clocks of fill and drain
selftest = job_time(64)
assert abs(selftest - 750e-9) < 1e-9
print(f"64-word self-test job: {selftest * 1e9:.0f} ns")
image_words = 8192 // 4 # 8 KB image, 4 bytes per word
bursts = image_words // 16 # 16-beat AXI bursts
cycles = bursts * 17 + 8 # 1 bubble cycle per burst + pipeline
image_time = cycles * T_SYS
print(f"8 KB copy: {bursts} bursts, {cycles} cycles, {image_time * 1e6:.2f} us")
assert bursts == 128 and cycles == 2184
rate = image_words * 4 / image_time / 1e6
print(f"sustained copy rate: {rate:.1f} MB/s")
assert 340 < rate < 365 # near the 361.4 MB/s burst-16 figure
same_bank = rate / 2 # one port shared by read and write streams
print(f"same-bank rate: {same_bank:.1f} MB/s")
assert 170 < same_bank < 185
def outcome(sbr_len, dma_len):
# the bridge sends sbr_len words and marks the last with tlast
if sbr_len < dma_len:
return f"ERR, COUNT={sbr_len}" # early tlast ends the job
if sbr_len > dma_len:
return "DONE, SBR stuck BUSY" # DMA stops accepting
return f"DONE, COUNT={dma_len}"
assert outcome(32, 64) == "ERR, COUNT=32"
assert outcome(64, 32) == "DONE, SBR stuck BUSY"
assert outcome(64, 64) == "DONE, COUNT=64"
assert outcome(31, 64) != outcome(64, 64) # any disagreement is visible
print("PASS: 750 ns self-test; 8 KB in 22.75 us; agreement outcomes hold")One length variable feeds both LEN registers in every driver, with the COUNT-equals-length check after DONE.
The NS bit is set deliberately per buffer, never left at reset by accident; secure buffers travel with NS cleared.
Source and destination buffers live on different SRAM banks in the common case, and the placement rule is written in the driver header.
Run the three LEN combinations deliberately: equal, bridge-short and bridge-long, and confirm ERR-with-32, stuck-BUSY and clean DONE respectively. If the DMA stays BUSY with COUNT at 0 from the first beat, read SBR STATUS before touching the DMA: an AXI SLVERR there with a secure source means NS was left at reset. If the rate halves with no error, check the bank placement: both buffers on one SRAM share its single port.
About 180.7 MB/s, half the 361.4 MB/s burst-16 figure: the read and write streams share SRAM0's single port, so each gets roughly half. Splitting the buffers across SRAM0 and SRAM1 recovers the full rate.