# MIPS Assembly Language Notes This note is for MIPS assembly language, detailed from the basic structure of MIPS registers to R, I, J instructions. The documnent includes notes and scenario labs. ## MIPS Register Structure MIPS structure includes 32 general-purpose registers and some specialized registers reserved for system useage. The following are the table of registers and their explaination: | Register Number | Register Name | Description | |-----------------|---------------|-------------------------------| | 0 | `$zero` | Constant zero | | 1 | `$at` | Assembler temporary | | 2-3 | `$v0-$v1` | Function return values | | 4-7 | `$a0-$a3` | Function arguments | | 8-15 | `$t0-$t7` | Temporaries | | 16-23 | `$s0-$s7` | Saved temporaries | | 24-25 | `$t8-$t9` | Temporaries | | 26-27 | `$k0-$k1` | Reserved for OS kernel | | 28 | `$gp` | Global pointer | | 29 | `$sp` | Stack pointer | | 30 | `$fp/$s8` | Frame pointer or saved temp | | 31 | `$ra` | Return address | **Short explanation:** `$zero` : It is useful when we want to do a comparison or status check with value 0. `$t0-$t7` : Usually we set variables that would be changed later on in this kind of registers. `$s0-$s7` : Usually we set variables that would **not** be changed in this kind of registers. ## Load Instructions The easiest instruction is load. There are 6 load instructions in MIPS assembly language. They each hold different usage for different cases. <br> ### `lw` Short for <b style="color: #0080FF;">load word</b>. This instruction helps load word from memory to register. Here is a simple example of it: ```assembly lw $t0, 0($s0) ``` In this case, `$s0` is a register that stores an address. The `lw` instruction gets the 32 bits data from that address and store it to register `$t0` for later use. `0` reprsent that there is no offset needed. If we want to store data from an array, we can manually calaulate the offset of the data we want. Let's say now `$s0` stores a base address for an array `A`, if we want to get the data of `A[3]`, we can calculate the offset as `4 bytes * 3 = 12 bytes`, thus the command can be written as: ```assembly lw $t0 12($s0) ``` The term `12($s0)` represent that we want to load the data in address with offset 4 bytes. <br> ### `lb` Short for <b style="color: #0080FF;">load byte</b>. Same as `lw`, this instruction helps load byte from memory to register. Take the same example, the command becomes: ```assembly lb $t0, 0($s0) ``` This time, it loads one byte of data from the memory address stored in register $s0 with an offset of 0, and stores the byte into register $t0 for later use. <br> ### `lbu` The unsigned version of `lb`. All values that loads by this command will become non-negative values. <br> ### `lh` Short for <b style="color: #0080FF;">load half word</b>. This instruction load hald a word (2 bytes) from memory to register. Same, using the example, the command becomes: ```assembly lh $t0, 0($s0) ``` It loads two byte of data from the memory address stored in register $s0 with an offset of 0, and stores the byte into register $t0 for later use. <br> ### `lhu` The unsigned version of `lh`. All values that loads by this command will become non-negative values. <br> ### `lui` Short for <b style="color: #0080FF;">load upper immediate</b>. The instruction loads 16 bits of data and store it to the upper part of the register. Here's an example. We can use the following instruction: ```assembly lui $t0, 0xABCD ``` It loads `ABCD` in `$t0`, which `$t0` becomes `ABCD0000`. `lui` only accept immediate value, so using register as source is not acceptable. <br> ## Store Instructions These instructions are used to store data from register to memory. <br> ### `sw` Short for <b style="color: #B766AD;">store word</b>. This instruction help store 8 bytes data from register to memory. Here's an example: ```assembly sw $t0, 0($s0) ``` This instruction stores the 8 bytes data in register `$t0` to the address stored in the register `$s0`. <br> ### `sh` Short for <b style="color: #B766AD;">store half word</b>. This instruction help store 4 bytes data from register to memory. The example becomes: ```assembly sh $t0, 0($s0) ``` This instruction stores the 4 bytes data in register `$t0` to the address stored in the register `$s0`. <br> ### `sb` Short for <b style="color: #B766AD;">store byte</b>. This instruction help store a byte of data from register to memory. The example becomes: ```assembly sb $t0, 0($s0) ``` This instruction stores the a byte of data in register `$t0` to the address stored in the register `$s0`. <br> ## Arithmetic Instructions We use these arithmetic instructions to do basic math calculation. ### `add` Short for <b style="color: #FF9224;">addition immediate</b>. This instruction helps add two values together. The following example shows the usage: ```assembly add $t2, $t0, $t1 ``` The meaning of registers are the following sequence: destination, source, target. The instruction means that add the value stored in `$t0` with the one stored in `$t1`, and then store the result in `$t2`. <br> ### `addi` Short for <b style="color: #FF9224;">addition immediate</b>. It is almost the same to `add`, as it only replace the target with immediate value. The example becomes: ```assembly addi $t0, $t1, 2 ``` The instruction means adding the value stored in `$t1` with 2 and then store it in `$t0`. <br> ### `sub` Short for <b style="color: #FF9224;">substitute</b>. This instruction helps substitute two values. The following example shows the usage: ```assembly sub $t2, $t0, $t1 ``` The meaning of registers are the following sequence: destination, source, target. The instruction means that substitute the value stored in `$t0` with the one stored in `$t1`, and then store the result in `$t2`. <br> ### `mul` Short for <b style="color: #FF9224;">multiply</b>. This instruction helps multiply two values. The following example shows the usage: ```assembly mul $t2, $t0, $t1 ``` The meaning of registers are the following sequence: destination, source, target. The instruction means that multiply the value stored in `$t0` with the one stored in `$t1`, and then store the result in `$t2`. <br> ### `mult` A different way of multiply instruction. Instead of storing the result in the particular register, `mult` store it in the `HI` and `LO` registers, which are outside the basic 32 registers. To get it out, we need to use `mfhi` or `mflo`. Look at the following example: ```assembly mult $t1, $t2 # Multiply $t1 and $t2, store result in HI and LO registers mflo $t0 # Move the lower 32 bits of the result from LO to $t0 mfhi $t1 # Move the upper 32 bits of the result from HI to $t1 ``` We can decide how many we want to get from the `HI` and `LO` registers. If the result isn't that large, we can simply get it using `mflo`. <br> ### `multu` The unsigned version of `mult`. The result will only be positive. <br> ### `div` Short for <b style="color: #FF9224;">division</b>. This instruction helps divide two values. The following example shows the usage: ```assembly div $t0, $t1 ``` The insturction devide the value stored in `$t0` with the one stored in `$t1`. The quotient will be stored in register `$lo`, and the remainder will be stored in `$hi`. Same as `mult`/`multu`, the result can be get using `mflo`, `mfhi`. <br> ### `divu` The unsigned version of `div`. The result will only be positive. <br> ## [Scenario]: Number add Assume that A stores in `$s0`, B stores in `$s1`, please write down the MIPS assembly code for the following C code: 1. A = A + 5; 2. B = A + B; <details> <summary>Ans 1</summary> Here's the MIPS assembly code for question 1: ```assembly lw $t0, 0($s0) # Load A from memory to $t0 addi $t0, $t0, 5 # A = A + 5 sw $t0, 0($s0) # Store updated A back to memory ``` Remember to load the value to `$tn` register and make addition on it, then store it to the target destination. </details> <details> <summary>Ans 2</summary> Here's the MIPS assembly code for question 2: ```assembly lw $t0, 0($s0) # Load A from memory to $t0 lw $t1, 0($s1) # Load B from memory to $t1 add $t1, $t0, $t1 # B = A + B sw $t1, 0($s1) # Store updated B back to memory ``` Since `$tn` register is normally used for storing values that would be rewrite, we can directly do calculation on `$t1`. </details> <br> ## Logical Instructions If we want to do simply comparison, we use logical instructions. It can be work with Jump instructions to achieve if statement and loops. <br> ### `and` This instruction do simple <b>and</b> operation with two values. Here's an example, assume that `$t0` stores 10 and `$t1` stores 20: ```assembly and $t2, $t0, $t1 ``` As 10 in binary is `00001010`, 20 in binary is `00010100`, the result becomes: ``` 0000 1010 ($t0 = 10) AND 0001 0100 ($t1 = 20) -------------- 0000 0000 ($t2 = 0) ``` The result(`0`) is stored in `$t2`. <br> ### `andi` Short for <b style="color: #8CEA00;">and immediate</b>. The immediate version of <b>and</b> operation. Assume `$t0` is still 10, the example becomes: ```assembly and $t1, $t0, 20 ``` The result(`0`) is stored in `$t1`. <br> ### `or` This instruction do simple <b>or</b> operation with two values. Here's an example, assume that `$t0` stores 10 and `$t1` stores 20: ```assembly or $t2, $t0, $t1 ``` As 10 in binary is `00001010`, 20 in binary is `00010100`, the result becomes: ``` 0000 1010 ($t0 = 10) OR 0001 0100 ($t1 = 20) -------------- 0001 1110 ($t2 = 30) ``` The result(`30`) is stored in `$t2`. <br> ### `ori` Short for <b style="color: #8CEA00;">or immediate</b>. The immediate version of <b>or</b> operation. Assume `$t0` is still 10, the example becomes: ```assembly ori $t1, $t0, 20 ``` The result(`30`) is stored in `$t1`. <br> ### `xor` This instruction do simple <b>xor</b> operation with two values. Here's an example, assume that `$t0` stores 10 and `$t1` stores 20: ```assembly xor $t2, $t0, $t1 ``` As 10 in binary is `00001010`, 20 in binary is `00010100`, the result becomes: ``` 0000 1010 ($t0 = 10) XOR 0001 0100 ($t1 = 20) -------------- 0001 1110 ($t2 = 30) ``` The result(`30`) is stored in `$t2`. <br> ### `nor` This instruction do simple <b>nor</b> operation with two values. Here's an example, assume that `$t0` stores 10 and `$t1` stores 20: ```assembly nor $t2, $t0, $t1 ``` As 10 in binary is `00001010`, 20 in binary is `00010100`, the result becomes: ``` 0000 1010 ($t0 = 10) NOR 0001 0100 ($t1 = 20) -------------- 1110 0001 ($t2 = 225) ``` The result(`225`) is stored in `$t2`. <br> ## Shift Instructions Shift instructions are used when we want to quickly multiply/divide values. For example, if we want to get the value in `A[i]` as A is stored in `$s0` and `i` is a variable, we need to first calculate the offset of `i`, which is `i * 4` and then add it with the base address of `A`. To do that, shift instruction is in it's usage. <br> ### `sll` Short for <b style="color: #D9006C;">shift left logical</b>. We can use it to multiply value with base 2. Here's an example, assume variable `i` stores in `$t0`: ```assembly sll $t0, 2 ``` This example will lead to the result that `i` is multiplied by 4, which in some case can calculate the offset when `i` is the index we want to get from an array. <br> ### `srl` Short for <b style="color: #D9006C;">shift right logical</b>. Here's an example assume number 4 is stored in `$t0`: ```assembly srl $t0, 1 ``` The instruction shift the value right with one bit, which divide the value with 2, so the value stored in `$t0` becomes 2. <br> ### `sra` Short for <b style="color: #D9006C;">shift right arithmetic</b>. Here's an example, with value `-8` stored in `$t0`: ```assembly sra $t0, $t0, 2 ``` This instruction let the origin value `-8`(`11111111 11111111 11111111 11111000` in binary) becomes `-12`(`11111111 11111111 11111111 11111110` in binary), as shifting the value right two bits with `1` coming in. <br> ### `rotr` Short for <b style="color: #D9006C;">rotate right</b>. Here's an example as the last one, but replace the instruction with `rotr`: ```assembly rotr $t0, $t0, 1 ``` This instruction let the origin value `-8`(`11111111 11111111 11111111 11111000` in binary) becomes `2147483644`(`01111111 11111111 11111111 11111100` in binary), the bit going out from right comes back to the left. <br> ## [Scenario]: Array operation Assume that base address of array A stored in `$s0`, base address of array B stores in `$s1`, i, j, k is at `$t0`, `$t1` and `$t2`. Please write down the MIPS assembly code for the following C code: 1. A[i] = 5; 2. B[k+3] = A[i] + j; <details> <summary>Ans 1</summary> Here's the MIPS assembly code for question 1: ```assembly sll $t0, $t0, 2 # Shift i left by 2 bits to get offset add $t3, $s0, $t0 # Add the base address of array A to the offset lw $t4, 0($t3) # Load the current value at A[i]'s address add $t4, $zero, 5 # Set $t4 to 5 sw $t4, 0($t3) # Store the value back ``` Calculate the offset and get the value, then set it back to where it should be. </details> <details> <summary>Ans 2</summary> Here's the MIPS assembly code for question 2: ```assembly sll $t0, $t0, 2 # Shift i left by 2 bits to get offset add $t3, $s0, $t0 # Add the base address of array A to the offset lw $t4, 0($t3) # Load A[i] add $t4, $t4, $t1 # Add j to A[i] addi $t2, $t2, 3 # Add 3 to k sll $t2, $t2, 2 # Shift (k+3) by 2 bits add $t3, $s1, $t2 # Add the base address of array B to the offset sw $t4, 0($t3) # Store the value (A[i] + j) into B[k+3] ``` Add the k+3 first then calculate the offset. </details> <br> ## Comparison Instructions Comparion instructions is used when we want to check which instructions we need to do next. There's s bunch of combination using comparison instructions. Comparison instructions will have to give a label(think of it as a tag) to jump to. <br> ### `beq` Short for <b style="color: #5CADAD;">branch if euqal</b>. The following is an example of the usage: ```assembly main: beq $t0, $t1, label add $t0, $t0, $t1 label: ... ``` Assume that `$t0` and `$t1` are equal, the instruction of addition will not be executed right away. Instead, it jumps to `label` and executes the instrutions in it first. <br> ### `bne` Short for <b style="color: #5CADAD;">branch if not euqal</b>. Similar to `beq`, this instruction helps verify if two values are not the same. So the example becomes: ```assembly main: bne $t0, $t1, label add $t0, $t0, $t1 label: ... ``` This time, only if `$t0` and `$t1` are different will lead to the "jump to label" action. <br> ### `blez` Short for <b style="color: #5CADAD;">branch if less than or equal to zero</b>. This instruction only takes one register an the label as input. It compares the input register and see if the value stored in it is less than or euqal to 0. Here's an example: ```assembly main: add $t0, $zero, $t0 blez $t0, label label: ... ``` As the `$t0` is set to zero, the condition `$t0 <= 0` is met, thus the jump action will be taken. <br> ### `bgez` Short for <b style="color: #5CADAD;">branch if greater than or equal to zero</b>. This instruction only takes one register an the label as input. It compares the input register and see if the value stored in it is greater than or euqal to 0. Here's an example: ```assembly main: add $t0, $zero, $t0 blez $t0, label label: ... ``` As the `$t0` is set to zero, the condition `$t0 >= 0` is met, thus the jump action will be taken. <br> ### `bltz` Short for <b style="color: #5CADAD;">branch if less than zero</b>. Similar to `blez` but without "equal to". It only check if the givin value is less than zero. Still the same example, this time it will not jump, as `$t0 = 0` and the condition `$t0 < 0` is not met. <br> ### `bgtz` Short for <b style="color: #5CADAD;">branch if greater than zero</b>. Similar to `blez` but without "equal to". It only check if the givin value is greater than zero. Still the same example, this time it will not jump, as `$t0 = 0` and the condition `$t0 > 0` is not met. <br> ## Set on Comparison Instructions If we want to do comparison but do not want to jump to other place, we can simply store the result in another register. These kind o instructions helps do the trick. ### `slt` Short for <b style="color: #B15BFF;">set on less than</b>. Here's an example, assume `$s0` is 0 and `$s1` is 1: ```assembly slt $s2, $s0, $s1 ``` The instruction verfiy if `$s0` is less than `$s1`. In the example it is, so `$s2` is set to 1. Otherwise, `$s2` is set to 0. <br> ### `slti` Short for <b style="color: #B15BFF;">set on less than immedite</b>. The only difference of it with `slt` is that instead of comparing two values coming from registers, it is comparing one from register with immediate value. The example becomes: ```assembly slti $s1, $s0, 10 ``` This instruction check if value in `$s0` is less than 10, if it is, set `$s1` to 1, otherwise set it to 0. <br> ### `sltu` Short for <b style="color: #B15BFF;">set on less than unsigned</b>. Same function as `slt`, but it treats values as unsigned. <br> ### `sltiu` Short for <b style="color: #B15BFF;">set on less than unsigned</b>. Same function as `slti`, but it treats values as unsigned. <br> ## Jump Instruction ### `j` Short for <b style="color: #FFD306;">jump</b>. The instruction jumps to label no matter what happened.Here's an example: ```assembly main: j label ... label: ... ``` All instuctions will not be executed if using `j` instruction. It will jump to `label` and continue executing instructions in it. <br> ### `jal` Short for <b style="color: #FFD306;">jump and link</b>. This instruction jumps to other place but will remember where it leave in `$ra` register. Here's an example: ```assembly main: jal func ... func: ... ``` Later in `func`, we can come back to the line of instruction we leave in `main` to keep going on. <br> ### `jr` Short for <b style="color: #FFD306;">jump register</b>. It jumps to the address stored in the target register. Take the example from the last one: ```assembly main: jal func ... func: ... jr $ra ``` After execution the instructions in `func`, it jumps back to where it leave, using the address stored in `$ra`. <br> ### `jalr` Short for <b style="color: #FFD306;">jump and link register</b>. Similar to `jal`, we can also jump to a particular address stored in a register instead of label. We use `jalr` to do so, assume that 0x100 is somewhere in `func`: ```assembly main: jalr 0x100 ... func: ... jr $ra ``` The instruction jumps to a line in `func`, executes the instructions below it, and finally reach `jr` and comes back to where it leave in `main`. <br> ### `eret` Short for <b style="color: #FFD306;">exception return</b>. If an error occur, this instruction can be used in excepition handling section. ```assembly eret ``` It will jump to the addres stored in th epc register. <br> ## [Scenario]: Conditional Assume that g, h, i, j, k stores at `$s0`, `$s1`, `$s2`, `$s3`, `$s4`, and base address of array A and array B stores at `$s6`, `$s7`. Please write down the MIPS assembly code for the following C code: 1. ``` if(i + 3 > k) { B[i+1] = A[j]; } else { B[h] = A[j] + A[k] * 3; } ``` 2. ``` if(A[i] == B[j]) { B[j] = A[k]; } else if (A[i] < B[j] ) { B[j-1] = A[k-1]; } else { B[j+1] = A[k+1]; } ``` <details> <summary>Ans 1</summary> Here's the MIPS assembly code for question 1: ```assembly main: addi $t0, $s2, 3 # Calculate i + 3 sub $t0, $t0, $s4 # Calculate (i + 3) - k bgtz $t0, func # Jump if $t0 > 0 add $t1, $s3, $s6 # A base address + offset j * 4 add $t2, $s4, $s6 # A base address + offset k * 4 lw $t1, 0($t1) # Load A[j] from memory lw $t2, 0($t2) # Load A[k] from memory mul $t2, $t2, 3 # A[k] *= 3 add $t1, $t1, $t2 # Add A[j] with A[k] * 3 add $t3, $s1, $s7 # B base address + offset h * 4 sw $t1, 0($t3) # Store result in B[h] j end # Jump to end func: add $t0, $s3, $s6 # A base address + offset j lw $t0, 0($t0) # Load A[j] from memory addi $t1, $s2, 1 # Calculate i + 1 sll $t1, $t1, 2 # Calculate (i + 1) * 4 to get offset add $t1, $t1, $s7 # B base address + offset (i + 1) * 4 sw $t0, 0($t1) # Store A[j] in B[i + 1] end: ... ``` Remember to first add the index and then multiply to get the real offset. All labels can be named as you wish. </details> <details> <summary>Ans 2</summary> Here's the MIPS assembly code for question 2: ```assembly main: sll $t0, $s2, 2 # Calculate i * 4 to get offset add $t0, $t0, $s6 # A base address + offset i * 4 lw $t0, 0($t0) # Load A[i] from memory sll $t1, $t1, 2 # Calculate j * 4 to get offset add $t1, $t1, $s7 # B base address + offset j * 4 lw $t1, 0($t1) # Load B[j] from memory beq $t0, $t1, equal # Check if A[i] == B[j] sub $t0, $t0, $t1 # Calculate A[i] - B[j] bltz $t0, less # Check if A[i] - B[j] less than 0 add $t0, $s4, 1 # Calculate k + 1 sll $t0, $t0, 2 # Calculate (k + 1) * 4 to get offset add $t0, $t0, $s6 # A base address + offset (k + 1) * 4 lw $t0, 0($t0) # Load A[k+1] from memory add $t1, $s4, 1 # Calculate j + 1 sll $t1, $t1, 2 # Calculate (j + 1) * 4 to get offset add $t1, $t1, $s7 # B base address + offset (j + 1) * 4 sw $t0, 0($t1) # Store A[k+1] to B[j+1] j end # Jump to end equal: sll, $t0, $s3, 2 # Calculate k * 4 to get offset add $t0, $t0, $s6 # A base address + offset k * 4 lw $t0, 0($t0) # Load A[k] from memory sll $t1, $s4, 2 # Calculate j * 4 to get offset add $t1, $t1, $s7 # B base address + offset j * 4 sw $t0, 0($t1) # Store A[k] to B[j] less: sub $t0, $s4, 1 # Calculate k - 1 sll $t0, $t0, 2 # Calculate (k - 1) * 4 to get offset add $t0, $t0, $s6 # A base address + offset (k - 1) * 4 lw $t0, 0($t0) # Load A[k-1] from memory sub $t1, $s4, 1 # Calculate j - 1 sll $t1, $t1, 2 # Calculate (j - 1) * 4 to get offset add $t1, $t1, $s7 # B base address + offset (j - 1) * 4 sw $t0, 0($t1) # Store A[k-1] to B[j-1] end: ... ``` We can keep reusing registers if there are not holding important datas. </details> <br> ## Instruction Structure The instructions in MIPS have different format for R, I and J instruction, but they all consist with 4 bytes. For R-format, it has three register section and a shift section, where I-format only has two. ### R-format | opcode | rs | rt | rd | shamt | funct | |--------|-------|-------|-------|-------|-------| | 6 bits | 5 bits| 5 bits| 5 bits| 5 bits| 6 bits| ### I-format | opcode | rs | rt | immediate | |--------|-------|-------|------------------| | 6 bits | 5 bits| 5 bits| 16 bits | ### J-format | opcode | address | |--------|---------------------------------| | 6 bits | 26 bits | To convert instructions into binary, we still need a function table to look up. ### R-Type Instructions | Instruction | Funct Code | |-------------|------------| | `add` | 32 | | `sub` | 34 | | `and` | 36 | | `or` | 37 | | `sll` | 0 | | `srl` | 2 | | `jr` | 8 | ### I-Type Instructions | Instruction | Opcode | |-------------|-------------| | `lw` | 35 | | `sw` | 43 | | `addi` | 8 | | `beq` | 4 | | `bne` | 5 | ### J-Type Instructions | Instruction | Opcode | |-------------|-------------| | `j` | 2 | | `jal` | 3 | ### Example Convert the given instruction into binary `add $t0, $t0, $t3`: First look up to the given tables and get the following informations. `add` -> 32 `$t0` -> 8 `$t3` -> 11 Upon knowing that, we can now convert them in binary. | opcode | rs | rt | rd | shamt | funct | |--------|-------|-------|-------|-------|--------| | 000000 | 01000 | 01011 | 01000 | 00000 | 100000 | Binary: 00000001000010110100000000100000 Hex: 010B4020 <br> ## [Scenario]: Convert the code Convert the following code in binary and hex: 1. `add $t0, $s1, $s2` 2. `lw $t0, 1200($t1)` <details> <summary>Ans 1</summary> Here's the convert result for question 1: `add` -> 32 `$t0` -> 8 `$s1` -> 17 `$s2` -> 18 | opcode | rs | rt | rd | shamt | funct | |--------|-------|-------|-------|-------|--------| | 000000 | 01001 | 01010 | 01000 | 00000 | 100000 | Binary: 00000001001010100100000000100000 Hex: 02324020 </details> <details> <summary>Ans 2</summary> Here's the convert result for question 2: `lw` -> 35 `$t0` -> 8 `$t1` -> 9 | opcode | rs | rt | immediate | |--------|-------|-------|------------------| | 000000 | 01001 | 01010 | 0000010010110000 | Binary: 00000001001010100000010010110000 Hex: 12A04B0 </details> <br> ## Writing an assembly program Here's some important concept of MIPS assembly program: ### Passing arguments and return value Whenever we need to call another function and needs to pass arguments, we set them in `$a` registers. We have only 4 `$a` registers, so if we want to pass more then that, we need to store them in `$sp` like local variables, which is described below. Similarily, when we want to return something, we set them in `$v` registers, which is also shown in the case below. ### The usage of `$s` and `$t` registers In C programming language, we can esaily assign a local variable in a function, however it is not the case in MIPS assembly language. In MIPS, assembly language, if we want to store value in a locl variable, we need to use `$s` registers. We have a commen sense that "every variable should be stored in `$s` registers, and temporary values should be stored in `$t` vraiables". That means, if we want to transfer the C code `a = a * 2 + 1`, then as we need to first calculate a * 2 then add it with 1 and assigned back to `a`: - the final value of `a * 2 + 1` shoud be stored in `$s` register where `a` represent to (in this case, `$s0`) - `a * 2` should be stored in `$t` register as it is a temporary calculation value ### Store registers' values to `$sp` If we need to assign local variables in function, we need to save the origin `$s` in stack and assign it for local variable use, then restore it after the function finished it's actions. Here's an simple example. Assume that in the `main` function we assigned two variables as `a = 1` and `b = 2`, and the `printI` function assigned a variable as `i = 1`, and print out `i`. We need to store the `$s0` since we're using it and we wan't to make sure it saves the data as what it is after finishing the function call: ```assembly main: li $s0, 1 # a = 1 li $s1, 2 # b = 2 jal v printI: addi $sp, $sp, -4 # ask for memory sw $s0, 0($sp) # store origin value of $s0 li $s0, 1 # i = 1 li $v0, 1 # set system call to "print integer" move $a0, $s0 # setup print value syscall # do system call lw $s0, 0($sp) # restore value from stack back to $s0 addi $sp, $sp, 4 # return memory jr $ra ``` If we have an empty `$s` register (in this case `$s2`~`$s7`), we can actually just use them, but we prefer not to do that, and assume there's no empty `$s` registers so that we can always do the best case: saving it temporary and restore it after use. This example might seen rediculous, and it is indeed. It is only used to display the way we store `$s` registers' values. <br> ## MIPS assembly program examples We can write an MIPS assembly program using online tools or install Mars on local. Mars can help us monitor the registers when we're testing our program. Following up there will be some examples of MIPS program that I think is worth prcticing. ### Case 1: Simple BMI calculator We want to write a BMI calculator using MIPS, it is pretty much the most easiest one we'll encounter. User inputs height and weight to the program, and if we the height is -1, terminate the progrm. Here's the c code for it, we need to transform it to MIPS. ```c #include <stdlib.h> #include <stdio.h> int calculateBMI(int height, int weight) { int bmi = (weight * 10000) / (height * height); return bmi; } void printResult (int bmi) { if (bmi <= 17) printf("%s", "underweight\n"); else if (bmi >= 25) printf("%s", "overweight\n"); else printf("%d\n", bmi); } int main() { int height, weight, bmi; while (1) { scanf("%d", &height); if (height == -1) break; scanf("%d", &weight); bmi = calculateBMI(height, weight); printResult(bmi); } return 0; } ``` The anaswer is like the following(not necessry to be completely the same, as mny registers can be replaced and reuse): ```assembly .data underweightStr: .asciiz "underweight\n" overweightStr: .asciiz "overweight\n" newline: .asciiz "\n" .text main: j while_loop while_loop: li $v0, 5 # Input height syscall move $s0, $v0 # Set height li $t0, -1 # Check if height = -1 beq $s0, $t0 end li $v0, 5 # Input weight syscall move $s1, $v0 # Set weight move $a0, $s0 # Set arguments move $a1, $s1 addi $sp, $sp, -4 # Ask for memory jal calculateBMI # Call calculateBMI addi $sp, $sp, 4 # Return memory move $a0, $v0 # Set argument jal printResult # Call printResult j while_loop # Go back to loop calculateBMI: sw $s0, 0($sp) # Store height to stack mul $t0, $a1, 10000 # Calculate weight * 10000 mul $t1, $a0, $a0 # Calculate height * height div $t0, $t1 # $t0 / $t1 mflo $s0 move $v0, $s0 # Set return value lw $s0, 0($sp) # Load height from stack jr $ra printResult: li $t0, 17 ble $a0, $t0, printUnderweight li $t0, 25 bge $a0, $t0, printOverweight li $v0, 1 move $a0, $a0 syscall li $v0, 4 la $a0, newline syscall jr $ra printUnderweight: li $v0, 4 la $a0, underweightStr syscall jr $ra printOverweight: li $v0, 4 la $a0, overweightStr syscall jr $ra end: li $v0, 10 syscall ``` We can see that we need a local variable called `bmi` in `calculateBMI`, so we store `$s0` and let `$s0` become `bmi`. After finished the function, we restore the value back to `$s0` so that it consist(which is `height`). ### Case 2: Recursion operation We will be doing a recursion program to print out the first n elements in Fibonacci numbers. For each time we enter the `fact` function, we store the current values like the return address, local variables, arguments and even temporary sotrage. Here's the origin C code: ```C #include <stdio.h> void print(int size, int *x) { for (int i=0; i<size; i++) { printf("%d,",x[i]); } } int fact (int n, int *x) { int t=0; if (n < 2) { x[n] = t = 1; return t; } else { x[n] = t = fact(n - 1, x) + fact(n - 2, x); } return t; } int main() { int x[100]; int n; x[0]=1; for (int i=1; i<100; i++) x[i]=0; scanf("%d", &n); fact(n, x); print(n, x); return 0; } ``` The answer will look something like below, it looks complicate at first glance, but after several times of practices it will become more clear and confident. ```assembly .data data: .space 400 dot: .ascii "," .text main: # assign x[100] la $s0, data # x[0] = 1 li $t0, 1 sw $t0, 0($s0) li $t0, 100 # set counter $t0 to 100 add $s1, $s0, 4 # let $s1 points to x[1] jal loop # get user input to n li $v0, 5 syscall move $s1, $v0 # call fact move $a0, $s1 # set n to arguments move $a1, $s0 # set x to arguments jal fact # call print move $a0, $s1 # set n to arguments move $a1, $s0 # set x to arguments jal print li $v0, 10 syscall loop: # load 0 to x[i] li $t1, 0 sw $t1, 0($s1) addi $s1, $s1, 4 # i += 1 addi $t0, $t0, -1 # counter -= 1 bne $t0, 1, loop # jump to loop if not end jr $ra fact: addi $sp, $sp, -24 sw $t1, 20($sp) sw $t2, 16($sp) sw $ra, 12($sp) sw $a0, 8($sp) sw $a1, 4($sp) sw $s0, 0($sp) li $s0, 0 # t = 0 # check if n > 1 slti $t0, $a0, 2 beq $t0, $zero, L1 sll $t0, $a0, 2 add $t0, $a1, $t0 # x[n] li $s0, 1 # t = 1 sw $s0, 0($t0) # x[n] = t move $v0, $s0 addi $sp, $sp, 24 jr $ra L1: # calculate fact(n - 1, x) addi $a0, $a0, -1 jal fact move $t1, $v0 sw $t1, 20($sp) # calculate fact(n - 2, x) addi $a0, $a0, -1 jal fact move $t2, $v0 sw $t2, 16($sp) lw $t1, 20($sp) # restore values lw $t2, 16($sp) lw $ra, 12($sp) lw $a0, 8($sp) lw $a1, 4($sp) sll $t0, $a0, 2 add $t0, $a1, $t0 # x[n] add $s0, $t1, $t2 # t = fact(n - 1, x) + fact(n - 2, x) sw $s0, 0($t0) move $v0, $s0 lw $s0, 0($sp) # restore $s0 addi $sp, $sp 24 jr $ra print: addi $sp, $sp, -12 sw $ra, 8($sp) sw $s0, 4($sp) sw $a0, 0($sp) li $s0, 0 # i = 0 move $t0, $a0 # counter = size jal print_loop lw $ra, 8($sp) lw $s0, 4($sp) lw $a0, 0($sp) jr $ra print_loop: # print x[i] add $t1, $s0, $a1 # x[i] lw $a0, 0($t1) # setup int to print li $v0, 1 syscall # print "," li $v0, 4 la $a0, dot syscall addi $s0, $s0, 4 addi $t0, $t0, -1 bgt $t0, 0, print_loop jr $ra ``` Notice that even the temporary value `$t1` and `$t2`(used to store the left branch answer and right branch answer) are stored in stack since we will have multiple branch answer calcultion, and we will first calulate the left answer. Assume we're currently in fact(4), we need to clculate fact(3) -> fact(2) -> fact(1) -> back to fact(4) -> fact(2) -> fact(1). You'll see that the `$t1` and `$t2` will be rewrite multiple times, so if we get the left branch answer and did not store it, when we get in the right branh calculation, `$t1` will be overwrite and cause the final answer wrong. So in this kind of multiple layer functions, we should always consider to store every elements we need so that it restore to the right answer as it is when we get back to last layer. ### Case 3: Selection sort The main concept of this example is to sort an array with 5 elements. It is actually just array operations, but it contains nested loop. The C code is as follow: ```C #include <stdlib.h> #include <stdio.h> void selectionSort(int array[], int n) { for (int i=0; i<n-1; i++) { int min_idx = i; for (int j=i+1; j<n; j++) { if (array[j] < array[min_idx]) { min_idx = j; } } int temp = array[min_idx]; array[min_idx] = array[i]; array[i] = temp; } } int main() { int array[5]; for (int i = 0; i < 5; i++) { scanf("%d", &array[i]); } selectionSort(array, 5); for(inti=0;i<5;i++) { printf("%d\n", array[i]); } return 0; } ``` To consider when to store the values to stack, we can think if we need to use those registers. In our case we also store `$s2` and `$s3` even we didn't use them before, that is to make sure we always let them stay the same after use. The answer of the example is as follow: ```assembly .data data: .space 20 line: .asciiz "\n" .text main: la $s0, data li $t0, 0 # input loop index j loop loop: li $v0, 5 # set input syscall syscall sll $t1, $t0, 2 add $t1, $t1, $s0 # array[i] sw $v0, 0($t1) addi $t0, $t0, 1 # i++ bgt $t0, 4, L1 j loop L1: move $a0, $s0 # set array to arguments li $a1, 5 # set 5 to arguments jal selectionSort li $t0, 5 li $s1, 0 move $t1, $a0 # load array to $t1 j printLoop printLoop: add $t2, $t1, $s1 # array[i] lw $t2, 0($t2) # load array[i] li $v0, 1 # print number move $a0, $t2 syscall li $v0, 4 # print \n la $a0, line syscall addi $s1, $s1, 4 addi $t0, $t0, -1 bne $t0, 0, printLoop j end selectionSort: addi $sp, $sp -28 sw $a0, 24($sp) sw $a1, 20($sp) sw $ra, 16($sp) sw $s0, 12($sp) sw $s1, 8($sp) sw $s2, 4($sp) sw $s3, 0($sp) li $s0, 0 # i = 0 jal iLoop lw $a0, 24($sp) lw $a1, 20($sp) lw $ra, 16($sp) lw $s0, 12($sp) lw $s1, 8($sp) lw $s2, 4($sp) lw $s3, 0($sp) addi $sp, $sp 28 jr $ra iLoop: move $s1, $s0 # min_idx = i addi $s2, $s0, 1 # j = i + 1 addi $sp, $sp -4 sw $ra, 0($sp) jal jLoop sll $t0, $s0, 2 add $t0, $a0, $t0 # array[i] lw $t1, 0($t0) # load array[i] sll $t2, $s1, 2 add $t2, $a0, $t2 # array[min_idx] lw $t3, 0($t2) # load array[min_idx] move $s3, $t3 # temp = array[min_idx] sw $t1, 0($t2) # array[min_idx] = array[i] sw $s3, 0($t0) # array[i] = temp addi $s0, $s0, 1 # i++ lw $ra, 0($sp) addi $sp, $sp 4 blt $s0, 4, iLoop jr $ra jLoop: sll $t0, $s2, 2 add $t0, $a0, $t0 # array[j] lw $t0, 0($t0) # load array[j] sll $t1, $s1, 2 add $t1, $a0, $t1 # array[min_idx] lw $t1, 0($t1) # load array[min_idx] blt $t0, $t1, L2 # if array[j] < array[min_idx] j L3 L2: move $s1, $s2 L3: addi $s2, $s2, 1 bgt $s2, 4, L4 j jLoop L4: jr $ra end: li $v0, 10 syscall ``` I use a lot of `Ln` as label to concat the loop with function. It is not the only solution, but is useful so that we don't need store a lots of jump registers. ### Case 4: Transpose matrix In this example, we'll need to write to functions to transpose a matrix. It is basically normal array operation, since we're basically doing the same thing in assembly when the C code is trying to use array or pointer. Here's the C code of the example: ```C #include <stdlib.h> #include <stdio.h> void inputMatrix(int A[3][3]) { for(inti=0;i<3;i++) { for (int j = 0; j < 3; j++) { scanf("%d", &A[i][j]); } } } void transposeMatrixA1(int A[3][3], int T[3][3], int size) { for (int i = 0; i < size; i++) { for(intj=0;j<size;j++) { T[j][i] = A[i][j]; } } } void transposeMatrixA2(int *B, int *T, int size) { int *ptrB, *ptrT, i; for (ptrB=B, ptrT=T, i = 1; ptrB<(B + (size*size)); ptrB++) { *ptrT = *ptrB; if (i < size) { ptrT += size; i++; } else { ptrT -= (size * (size - 1) - 1); i = 1; } } } void outputMatrix(int A[3][3]) { for(inti=0;i<3;i++) { for(intj=0;j<3;j++) { printf("%d ", A[i][j]); } printf("\n"); } } int main() { int A[3][3]; int transposeOfA1[3][3]; int transposeOfA2[3][3]; int *ptrA = &A[0][0]; int *ptrTA2 = &transposeOfA2[0][0]; inputMatrix(A); transposeMatrixA1(A, transposeOfA1, 3); transposeMatrixA2(ptrA, ptrTA2, 3); outputMatrix(transposeOfA1); outputMatrix(transposeOfA2); return 0; } ``` As it contains lots of for loops, I use a lot of label to concat the instructions and jump between them. Look at the comments: ```assembly .data matrixSize: .space, 36 matrixSize2: .space, 36 matrixSize3: .space, 36 space: .asciiz, " " changeLine: .asciiz, "\n" .text main: la $s0, matrixSize # A[3][3] la $s1, matrixSize2 # transposeOfA1[3][3] la $s2, matrixSize3 # transposeOfA2[3][3] move $s3, $s0 # *ptrA = &A[0][0] move $s4, $s2 # *prtTA2 = &transposeOfA2[0][0] # inputMatrix(A) move $a0, $s0 jal inputMatrix # transposeMatrixA1(A, transposeOfA1, 3) move $a0, $s0 move $a1, $s1 li $a2, 3 jal transposeMatrixA1 # transposeMatrixA2(ptrA, ptrTA2, 3) move $a0, $s3 move $a1, $s4 li $a2, 3 jal transposeMatrixA2 # outputMatrix(transposeOfA1) move $a0, $s1 jal outputMatrix # outputMatrix(transposeOfA2) move $a0, $s2 jal outputMatrix move $a0, $s3 move $a1, $s4 li $v0, 10 syscall inputMatrix: addi $sp, $sp, -12 sw $s0, 8($sp) sw $s1, 4($sp) sw $ra, 0($sp) move $t0, $s0 # base address of A li $s0, 0 # i = 0 j L1 L1: addi $sp, $sp, -4 sw $ra, 0($sp) mul $t1, $s0, 12 # i * 12 li $s1, 0 # j = 0 jal L2 lw $ra, 0($sp) addi $sp, $sp, 4 addi $s0, $s0, 1 # i++ bne $s0, 3, L1 # if i != 3 lw $s0, 8($sp) lw $s1, 4($sp) lw $ra, 0($sp) addi $sp, $sp, 12 jr $ra L2: sll $t2, $s1, 2 add $t3, $t1, $t2 # i * 12 + j * 4 add $t3, $t3, $t0 # A[i][j] li $v0, 5 syscall sw $v0, 0($t3) addi $s1, $s1, 1 bne $s1, 3, L2 # if j != 3 jr $ra transposeMatrixA1: addi $sp, $sp, -12 sw $s0, 8($sp) sw $s1, 4($sp) sw $ra, 0($sp) li $s0, 0 # i = 0 jal L3 lw $s0, 8($sp) lw $s1, 4($sp) lw $ra, 0($sp) addi $sp, $sp, 12 jr $ra L3: addi $sp, $sp, -4 sw $ra, 0($sp) mul $t0, $s0, 12 # i * 12 li $s1, 0 # j = 0 jal L4 addi $s0, $s0, 1 lw $ra, 0($sp) addi $sp, $sp, 4 bne $s0, $a2, L3 # if i != size jr $ra L4: mul $t2, $s0, 4 # i * 4 mul $t3, $s1, 4 # j * 4 mul $t1, $s1, 12 # j * 12 add $t4, $t0, $t3 # i * 12 + j * 4 add $t5, $t1, $t2 # j * 12 + i * 4 add $t4, $t4, $a0 # A[i][j] lw $t4, 0($t4) # load A[i][j] add $t5, $t5, $a1 # T[j][i] sw $t4, 0($t5) # T[j][i] = A[i][j] addi $s1, $s1, 1 bne $s1, $a2, L4 # if j != size jr $ra transposeMatrixA2: addi $sp, $sp, -16 sw $s0, 12($sp) sw $s1, 8($sp) sw $s2, 4($sp) sw $ra, 0($sp) move $s0, $a0 # ptrB move $s1, $a1 # ptrT li $s2, 1 # i = 1 j L5 L5: lw $t0, 0($s0) # load *ptrB sw $t0, 0($s1) # *ptrT = *prtB blt $s2, $a2, L6 # if i < size j L7 # else L6: sll $t1, $a2, 2 # size * 4 add $s1, $s1, $t1 # ptrT += size addi $s2, $s2, 1 # i++ j L8 L7: addi $t1, $a2, -1 # size - 1 mul $t1, $t1, $a2 # size * (size - 1) addi $t1, $t1, -1 # size * (size - 1) - 1 sll $t1, $t1, 2 # (size * (size - 1) - 1) * 4 sub $s1, $s1, $t1 # ptrT -= (size * (size - 1) - 1) * 4 li $s2, 1 # i = 1 j L8 L8: mul $t1, $a2, $a2 # size * size sll $t1, $t1, 2 # size * size * 4 add $t1, $a0, $t1 # B + size * size * 4 addi, $s0, $s0, 4 # prtB += 4 blt $s0, $t1, L5 # if ptrB < (B + (size*size)) lw $s0, 12($sp) lw $s1, 8($sp) lw $s2, 4($sp) lw $ra, 0($sp) addi $sp, $sp, 16 jr $ra outputMatrix: addi $sp, $sp, -12 sw $s0, 8($sp) sw $s1, 4($sp) sw $ra, 0($sp) move $t0, $a0 # base address of A li $s0, 0 # i = 0 j L9 L9: addi $sp, $sp, -4 sw $ra, 0($sp) mul $t1, $s0, 12 # i * 12 li $s1, 0 # j = 0 jal L10 lw $ra, 0($sp) addi $sp, $sp, 4 addi $s0, $s0, 1 # i++ li $v0, 4 la $a0, changeLine syscall bne $s0, 3, L9 # if i != 3 lw $s0, 8($sp) lw $s1, 4($sp) lw $ra, 0($sp) addi $sp, $sp, 12 jr $ra L10: sll $t2, $s1, 2 add $t3, $t1, $t2 # i * 12 + j * 4 add $t3, $t3, $t0 # A[i][j] li $v0, 1 lw $a0, 0($t3) syscall li $v0, 4 la $a0, space syscall addi $s1, $s1, 1 bne $s1, 3, L10 # if j != 3 jr $ra ``` Make sure to check the memory when debugging. It helps a lot when we don't know what causes the answer to be wrong. Setting breakpoints and move step forward is also an important way of finding the problem.