The Art of Chip-8

birdculture1 pts0 comments

The Art of CHIP-8

CHIP-8 is a programming language originally developed for the 1977 COSMAC VIP kit computer. CHIP-8 programs are composed of a series of two-byte instructions resembling machine-code for a simple virtual instruction-set architecture, so CHIP-8 interpreters are often also referred to as "emulators". Indeed, writing a CHIP-8 interpreter is an excellent way to learn the principles underlying emulators for antique computers and game consoles, and as a result there are a dizzying array of thousands of CHIP-8 runtimes available for almost every conceivable platform.

For historical platforms to live, rather than simply be preserved, we must write new software for them. The profusion of CHIP-8 implementations comes in turn with a great deal of confusion, as a half-century-long game of telephone has produced a wide variety of diverging behaviors in interpreters. Over the course of developing Octo, my high-level CHIP-8 assembler, I helped popularize and standardize a variety of "quirks flags" which capture common divergences between extant CHIP-8 flavors, and investigated many dark, unspecified corners of influential interpreters. There are now mature test suites available for CHIP-8 interpreters and their variants, so there’s no excuse for modern interpreters to get the details wrong. Still, the reality of CHIP-8 in the wild is fragmented: many interpreters for obscure platforms are written by beginners unaware of any broader hobbyist community and abandoned as soon as they (appear to) correctly run PONG.CH8.

In this article I will examine CHIP-8 as an instruction set and its practical implications for writing new programs, distilling a number of scattered tutorials, examples, and FAQs I’ve written in the past. I will specifically point out approaches which are portable across all but the buggiest and least complete existing CHIP-8 interpreters. Example code will use Octo’s notation; this document is not intended as a complete reference manual for Octo assembly language, but I will endeavor to explain new ideas as we encounter them.

Index

System Overview

Arithmetic Instructions

Memory

Subroutines

Control Flow

Input

Random Numbers

Timing

Output

Where To Go From Here

Further Reading

System Overview

CHIP-8 operates in a 12-bit address space. The original CHIP-8 interpreter resided in the first 512 bytes of this space, with programs starting at address 0x200. It also reserved some of the upper region of the address space for a stack, a framebuffer, and several scratchpads. As a result, we are left with a maximum of 3232 bytes for our code and data. Modern CHIP-8 interpreters are often more generous, leaving up to 3584 bytes for user programs, and they will often use the low 512 bytes of memory to store their hex font(s) or nothing at all, leaving it available for programs to manipulate.

Your code and data should fit within 3232 bytes for maximum portability.

We have a file of 16 general-purpose 8-bit registers named v0-vf, giving the platform a pleasantly RISC-ey feel. The 12-bit1 "index register" i is used for all operations which reference or manipulate memory. There is an internal stack for threading subroutine return addresses, but it is opaque: the instruction set does not allow programs to freely push or pop temporary values or inspect the contents of the stack.

Programs take input from a hexadecimal keypad. For output, we have a 64x32 pixel 1-bit bitmapped display and a simple piezo buzzer for making noise. We are also afforded a non-interrupting delay timer and a random number generator.

There are 34 elementary CHIP-8 instructions. In the descriptions below, vx and vy are any v-register, and NNN, NN, and N represent an immediate 12-bit, 8-bit, or 4-bit value, respectively:

Machine Code<br>Octo Syntax<br>Notes

00E0<br>clear<br>Clear the display.

00EE<br>; or return<br>Exit a subroutine.

1NNN<br>jump NNN

2NNN<br>NNN or :call NNN<br>Call a subroutine.

3XNN<br>if vx != NN then<br>Conditional skip.

4XNN<br>if vx == NN then<br>Conditional skip.

5XY0<br>if vx != vy then<br>Conditional skip.

6XNN<br>vx := NN

7XNN<br>vx += NN

8XY0<br>vx := vy

8XY1<br>vx |= vy<br>Bitwise OR .

8XY2<br>vx &= vy<br>Bitwise AND .

8XY3<br>vx ^= vy<br>Bitwise XOR .

8XY4<br>vx += vy<br>vf gets 1 on carry, otherwise 0.

8XY5<br>vx -= vy<br>vf gets 0 on borrow, otherwise 1.

8XY6<br>vx >>= vy<br>vf gets old least significant bit.

8XY7<br>vx =- vy<br>vf gets 0 on borrow, otherwise 1.

8XYE<br>vx<br>vf gets old most significant bit.

9XY0<br>if vx == vy then<br>Conditional skip.

ANNN<br>i := NNN

BNNN<br>jump0 NNN<br>Jump to address NNN + v0.

CXNN<br>vx := random NN<br>Random byte bitwise AND ed with NN.

DXYN<br>sprite vx vy N<br>Draw on display; vf gets 1 on collision, otherwise 0.

EX9E<br>if vx -key then<br>Is a key not pressed?

EXA1<br>if vx key then<br>Is a key pressed?

FX07<br>vx := delay

FX0A<br>vx := key<br>Wait for a keypress.

FX15<br>delay := vx

FX18<br>buzzer := vx

FX1E<br>i += vx

FX29<br>i := hex vx<br>Set i to a hex character sprite.

FX33<br>bcd vx<br>Decode vx into binary-coded decimal.

FX55<br>save vx<br>Save v0-vx to memory address i...

chip interpreters programs gets code address

Related Articles