32 general registers, of which Nintendo has given a naming convention.
- R0 = always zero. Any attempts to modify this register silently fail.
- T0-T9 = scratch registers. CPU RAM.
- S0-S7 = registers saved upon function protocol. Trash at will if you know how.
- A0-A3 = parameter passing to subroutines. Formal but not rigid.
- RA = return address from subroutine. Not pulled from 'stack'. Change at convenience.
- V0-V1 = arithmetic values, function return values.
- SP = stack pointer. Informal.
- AT = assembler temporary. Free use.
These are formal definitions but not strictly enforced, save for R0 which is hardwired.
Instructions are WORD sized (32-bits).
In addition to the CPU, there are three other coprocessors (COPs).
- COP0 = memory management unit (MMU). Better known as 'virtual memory'.
- COP1 = floating-point unit (FPU).
- COP2 = video coprocessor (RCP).
When performing branches, a 1-cycle delay is incurred before it is executed. To optimize CPU time, the next instruction following a branch is executed during the delay. This means a branch instruction such as beq r0,r0,8006D234h would also execute the instruction following it. There is a limit on which opcodes can be placed in the delay slot.
Note that 'beq r0,r0,TARGET' is effectively 'bra TARGET'.
The following machine code is found in Mario Golf.
[1120:0027] 800B0130: BEQ t1[800FBBD0],r0,800B01D0h [0000:0000] 800B0134: NOP
NOP (no operation) is executed before the next instruction is fetched from memory.
[0c02:c0d7] 800B01B4: JAL 800B035C [0120:2021] 800B01B8: ADDU a0,t1[800FBBD0],r0
An ADDU (add unsigned) is executed as the program counter is set to address 800B035C.
For our hobbyist purposes, it is much safer to always inefficiently waste a 'NOP'(no operation) in the slot, and optimize later if necessary.
The N64 CPU has an interesting quirk in how it handles addition:
LI $A0, 8013 ;A0 = 0x80130000 ADDIU $A0, $A0, FFFF ;A0 = 0x8012FFFF
Essentially when adding a value greater than 0x7FFF, even with the unsigned addition instructions, the effect is as if the value were negative. This also affects relative addressing, i.e.:
LI $A0, 8013 ;A0 = 0x80130000 LW $A1, FFFF($A0) ;load word from 0x8012FFFF