A portable CHIP-8 interpreter/emulator written in pure C11, inspired by the architecture and coding style of PeakRacing/nes.

- 35 CHIP-8 opcodes — full original instruction set (no SUPER CHIP-8 extensions)
- Three-layer architecture — portable core (
inc/ + src/), example port layer (port/), and SDL2 implementation (sdl/)
- Platform abstraction — standard C memory, file I/O, and rendering via user-overridable weak symbols
- SDL2 frontend — hardware-accelerated rendering, keyboard input, and square-wave audio
- Battery included — SDL2 fetched automatically by xmake, no manual dependency installation
- Configurable — display scale, CPU frequency, color depth, and logging level at compile time
- xmake (install via
winget install xmake on Windows, or your platform's package manager)
- SDL2 is auto-fetched by xmake — nothing to install manually
cd sdl
xmake # fetch SDL2, compile, link
xmake run chip8 <romfile> # load and run a CHIP-8 ROM
cd sdl
xmake f -m debug
xmake
xmake run chip8 <romfile>
chip8/
├── inc/ # Portable public headers (no platform deps)
│ ├── chip8.h # Core struct chip8_t + lifecycle API
│ ├── chip8_cpu.h # CPU state (16 registers, stack, timers)
│ ├── chip8_display.h # 64×32 monochrome framebuffer
│ ├── chip8_keypad.h # 16-key bitfield
│ ├── chip8_rom.h # ROM loader
│ ├── chip8_log.h # Tiered logging macros
│ └── chip8_default.h # Platform abstraction + compile-time config
├── src/ # Portable core (no platform deps)
│ ├── chip8.c # Init, deinit, main emulation loop
│ ├── chip8_cpu.c # 35-opcode fetch-decode-execute
│ ├── chip8_rom.c # Raw binary ROM loader
│ └── chip8_default.c # Default weak-function stubs
├── port/ # Example port (user-replaceable)
│ ├── chip8_conf.h # Configurable macros
│ └── chip8_port.c # Platform function stubs
└── sdl/ # SDL2 platform implementation
├── xmake.lua # xmake build config
├── main.c # Entry point: init → load → run → free
└── port/
├── chip8_conf.h # SDL-specific config (FS enabled)
└── chip8_port.c # SDL window, renderer, input, audio, frame timing
inc/ + src/ — never include platform headers (no <SDL.h>, <windows.h>)
port/ — example port. Functions declared with CHIP8_WEAK so users can override them
sdl/port/ — SDL-specific adapter. Overrides port/ stubs with real SDL2 implementations
src/*.c — only include "chip8.h" (which aggregates all public headers)
Physical keyboard 1 2 3 4 Q W E R A S D F Z X C V maps to the CHIP-8 hex keypad:
| CHIP-8 |
Keyboard |
|
CHIP-8 |
Keyboard |
1 |
1 |
|
9 |
D |
2 |
2 |
|
A |
Z |
3 |
3 |
|
0 |
X |
C |
4 |
|
B |
C |
4 |
Q |
|
F |
V |
5 |
W |
|
7 |
A |
6 |
E |
|
8 |
S |
D |
R |
|
E |
F |
CHIP-8 Keypad Layout: Physical Keyboard:
┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ C │ │ 1 │ 2 │ 3 │ 4 │
├───┼───┼───┼───┤ ├───┼───┼───┼───┤
│ 4 │ 5 │ 6 │ D │ │ Q │ W │ E │ R │
├───┼───┼───┼───┤ ├───┼───┼───┼───┤
│ 7 │ 8 │ 9 │ E │ │ A │ S │ D │ F │
├───┼───┼───┼───┤ ├───┼───┼───┼───┤
│ A │ 0 │ B │ F │ │ Z │ X │ C │ V │
└───┴───┴───┴───┘ └───┴───┴───┴───┘
| Address Range |
Usage |
0x000 – 0x04F |
Font set (16 characters × 5 bytes = 80 bytes) |
0x050 – 0x1FF |
Reserved (original COSMAC VIP interpreter area) |
0x200 – 0xFFF |
Program ROM + working RAM |
- 16 × 8-bit general-purpose registers V0–VF (VF doubles as carry/borrow flag)
- 16-bit index register I
- 16-bit program counter PC
- 8-bit stack pointer SP (16-level call stack)
- 8-bit delay timer (decremented at 60 Hz)
- 8-bit sound timer (beeps while > 0, decremented at 60 Hz)
Instruction Set (35 opcodes)
| Category |
Opcodes |
Description |
| Display |
00E0, DXYN |
Clear screen, draw sprite |
| Flow |
1NNN, 2NNN, 00EE, BNNN |
Jump, call, return |
| Conditional |
3XNN, 4XNN, 5XY0, 9XY0 |
Skip if equal / not equal |
| Constants |
6XNN, 7XNN |
Load, add |
| Bitwise |
8XY1, 8XY2, 8XY3, 8XY6, 8XYE |
OR, AND, XOR, SHR, SHL |
| Arithmetic |
8XY4, 8XY5, 8XY7 |
Add / subtract with carry/borrow |
| Memory |
ANNN, FX1E, FX29, FX33, FX55, FX65 |
I-register, BCD, block load/store |
| Timer |
FX07, FX15, FX18 |
Delay / sound timer access |
| Keypad |
EX9E, EXA1, FX0A |
Key pressed / not pressed / wait |
| Random |
CXNN |
Random AND mask |
Edit sdl/port/chip8_conf.h before building:
| Macro |
Default |
Description |
CHIP8_DISPLAY_SCALE |
16 |
Scale factor (64×32 → 1024×512) |
CHIP8_CPU_FREQ |
500 |
Instructions per second |
CHIP8_COLOR_DEPTH |
32 |
Pixel format (32 = ARGB8888, 16 = RGB565) |
CHIP8_LOG_LEVEL |
INFO |
Log verbosity (NONE / ERROR / WARN / INFO / DEBUG) |
CHIP8_USE_FS |
1 |
Enable file system for ROM loading |
- Copy
port/chip8_conf.h and port/chip8_port.c as starting templates
- Provide platform-specific implementations for the function stubs declared in
chip8_default.h:
chip8_malloc, chip8_free, chip8_memset, chip8_memcpy, chip8_memcmp — memory operations
chip8_initex, chip8_deinitex — platform init/teardown
chip8_draw — render framebuffer to screen
chip8_frame — per-frame hook (input polling, audio, frame pacing)
- (Optional)
chip8_fopen, chip8_fread, chip8_fwrite, chip8_fseek, chip8_fclose — file I/O
- Build with your platform's toolchain, linking against
inc/ headers and src/*.c source files
| Directory |
Purpose |
Platform-Dependent |
inc/ |
Public headers |
No |
src/ |
Core emulator |
No |
port/ |
Example port (stubs) |
Configurable |
sdl/ |
SDL2 frontend + build |
Yes |
Apache License 2.0 © PeakRacing