ALAN JAHNEL / Software engineering

Writing

GBA Assembly: instruction and syntax reference

Look up ARM7TDMI instructions, registers, flags, conditions, addressing, the stack and directives, with examples and links to the series.

This article is a reference to use alongside the From hardware to a game series and the assembly Tower Defense tutorial. It explains what instructions read, what they change, and how syntax connects to the CPU and memory.

The target is the Game Boy Advance, with an ARM7TDMI CPU, ARMv4T architecture, and instructions in the ARM state, using GNU assembler. The original Game Boy and Game Boy Color use a different CPU: these commands are not a reference for those consoles. The GBA also supports Thumb, but its formats and restrictions differ from the ARM examples here.

This reference covers every instruction family and directive used in the six examples and the teaching Tower Defense. It also introduces some additional operations. Special variants and complete encoding details belong in the manuals linked at the end. Unless stated otherwise, examples use ordinary registers rather than the special behavior of instructions writing to pc.

How to use this reference

Each link below opens the relevant section. Other articles place reference links next to the concept or code that uses them. Open the reference in another tab if you want to keep your position in the tutorial.

What you found in the codeWhere to look
#, =, brackets, braces, commentsSyntax
r0 through r15, sp, lr, pc, CPSRRegisters
Hexadecimal, negative values, large constantsNumbers and immediates
mov, moveq, movneMOV
ldr, str, ldrh, strh, and ldr =nameLDR and STR
[r0], [r0, #4], [r0], #4, !Addressing
add, sub, rsb, subsADD, SUB, and RSB
mulMUL
and, orr, eor, bic, mvn, orrsBit operations
lsl, lsr, asr, rorShifts
N, Z, C, V, and suffix sFlags
cmp, cmpeq, tstCMP and TST
eq, ne, lo, hs, lt, ge, hi, leConditions
b, beq, bne, 1b, 1fB and labels
bl, blne, bx, bxne, bx lrBL and BX
push, pop, the stack, and preservationPUSH and POP
msr cpsr_c, #0xdfMRS and MSR
.equ, .space, .section, .if, .ltorgAssembler directives
MEMORY, SECTIONS, .bss, headerLinker and header
as, ld, objcopy, nm, objdumpBuild tools
BIOS calls and other familiesSWI and BIOS, other instructions

The short blocks in this article are reading and syntax examples, not complete ROMs. Symbols such as coins must be defined and laid out by the program using them. To run a complete program, use the six complete examples or the step-by-step first ROM.

Syntax

A line can contain a label, an operation, its operands, and a comment. This is a real line that could appear inside a routine:

ARM assembly
paint: strh r1, [r0], #2    @ Write a pixel and advance two bytes.
PartMeaning
paint:Names this code position; it does not execute an operation
strhInstruction name, also called a mnemonic
r1Register containing the value to store
[r0]Memory access at the address held in r0
, #2 after the bracketsUpdates the address after storing
@ ...A comment in GNU assembler ARM syntax

#20 is the immediate value 20. In ldr r0, =coins, =coins asks the assembler to load the symbol's value; it does not mean reading the variable. {r4, lr} is a register list used by operations such as push. A dot in .equ or .section marks a tool directive rather than a CPU instruction.

There is no single operand-count rule. add normally has a destination and two inputs; cmp has two inputs and no numeric destination; b takes an execution destination. Do not treat every first operand as a destination.

ARM suffixes add information: in strh, h means halfword; in subne, ne is a condition; in subs, s requests flag updates. These are different jobs. For the commands here, GNU assembler accepts forms such as addeq and addseq: the second adds under condition EQ and updates the flags.

Registers

CPU registers hold 32-bit values. Their names do not determine whether a value is a position, an address, or a color: the program gives the bits meaning.

NameRole
r0 through r12Working values and addresses
r13, or spStack pointer
r14, or lrReturn address produced by a bl call
r15, or pcRegister associated with execution flow
cpsrFlags, ARM/Thumb state, CPU mode, and interrupt control

Reading a RAM word requires placing an address in a register and using a load. An ordinary addition operates on registers; this instruction set has no add [address], #1 form that directly increments a memory variable.

In ARM state, the usual instruction read of pc reflects the pipeline and corresponds to the current instruction address plus eight bytes. A disassembly may therefore show a PC-relative load. That does not mean the program skipped two instructions. For branches and returns, use the patterns under B and labels and BL and BX.

Do not confuse CPU registers with KEYINPUT, VCOUNT, or DISPCNT: those are peripheral hardware registers, accessed through I/O addresses. r0 can hold the address of DISPCNT without becoming DISPCNT itself.

Numbers and immediates

20 and 0x14 represent the same number. 0x indicates hexadecimal, whose digits run from 0 through 9 and A through F. A byte is eight bits, a halfword 16 bits, and an ARM word 32 bits.

A word containing 0xFFFFFFFF represents 4,294,967,295 as unsigned or -1 as signed two's complement. Registers have no type label: the operation and condition determine the interpretation.

Ordinary calculations produce 32-bit results. Adding one to 0xFFFFFFFF leaves zero in the destination's 32 bits; requested flags help identify carry and overflow. Signed overflow V is not the same as unsigned carry C.

Not every 32-bit constant fits an ARM instruction's immediate field. In many data-processing operations, the immediate is formed from eight bits rotated by an even number of positions. Merely fitting in a register does not guarantee a direct encoding.

ARM assembly
mov r0, #20
ldr r1, =0x12345678

The second line uses a pseudoinstruction so the assembler can choose a suitable representation. It may emit an equivalent move or read a literal near the code. See LDR and STR and .ltorg.

MOV

mov destination, source copies the source value into the destination. The source can be another register or an immediate the tool can encode.

ARM assembly
mov r0, #20
mov r1, r0
mov r2, r1, lsl #1

At the end, r0 and r1 contain 20; r2 contains 40. The shift contributes to the value written to r2 without changing r1.

Ordinary mov neither accesses a RAM variable nor updates flags. movs updates the flags applicable to the logical operation. moveq and movne perform the move only when their condition holds. They use existing flags rather than implicitly comparing the move's source.

GNU assembler may rewrite some constants into an equivalent form such as mvn. Inspect the ELF disassembly to see what was emitted.

LDR and STR

ldr destination, [address] loads a word from memory. str source, [address] stores a word. Direction matters: str takes the source value first, with memory as the destination.

ARM assembly
ldr r0, =coins
ldr r1, [r0]
add r1, r1, #5
str r1, [r0]

The first line puts the address represented by coins into r0. The second reads the balance. The third calculates a new balance. Only the fourth changes RAM. If the balance was 20, it becomes 25.

ldr r0, =coins is an assembler pseudoinstruction. The equals sign distinguishes this request from a bracketed memory read. If the symbol represents an address, we load an address; if it represents an .equ constant, we load that constant.

InstructionsWidthLoad behavior
ldr / str32 bitsLoad or store a word
ldrh / strh16 bitsldrh fills the destination's upper bits with zeros
ldrb / strb8 bitsldrb fills the destination's upper bits with zeros
ldrsh16 bitsSign-extends to 32 bits
ldrsb8 bitsSign-extends to 32 bits

If r1 contains 0x12345678, strh r1, [r0] stores only 0x5678. There is no strsh: a narrower store discards upper bits regardless of signed interpretation.

Our RAM word accesses are aligned to four bytes and halfword accesses to two. Do not assume an unaligned access behaves like an ordinary read. The GBA region matters too: VRAM has its own restrictions, and a byte store does not behave like an isolated RAM byte store. The examples draw using strh.

The GBA is little-endian: 0x12345678 occupies bytes 78 56 34 12 in ascending addresses. That describes byte organization, not a reversed arithmetic value. The load/store forms used here do not update NZCV.

Addressing

Addresses count bytes. Incrementing a pointer by one does not automatically advance by one word or pixel.

Consider each row independently, starting with r0 = 0x02000000:

FormAddress accessedr0 afterward
str r1, [r0]0x020000000x02000000
str r1, [r0, #4]0x020000040x02000000
str r1, [r0, #4]!0x020000040x02000004
str r1, [r0], #40x020000000x02000004

! requests base-register writeback in the pre-indexed form. In the post-indexed form, after the brackets, the base updates after the access. This changes the base register, not a variable that happened to live at the old address.

For words, some forms also allow a shifted register index:

ARM assembly
ldr r2, [r0, r1, lsl #2]
strh r4, [r5], #2

The first line multiplies the index by four to select a word. The second stores a pixel and advances two bytes. Addressing forms are not identical for all widths: on ARM7TDMI, do not copy a scaled ldr index directly into an ldrh or strh form.

You use these patterns when drawing rectangles: the pointer traverses consecutive pixels, then skips the remaining distance to the next row.

ADD, SUB, and RSB

OperationCalculation
add r0, r1, r2r0 = r1 + r2
sub r0, r1, r2r0 = r1 - r2
rsb r0, r1, r2r0 = r2 - r1

rsb reverses the subtraction order. It is useful for 240 - width or negating a value with 0 - value.

ARM assembly
mov r1, #12
add r0, r1, #4
sub r2, r1, #4
rsb r3, r1, #0

The results are 16 in r0, 8 in r2, and the 32-bit representation of -12 in r3. r1 remains 12.

Without s, these operations do not update flags. adds, subs, and rsbs update NZCV. A loop can use subs counter, counter, #1 followed by bne to repeat while the result is nonzero. The condition suffix in subne does a different job: it determines whether subtraction executes.

In Tower Defense, rsblt r2, r2, #0 negates a difference only when it is negative according to the preceding comparison. For the small position values used there, this calculates an absolute value. The smallest signed 32-bit integer is a special case: its positive counterpart does not fit the same representation.

MUL

mul destination, input1, input2 multiplies two registers and keeps the product's lower 32 bits. Multiplicands cannot be immediate operands.

ARM assembly
mov r1, #80
mov r5, #240
mul r6, r1, r5

r6 becomes 19,200, the first part of locating an image row.

ARM7TDMI operand restrictions must not be replaced with rules for a newer ARM CPU. In the ARM form here, keep the destination different from the first multiplicand: mul r6, r1, r5 meets that requirement. Our examples use mul without flag updates.

For a full 64-bit product, instructions such as umull and smull exist; see additional operations. A 32-bit product overflowing does not automatically cause a game exception.

AND, ORR, EOR, BIC, and MVN

These operations work on individual bit positions. They do not interpret a value as a button, state, or color.

InstructionResultTypical use
and r0, r1, r2r1 AND r2Keep bits selected by a mask
orr r0, r1, r2r1 OR r2Combine options or bit fields
eor r0, r1, r2r1 XOR r2Toggle selected bits
bic r0, r1, r2r1 AND (NOT r2)Clear selected bits
mvn r0, r1NOT r1Invert all 32 bits

Consider just the lowest four bits:

Plain text
value = 1100     mask = 1010
AND   = 1000
OR    = 1110
XOR   = 0110
BIC   = 0100

In the button example, mvn turns the active-low convention into set bits for pressed buttons. An and with 0x03FF removes bits outside the ten controls. bic then removes buttons that were already pressed in the previous frame.

Ordinary forms do not update flags. Forms with s, such as orrs, update N and Z, may update C according to the operand shift, and preserve V. For example, orrs r0, r0, r1 lets you check whether both values were zero: only then is the result zero. Tower Defense uses this pattern when checking that a wave is over.

Shifts

A shift can be part of an instruction's second operand. ARM includes shifting circuitry associated with these operations; the source register does not need to be changed first.

OperationEffect
lsl #nShift left, inserting zeros on the right
lsr #nShift right, inserting zeros on the left
asr #nShift right, repeating the sign bit on the left
ror #nRotate: bits leaving the right reenter on the left
ARM assembly
mov r0, #3
mov r1, r0, lsl #4
add r2, r1, r0, lsl #1

r1 receives 48. r2 receives 48 + 6, or 54. r0 stays 3. For our coordinate values, lsl #4 is equivalent to multiplying by 16, provided the result fits the available width.

For unsigned values, lsr can represent division by a power of two with the remainder discarded. For negatives, asr preserves the sign and does not always match division in a language that rounds toward zero. rrx is a one-bit rotation involving flag C.

Amounts zero and 32 have special encoding and behavior. Introductory examples use small positive shifts; check the instruction and format in the manual for other amounts.

Flags and the S suffix

The main flags live in CPSR:

FlagMeaning
NResult bit 31 is one
ZResult is zero
CCarry for addition or no borrow for subtraction; in logical operations it may come from the shift
VArithmetic result does not fit the signed 32-bit range

N alone does not mean a signed comparison is “less than”: overflow must also be considered. That is why LT tests N != V.

ARM assembly
mov r0, #0
subs r0, r0, #1

After subtraction, r0 = 0xFFFFFFFF, N is one, Z is zero, C is zero because borrowing occurred, and V is zero because signed -1 fits.

cmp and tst update flags without requiring an explicit final s. sub and subs, however, are different forms. Ordinary ldr, str, and mov do not automatically erase a previous comparison result.

A condition always observes the most recent flags, not necessarily the nearest comparison you see in the text. An instruction with s between cmp and bne can change the branch's meaning. A function call may also change flags; do not rely on them across a call without an explicit convention.

CMP and TST

cmp a, b updates NZCV as if calculating a - b but discards the numeric result. Both operands stay unchanged.

ARM assembly
cmp r1, #20
blo sem_dinheiro
sub r1, r1, #20

If r1 is below 20 unsigned, execution goes to sem_dinheiro. Otherwise it continues and subtracts the cost. Comparison alone does not implement an if block: a branch or conditional instruction uses the result.

tst a, mask updates logical flags as if performing AND without saving the AND result. We mainly use Z to detect whether any selected bit was set:

ARM assembly
tst r1, #16
bne direita_pressionada

This checks bit 4. With a mask containing multiple set bits, a nonzero result means at least one selected bit is set, not necessarily all of them. To test all, store an AND result and compare it to the mask.

cmpeq is conditional comparison. This pattern checks whether both coordinates match a point:

ARM assembly
cmp r0, #6
cmpeq r1, #2
bne fora_do_ponto

If X is not 6, the second comparison does not execute and the first unequal result remains. If X is 6, the second comparison decides whether Y is 2. The branch is therefore taken if either coordinate differs.

Conditions

In ARM state, the instructions here can take condition codes. The same condition can appear in bne, movne, strne, or bxne: the instruction's effect occurs only when flags satisfy it.

SuffixFlag testMeaning after cmp a, b
eqZ = 1Equal
neZ = 0Not equal
cs / hsC = 1a >= b, unsigned
cc / loC = 0a < b, unsigned
hiC = 1 and Z = 0a > b, unsigned
lsC = 0 or Z = 1a <= b, unsigned
geN = Va >= b, signed
ltN != Va < b, signed
gtZ = 0 and N = Va > b, signed
leZ = 1 or N != Va <= b, signed
mi / plN = 1 / N = 0Sign bit one / zero
vs / vcV = 1 / V = 0Signed overflow occurred / did not occur
alAlwaysUsual form without a condition suffix

For r0 = 0xFFFFFFFF and cmp r0, #0, bhi would be taken because the unsigned value is greater, and blt would also be taken because the signed value is -1. There is no contradiction: the same bits have different interpretations.

Movement examples use signed conditions for temporarily negative coordinates. Some Tower Defense bounds checks instead use an unsigned comparison that also rejects -1, since it appears enormous under that interpretation.

A false condition prevents the instruction's effect but does not imply zero execution cost. Do not estimate a frame's timing by counting only commands that change values.

B and labels

b destination transfers execution to the label without creating a return address in lr. A conditional form such as bne branches only when its condition holds. Without a taken branch, execution continues at the next instruction.

ARM assembly
mov r2, #3
repeat:
    subs r2, r2, #1
    bne repeat

The decrement occurs three times. The program recognizes no special loop structure: it recognizes subtraction, flags, and execution-flow changes.

GNU assembler also offers reusable numeric labels:

ARM assembly
    b 1f
1:  mov r0, #3
2:  subs r0, r0, #1
    bne 2b

1f finds the next 1: forward; 2b finds the most recent 2: backward. The b in 2b means backward, not button B. Tools resolve those names before execution.

A named label does not automatically create function scope. Without a branch or return, execution may continue through whatever follows the label. See GNU assembler's local-label rules.

BL and BX

bl routine branches and puts the address for continuing after the call in lr. In the ARM state used here, that return points to the next instruction after bl. Routines commonly return using bx lr.

ARM assembly
double_value:
    add r0, r0, r0
    bx lr

This doubles r0 and returns to the caller. No function object protects registers: the contract must specify inputs, outputs, and what may change.

bx register jumps to the address in the register and may change ARM/Thumb state according to destination bit 0. It does not create a return in lr. Code addresses must satisfy the selected state's alignment requirements.

A routine making another bl call overwrites its own lr. To return to its original caller, it must preserve that value, usually on the stack. In contrast, b fill_rect at the end of paint_hero forwards execution without replacing lr: fill_rect returns directly to the caller of paint_hero.

blne new_game is a conditional call. bxne lr is a conditional early return. Neither implicitly compares values; both use existing flags.

PUSH and POP

The stack occupies RAM and is controlled by sp. In our examples it grows toward lower addresses. push reserves space and stores registers; pop reads them back and releases that space.

ARM assembly
outer:
    push {r4, lr}
    mov r4, #20
    bl inner
    pop {r4, lr}
    bx lr

If sp was 0x03007F00, pushing two registers moves it to 0x03007EF8. The list occupies eight bytes, four per register. In ascending addresses, the saved values are r4, then lr. Register numbers determine the order, not the textual idea of copying a list backward.

On ARM these forms use multiple-store/load operations equivalent to stmdb sp! and ldmia sp!. {r5-r8, lr} means five registers and therefore 20 bytes.

Every return path must restore anything pushed. Returning before pop leaves the stack unbalanced. The list must also preserve the registers required by the routine's contract, not just lr.

The examples use local conventions, including color in r4. This is not the standard contract for an arbitrary C function. When mixing languages, respect the chosen ABI, preserved registers, and AAPCS eight-byte stack alignment at public interfaces. Do not assume every internal call here was written for that integration.

MRS and MSR

mrs reads a status register into an ordinary register. msr writes selected status fields. They operate on CPU state, unlike a memory-addressed video-register write.

ARM assembly
mrs r0, cpsr
msr cpsr_c, #0xdf

In example startup, msr cpsr_c, #0xdf sets the control field: System mode, ARM state, and masked IRQ/FIQ. The lowest five bits of 0xDF are 11111, the System-mode code; I and F are one, and T is zero.

_c selects the control field rather than NZCV. Writing it requires the appropriate privilege context. These programs poll and install no interrupt handlers; copying this startup into an IRQ-dependent program may stop it working.

Do not use msr to casually alternate between ARM and Thumb. State changes use the appropriate mechanisms such as bx. The instruction above is initial configuration while already in ARM state.

Assembler directives

Directives guide GNU assembler. Some select syntax; others define symbols or place data into sections. They are not CPU operations like additions or loads.

DirectiveJob in this project
.syntax unifiedSelect GNU assembler's unified syntax
.cpu arm7tdmiSelect the target and restrict available instructions
.armEmit ARM-state code; it does not switch CPU state at runtime by itself
.equ NAME, valueDefine a value symbol without reserving a RAM variable
.section name, "ax"Select a section; a means allocation and x executable code
.global nameMake a symbol visible for linking and other tools
.space nReserve n bytes, interpreted according to section and linking
.balign nAdvance to alignment on a multiple of n bytes
.word valueEmit 32-bit data
.hword valueEmit 16-bit data
.byte valueEmit eight-bit data
.ltorgEmit the section's pending literal pool at this point
.if expression / .endifInclude code based on an assembly-time expression
.ifndef NAMEInclude a block if the symbol is not defined
.error "message"Fail assembly with an error

A hardware constant and a variable play different roles:

ARM assembly
.equ KEYINPUT, 0x04000130
.section .bss
.balign 4
.global coins
coins: .space 4

KEYINPUT names an address fixed by hardware. coins marks four bytes the linker will place in RAM. The project uses NOLOAD BSS; reserving that space does not make the CPU zero it. Startup must clear the variables.

.ltorg matters when ldr =value depends on a reachable literal near the code. Literals are data: do not let execution fall directly into them. In the examples, a return or branch ends the execution path before the emitted area.

Assembly-time conditions differ from game conditions. .if LESSON >= 2 selects what enters the file before the ROM exists. cmp followed by bne decides at runtime. Consult ARM directives and the general assembler manual for other forms.

Linker and header

The assembler creates instructions, data, and references. The linker organizes them and resolves addresses between sections and files.

gba.ld itemMeaning
OUTPUT_FORMAT / OUTPUT_ARCHOutput ELF format and architecture
ENTRY(_start)Entry point declared in ELF
MEMORY, ORIGIN, LENGTHAvailable regions and sizes
SECTIONSRules placing input sections
KEEP(*(.header))Preserve our reserved header section
.text / .rodataInstructions and constants arranged in ROM
.bss (NOLOAD)State space without corresponding initial ROM bytes
ALIGN(4)Align the position to four bytes
__bss_start, __bss_endBounds used by the clearing loop
ASSERTFail linking when a layout condition is not met

The linker script's dot, ., represents its layout position. It is not the same as the dot beginning an assembler directive, despite their similar notation.

The header is a GBA cartridge-format requirement. We reserve bytes at the ROM's beginning, place a branch instruction at entry, and use rom.py to fill fields and checksum. ENTRY(_start) alone does not make the console search for a textual symbol: the binary presents bytes and instructions at specific positions.

.header is a project naming convention; the linker places it correctly. Header Python runs on the development computer. No linker or Python command automatically becomes a GBA CPU instruction. GNU ld's SECTIONS documentation details the layout.

Build tools

These are terminal commands, not assembly-language instructions:

CommandJob
arm-none-eabi-asAssemble .s text into an .o object
arm-none-eabi-ldLink objects and organize ELF addresses
arm-none-eabi-objcopy -O binaryExtract ROM binary contents
python3 rom.py file.gbaFill and verify the example's header
arm-none-eabi-nm -n file.elfList symbols in address order
arm-none-eabi-objdump -d file.elfDisassemble instructions
arm-none-eabi-objdump -h file.elfShow sections

-mcpu=arm7tdmi selects the target. -g includes debugging information. -o chooses the output filename. -T gba.ld selects the linker script. In Tower Defense, --defsym LESSON=2 defines an assembly-time symbol to select a stage.

The .elf preserves information useful for inspecting addresses and names. The .gba holds the cartridge opened in the emulator. Changing .s without rebuilding does not change an old ROM on disk.

SWI and BIOS

swi causes a software exception. GBA BIOS services can be called through that mechanism, following the appropriate service identifier, argument convention, and ARM or Thumb state. It is not a personal computer operating-system call.

None of the six examples depends on swi: video waits poll VCOUNT. A BIOS service waiting on interrupts requires the relevant configuration to be prepared. Do not replace polling with a BIOS call without checking those requirements.

Begin BIOS experiments with a small program, a documented function convention, and an observable outcome. The instruction reference explains how an exception is requested; platform documentation explains the service handling it.

Other ARM7TDMI instructions

These families complement the tutorial instructions. This table is a guide for later study, not a recommendation to add all of them to your first game.

FamilyJob
adcAdd operands and the current carry
sbcSubtract the operand and the borrow represented by 1 - C
rscReverse subtraction with the same borrow treatment
cmnSet flags as for addition, discarding the result
teqSet logical flags as for XOR, discarding the result
mlaMultiply and add an accumulator, producing 32 bits
umull / smullUnsigned / signed long multiplication into two result registers
umlal / smlalLong multiplication with 64-bit accumulation
ldm / stmLoad / store a register list, with ordering and writeback variants
swp / swpbSwap a register and memory value, as a word / byte

ARM7TDMI has no sdiv, udiv, movw, movt, blx, or later architectures' it blocks. Division needs a routine, an appropriate BIOS service, or a mathematically valid transformation. Calling across ARM and Thumb on this CPU requires ARMv4T interworking patterns.

ARMv4T also lacks a dedicated instruction with the modern NOP encoding. The assembler may accept nop and emit an equivalent operation, such as moving a register into itself. Checking the target and disassembly distinguishes accepted tool syntax from new CPU capabilities.

A worked example

Read this fragment assuming coins is defined in RAM and contains 20:

ARM assembly
spend_once:
    ldr r0, =coins
    ldr r1, [r0]
    cmp r1, #5
    bxlo lr
    sub r1, r1, #5
    str r1, [r0]
    bx lr
StepWhat changes
Load =coinsr0 receives an address; the balance has not been read
Read [r0]r1 receives 20
Compare with 5Flags describe subtraction; r1 remains 20
Conditional returnDoes not return because 20 is not below 5 unsigned
Subtractr1 becomes 15; RAM still holds 20
StoreRAM now holds 15
ReturnExecution continues through the address in lr

With an initial balance of 3, the routine would return before subtracting. It preserves neither r0, r1, nor flags, and reports no separate success value: those choices are part of this fragment's contract.

This exercise combines the series' essential distinction: a constant may represent an address, a register may hold that address, and a bracketed instruction accesses memory contents. At an I/O address, the same operation interacts with hardware.

Common mistakes

Symptom or misunderstandingWhat to check
“I changed r1, but the balance did not change”Did you forget str?
“ldr =coins gave me a huge number”You loaded an address, not the balance
“bne uses the wrong comparison”Did an instruction or call change the flags?
“-1 seems greater than zero”Was the comparison unsigned?
“The next pixel appears in the wrong place”Did the address advance two bytes, and was the row boundary handled?
“The function returns to the wrong place”Did a nested call overwrite lr?
“Restart repeats while I hold Start”Is the rule using current state instead of a new press?
“.space 4 did not start at zero”Reserving space does not initialize RAM at runtime
“An instruction from the internet will not assemble”Does it belong to ARM7TDMI and the selected ARM state?
“It assembled but does not run correctly”Check layout, entry, header, CPU state, and hardware accesses as well as syntax

Official references

The ARM7TDMI Technical Reference Manual, DDI 0210C describes the CPU model and summarizes instructions. Use the ARM-instruction and condition sections while respecting the target architecture. Newer ARM reference cards can contain operations absent from the GBA.

The GNU assembler ARM manual defines the tool syntax used here. Its directive and symbol and local-label pages help interpret program text. The GNU ld script manual covers linking.

To continue with runnable experiments, return to the nine-chapter index. To apply these operations in a round, follow the main Tower Defense tutorial.

Back to home