This document is the definitive specification of the QuAC1 instruction set that we will be implementing in this course. If another source contradicts this document, this takes precedence.
Memory#
- Minimum addressable unit is 16-bit words
- 16-bit addressed
- Total addressable memory is 128 KB (64 k words)
Arithmetic#
- Numbers add and subtract according to two’s complement arithmetic.
- When adding numbers beyond the limits of 16 bits, the value wraps around back to 0 (effectively keeping bits 0–15 and discarding bits 16 and above).
Registers#
All registers start initialised to 0x0000
, and are 16-bits wide.
Code | Mnemonic | Meaning | Behaviour |
---|---|---|---|
000 | rz |
Zero Register | Always reads as zero, even after being written to. |
001 | r1 |
Register 1 | General purpose register. |
010 | r2 |
Register 2 | General purpose register. |
011 | r3 |
Register 3 | General purpose register. |
100 | r4 |
Register 4 | General purpose register. |
101 | fl |
Flag register | See Flags. |
110 | pc |
Program Counter | See Program Counter. |
111 | - | Undefined | Any operation with this register is undefined. |
-
rz
,fl
, andpc
may also be described asr0
,r5
, andr6
respectively. -
An instruction is allowed to write to
rz
, however the next time an instruction readsrz
it will still read as0
. -
r1
,r2
,r3
, andr4
are the general purpose registers. You may write to them, and they will store that value. Reading from a general purpose register returns the last value written to them.
Instruction Encoding#
Each instruction in QuAC has one of two formats:
- Register Operands Format (R-Format), or
- Immediate Format (I-Format).
These formats describe the general segmentation of an instruction into data fields. These fields are given below.
There’s nothing enforcing future instructions fall into these two formats: R-Format and I-Format only describe the general pattern existing instructions follow. New instructions could follow an entirely different encoding format.
Register Operands Format (R-Format)#
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
op |
cond |
rd |
0 | ra |
0 | rb |
Immediate Format (I-Format)#
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
op |
cond |
rd |
imm8 |
Definitions#
All Formats#
Name | Meaning | Description |
---|---|---|
op |
Opcode | Specifies the operation to perform. |
cond |
Condition | Determines whether this instruction executes. See Conditions. |
rd |
Destination / Data Register | (not str ) Determines which register the result of this instruction is written to, or (for str ) is used as the register containing the data to store to memory.2 |
R-Format only#
Name | Meaning | Description |
---|---|---|
0 | Literal zero | The value is always 0. |
ra |
First Operand / Address Register | The register used for the first operand in ALU instructions, or for the address in ldr and str . |
rb |
Second Operand Register | The register used for the second operand in ALU instructions. ldr and str set this to 0. |
I-Format Only#
Name | Meaning | Description |
---|---|---|
imm8 |
Immediate (8-bit) | An immediate, or constant, 8-bit value that is directly encoded into the instruction. |
Hardware Instructions#
The following table lists all instructions a hardware implementer of QuAC must handle. The section Pseudo-Instructions lists several more instructions, but the machine code of each additional instruction matches one of the (possibly more general) instructions here. By implementing these, you gain the full pseudo-instruction support ‘for free’.
Every instruction in QuAC can have a condition suffix appended to it. See Conditions for details. The suffix is used to determine the cond
bit used in the machine code column.
Syntax | Semantic | Machine Code |
I-Format Instructions | ||
movl rd, imm8 |
rd ≔ #imm8 |
0000 <cond> <rd> <imm8> |
seth rd, imm8 |
See below | 0001 <cond> <rd> <imm8> |
R-Format Memory Instructions | ||
str rd, [ra] |
[ra] ≔ rd |
0100 <cond> <rd> 0 <ra> 0000 |
ldr rd, [ra] |
rd ≔ [ra] |
0101 <cond> <rd> 0 <ra> 0000 |
R-Format ALU Instructions | ||
add rd, ra, rb |
rd ≔ ra + rb |
1000 <cond> <rd> 0 <ra> 0 <rb> |
sub rd, ra, rb |
rd ≔ ra - rb |
1001 <cond> <rd> 0 <ra> 0 <rb> |
and rd, ra, rb |
rd ≔ ra & rb |
1010 <cond> <rd> 0 <ra> 0 <rb> |
xor rd, ra, rb |
rd ≔ ra ^ rb |
1011 <cond> <rd> 0 <ra> 0 <rb> |
seth
moves an 8-bit constant (imm8) into the high byte of the destination register rd
, leaving the low byte of rd
unchanged. Formally,
rd ≔ (#imm8 << 8) | (rd & 0xff)
Note that the imm8
inputs to movl
and seth
are unsigned. Putting negative values as inputs for these instructions
does not work in the assembler.
16-bit values that do not correspond to a machine code pattern in this table are undefined instructions. A correct QuAC program will never attempt to execute an undefined instruction. Hardware may act in any way it chooses if a program does.
More details on what each instruction does, and how they affect the flags, can be found here.
Program counter#
The program counter register pc
behaves as a general purpose register the same as r1
–r4
with respect to instructions reading and writing to it.
Its value is also used as the address to fetch the next instruction from. Therefore, reading its value will give you the address of the currently executing instruction. After an instruction is executed pc
is incremented by 1 unless pc
was written to by the just-finished instruction.3 If that instruction’s condition failed, then pc
is still incremented, even if the instruction would have written to it.
Flags#
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Undefined | V | C | N | Z |
The flag register fl
stores the 4 flags generated by the ALU whenever any ALU instruction is successfully executed.
- If an ALU instruction is executed (
add
,sub
,and
, orxor
) is executed, the flags are updated. - If a non-ALU instruction (
str
,ldr
,movl
, orseth
) is executed, the flags are left unchanged. - If an ALU instruction’s condition fails, then the flags are left unchanged.
fl
may be read from like any other general purpose register. Writing directly to fl
is undefined behaviour.4
The undefined bits impose no restrictions on reads and writes. They may even be changed when performing non-ALU instructions or when an instruction fails its condition.
The rules for setting each flag when executing an ALU operation are as follows
Flag | Meaning | Description |
---|---|---|
Z | Zero | Set if the result is zero. |
N | Negative | Set if the result, viewed as a 2’s complement signed integer, is negative. |
C | Carry | If the instruction is add or sub , then set if the calculation carried. If the instruction is and or xor , then always 0. |
V | Overflow | If the instruction is add or sub , then set if the calculation overflowed. If the instruction is and or xor , then always 0. |
See lab 2 for hints on calculating when these conditions occur.
Conditions#
Conditions are fields on an instruction that control whether that instruction is executed or not. Before executing the instruction, the CPU checks the condition on the instruction against the current CPU state.
- If the condition succeeds, then the instruction is executed like normal.
- If the condition fails, then the instruction is not executed, acting as if it were a
nop
.
The QuAC ISA defines two conditions
Name | Suffix | Encoding | Condition | Meaning |
---|---|---|---|---|
Always | - | 0 |
- | Always executes |
Equals | z |
1 |
Z == 1 |
Execute if latest ALU result was zero |
The encoding bit is substituted in place of cond
in the instruction table.
The names of the conditions are derived from the cmp ra, rb
instruction. For example, the equals condition checks for Z == 1
because cmp ra, rb
sets the Z flag if and only if ra == rb
.
Other conditions can be emulated using these. For example,
@ Conditionally execute on C
movl r1, 0b100 @ mask for the carry bit
and rz, fl, r1 @ bitwise AND the mask with the flag
jpz skip_if_fail @ jumps if C was not set
nop @ only executed if C was set
skip_if_fail:
nop
@ Conditional jump on 'greater than' condition
@ (Greater than is equivalent to Z = 0 and N = 0)
movl r1, 0b11 @ mask for Z and N
and rz, fl, r1 @ bitwise AND the mask with flag
jpz <label> @ only executed if Z and N were 0
Pseudo-Instructions#
These instructions are special cases of instructions that already exist in the ISA, but make writing assembly a bit easier. Note how the machine code in each case corresponds to an instruction in the above table. By implementing those base ISA instructions, the pseudo-instructions are obtained for free.
Syntax | Meaning | Machine Code |
---|---|---|
mov rd, ra |
rd ≔ ra |
1000 <cond> <rd> 0 <ra> 0000 |
jpr ra |
pc ≔ ra |
1000 <cond> 110 0 <ra> 0000 |
cmp ra, rb |
ra - rb |
1001 <cond> 000 0 <ra> 0 <rb> |
nop |
Do nothing | 0000 0000 0000 0000 |
jpm [ra] |
pc ≔ [ra] |
0101 <cond> 110 0 <ra> 0000 |
jp imm8 |
pc ≔ imm8 |
0000 <cond> 110 <imm8> |
jp <label> |
pc ≔ <label> |
Up to the assembler |
The compare instruction cmp
is useful for comparing the values between two registers. It is synthesised as a subtraction with rz
as the destination register (cmp ra, rb
is equivalent to sub rz, ra, rb
), which means the result of the subtraction is discarded, but the flags are still set.
cmp r1, r2 @ if r1 == r2:
movlz r3, 1 @ r3 = 1
-
Quarel-Akram-Connor, after the designers Shoaib Akram, Jon Connor and David Quarel. ↩
-
All instruction in QuAC except for
str
write a result to a destination register.str
usesrd
as the data to store to memory. While a little confusing, this makes the implementation in hardware a bit easier to deal with. ↩ -
This allows for the synthesis of many other instructions. For instance, we can use the following code to skip over a small piece of code.
movl r2, 2 @ r2 ≔ 2 add pc, r2 @ pc jumps two addresses forward movl r1, 1 @ this instruction gets skipped movl r3, 3 @ this instruction gets executed
Or (stranger still) to load the machine code of an instruction itself, and use it as data
ldr r1, [pc] @ r1 ≔ 0x5710
-
The reason why writing to the flag register is undefined is to make the base ISA as compatible as possible with extensions, as well as make the implementation easier. You might decide that writing to
fl
has no effect, or that it can be written to like any other register. Both are compatible with the spec. You might also want to define a meaning for the other bits in the flag register, and have the CPU behave differently depending on the value of those bits set by the user. ↩