LW R5,(R3) ; R3 contains
address of a word.
; Load R5 from this address.
We have already used three SPASM instructions in the above examples. There are several more instructions for loading data into registers. These are listed in the table below. There are also example instructions in the table and the result of the instruction is shown in the right-most column. To interpret the results of the instructions assume that the memory address labelled with var1 and the following three memory addresses contain the following hex values:
var1: FF FF 01 23
|MOV||move between registers||MOV R1,R2||contents of R2|
|LB||load byte||LB R1,var1||0xFFFFFFFF|
|LH||load half-word||LH r1,var1||0xFFFFFFFF|
|LW||load word||lw r1,var1||0xFFFF0123|
|LBU||load unsigned byte||lbu r1,var1||0x000000FF|
|LHU||load unsigned half-word||LHU r1,var1||0x0000FFFF|
|LLH||load left half||LLH r1,var1||0xFFFF0000|
|LI||load immediate||LI r1,0x123||0x00000123|
|LLHI||load left half immediate||LLHI r1,0x123||0x01230000|
Sometimes we do not want to interpret these values as signed numbers. In these cases we would rather the bit pattern top be loaded as is, that is, without sign extension into the most significant bits. The LBU and LHU are designed for this purpose.
It is also useful to load data into the left half word (bits 16-31). This will allow us to build a 32-bit value from 16 bit components. LLH can load the left half of a register from memory and LLHI can load it from the immediate value specified. It is important to note that both of these instructions load zeros into the right half word of the register. Nevertheless, by using LLHI and an ANDI instruction (Topic 3) we can load a 32-bit immediate value in only two instructions.
At this point you may be wondering why there is only one immediate instruction and not a separate one for loading byte and half-word immediate values. The answer is that LI does this as well. To see why you should observe that LW, LH and LB load signed values from memory. Since the left-most bits are filled with the sign bit it makes no difference if a byte or half-word is loaded. In addition, an immediate value can only be a 16-bit signed value to fit into the offset field of the instruction.
We will now look at instructions that store data from registers into memory.
The following table shows the four store instructions. For the
examples assume that R1 contains the value 0x12345678 and the four memory
locations at var1 are zero.
|op-code||description||example||var1, var1+1, ...|
|SB||staore byte||SB var1,R1||78 00 00 00|
|SH||store half-word||SH var1,R1||56 78 00 00|
|SW||store word||SW var1,R1||12 34 56 78|
|SLH||store left half-word||SLH var1,R1||12 34 00 00|
Note that these instructions only access the amount of memory specified
in the instruction. For example, an SBinstruction only modifies a
single byte in memory. Bytes following the modified byte or half-word
are not changed at all.
SPASM implements separate arithmetic instructions for signed and unsigned interpretation of numbers. It uses 2's complement arithmetic for signed numbers. If you need to revise 2's complement numbers you should return to Topic 1 now.
A problem faced by all CPU designers is what to do if the result of an arithmetic operation does not fit into a register. This can happen in any of the four binary operations listed above. There are several solutions to this problem. The first is to ignore it. This is the simplest approach but means that the answer is not correct since bits which make up the correct value have been lost. A second solution is to set one or more flags in the processor's status register. This approach has been adopted by the Intel 80x86 designers. A third approach is to generate a system interrupt whenever overflow occurs. We will discuss interrupts in Topic 4.
SPASM adopts two of the above approaches. When performing unsigned arithmetic any overflow is ignored. The programmer is responsible for detecting any overflow in this case. When performing signed arithmetic an interrupt is generated if overflow occurs. To avoid interrupts a programmer must either check the arguments to the instruction before its execution or to intercept the interrupt itself. You will see how to do this in a future laboratory session.
The table below lists all of SPASM's arithmetic instructions.
Notice that instructions with two register arguments always modify the
register on the left-hand side.
|NEG||NEG R1||Replace R1 with its 2s complement|
|ADD||ADD R1,R2||Add signed R2 to signed R1. Interrupt on overflow.|
|ADDU||ADDU R1,R2||Add unsigned R2 to unsigned R1. Ignore overflow.|
|ADDI||ADDI R1,23||Add immediate value to signed R1. Interrupt if overflow.|
|ADDUI||ADDUI R1,23||Add immediate value to unsigned R1. Ignore overflow.|
|SUB||SUB R1,R2||Subtract signed R2 from signed R1. Interrupt on overflow.|
|SUBU||SUBU R1,R2||Subtract unsigned R2 from unsigned R1. Ignore overflow.|
|SUBI||SUBI R1,R2||Subtract immediate value from signed R1. Interrupt if overflow.|
|SUBUI||SUBUI R1,R2||Subtract immediate value from unsigned R1. Ignore overflow.|
|MUL||MUL R1,R2||Multiply signed R1 by signed R2 with result in R1. Interrupt on overflow.|
|MULU||MULU R1,R2||MULU MULU R1,R2 Multiply unsigned R1 by unsigned R2 with result in R1. Ignore overflow.|
|DIV||DIV R1,R2||Divide signed R1 by signed R2 with dividend in R1 and remainder in R2. Interrupt if R2=0.|
|DIVU||DIVU R1,R2||Divide unsigned R1 by unsigned R2 with dividend in R1 and remainder in R2. Interrupt if R2=0.|
Both addition and subtraction have separate instructions for an immediate operand. These are very useful for processing sequences of data such as character strings. For example, if we have the address of a string in R3 then we can make the address change to the next character in the string by adding one to the register as follows.
Here, the immediate value of 3 is loaded into R1 and then R4 is multiplied by R1.
You should also note that the division instructions are unique because
they modify both of their register
arguments. It is sometimes useful to think of the result as the div and mod of the arguments (using programming language terminology). This means that if we wished to calculate the modulus of two numbers then it would appear in the second argument.
Division is also unique because arithmetic overflow cannot occur.
However, division can generate an interrupt because a divide by zero is
an illegal operation. Both versions of the DIV instruction can cause
such an interrupt.
Figure 3.3 below lists all of the logical instructions. You will
see that there are immediate addressing mode versions of most instructions.
The final column of this table shows the value of R1 after the example
instruction if initially R1=0b100100111, R2=0b1111101010
and R3=0b10. Note that the binary values have been compressed with
ellipsis (…) to fit in the table.
|NOT||Logical complement||NOT R1||1...1011011000|
|AND||AND registers, result in first argument||AND R1,R2||0100100010|
|ANDI||AND register with immediate value||ANDI R1,0xF||0000000111|
|OR||OR registers, result in first argument||OR R1,R2||1111101111|
|ORI||OR register with immediate value||ORI R1,0x4||1100101111|
|XOR||XOR registers, result in first argument||XOR R1,R2||1011001101|
|XORI||XOR register with immediate value||XORI R1,0x5||0100100010|
|SHL||Shift bits of register1 left register2 places (zero fill)||SHL R1,R3||10010011100|
|SHLI||Shift bits of register left immediate value places||SHLI R1,1||1001001110|
|SHR||Shift bits of register1 right register2 places (zero fill)||SHR R1,R3||0000100100|
|SHRI||Shift bits of register right immediate value places||SHR R1,5||0000001001|
|ROL||Rotate bits in register1 left register2 places||ROL R1,R3||00…011100|
|ROLI||Rotate bits in register left immediate value places||ROLI R1,1||1001001110|
|ROR||Rotate bits in register1 right register2 places||ROR R1,R3||110…01001001|
|RORI||Rotate bits in register right immediate value places||RORI R1,2||110…01001001|
The AND instruction is useful for masking bits. A mask is a bit pattern that can be used to isolate individual bits from a large collection of bits. To demonstrate a mask, suppose we wished to isolate the right most byte from a 32 bit value. This can be done by performing an AND operation with the binary value 0b11111111 (ie. 0xFF).
R1 = 0101 0001 1010 0010 1111 1110 0101 0111
R1 = 0000 0000 0000 0000 0000 0000 0101 0111
You should check the individual bits to prove to yourself that this works.
A mask is also useful for checking if an individual bit is set. For example, we can check if bit number 3 is set in R1 by the following two instructions.
This sequence of instructions will jump to the label bit2_set only if bit 2 is set in R1. You should write down the bit patterns to check this.
The OR instruction can be used in a similar way to set bits. If you think about all of the possible arguments to an OR operation you will see that if a bit is set in one of the arguments then it will be set in the answer. For example, to set bits 0, 3 and 5 in register R5 we can use the following instruction.
We should also note a subtle feature of the shift instructions.
As bits are shifted left and right, 0 bits are inserted into the register
where the bits have been removed. This is significant if you use
shift right operations on signed numbers. As soon as a register interpreted
as a negative number is shifted right any number of bits then it becomes
a positive number since the sign bit is changed to a zero. This means
that a shift operation cannot be used on negative numbers for fast divide
by two operations.
There are many jump instructions. The simplest is an unconditional jump that always modifies the PC when it is encountered. The remaining jump instructions only modify the PC if a certain condition is true. In the SPASM computer this may be the result of comparing the contents of two registers or comparing a register with the value zero. The large number of instructions results from the different possible comparisons and the different interpretation of the values as signed or unsigned numbers.
Note that other CPU designs also provide jump conditional instructions based on other conditions. For example, the Intel 80x86 processors have jump instructions that change the PC based on the status of flags in the processors flags register.
The table below lists the SPASM jump instructions. The table lists
the jump instructions that use register comparison first. These are
followed by the instructions that compare a single register with zero.
|JMP||Always jump||JMP lab1|
|JE||jump if registers are equal||JE R1,R2,lab1|
|JNE||jump if registers are not equal||JNE R1,R2,lab1|
|JGT||jump if reg1 > reg2||JGT R1,R2,lab1|
|JLT||jump if reg1 < reg2||JLT R1,R2,lab1|
|JGE||jump if reg1 >= reg2||JGE R1,R2,lab1|
|JLE||jump if reg1 <= reg2||JLE R1,R2,lab1|
|JGTU||jump if reg1 > reg2 (unsigned values)||JGTU R1,R2,lab1|
|JLTU||jump if reg1 < reg2 (unsigned values)||JLTU R1,R2,lab1|
|JGEU||jump if reg1 >= reg2 (unsigned values)||JGEU R1,R2,lab1|
|JLEU||jump if reg1 <= reg2 (unsigned values)||JLEU R1,R2,lab1|
|JEZ||jump if register = 0||JEZ R1,lab1|
|JNEZ||jump if register not = 0||JNEZ R1,lab1|
|JLTZ||jump if register < 0||JLTZ R1,lab1|
|JGTZ||jump if register > 0||JGTZ R1,lab1|
|JLEZ||jump if register <= 0||JLEZ R1,lab1|
|JGEZ||jump if register >= 0||JGEZ R1,lab1|
You should also note that the instructions that compare a register with zero interpret the register as a signed value. It does not make sense to compare an unsigned value with zero since zero is always the lowest unsigned value.
It is also useful to note that the register compare instructions can be read by placing the Boolean comparison operation between the registers as they appear in the argument list. For example, the following instruction is the equivalent of the Boolean expressions (R1 < R2).
A stack is, as the name suggests, a collection of items. In SPASM
the stack is an area in memory used to store a collection of 32-bit words.
A SPASM stack has two unique operations for altering the number of words
in the stack. We may put a word on top of the stack (a push operation)
or we may take something off the stack (a pop operation). We may
never change the number of words in the stack by taking anything out of
the middle of the stack (although we read or write values to words in the
stack). The following Figure 3.4 shows a diagram of a program's stack
before and after three stack operations. For this example, assume
R1 contains the value 23 and R2 contains the value 17.
Here you can see that a good way to visualise what is happening is to visualise elements stacked onto the stack with the push operation. When an element is removed with the pop operation then it is also placed into a register.
The CPU must remember the location of the top of the stack. This
is always stored in the stack pointer (SP) register. You will see
below that we can access the elements on the stack by copying SP into a
register and using indirect addressing. You will also see that the
stack mechanism is a good way to implement subroutines.
We will now look at each of the four SPASM stack based instructions. Before we start we should note an important thing about program stacks. Stacks start at an address and grow backwards in memory, that is, towards address zero. This is very useful because we can immediately detect when a stack overflows because an incorrect address will appear in the stack pointer. In the case of the SPASM computer we have seen that the stack is the first data area in memory. This means that if a stack becomes full we will try to access a negative address and thereby cause an interrupt to be generated. We will examine interrupts in Topic 4.
PUSH and POP instructions
The next Figure 3.5 shows the PUSH instruction. It simply adds
a 32 bit value to the top of the stack. The PUSH instruction copies the
value from a register to the top of the stack. Since a register is
32 bits, the SP is decreased by four. Note that the contents of R1
do not change. Notice also that the SP is decreased rather an increased
since the stack grows toward address zero.
The POP instruction is the opposite of the PUSH instruction. Figure 3.6 shows that it takes a 32-bit value off the top of the stack and puts it into the specified register.
The POP instruction overwrites the current content of the specified register. It also increases the SP since removing an item from the top of the stack requires the SP to move away from address zero.
The following activity is designed to test your understanding of the program's stack. You should make sure that you understand the PUSH and POP instructions before continuing to the CALL and RET instructions in the following section since they are really more complex instructions.
We now move onto the CALL and RET instructions. As mentioned above, these are used to implement subroutines in assembler language.
To return from a subroutine we execute the RET instruction. This
instruction has no arguments and simply pops the top of the stack into
the PC so that the next instruction executed is at the address that was
stored on the top of the stack by the CALL instruction. Figure 3.8
shows the operation of the RET instruction.
Although the RET instruction is rather simple in its operation, great care must be taken in its use. The programmer must be certain that a return address is on top of the stack. Examine the following subroutine:
The final operation of stacks is to access elements while the are still on the stack.
|NOP||Do nothing at all||NOP|
|INT||Force interrupt||INT 10|
The HALT instruction stops the SPASM CPU's fetch-execute cycle. The program must be restarted before you can execute any more instructions.
The INT instruction can be used to force an interrupt on the CPU.
The most often use for this instruction is to call the operating system
with interrupt number 7 (See Operating system calls).