rezarezvan.com

Part 2 - MIPS

EDA333
March 22, 2023
4 min read
Table of Contents
EDA333_2

In this part we’ll properly cover the MIPS architecture in depth.

Parameter transferring

When calling a function in C code, what is happening at the assembler level is, calling a subroutine (usually).

What we need to do before a subroutine call, is the following:

  1. Place the input parameters into their respective registers
  2. Call the function/subroutine
  3. Allocate memory for eventual saved and local variables
  4. Execute the function/procedure
  5. Place the result in a register
  6. Reset the stack, if needed
  7. Return to where the function/subroutine got called.

Register conventions

Since we now need to store our input parameters into register before we call a function, we need to create a convention to where we expect things to be.

MIPS has this following convention:

  • $a_0 - a_3$: Input parameters

  • $v_0, v_1$: Result values

  • $t_0 - t_9$: Temporary variables

  • $s_0 - s_7$: “Saved” registers

    • Must be saved and reset by each respective subroutine.
  • $gp$: global pointer

  • $sp$: stack pointer

  • $fp$: frame pointer

  • $ra$: return address

We discussed a lot of subroutines, but how do we actually call one?

jal Label will jump to a label (starting point of a subroutine).

To return from a subroutine call jr $ra.

A very simple function like:

int example(int g, int h, int i, int j) {
int f;
f = (g + h) - (i + j);
return f;
}

Given that the arguments are in $a_0, \ldots, a_3$, f is in $s_0$ (therefore we must save $s_0$ to the stack before!).

The result will be stored in $v_0$

MIPS Code:

example:
addi $sp, $sp, -4 # Allocate space for one variable, s_0
sw $s0, 0($sp) # Save s_0
add $t0, $a0, $a1 # (g + h)
add $t1, $a2, $a3 # (i + j)
sub $s0, $t0, $t1 # f = (g + h) - (i + j)a
add $v0, $s0, $zero # Store value
lw $s0, 0($sp) # Reset s_0 value
addi $sp, $sp, 4 # Reset stack pointer
jr $ra # Return

Let’s now take a recursive

int fact(int n) {
if(n < 1) {
return 1;
} else {
return n * fact(n - 1);
}
}

input parameter n in $a_0$, result in $v_0$

MIPS:

fact:
addi $sp, $sp, -8 # Adjust stack so we can store 2 variables
sw $ra, 4($sp) # Save return address
sw $a0, 0($sp) # Save input register
slti $t0, $a0, 1 # test for n < 1
beq $t0, $zero, L1
addi $v0, $zero, 1 # If n < 1, result = 1
addi $sp, $sp, 8 # Pop stack
jr $ra # Return
L1: addi $a0, $a0, -1 # Decrement n
jal fact # Recursive call
lw $a0, 0($sp) # restore orignal n
lw $ra 4($sp) # and return address
addi $sp, $sp, 8 # Pop stack
mul $v0, $a0, $v0 # n * fact(n - 1)
jr $ra # return

Less than a word

Sometimes we want to only access a byte or half-word:

lb rt, offset(rs)
lh rt, offset(rs)
# Unsigned
lbu rt, offset(rs)
lhu rt, offset(rs)
# Save
sb rt, offset(rs)
sh rt, offset(rs)

Indexing vs Pointers

  • Vector indexing requires:

    • Multiplication of the index with length

    • Summation of this product to the vectors start address

  • Pointer corresponds directly to a memory address

    • Therefore, it’s faster

Common mistakes

Common mistakes when writing programs in MIPS are:

  • Incrementing with 1 and not 4!

  • MIPS uses byte addressing