Game Boy Advance DMA: Channel Priority, Trigger Timing, and Sound FIFOs
Configure GBA DMA with correct channel priority, immediate and video triggers, address modes, sound FIFO bursts, and measurable emulator tests.
The Game Boy Advance exposes four general-purpose DMA channels, but they are not four identical background copy loops. Channel number establishes priority, start timing selects when a request is eligible, address modes control how each transfer advances, and special modes connect DMA to sound FIFO or video capture behavior. A transfer also competes with the CPU for the system bus. Treating DMA as an instant memcpy produces plausible results in simple programs and fails when a game uses HBlank effects, streams audio, or arms more than one channel.
The most useful model is a triggered bus master. A channel is configured while disabled, becomes active after the enable bit is written, waits for its selected request condition, arbitrates with other requests, transfers a defined number of units, and then either disables or remains armed according to its mode. This article describes that control path and the constraints that matter to emulator authors and homebrew developers.
Four channels with fixed ordering
DMA0 has the highest priority, followed by DMA1, DMA2, and DMA3. If multiple eligible requests compete, lower-priority channels wait until the higher-priority transfer completes. DMA also suspends CPU execution while the bus transfer is active. The result is not merely a lower CPU throughput: code observing display state or relying on cycle deadlines sees the stall at a specific point in its instruction stream.
The channels have different practical specialties. DMA0 is often chosen for timing-sensitive memory-to-video work. DMA1 and DMA2 can serve the two direct-sound FIFOs in special timing mode. DMA3 can access Game Pak ROM/flash in ways other channels cannot; no DMA channel should be assumed to access Game Pak SRAM as ordinary memory. These are capability and routing constraints, not just recommendations from style guides.
GBATEK is a community-maintained technical reference rather than a Nintendo manual, and mGBA’s DMA code is a useful independent implementation reference. For each claim, keep the hardware rule separate from a core’s default behavior or a developer convention. A particular library may reserve channels, install handlers, or impose alignment that the DMA controller itself does not.
Programming the transfer contract
Each channel has source, destination, count/control registers. Software should set source and destination addresses, transfer width, start timing, repeat behavior, and address control while the channel is disabled, then enable it last. This avoids starting a transfer with a half-configured register set. For immediate mode, enabling the channel requests a transfer as soon as the controller accepts it; VBlank and HBlank modes wait for their display event, and special mode is assigned to hardware-specific request sources.
The transfer size is expressed in halfwords or words depending on the width bit. Count semantics vary by channel and mode, and a zero count can represent a channel-specific maximum rather than “transfer nothing.” Do not copy a literal from one channel’s example into another without checking its documented count width and trigger behavior. Address control can increment, decrement, remain fixed, or use the special reload behavior for a repeated destination. Source and destination alignment also matter: the address behavior is defined at the selected transfer width, not by the host CPU’s memcpy semantics.
The following C-like setup is explanatory pseudocode; register definitions and volatile access must come from the target SDK header:
/* Configure while disabled; exact fields depend on SDK definitions. */
DMAx_SRC = source;
DMAx_DST = destination;
DMAx_COUNT = unit_count;
DMAx_CONTROL = width | source_mode | destination_mode | trigger_mode;
DMAx_CONTROL |= DMA_ENABLE; /* arm only after all other fields */
This ordering is particularly important for HBlank. If software writes the enable bit after the target event has already passed, it should not assume the transfer happened retroactively. Establish when the channel becomes armed relative to the display event, and test that boundary.
Immediate, VBlank, and HBlank requests
Immediate DMA is useful for copies that must complete before the next CPU-visible operation. It is also the easiest way to freeze the CPU unexpectedly: large copies can occupy thousands of cycles. Keep the transfer count bounded and avoid assuming that an interrupt handler continues executing while DMA owns the bus.
VBlank DMA is commonly used to update palette RAM, OAM, or video memory when the display is in a safe blanking interval. HBlank DMA repeats work at a scanline boundary and enables raster effects. These modes are not equivalent to running a loop from a VBlank or HBlank interrupt handler. DMA reduces CPU instruction overhead and has hardware-defined arbitration timing; a software loop has branch, interrupt-entry, and bus access costs.
Video memory regions impose access-window restrictions while the display is active. In particular, a transfer to VRAM/OAM/palette memory needs to respect the relevant blanking or forced-blank conditions. Do not generalize this to “all DMA may run only during blank”: transfers among other address spaces and sound requests have different rules. The destination and trigger must be considered together.
Repeated HBlank transfers remain armed for future eligible lines when repeat is enabled. When repeat is disabled, completion clears the enable state. For repeated destinations, a reload mode may restore the destination address for each trigger. A raster effect that depends on changing one palette entry per line should therefore specify a fixed per-line source/destination contract and not assume that the controller implicitly advances both pointers in the same way.
Sound FIFO DMA is a special protocol
Direct Sound A and B are fed through FIFO registers. DMA1 or DMA2 can be configured in sound-FIFO timing mode, with repeat enabled and the destination fixed to the selected FIFO. On a sound request, hardware transfers a four-word burst; the programmable word count and ordinary destination increment settings do not act like a generic DMA copy in this mode. The timer and sound routing select playback cadence, while FIFO request timing determines when the next burst is needed.
This is a deadline system. A higher-priority DMA request can delay sound DMA. A sufficiently long DMA0 operation can starve the FIFO long enough to produce an underrun or audible discontinuity. A useful test records the timer overflow, FIFO request, start/end of each DMA burst, bytes remaining, and output sample timeline. Merely checking that a sound buffer is nonempty at frame end can miss a short underrun in the middle.
Use DMA1 for FIFO A and DMA2 for FIFO B only when your software follows the hardware’s allocation rules and does not reconfigure those channels for another purpose during playback. Initialize the timers, sound control routing, FIFO state, and DMA registers in a known order. When stopping a stream, disable the request source and disarm DMA deliberately; do not leave a repeated transfer armed against a FIFO that is no longer being consumed.
Video capture and bus access
DMA3 supports a video-capture mode with its own start/stop behavior. It should not be confused with ordinary HBlank DMA even though both are related to display timing. Capture has a defined active interval and per-line transfer pattern. Implementations should validate the documented VCOUNT range and the capture enable lifecycle rather than approximating it as a full-frame copy.
Game Pak access timing includes wait states and sequential/nonsequential differences. A DMA transfer from ROM is not a flat-cost operation: the first access and later sequential accesses can have different timing, and crossing a region/boundary may alter the cost. DMA timing also depends on source/destination bus regions and transfer width. Emulators that need CPU-visible accuracy should use the memory bus timing tables rather than a global “two cycles per unit” constant.
Debugging and acceptance tests
Test one trigger and one address mode at a time:
- Configure immediate DMA with a small, aligned internal-RAM buffer; verify exact copied units, final addresses, CPU stall length, and enable-bit clearing.
- Arm VBlank DMA just before and just after VBlank entry. Check whether the transfer begins at the next eligible event and verify display-memory results.
- Use HBlank repeat to write a different palette value each line. Capture the native-resolution frame and log each trigger.
- Start competing DMA channels simultaneously and confirm channel priority, suspension, and completion order.
- Configure Sound FIFO A/B DMA with timer requests; run while another channel requests the bus and measure underruns.
- Verify channel-specific count-zero behavior and document limits without assuming identical count register widths.
- Attempt source/destination accesses in the Game Pak ROM, SRAM, EWRAM, IWRAM, palette, VRAM, and OAM regions permitted by the documented controller.
- Save and restore state while a request is pending, while a transfer is active if supported, and between repeated HBlank triggers.
For every result, record BIOS/firmware assumptions, region timing, CPU clock mode, memory wait states, channel registers, trigger event, and transfer width. An “it copied the data” test is not enough when the visible defect is a line-shifted raster effect or intermittent audio crackle.
Emulator architecture recommendations
Maintain one DMA arbiter per emulated GBA, not one host task per DMA channel. Requests should be queued with priority, source/destination bus state, transfer unit, and trigger metadata. DMA reads and writes must pass through memory-region handlers so side effects and wait-state costs remain consistent with CPU access. The CPU runner should account for suspension in its timeline and resume at the correct boundary.
Use separate implementations for generic transfers, sound-FIFO requests, and video-capture mode while sharing a common bus-transfer primitive. The special cases have different count, destination, and repeat semantics; forcing them into one universal loop tends to create conditionals that silently diverge. Serialize active channel state, pending trigger, latched source/destination, and the arbiter’s current owner so save states resume deterministically.
The GBA DMA controller is best understood as a small scheduling system. The transfer registers define a data path, trigger logic defines eligibility, priority resolves simultaneous requests, and the bus determines how long other components wait. Once those pieces are observable in logs, timing defects can be corrected at their cause rather than hidden with frame pacing or audio buffering.
Related:
- Game Boy OAM DMA: Copy Timing, Bus Conflicts, and Emulator Accuracy
- Sprite Limits and Scanlines: Why Accurate Emulators Preserve Flicker and Dropout
Sources: