No description
- Rust 72.7%
- Assembly 22.1%
- Python 5.1%
|
|
||
|---|---|---|
| .cargo | ||
| .forgejo/workflows | ||
| asm | ||
| assembler | ||
| assets | ||
| core | ||
| docs | ||
| emulator | ||
| tests | ||
| tools | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| custom_font.txt | ||
| Makefile | ||
| README.md | ||
| rust-analyzer.toml | ||
Cove
A from-scratch 16-bit fantasy console in Rust. Custom CPU, assembler, graphics pipeline, audio engine, and debugger — 11k lines across 3 crates, 317+ unit tests, 29 e2e tests, 3077 lines of documentation.
cargo build --release && cargo test && make e2e
Why this exists
I wanted to build something that touches every layer of a real system — CPU architecture, assembly language design, graphics, audio, input, memory mapping, interrupts, debugging tools — without relying on an OS or existing runtime. The constraint of a limited platform (tiny screen, tight memory, slow clock) makes for interesting engineering problems and honest optimisation work.
Architecture
flowchart LR
A[assembler<br/>.asm → .bin] --> B[core<br/>CPU, mem, gfx,<br/>audio, syscalls]
C[emulator<br/>SDL2 frontend,<br/>debugger REPL] --> B
B --> D[Cartridge file<br/>.cove.png / .bin]
core/—#![no_std], zero I/O dependencies. CPU, instruction decoder, memory-mapped I/O, graphics pipeline, audio engine, 21 syscalls. Clean separation: every component is testable in isolation.assembler/— Two-pass assembler with labels, forward references, automatic short-to-long encoding expansion, symbol table output.emulator/— SDL2 window with integer-scaled rendering, audio via rodio, memory-mapped keyboard/mouse/controller input, async HTTP, REPL debugger with breakpoints, disassembly, and backtrace.
The CPU in one minute
| Feature | Detail |
|---|---|
| Word size | 16-bit |
| Registers | 8 general purpose (R0–R7), PC, SP, FL |
| Instruction encoding | Mixed 16/32-bit (assembler auto-selects) |
| Addressing modes | reg-reg, short imm (6-bit), long imm (16-bit), zero-page (64 bytes), register indirect, indexed, absolute |
| 49 instructions | ALU (ADD, SUB, MUL, DIV, AND, OR, XOR, NOT, shifts, CMP), data (MOV, LDW, LDB, STW, STB), control (JMP, Jcc with 8 condition codes, CALL reg-indirect, RET, RTI), stack (PUSH, POP, PUSHF, POPF), system (SYS, HLT, EI, DI) |
| Cycle costs | 1–5 cycles per instruction, deterministic |
| Interrupts | VBlank (60 Hz) and timer, 2-level vector table, tear-free framebuffer snapshot |
Platform specs
| Display | 128×128, 4 bpp nibble-packed, 4 palette banks × 16 colours (24-bit RGB), integer scaling with letterboxing |
| Audio | 4 independent channels, 5 waveforms (square 25%/50%, triangle, noise, sawtooth), ADSR envelopes, memory-mapped registers |
| Memory | 64 KB — zero-page, code/data, 512-sprite bank, framebuffer, palette/system/audio/input registers, 2 KB save data, 4 KB stack |
| Tilemap | Hardware-scrolling background layer, configurable width, optional transparency |
| Input | Memory-mapped bitmapped — full keyboard, mouse (X/Y + 3 buttons), 2 controllers (analog sticks + D-pad + 8 buttons each) |
| Syscalls | 21 — debug print, sprite, tile, text rendering, shapes (line/rect/circle), random, sqrt, sin, async HTTP (4 concurrent), save/load, PRNG seed, exit |
| Cartridge | .cove.png = valid PNG with embedded 64 KB binary + optional background image; or raw .bin |
| Save data | 2 KB persistent region, stored inline in cartridge file, explicit flush/reload |
| Assembler | Two-pass, case-insensitive mnemonics, labels, .org/.dw/.db/.ds/.equ directives, string escapes, auto short/long expansion, .sym output |
| Debugger | In-emulator REPL: step, breakpoints (address/symbol), disassembly with annotated return addresses, registers, memory hex view, stack backtrace |
What's inside
37 assembly example programs in asm/:
| Demo | What it shows |
|---|---|
quicksort_anim.asm |
Animated Lomuto quicksort — 128 bars sorted with SYS_LINE each frame |
scroll_demo.asm |
Hardware tilemap scrolling with transparency |
audio_demo.asm |
Multi-channel ADSR, waveforms, frequency sweeps |
interrupt_demo.asm |
VBlank and timer interrupts with nesting |
pendulum.asm |
Physics simulation: fixed-point trigonometry, line drawing |
http_test.asm |
Async HTTP GET via 4-concurrent syscalls |
datetime.asm |
Real-time clock registers |
gfx.asm |
Full sprite + tile + text + shape compositing |
sieve.asm |
Prime sieve — register allocation, loops, data layout |
multable.asm |
Multiplication table — nested loops, formatted output |
font_test.asm |
Custom font rendering via SYS_PRINT_CUSTOM |
Plus 29 end-to-end tests in tests/asm/ covering every instruction group,
addressing mode, flag behaviour, and syscall.
Engineering highlights
core/— the CPU and memory.- Instruction decoder — single
decode()method on a 16-bit word. Every encoding path (opcode, dst, src, extra, mode) is verified with property-style decode/encode round-trip tests. - Tear-free rendering — framebuffer is atomically snapshotted at the start of VBlank before the interrupt fires. The displayed frame never shows mid-update artefacts.
- Custom two-pass assembler — labels can be forward-referenced. The assembler estimates instruction sizes, detects when a jump/immediate exceeds short-form range, expands to long form, and re-lays out the 64 KB image. Converges in 2–3 passes.
- 30+ e2e tests — each
.asmintests/asm/is assembled and run headless, with assertions on register values, memory contents, and exit codes.
Getting started
cargo build --release
# Assemble and run a demo
./target/release/assembler asm/quicksort_anim.asm -o quicksort.bin
./target/release/emulator quicksort.bin
# Headless testing
./target/release/emulator tests/asm/add.bin --cli --dump 0010-0020
# Debugger REPL
./target/release/emulator program.bin
# E2E tests
make e2e
Documentation
- Programmer's Reference Manual — 2122 lines, covers every instruction, syscall, register, and memory region
- Specification — 955 lines, encoding details, memory map, interrupt timing, cartridge format
License
MIT
