MIPS data and registers
Microprocessor without Interlocked Pipeline Stages (MIPS) is a popular reduced instruction set computer (RISC) architecture used in various processors, including those found in embedded systems, workstations, and supercomputers. Understanding MIPS data and registers is essential for programming and optimizing MIPS-based systems. The data and registers play a critical role in the MIPS architecture and determine how the processor operates.
In this blog post, we will explore the fundamentals of MIPS data and registers, including their types, uses, and functionalities.
MIPS registers
All data in MIPS programs must be in registers. Registers are small amounts of memory inside the central processing unit (CPU). MIPS has 32 registers that are each 32 bits long, which means they can hold a 32-bit address. They are named from 0 to 31.
When writing about registers, use a $ in front of it. Thus, “they are named from 0 to 31” should be written as “they are named from $0 to $31.” If $ is not in front of the register, the assembler will assume it is a constant.
🌸👋🏻 Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, academics, boats, software freedom, you get the idea.
Memory vs registers
Although registers have memory and can hold 32-bit addresses, they do not have memory addresses. Only things stored in memory have memory addresses. In other words, you can’t have a memory address to a register because registers are not located in memory.

Inside high-level languages like C, all variables have memory addresses and you can have pointers to memory addresses. In contrast, you cannot point to MIPS assembly registers, nor do MIPS variables (aka, operands) have memory addresses. Instead, the programmer uses the register name (e.g., $t0) to specify a register, and the CPU examines the instruction operands, which are represented with a 5-bit code.
Learn more: Post coming soon.
MIPs registers
The speed of memory is slow compared to registers. The only instructions that use memory operands are load and store (i.e., lw, lh, lb, lbu, lhu, sw, sh, and sb); these are slow. All other instructions use register operands, making their execution faster.
Families
Registers grouped into families. Families imply how the register should be used in standard usage. It makes your code easier to read when you use the alias name rather than the register name because you will know what family it belongs to. I found a helpful video on MIPS families that explains this concept in more detail.
Chart
The color of the name column shows the family of the register.

Zero registers
$zero always contains a value of 0. If you try to write to zero, the hardware throws out the new value, so that 0 remains at $zero. Thus, trying to write to $zero or using $zero as a destination address is useless.
#
# 0 = 0 + 5
#
addi $zero, 5
However, you can use it with other other registers. When you read $zero, a numerical value of 0 will always be returned.
#
# $t0 = $zero + $t5
#
li $t5, 6
#
# 6 = 0 + 6
#
add $t0, $zero, $t5
$zero can be used anywhere.
Reserved
$at is reserved for the assembler as a temporary value. In general, programmers should not use $at because it is reserved. It is not impossible to use, it is just the assembler overwrites it if you use an instruction that requires the use of $at.
Return values
The V registers ($v0 and $v1) are for expression validation and function results. You can return up to two values because there are two V registers. However, you should remember to zero out (li $v0, 0) values before storing something in it in case it held a previous value.
Arguments
The A registers ($a0 – $a3) are used to store arguments before jumping to a procedure or function.
#
# inside the main function
#
li $a0, 1
li $a1, 2
li $a2, 3
li $a3, 4
#
# passes 1, 2, 3, and 4 into the function
#
j function
# ############
# ... code ...
# ############
function:
move $t0, $a0
move $t1, $a1
move $t2, $a2
move $t3, $a3
Note that if you need to pass more than four arguments to a routine, you can pass them on the stack.

Source: MIPS Programming Tutorial: Passing more than 4 arguments to a procedure
Temporary registers
T registers ($t0 – $t9) are for temporary storage. They are caller-saved registers and can be overwritten by called routines (global variables). In other words, the values in these registers are not preserved across function calls.
T registers are meant to be used immediately, similar to a local variable in other programming languages. Thus, if you want to save them, you must push them onto the stack (see this video on saving MIPS registers to the stack) before calling a function because the calling function is allowed to change temporary registers.
The two families that programmers interact with most are T and S.
Saved registers
S registers ($s0 – $s7) do not change. They are callee-saved registers and are preserved across function calls. Although MIPS does not have scope, you can think of these similar to a global variable.
Operating system – reserved
The operating system owns its own memory and context. Sometimes interrupts can occur, where the OS automatically saves the current program counter and jumps to the address of the interrupt. Effectively, interrupts a type of exception that causes the program’s execution to be interrupted.
The goal of the K registers ($k0 and $k1) is to ensure interrupts happen quickly.
K registers are “reserved for the assembler and operating system and should not be used by user programs or compilers” (Patterson and Hennessy).
Global area pointer
The global pointer register ($gp) points to global data, specifically the middle of a 64K block of memory in the static data segment” (Patterson and Hennessy). The global pointer allows you to manipulate data that has been allocated.
Stack pointer
The stack pointer register ($sp) points to the top of the stack, also known as the last location on the stack. In other words, the stack pointer allows you to keep track of what stack in the program you are on. Sometimes you will need to manipulate (increment or decrement) the stack pointer in a push/pop fashion, to store temporary variables.
🌸👋🏻
Let’s take this to your inbox.
You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, academics, boats, software freedom, you get the idea.

Frame pointer
The frame pointer ($fp) points at location in the stack and it is used for building standard stack frames. It has the same value throughout the execution of a function, so all local data may be accessed through hard-coded offsets from the frame pointer.
Return address
The return address ($ra) is the return address from a procedure call. This register is written by the jal instruction. In short, when a function is called, the return address is saved in $ra.
Scope
Scope does not exist in MIPS assembly, but you can simulate scope by saving an old variable value somewhere, calling a function and manipulating the variable’s value, and then restoring the original value after the manipulation is done. It will appear as if the register did not change.
#
# example code to save and restore registers
#
#
# assume we have a variable stored in $t0
#
li $t0, 10 # initialize $t0 with a value of 10
#
# call a function that modifies the value of $t0
#
jal ModifyValue
# ###########
# ... imaginary code body ...
# ###########
#
# function to modify the value of $t0
#
ModifyValue:
#
# save the original value of $t0 on the stack
#
addiu $sp, $sp, -4 # adjust the stack pointer
sw $t0, 0($sp) # store the value of $t0 on the stack
addi $t0, $t0, 5 # add 5 to the value of $t0
#
# restore the original value of $t0 from the stack
#
lw $t0, 0($sp) # load the value of $t0 from the stack
addiu $sp, $sp, 4 # adjust the stack pointer back
jr $ra # return from the function
Fake scope in MIPS assembly by saving and restoring registers (Gist).
One thing to note is that you cannot perform this with $s registers, but you can use the $T registers in this fashion. (Note: Beginner MIPS programmers only interact with $S and $T registers.)
Data transferring and storage
Unfortunately, 32 registers are not enough storage space for most programs. Thus, programmers put data into memory using load and store (LS) instructions. Depending on the size of the data being loaded or stored, different instructions are used.
- Byte (8 bits): A single byte of data.
- Halfword (16 bits): Two bytes of data.
- Word (32 bits): Four bytes of data, which is the size of a register in MIPS.
- Doubleword (64 bits): Eight bytes of data.
Variables in C
Now that we covered memory and registers, let’s examine data and operators.
In high-level languages, you store data in variables. Variables have three traits: type, name, and location in the stack memory where a value lies.
//
// include the standard input/output library for
// functions like printf
//
#include <stdio.h>
//
// main function
// aka. the entry point of the program
//
int main() {
//
// declare a pointer variable named boop
//
int *boop;
//
// declare an integer variable named value
// and initialize it to 42
//
int value = 42;
//
// assign the memory address of value
// to the pointer boop
//
boop = &value;
//
// print the memory location boop points to
//
// when you run the code, the output might look something
// like this (though the exact memory address will vary):
//
// Memory location boop points to: 0x7ffee2a99a14
//
printf("Memory location boop points to: %p\n", (void *)boop);
//
// return from function
// 0 indicates successful execution to the OS
//
return 0;
}
How to find a where a variable points to in memory (Gist).
The code will print the memory location that the pointer boop is pointing to. This memory location is the address of the integer variable value that was assigned to boop.
When you run the code, the output might look something like this (though the exact memory address will vary):
Memory location boop points to:
0x7ffee2a99a14
In this example output, 0x7ffee2a99a14
is the hexadecimal representation of the memory address where the value variable is stored. Keep in mind that the specific memory address will vary each time you run the program due to differences in memory allocation.
Variables in MIPS
In MIPS assembly, variable names are represented by registers, which do not have types. Thus, variables in MIPS assembly do not have types. However, the data stored in them do, like byte, address, and integer (unsigned and signed).
For example, load byte (lb) is used to read a byte from memory into a register, and store byte (sb) is used to save a byte from a register into memory.
#
# $s1 = Memory[$s2 + 20]
#
lb $s1, 20($s2)
#
# Memory[$s2 + 20] = $s1
#
sb $s1, 20($s2)
LS instructions
There are many different load and store instructions. Combined, these instructions are known as “transfer” instructions, and they are often used when dealing with data structures like arrays or structures.
This information is loaded and stored in order to perform operations by the arithmetic logic unit, which may be covered in a future post.
Instruction | Example | Meaning | Comments | Format |
---|---|---|---|---|
load word | lw $s1, 20($s2) | $s1 = Memory[$s2 + 20] | Word from memory register | I |
store word | sw $s1, 20($s2) | Memory[$s2 + 20] = $s1 | Word from register to memory | I |
load half | lh $s1, 20($s2) | $s1 = Memory[$s2 + 20] | Halfword memory to register | I |
load half unsigned | lhu $s1, 20($s2) | $s1 = Memory[$s2 + 20] | Halfword memory to register | I |
store half | sh $s1, 20($s2) | Memory[$s2 + 20] = $s1 | Halfword register to memory | I |
load byte | lb $s1, 20($s2) | $s1 = Memory[$s2 + 20] | Byte from memory to register | I |
load byte unsigned | lbu $s1, 20($s2) | $s1 = Memory[$s2 + 20] | Byte from memory to register | I |
store byte | sb $s1, 20($s2) | Memory[$s2 + 20] = $s1 | Byte from register to memory | I |
load linked word | ll $s1, 20($s2) | $s1 = Memory[$s2 + 20] | Load word as 1st half of atomic swap | I |
store condition word | sc $s1, 20($s2) | Memory[$s2 + 20] = $s1; $s1 = 0 or 1 | Store word as 2nd half of atomic swap | I |
load upper immediate | lui $s1, 20 | $s1 = 20 * 2^16 | Loads constant in upper 16 bits | I |
Table 2.2.2 Hennessy
Conclusion
In conclusion, understanding MIPS data and registers is essential for efficient programming and optimization of MIPS-based systems. The data and registers play a critical role in the MIPS architecture, determining how the processor operates and the type of data that can be processed. By knowing the different types of data and registers available in MIPS, programmers can select the appropriate data type for the task at hand, optimize their programs, and ensure that their MIPS-based systems run smoothly without errors or performance issues.
If you enjoyed this post on MIPS Data and Registers, consider reading Computer Memory and Performance.
Citations
Patterson, David A., and John L. Hennessy. Computer Organization and Design MIPS Edition: The Hardware/Software Interface. Morgan Kaufmann, 2020.
You must be logged in to post a comment.