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.

Back to the top

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

Back to the top

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 NOT
  • val = (expr) ? tr : fl - this is a simplified version of an if-then-else statement
    • if (expr) is true, then val is set to tr. if (expr) is false, then val is set to fl
  • < - less than
  • == - is equal to
  • != - is NOT equal to
  • SignExtend(val) - take the value val and sign extend it to the required bit size(see Signed & Unsigned for more info)
  • ZeroExtend(val) - take the value val and zero extend it to the required bit size(see Signed & Unsigned for more info)

Back to the top

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.

Back to the top

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

Back to the top

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

Back to the top

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

Back to the top

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 .

Back to the top

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

Back to the top

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.

Back to the top

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.

Back to the top

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.

Back to the top

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.

Back to the top

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

Back to the top

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

Back to the top