N64 Programming/CPU overview
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.
- 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. 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.
Care is required when using the unsigned family of MIPS instructions as the signedness refers to only whether the instruction will generate a trap on overflow. MIPS will sign-extend an operand regardless of whether ADDI or ADDIU is used:
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, e.g.:
LI $A0, 8013 ;A0 = 0x80130000 LW $A1, FFFF($A0) ;load word from 0x8012FFFF