ZX16 RISC ISA Specification
The ZX16 RISC ISA is a 16-bit reduced-instruction-set architecture designed for simplicity, efficiency, and educational use. It features:
16-bit fixed-width instructions and data
8 general-purpose registers (x0–x7) plus a 16-bit PC
64 KB flat address space for code, data, MMIO, and interrupt vectors
8 instruction formats selected by bits [2:0]
50+ real & pseudo-instructions covering ALU, branches, loads/stores, jumps, upper-immediates, and syscalls
Smart immediates : any immediate < 16 bits is sign-extended
PC-relative control flow : all branches, J and JAL use PC-relative offsets
Memory-mapped I/O at 0xF000–0xFFFF
16 interrupt vectors at 0x0000–0x001F (2 bytes each; reset at 0x0000)
Compact 16-bit instructions
Two-operand ALU (rd/rs1 is both destination & first source)
Smart assembler handles large constants via pseudo-ops
Rich syscall interface for I/O, graphics, audio
Little-endian byte order
Aligned memory access required for word operations
Byte-addressable memory
General-Purpose Registers
Register
ABI
Role
Notes
x0
t0
Temporary
Caller-saved scratch/Assembler Temporary
x1
ra
Return address
Used by JAL/JALR
x2
sp
Stack pointer
Initialized to 0xEFFE
x3
s0
Saved / Frame pointer
Callee-saved
x4
s1
Saved
Callee-saved
x5
t1
Temporary
Caller-saved scratch
x6
a0
Arg0 / Return value
x7
a1
Arg1
Further args spill to stack
PC : 16-bit program counter
PC : Initialized to 0x0000 on reset
x0-x7 : Undefined values on reset (not initialized)
Range
Usage
0x0000–0x001F
Interrupt vector table (16 entries × 2 bytes)
0x0020–0xEFFF
RAM & ROM
0xF000–0xFFFF
MMIO (I/O registers start at 0xF000)
Endianness : Little-endian
Addressing : Byte-addressable
Alignment : Word accesses (SW/LW) must be aligned to even addresses
Stack : Grows downward from 0xEFFE
16 fixed entries at 0x0000–0x001E (2 bytes each)
Reset handler at 0x0000; others at 0x0002, 0x0004, …, 0x001E
Every instruction is 16 bits, with bits [2:0] as primary opcode:
opcode ([2:0])
Format
000
R-Type
001
I-Type
010
B-Type
011
S-Type
100
L-Type
101
J-Type
110
U-Type
111
SYS-Type
ZX16 Instruction Format Field Layouts
All instructions are 16 bits. Bits [2:0] select the format/opcode.
All instructions are 16 bits with bits [2:0] as primary opcode:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ funct4 │ rs2 │ rd/rs1 │ func3 │ 0 │ 0 │ 0 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15:12] funct4
[11:9] rs2
[8:6] rd/rs1 (two‐operand: dest & first source)
[5:3] func3
[2:0] 000
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ imm7 (signed) │ rd/rs1 │ func3 │ 0 │ 0 │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15:9] imm7 (7-bit signed immediate, sign-extended)
[8:6] rd/rs1
[5:3] func3
[2:0] 001
Note : For shift instructions: shift_amt = imm7[3:0]}; imm7[6:4] are used to determine the shift type.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ imm[4:1] │ rs2 │ rs1 │ func3 │ 0 │ 1 │ 0 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15:12] imm[4:1] (high 4 bits of 5-bit signed offset, imm[0] = 0) -- Range: -16 to 14.
[11:9] rs2 (ignored for BZ/BNZ)
[8:6] rs1
[5:3] func3
[2:0] 010
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ imm[3:0] │ rs2 │ rs1 │ func3 │ 0 │ 1 │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15:12] imm[3:0] (4-bit signed store offset) -- Range: -8 to +7
[11:9] rs2 (data register)
[8:6] rs1 (base register)
[5:3] func3
[2:0] 011
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ imm[3:0] │ rs2 │ rd │ func3 │ 1 │ 0 │ 0 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15:12] imm[3:0] (4-bit signed load offset) -- Range: -8 to +7
[11:9] rs2 (base register)
[8:6] rd (destination register)
[5:3] func3
[2:0] 100
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ L │ imm[9:4] │ rd │ imm[3:1] │ 1 │ 0 │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15] link flag (0 = J, 1 = JAL)
[14:9] imm[9:4] (high 6 bits of 10-bit signed offset, imm[0] = 0) -- Range: -1024 to +1022
[8:6] rd (link register for JAL)
[5:3] imm[3:1] (low 3 bits of offset)
[2:0] 101
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ F │ imm[15:10] │ rd │ imm[9:7] │ 1 │ 1 │ 0 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15] flag (0 = LUI, 1 = AUIPC)
[14:9] imm[15:10] (high 6 bits of immediate)
[8:6] rd
[5:3] imm[9:7] (mid 3 bits of immediate)
[2:0] 110
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ svc[9:0] │ 0 │ 0 │ 0 │ 1 │ 1 │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
[15:6] svc (10-bit system-call number)
[5:3] 000
[2:0] 111
Instruction Set Reference
Mnemonic
Description
ADD
rd ← rd + rs2
SUB
rd ← rd – rs2
SLT
rd ← (rd < rs2) ? 1 : 0
SLTU
rd ← (unsigned rd < unsigned rs2) ? 1 : 0
SLL
rd ← rd << (rs2 & 0xF)
SRL
rd ← rd >> (rs2 & 0xF) (logical)
SRA
rd ← rd >> (rs2 & 0xF) (arithmetic)
OR
rd ← rd ∣ rs2
AND
rd ← rd ∧ rs2
XOR
rd ← rd ⊕ rs2
MV
rd ← rs2
JR
PC ← rd
JALR
rd ← PC + 2; PC ← rs2
Mnemonic
Description
ADDI
rd ← rd + sext(imm7)
SLTI
rd ← (rd < sext(imm7)) ? 1 : 0
SLTUI
rd ← (unsigned rd < unsigned sext(imm7)) ? 1 : 0
SLLI
rd ← rd << imm[3:0]
SRLI
rd ← rd >> imm[3:0] (logical)
SRAI
rd ← rd >> imm[3:0] (arithmetic)
ORI
rd ← rd ∣ sext(imm7)
ANDI
rd ← rd ∧ sext(imm7)
XORI
rd ← rd ⊕ sext(imm7)
LI
rd ← sext(imm7)
Mnemonic
Description
BEQ
PC ← PC + offset if x[rs1] == x[rs2]
BNE
PC ← PC + offset if x[rs1] != x[rs2]
BZ
PC ← PC + offset if x[rs1] == 0
BNZ
PC ← PC + offset if x[rs1] != 0
BLT
PC ← PC + offset if x[rs1] < x[rs2]
BGE
PC ← PC + offset if x[rs1] ≥ x[rs2]
BLTU
PC ← PC + offset if unsigned x[rs1] < unsigned x[rs2]
BGEU
PC ← PC + offset if unsigned x[rs1] ≥ unsigned x[rs2]
Mnemonic
Description
SB
Mem[x[rs1] + sext(imm4)] ← least-significant byte of x[rs2]
SW
Mem[x[rs1] + sext(imm4)] ← x[rs2] (16 bits)
Mnemonic
Description
LB
rd ← sign-extended byte at Mem[x[rs2] + sext(imm4)]
LW
rd ← word at Mem[x[rs2] + sext(imm4)] (16 bits)
LBU
rd ← zero-extended byte at Mem[x[rs2] + sext(imm4)]
Mnemonic
Description
J
PC ← PC + offset
JAL
x[rd] ← PC + 2; PC ← PC + offset
Mnemonic
Description
LUI
rd ← (imm[15:7] << 7)
AUIPC
rd ← PC + (imm[15:7] << 7)
Mnemonic
Description
ECALL
Trap to service number in bits [15:6]
Instruction Identification Table
This table shows, for each instruction, the key fields used to distinguish it: the primary opcode (bits [2:0]), plus any funct4, func3, link or flag bits, or immediate‐pattern conditions.
Mnemonic
Format
opcode [2:0]
funct4 [15:12]
func3 [5:3]
link/flag (bit15)
imm‐pattern/notes
R-Type
ADD
R
000
0000
000
—
two‐operand
SUB
R
000
0001
000
—
SLT
R
000
0010
001
—
SLTU
R
000
0011
010
—
SLL
R
000
0100
011
—
logical left shift
SRL
R
000
0101
011
—
logical right shift
SRA
R
000
0110
011
—
arithmetic right shift
OR
R
000
0111
100
—
AND
R
000
1000
101
—
XOR
R
000
1001
110
—
MV
R
000
1010
111
—
move
JR
R
000
1011
000
—
PC ← rd
JALR
R
000
1100
000
—
link in rd, then PC ← rs2
I-Type
ADDI
I
001
—
000
—
imm7 signed
SLTI
I
001
—
001
—
imm7 signed
SLTUI
I
001
—
010
—
imm7 unsigned compare
SLLI
I
001
—
011
—
shift left logical, imm7[6:4]=001
SRLI
I
001
—
011
—
shift right logical, imm7[6:4]=010
SRAI
I
001
—
011
—
shift right arithmetic, imm7[6:4]=100
ORI
I
001
—
100
—
ANDI
I
001
—
101
—
XORI
I
001
—
110
—
LI
I
001
—
111
—
load imm7
B-Type
BEQ
B
010
—
000
—
offset = sext(imm[4:1]∥0)
BNE
B
010
—
001
—
BZ
B
010
—
010
—
ignore rs2
BNZ
B
010
—
011
—
ignore rs2
BLT
B
010
—
100
—
BGE
B
010
—
101
—
BLTU
B
010
—
110
—
unsigned compare
BGEU
B
010
—
111
—
unsigned compare
S-Type
SB
S
011
—
000
—
store byte, offset=sext(imm[3:0])
SW
S
011
—
001
—
store word
L-Type
LB
L
100
—
000
—
load byte
LW
L
100
—
001
—
load word
LBU
L
100
—
100
—
load byte unsigned
J-Type
J
J
101
—
—
link=0
offset = sext({imm[9:4],imm[3:1],0})
JAL
J
101
—
—
link=1
link in rd, then PC-relative
U-Type
LUI
U
110
—
—
flag=0
imm from bits [15:7]<<7
AUIPC
U
110
—
—
flag=1
PC + (imm<<7)
SYS-Type
ECALL
SYS
111
—
—
—
trap to service number [15:6]
ZX16 supports several pseudo-instructions that expand to one or more real instructions:
LI16 rd, imm16 - Load 16-bit immediate
Standard case (bit 6 clear):
LI16 x1, 0x1234
Expands to:
LUI x1 , 0x24 # Load upper 9 bits ( 0x1234 >> 7 = 0x24 )
ADDI x1 , 0x34 # Add lower 7 bits ( 0x1234 & 0x7F = 0x34 )
Corner case (bit 6 set - ADDI immediate will be negative):
Expands to:
LUI x1 , 0x02 # Load upper 9 bits + 1 (( 0x00FF >> 7 ) + 1 = 0x02 )
ADDI x1 , - 1 # Add lower 7 bits as signed ( 0xFF & 0x7F = 0x7F = - 1 in 7 - bit signed)
LA rd, label - Load address
LA x1 , data_label
# Expands to:
AUIPC x1 , ((label - PC) >> 7 ) # PC + upper bits of relative offset
ADDI x1 , ((label - PC) & 0x7F ) # Add lower 7 bits of relative offset
Note: The same LI16 bit 6 corner case must be handled.
LJ label * - Long Jump (for distances beyond J range)
LJ distant_label
# Expands to:
LI16 x0 , distant_label # Load full address into temp register
JR x0 # Jump to address in register
PUSH rd - Push register to stack
PUSH x1
# Expands to:
ADDI x2 , - 2 # SP - = 2 (decrement stack pointer)
SW x1 , 0 (x2) # Store register at new SP
POP rd - Pop from stack to register
POP x1
# Expands to:
LW x1 , 0 (x2) # Load from current SP
ADDI x2 , 2 # SP + = 2 (increment stack pointer)
CALL label - Call function
CALL func_name
# Expands to:
JAL x1 , offset # Jump and link (return address in x1/ra)
RET - Return from function
RET
# Expands to:
JR x1 # Jump to return address (x1/ra)
INC rd - Increment register
INC x1
# Expands to:
ADDI x1 , 1 # rd = rd + 1
DEC rd - Decrement register
DEC x1
# Expands to:
ADDI x1 , - 1 # rd = rd - 1
NEG rd - Negate register (two's complement)
NEG x1
# Expands to:
XORI x1 , - 1 # Invert all bits ( XOR with 0x7F sign - extended)
ADDI x1 , 1 # Add 1 to complete two's complement
NOT x1
# Expands to:
XORI x1 , - 1 # XOR with all 1s ( 0x7F sign - extended to 0xFFFF )
CLR rd - Clear register to zero
CLR x1
# Expands to:
XOR x1 , x1 # x1 = x1 XOR x1 = 0
NOP
# Expands to:
ORI x0 , 0 # x0 = x0 + x0 (does nothing useful)
Arguments : x6 (a0), x7 (a1); additional arguments spill to stack
Return value : x6 (a0)
Return address : x1 (ra) - set by JAL/JALR, used by RET
Stack pointer : x2 (sp) - points to top of stack
Caller-saved : x0 (t0), x5 (t1), x6 (a0), x7 (a1)
Callee-saved : x3 (s0), x4 (s1)
Special : x1 (ra), x2 (sp)
Stack grows downward (toward lower addresses)
Callee must restore stack pointer before returning
Word-aligned stack operations recommended
I-Type : -64 to +63 (7-bit signed)
S-Type/L-Type : -8 to +7 (4-bit signed)
B-Type : -16 to +14 bytes (5-bit signed, word-aligned)
J-Type : -1024 to +1022 bytes (10-bit signed, word-aligned)
U-Type : 0 to 511 (9-bit unsigned immediate 0-511, shifted left 7 bits)
Shift amount : Limited to 0-15 (4 bits)
R-Type shifts : Use rs2 & 0xF as shift amount
I-Type shifts : Use imm[3:0] as shift amount, imm[6:4] selects operation
Byte operations : SB, LB, LBU - no alignment required
Word operations : SW, LW - must be aligned to even addresses
Endianness : Little-endian (LSB at lower address)
Branches : PC-relative, word-aligned targets
Jumps : PC-relative, word-aligned targets
Register jumps : JR, JALR - absolute addressing
Please refer to the contribution guide here .