The art of CHIP-8
The Art of CHIP-8
The CHIP-8 is a specialized programming language that first appeared in 1977, designed for the COSMAC VIP kit computer.
Because CHIP-8 programs consist of a sequence of two-byte instructions—essentially acting as machine code for a basic virtual instruction-set architecture (ISA)—the software used to run them is frequently called an interpreter emulator.
"For historical platforms to live, rather than simply be preserved, we must write new software for them."
Developing a CHIP-8 interpreter is a fantastic entry point for anyone wanting to understand how emulators for retro consoles and vintage computers function. Consequently, there are thousands of different CHIP-8 runtimes available across nearly every platform imaginable.
The Fragmentation Problem
This explosion of implementations has led to a "game of telephone" spanning fifty years, resulting in significant behavioral discrepancies between different interpreters. While developing Octo (my high-level assembler for CHIP-8), I worked to standardize "quirks flags" to categorize these divergences and explored the undocumented behaviors of influential early interpreters.
While professional test suites now exist—meaning modern developers have no excuse for inaccuracy—the ecosystem remains fragmented. Many interpreters are written by novices who abandon the project as soon as they successfully launch PONG.CH8.
Goal of this guide: I will analyze CHIP-8 as an ISA and provide practical advice for writing portable programs, synthesizing my previous tutorials and FAQs. I will focus on methods that work across almost all interpreters, excluding only the most broken ones.
Note: I will use Octo's notation for examples. While this isn't a full Octo manual, I will explain concepts as they arise.
🖥️ System Overview
CHIP-8 utilizes a 12-bit address space. The memory layout historically looked like this:
Memory Constraints
- Original Layout: The first 512 bytes were reserved for the interpreter; programs began at
0x200. - Available Space: This leaves a maximum of 3,232 bytes for code and data.
- Modern Variations: Some interpreters are more lenient, offering up to 3,584 bytes or allowing programs to use the first 512 bytes.
- Portability Tip: Keep your data and code within the 3,232-byte limit.
Hardware Specifications
The system feels reminiscent of a RISC architecture:
- Registers: 16 general-purpose 8-bit registers (
v0throughvf). - Index Register: A single 12-bit register
iused for memory addressing. - The Stack: An internal, opaque stack for subroutine returns. You cannot manually push/pop values or inspect it.
- Output:
- A 1-bit bitmapped display ( pixels).
- A simple piezo buzzer.
- Utilities: A non-interrupting delay timer and a random number generator.

⚙️ Machine Code & Syntax
Instructions are always two bytes wide and aligned by nybble. This design makes hand-assembling with pen and paper feasible and simplifies self-modifying code.
Instruction Reference Table
| Machine Code | Octo Syntax | Description |
|---|---|---|
00E0 | clear | Wipes the display. |
6XNN | vx := NN | Sets vx to immediate value NN. |
7XNN | vx += NN | Adds immediate NN to vx. |
8XY0 | vx := vy | Sets vx to the value of vy. |
8XY1 | `vx | = vy` |
DXYN | sprite vx vy N | Draws sprite at vx, vy with height N; vf = 1 if collision occurs. |
FX15 | delay := vx | Sets the delay timer to vx. |
FX18 | buzzer := vx | Sets the buzzer timer to vx. |
FX1E | i += vx | Increments index register i by vx. |
FX29 | i := hex vx | Sets i to the location of the hex character sprite for vx. |
FX65 | load vx | Loads v0 through vx from memory starting at i. |
🧮 Arithmetic Instructions
Immediate Operations
Two instructions handle immediate values:
6XNNvx := NN7XNNvx += NN
Register Operations
The following operations manipulate two registers, storing the result in vx. Octo uses C-style operators with a Pascal-style assignment (:=).
| Machine Code | Octo Syntax | Operation |
|---|---|---|
8XY0 | vx := vy | Assignment |
8XY1 | `vx | = vy` |
8XY2 | vx &= vy | Bitwise AND |
8XY3 | vx ^= vy | Bitwise XOR |
8XY4 | vx += vy | Addition |
8XY5 | vx -= vy | Subtraction |
8XY6 | vx = vy | Equality check |
8XY7 | vx =- vy | Reverse Subtraction |
8XYE | vx = vy | Inequality check |
Nuances of Arithmetic:
- Undocumented Ops: Instructions like
^=,&=, and=-were not in original docs but existed due to the RCA-1802 encoding. - Reverse Subtraction: The
=-operation is defined as: (The result is still stored in ).
The "Shift Quirks"
Shift instructions are meant to set vx to vy shifted by one bit, with the overflow stored in vf. However, many modern interpreters incorrectly shift vx in-place.
Solution for Portability:
- Use the same register for both arguments (e.g.,
vxandvx) to ensure consistent behavior across all interpreters.
Bitwise Side Effects
Some interpreters cause |=, &=, and ^= to modify the vf register. To ensure your code is portable:
- Assume
vfis destroyed during any bitwise operation. - Bitwise NOT: Since there is no dedicated NOT instruction, perform an XOR with a constant:
vx ^= 0xFF ; This effectively performs a NOT operation