https://spimsimulator.sourceforge.net/
Tip: Vim Configuration for Assembly
" Show tabs set list " Never expand tabs to spaces set noexpandtab " Make tabs take up 8 spaces set tabstop=8 set shiftwidth=8
Note: In this class we’ll be learning 32-bit MIPS, not 64-bit MIPS.
MIPS Instructions:
Note: MIPS Instruction Set
- Stanford MIPS commercialised by MIPS Technologies
- Similar ISAs have a large share of embedded core market
- e.g., consumer electronics, network/storage, equipment, cameras, printers, vacuums, etc.
- Embedded: When the CPU is inside the system.
- Remember: MIPS is a 3-register instruction machine
- (When we use an instruction that doesn’t use 3 operands, we’re actually looking at a pseudo-instruction.)
Name | Register | Usage |
---|---|---|
$zero | $0 | Always 0 |
$at | $1 | Reserved for assembler use |
$v0 —$v1 | $2 —$3 | Result values of a function |
$a0 —$a3 | $4 —$7 | Arguments of a function |
$t0 —$t7 | $8 —15 | Temporary values |
$s0 —$s7 | $16 —$23 | Saved registers |
$t8 —$t9 | $24 —$25 | More temporaries |
$k0 —$k1 | $26 —$27 | Reserved for OS kernel |
$gp | $28 | Global pointer |
$sp | $29 | Stack pointer |
$fp | $30 | Frame pointer |
$ra | $31 | Return address |
Notes:
$zero
is a read-only register.$at
stands for “assembler temporary”.
- In general, we can’t/won’t be using
$at
- Note how we can only return two values from a function (
$v0
—$v1
)
Registers:
$t0
—$t7
: Reg’s 8—15$t8
—$t9
: Reg’s 24—25$s0
—$s7
: Reg’s 16—23
Mnemonic | Operands | Instruction | Register Transfer | Type | Op/Funct |
---|---|---|---|---|---|
add | rd, rs, rt | Add | rd = rs + rt | R | 0/20 |
sub | rd, rs, rt | Subtract | rd = rs - rt | R | 0/22 |
addi | rt, rs, imm | Add Imm. | rt = rs + imm± | I | 8 |
addu | rd, rs, rt | Add Unsigned | rd = rs + rt | R | 0/21 |
subu | rd, rs, rt | Subtract Unsigned | rd = rs - rt | R | 0/23 |
addiu | rt, rs, imm | Add Imm. Unsigned | rt = rs + imm± | I | 9 |
mult | rs, rt | Multiply | {hi, lo} = rs * rt | R | 0/18 |
mul | rd, rs, rt | Multiply without overflow | rd = rs * rt | R | c/2 |
div | rs, rt | Divide | lo = rs / rt; hi = rs % rt | R | 0/1a |
multu | rs, rt | Multiply Unsigned | {hi, lo} = rs * rt | R | 0/19 |
mulu | rd, rs, rt | Multiply without overflow, uns | rd = rs * rt | R | 0/19 |
divu | rs, rt | Divide Unsigned | lo = rs / rt; hi = rs % rt | R | 0/1b |
mfhi | rd | Move From HJ | rd = hi | R | 0/10 |
mflo | rd | Move From LO | rd = lo | R | 0/12 |
mthi | rs | Move to HI | hi = rs | R | 0/11 |
mtlo | rs | Move to LO | lo = rs | R | 0/13 |
and | rd, rs, rt | And | rd = rs & rt | R | 0/24 |
or | rd, rs, rt | Or | rd = rs | rt | R | 0/25 |
nor | rd, rs, rt | Nor | rd = ̃(rs | rt) | R | 0/27 |
xor | rd, rs, rt | eXclusive Or | rd = rs ˆ rt | R | 0/26 |
andi | rt, rs, imm | And Imm. | rt = rs & imm0 | I | c |
ori | rt, rs, imm | Or Imm. | rt = rs | imm0 | I | d |
xori | rt, rs, imm | eXclusive Or Imm. | rt = rs ^ imm0 | I | e |
sll | rd, rt, sh | Shift Left Logical | rd = rt << sh | R | 0/0 |
srl | rd, rt, sh | Shift Right Logical | rd = rt >>> sh | R | 0/2 |
sra | rd, rt, sh | Shift Right Arithmetic | rd = rt >> sh | R | 0/3 |
sllv | rd, rt, rs | Shift Left Logical Variable | rd = rt << rs | R | 0/4 |
srlv | rd, rt, rs | Shift Right Logical Variable | rd = rt >>> rs | R | 0/6 |
srav | rd, rt, rs | Shift Right Arithmetic Variable | rd = rt >> rs | R | 0/7 |
slt | rd, rs, rt | Set if Less Than | rd = rs < rt ? 1 : 0 | R | 0/2a |
sltu | rd, rs, rt | Set if Less Than Unsigned | rd = rs < rt ? 1 : 0 | R | 0/2b |
slti | rt, rs, imm | Set if Less Than Imm. | rt = rs < imm\pm ? 1 : 0 | I | a |
sltiu | rt, rs, imm | Set if Less Than Imm. Unsigned | rt = rs < imm\pm ? 1 : 0 | I | b |
j | addr | Jump | PC = PC &0xF0000000 | (addr0<< 2) | J | 2 |
jal | addr | Jump And Link | $ra = PC + 8; PC = PC&0xF0000000 | (addr0<< 2) | J | 3 |
jr | rs | Jump Register | PC = rs | R | 0/8 |
jalr | rs | Jump And Link Register | $ra = PC + 8; PC = rs | R | 0/9 |
beq | rt, rs, offset | Branch if Equal | if (rs == rt) PC += 4 + (imm\pm << 2) | I | 4 |
bne | rt, rs, offset | Branch if Not Equal | if (rs \ne rt) PC += 4 + (imm\pm << 2) | I | 5 |
syscall | System Call | c0_cause = 8 << 2; c0_epc = PC; PC = 0x80000080 | R | 0/c | |
lui | rt,imm | Load Upper Imm. | rt = imm << 16 | I | f |
lb | rt,imm(rs) | Load Byte | rt = SignExt(M1[rs + imm±]) | I | 20 |
lbu | rt,imm(rs) | Load Byte Unsigned | rt = M1[rs + imm±] & 0xFF | I | 24 |
lh | rt,imm(rs) | Load Half | rt = SignExt(M2[rs + imm±]) | I | 21 |
lhu | rt,imm(rs) | Load Half Unsigned | rt = M2[rs + imm±] & 0xFFFF | I | 25 |
lw | rt,imm(rs) | Load Word | rt = M4[rs + imm±] | I | 23 |
sb | rt,imm(rs) | Store Byte | M1[rs + imm±] = rt | I | 28 |
sh | rt,imm(rs) | Store Half | M2[rs + imm±] = rt | I | 29 |
sw | rt,imm(rs) | Store Word | M4[rs + imm±] = rt | I | 2b |
Note: Using
mult
- Multiplying two number with
mult
requires two instructions, one to multiply (mult
) and another to read the multiplication result from theHI
orLO
register (mfhi
ormflo
).- If the resulting number doesn’t overflow (is less than 32-bits), you can use
mflo
to get it from theLO
register.What
hi
andlo
contain:
lo
: Containsrs / rt
hi
: Containsrs % rt
Example :
li $t1, 2 # A: Multiplies t0 and t1 mult $t0, $t1 mflo $t0 # B: Same thing, using mul mul $t0, $t0, $t1
Note:
move
v.s.li
move
moves the value of one register into another,li
puts an immediate value directly into a register.
Pseudo-Instructions: Instructions that don’t have a direct hardware implementation.
Pseudo | Operands | Instruction | Register Transfer |
---|---|---|---|
move | rd, rs | Move | rd = rs |
li | rd,imm | Move immediate | rd = imm |
la | rd, label | Load address | rd = &label |
mul | rd, rs, src | Multiply (no overflow) | rd = rs * src |
div | rd, rs, src | Divide | rd = rs / src |
rem | rd, rs, src | Remainder | rd = rs % src |
add | rs, rd, imm | Add immediate, use addi | rd = rd + imm |
add | rd, imm | Add immediate | rd += imm |
sub | rd, rs, src | Subtract immediate | rd = rs – imm |
sub | rd imm | Subtract immediate | rd -= imm |
b | offset | Branch | goto offset |
beqz | rs, label | Branch on equal zero | if (rs == 0) goto label |
bnez | rs, label | Branch on not equal zero | if (rs \ne 0) goto label |
bgez | rs, label | Branch on Greater Than or Equal to Zero | if (rs \ge 0) goto label |
bgtz | rs, label | Branch on Greater Than Zero | if (rs > 0) goto label |
blez | rs, label | Branch on Less Than or Equal to Zero | if (rs \le 0) goto label |
bltz | rs, label | Branch on Less Than Zero | if (rs < 0) goto label |
beq | rs, src, label | Branch on equal | if (rs == src) goto label |
bne | rs, src, label | Branch on not equal | if (rs \ne src) goto label |
bge | rs, src, label | Branch on greater than equal | if (rs \ge src) goto label |
bgt | rs, src, label | Branch on greater than | if (rs > src) goto label |
ble | rs, src, label | Branch on less than equal | if (rs \le src) goto label |
blt | rs, src, label | Branch on less than | if (rs < src) goto label |
seq | rd, rs, src | Set equal | rd = rs == src ? 1 : 0 |
sne | rd, rs, src | Set not equal | rs = rt \ne src ? 1 : 0 |
sge | rd, rs, src | Set greater than equal | rs = rt \ge src ? 1 : 0 |
sgt | rd, rs, src | Set greater than | rs = rt > src ? 1 : 0 |
sle | rd, rs, src | Set less than equal | rs = rt \le src ? 1 : 0 |
slt | rd, rs, src | Set less than | rs = rt < src ? 1 : 0 |
Example: This pseudo-instruction
move $t0, $s0
is translated into this real instruction:addu $t0, $zero, $s0
Name | op | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|---|
Bits | 6 | 5 | 5 | 5 | 5 | 6 |
op
: Opcoders
: First source register numberrt
: Second source register numberrd
: Destination register numbershamt
: Shift amount00000
for nowfunct
: Function codeName | op | rs | rt | imm |
---|---|---|---|---|
Bits | 6 | 5 | 5 | 16 |
op
: Opcoders
: First source register numberrt
: Second source register numberimm
: Immediate valueName | op | addr |
---|---|---|
Bits | 6 | 26 |
op
: Opcodeaddr
: Address (of a label).Related Notes: Hexadecimal, Number Systems (CS2640)
Example: Encoding an assembly instruction into hexadecimal
add $t0, $t1, $t2
Converting Registers to Binary:
$t0
: 01000 (rd)
- (Register 8)
$t1
: 01001 (rs)- (Register 9)
$t2
: 01010 (rt)- (Register 10)
According to the reference table, opcode and func is
0/32
, therefore:
- The opcode is
000000
- The func is
100000
As this isn’t a shift instruction,
shamt
is 000000.Putting it all together: \begin{aligned} \text{R-Format: }& \text{op $+$ rs $+$ rt $+$ rd $+$ shamt $+$ funct} \\ \text{Binary: }& 0000 0001 0010 1010 0100 0000 0010 0000 \\ \text{Hexadecimal: }& 012A4020 \end{aligned}
Example: Decoding hexadecimal into an assembly instruction \begin{aligned} \text{Hexadecimal: }& 012A4020 \\ \text{First Six Binary Digits: }& 0000 00 \\ \text{Last Six Binary Digits: }& 10 0000 \\ \end{aligned}
- So we know
op
is 0- So we know
funct
is 32Now that we know the
op/fn
, we know the type and command, and can convert the rest of the hexadecimal into binary and convert the rest.
Assembly Line Format:
[ label: ] opcode [ operand(s) ]
#
Design Principle 1: Simplicity favors regularity.
- Regularity makes implementation simpler
- Simplicity enables higher performance at lower cost.
Arithmetic Operations: Have three operands.
Example: Arithmetic Operations
# a <- b + c add a,b,c
Example: Arithmetic in C and MIPS
= (g + h) - (i + j) f
# t0 <- g + h add $t0,$s1,$s2 # t1 <- i + j add $t1,$s3,$s4 # f <- t0 - t1 sub f,$t0,$t1
- Note how we have to handle the order of operations ourselves and split equations into smaller terms.
Design Principle 2: Smaller is faster
Anchor Link: Register Set Reference
Arithmetic instructions use register operands.
MIPS has a 32 \times 32-bit register file.
Assembler Names:
Label: Symbolic name for a memory address. Can be an instruction or data.
main
..text
main: add $t2,$t0,$t1
Segment: Logical part of code that translates to a specific memory location.
Directives: Tell the assembler how to organize data.
Name | Parameters | Description |
---|---|---|
.data | addr | Data segment. |
.text | addr | Text segment. |
.kdata | addr | Kernel data segment. |
.ktext | addr | Kernel text segment. |
.extern | sym size | Declare as global label sym |
.globl | sym | Declare as global the label sym |
Example: Using a label.
count: .word 0 # end
Example: Using
.text
.text main: li $t0,10 li $t1,20 add $a0,$t0,$t1 # end
li
: Load immediate. Is a pseudo-instruction.
- The assembler will turn
li $t0,10
intoaddi $t0,$zero,$t0
.data
.word n # 32-bit
.byte nn # 8 bit
.ascii '?' # ASCII string
.asciiz "$$$" # zero-terminated ASCII string
.half n # 16-bit
.space n # n bytes
.word
Use the lw
(load word) command to load a value from a word into a register.
Use the sw
(store word) command to load a value from a register back into a word.
Example: Loading a word into a register
.data
sumIs: .asciiz "The sum is "
value1: .word 15
value2: .word 25
sum: .word 0
.text
main:
lw $t0, value1
lw $t1, value2
# Print string
la $a0, sumIs
li $v0, 4
syscall
# Add integers and store in sum
add $t2, $t0, $t1
sw $t2, sum
# Print result
lw $v0, sum
li $v0, 1
syscall
# Exit
li $v0, 10
syscall
# End of program
Syscall: Special instruction that interfaces with the I/O subsystem.
Services | System Call Code | Arguments | Result |
---|---|---|---|
print_int | 1 | $a0=integer | |
print_float | 2 | $f12=float | |
print_double | 3 | $f12=double | |
print_string | 4 | $a0=string | |
read_int | 5 | integer (in $v0) | |
read_float | 6 | float (in $f0) | |
read_double | 7 | double (in $f0) | |
read_string | 8 | $a0=buffer,$a1=length | |
sbrk | 9 | $a0=amount | address (in $v0) |
exit | 10 | ||
print_char | 11 | $a0=char | |
read_char | 12 | char (in $a0) | |
open | 13 | $a0=filename (string), $a1=flags, $a2=mode | file descriptor (in $a0) |
read | 14 | $a0=file descriptor, $a1=buffer, $a2=length | num chars read (in $a0) |
write | 15 | $a0=file descriptor, $a1=buffer, $a2=length | num chars written (in $a0) |
close? | 16 | $a0=file descriptor | |
exit2 | 17 | $a0=result |
Note: More on some syscalls
- print_int: Passes an integer and prints it on the console.
- print_float: Prints a single floating point number.
- print_double: Prints a double precision number.
- print_string: Passes a pointer to a null-terminated string.
- read_int, read_float, read_double: Read an entire line of input up to and including a newline.
- read_string: Same semantics as the UNIX library routine
fgets
.
- Reads up to
n-1
characters into a buffer and terminates the string with a null byte. If there are fewer characters on the current line, it reads through the newline and again null-terminates the string.- sbrk: returns a pointer to a block of memory containing n additional bytes
- exit: stops program execution
Example: Using some syscalls
.data str: .asciiz "Enter an integer to double: " .text main: # Print a string (syscall 4) la $a0, str # Put address to the string in $a0 li $v0, 4 syscall # Read an integer (syscall 5) li $v0, 5 syscall move $t0, $v0 # Now move the number from $v0 to $t0 # Double user's integer li $t1, 2 mult $t0, $t1 mflo $t0 # Print the resulting integer (syscall 5) move $a0, $t0 li $v0, 1 syscall # Exit Program li $v0, 10 syscall # End of program
# This program does nothing and exits gracefully.
.text
main:
li $v0, 10
syscall
# The end
Example: Adding two numbers
.data sumIs: .asciiz "The sum is " .text main: li $t0, 15 li $t1, 25 # Print string la $a0, sumIs li $v0, 4 syscall # Add integers and print to console add $a0, $t0, $t1 li $v0, 1 syscall # Print newline li $a0, '\n' li $v0, 11 syscall # Exit li $v0, 10 syscall
Important: Don’t forget to print a newline character before exiting the program!
beq rt, r, label
bne rd, rs, label
if
statement, it also maintains the order of if
and else
in the code.Important: We will not be using the jump instruction.
Example: Basic if in C v.s. MIPS
- C
if (a != o) { // Do output }
- MIPS
# If zero, jump to the endif label beqz $t0, endif # Do output endif:
Example: Basic if in C v.s. MIPS
- C
if (t0 < 10) { // Do output }
- MIPS
# If zero, jump to the endif label bge $t0, 10, endif # Do output endif:
Guideline: To work with multiple
endif
labels, will just add a number to the end (e.g.,endif0:
,endif1:
, etc.)
Example: Branched if in C v.s. MIPS
- C
if (t0 < 10) { // output 1 } else { // output 2 }
- MIPS
bge $t0, 10, else # output 1 b endif else: # output 2 endif:
Example: Else-if in C v.s. MIPS
- C
if (t0 == 0) { ++; t0} else { ++; t1}
- MIPS
if: bnez $0, else addi $t0, 1 b endif else: addi $t1, 1 endif:
To loop, we’ll use a register to control the loop
Example: A while statement
.data hello: .asciiz "hello\n" .text main: li $t0, 1 # Initialize loop control register while: bgt $t0, 10, endw # While loop la $a0, hello li $v0, 4 syscall addi $t0, 1 # increment loop control register b while endw: li $v0, 10 # End of while loop syscall # end of program
Example: Nesting an if statement inside a while statement
.data hello: .asciiz "hello\n" .text main: li $t0, 1 # Initialize loop control register while: bgt $t0, 10, endw # While loop # Print loop counter variable if it is less than 5 bge $t0, 5, endif move $a0, $t0 li $v0, 1 syscall endif: la $a0, hello li $v0, 4 syscall addi $t0, 1 # increment loop control register b while endw: li $v0, 10 # End of while loop syscall # end of program
Example: While-loop in C v.s. MIPS
- C
int t1=0; int t0=1; while (t0 <= 100) { += t0; t1 ++; t0}
- MIPS
li $t1, 0 li $t0, 1 while: bgt $t0, 100, endw addi $t1, $t1, $t0 addi $t0, 1 b while endw:
These commands are used by bge
, bgt
, ble
, and blt
seq
sne
sge
sgt
sle
slt
Relevant Notes:
Example: Checking if a number is even using a mask
li $t1, 1 # Mask the LSBIT and $t2, $t0, $t1 # If $t2 is zero, then t0 is even bnez $t2, endif # t0 is even, do whatever. endif:
Example: Checking if a number is divisible by four
li $t1, 3 # Mask last two LSBITS (this is 11 in binary) and $t2, $t0, $t1 # If $t2 is zero, then t0 is divisible by four bnez $t2, endif # t0 is divisble by 4 (continue calculations here) endif:
- (If you list out every possible value for 4 bits, you’ll be able to verify this.)
Note: MSBIT Mask: 0x8000000
- (Use this to check if a signed integer is negatie)
By default, integers are signed,
Beware: Unsigned operations don’t generate traps on overflow, while signed operations will generate traps on overflow.
Example: Translating C to MIPS
- C
#include <stdio.h> int sum; int n = 100; int main() { register int i = 1; = 0; sum while (i <= n) { += i; sum ++; i} ("%d\n", sum); printf}
- One-to-one conversion to MIPS
.data sum: .word 0 n: .word 100 .text main: li $t0, 1 # t0: i sw $zero, sum lw $t1, n while: bgt $t0, $t1, endw # sum += i lw $t2, sum add $t2, $t2, $t0 sw $t2, sum # i++ addi $t0, $t0, 1 b while endw: lw $a0, sum li $v0, 1 syscall # Exit li $v0, 10 syscall # End of program
- This code is very inefficient, it reads and writes to memory excessively.
- MIPS (Better)
.data sum: .word 0 n: .word 100 .text main: li $t0, 0 lw $t1, n li $t2, 1 while: bgt $t2, $t1, endw add $t0, $t0, $t2 addi $t2, $t2, 1 b while endw: sw $t0, sum lw $a0, sum li $v0, 1 syscall # Exit li $v0, 10 syscall # End of program
- This is not a one-to-one translation of the C code, but it only writes to memory once, making it much more efficient.
- Or, you could use the explicit form: n(n+1)/2 so that the program is O(1) instead of O(n)
.data sum: .word 0 n: .word 100 .text main: # t0 <- n(n+1)/2 lw $t0, n addi $t1, $t0, 1 # n + 1 mul $t0, $t0, $t1 # * (n + 1) sra $t0, $t0, 1 # / 2 # Print and exit sw $t0, sum lw $a0, sum li $v0, 1 syscall li $v0, 10 syscall # End of program