Skip to content

Repository files navigation

uVM

This is an 8-bit virtual machine with 16-bit addressing. It is RISC-style with no direct operations on memory, instead operations are done on one of 16 8-bit registers with 6 16-bit registers mapped over them.

An ASM language is defined and implemented for this VM.

Building / Running

CMake and a C build toolchain are required to build. On a debian-based system install build-essential and cmake packages.

# setup makefiles (run once)
$ cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" -S . -B ./cmake-build-release
# build project
$ cmake --build ./cmake-build-release --target uVM

Usage is printed with no options given.

$ ./cmake-build-release/uVM Usage:
uVM program.bin # load at 0x0000
uVM -a 0x100 file1.bin -a 0x200 file2.bin # multiple files at absolute addresses
uVM -p 0x100 program.bin # set PC to 0x100
uVM -c file.asm [-o output.bin] # assemble into binary format

Compiling and running asm files can be achieved with the following.

$ ./cmake-build-release/uVM -c programs/basic.asm -o programs/basic.bin
Assembling 'programs/basic.asm' ->'programs/basic.bin'
$ /cmake-build-release/uVM programs/basic.bin Loaded 'programs/basic.bin' at 0x0000 (272 bytes)
VM Halted with code 0
PC: 0110 carry: 0 halted: 1 haltcode: 0000 (0)
R0: 00 (000) R4: 02 (002) R8: 00 (000) RC: 00 (000) R1: 01 (001) R5: 00 (000) R9: 00 (000) RD: 00 (000) R2: 64 (100) R6: 00 (000) RA: 00 (000) RE: 00 (000) R3: 00 (000) R7: 00 (000) RB: 00 (000) RF: 00 (000) RXB: 00 (00000) RXE: 00 (00000)
RXC: 00 (00000) RXF: 00 (00000)
RXD: 00 (00000)

VM

Registers and Flags

  • 8-bit registers 16 registers (R0RF), 8-bit each.
  • 16-bit registers 16 registers (RX0-RXF) mapped over the 8-bit registers.
    • RX0-RX9 with low bytes mapped to
    • RXA-RXF mapped to pairs of 8-bit registers
  • R0/RX0 always reads as 0. Writes to R0 are a no-op.
  • PC (program counter), 16-bit. Increments by 2 each instruction fetch. Must be word-aligned.
  • carry, 1-bit flag used by ADC and SBC.

16-bit Register Mapping

Some instructions can use two 8-bit registers as one 16-bit register for addressing.

The upper 12 registers form six extended 16-bit address registers (big-endian, high byte : low byte):

AliasRegisters
RXAR4 : R5
RXBR6 : R7
RXCR8 : R9
RXDRA : RB
RXERC : RD
RXFRE : RF

vm_get_rx() returns the 16-bit value of a register: 0x0-0x9 return the 8-bit variant, with 0xA-0xF returning the above registers.

Memory

  • 64KB address space (0x0000 - 0xFFFF).
  • All operations work on registers only, no direct memory-to-memory operations.
  • Memory access is via hook functions: readAddr and writeAddr on the VM struct.

Instruction Format

All instructions are two bytes (big-endian). The upper four bits are the opcode. Three operand formats share the lower 12 bits:

FormatLayout (16 bits)Description
Q[op:4][rd:4][imm8:8]Destination register + 8-bit immediate
S[op:4][rd:4][rx:4][ry:4]Destination + two source registers
T[op:4][rd:4][rx:4][imm4:4]Destination + source register + 4-bit immediate
V[op:4][imm12:12]12-bit immediate

Operations / Opcodes

Operand terms

  • exitcode - sets the vm exitcode
  • rd/rx/ry - 8-bit register
  • rdx - rd with extended register support
  • imm4 - immediate value 4-bit
  • imm4_s - immediate value 4-bit
  • imm4_s_x2 - immediate value 4-bit multiplied by two
  • test - a conditional jump type
  • imm8 - immediate value 8-bit
  • imm8_s - signed immediate value 8-bit
  • imm8_s_x2 - signed immediate value 8-bit multiplied by two
  • imm12 - immediate value 12-bit
  • imm12_s - signed immediate value 12-bit
  • imm12_s_x2 - signed immediate value 12-bit multiplied by two
  • carry - carry flag

All jumps are in words/instructions.

OpMnemonicFormatOperationDescription
0HLTVexitcode ← imm12Halt the VM. Sets halted = true, and the exit code to imm12
1LDATrd ← [rdx + imm4]Load byte from memory at 16-bit address rdx + imm4 into rd
2STAT[rdx + imm4] ← rxStore rx to memory at 16-bit address rdx + imm4
3LDIQrd ← imm8Load imm8 into rd
4ADDSrd ← rx + ry8-bit add. Clears carry
5ADCSrd ← rx + ry + carry8-bit add with carry. Sets carry on overflow
6SUBSrd ← rx - ry8-bit subtract. Clears carry
7SBCSrd ← rx - ry - carry8-bit subtract with borrow. Propagates borrow bit-by-bit into carry
8NOTTrd ← ~rxBitwise NOT of rx
9BITTrd ← rd OP rxBitwise operation on rd using rx. OP: 0=AND, 1=OR, 2=XOR, 4=MOD
ASHLTrd ← rx << imm4Logical left shift by imm bits
BSHRTrd ← rx >> imm4Logical right shift by imm bits
CJMPVPC ← PC + imm12_s_x2Relative jump. Update PC with imm12_s_x2
DJPFQPC ← rdx + imm8_s_x2Far jump. Set PC to the value in rdx with imm8_s_x2 value
EJNZQPC ← PC + imm8_s_x2 if rd != 0Conditional jump. Add imm8_s_x2 to PC if rd is not zero
FJPCTPC ← PC + 2 if !test(rd,rx)Conditional jump. Skips the next instruction if test passes for values in rd and rx

Conditional Jumps

Test types, used for comparing values in rd and rx.

Tests are mapped into a 4-bit value and can be in any combination

3 2 1 0
| | | |
| | | +-- Equality
| | +------ Less Than
| +---------- Greater Than
+-------------- Negate Result

Setting bits 0–3 will pass for any value, negating it will not pass for any value, similarly not setting any bits will not pass for any value.

MnemonicNumberBinaryTest
EQ10001Equals
~EQ91001Not equals
LT20010Less than
~LT101010Not less than
LTE30011Less than or equal
~LTE111011Not less than or equal
GT40100Greater than
~GT121100Not greater than
GTE50101Greater than or equal
~GTE131101Not greater than or equal

Error Handling

Errors are reported via the error hook and halt the VM:

CodeConstantCondition
1VM_ERR_MISALIGNPC is not word-aligned on instruction fetch.
2VM_ERR_UNKNOWN_OPOpcode is not recognized (0–F). Redundant, exists for completeness.
3VM_ERR_OUT_OF_BOUNDSMemory access address >= 0x10000.
4VM_ERR_JUMP_SELFAn jump instruction jumped to itself
5VM_ERR_INVALID_BIT_OPBIT instruction has invalid operation code (not 0, 1, 2, or 4).

Execution Model

  1. vm_step() fetches one 2-byte instruction from readAddr(PC).
  2. Decodes the opcode and operand format.
  3. Executes the operation, writing the result to rd via vm_put_r().
  4. Increments PC by 2 (except for jumps which set PC directly).
  5. The main loop calls vm_step() repeatedly until vm->halted is true.

TODO

ASM

  • allow variables in STA/LDA address offset
  • allow referencing variable high/low byte with :h/:l

VM

  • Add device support
    • Console
    • File

About

8-bit Virtual Machine written in C

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages