A CHIP-8 interpreter, assembler and disassembler in C
This package contains an interpreter for CHIP-8 as well as a command-line assembler and disassembler.
It also supports the SuperChip instructions.
The syntax of the assembler and disassembler is based on the syntax described in Cowgod's Chip-8 Technical Reference v1.0, by Thomas P. Greene
Frédéric Devernay's SVision-8 website has a wealth of information. He also has a collection of CHIP-8 games and programs in his GAMES.zip.
make
from the shell.make
from the MSYS
shell.To use the emulator:
./chip8 game.ch8
where game.ch8 is the binary CHIP-8 file.chip8 game.ch8
or chip8-gdi game.ch8
depending on
which of the implementations (see below) you want to use.The assembler and disassemblers are simple command line applications and platform independent.
To use the assembler, type
$ ./c8asm -o file.c8h file.asm
This will assemble file.asm
into a binary file.c8h
. If the -o
is not
specified it will default to a.c8h
.
To use the disassembler, run the command
$ ./c8dasm a.ch8 > outfile.asm
where a.ch8
is the file you want to disassemble.
The core of the emulator is in chip8.c
. The idea is that this core be
platform independent and then hooks are provided for platform specific
implementations.
The API is described in chip8.h
. The docs
target in the Makefile generates
HTML documentation from it.
Two implementations are provided in this repository:
In both versions
bmp.h
and bmp.c
(together with the fonts/
directory) is used to draw
and manipulate the bitmap graphics. See also
https://github.com/wernsey/bitmap
render.c
implements the init_game()
, deinit_game()
and render()
functions that forms the core of both implementations and demonstrates how
the interpreter's API works.The render()
function checks the keyboard and executes the interpreter a
couple of times by calling c8_step()
and redraws the screen if it changed.
The SDL and Win32 frameworks were written in such a way that the render()
function works with both with only a couple of minor modifications.
The implementations feature a rudimentary debugger: Press F5 to pause a running game. The program counter and the current instruction will be displayed at the bottom of the screen, along with the values of the 16 Vx registers. Press F6 to step through the program to the next instruction and F8 to resume the program.
The Makefile
will build the SDL version by default, and build the GDI version
under Windows.
The SDL-based implementation is intended for portability. The files pocadv.c
and pocadv.h
implement a wrapper around the SDL that contains the main()
function, the SDL event loops and so on.
The included emscripten.mak
file is used to compile the SDL implementation to
JavaScript with Emscripten for running the
interpreter in a web browser. The chip8.html
is a wrapper around the
Emscripten-generated JavaScript. If you want to use this implementation:
./GAMES/
directorymake -f emscripten.mak
Module.arguments
variable in the JavaScript in chip8.html
chip8.html
in a web server.I built the emscripten version through the emscripten SDK installed
according to the installation instructions. I had
some linker errors with Ubuntu's emscripten
package that I couldn't
resolve.
The native Windows version uses a simple hook around the Win32 GDI and requires no third party dependencies.
gdi.h
and gdi.c
implements the native Windows code. It implements a
WinMain
function with the main Win32 events processing loop. It binds the
window's GDI context to a Bitmap
object so that a render function can draw
onto it and fires off periodic WM_PAINT
messages which calls the render()
function to draw the screen.
I've consulted several sources for my implementation (see references below), and there were some discrepancies. This is how I handled them:
2nnn
, cowgod says the stack pointer is incremented first (i.e.
stack[++SP]
), but that skips stack[0]
. My implementation does it the
other way round.Fx55
and Fx65
instructions doesn't change I
in my implementation:QUIRKS_MEM_CHIP8
to control thisDxy0
instruction apparently also works differently in
lo-res mode.QUIRKS_CLIPPING
to control thishp48_flags
is not cleared between runs (See octo-superchip); I don't make any effort
to persist them, though.speed
in render.c
). The value of 1200 instructions per second seems like
a good value to start with.Cowgod's Chip-8 Technical Reference v1.0, by Thomas P. Greene,
How to write an emulator (CHIP-8 interpreter) by Laurence Muller (archived)
CHIP8 A CHIP8/SCHIP emulator Version 2.2.0, by David Winter
Chip 8 instruction set, author unknown(?)
Byte Magazine Volume 03 Number 12 - Life pp. 108-122. "An Easy Programming System," by Joseph Weisbecker
Mastering CHIP-8 by Matthew Mikolay
Octo, John Earnest
The Octo SuperChip document, by John Earnest
CHIP‐8 Technical Reference, by Matthew Mikolay
Timendus' chip8-test-suite was extremely useful to help clarify and fix the quirks.
Tobias V. Langhoff's Guide to making a CHIP-8 emulator
Chip-8 on the COSMAC VIP: Drawing Sprites, by Laurence Scotford (archive link)
CHIP-8 extensions and compatibility - explains several of the variants out there
https://github.com/zaymat/super-chip8
https://github.com/dario-santos/Super-Chip-Emulator has a collection of ROMs I used for testing
https://github.com/JohnEarnest/chip8Archive - Archive of CHIP8 programs.
This code is licensed under the Apache license version 2:
Copyright 2015-2016 Werner Stoop
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
5-quirks.ch8
test.-q
command line option to control the quirksinclude "file.asm"
directive.
c8_load_txt()
by default, but can be made to point elsewhere (or set to
NULL
and disable includes completely)text "hello"
directive in the assembler, that places a null
terminated string in the bytecode. Users might be able to display the text at some point
if you have the right sprites; Octo does it.SYS nnn
(0nnn
) instructions break
out into the environment outside.
* It's meant as a bit of a joke, might be neat if you embed a CHIP-8 interpreter
in another program and call out to it as a sort of scripting language.-m addr=val
, that will set the byte at addr
to val
in the
RAM before running the interpreter.
* A immediate use case is for, example, Timendus's 5-quirks.ch8
test that allows you
to write a value between 1 and 3 to 0x1FF
and then the program will bypass the initial
menu and skip directly to the corresponding test. I imagine that while developing and
debugging CHIP-8 programs it might be useful to have such a mechanism.stepper->token
.map
file output by the assembler...Porting to the Amiga 500 might be an interesting challenge to get it truly portable: The Amiga's bus is word aligned, so if the program counter is ever an odd number then the system might crash when it tries to retrieve an instruction. Also, the Amiga is big endian, so that might reveal some problems as well.
XO-Chip compatibility seems like something worth striving for. Here's a short checklist of the changes. Also look at how Octo modifies some instructions.