This covers the practical side of writing MIPS assembly: translating C expressions and control flow into MIPS, using the stack to save and restore registers, implementing loops, working with the five addressing modes, manipulating individual bits with ori and andi, and calling subroutines with jal and jr.
Difficulty: Intermediate | Prerequisites: Binary representation (Part 1), instruction formats and registers (Part 2).
Knowing the instruction formats is necessary but not sufficient. The exam tests whether you can actually use MIPS assembly to solve problems: translate a C if/else or loop into branches and labels, manage the stack when calling a function, and use bitwise instructions to flip specific bits in a register.
This is where the majority of the exam points are. The 7 short-answer coding questions and many of the 20 multiple-choice questions test these skills directly. If you can write MIPS for a simple C function without hesitation, you are well positioned.
Stack
A region of memory used for temporary storage, especially for saving register values during function calls. It grows downward in MIPS (toward lower addresses). Think of it as a scratch pad that functions use to remember what they were doing before being called.
Push (onto the stack)
Allocating space on the stack and storing a value there. In MIPS: decrement $sp by 4, then store the register with sw.
Pop (off the stack)
Retrieving a value from the stack and freeing the space. In MIPS: load the register with lw, then increment $sp by 4.
Addressing mode
The method an instruction uses to determine where its operand is located. MIPS has five: immediate, register, base (displacement), PC-relative, and pseudodirect.
sll (shift left logical)
Shifts a register value left by a specified number of bit positions, filling with zeros on the right. Each left shift by 1 is equivalent to multiplying by 2. In simple terms, it is a fast way to multiply by powers of 2.
ori (OR immediate)
Performs a bitwise OR between a register and a 16-bit zero-extended immediate. Used to force specific bits to 1 without changing other bits.
andi (AND immediate)
Performs a bitwise AND between a register and a 16-bit zero-extended immediate. Used to force specific bits to 0 (by AND-ing with 0 in those positions) while leaving other bits unchanged.
beq / bne (branch on equal / branch on not equal)
Conditional branch instructions. beq branches if two registers are equal; bne branches if they are not. Both use PC-relative addressing.
slt (set less than)
Sets a destination register to 1 if the first source register is less than the second, and to 0 otherwise. Used to build if/else conditions in MIPS since there is no "branch if less than" instruction.
jal (jump and link)
Jumps to a target address and saves the return address (PC + 8) in $ra. This is how you call a subroutine in MIPS.
jr (jump register)
Jumps to the address stored in a register. jr \$ra is the standard way to return from a subroutine.
Map each C variable to a register, then express each operation as one MIPS instruction. MIPS instructions do one operation at a time, so a compound expression like x = x + y + z - q requires multiple instructions.
Example (x, y, z, q in $s1 to $s4):
add \$s1, \$s1, \$s2 # x = x + y
add \$s1, \$s1, \$s3 # x = x + z
sub \$s1, \$s1, \$s4 # x = x - qAlternatively, use a temporary register to avoid overwriting x partway through:
add \$s1, \$s1, \$s2
add \$s1, \$s1, \$s3
sub \$s1, \$s1, \$s4MIPS has no if/else construct. You build it from branches and labels.
C code: if (a < b) c = b; else c = a; (a, b, c in $t1, $t2, $t3)
slt \$t0, \$t1, \$t2 # \$t0 = 1 if a < b
beq \$t0, \$zero, else # if a >= b, go to else
add \$t3, \$t2, \$zero # c = b
j done
else:
add \$t3, \$t1, \$zero # c = a
done:Alternatively, blt is a pseudo-instruction the assembler expands for you: blt \$t1, \$t2, then_label.
A C function becomes a labelled block in MIPS. Arguments arrive in $a0 to $a3, the return value goes in $v0, and you return with jr \$ra.
C code:
int func(int n) {
if (n == 0) return 0;
else return n - 1;
}MIPS:
func:
beq \$a0, \$zero, ret_zero
addi \$v0, \$a0, -1 # return n - 1
jr \$ra
ret_zero:
add \$v0, \$zero, \$zero # return 0
jr \$raCalling the subroutine and storing the result:
jal func # call func; \$ra = return address
sw \$v0, result(\$zero) # store return valueThe stack grows downward. $sp always points to the top of the stack (the last item pushed).
Push $s0:
addi \$sp, \$sp, -4 # make room (4 bytes = 1 word)
sw \$s0, 0(\$sp) # store \$s0 at the new topPop $s0:
lw \$s0, 0(\$sp) # load from top of stack
addi \$sp, \$sp, 4 # free the spaceThe order matters. When pushing, decrement first, then store. When popping, load first, then increment.
C code: for (i = 0; i < 3; i++) array[i] += 3;
Assume array address in $s6, i in $s3 (initialised to 0).
addi \$t0, \$zero, 3 # \$t0 = loop limit
loop:
slt \$t1, \$s3, \$t0 # \$t1 = 1 if i < 3
beq \$t1, \$zero, done # exit if i >= 3
sll \$t2, \$s3, 2 # \$t2 = i * 4 (byte offset)
add \$t2, \$s6, \$t2 # \$t2 = address of array[i]
lw \$t3, 0(\$t2) # load array[i]
addi \$t3, \$t3, 3 # array[i] += 3
sw \$t3, 0(\$t2) # store back
addi \$s3, \$s3, 1 # i++
j loop
done:Key point: each int is 4 bytes, so the offset is i * 4, computed with sll (shift left by 2 = multiply by 4).
Immediate addressing: the operand is a constant in the instruction itself. Example: addi \$t0, \$t0, 5.
Register addressing: the operand is in a register. Example: add \$t0, \$t1, \$t2.
Base (displacement) addressing: the address is formed by adding a register to an offset. Used by lw, sw, lb, sb. Example: lw \$t0, 8(\$s0) loads from address ($s0 + 8).
PC-relative addressing: the branch target is PC + 4 + (offset * 4). Used by beq and bne.
Pseudodirect addressing: the jump target is formed from the top 4 bits of the PC concatenated with the 26-bit address field shifted left by 2. Used by j and jal.
Setting specific bits to 1 (ORI)
OR-ing a bit with 1 forces it to 1. OR-ing with 0 leaves it unchanged. So to set bits 5 and 7 to 1:
Bit 7 = 0x80, bit 5 = 0x20. Together: 0x00A0.
ori \$t0, \$t0, 0x00A0
Clearing specific bits to 0 (ANDI)
AND-ing a bit with 0 forces it to 0. AND-ing with 1 leaves it unchanged. So to clear bits 5 and 3:
Create a mask with 0 in positions 5 and 3, 1 everywhere else.
Bit 5 = 0x20, bit 3 = 0x08. Mask = 0xFFFF XOR (0x20 | 0x08) = 0xFFFF XOR 0x28 = 0xFFD7.
andi \$t0, \$t0, 0xFFD7
add \$t0, \$t0, \$t0 doubles $t0 (adds it to itself).
sll \$t0, \$t0, 1 also doubles $t0 (shifts left by 1 = multiply by 2).
Both produce the same result. The exam tests whether you recognise this equivalence.
MIPS mult and div instructions place their results in special HI and LO registers.
mult \$s0, \$s1 multiplies and puts the low 32 bits in LO, high 32 bits in HI.
mflo \$t0 moves the LO register into $t0.
div \$s0, \$s1 divides; quotient in LO, remainder in HI.
Example: c = 10*a - (b * c) with a, b, c in $s0, $s1, $s2:
addi \$t0, \$zero, 10
mult \$s0, \$t0
mflo \$t1 # \$t1 = 10*a
mult \$s1, \$s2
mflo \$t2 # \$t2 = b*c
sub \$s2, \$t1, \$t2 # c = 10*a - (b*c)Students write the push sequence in the wrong order: storing before decrementing $sp. This overwrites data below the stack pointer. Always decrement first, then store.
Students forget that array indexing in MIPS requires multiplying the index by 4 (for word-sized elements). If you load from 0(\$s6) when i = 1, you get array[0], not array[1]. You need the offset i * 4.
Students confuse beq/bne with "branch if less than." MIPS has no single blt instruction at the machine level. You need slt followed by beq or bne with $zero.
Students think ori sets bits to 0. ORI sets bits to 1. To clear bits, use andi with a mask that has 0s in the positions you want to clear.
Students forget to save $ra on the stack before calling another function from within a function. If you call jal inside a subroutine, it overwrites $ra, and your own return address is lost unless you saved it.
⚠️ Converting a C expression or short function to MIPS is one of the 5-to-6-point short-answer questions. Practice until this is automatic.
⚠️ Push and pop sequences appear on the exam, both as standalone questions and inside subroutine questions. Know the two-instruction pattern cold.
⚠️ The exam asks you to name all five addressing modes and give a sample instruction for each. Memorise them with one example each.
⚠️ ORI and ANDI bit-manipulation questions appear on the exam. You will be asked to fill in the immediate value to set or clear specific bits. Work through the bit positions carefully.
⚠️ The exam tests whether you know how jal and jr $ra work together for subroutine calls and returns.
⚠️ Understanding what add \$t0, \$t0, \$t0 does (doubling) and rewriting it with sll is a direct exam question.
True or False: To push a register onto the stack, you first store with sw, then decrement $sp.
Answer: False. Decrement $sp first, then store.
Fill in the blank: To force bit 3 of $t0 to 1 without changing any other bits, use ori \$t0, \$t0, ______.
Answer: 0x0008 (which is 2^3 = 8).
True or False: sll \$t0, \$t1, 3 multiplies $t1 by 8 and stores the result in $t0.
Answer: True. Shifting left by 3 = multiplying by 2^3 = 8.
What does jr \$ra do?
Answer: Jumps to the address stored in the $ra register, which returns from a subroutine.
Fill in the blank: In a for loop that iterates over an array of ints, the byte offset for element i is computed as ______.
Answer: i * 4 (or equivalently, sll i by 2).
Stack operations connect directly to subroutine calling conventions. When you write a function that calls another function (nested calls), you must push $ra onto the stack before the inner jal, then pop it before your own jr $ra. This connects to the register preservation rules in Part 2: $s registers are callee-saved, so a function that uses them must push and pop them.
Bit manipulation with ori and andi connects to instruction encoding (Part 2). The immediate field in I-type instructions is the same constant you supply to ori or andi. Understanding how those bits map is the same skill as encoding/decoding instructions.
Addressing modes connect to instruction formats. Immediate addressing corresponds to I-type instructions, register addressing to R-type, base addressing to lw/sw (I-type), PC-relative to branches (I-type), and pseudodirect to j/jal (J-type).
Q: Write MIPS assembly for x = x + y + z - q where x, y, z, q are in $s1 through $s4.
A: add \$s1, \$s1, \$s2 then add \$s1, \$s1, \$s3 then sub \$s1, \$s1, \$s4.
Q: Write the two-instruction sequence to push $s0 onto the stack.
A: addi \$sp, \$sp, -4 followed by sw \$s0, 0(\$sp).
Q: Write the two-instruction sequence to pop $s0 off the stack.
A: lw \$s0, 0(\$sp) followed by addi \$sp, \$sp, 4.
Q: What immediate value should you use in ori \$t0, \$t0, ____ to force bits 5 and 7 to 1?
A: 0x00A0. Bit 5 = 0x20, bit 7 = 0x80. 0x20 + 0x80 = 0xA0.
Q: What immediate value should you use in andi \$t0, \$t0, ____ to force bits 5 and 3 to 0?
A: 0xFFD7. Start with 0xFFFF, clear bit 5 (0x20) and bit 3 (0x08): 0xFFFF - 0x28 = 0xFFD7.
Q: What does add \$t0, \$t0, \$t0 do, and how would you rewrite it with sll?
A: It doubles $t0. Rewrite: sll \$t0, \$t0, 1.
Q: Name the five MIPS addressing modes and give one instruction example for each.
A: Immediate (addi), Register (add), Base/displacement (lw), PC-relative (beq), Pseudodirect (j).
MIPS assembly programming, C to MIPS conversion, if else MIPS, for loop MIPS, stack push pop, stack pointer, subroutine call, jal, jr, return address, addressing modes, immediate addressing, register addressing, base addressing, displacement addressing, PC-relative, pseudodirect, ori set bits, andi clear bits, bit manipulation, sll multiply by 2, shift left logical, mult mflo, MIPS function call convention, callee-saved registers, slt branch pattern