A collection of ROM images with tests that will aid you in developing your own CHIP-8, SCHIP or XO-CHIP interpreter (or "emulator")
I found it hard to find reliable sources on what is the right behaviour and what is not, especially with the subtle differences between the original "Cosmac VIP" CHIP-8 and the HP84's SCHIP (or "superchip"). Now that I have written and ported a couple of interpreters as well as a few programs and games for the platform, I thought it was time to put that knowledge into code.
If you're having issues with your interpreter, you can find help in the EmuDev
discord channel #chip-8
. Every test has a clearly
visible version number, which will help people to diagnose your problems if you
share a screenshot. If you discover a problem with this test ROM itself, feel
free to file an issue or open a pull request. It's open source, licensed under
the GPLv3, and you're welcome to contribute.
- Download ROM (source code available here)
- Run this ROM in Octo to see what's supposed to happen
The first test is a very simple splash screen. It is very similar to the often
used IBM ROM, but it is actually a bit easier to get running.
First, it doesn't use the "add value to register" opcode (7XNN
) and second it
only draws aligned sprites to the screen (the X coordinate is always a multiple
of 8). So if you use an array of bytes for your display buffer, you don't have
to shift the bits of the sprite to align with the buffer.
Run the ROM for 39 cycles to see this splash screen on the display. This first test can tell you if you're interpreting these opcodes properly:
00E0
- Clear the screen6xnn
- Load normal register with immediate valueAnnn
- Load index register with immediate valueDxyn
- Draw sprite to screen (only aligned)
If you run the ROM for more than 39 cycles, it will enter an endless loop. If that also works as expected, you've also correctly interpreted the jump opcode:
1nnn
- Jump
- Download ROM (source code available here)
- Run this ROM in Octo to see what's supposed to happen
The second test is the classic IBM ROM. If the splash screen works as expected, this test should be pretty easy to pass, but it is a rite of passage for any CHIP-8 developer.
I did not write this ROM, it's probably decades old and I'm not sure who wrote it so I can't really credit them. The code in this suite is a re-implementation in Octo-mnemonics by my own hand, but it compiles to the exact same bytes as the original (except for the version number).
Run the ROM for 20 cycles to see the IBM logo on the display. If you can see the IBM logo, you are properly interpreting these opcodes:
00E0
- Clear the screen6xnn
- Load normal register with immediate valueAnnn
- Load index register with immediate value7xnn
- Add immediate value to normal registerDxyn
- Draw sprite to screen (un-aligned)
If you run the ROM for more than 20 cycles, it will enter an endless loop. If that also works as expected, you've also correctly interpreted the jump opcode:
1nnn
- Jump
- Download ROM (source code available here)
- Run this ROM in Octo to see what's supposed to happen
This ROM is an adaptation of another famous one, the CHIP-8 test rom written by corax89. Many people use corax89's ROM to prove to themselves that their interpreter runs as it should. However, there are a couple of minor issues with the ROM and corax89 doesn't really seem to be maintaining this ROM anymore.
So I've taken the liberty to fix those issues, add a few new tests and make some cosmetic changes for this test suite.
The codes on the screen correspond to the functioning of these opcodes:
3xnn 2nnn 8xy4 Fx55
4xnn 00EE 8xy5 Fx33
5xy0 8xy0 8xy7 Fx1E
7xnn 8xy1 8xy6 Registers
9xy0 8xy2 8xyE
1nnn 8xy3 Fx65
If you see a checkmark, they're at least somewhat functional in the happy path. If you see a cross you can be sure that you have an issue with that opcode.
Here's a rough description of what these opcodes should do to pass the tests:
3xnn
- ifvX == nn
, skip next opcode4xnn
- ifvX != nn
, skip next opcode5xy0
- ifvX == vY
, skip next opcode7xnn
-vX += nn
9xy0
- ifvX != vY
, skip next opcode1nnn
-jump nnn
(goto)2nnn
-call nnn
(subroutine)00EE
-return
from subroutine8xy0
-vX = vY
8xy1
-vX |= vY
8xy2
-vX &= vY
8xy3
-vX ^= vY
8xy4
-vX += vY
8xy5
-vX -= vY
8xy7
-vX = vY - vX
8xy6
-vY >>= vX
orvX >>= vX
depending on quirks8xyE
-vY <<= vX
orvX <<= vX
depending on quirksFx65
- load registersv0
-vX
from memory starting ati
Fx55
- save registersv0
-vX
to memory starting ati
Fx33
- store binary-coded decimal representation ofvX
to memory ati
,i + 1
andi + 2
Fx1E
-i += vX
Registers
- Thev0
-vF
registers should be 8 bits wide. This tests to see if it can overflow your registers.
If you are having trouble figuring out how each opcode is supposed to behave, check out Tobias' guide or Gulrak's growing CHIP-8 references.
- Download ROM (source code available here)
- Run this ROM in Octo to see what's supposed to happen
This test checks to see if your math operations function properly on some given
set of input values. But more importantly: it checks to see if you set the flag
register vF
properly when running those opcodes, and if you don't mess up vF
too early (when vF
is used as one of the operands). This is often an issue as
the flags are pretty unintuitive and fairly hard to debug.
When running this test you should see the above on your screen. Each code on the
screen corresponds to an opcode, and shows if the output value is correct (first
checkmark), if the flag in vF
is correct (second checkmark), if vF
can be
used as the vY
input (third checkmark) and if vF
can be used as the vX
input (fourth checkmark, where present). If you see a cross instead of a
checkmark in any of these spots, you have an issue in your interpreter logic.
First, a note on the third and fourth checkmarks: these check to see if an
instruction where vF
is one of the operands (like v0 += vF
/ 0x80F4
) works
as expected. It's easy to make the mistake of setting the vF
register first,
and then performing the mathematical operation. If you do that, however, vF
will not hold the right value anymore at calculation time and your maths will be
off when using that register as an input.
The top part (that starts with "HAPPY" for "happy path") checks the behaviour of the following opcodes, in the case where we don't expect an overflow, carry or shifted out bit:
HAPPY 8xy1 8xy2
8xy3 8xy4 8xy5
8xy6 8xy7 8xyE
8xy1
-vX |= vY
8xy2
-vX &= vY
8xy3
-vX ^= vY
8xy4
-vX += vY
8xy5
-vX -= vY
8xy6
-vY >>= vX
orvX >>= vX
depending on quirks8xy7
-vX = vY - vX
8xyE
-vY <<= vX
orvX <<= vX
depending on quirks
The bottom part (that starts with "CARRY") checks behaviour of the following opcodes, in the case that there is an overflow, carry or shifted out bit:
CARRY 8xy4 8xy5
8xy6 8xy7 8xyE
8xy4
-vX += vY
8xy5
-vX -= vY
8xy6
-vY >>= vX
orvX >>= vX
depending on quirks8xy7
-vX = vY - vX
8xyE
-vY <<= vX
orvX <<= vX
depending on quirks
The last row (that starts with "OTHER") checks that the opcode Fx1E
(i += vX
) properly adds the value of register vX
to the index register, first for a
regular register and then when using vF
as the vX
input register. For this
test, only the value is checked because overflow of the index register is not
really defined in CHIP-8 and this opcode is not supposed to influence the flag
register.
See this article or this article for more information on the arithmetic operations and the flags.
- Download ROM (source code available here)
- Run this ROM in Octo to see what's supposed to happen
CHIP-8, SCHIP and XO-CHIP have subtle differences in the way they interpret the bytecode. We often call these differences quirks. This test detects which quirks your interpreter implements, and if those quirks match the platform you're trying to target. This is one of the hardest parts to "get right" and often a reason why "some games work, but some don't".
The test asks you to choose the platform you are targeting:
You can press any of the numbers 1
to 3
on the CHIP-8 keypad to jump to the
corresponding test.
Alternatively, you can move the cursor up and down with CHIP-8 keys E
and F
and select an item with A
. This feature mainly exists so people implementing
interpreters for platforms with limited input devices (like a game controller)
can map their buttons to those CHIP-8 keys and have an intuitive interface too.
If you want to repeat a test often or even automate them, having to use the
graphical menu just gets in the way. In that case, you can force this ROM to
select a specific platform by loading a value between 1
and 3
into memory at
the address 0x1FF
(512
).
The test will now run through a couple of steps, which you will see on the screen as a loading progress bar followed by some artifacts. After about three seconds, you should see this screen:
The screen shows you if the following quirks are detected as active ("on", "off" or an error) and if that matches your chosen target platform (a checkmark or a cross).
vF reset
- The AND, OR and XOR opcodes (8xy1
,8xy2
and8xy3
) reset the flags register to zero. Test will showE1
if the AND and OR tests don't behave the same andE2
if the AND and XOR tests don't behave the same.Memory
- The save and load opcodes (Fx55
andFx65
) increment the index register. More information here and here.Display wait
- Drawing sprites to the display waits for the vertical blank interrupt, limiting their speed to max 60 sprites per second. More information here. Test will showLOW
if the number of cycles per frame is too low for the test to be deterministic (this is not necessarily an error, but a suggestion to rerun the test with a higher number of cycles per frame).Clipping
- Sprites drawn at the bottom edge of the screen get clipped instead of wrapping around to the top of the screen. When clipping is off, the test checks if sprites get rendered at the right coordinates on the other side of the screen. This also tests that sprites drawn at coordinates ofx > 63
and/ory > 31
wrap around tox % 64
andy % 32
. More information here. Test will showE1
if the clipping is inconsistent in different dimensions or wrapping to the wrong coordinates andE2
if sprites don't wrap around as expected.Shifting
- The shift opcodes (8xy6
and8xyE
) only operate onvX
instead of storing the shifted version ofvY
invX
(more information here). Test will showE1
if the shift opcodes behave differently.Jumping
- The "jump to some address plusv0
" instruction (Bnnn
) doesn't usev0
, butvX
instead whereX
is the highest nibble ofnnn
(more information here)
Note that you need timer support for this test to run.
See this excellent table by Gulrak for an overview of all the known quirks for the relatively popular CHIP-8 versions. See this website for a lot more information about all the historical versions of the platform and the different quirks among them.
- Download ROM (source code available here)
- Run this ROM in Octo to see what's supposed to happen
This test allows you to test all three CHIP-8 key input opcodes. It shows you a little menu, asking which opcode you would like to test:
You can press any of the numbers 1
to 3
on the CHIP-8 keypad to jump to the
corresponding test.
Alternatively, you can move the cursor up and down with CHIP-8 keys E
and F
and select an item with A
. This feature mainly exists so people implementing
interpreters for platforms with limited input devices (like a game controller)
can map their buttons to those CHIP-8 keys and have an intuitive interface too.
If you want to repeat a test often or even automate them, having to use the
graphical menu just gets in the way. In that case, you can force this ROM to
select a specific platform by loading a value between 1
and 3
into memory at
the address 0x1FF
(512
).
Ex9E
skips the next instruction if the key indicated in vX
is currently
pressed. In the test, when you press a key, the corresponding value lights up on
the screen.
Pressing keys 1 and 6
ExA1
skips the next instruction if the key indicated in vX
is currently
not pressed. In the test, when you are not pressing a key, the
corresponding value lights up on the screen.
Pressing keys 1 and 6
Fx0A
waits for a key press and returns the pressed key in vX
.
The test asks you to press a key on the CHIP-8 keypad. When you do, it checks for two issues that are easy to accidentally introduce when implementing this opcode. If all is well, you should be seeing a checkmark and "all good" on the screen:
Otherwise, you can get either of these errors:
NOT HALTING
- Your implementation immediately returns the value of any currently pressed keys invX
, instead of halting the interpreter until a key is pressed (note that this needs timer support to be accurate)NOT RELEASED
- Your implementation doesn't wait for the pressed key to be released before resuming
See this article for more information.
Do you find an issue in this test suite that you think you can fix? Feel free to submit a PR! Here's how to build the project, assuming you have Nodejs and NPM installed:
git clone [email protected]:Timendus/chip8-test-suite.git
cd chip8-test-suite
npm install
# Build all the tests in `src/tests/` to `bin/`:
npm start
# Build a specific test:
npm run build-logo
npm run build-ibm
npm run build-corax
npm run build-flags
npm run build-quirks
npm run build-keypad
Note that the npm
scripts use the MacOS command pbcopy
to copy
the resulting Octo source file to the clipboard. Depending on your OS this may
not work properly. Edit package.json
and remove this part from the end of scripts
->
build-test
if you get errors:
&& cat bin/${TEST}.8o | pbcopy