Instruction Set and Assembly Language
This section describes all the instructions and pseudo-instructions supported by the PLP system. It also gives examples on how to use each instruction and notes on any limitations.
Comments
Comments may appear anywhere in the program’s code, including on label, instruction, and directive lines.
To use a comment, type #
before your comment. All text after the #
until the end of the line is ignored by the assembler.
- For example, if you wanted to put a comment on a blank line as well as a instruction line, the code would be:
# This is the first comment
addu $s0, $s0, $s1 # This is the second comment
Note: Comments are very helpful for debugging and helping others who read the code to understand what a certain segment of code is supposed to do.
Values
Some instructions require a value to be given. PLPTool will accept values in 4 formats: binary (base 2), decimal (base 10), hexadecimal (base 16), and ASCII (converts from ascii encoding to a value). The representation is indicated using a prefix before the value.
Representation | Format | Sample Usage | Notes |
---|---|---|---|
Binary | 0b <value> |
0b10110 | Binary values can only contain 1’s and 0’s |
Decimal | <value> | 1975 | A negative value can be represented by including a - symbol before the value. The value will be represented in two’s complement |
Hexadecimal | 0x <value> |
0xfc10 | The prefix, 0h , can also be used for hexadecimal values |
ASCII | ' <character>' |
‘a’ | ASCII strings (more than one ASCII character) can only be used with assembler directives |
Symbols
This document uses the following symbols and expressions throughout, refer here if you come accross something that is not familiar.
=
- equals+
- plus-
- minus/subtract*
- multiply>>
- signed shift right<<
- shift left&
- bitwise AND|
- bitwise OR~
- inverse/bitwise NOTval = (expr) ? tr : fl
- this is a simplified version of an if-then-else statement- if
(expr)
is true, thenval
is set totr
. if(expr)
is false, thenval
is set tofl
- if
<
- less than==
- is equal to!=
- is NOT equal toSignExtend(val)
- take the valueval
and sign extend it to the required bit size(see Signed & Unsigned for more info)ZeroExtend(val)
- take the valueval
and zero extend it to the required bit size(see Signed & Unsigned for more info)
Assembler Directives
Memory Organization
In order to resolve branch and jump targets, the user must inform the assembler where program starts in memory before any instructions, labels, or includes are written/executed.
The format for this would be .org
followed by the address in memory desired. The address must be word aligned, meaning it must a 32-bit number that is a multiple of 4.
- For example, to begin the program at the address 0x10000000, the code would be:
.org 0x10000000
IMPORTANT NOTE: This must be the first non-comment line in the main source file. It is possible, however, to have multiple .org statements throughout the program.
Data and String Allocation
There are three ways to allocate space for data with PLPTool:
- A single word
- Space in terms of numbers of words
- A string
Single Word Allocation
The .word
directive allocates a single word with or without an initial value. This is especially useful after a label for ease of access.
- For example, to allocate a variable and initialize it to the value
4
, the code would be:
.org 0x10000000
my_variable:
.word 4
main:
li $t0, my_variable # get a pointer to my_variable
lw $t1, 0($t0) # $t1 has the value of my_variable (4) now
Space Allocation
PLPTool supports allocating space by taking the number of words to be allocated by using the .space
directive, as opposed to a single word with the .word
directive.
- For example, to allocate a variable with a length of 2 words, the code would be:
.org 0x10000000
long_variable:
.space 2
main:
li $t0, long_variable # get a pointer to the variable
lw $t1, 0($t0) # get the first word
lw $t2, 4($t0) # get the second word
String Allocation
PLPTool supports three types of string allocation:
.ascii
- This allocates a packed array of characters without a trailing null character (terminator) to indicate the end of the string.
For example, if you wanted to allocate a variable with a string using the .ascii
directive, the code would be:
my_string_ascii:
.ascii "example string" # no null terminator
.asciiz
- This allocates a packed array of characters with a trailing null character that indicates the end of the string.
For example, if you wanted to allocate a variable with a string using the .asciiz
directive, the code would be:
my_string_asciiz:
.asciiz "example string" # null terminator inserted at end of string
.asciiw
- This allocates a word aligned array of characters with a trailing null character that indicates the end of the string.
For example, if you wanted to allocate a variable with a string using the .asciiw
directive, the code would be:
my_string_asciiw:
.asciiw "example string" # word aligned, null terminator inserted at end of string
Note: PLPTool also supports escaping newline characters with \n
.
Labels
Labels allow the programmer to use branch and jump instructions. A label is used to mark sections of code within the program.
To implement a label, type the name of label you wish to use followed by a colon.
- For example, to create a label called “main”, the code would be:
.org 0x10000000
main:
# PLP instructions here
- It is the standard convention to have the first label in a program titled “main”.
Note: It is possible to load a pointer to a label using the load immediate instruction li
.
.org 0x10000000
main:
# PLP instructions here
label2:
li $t0 , main
Operations
Below is the list of all operations within PLP, broken down into sections by their type. The syntax, an example, a representative expression, and any notes are provided. Hover over the operation to see the exapanded title.
Arithmetic Operations
These operations allow for basic arithmetic, such as addition and subtraction, within PLP.
IT SHOULD BE NOTED that addu
, addiu
, and subu
are mislabeled. The trailing u
normally implies unsigned. However, all three of these operations are signed.
Syntax | Expression | Sample Usage | Notes |
---|---|---|---|
addu $rd, $rs, $rt |
rd = rs + rt; |
addu $v0, $a0, $a1 |
Unsigned addition(see above) |
addiu $rd, $rs, imm |
rd = rs + SignExtend(imm); |
addiu $v0, $a0, 0xFEED |
Unsigned addition(see above), add $a0 with 65261 |
subu $rd, $rs, $rt |
rd = rs - rt; |
subu $v0, $a0, $a1 |
Unsigned subtraction(see above) |
mullo $rd, $rs, $rt |
rd = (rs * rt) & 0xFFFFFFFF; |
mullo $v0, $a0, $a1 |
Low order bits of 64-bit multiplication result (bits [31:0]) |
mulhi $rd, $rs, $rt |
rd = (rs * rt) >> 32; |
mulhi $v0, $a0, $a1 |
High order bits of 64-bit multiplication result (bits [61:32]) |
lui $rt, imm |
rt = imm << 16; |
lui $a0, 0xFEED |
Write 0xFEED0000 to $a0 register. |
$rd
is the destination register, where the resulting value will go.
$rs
is the source registers: this is the value that the operation acts upon.
$rt
is the target register: this is the value that the operation uses.
imm
is a 16-bit integer that can be represented by any of the methods given by PLPTool.
It should be noted that lui
is not used by itself very often. Instead, ori
is used in its place, or lui
is used as part of the psuedo-operation li
.
IMPORTANT NOTE: If imm
is greater than 16 bits, the assembler will truncate the more significant bit positions beyond the sixteenth place. This means the maximum immediate value is 65535.
Example
# main source file
.org 0x10000000
# Arithmetic Examples
main:
# Load values to use
li $t0 , 0b100 # loading 4 into $t0 using binary notation
li $t1 , 0xF # loading 15 into $t1 using hex notation
li $t2 , 8 # loading 8 into $t2
# add
addu $t3 , $t1 , $t0 # adds $t1(15) and $t0(4) and stores into $t3
# result in $t3 is now 19
addu $t3 , $t3 , $t2 # adds $t3(19) and $t2(8) and stores into $t3
# note, you can use the same register for deistination, source, and target
# result in $t3 is now 27
addiu $t3 , $t3 , 3 # add an immediate value to $t3, making it 30, storing ing $t3
addiu $t3 , $t3 , -10 # add a negative ten to $t3, store in $t3, now 20
# note the lack of subiu, add handles both immediate value operations
# multiply
mullo $t4 , $t3 , $t3 # multiply $t3(27) and $t3(27), store the LOWER 8 bytes(1 word)
li $t0 , 65535 # load 65535 into $t0 (0xFFFF)
li $t1 , 65535 # load 65535 into $t1 (0xFFFF)
mullo $t2 , $t0 , $t1 # multiply $t0 and $t1, store LOWER word into $t2
mulhi $t3 , $t0 , $t1 # multiply $t0 and $t1, store UPPER word into $t3
# it should be noted that mullo and mulhi are deeply related
# if the product of mullo overflows(is higher than you can represented with a signed integer), mulhi will return the sign bit, along with the rest of the bits
# $t2 will have 4294836225(0xFFFE0001)
# $t3 will have 0(0x00000000)
# NOTE: the most significant bit here is the sign bit(0) due to overflow
# to read the whole number, stack the hex digits like so
# 0x00000000 0xFFFE0001
# UPPER LOWER
# 0x00000000FFFE0001 (4294836225)
# TOTAL
# another example, using negatives
li $t0 , -45 # load a negative value(-45) into $t0
li $t1 , 295 # load 295 into $t1
mulhi $t3 , $t0 , $t1 # multiply $t0 and $t1, store HIGH order bits in $t3
mullo $t2 , $t0 , $t1 # multiply $t0 and $t1, store LOW order bits in $t2
# here, we get a negative result. since mullo and mulhi are SIGNED operations, the result will be represented in two's complement
# $t2 will have 0xFFFFCC25(-13275) this is the lower bits of the result, in two's complement
# $t3 will have 0xFFFFFFFF(-1) this is the higher bits of the result, in two's complements, this value is not used alone
# note that $t3 is all 1's(F is 1111 in decimal). these leading 1's do not modify the value of a negative number, just as leading 0's do not modify the value of a positive number
# the final result would be 0xFFFFFFFFFFFFCC25(-13275)
# one more example, with large numbers and a negative output
li $t0 , 87578778 # load a large number into $t0
li $t1 , -87578778 # load a large negative number(small) into $t1
mullo $t2 , $t0 , $t1 # multiply, low order bits
mulhi $t3 , $t0 , $t1 # multiply, high order bits
# here, the upper bits are neccessary to properly represent the value
# $t2 will have 0x19F5C35C(435536732) note how this is not negative by itself, it needs the upper bits to be complete
# $t3 will have 0xFFE4C023(-1785821) these two numbers, when combined, show the real result
# 0xFFE4C02319F5C35C(-7670042355973284) this is the real result
Note: clipboard access is not available on all platforms, results may vary.
Logical/Bitwise Operations
These operations allow for basic logical/bitwise operations, such as AND or OR, to be preformed on values and registers.
Syntax | Expression | Sample Usage | Notes |
---|---|---|---|
and $rd, $rs, $rt |
rd = rs & rt; |
and $v0, $a0, $a1 |
Bitwise logical AND |
andi $rd, $rs, imm |
rd = rs & ZeroExtend(imm); |
andi $v0, $a0, 1337 |
Bitwise logical AND |
or $rd, $rs, $rt |
rd = rs | rt; |
or $v0, $a0, $a1 |
Bitwise logical OR |
ori $rd, $rs, imm |
rd = rs | ZeroExtend(imm); |
ori $v0, $a0, 0x0539 |
Bitwise logical OR |
nor $rd, $rs, $rt |
rd = ~(rs | rt); |
nor $v0, $a0, $a1 |
Bitwise logical NOR |
slt $rd, $rs, $rt |
rd = (rs < rt) ? 1 : 0; |
slt $v0, $a0, $a1 |
Signed compare |
slti $rd, $rs, imm |
rd = (rs < SignExtend(imm)) ? 1 : 0; |
slti $v0, $a0, 0xDEAD |
Signed compare |
sltu $rd, $rs, $rt |
rd = (rs < rt) ? 1 : 0; |
sltu $v0, $a0, $a1 |
Unsigned compare |
sltiu $rd, $rs, imm |
rd = (rs < SignExtend(imm)) ? 1 : 0; |
sltiu $v0, $a0, 0xDEAD |
Unsigned compare |
sll $rd, $rt, shamt |
rd = rt << shamt; |
sll $v0, $a0, 0x12 |
Shift $a0 by 18 to the left and store the result in $v0 |
sllv $rd, $rs, $rt |
rd = rs << rt; |
sllv $v0 , $a0 , $a1 |
Shift $a0 by $a1 to the left and store the result in $v0 |
srl $rd, $rt, shamt |
rd = rt >> shamt; |
srl $v0, $a0, 18 |
Shift $a0 by 18 to the right and store the result in $v0 |
srlv $rd, $rs, $rt |
rd = rs >> rt; |
srlv $v0 , $a0 , $a1 |
Shift $a0 by $a1 to the right and store the result in $v0 |
$rd
is the destination register, where the resulting value will go.
$rs
is the source registers: this is the value that the operation acts upon.
$rt
is the target register: this is the value that the operation uses.
imm
is a 16-bit integer that can be represented by any of the methods given by PLPTool.
shamt
is a 5-bit integer that can be represented by any of the methods given by PLPTool.
IMPORTANT NOTE: If the shift amount value is greater than 5 bits, the assembler will truncate the more significant positions beyond the fifth bit. This means the maximum shift amount is 31.
IMPORTANT NOTE: If imm
is greater than 16 bits, the assembler will truncate the more significant bit positions beyond the sixteenth place. This means the maximum immediate value is 65535.
EXAMPLE
# main source file
.org 0x10000000
# Logical Examples
main:
# load values to use below
li $t0 , 0b110101
li $t1 , 0b001100
# AND
and $t2 , $t0 , $t1 # ANDs $t0 and $t1 to get 0b000100 (8)
andi $t2 , $t0 , 0b000011 # and $t0 with 0b000011 to get 0b000001 (1)
# OR
ori $t3 , $t0 , 0b111111 # OR $t0 and 0b111111 to get 0b111111 (63)
or $t3 , $t0 , $t1 # OR $t0 and $t1 to get 0b111101 (61)
# NOR
nor $t4 , $t0 , $t1 # NOR $t0 and $t1 to get 0b000010 with leading 1s
# less than
li $t0 , 30
li $t1 , -16
slt $t5 , $t0 , $t1 # if $t0 is less than $t1, $t5 will be 1, else it will be 0
# since slt is signed, this will return 0
sltiu $t5 , $t0 , -2 # unsigned comparison means that -2 is greater than 30, $t5 will be 1
# -2 is 0xFFFFFFFE and 30 is 0x00000000E
# shift
li $t0 , 0b10001101 # load $t0 with a value, represented in binary
li $t1 , 4 # load $t1 with a value to shift by
li $t6 , 0x8000000F # load negative value
sllv $t7 , $t0 , $t1 # shift $t0(0b10001101) left $t1(4) bits, result will be 0b100011010000
srl $t8 , $t6 , 3 # shift $t6(0x8000000F) right 3 bits, should result in (0x10000001)
srlv $t9 , $t6 , $t1 # shift $t6 right 4 bits
# note the result of this is NOT negativem srl and sll are UNSIGNED operations
Note: clipboard access is not available on all platforms, results may vary.
Control Flow Operations
These operations allow for a program to execute instructions in a non-linear fashion. Without them it would only be possible to run instructions in order from the first instruction to the last instruction in memory. Control flow operations allow a program to execute instructions from a specified location in a program either conditional or unconditionally.
IMPORTANT NOTE: There is a “branch delay slot” immediately after all control flow instruction (jumps and branchs). The next line of code following the jump/branch will always be executed along with the jump/branch. To avoid complications, it is generally advisable to put a no-operation instruction (nop
) immediately after the jump/branch instruction, unless the branch delay slot needs to be utilized.
Conditional (Branch)
In a high level language, conditional statements are typically written as if statements. In PLP, branch instructions are used for conditional execution. Branch instructions use two registers and a label. They check the equalitiy of the value in two specified registers. There are two types of branch instructions, branch-if-equal (beq
) and branch-if-not-equal (bne
). A beq
instruction will begin executing instructions at the specified label if the two register values are equal. A bne
instruction will begin executing instructions at the specified label if the two register values are not equal. If the condition of a branch instruction was met then it is common to say, “the branch was taken”.
Branching based on an inequality requires the use of two instructions, a set-less-than instruction (slt
) and a branch instruction. The branch instruction can be used to compare the result of the slt
with the zero register ($0
).
Syntax | Expression | Sample Usage | Notes |
---|---|---|---|
beq $rt, $rs, label |
if(rt == rs) PC = PC + 4 + imm; |
beq $a0, $a1, done |
Branch to done if $a0 and $a1 are equal |
bne $rt, $rs, label |
if(rt != rs) PC = PC + 4 + imm; |
bne $a0, $a1, error |
Branch to error if $a0 and $a1 are NOT equal |
Unconditional (Jump)
A jump instruction always goes to a given in the instruction (some languages call this a GOTO instruction). PLP has several types of jump instructions. The simplist jump (j
) goes to the label given in the instruction. The other jump instructions use registers and allow for more sophisticated control flow.
The jump-and-link (jal
) instruction saves the address of the instruction following the instruction’s branch delay slot into register $ra
(return address). The jump-register (jr
) instruction can be used to jump back to this return address. This can be taken advantage of to write pieces of code, after a label, that can be used from multiple locations in the program. This reusable piece of code is often called a subroutine.
Syntax | Expression | Sample Usage | Notes |
---|---|---|---|
j label |
PC = jump_target; |
j loop |
Jump to loop label |
jal label |
ra = PC + 4; PC = jump_target; |
jal read_serial |
Jump to read_serial after saving return address to $ra |
jr $rs |
PC = rs; |
jr $ra |
Load the content of $ra into PC register |
jalr $rd, $rs |
rd = PC + 4; PC = rs; |
jalr $s5, $t0 |
Jump to location gien by contents of rs , save return address in rd . |
label
is the name of a label somewhere in the program, usually a string.
$rs
is the source registers: this is the value that the operation acts upon.
$rd
is the destination register, where the resulting value will go.
$rt
is the target register: this is the value that the operation uses.
EXAMPLE
# main source file
.org 0x10000000
# Jump and Branch examples
li $t0 , 250 # load 250 into $t0
li $t1 , 100 # load 100 into $t1
li $t3 , 300 # load 300 into $t3
li $s4 , fun2 # load the address of fun2 into $s4
li $s0 , main # load the address of main into $s0
main:
beq $t0 , $t1 , end # if $t0 and $t1 are equal, branch to end label
nop # nop in branch delay slot
jalr $ra, $s4 # jump and link to the label in $s4, store the current PC in $ra
ori $t4 , $0 , 5 # use branch delay slot to load 5 into $t4 using ori
slt $t4 , $t0 , $t3 # compare $t0 to $t3, store result(0 or 1) in $t4
bne $t4 , $zero , func1 # if $t4 is NOT 0, branch to func1
nop # nop in branch delay slot
j main
nop
fun2:
addu $t1 , $t1 , $t4 # add $t4 to $t1, store result in $t1
jr $ra # jump to the memory address in $ra
nop # nop in branch delay slot
end:
j end # an infinite loop
nop # common in programs that use interrupts
func1:
addiu $t3 , $t3 , -10 # add -10 to $t3, store value in $t3
jal fun2 # jump and link to fun2
ori $t4 , $zero , 5 # branch delay slot to load 5 into $t4
j main # jump to main
nop # nop in branch delay slot
Note: clipboard access is not available on all platforms, results may vary.
Data-Transfer Operations
These operations allow a value to be copied from memory into a register and from a register to memory. Both instructions have the same basic syntax and only differ in the direction data is being copied. Each requires the use of two registers: one register is where data is either being copied to or from and the other register contains the memory address where data is being transfered to or from (it is being used as a pointer).
The first data-transfer operations is load word (lw), which loads or reads a value from a memory address into a register. The diagram below shows how this instruction works. Before the instruction is run, the second register in the instruction, $t0 in this example, must already contain the memory address to be read from. In this case it has the value 0x100000f4
so this is the memory address that will be read from. The lw instruction will copy the value at this address into the first register, $t1.
The second data-transfer operation is store word (sw), which stores or writes a value from a register into memory address. At the assembly level, memory (RAM) is used as storage for data that is not currently being used because instructions no single instruction, other than data-transfer instructions, can use or modify data kept in memory. For this reason an easy way to remember the difference in direction between load word and store word is that store word is putting a value in storage (memory) for later use.
NOTE: If you are trying to copy a value from one register to another you are most likely looking for the move pseudo-op.
Instruction | Example | Meaning | Notes |
---|---|---|---|
load word | lw $t0, 8($t1) |
$t0 = Memory[$t1 + 8] |
A memory address is calculated by adding the value in $t1 with 8. The value at this address is copied into $t0 . |
store word | sw $t0, 12($t1) |
Memory[$t1 + 12] = $t0 |
A memory address is calculated by adding the value in $t1 with 12. The value in $t0 copied into this address. |
EXAMPLE
# main source file
.org 0x10000000
main:
li $t0 , 0x1000F000 # load a memory address into $t0
li $t1 , 0x55 # load a value into $t1
sw $t1 , 0($t0) # store the value from $t1 into the memory location in $t0 with an offset of 0
# to break this down a bit
# $t1 is the register in which the value is located
# $t0 is the register where the memory location is located
# 0 is the byte offset
# in the end, the value of $t1 will be placed in the memory location of $t0 + 0
lw $t2 , 0($t1) # load the value from the memory address stored in $t1 with an offset of 0 into the $t2 register
# to break this down a bit
# $t2 is the register in which the value will be loaded into
# $t1 is the register where the memory location is located
# 0 is the byte offset
# in the end, the value of $t2 will be loaded from the memory location of $t1 + 0
Note: clipboard access is not available on all platforms, results may vary.
Pseudo-Operations
The PLP assembler supports several pseudo-operations to make programming easier. The following pseudo-operations are supported by PLP:
Pseudo-op | Equivalent instruction(s) | Notes |
---|---|---|
nop |
sll $0, $0, 0 |
No-operation. Can be used for branch delay slots |
b label |
beq $0, $0, label |
Branch always to label |
move $rd, $rs |
add $rd, $0, $rs |
Copy $rs to $rd |
push $rt |
addiu $sp, $sp, -4; sw $rt, 4($sp) |
Push $rt into the stack |
pop $rt |
lw $rt, 4($sp); addiu $sp, $sp, 4 |
Pop data from the top of the stack to $rt |
li $rd, imm |
lui $rd, (imm & 0xff00) >> 16; ori $rd, imm & 0x00ff |
Load a 32-bit number to $rd |
li $rd, label |
lui $rd, (imm & 0xff00) >> 16; ori $rd, imm & 0x00ff |
Load the address of a label to a register to be used as a pointer. |
call label |
Save $aX, $tX, $sX, and $ra to stack and call function | |
return |
Restore $aX, $tX, $sX, and $ra from stack and return | |
save |
Save all registers except for $zero to stack | |
restore |
Restore all registers saved by ‘save’ in reverse order | |
lwm $rt, imm32/label |
Load the value from a memory location into $rt | |
swm $rt, imm32/label |
Store the value in $rt to a memory location |
Registers Names and Conventions
Aside from $zero, $at, $iv, $ir, $sp and $ra, PLP does not explicitly assign special functions to a register. This section lays down some conventions on how the other registers should be used. All libraries included with PLPTool adhere to these conventions.
Register | Usage | Notes |
---|---|---|
$zero |
Constant value 0 | This register can not be written to and always returns the value 0 |
$at |
Assembler temporary | Assembler reserved, do not use |
$v0 - $v1 |
Values for results | Use for return values of functions |
$a0 - $a3 |
Arguments | Use for arguments of functions |
$t0 - $t9 |
Temporaries | Do not use these registers across function calls, as they will most likely be corrupted |
$s0 - $s7 |
Saved temporaries | These registers should be saved and restored after function calls |
$i0 - $i1 |
Interrupt temporaries | Use inside Interrupt Service Routine (ISR) |
$iv |
Interrupt vector | The CPU jumps to the address pointed by this register when an interrupt occurs |
$sp |
Stack pointer | Use this register to implement a stack |
$ir |
Interrupt return address | Written by the CPU when an interrupt occurs |
$ra |
Return address | Do not manually write to this register unless restoring from the stack for nested function calls. Use this register to return from a function using the jump register instruction |