A functional CHIP-8 interpreter written in Rust. This project emulates the hardware specifications of the 1970s CHIP-8 virtual machine, allowing it to run classic games like Pong, Tetris, and Blitz.
The interpreter implements the following virtual hardware components:
- Memory: 4096 bytes (4KB) of RAM.
-
Registers: 16 general-purpose 8-bit registers (
$V0$ to$VF$ ).$VF$ is also used as a flag for carry, borrows, and collision detection. -
Index Register (
$I$ ): A 16-bit register used for memory addresses. -
Program Counter (
$PC$ ): A 16-bit register pointing to the current instruction. -
Display: A
$64 \times 32$ monochrome display (2048 pixels total). - Timers: Two 8-bit timers (Delay and Sound) that decrement at 60Hz.
- Stack: Used to store return addresses when calling subroutines.
-
Input: A 16-key hexadecimal keypad (
$0$ through$F$ ).
The CHIP-8 uses 35 opcodes, each 2 bytes long. In the table below:
- NNN: Address (12 bits)
- NN: 8-bit constant
- X, Y: 4-bit register identifiers
| Opcode | Description |
|---|---|
00E0 |
Clears the display. |
00EE |
Returns from a subroutine. |
1NNN |
Jumps to address NNN. |
2NNN |
Calls subroutine at NNN. |
3XNN |
Skips next instruction if VX == NN. |
4XNN |
Skips next instruction if VX != NN. |
5XY0 |
Skips next instruction if VX == VY. |
6XNN |
Sets VX to NN. |
7XNN |
Adds NN to VX (Carry flag unchanged). |
8XY0 |
Sets VX to the value of VY. |
8XY1 |
Bitwise OR (`VX |
8XY2 |
Bitwise AND (VX &= VY). Resets VF to 0. |
8XY3 |
Bitwise XOR (VX ^= VY). Resets VF to 0. |
8XY4 |
Adds VY to VX. VF is set to 1 if there is a carry. |
8XY5 |
Subtracts VY from VX. VF is set to 0 if there is a borrow. |
8XY6 |
Stores LSB of VX in VF then shifts VX right by 1. |
8XY7 |
Sets VX to VY - VX. VF is set to 0 if there is a borrow. |
8XYE |
Stores MSB of VX in VF then shifts VX left by 1. |
9XY0 |
Skips next instruction if VX != VY. |
ANNN |
Sets I to address NNN. |
BNNN |
Jumps to address NNN + V0. |
CXNN |
Sets VX to result of rand() & NN. |
DXYN |
Draws a sprite at (VX, VY) with width 8 and height N. |
EX9E |
Skips next instruction if key in VX is pressed. |
EXA1 |
Skips next instruction if key in VX is not pressed. |
FX07 |
Sets VX to the value of the delay timer. |
FX0A |
Awaits a key press and stores the result in VX. |
FX15 |
Sets the delay timer to VX. |
FX18 |
Sets the sound timer to VX. |
FX1E |
Adds VX to I. |
FX29 |
Sets I to the location of the sprite for character in VX. |
FX33 |
Stores the BCD representation of VX in memory at I, I+1, I+2. |
FX55 |
Stores registers V0 through VX in memory starting at I. |
FX65 |
Fills registers V0 through VX from memory starting at I. |
- Rust compiler and Cargo.
- Dependencies for Macroquad (system libraries for graphics).
To run a specific ROM, pass the path as an argument:
cargo run -- path/to/rom
By default, if no path is provided, the interpreter attempts to load a test opcode ROM.
The original CHIP-8 used a 4x4 keypad. This interpreter maps the keys to a standard QWERTY keyboard as follows:
| Keyboard | CHIP-8 |
|---|---|
| 1 2 3 4 | 1 2 3 C |
| Q W E R | 4 5 6 D |
| A S D F | 7 8 9 E |
| Z X C V | A 0 B F |