Instruction set
QCPU 2 5-3-2-3-3 reference
| QCPU 2 | Instruction | Description | Representation | Notes |
|---|---|---|---|---|
00000-DDD-00-SSS-SSS |
add dd, ds1, ds2 | * ** Addition | dd = ds1 + ds2 |
|
00000-DDD-01-SSS-SSS |
add ad, as1, as2 | ** Addition | ad = as1 + as2 |
|
00000-DDD-10-SSS-SSS |
add dd, as1, ds2 | ** Addition | dd = as1 + ds2 |
|
00000-DDD-11-SSS-SSS |
add ad, as1, ds2 | ** Addition | ad = as1 + ds2 |
|
00001-III II III-WWW |
addi dw, imm | Addition with sign ext. immediate | dw += imm |
|
00010-III II III-WWW |
addi aw, imm | Addition with sign ext. immediate | aw += imm |
|
00011-III II III-SSS |
cmpi ds1, imm | Comparison with sign ext. immediate, flags only | zr = ds1 - imm |
|
00100-DDD-00-SSS-CCC |
movec cc, dd, ds1 | Move conditionally | dd = ds1 on condition |
use clr dd for zero |
00100-DDD-01-SSS-CCC |
movec cc, ad, as1 | Move conditionally | ad = as1 on condition |
use clr ad for zero |
00100-DDD-10-SSS-SSS |
movez dd, ds1, ds2 | Move register if register is zero | dd = ds2 if ds1 == 0 |
use clr dd for zero |
00100-DDD-11-SSS-SSS |
movez ad, as1, as2 | Move register if register is zero | ad = as2 if as1 == 0 |
use clr ad for zero |
00101-DDD-00-SSS-SSS |
sub dd, ds1, ds2 | Subtraction | dd = ds1 - ds2 |
|
00101-DDD-01-SSS-SSS |
sub ad, as1, as2 | Subtraction | ad = as1 - as2 |
|
00101-DDD-10-SSS-SSS |
slt dd, ds1, ds2 | Set one if less than register | dd = ds1 < ds2 ? 1 : 0 |
sign bit; greater than or equal to with xori dd, 1 |
00101-DDD-11-SSS-SSS |
sltu dd, ds1, ds2 | Set one if less than register unsigned | dd = ds1 < ds2 ? 1 : 0 |
carry bit; greater than or equal to with xori dd, 1 |
00110-DDD-00-SSS-SSS |
||||
00110-DDD-01-SSS-SSS |
ior dd, ds1, ds2 | Bitwise incl. OR (insert bits) | dd = ds1 | ds2 |
|
00110-DDD-10-SSS-SSS |
and dd, ds1, ds2 | Bitwise AND (mask bits) | dd = ds1 & ds2 |
|
00110-DDD-11-SSS-SSS |
xor dd, ds1, ds2 | Bitwise excl. OR (toggle bits) | dd = ds1 ^ ds2 |
|
00111-III II III-WWW |
iori dw, imm | Bitwise incl. OR with sign ext. immediate | dw |= imm |
|
01000-III II III-WWW |
ioriu dw, uimm | Bitwise incl. OR with immediate | dw |= uimm |
|
01001-III II III-WWW |
andi dw, imm | Bitwise AND with sign ext. immediate | dw &= imm |
|
01010-III II III-WWW |
andiu dw, uimm | Bitwise AND with immediate | dw &= uimm |
|
01011-III II III-SSS |
mask ds1, uimm | Bitwise AND with immediate, flags only | zr = ds1 & uimm |
|
01100-III II III-WWW |
xori dw, imm | Bitwise excl. OR with sign ext. immediate | dw ^= imm |
|
01101-III II III-WWW |
xoriu dw, uimm | Bitwise excl. OR with immediate | dw ^= uimm |
|
01110-0II II-SSS-DDD |
bsl dd, ds1, uimm | Barrel shift left (logical/arithmetic) | dd = ds1 << uimm |
|
01110-1II II-SSS-DDD |
bsr dd, ds1, uimm | Barrel shift right (logical) | dd = ds1 >> uimm |
|
01111-0II II-SSS-DDD |
bsrs dd, ds1, uimm | Barrel shift right signed (arithmetic) | dd = ds1 >> uimm sign ext. |
|
01111-1II II-SSS-DDD |
brr dd, ds1, uimm | Barrel rotate right | dd = ds1 >> uimm | ds1 << -uimm |
|
10000-DDD-XX-SSS-SSS |
||||
10001-DDD-XX-SSS-SSS |
||||
10010-III II III-DDD |
lli dd, imm | Load lower sign ext. immediate | dd = imm |
unsigned with andiu dd, 0xFF |
10011-III II III-DDD |
lui dd, uimm | Load upper immediate | dd = uimm << 8 |
full immediate is lui and ioriu |
10100-III II III-DDD |
lua ad, uimm | Load upper address | ad = uimm << 8 |
full immediate is lua and addi??? |
10101-III II III III |
jmpl uimm | Jump and link | rp = ip + 2; ip = uimm |
32 byte aligned |
10110-III II III III |
jmprl imm | Jump relative and link | rp = ip + 2; ip += imm |
+-4096 byte reach, 2 byte aligned |
10111-III II III-CCC |
brh cc, imm | Branch | ip += imm on condition |
+-256 byte reach, 2 byte aligned |
11000-III II III-CCC |
brhl cc, imm | Branch and link | rp = ip + 2; ip += imm on condition |
+-256 byte reach, 2 byte aligned |
11001-III II-SSS-WWW |
xch dw, as1, imm | Atomic exchange with static offset | dw xch *(as1 + imm) sign ext. dw |
dw = zr is atomic clear; range imm +-16 bytes; cut8 |
11010-III II-SSS-WWW |
xchw dw, as1, imm | Atomic exchange word with static offset | dw xch *(as1 + imm) |
dw = zr is atomic clear; range imm +-32 bytes |
11011-III II-SSS-DDD |
move dd, imm(as1) | Memory load with static offset | dd = *(as1 + imm) sign ext. dd |
dd = zr is prefetch; range imm +-16 bytes |
11100-III II-SSS-DDD |
movew dd, imm(as1) | Memory load word with static offset | dd = *(as1 + imm) |
dd = zr is prefetch; range imm +-32 bytes |
11101-III II-SSS-SSS |
move imm(as1), ds2 | Memory store with static offset | *(as1 + imm) = ds2 |
ds2 = zr is clear; range imm +-16 bytes; cut8 |
11110-III II-SSS-SSS |
movew imm(as1), ds2 | Memory store word with static offset | *(as1 + imm) = ds2 |
ds2 = zr is clear; range imm +-32 bytes |
11111-000-00-SSS-DDD |
move dd, -(as1) | Memory load with pre-decrement | dd = *(--as1) sign ext. dd |
dd = zr is prefetch |
11111-001-00-SSS-DDD |
movew dd, -(as1) | Memory load word with pre-decrement | dd = *(--as1) |
dd = zr is prefetch |
11111-010-00-SSS-DDD |
move dd, (as1)+ | Memory load with post-increment | dd = *(as1++) sign ext. dd |
dd = zr is prefetch |
11111-011-00-SSS-DDD |
movew dd, (as1)+ | Memory load word with post-increment | dd = *(as1++) |
dd = zr is prefetch |
11111-100-00-SSS-SSS |
move -(as1), ds2 | Memory store with pre-decrement | *(--as1) = ds2 |
ds2 = zr is clear; cut8 |
11111-101-00-SSS-SSS |
movew -(as1), ds2 | Memory store word with pre-decrement | *(--as1) = ds2 |
ds2 = zr is clear |
11111-110-00-SSS-SSS |
move (as1)+, ds2 | Memory store with post-increment | *(as1++) = ds2 |
ds2 = zr is clear; cut8 |
11111-111-00-SSS-SSS |
movew (as1)+, ds2 | Memory store word with post-increment | *(as1++) = ds2 |
ds2 = zr is clear |
11111-III-01-III-000 |
sysc uimm | System call | irp = ip + 2; ip = vec; imsk = 1; |
|
11111-III-01-III-001 |
||||
11111-III-01-III-010 |
||||
11111-III-01-III-011 |
||||
11111-III-01-III-100 |
||||
11111-III-01-III-101 |
||||
11111-III-01-III-110 |
||||
11111-III-01-III-111 |
||||
11111-III-10-III-SSS |
csrw ds1, uimm | *** Control & Status Register write | csr(uimm) = ds1) |
|
11111-III-11-III-DDD |
csrr dd, uimm | *** Control & Status Register read | dd = csr(uimm) |
|
11111-000-11-000-000 |
nop | No operation | literally nothing | CSR read dest zr are zero-operand instructions |
11111-000-11-001-000 |
shf | Set half carry flag | h = 1 |
|
11111-000-11-010-000 |
rhf | Reset half carry flag | h = 0 |
|
11111-000-11-011-000 |
ssf | Set sign flag | s = 1 |
|
11111-000-11-100-000 |
rsf | Reset sign flag | s = 0 |
|
11111-000-11-101-000 |
||||
11111-000-11-110-000 |
||||
11111-000-11-111-000 |
||||
11111-001-11-000-000 |
reti | *** Return from interrupt | ip = csr(irp) |
|
11111-001-11-001-000 |
waiti | *** Wait for interrupt | ||
11111-001-11-010-000 |
ftlb | *** Flush TLB | ||
11111-001-11-011-000 |
fence | *** Fence | flush pending store operations | |
11111-001-11-100-000 |
||||
11111-001-11-101-000 |
||||
11111-001-11-110-000 |
||||
11111-001-11-111-000 |
||||
11111-010-11-???-000 |
||||
11111-011-11-???-000 |
||||
11111-100-11-???-000 |
||||
11111-101-11-???-000 |
||||
11111-110-11-???-000 |
||||
11111-111-11-???-000 |
| Pseudoinstructions | Instruction | Description | Translation | Notes |
|---|---|---|---|---|
00000-000-00-000-000 |
bkpt | * Breakpoint | add zr, zr, zr |
|
00000-DDD-00-000-SSS |
move dd, ds1 | ** Move data register to data register | add dd, zr, ds1 |
|
00000-DDD-01-000-SSS |
move ad, as1 | ** Move address register to address register | add ad, zr, as1 |
|
00000-DDD-10-SSS-000 |
move dd, as1 | ** Move address register to data register | add dd, as1, zr |
|
00000-DDD-11-000-SSS |
move ad, ds1 | ** Move data register to address register | add ad, zr, ds1 |
|
00100-DDD-00-000-CCC |
clrc cc, dd | Clear conditionally | movec cc, dd, zr |
|
00100-DDD-01-000-CCC |
clrc cc, ad | Clear conditionally | movec cc, ad, zr |
|
00100-001-01-010-CCC |
retc cc | Return conditionally | movec cc, ip, rp |
|
00101-000-00-SSS-000 |
test ds1 | Test register | sub zr, ds1, zr |
|
00101-000-01-SSS-000 |
test as1 | Test register | sub zr, as1, zr |
|
00101-DDD-00-000-SSS |
neg dd, ds1 | Negation | sub dd, zr, ds1 |
|
00101-000-00-SSS-SSS |
cmp ds1, ds2 | Comparison | sub zr, ds1, ds2 |
|
00101-000-01-SSS-SSS |
cmp as1, as2 | Comparison | sub zr, as1, as2 |
|
00001-000 00 001-WWW |
inc dw | Increment (byte stride) | addi dw, 1 |
|
00001-111 11 111-WWW |
dec dw | Decrement (byte stride) | addi dw, -1 |
|
00001-000 00 001-WWW |
incw dw | Increment (word stride) | addi dw, 2 |
|
00001-111 11 110-WWW |
decw dw | Decrement (word stride) | addi dw, -2 |
|
00010-000 00 001-WWW |
inc aw | Increment (byte stride) | addi aw, 1 |
|
00010-111 11 111-WWW |
dec aw | Decrement (byte stride) | addi aw, -1 |
|
00010-000 00 001-WWW |
incw aw | Increment (word stride) | addi aw, 2 |
|
00010-111 11 110-WWW |
decw aw | Decrement (word stride) | addi aw, -2 |
|
00101-DDD-10-SSS-000 |
sneg dd, ds1 | Set one if negative | slt dd, ds1, zr |
|
00101-DDD-10-000-SSS |
spos dd, ds1 | Set one if positive and non-zero | slt dd, zr, ds1 |
|
00101-DDD-11-000-SSS |
snez dd, ds1 | Set one if not equal to zero | sltu dd, zr, ds1 |
|
01010-000 01 111-WWW |
cut4 dw | Cut register to 4 bit | andiu dw, 0x0F |
|
01010-111 11 111-WWW |
cut8 dw | Cut register to 8 bit | andiu dw, 0xFF |
|
01010-000 00 000-WWW |
clrl dw | Clear lower 8 bit register | andiu dw, 0x00 |
|
01100-111 11 111-WWW |
not dw | Bitwise NOT | xori dw, 0xFF |
|
01101-111 11 111-WWW |
not8 dw | Bitwise NOT lower 8 bit | xoriu dw, 0xFF |
|
10100-III II III-001 |
jmp uimm | Jump | lua ip, uimm |
|
00000-001-01-000-SSS |
jmpd as1 | Jump to register | add ip, zr, as1 |
|
00000-001-11-000-SSS |
jmpd ds1 | Jump to register | add ip, zr, ds1 |
|
00010-III II III-001 |
jmpr imm | Jump relative | addi ip, imm |
|
00000-001-01-000-010 |
ret | Return | add ip, zr, rp |
|
11011-000 00-SSS-000 |
prfd imm(as1) | Prefetch data | move zr, imm(as1) |
|
11111-???-10-???-000 |
clri | Clear pending interrupt | csrw zr, csr(imsk) |
|
11111-???-10-???-SSS |
prfi ds1 | Prefetch instruction | csrw ds1, csr(prfi) |
* Special control for breakpoint.
** Addition with any zero register disables flag update (for reg-to-reg move).
*** Kernel mode only.
Notation legend:
S/ds1/ds2: data register source 1/2S/as1/as2: address register source 1/2D/dd: data register destinationD/ad: address register destinationW/dw: data register source and writebackW/aw: address register source and writebackI/uimm: unsigned immediateI/imm: sign extended immediateC/cc: condition code of flag register
| Pseudoinstructions | Usage | Description | Notes |
|---|---|---|---|
| u8 uint | u8 255, u8 @const |
Unsigned byte | |
| u16 uint | u16 65535, u16 @const |
Unsigned word | |
| u24 uint | u24 16777215, u24 @const |
Unsigned 24 bit word | |
| u32 uint | u32 4294967295, u32 @const |
Unsigned 32 bit word | |
| i8 int | i8 -128, i8 @const |
Signed byte | |
| i16 int | i16 -32768, i16 @const |
Signed word | |
| i24 int | i24 -8388608, u24 @const |
Signed 24 bit word | |
| i32 int | i32 -2147483648, u32 @const |
Signed 32 bit word | |
| reserve type[, len] | reserve u8, 256, reserve u8, @len |
Reserve opaque space | |
| ascii text[, sen] | ascii "Hello word!", ascii "Cstr" 0 |
ASCII text |
1 Introduction
The QCPU 2 microprocessor is a 16 bit RISC architecture, inspired by RISC-V and the Motorola 68000. It features a classic five stage pipeline. Structurally, the CPU consists of 8 data registers, 8 address registers, Control & Status Registers (CSRs) (e.g. system information, flag register, interrupt control, and virtual memory control), a Branch Target Buffer (BTB), a Translation Look-Aside Buffer (TLB), Arithmetic Logic Unit (ALU), Barrel Shifter (BS), address register comparators (i.e. over-/underflow exceptions, stack boundaries), asynchronous memory instructions (i.e. scoreboarding), atomic memory instructions, and timer.
The memory model is a 16 bit, virtually mapped address space. It is byte (8 bit) or word (16 bit) addressable, containing either main memory or I/O devices. Virtual memory for userland can be remapped in kernel mode through the use of CSRs. CSRs are also used to configure the CPU and read data from the CPU, such as the page table and interrupts.
2 Kernel mode
QCPU 2 features two system privilege levels: user mode and kernel mode.
In contrast to many privileged instruction sets, and perhaps by current knowledge to all instruction sets, the QCPU 2 architecture defines a kernel region in the memory map to determine the effective privilege level of the CPU. Any memory operation, either instruction or data, made from within userland to the kernel region is considered an access violation. The system is promoted to the kernel mode privilege level when the kernel region is accessed by the instruction pointer only through interrupts, which are either hardware exceptions or system calls, to predefined call locations. Consequently, kernel mode is not a state held from within the CPU; once promoted, a return to userland is implicit when the instruction pointer exits the kernel region of memory. This sophisticated approach minimalises any attack surface of userland execution from within a kernel mode state by dividing memory ownership to these privilege levels.
3 Registers
QCPU 2 has two general-purpose register domains, both having distinct and similar characteristics. Data Registers (DRs) are the most accessible register types in the microprocessor where the majority of the instructions operate on. Data transferred with memory move or CSR instructions are done with these DRs. Transfers are executed asynchronously with internal scoreboarding.
In contrast, Address Registers (ARs) are used for bus addresses, pointers, and length types. These registers are used with memory move operations. Data can be exchanged between DRs and ARs through one of the move pseudoinstructions (translated to add with zero between DRs and ARs). There are special purpose registers mapped as ARs, such as the instruction pointer (write to jump) and return pointer (destination for link instructions). ARs have reserved registers such as the stack pointer which can be used in memory move operations. Overflows on ARs can emit hardware exceptions to the microprocessor. In a special case, the stack pointer register can emit a hardware exception if low or high limits are exceeded, which are configured in CSRs.
Lastly, CSRs are used for system-mapped registers, such as for system information, the flag register, interrupt control, and virtual memory control. CSRs can be read from and written to with DRs in kernel mode.
| Domain | Register | Identifier | Size |
|---|---|---|---|
| Data Register | r0 |
zr (zero) | 16 bits |
| Data Register/Async | r1 |
d1 | 16 bits |
| Data Register/Async | r2 |
d2 | 16 bits |
| Data Register/Async | r3 |
d3 | 16 bits |
| Data Register/Async | r4 |
d4 | 16 bits |
| Data Register/Async | r5 |
d5 | 16 bits |
| Data Register/Async | r6 |
d6 | 16 bits |
| Data Register/Async | r7 |
d7 | 16 bits |
| Address Register | r8 |
zr (zero) | 16 bits |
| Address Register | r9 |
ip (instruction ptr) | 16 bits * |
| Address Register | r10 |
rp (return ptr) | 16 bits * |
| Address Register | r11 |
sp (stack ptr) | 16 bits |
| Address Register | r12 |
fp (frame ptr) | 16 bits |
| Address Register | r13 |
a1 | 16 bits |
| Address Register | r14 |
a2 | 16 bits |
| Address Register | r15 |
a3 | 16 bits |
* Aligned by 2.
4 Flags
System flags are updated once an Arithmetic Logic Unit (ALU) operation is performed on a general-purpose register. Memory move and CSR operations are excluded. Destination to DRs and comparison operations (addition or subtraction to zr register, and cmpi instruction) update all flags, whilst destination to ARs update the overflow and zero flags only.
In a special case, reg-to-reg move operations (pseudoinstructions for add with zero) do not update the flag register.
| Flags | Name | Representation | Updates on |
|---|---|---|---|
000 |
c | if overflow |
DR, AR, comparison |
001 |
h | if half overflow |
DR |
010 |
s | if signed |
DR, comparison |
011 |
z | if result == 0 |
DR, AR, comparison |
100 |
nc | if !overflow |
(c) |
101 |
nh | if !half overflow |
(h) |
110 |
ns | if !signed |
(s) |
111 |
nz | if result != 0 |
(z) |
5 Conventions for ABIs
In the ABI, it is the callee’s responsibility to save any register it clobbers, even if it is possibly unused by the caller. Epiloques from subroutines perform loads from memory as early as possible which is beneficial through scoreboarding.
The callee is not obligated to move the rp (return pointer) to memory if it is itself not a caller.
Arithmetic and logical instructions writing to zr (zero) are considered flags-only operations. However, instruction add zr, zr, zr is a hard-wired bkpt (breakpoint) instruction. Moves between registers are translated to add instructions, and therefore update the appropriate flags.
Subroutines may use the half-carry flag as error bit, or any function as defined in the subroutine contract, through the use of the shf (set half flag) and rhf (reset half flag) instructions.
6 Pipeline
QCPU 2 uses a classic 5-stage RISC pipeline.
| Fetch | Decode | Execute | Memory | Writeback |