Skip to main content

Instruction Set Architecture

The SaarCPU ISA is a rather CISC-ish little-endian 8-bit instruction set architecture. All its opcodes are precisely 8 bit long, but immediates may be attached in a second or a third byte. While it focuses on 8-bit operations, we've implemented a few instructions that operate on 16 bit register pairs for convenience.

The ISA exposes seven 8-bit registers. a, b, c and d are general-purpose registers. p (for “page”) and i (“index”) can be used in any place where the above can be used, however they can also be to fetch one operand from memory. In this case, we denote the operand as [pi]. p serves as the upper 8 bits of the address, i as the lower bits. The last and most special register is the accumulator acc. It can also be used everywhere where a, b, c and d can be used. But acc also serves as the target and source operand of every 8-bit arithmetic or logical operation. (In case of two operands, the other operand can be an immediate, [pi] or any other register.) An overview of the encoding in instruction opcodes can be found below. 8-bit operands are called reg8\mathit{reg8}.

There are two reasons for having the accumulator: First, a data bus is rather costly at the hardware-level. So we decided to only have a single data bus. For binary operations, this means that both operands or one operand and the result need to be stored in special registers anyway.
We more or less follow the design of Ulf Casper’s build: one operand is transferred via the data bus, the other one as well as the result are stored in a special register. For more details, refer to the ALU documentation. Models of transferring ALU operands via data busses It would have been possible to build arithmetic instructions which can take arbitrary registers for the target and source. However, this would have consumed many opcodes, our second reason. Furthermore, this only shifts the burden of performing the moves to the microcode; the cycle count would not have been much lower.

For 16-bit operations, two 8-bit registers can be used together: ab consists of a as the upper byte and b as the lower byte, cd consists of c and d, pi of p and i. 16-bit instructions may also access the stack pointer sp. Such 16-bit operands are called reg16\mathit{reg16}.

Furthermore, there is the 16-bit program counter. It (more or less) describes which instruction is currently executing. To avoid confusion, we use PC\mathit{PC} for the address of first byte after the current instruction’s opcode when defining semantics of instructions. pc\mathit{pc} refers to the register.

As mentioned above, our ISA uses little-endian. In particular this means that a 16-bit immediate is encoded with the lower byte first. call pushes the upper byte of the program counter first to have the lower byte at the lower address.

The ISA defines four flags, which are updated at arithmetic/logic operations:

  • The sign flag S (or SF\mathit{SF}) is the most significant bit of the ALU result.
  • The zero flag Z (or ZF\mathit{ZF}) is set if and only if the ALU result is zero.
  • The overflow flag V (or OF\mathit{OF}) indicates a signed overflow (crossing the boundary between 127127 and 128-128).
  • The carry flag C (or CF\mathit{CF}) indicates an unsigned wrap (crossing the boundary between 255255 and 00).

When we specify the affected flags below, the letter means set accordingly, - means not set, and ? means undefined.

For movs, our ISA supports a three more sophisticated addressing modes, see below.

Notation

The assembly syntax we use is to some extent inspired by x86 assembly (intel syntax). In particular, the destination is denoted before the source. For memory indirections, we also use square brackets [ and ]. So for example mov acc, [ab+42] means to load the value at address ab+42\mathit{ab} + 42 into acc\mathit{acc}.

To describe the semantics of an instruction like mov acc, [reg16+imm8s16] (imm8s16 stands for an 8-bit immediate that is sign-extended to 16 bits), we use the following notation: accmem[reg16+sext(imm8)]\mathit{acc} \gets \mathit{mem}[\mathit{reg16} + \mathit{sext}(\mathit{imm8})].

Encoding

EncodingAffected FlagsInstructionComments
00 000 000SZVCreset
00 aaa 000other alua000
00 fff 001----other control flow
00 ddd 010----mov d, imm8
00 bbb 011binop acc, imm8
00 rrr 100----push rr[pi]
00 110 100----pushf
00 rrr 101----pop rr[pi]
00 110 101----popf
00 ccc 11n----jcc imm8s16/imm16
01 bbb rrrbinop acc, r
10 ddd rrr----mov d, rdr, no acc from/to [pi]
10 000 000----prefix_a16
10 001 001----i2c_send
10 010 010----i2c_recv
10 011 011----spi
10 100 100----switch_fb
10 101 101(unused)
10 110 110----hlt
10 111 111(unused)
10 111 110----mov [imm16], acc
10 110 111----mov acc, [imm16]
11 00 iiww----mov [i(w)], acc
11 01 iiww----mov acc, [i(w)]
11 10 00wwSZVCadd ab, w
11 10 01wwSZVCsub ab, w
11 10 10wwSZVCadd w, imm8s16
11 10 11ww----mov w, imm16
11 11 vvww----mov v, wvw
11 11 0000(unused)
11 11 0101(unused)
11 11 1010----assertz
11 11 1111----nop

bbb: binary operator

EncodingInstructionAffected Flags
000addSZVC
001adcSZVC
010subSZVC
011sbcSZVC
100andSZ??
101orSZ??
110xorSZ??
111cmpSZVC

aaa: other ALU operations

EncodingInstructionAffected FlagsDescription
000(reset)
001rcrSZ?Crotate right acc through carry
010shrSZ?Cshift right acc filling in 0
011sarSZ?Cshift right acc replicating MSB
100notSZVC~acc
101negSZVC-acc
110clc---Cclear carry
111stc---Cset carry

fff: other control flow

EncodingInstructionDescription
000jmp piindirect jump
001call imm16call absolute
010call piindirect call
011retreturn
100int imm8software interrupt
101iretreturn from interrupt
110clidisable interrupts
111stienable interrupts

ddd/rrr: 8-bit operand

EncodingMeaning
000a
001b
010c
011d
100p (page)
101i (index)
110[pi] memory indirect
111acc

ii: address mode

EncodingAddress ModeNotation
00register[reg16]
01register post-increment[reg16++]
10register pre-decrement[--reg16]
11register + imm8s16[reg16+imm8s16]

vv/ww: 16 bit register

EncodingRegister
00ab
01cd
10pi
11sp

We use ab as the 16-bit accu. This just means that most 16-bit instructions only support ab as the destination and has nothing to do with the 8-bit register acc.

Instruction length

For each instruction, we give the number of micro-instructions / clock cycles needed to execute it. Often, using prefix_a16 makes the instruction take more clock cycles. You can then find documentation like

5 cycles for add acc, [pi]/adc acc, [pi] with prefix_a16 (the sequence prefix_a16; add acc, [pi] takes 6 cycles)

This means that add acc, [pi] takes 5 cycles if the prefix_a16 latch is set. Since the prefix_a16 instruction, which sets the latch, also takes one cycle, the total length of this two-instruction sequence is 6 cycles.