Assembly language
This page describes the assembly language spimbot assembles. Simulator describes the machine a program runs on.
Source files
#Each line holds zero or more statements separated by ;. A # outside a string or character literal starts a comment that runs to the end of the line. A statement is any number of labels followed by at most one directive, instruction or constant definition.
# Prints "hello" and exits.
.data
hello: .asciiz "hello\n"
.text
main:
li $v0, 4 # print_string
la $a0, hello
syscall
jr $ra
Mnemonics are case-insensitive. Directive names, labels, constants and register names are case-sensitive, and register names are lowercase.
A name starts with a letter, _ or . and continues with letters, digits, _ and .. name: defines a label at the address of the next item.
Operands are separated by commas, and the commas are required.
Numbers and strings
#Integers are decimal (42, and 010 is ten) or hexadecimal (0x2a). Write negative numbers with a unary minus. A character literal such as '*' is one byte. A floating-point literal has a point with digits on both sides, an exponent, or both, such as 1.5, 2e3 and 6.02e-23. Only .float, .double, li.s and li.d accept one.
Strings are double-quoted and stay on one line. Strings and character literals take the C escapes, such as \n, \t, \\, \" and \0.
Registers
#General-purpose registers are written $ and a number from 0 to 31, or $ and a name:
| Number | Name | Use |
|---|---|---|
$0 |
$zero |
Always 0 |
$1 |
$at |
Assembler temporary, which expansions use |
$2-$3 |
$v0-$v1 |
Return values. $v0 selects the syscall |
$4-$7 |
$a0-$a3 |
Arguments |
$8-$15 |
$t0-$t7 |
Caller-saved temporaries |
$16-$23 |
$s0-$s7 |
Callee-saved |
$24-$25 |
$t8-$t9 |
Caller-saved temporaries |
$26-$27 |
$k0-$k1 |
Reserved for the exception handler |
$28 |
$gp |
Global pointer |
$29 |
$sp |
Stack pointer |
$30 |
$fp, $s8 |
Frame pointer |
$31 |
$ra |
Return address |
Floating-point registers are $f0-$f31, and condition codes are $fcc0-$fcc7 or a number from 0 to 7. Coprocessor 0 and floating-point control registers are written by number: $12 is Status in mfc0 $t0, $12, and $31 is FCSR in cfc1 $t0, $31.
Expressions
#Any integer operand is an expression over numbers, labels and constants, such as table+8 or end-start. Parentheses group subexpressions. The operators, from highest precedence to lowest, are:
- Unary
-and+ *and/, with/rounding toward zero- Binary
+and- >>, an arithmetic shift right
Operands such as shift amounts, bit-field positions and sizes, alignments, .space sizes, repeat counts, segment addresses, lui and trap immediates, and integer values in .float and .double take no labels. A constant defined earlier in the same file without labels works there.
Constants
#name = expression or .set name, expression defines a constant.
ROWS = 16
COLS = ROWS * 2
.data
grid: .space ROWS * COLS
Constants are scoped like labels, as Multiple files describes.
Segments
#A segment directive switches to its segment, at the address it gives or where the segment left off:
| Directive | Segment | Starts at | Address range |
|---|---|---|---|
.text [addr] |
User code | 0x00400000 |
0x00400000-0x0fffffff |
.data [addr], .rdata [addr], .sdata [addr] |
User data | 0x10010000 |
0x10000000-0x7fffffff |
.ktext [addr] |
Kernel code | 0x80000000 |
0x80000000-0x8fffffff |
.kdata [addr] |
Kernel data | 0x90000000 |
0x90000000-0xfffeffff |
The memory map shows how much of these ranges a program can use. Instructions go only in .text and .ktext, and data directives work in any segment. Items that overlap are an error.
Multiple files
#spimbot assembles every file you give it into one program, in the order given. Each file starts in .text, and each segment continues where the previous file left it.
Labels and constants are local to their file. .globl name or .global name exports a name the file defines to every file, and a file's own definition takes precedence over an exported one. Exporting a name the file does not define, or a name another file already exports, is an error. main is exported from the first file that defines it unless a file exports it with .globl.
Common and external symbols
#.comm name, size reserves size zeroed bytes for name at the next word-aligned address in .data and exports name. .lcomm does the same without exporting. Neither changes the current segment.
.extern name[, size] makes name global. When the file does not define name and no other file exports it, a nonzero size gives name zeroed storage in the 64 KiB small-data area at 0x10000000, which $gp addresses.
Directives
#| Directive | Arguments | Meaning |
|---|---|---|
.text, .data, .rdata, .sdata, .ktext, .kdata |
[address] |
Switch segment. See Segments |
.byte |
value, … |
8-bit integers, -128 to 255 |
.half |
value, … |
16-bit integers, -32,768 to 65,535, aligned to 2 |
.word |
value, … |
32-bit integers, -2,147,483,648 to 4,294,967,295, aligned to 4 |
.float |
value, … |
Single-precision floats, aligned to 4 |
.double |
value, … |
Double-precision floats, aligned to 8 |
.ascii |
"string", … |
Each string's bytes |
.asciiz |
"string", … |
Each string's bytes and a zero byte |
.space |
n |
n zero bytes |
.align |
n |
Advance to the next multiple of 1 << n bytes, n from 1 to 16. .align 0 turns off automatic alignment until the next segment directive |
.globl, .global |
name, … |
Export names from this file |
.extern |
name[, size] |
Declare a global name. See Common and external symbols |
.comm, .lcomm |
name, size |
Reserve zeroed storage in .data |
.lab |
name |
Define the label name here |
.struct |
offset |
Start a structure layout, described below |
.repeat, .rept |
count |
Repeat the statements up to the matching .endr count times. Blocks nest |
.endr |
End a .repeat block |
|
.set |
name, value |
Define a constant |
.err |
Report an error at this line | |
.set (other forms), .ent, .end, .file, .loc, .frame, .mask, .fmask, .option, .verstamp, .vreg, .livereg, .bgnb, .endb, .alias, .noalias, .asm0 |
Anything | Ignored |
A data directive value can repeat as value:count: .word 0:8 is eight zero words, and .byte 1, 2:3 is the bytes 1, 2, 2, 2. A value that does not fit its directive's size is an error.
Each repetition of a .repeat block defines its labels and constants again, so a block that repeats more than once cannot define any.
.struct offset starts a layout in which labels name offsets from offset. Its data directives advance the offset and write no data, so their values must be zero. The layout ends at the next segment directive or the end of the file.
.struct 0
point_x: .word 0
point_y: .word 0
point_size:
.text
get_y:
lw $v0, point_y($a0) # $a0 holds a point's address
jr $ra
Here point_y is 4 and point_size is 8. These names are labels, so point_y($a0) expands through $at.
Instructions
#The assembler accepts the MIPS32 Release 2 instruction set with its floating-point and coprocessor instructions. In the tables, rd, rs and rt are general-purpose registers, fd, fs, ft and fr floating-point registers, imm an immediate, sa a shift amount and addr a memory operand.
Arithmetic
#| Mnemonics | Operands | Operation |
|---|---|---|
add, addu, sub, subu |
rd, rs, rt |
Sum or difference. add and sub raise Overflow on signed overflow |
addi, addiu |
rt, rs, imm |
As add and addu, with a sign-extended 16-bit immediate |
slt, sltu |
rd, rs, rt |
1 if rs < rt, signed or unsigned, else 0 |
slti, sltiu |
rt, rs, imm |
As slt and sltu, with a sign-extended 16-bit immediate |
mul |
rd, rs, rt |
Low 32 bits of the product. HI and LO keep their values |
clz, clo |
rd, rs |
Count of leading zero or one bits |
seb, seh |
rd, rt |
Sign-extended low byte or halfword |
wsbh |
rd, rt |
Bytes swapped within each halfword |
lui |
rt, imm |
imm in the upper halfword, zeros below |
movz, movn |
rd, rs, rt |
Copy rs to rd if rt is zero, or nonzero |
movf, movt |
rd, rs[, cc] |
Copy rs to rd if floating-point condition cc (default 0) is false, or true |
Logic and bit fields
#| Mnemonics | Operands | Operation |
|---|---|---|
and, or, xor, nor |
rd, rs, rt |
Bitwise operation |
andi, ori, xori |
rt, rs, imm |
Bitwise operation with a zero-extended 16-bit immediate |
ext |
rt, rs, pos, size |
The size-bit field of rs at bit pos, zero-extended |
ins |
rt, rs, pos, size |
The low size bits of rs inserted into rt at bit pos |
A bit field has at least 1 bit and fits in 32 bits: pos + size is at most 32.
Shifts and rotates
#sll, srl, sra and rotr take rd, rt, sa and shift or rotate right rt by the constant sa, 0 to 31. sllv, srlv, srav and rotrv take rd, rt, rs and shift or rotate by the low 5 bits of rs. Each shift and rotate assembles to its register or constant form to match the amount: sll rd, rt, rs is sllv. nop, ssnop and ehb take no operands and do nothing.
Multiply and divide
#| Mnemonics | Operands | Operation |
|---|---|---|
mult, multu |
rs, rt |
64-bit product in HI (high word) and LO (low word) |
madd, maddu, msub, msubu |
rs, rt |
Product added to or subtracted from HI:LO |
div, divu |
rs, rt |
Quotient in LO, remainder in HI. Division by zero leaves HI and LO unchanged |
mfhi, mflo |
rd |
Copy HI or LO to rd |
mthi, mtlo |
rs |
Copy rs to HI or LO |
Branches
#| Mnemonics | Operands | Branches if |
|---|---|---|
beq, bne |
rs, rt, label |
rs == rt, rs != rt |
blez, bgtz, bltz, bgez |
rs, label |
rs compared with zero, signed |
bltzal, bgezal |
rs, label |
As bltz and bgez, and write the return address to $ra, taken or not |
beql, bnel, blezl, bgtzl, bltzl, bgezl, bltzall, bgezall |
As above | As above, and skip the delay slot when not taken |
bc1t, bc1f, bc1tl, bc1fl |
[cc,] label |
Floating-point condition cc (default 0) is true or false |
A branch reaches 32,768 instructions either way. For a farther target, use j.
Jumps
#| Mnemonics | Operands | Operation |
|---|---|---|
j |
label or rs |
Jump. j rs is jr rs |
jal |
label, rs or rd, rs |
Jump and write the return address to $ra, or to rd. The register forms are jalr |
jr |
rs or label |
Jump to the address in rs. jr label is j label |
jalr |
rs, rd, rs or label |
Jump to the address in rs and write the return address to rd (default $ra). jalr label is jal label |
j and jal reach the 256 MiB region of the instruction after the jump. For a farther target, use jr.
Loads and stores
#| Mnemonics | Operands | Operation |
|---|---|---|
lb, lbu, lh, lhu, lw |
rt, addr |
Load a byte, halfword or word. lbu and lhu zero-extend, the others sign-extend |
lwl, lwr |
rt, addr |
Load the left or right part of an unaligned word, merged into rt |
ll |
rt, addr |
Load a word and start an atomic sequence |
sb, sh, sw |
rt, addr |
Store a byte, halfword or word |
swl, swr |
rt, addr |
Store the left or right part of an unaligned word |
sc |
rt, addr |
Store a word if the atomic sequence holds. rt becomes 1 on success, 0 on failure |
lwc1, ldc1, swc1, sdc1 |
ft, addr |
Load or store a floating-point word or doubleword |
lwxc1, ldxc1, swxc1, sdxc1 |
fd, index(base) |
Load or store at base + index |
An indexed operand such as $t1($t0) has no space before the (. Alignment rules are in Bus errors and alignment. cache, pref, prefx, synci and sync assemble and do nothing.
Traps and system calls
#| Mnemonics | Operands | Operation |
|---|---|---|
teq, tne, tge, tgeu, tlt, tltu |
rs, rt[, code] |
Raise Trap if the comparison holds |
teqi, tnei, tgei, tgeiu, tlti, tltiu |
rs, imm |
Raise Trap if the comparison with the immediate holds |
syscall |
[code] |
Perform the syscall $v0 selects |
break |
[code] |
Raise Breakpoint |
teq rs, imm is teqi, and likewise for the other register traps.
Coprocessor 0
#| Mnemonics | Operands | Operation |
|---|---|---|
mfc0, mtc0 |
rt, $n[, sel] |
Read or write coprocessor 0 register n, select 0 to 7 (default 0) |
eret |
Return from an exception to EPC | |
ei, di |
[rt] |
Enable or disable interrupts. rt receives the previous Status |
wait |
[code] |
Stall until an interrupt can be taken |
Floating point
#Floating-point mnemonics end in a format: .s single, .d double or .w 32-bit integer.
| Mnemonics | Formats | Operands | Operation |
|---|---|---|---|
add, sub, mul, div |
.s, .d |
fd, fs, ft |
Arithmetic |
abs, neg, mov |
.s, .d |
fd, fs |
Absolute value, negation, copy |
sqrt, recip, rsqrt |
.s, .d |
fd, fs |
Square root, reciprocal, reciprocal square root |
madd, msub, nmadd, nmsub |
.s, .d |
fd, fr, fs, ft |
fs * ft + fr, fs * ft - fr, and their negations |
movz, movn |
.s, .d |
fd, fs, rt |
Copy if rt is zero, or nonzero |
movf, movt |
.s, .d |
fd, fs[, cc] |
Copy if condition cc is false, or true |
c.cond |
.s, .d |
[cc,] fs, ft |
Set condition cc (default 0) to the comparison |
cvt.s, cvt.d |
Another of .s, .d, .w |
fd, fs |
Convert to single or double |
cvt.w, round.w, trunc.w, ceil.w, floor.w |
.s, .d |
fd, fs |
Convert to a 32-bit integer: current rounding mode, nearest, toward zero, up, down |
mfc1, mtc1, mfhc1, mthc1 |
rt, fs |
Copy a word between rt and the low or high word of fs |
|
cfc1, ctc1 |
rt, $n |
Read or write control register n |
The conditions are f, un, eq, ueq, olt, ult, ole and ule, and the signaling sf, ngle, seq, ngl, lt, nge, le and ngt, which also signal Invalid on a quiet NaN.
A double names an even register and the odd register after it. The 64-bit integer format .l and the paired-single format .ps run only with Status.FR set, as Bare mode and 64-bit registers describes. Rounding and exceptions are in Floating-point unit.
Immediate and addressing forms
#These forms of real instructions assemble to another real instruction or expand through $at:
- Two operands:
addi $t0, 5isaddi $t0, $t0, 5.addi,addiu,slti,sltiu,andi,ori,xori,add,addu,sub,subu,and,or,xor,nor,slt,sltu,sllv,srlvandsravtake this form. - An immediate or label as the last operand of
add,addu,sub,subu,and,or,xor,nor,slt,sltu,movz,movnandmul. A constant that fits the matching immediate instruction uses it:add $t0, $t1, 5isaddi $t0, $t1, 5. Any other value expands toli $at, valueand the register instruction. - An immediate as
rtofmult,multu,div,divu,madd,maddu,msub,msubu,beq,bne,beqlandbnel: 0 is$zero, and any other value goes into$atwithli. - A load or store address as
offset(base),(base),label,label+offset,label(base)or an absolute address. A constant offset from -32,768 to 32,767 fits the instruction:lw $t0, 0x100islw $t0, 0x100($zero). A label or a larger offset expands tolui $at, high, thenaddu $at, $at, basewhen there is a base, then the access with the low half as offset.
Pseudo-instructions
#Where the table says rt/imm, rt can be an immediate: 0 is $zero, and any other value goes into $at first with li $at, imm.
| Pseudo-instruction | Expansion |
|---|---|
li rd, imm |
One addiu, ori or lui when the value allows, else lui $at, high; ori rd, $at, low. A value with labels always takes two instructions. The value is any 32-bit pattern |
la rd, label |
As li rd, label |
la rd, offset(base) |
addiu rd, base, offset when offset is a constant that fits 16 bits, else lui $at, high; ori $at, $at, low; addu rd, base, $at |
move rd, rs |
addu rd, $zero, rs |
not rd, rs |
nor rd, rs, $zero |
neg rd, rs |
sub rd, $zero, rs |
negu rd, rs |
subu rd, $zero, rs |
abs rd, rs |
sra $at, rs, 31; xor rd, rs, $at; sub rd, rd, $at |
b label, bal label |
beq $zero, $zero, label, bgezal $zero, label |
beqz rs, label, bnez rs, label |
beq rs, $zero, label, bne rs, $zero, label |
blt rs, rt/imm, label |
slt $at, rs, rt; bne $at, $zero, label, or slti $at, rs, imm; bne $at, $zero, label when imm is a nonzero constant from -32,768 to 32,767 |
bgt rs, rt/imm, label |
slt $at, rt, rs; bne $at, $zero, label, or slti $at, rs, imm+1; beq $at, $zero, label when imm is a nonzero constant and imm+1 is from -32,768 to 32,767 |
ble rs, rt/imm, label |
slt $at, rt, rs; beq $at, $zero, label, or slti $at, rs, imm+1; bne $at, $zero, label when imm is a nonzero constant and imm+1 is from -32,768 to 32,767 |
bge rs, rt/imm, label |
slt $at, rs, rt; beq $at, $zero, label, or slti $at, rs, imm; beq $at, $zero, label when imm is a nonzero constant from -32,768 to 32,767 |
bltu, bgeu |
As blt and bge, with sltu and sltiu |
bgtu, bleu |
As the register forms of bgt and ble, with sltu |
seq rd, rs, rt/imm |
xor rd, rs, rt; sltiu rd, rd, 1 |
sne rd, rs, rt/imm |
xor rd, rs, rt; sltu rd, $zero, rd |
sgt rd, rs, rt/imm |
slt rd, rt, rs |
sge rd, rs, rt/imm |
slt rd, rs, rt; xori rd, rd, 1 |
sle rd, rs, rt/imm |
slt rd, rt, rs; xori rd, rd, 1 |
sgtu, sgeu, sleu |
As sgt, sge, sle, with sltu |
ror rd, rs, sa/rt |
rotr rd, rs, sa or rotrv rd, rs, rt |
rol rd, rs, sa/rt |
rotr rd, rs, (32 - sa) & 31, or subu $at, $zero, rt; rotrv rd, rs, $at |
div, divu, rem, remu with rd, rs, rt |
teq rt, $zero, 7; div rs, rt; mflo rd, with divu in the unsigned forms and mfhi in rem and remu |
The same with rd, rs, imm |
li $at, imm; div rs, $at; mflo rd, with no zero check. A constant 0 is an error |
The same with $zero as rd |
The division alone, with no zero check and no mflo or mfhi |
mulo rd, rs, rt/imm |
mult rs, rt; mfhi $at; mflo rd; sra rd, rd, 31; tne rd, $at, 6; mflo rd, which traps on signed overflow |
mulou rd, rs, rt/imm |
multu rs, rt; mfhi $at; mflo rd; tne $at, $zero, 6, which traps on unsigned overflow |
ulw rd, addr, usw rs, addr |
lwl rd, offset+3(base); lwr rd, offset(base), or the same with swl and swr |
ulh rd, addr |
lb rd, offset+1(base); lbu $at, offset(base); sll rd, rd, 8; or rd, rd, $at |
ulhu rd, addr |
As ulh, with lbu for the high byte |
ush rs, addr |
sb rs, offset(base); srl $at, rs, 8; sb $at, offset+1(base) |
ld rt, addr, sd rt, addr |
Two lw or sw of rt and the next register, at offset and offset+4 |
l.s, l.d, s.s, s.d |
lwc1, ldc1, swc1, sdc1 |
li.s fd, value |
li $at, bits; mtc1 $at, fd |
li.d fd, value |
li $at, low; mtc1 $at, fd; li $at, high; mthc1 $at, fd |
mfc1.d rt, fs, mtc1.d rt, fs |
mfc1 or mtc1 of rt, then mfhc1 or mthc1 of the next register |
With a label, an offset that does not fit 16 bits, or a load whose base is its destination, ulw, ulh, ulhu, usw, ush, ld and sd put the address in $at first and access it at offset 0.
The $at register
#An instruction whose expansion uses $at cannot name $at in any operand, including a memory operand's base. The exceptions are the destinations of li, la, lb, lbu, lh, lhu, lw and ll. An instruction whose expansion does not use $at can name it anywhere.
Bare mode and 64-bit registers
#check and exec take two assembler options. Matches always assemble bots with both off.
--bare requires each statement to be exactly one real instruction under its own mnemonic. Pseudo-instructions, forms that assemble to another instruction (add $t0, $t1, 5, jr label) and addresses that need $at are errors. Two-operand forms such as addi $t0, 5 are accepted.
--fp64 lets 64-bit floating-point operands name odd registers, for programs that set Status.FR to get 32 registers of 64 bits. See Floating-point unit.
Errors
#The assembler reports every error at once, sorted by file, line and column, with the source line, carets under the problem and a note when a fix is likely. Errors that belong to no line, such as a missing main or overlapping items, come last. When there is any error, nothing runs.