Skip to content

CPU: ARM & Thumb execution

Decode an instruction, follow registers and flags, and see how execution meets memory and timing.

7 min read Updated 28 Oct 2026

On this page

The GBA’s CPU executes the game’s program. It calculates positions, follows branches, copies data and tells the rest of the machine what to do through memory-mapped registers. GBAx reproduces that behaviour using the ARM interpreter inherited from its mGBA foundation. The game’s instructions are interpreted as operations on an emulated CPU state, rather than being executed directly by your computer’s processor.

This article follows that process from instruction bits to visible consequences. You do not need to know ARM assembly first: the worked example uses just an addition and a memory write.

The processor state GBAx has to preserve

The Game Boy Advance uses an ARM7TDMI processor. Its general-purpose registers are 32 bits wide. Registers hold temporary values, addresses and results; memory holds the larger body of game data. The program counter identifies the instruction stream, while the stack pointer and link register support stack operations and subroutine calls.

The CPU also has a current program status register, usually called CPSR. Its arithmetic flags record whether a result was negative or zero and whether an operation produced an unsigned carry or signed overflow. Other bits describe execution state and interrupt masking. Those bits are observable by the game: losing one flag can make a later conditional branch choose the wrong path.

Exceptions introduce another requirement. Some processor modes have banked registers, and saved status registers preserve the previous status. When an interrupt is taken, the emulator must switch the appropriate state and preserve a return path. Simply jumping to a handler address would leave the machine incomplete.

State Purpose Example consequence of an error
General registers Store operands, addresses and results. A position calculation or memory address becomes wrong.
Program counter and prefetched instructions Track where execution is going and which instruction bytes are already represented. A branch resumes at the wrong instruction.
CPSR flags and mode Control conditions, ARM/Thumb state and exception behaviour. A comparison takes the wrong branch or an interrupt arrives incorrectly.
Banked registers and saved status Preserve context across privileged modes and exceptions. The game cannot return correctly from its handler.

ARM and Thumb are two instruction encodings

ARM instructions occupy 32 bits. Thumb instructions occupy 16 bits, giving a more compact instruction stream. Thumb is not a separate 16-bit processor: it operates on the same underlying 32-bit register state, with a different encoding and a more restricted set of directly expressible operations.

Code density matters on the GBA because instruction fetches also use the memory bus. A shorter instruction stream can reduce cartridge fetch traffic, while code placed in fast internal RAM has different trade-offs. “ARM is always faster” or “Thumb is always slower” is therefore an unreliable description; the instruction sequence and where it executes both matter.

The core tracks the current execution mode. In arm.c, ARM and Thumb use separate step functions and instruction dispatch tables. An operation that changes execution state must update that mode and refill the instruction stream consistently. It cannot merely reinterpret the next bytes without updating the CPU’s other assumptions.

Fetch, decode and execute in the reviewed core

The ARM step function begins with the next prefetched opcode. It advances the emulated program counter and updates the prefetch entries. For a conditional ARM instruction, it checks the instruction’s condition against the current flags. An instruction whose condition fails skips its main operation, but still contributes the appropriate instruction-fetch timing.

For an instruction that will execute, selected opcode bits index a table of instruction handlers. The handler implements the arithmetic, branch, load/store or other behaviour. Thumb follows the same broad idea with its own opcode layout and handler table. This is a native-code interpreter: the handlers are compiled into the desktop application, while the game’s instructions remain data that those handlers interpret.

  1. FetchTake the next opcode from the represented instruction stream.
  2. DecodeCheck the mode, condition and instruction fields.
  3. ExecuteUpdate registers or invoke a memory/hardware operation.
  4. AccountAdd emulated cycles and check for due hardware events.
The useful conceptual stages of an instruction. This diagram describes software responsibilities; it is not a claim that each box takes exactly one hardware cycle.

A worked example: adding a value and storing it

Suppose register r0 contains 3 and r1 contains the aligned work-RAM address 0x02000000. Consider this illustrative ARM sequence:

ADDS r0, r0, #2
STR  r0, [r1]

The first instruction adds 2 to r0, producing 5. The S suffix requests a flag update. For these operands, the result is positive and nonzero, with no carry out or signed overflow. The interpreter updates both the value in r0 and the relevant CPSR flags. An ADD without that suffix would have different flag behaviour.

The second instruction takes the address from r1 and sends a 32-bit store through the emulated memory interface. Because this address belongs to external work RAM, the memory implementation writes the value there. In the GBA’s little-endian byte order, the four bytes starting at that address become 05 00 00 00.

The same store instruction aimed at an I/O register could instead change a hardware component. The CPU handler does not need a separate instruction for “start a timer”: it performs a memory write, and the address decoder routes that write to timer register handling. That connection is explained in Memory & the system bus.

Why the program counter needs special treatment

The original ARM pipeline makes the program counter visible to instructions in a way that is ahead of the instruction currently executing. In common ARM cases that means an offset of eight bytes; in Thumb it is commonly four bytes, with instruction-specific alignment rules. Code that uses PC-relative addresses relies on those details.

The reviewed interpreter maintains two prefetch entries and has explicit paths for writing the program counter. Branching changes the active instruction region and refills those entries. This matters when the target lies in a different memory region or switches ARM/Thumb state. A branch is more than assigning a new integer to a variable named PC.

Instruction prefetch and the Game Pak bus prefetch mechanism are related to fetching code, but they are not the same feature. One represents the processor’s instruction stream; the other concerns the cartridge bus and its timing. Keeping those concepts separate avoids a common source of confusion when reading emulator code.

Instruction results and instruction timing both matter

Instruction handlers account for time as well as state changes. Loads, stores, branches and multiplications do not all have the same timing, and memory accesses can have different costs depending on the region and access sequence. The CPU’s cycle counter therefore advances according to the work being modelled, not according to how many C statements the host executed.

ARMRunLoop executes until its cycle counter reaches the next scheduled event boundary, then hands control to GBA event processing. A timer may overflow, a video phase may change or a DMA operation may block the CPU. After that work is serviced, the instruction loop continues.

The timing system also passes lateness information to callbacks. That detail is important: this implementation does not claim to pause and reproduce every internal electrical phase of every instruction. It models the observable behaviour needed by software, with explicit timing rules and handling for difficult interactions. Correctness is established through hardware research, tests and reproducible cases, not by calling the interpreter “perfect.”

Interrupts and BIOS calls change the execution path

A hardware interrupt requires several conditions to line up: an interrupt source requests service, the relevant enable bits allow it, and the processor’s interrupt mask permits entry. The core then performs exception handling, including mode and register-state changes. When the game’s handler finishes, the previous execution context must be restored correctly.

A software interrupt instruction, often used for BIOS services, follows another exception/service path. Those services include operations useful to games, such as copying or decompressing data. See BIOS, boot and software interrupts for how the core supplies that behaviour.

What CPU accuracy looks like to a player

A CPU bug does not necessarily produce an immediate crash. A wrong carry flag might affect one calculation much later. A return address error can appear as a lock-up after a menu closes. A timing mismatch may only surface when a particular sound transfer overlaps a memory access. Those symptoms are why a short, reproducible test is more useful than a broad claim that a game “mostly works.”

To continue the execution path beyond the processor, read Timers, DMA & interrupts. It explains how the rest of the machine gets its turn while game code is running.

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.

Need help with a specific problem? Start with troubleshooting →