Skip to the content
PCBKitNew board
2 layers245 × 126 mmUVM, the printed circuit board in 3D

8-bit Virtual Machine written in C

Size
245 × 126 mm
Copper layers
2
Thickness
1.6 mm
Parts
205
Nets
359
Pads
913
Tracks, vias
2,415, 43
Pours
2
Routed
Yes
KiCad
KiCad 5 (file 20171130)
Created
16 Oct 2018
Updated
7 Nov 2026

Overview

UVM is an open-source printed circuit board by sam159, from the sam159/uVM repository on GitHub, published under the Apache-2.0 licence. It is a 2-layer board of 245 × 126 mm with 205 parts on 359 nets, routed with 2,415 tracks and 43 vias.

It is built around the ATMEGA328P-PU, A13 and 74HC595, with the 74HC165D, 23LCV512-E/P and L7805 among its other parts. Its parts include 122 diodes, 23 ICs, 22 switches, 7 capacitors and 5 resistors. It belongs with the retro computing and games boards and the AVR (ATmega, ATtiny) boards of this library.

Main components

6

README

Readme.md

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.

shell
# 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.

shell
$ ./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.

shell
$ ./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) 
RXA: 200 (00512) RXB: 00 (00000) RXE: 00 (00000)
RXC: 00 (00000) RXF: 00 (00000) RXD: 00 (00000)

VM

Registers and Flags

  • 8-bit registers 16 registers (R0–RF), 8-bit each.
  • 16-bit registers 16 registers (RX0-RXF) mapped over the 8-bit registers.
    • RX0-RX9 mirroring R0-RF extended to 16-bits
    • 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
0HLTQexitcode ← rd + imm8Halt the VM. Sets halted = true, and the exit code to rd + imm8
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. Sets carry on overflow
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

text
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
    • File

Parts

205
QtyValueFootprintReferences
122LEDLED_THT:LED_D5.0mmD1, D10, D11, D100, D101, D102, D103, D104, D105, D109, D110, D111, D112, D113, D114, D115 +106
1674HC595Package_DIP:DIP-16_W7.62mmU4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18, U19
15330Resistor_THT:R_Array_SIP9RN1, RN2, RN3, RN4, RN5, RN6, RN7, RN8, RN9, RN10, RN11, RN12, RN13, RN14, RN16
510KResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_HorizontalR1, R2, R3, R4, R5
3100nFCapacitor_THT:C_Disc_D6.0mm_W2.5mm_P5.00mmC1, C4, C7
310KResistor_THT:R_Array_SIP9RN17, RN18, RN19
374HC165Package_DIP:DIP-16_W7.62mmU20, U21, U22
26pFCapacitor_THT:C_Disc_D6.0mm_W2.5mm_P5.00mmC5, C6
11000uFCapacitor_THT:CP_Radial_D8.0mm_P3.80mmC2
110KPotentiometer_THT:Potentiometer_Alps_RK09K_Single_VerticalRV1
116MHzCrystal:Crystal_HC18-U_VerticalY1
1220nFCapacitor_THT:C_Disc_D4.7mm_W2.5mm_P5.00mmC3
123LCV512Package_DIP:DIP-8_W7.62mm_SocketU2
13V CR2032Battery:BatteryHolder_Keystone_105_1x2430BT1
1A0uvm:TOGGLE_SPDT_ON_ON_2MSSW20
1A1uvm:TOGGLE_SPDT_ON_ON_2MSSW19
1A10uvm:TOGGLE_SPDT_ON_ON_2MSSW10
1A11uvm:TOGGLE_SPDT_ON_ON_2MSSW9
1A12uvm:TOGGLE_SPDT_ON_ON_2MSSW7
1A13uvm:TOGGLE_SPDT_ON_ON_2MSSW5
1A14uvm:TOGGLE_SPDT_ON_ON_2MSSW3
1A15uvm:TOGGLE_SPDT_ON_ON_2MSSW1
1A2uvm:TOGGLE_SPDT_ON_ON_2MSSW18
1A3uvm:TOGGLE_SPDT_ON_ON_2MSSW17
1A4uvm:TOGGLE_SPDT_ON_ON_2MSSW16
1A5uvm:TOGGLE_SPDT_ON_ON_2MSSW15
1A6uvm:TOGGLE_SPDT_ON_ON_2MSSW14
1A7uvm:TOGGLE_SPDT_ON_ON_2MSSW13
1A8uvm:TOGGLE_SPDT_ON_ON_2MSSW12
1A9uvm:TOGGLE_SPDT_ON_ON_2MSSW11
1ATmega328P-PUPackage_DIP:DIP-28_W7.62mm_SocketU1
1Barrel_JackConnector_BarrelJack:BarrelJack_HorizontalJ1
1Deposit / Nextuvm:TOGGLE_SPDT_ON_ON_2MSSW8
1Examine / Nextuvm:TOGGLE_SPDT_ON_ON_2MSSW6
1FDTI SerialConnector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_VerticalJ3
1ICSPConnector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_VerticalJ2
1L7805Package_TO_SOT_THT:TO-220-3_Horizontal_TabDownU3
1MCP23017_SPPackage_DIP:DIP-28_W7.62mmU23
1PortABConnector_PinSocket_2.54mm:PinSocket_2x08_P2.54mm_VerticalJ5
1PowerConnector_PinSocket_2.54mm:PinSocket_2x02_P2.54mm_VerticalJ4
1REG Selectuvm:TOGGLE_SPDT_ON_ON_2MSSW22
1RESETButton_Switch_THT:SW_PUSH_6mm_H5mmSW21
1Reset / Clearuvm:TOGGLE_SPDT_ON_ON_2MSSW4
1Run / Stepuvm:TOGGLE_SPDT_ON_ON_2MSSW2

Questions

How big is the UVM PCB?

It measures 245 × 126 mm and has 2 copper layers, 1.6 mm thick.

What parts does the UVM use?

205 parts, built around the ATMEGA328P-PU, A13, 74HC595 and 74HC165D. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the UVM KiCad files?

From the sam159/uVM repository on GitHub, at the commit this page was made from: the board is pcb/uvm.kicad_pcb. PCBKit opens the board in the browser, with nothing to install.

Can I use the UVM design in my own project?

Its repository publishes it under the Apache-2.0 licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

Similar boards

  • 2 layers93.2 × 67 mmBoard 722039m, the printed circuit board in 3D

    Board 722039m

    fabio-d/minitel-native

    Software and hardware tools to run native programs on the Minitel

    24 parts10CERN-OHL-W-2.0
  • 2 layers95.3 × 110 mmCatan, the printed circuit board in 3D

    Catan

    meltzg/arduino-playground

    69 parts1
  • 2 layers120 × 120 mmDiagram chart, the printed circuit board in 3D

    Diagram chart

    MMDavito/8_switch_rgb

    115 parts
  • 2 layers150 × 150 mmDk2 00, the printed circuit board in 3D

    Dk2 00

    mattybrad/drumkid2

    140 parts12GPL-3.0
  • 2 layers135 × 184.5 mmEMP, the printed circuit board in 3D

    EMP

    stonedDiscord/Automatenplatinen

    Reverse engineerte Hardware aus deutschen Spielautomaten.

    196 parts5BSD-2-Clause
  • 2 layers100 × 100 mmFlipdot H Bridge, the printed circuit board in 3D

    Flipdot H Bridge

    comakingspace/FlipDotDisplay

    They were bought at the Makerfaire Hannover 2016.

    76 parts3