-
Notifications
You must be signed in to change notification settings - Fork 38
MIPS
- Introduction to the MIPS32®Architecture
- The MIPS32® Instruction Set
- The MIPS32® Privileged Resource Architecture
- MIPS32® Instruction Set Quick Reference
The processor is the central part of all the computer: it reads instructions from chips first, and then, it uses its hardware interface to start all peripherals and then, the OS and applications. MIPS describes a set of instructions and registers (some kind of "processor specifications" that all the processors of the same architecture follow).
Each instruction describes something to do; the instruction set is specific to MIPS.
Each instruction has a size of 32-bit and is read from the RAM. The PSP processor is able to read from 32 CPU registers, and also has special coprocessors: COP0, COP1 (FPU) and COP2 (VFPU) that I will maybe describe later; but they're used much less than the two other ones.
The processor flow is very basic: it reads an instruction, executes it, and goes to next line, except if a special instruction told him to go somewhere else, through a jump (go to address) or a branch (go to address depending on a condition).
So, you can see in your PRXTool output a set of addresses or instructions that will be executed from other modules, when they jump to specific parts: then can basically jump to the start of any exported function.
The list of "normal" CPU registers is:
- $zr - its content is always 0
- $at - assembler temporary, sometimes used to access hardware addresses or in parts written in assembly
- $v0 - normal register, used by functions to return variables
- $v1 - normal register, used by functions to return variables
- $a0 - normal register, used to pass arguments to functions
- $a1 - normal register, used to pass arguments to functions
- $a2 - normal register, used to pass arguments to functions
- $a3 - normal register, used to pass arguments to functions
- $t0 - normal register, used to pass arguments to functions
- $t1 - normal register, used to pass arguments to functions
- $t2 - normal register, used to pass arguments to functions
- $t3 - normal register, used to pass arguments to functions
- $t4 - normal register
- $t5 - normal register
- $t6 - normal register
- $t7 - normal register
- $s0 - normal register, whose content is restored after a function returns
- $s1 - normal register, whose content is restored after a function returns
- $s2 - normal register, whose content is restored after a function returns
- $s3 - normal register, whose content is restored after a function returns
- $s4 - normal register, whose content is restored after a function returns
- $s5 - normal register, whose content is restored after a function returns
- $s6 - normal register, whose content is restored after a function returns
- $s7 - normal register, whose content is restored after a function returns
- $t8 - normal register
- $t9 - normal register
- $k0 - kernel register, used by code written in assembly
- $k1 - kernel register, used by PSP to manage kernel functions access
- $gp - global pointer, rarely used
- $sp - pointer to the top of the stack, where any thread/function can store its data
- $fp - normal register, whose content is restored after a function returns
- $ra - return address, storing the address where a function has to return
There are also two special CPU registers: hi and lo, used for multiplications and divisions.
There is also pc, which contains the address of the current instruction. But it can't be accessed from the CPU; it's just used to describe jumps and branching.
All these registers are 32-bit long.
Note that instead of the classic type names int, short etc., we will use the letter 's' or 'u' followed by a number: 's' is for 'signed' values, 'u' is for unsigned values, and then '8' is for chars, '16' is for shorts, '32' is for ints, and '64' is for long long ints. In example, 's32' is 'signed int' or 'int'.
Here is a list of the most simple instructions and their C equivalent ('$regX' is the content of any register, 'num' is a constant value):
nop <=> (nothing)
- Arithmetic instructions:
addu $reg1, $reg2, $reg3 <=> $reg1 = $reg2 + $reg3
addiu $reg1, $reg2, num <=> $reg1 = $reg2 + num
subu $reg1, $reg2, $reg3 <=> $reg1 = $reg2 - $reg3
slt $reg1, $reg2, $reg3 <=> $reg1 = (s32)$reg2 < (s32)$reg3
slti $reg1, $reg2, num <=> $reg1 = (s32)$reg2 < (s32)num
sltu $reg1, $reg2, $reg3 <=> $reg1 = (u32)$reg2 < (u32)$reg3
sltiu $reg1, $reg2, num <=> $reg1 = (u32)$reg2 < (u32)num
lui $reg1, num <=> $reg1 = num << 16;
- Bitwise instructions (if you don't know what it is, check this page):
and $reg1, $reg2, $reg3 <=> $reg1 = $reg2 & $reg3
andi $reg1, $reg2, num <=> $reg1 = $reg2 & num
or $reg1, $reg2, $reg3 <=> $reg1 = $reg2 | $reg3
ori $reg1, $reg2, num <=> $reg1 = $reg2 | num
xor $reg1, $reg2, $reg3 <=> $reg1 = $reg2 ^ $reg3
xori $reg1, $reg2, num <=> $reg1 = $reg2 ^ num
nor $reg1, $reg2, $reg3 <=> $reg1 = ~($reg2 | $reg3)
- Multiplication / division:
mult $reg1, $reg2 <=> lo = (s32)$reg1 * (s32)$reg2; hi = ((s32)$reg1 * (s32)$reg2) >> 32
multu $reg1, $reg2 <=> lo = (u32)$reg1 * (u32)$reg2; hi = ((u32)$reg1 * (u32)$reg2) >> 32
div $reg1, $reg2 <=> lo = (s32)$reg1 / (s32)$reg2; hi = (s32)$reg1 % (s32)$reg2
divu $reg1, $reg2 <=> lo = (u32)$reg1 / (u32)$reg2; hi = (u32)$reg1 % (u32)$reg2
mfhi $reg1 <=> $reg1 = hi<
mthi $reg1 <=> hi = $reg1
mflo $reg1 <=> $reg1 = lo
mtlo $reg1 <=> lo = $reg1
- Shifting:
sll $reg1, $reg2, num <=> $reg1 = $reg2 << num
srl $reg1, $reg2, num <=> $reg1 = (u32)$reg2 >> num
sra $reg1, $reg2, num <=> $reg1 = (s32)$reg2 >> num
sllv $reg1, $reg2, $reg3 <=> $reg1 = $reg2 << $reg3
srlv $reg1, $reg2, $reg3 <=> $reg1 = (u32)$reg2 >> $reg3
srav $reg1, $reg2, $reg3 <=> $reg1 = (s32)$reg2 >> $reg3
- Reading from or writing to RAM:
lb $reg1, num($reg2) <=> $reg1 = *(s8*)($reg2 + num)
lbu $reg1, num($reg2) <=> $reg1 = *(u8*)($reg2 + num)
lh $reg1, num($reg2) <=> $reg1 = *(s16*)($reg2 + num)
lhu $reg1, num($reg2) <=> $reg1 = *(u16*)($reg2 + num)
lw $reg1, num($reg2) <=> $reg1 = *(s32*)($reg2 + num) OR $reg1 = *(u32*)($reg2 + num)
sb $reg1, num($reg2) <=> *(s8*)($reg2 + num) = $reg1 OR *(u8*)($reg2 + num) = $reg1
sh $reg1, num($reg2) <=> *(s16*)($reg2 + num) = $reg1 OR *(u16*)($reg2 + num) = $reg1
sw $reg1, num($reg2) <=> *(s32*)($reg2 + num) = $reg1 OR *(u32*)($reg2 + num) = $reg1
- Macros (not actual MIPS instructions but PRXTool outputs them for clarity):
li $reg1, num (from: addiu $reg1, $zr, num) <=> $reg1 = num
move $reg1, $reg2 (from: addiu $reg1, $reg2, 0 or addu $reg1, $reg2, $zr) <=> $reg1 = $reg2
First, I have to describe you the delay slot. That's it, after each jump or branch, there is a "delay" of 1 instruction: the instruction after the jump will be executed BEFORE jumping.
There four three jumping instructions: j, jal, jr and jalr.
j is used to jump to an address (in the PRXTool output, it'll be a label):
j loc_A
[next instruction]
is equivalent to:
[C equivalent of the "next instruction"]
goto loc_A
Yes, it's reversed, due to the delay slot!
jr is the same as the j instruction, the difference being that jr $reg1
jumps to the address stored in $reg1. It can be used to return from functions (jr $ra), or for C 'switch'es which were turned into a table of addresses (I'll explain that later).
jal is used to jump to a function: it will jump to an address (label like sub_... or exported function), like the j instruction, the difference being that the address where it has to return (the address of the instruction after the delay slot instruction) will be stored in the $ra register, so the called function can return to the caller function by using jr $ra
.
There is a last instruction, jalr, which acts like jal, but jumping to the address stored in a register like jr. It's used to call a function pointer.