Worked examples — DMA controllers
This page is a drill hall. The parent note built the machinery — the registers, the state machine, the transfer modes. Here we use it, and we deliberately hunt down the awkward corner cases: the transfer that isn't a whole number of words, the zero-length request, the moment the CPU starves, the fixed-width FIFO that must not auto-increment.
Every symbol we lean on is re-anchored the first time it appears, so you can read this cold.
The scenario matrix
Think of a DMA transfer as a little machine with four dials: how much data, how wide each step, which mode (does it hog the bus or share it?), and which addresses move (do source/destination auto-increment, or stay pinned to a hardware FIFO?). The bugs and exam traps all live at the extremes of those dials.
| Cell | Case class | What makes it tricky | Hit by |
|---|---|---|---|
| A | Clean burst, everything divides | The "textbook" baseline | Ex 1 |
| B | Byte count not a multiple of width | Leftover partial word — round up? error? | Ex 2 |
| C | Zero-length transfer (BCR = 0) | Degenerate: does it finish instantly or hang? | Ex 3 |
| D | Cycle-stealing vs burst on the same job | Throughput vs CPU latency trade-off | Ex 4 |
| E | Fixed source address (peripheral FIFO) | Increment bit must be 0 or you corrupt memory | Ex 5 |
| F | Real-world word problem (SSD streaming) | Pick a mode from a throughput requirement | Ex 6 |
| G | Transparent mode, CPU heavily using bus | Limiting behaviour: effective rate → tiny | Ex 7 |
| H | Exam twist: two masters, priority scheme | Starvation under fixed vs round-robin | Ex 8 |
The eight examples below tile every cell. Keep the matrix open in your head as a checklist.
Recall The three counters we keep re-using
SAR = Source Address Register (where we read from). DAR = Destination Address Register (where we write to). BCR = Byte Count Register (bytes still to move — counts down). width = bytes moved per step (1 = byte, 2 = halfword, 4 = word). One step does: read at SAR, write at DAR, then SAR += width (if increment on), DAR += width (if on), BCR -= width. Transfer ends when BCR = 0. See Memory-Mapped IO for how the CPU pokes these registers.
Cell A — the clean baseline
- Count the transfers. .
Why this step? Each step moves
width = 4bytes, so the number of steps is total bytes ÷ 4. It is fewer than 4096 — wider steps mean fewer of them. - Add setup + per-transfer cycles. Burst time model from the parent: Why this step? In burst mode the DMA grabs the bus once (pay setup once) then fires all 1024 transfers back-to-back — the setup cost is amortised.
- Convert cycles → time. One cycle at 100 MHz is . Why this step? The bus clock sets the length of a cycle; frequency and period are reciprocals.
Verify: bytes ✓ (all data accounted for). CPU is blocked the full 10.28 μs because burst mode never releases the bus mid-transfer.

Cell B — the byte count that doesn't divide
Forecast: decide which is safer for a memory-to-memory copy before reading.
- Do the integer division. full 32-bit transfers = 4096 bytes. Why this step? Floor, not round — you can never move more bytes than requested.
- Compute the remainder. bytes left over. Why this step? This is the tail the full-word engine can't reach.
- Emit a fix-up transfer. A well-designed controller drops width to 2 (halfword) for one final step: 1 halfword = 2 bytes → BCR hits exactly 0. Why this step? A narrower final beat lands on the exact byte boundary without over-writing the next memory location.
- Total transfers = 1024 (word) + 1 (halfword) = 1025 beats.
Verify: ✓. If the controller lacks remainder support, the correct behaviour is to error out — never silently write 4100 bytes (that would clobber 2 bytes past the buffer, a classic overflow bug).
Cell C — the degenerate zero-length transfer
Forecast: many students say "it does one transfer." Watch the ordering.
- Re-read the FSM exit test. The parent's machine checks
BCR = 0after a transfer, but a correct design also checks it on entry to the transfer state. Why this step? If you only test after, a zero-length job would perform one unwanted transfer before noticing — moving a byte you never asked to move. - Entry guard fires immediately. With BCR already 0, State 2 (TRANSFER) is skipped entirely; the machine jumps IDLE → REQUEST → check → DONE. Why this step? Zero work requested = zero work done.
- Done bit + interrupt still fire. It sets
CSR.Done = 1and raises the IRQ if IE = 1. Why this step? Software waiting on completion must be woken even for an empty transfer, or it deadlocks.
Verify: transfers executed = ✓. Bytes written = 0 ✓. The machine terminates (does not hang), which is the whole point of the entry guard.
Cell D — same job, two modes
Forecast: guess the slowdown factor — 2×? 4×?
- Cycle-steal time model. DMA grabs and releases the bus every beat, so setup is paid per transfer: Why this step? No amortisation — each beat re-arbitrates. That is the price of politeness.
- Convert. .
- Slowdown factor. slower than burst. Why this step? Each beat now costs 4 cycles instead of 1 — hence ~4×.
- But: between every beat the CPU can seize the bus. Worst-case CPU wait shrinks from 1028 cycles (whole burst) to about 4 cycles (one beat). Why this step? Cycle-stealing trades throughput for latency — vital when the CPU has real-time deadlines.
Verify: ✓ and ✓. Burst wins on throughput; cycle-stealing wins on CPU responsiveness. See Bus Architecture for how request/grant lines make this hand-off possible.

Cell E — fixed address (peripheral FIFO)
Forecast: which of the two increment bits is 0, and which is 1?
- Source is a FIFO → fix SAR. Set
CSR bit 6 (Increment SAR) = 0. SAR stays0x4000_1000for all 16 reads. Why this step? Hardware re-presents a new byte each read from the same address; moving the address would read the wrong registers. - Destination is a buffer → increment DAR. Set
CSR bit 7 (Increment DAR) = 1. DAR walks0x2000_0000, 0x2000_0001, …. Why this step? Each captured byte needs its own memory slot; RAM addresses must advance. - BCR = 16, width = 1 → beats.
Verify (trace last few beats):
| Beat | read from (SAR) | write to (DAR) | BCR after |
|---|---|---|---|
| 1 | 0x40001000 | 0x20000000 | 15 |
| 2 | 0x40001000 | 0x20000001 | 14 |
| … | 0x40001000 | … | … |
| 16 | 0x40001000 | 0x2000000F | 0 |
Final DAR = 0x2000_0000 + 15 = 0x2000_000F, BCR = 0 ✓. SAR never moved ✓.
Cell F — real-world word problem
Forecast: guess the required utilisation as a percentage — under 100%?
- Peak DMA bandwidth. Bytes/beat × beats/sec: Why this step? Bandwidth = (data per event) × (events per second). This is the ceiling if DMA owns the bus 100%.
- Compare to demand. Demand = 2.0 GB/s. Since , a single 64-bit/200 MHz channel cannot keep up even at full utilisation. Why this step? Required utilisation — impossible, you can't use a bus more than 100%.
- Fix options. Widen to 128-bit ( GB/s → need ) or raise clock to 250 MHz ( GB/s → need exactly 100%, no margin). Why this step? Bandwidth scales with both width and clock; you buy headroom by increasing either.
Verify: ✓; ✓; widened and ✓. Choose 128-bit for the 37.5% safety margin — real systems avoid running a shared bus at 100%.
Cell G — transparent mode, limiting behaviour
Forecast: if the CPU hogs 90%, roughly what fraction is left for DMA?
- Free-cycle fraction. CPU busy 90% ⇒ DMA sees only of cycles. Why this step? Transparent DMA is a scavenger — it lives on leftovers, never competes.
- Effective beats/sec. beats/s.
- Effective bandwidth. . Why this step? Same bandwidth formula, throttled by the free fraction.
- Limiting case. As CPU usage → 100%, free fraction → 0, so effective rate → 0 B/s. The transfer never finishes. Why this step? This is transparent mode's fatal flaw: zero CPU interference, but throughput becomes unpredictable and can collapse to nothing.
Verify: MB/s ✓; the ✓. Compare with burst mode's guaranteed 400 MB/s (100% × 100M × 4) — transparent mode trades that guarantee for zero CPU stall.
Cell H — exam twist: two masters, priority
Forecast: which scheme starves someone, and which is "fair but slow for both"?
- Check feasibility. — the bus is oversubscribed, someone must lose. Why this step? You cannot allocate more than 100% of a shared resource; the only question is how the shortfall is distributed.
- Fixed priority. Disk always wins arbitration ⇒ Disk gets its full 60%. Net gets whatever's left: . Net is starved below its 60% need (only 40% delivered). Why this step? Daisy-chained fixed priority (parent note) never lets a lower device pre-empt a higher one — simple hardware, unfair outcome.
- Round-robin. Bus alternates grants, so each channel gets an equal share of the 100%: each. Why this step? Rotating priority (parent's rule) equalises access — both fall short of 60%, but equally short.
- Compare shortfalls. Fixed: Net misses by points, Disk fine. Round-robin: each misses by points. Why this step? Fairness spreads the pain; the worst-case channel is better off (10 vs 20 points short).
Verify: fixed ✓ (Net starved by 20); round-robin ✓ (each short by 10). See Bus Architecture and the parent's arbitration section. When both devices are real-time, round-robin's bounded fairness is why it's preferred.
Recall checkpoint
Recall
A 4098-byte transfer at 32-bit width takes how many beats (with remainder support)? ::: 1025 — 1024 word beats (4096 B) plus one halfword beat (2 B). Why must the FSM test BCR=0 on entry, not only after a transfer? ::: So a zero-length request moves zero bytes instead of one unwanted byte before noticing it's done. For reading a fixed peripheral FIFO, which increment bit is 0? ::: Increment SAR (bit 6) = 0 — the FIFO address must stay pinned; only DAR advances. Cycle-stealing was ~4× slower than burst here. Where did the 4× come from? ::: Each beat pays 3 arbitration + 1 transfer = 4 cycles instead of burst's 1 amortised cycle. A 64-bit / 200 MHz bus peaks at what bandwidth? ::: 8 B × 200e6 = 1.6 GB/s — below a 2.0 GB/s SSD, so you must widen or clock up. In transparent mode, what is the DMA rate as CPU bus usage → 100%? ::: It → 0 B/s; the DMA starves entirely.
Related depth: Cache Coherency (why DMA writes can go stale in cache), SDRAM Controllers (the memory side of these beats), ARM AMBA AXI (a real bus that implements this arbitration).