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.
Usually, ever register holds a value of 32-bits (aligned) (XX XX YY YY) which can be sign extended (to hold larger values).However, depending on the kernel addressing mode, they can also hold 64-bits. Normally, to maximize performance, they are 32-bits wide. Luckily, having a static length be is 32, or 64-bits it creates a simplified memory model which makes it easier for editing purposes rather than needing to remember rules or certain opcodes that have more or less than the general "usual" amount. The following is an arithmetic instruction; the opcode (ADD)'s usage as an example:
Binary Opcode Format
| 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, COP1 and RSPCOP0
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|
*The value of saved registers (Otherwise known as non-volatile 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.Essentially, if there is no asterisk they can be changed without consequences which makes them volatile registers.
As seen below, each processor has 31 registers(and two hidden ones), while the CPU has 32 (and three hidden ones). COP0 (MMU) converts virtual addresses into physical addresses, selects an operating mode and control exceptions and the COP1 (FPU) is for floating point operations. The Reality Co-Processor, which is composed of the RSP (signal) and RDP (drawing), together acts as a polygon cruncher. The RSP's COP0 handles communication between the CPU and the RCP through DMA, the COP2 is the RSP's Vector Unit (VU), and the RSP uses it's own microcode (uCodes). As seen below, I implemented a naming convention to make it easier to recall their usages.
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 - (May not want to use if hooked to JR $RA (unless you know what that function returns))|
|4-7||$a0 - $a3||Arguments: First four parameters for a subroutine - (Avoid using if you've hooked a JAL/JALR (unless what arg the function expects is known))|
|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: Do not use $K0, exception handler uses this, $K1 is an open register that's value is almost always 0xAAA (read note below)|
|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|
|spec||Hi/Lo||These special registers that aren't directly accessible. They're used to store result of multiplication and division|
|spec||PC||The Program Counter stores the address of the instruction that's currently being executed|
|spec||LLB||dedicated for load-link and store-conditional instructions which can be used to perform SYNC operations|
spec = Special registers
- The $K0 and $K1 registers should not be used because almost all mods will only modify code that is executed while the CPU is in "User" state. In user state, a hardware interrupt can occur at any point during execution, thus invalidating the $K0 and $K1 registers.
- 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.
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
Processor cycle count
|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
Address at which to resume execution after an 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.
For further information.
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 two hidden/special registers for the COP1 are FCR0 and FCR31: they're the floating point Implementation and Control registers.
For more information.
Reality Signal Coprocessor (RSPCOP0) Registers
|RW||I/DMEM address for DMA|
|RW||DRAM address for DMA|
|RW||DMA READ length (DRAM -> i/DMEM)|
|RW||DMA WRITE length (DRAM <- I/DMEM)|
|RW||RDP command buffer START|
|RW||RDP command buffer END|
|R||RDP command buffer CURRENT|
|RW||RDP clock counter|
|R||RDP command buffer BUSY|
|R||RDP pipe BUSY|
|R||RDP TMEM BUSY|
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|