Skip to content

shalan/zx16

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZX16 RISC ISA Specification

Overview

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)

Key Features

  • 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

Register Architecture

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

Special Registers

  • PC: 16-bit program counter

Reset Behavior

  • PC: Initialized to 0x0000 on reset
  • x0-x7: Undefined values on reset (not initialized)

Memory Map

Range Usage
0x0000–0x001F Interrupt vector table (16 entries × 2 bytes)
0x0020–0xEFFF RAM & ROM
0xF000–0xFFFF MMIO (I/O registers start at 0xF000)

Memory Properties

  • Endianness: Little-endian
  • Addressing: Byte-addressable
  • Alignment: Word accesses (SW/LW) must be aligned to even addresses
  • Stack: Grows downward from 0xEFFE

Interrupt Vector Table

  • 16 fixed entries at 0x0000–0x001E (2 bytes each)
  • Reset handler at 0x0000; others at 0x0002, 0x0004, …, 0x001E

Instruction Formats

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.

Instruction Formats

All instructions are 16 bits with bits [2:0] as primary opcode:

R-Type (opcode = 000)

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

I-Type (opcode = 001)

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.

B-Type (opcode = 010)

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

S-Type (opcode = 011)

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

L-Type (opcode = 100)

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

J-Type (opcode = 101)

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

U-Type (opcode = 110)

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

SYS-Type (opcode = 111)

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

R-Type Instructions

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

I-Type Instructions

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)

B-Type Instructions

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]

S-Type Instructions

Mnemonic Description
SB Mem[x[rs1] + sext(imm4)] ← least-significant byte of x[rs2]
SW Mem[x[rs1] + sext(imm4)] ← x[rs2] (16 bits)

L-Type Instructions

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)]

J-Type Instructions

Mnemonic Description
J PC ← PC + offset
JAL x[rd] ← PC + 2; PC ← PC + offset

U-Type Instructions

Mnemonic Description
LUI rd ← (imm[15:7] << 7)
AUIPC rd ← PC + (imm[15:7] << 7)

SYS-Type Instructions

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]

Pseudo-Instructions

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):

LI16 x1, 0x00FF

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 rd - Bitwise NOT

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 - No operation

NOP
# Expands to:
ORI x0, 0         # x0 = x0 + x0 (does nothing useful)

Calling Convention

Function Calls

  • 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

Register Usage

  • Caller-saved: x0 (t0), x5 (t1), x6 (a0), x7 (a1)
  • Callee-saved: x3 (s0), x4 (s1)
  • Special: x1 (ra), x2 (sp)

Stack Management

  • Stack grows downward (toward lower addresses)
  • Callee must restore stack pointer before returning
  • Word-aligned stack operations recommended

Implementation Notes

Immediate Ranges

  • 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 Operations

  • 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

Memory Access

  • 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)

Control Flow

  • Branches: PC-relative, word-aligned targets
  • Jumps: PC-relative, word-aligned targets
  • Register jumps: JR, JALR - absolute addressing

Contribution

Please refer to the contribution guide here.

About

A 16-bit RISC-V Inspired ISA

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 13