Registers are the variables used by their respective processor/co-processor. MIPS has more registers, and less addressing modes in comparison to a lot of other instruction sets. A plus to this is that it operates more efficiently with a slower memory bus.
Every register holds a value of 32-bits (aligned) (XX XX YY YY) which can be sign extended (to hold larger values). Luckily, having a static length gives it a simplified memory model for editing purposes. The following is an arithmetic instruction; the opcode (ADD)'s usage as an example:
| ADD | ADD word | |-----------|---------------------------------------------------| | 000000 | rs | rt | rd | 00000 |100000 (32)| ------6----------5---------5---------5---------5----------6------ Format: ADD rd, rs, rt Purpose: To add 32-bit integers. If overflow occurs, then trap. Comment: ADD rd, r0, rs is equal to a MOVE rd, rs Descrip: rd = rs + rt
Brief rundown of register instruction format for CPU, COP0 and COP1
Taken directly from: https://github.com/mikeryan/n64dev/blob/master/docs/n64ops/n64ops%23a.txt
|rs||5-bit||source register specifier|
|rt||5-bit||target (source/destination) register or branch condition|
|rd||5-bit||5-bit destination register specifier|
|fs||5-bit||floating point source register specifier|
|ft||5-bit||floating point target (source/destination)|
|fd||5-bit||floating point destination register specifier|
|offset||16-bit||branch displacement or address displacement|
|target||26-bit||jump target address|
As seen below, each processor has 31 registers (CPU has 32). COP0 (MMU) converts virtual addresses into physical addresses, selects an operating mode and control exceptions, while COP1 (FPU) is for floating point operations, and COP2 (RCP), or the "Reality Co-Processor" which is composed of the RSP (signal) and RDP (drawing) and together acts as a polygon cruncher.
CPU General Purpose Registers
|0||zero, r0||Is a hardwired zero value and cannot be changed|
|1||at||Assembler Temporary: Reserved by assemblers (Pseudo instructions)|
|2-3||$v0 - $v1||Value: Subroutine return value|
|4-7||$a0 - $a3||Arguments: First four parameters for a subroutine|
|8-15||$t0 - $t7||Temporary registers (Accessible to a subroutine without saving)|
|16-23||$s0 - $s7||Saved registers*: preserved across function calls (Must be saved and restored by the subroutine)|
|24-25||$t8 - $t9||Temporary registers (Subroutines can use without saving, but is not kept across procedure calls)|
|26-27||$k0 - $k1||Kernel: reserved for kernel operations, most importantly thread context switching. Do NOT use $K0 or $K1 outside kernel code|
|29||$sp||Stack Pointer* (points to the last location on the stack)|
|30||$fp/s8||Frame Pointer* or Saved value*|
|31||$ra||Return Address for a subroutine|
|32||Hi/Lo||Special registers that aren't directly accessible (used to store result of multiplication and division)|
Note: However, you can access the contents of Hi and Lo with special instruction MFHI ("move from Hi") and MFLO ("move from Lo"). Another Note: The stack is a place in memory where the game backs up registers temporarily while doing other things. Larger addresses being stored at the bottom of the stack, smaller at the top of the stack (otherwise known as "stack limit") The stack pointer is usually a register that contains the top of the stack.
Unless you're writing kernel code, the $K0 and $K1 registers should never be used in custom mods. The reason for this is that when an interrupt occurs (and hardware interrupts can occur at any time), execution jumps immediately into the interrupt vector table and the kernel takes over. The only registers available for free use is $k0 and $k1, so the state of these registers will be obliterated by the kernel code. You can disable hardware interrupts, but you're most likely better off finding a free general purpose register
Co-processor 0 (COP0) Registers
|0||Index||Index into the TLB array (entry index register)|
|1||Random||Randomly generated index into the TLB array (randomized access register)|
|2||EntryLo0||Low-order portion of the current TLB entry for even-number virtual pages|
|3||EntryLo1||Low-order portion of the TLB entry for odd-number virtual pages|
|4||Context||Pointer to page-table entry in memory/lookup address|
|5||PageMask||Control for variable page size in TLB entries|
|6||Wired||Controls the number of fixed TLB entries|
|8||BadVAddr||Stores virtual address for the most recent address related exception|
|9||Count||Increments every time an opcode is processed|
|10||EntryHi||High-order portion of the TLB entry|
|11||Compare||Timer interrupt control|
|12||Status||Process status register/Used for exception handling|
|13||Cause||Exception cause register/Stores the type of exception that last occurred|
|Contains address of instruction that caused the exception|
|15||PRId||Processor identification and revision.|
|17||LLAddr||Load linked address|
|20||XContext||Context register for R4300i addressing/PTE array related|
|27||CacheErr||Cache parity error control and status|
|28||TagLo||Low-order portion of cache tag interface|
|29||TagHi||High-order portion of cache tag interface (reserved)|
|30||ErrorEPC||Error exception Program Counter|
Translation Lookaside Buffer (TLB): Address aliasing/Virtual addresses are mapped to physical addresses through the TLB.
Co-processor 1 (COP1) Registers
|$f0-$f2||Floating point function return values|
|$f12 - $f14||Floating point function parameters|
|$f16 - $f18||Temporary floating point registers|
|$f20 - $f30||Saved floating point registers*|
*The value of saved registers are preserved across function calls. Any routine that uses a saved register must first store their value on the stack before use, and restore the value before exiting the routine.
Bitwise Operators (Shifting)
Bitwise operations are operations that, as the name suggests, take place on the smallest scale (bits) with the use of an operation. Outside of it's practical usage here, there are other reasons why programmers need to be familiar with operating on the bit level: sockets, bitfield flags, graphics, encryption, in tight loops you can avoid conditional statements with them, and so forth. Needless to say, bitwise operations are everywhere! Anyways, Left shift is denoted by << and Right is >>.
Left Shift (<<)
Left shift works like multiplication. Shifting left makes all right-most bits shift over to the left, then fills zeros into blank spaces!
|SLL:||0011 1100||<< 1|
Right Shift (>>)
Right shift works like division. While shifting to the right, bits that cross the most right slot do not appear on the other side, but instead the newly shifted bits from the far most left side are zero's.
|SRL:||0011 1100||>> 2|
With MIPS, though, we have the option of arithmetic (SRA) or logical shifts (SRL/SLL). SRA is an acronym for Shift Right Arithmetic and SRL stands for Shift Right Logical: SRL shifts will always result in 0's on the far left side, while SRA will treat the leftmost, or Most Significant Bit (MSB) as a sign bit, copying it's value into the "blank" spaces. This is for dividing negative numbers.
|SRA:||1010 0111||>> 3|