Skip to content

Timers, DMA & interrupts

See how scheduled events, bus transfers and interrupt requests coordinate the machine.

6 min read Updated 28 Oct 2026

On this page

The CPU is not the only component that makes progress while a game runs. Timers overflow, the display enters blanking intervals and data transfers move graphics or sound samples. Interrupts give the game a way to respond. GBAx coordinates those events through the timing system inherited from mGBA, so each component observes a consistent sequence of changes.

This is where apparently unrelated problems can meet. A graphics effect may depend on HBlank DMA; music may depend on a timer refilling a sound FIFO; a game waiting for an interrupt can freeze if the event is never delivered.

Timers turn elapsed cycles into events

The GBA has four 16-bit timers. A running timer counts toward overflow, then reloads according to its configuration. A prescaler can make it advance more slowly than the system clock. Timers other than the first can also cascade, advancing from another timer’s overflow instead of directly from the clock.

An emulator does not need to increment a host variable once for every emulated clock tick. The reviewed timer code can calculate the elapsed count from the timing system, update the visible register and schedule the next overflow. This preserves the required result while avoiding millions of unnecessary host operations.

Writing the timer’s control register can enable it, change its prescaler or change whether it cascades. Those actions affect the next scheduled event. Register writes therefore update both the stored control value and the timer’s operating state.

A worked timer example

For a simple illustrative case, take a non-cascaded timer using a divisor of 1 and a reload value of 0xFF00. There are 256 increments from that value to the 16-bit wrap point. At a 16,777,216 Hz system clock, repeated overflows would occur at 65,536 Hz under those simplified conditions.

The useful relationship is:

overflow interval in clock cycles
    = (65536 - reload value) × prescaler

Real register writes and enable transitions also have timing rules, and cascaded timers follow a different input. The formula describes the steady interval for this basic case; it is not a replacement for the implementation’s handling of a timer being reconfigured partway through execution.

When an overflow is serviced, the core can request a timer interrupt, advance the next cascaded timer and consume a Direct Sound sample if that audio channel uses the timer. One event can therefore have several consequences.

DMA moves data without a CPU copy loop

Direct Memory Access lets a game configure a source address, destination address, transfer count and control bits, then have hardware perform the transfers. The GBA has four DMA channels. They have priorities and channel-specific restrictions; they are not four independent transfers that all use the bus simultaneously.

Transfers can start immediately or in response to an event such as HBlank, VBlank or an audio FIFO request, depending on the channel and configuration. Source and destination addresses can advance, remain fixed or use other supported addressing modes. A fixed destination is useful when repeatedly writing sample data into a sound FIFO.

In the reviewed DMA code, each active channel keeps its current source, destination and remaining count. The transfer path reads and writes through the emulated memory system, updates addresses, accounts for access timing and schedules further work. CPU execution is marked blocked while DMA occupies the relevant execution interval.

Why DMA must cost time

A naive emulator could copy the whole block with a host memcpy and continue the CPU immediately. The bytes might be correct, but the rest of the machine would see the wrong timeline. The game could execute instructions during time that real hardware would have spent transferring data.

Transfer width, memory regions and sequential/non-sequential access costs affect how long the work takes. Other DMA channels may compete for service. Completion can request an interrupt or prepare a repeating channel for its next trigger. These behaviours make DMA part of the machine model, not merely a performance optimisation for copying bytes.

  1. ConfigureThe game sets source, destination, count and trigger.
  2. TriggerAn immediate request or hardware event makes the channel ready.
  3. TransferThe bus moves data while CPU execution is blocked as required.
  4. CompleteThe channel updates state and may request an interrupt.
The lifetime of a DMA operation. Repeating modes and channel priority add further decisions around this basic sequence.

HBlank DMA: changing a picture one line at a time

A game can prepare a table of background parameters and arrange for DMA to copy the next value at a horizontal-blank event. Each following scanline then uses a different value. This can create a wavy background, a perspective-like floor or another raster effect without the CPU manually copying the value for every line.

For that effect to look right, the display event, DMA transfer and register update must occur in the intended order. Moving every transfer to the end of the frame would preserve the final register value while losing the image the game meant to create. This is a practical reason to use a shared event timeline.

An interrupt request is not immediate execution

Hardware raises an interrupt request by setting the appropriate pending state. Whether the CPU takes an IRQ also depends on enabled sources, the master interrupt enable and the processor’s interrupt mask. A pending request can remain pending until the relevant conditions allow service.

The core’s GBA IRQ handling checks those conditions and enters the ARM exception path when appropriate. That path preserves the required processor context, selects the interrupt mode and redirects execution. The game’s handler acknowledges the event and eventually returns. A correct pending flag with an incorrect exception return is still broken emulation.

CPU halt adds another interaction. A game can stop normal instruction execution while waiting for work. The emulator should continue advancing toward events that can change that state; it cannot freeze the entire machine just because the CPU is not currently executing instructions.

How the event scheduler keeps order

The shared timing code stores events with scheduled times and priorities. The CPU loop runs toward the next event boundary, then GBA event processing dispatches due callbacks. If an instruction boundary causes a callback to be serviced after its nominal time, the callback receives lateness information and can use it when scheduling subsequent work.

This makes the schedule explicit, but it does not remove the need to test edge cases. Events at similar times, overlapping bus activity and unusual register writes can reveal incorrect assumptions. mGBA’s DMA research documents cases where real hardware behaviour was more complicated than a simplified description suggested.

Read Audio: sound channels & timing to follow timer-driven FIFO playback, or Video: backgrounds, sprites & scanlines to see how display events consume this timing machinery.

Sources & implementation

Implementation references follow GBAx 2.0.0 Preview 1, commit c1a826ae. The hardware implementation derives from mGBA; these links point to the matching GBAx source, rather than assuming a newer upstream revision behaves identically.

Further reading: mGBA development: investigating timing-sensitive bugs.

Need help with a specific problem? Start with troubleshooting →