Step 1 of 5 · Reading · ~3 min
Read
Production
Quirks: COSMAC vs Modern
If you write a CHIP-8 emulator you will get the same bug report many times: "game X works in another emulator but not in yours". The cause is almost always a quirk. CHIP-8 was re-implemented on different machines over four decades, and each implementation settled the vague corners of the specification differently. ROMs were then written against whichever interpreter their author owned, and now depend on its choices. After this lesson your emulator can switch between three well-known behaviours.
Five places the implementations disagree
quirk COSMAC VIP MODERN XO-CHIP
1 shift 8XY6, 8XYE shift VY into VX shift VX in place shift VY into VX
2 store/load FX55, FX65 I ends at I+X+1 I unchanged I ends at I+X+1
3 jump BNNN add V0 BXNN: add VX add V0
4 logic 8XY1, 8XY2, 8XY3 VF set to 0 VF untouched VF untouched
5 drawing at the edge clip clip wrap
COSMAC is the original 1977 interpreter. MODERN is the behaviour of the SUPER-CHIP generation and of most emulators written since, which is why a ROM from the 1990s usually expects it. XO-CHIP is the extended dialect used by Octo, mixing some of each. The community's tables by Timendus (the quirks test of his CHIP-8 test suite reports each one on screen) and the options in the Octo editor use the same list.
How each one shows up
1. Shift. The original had no single-register shift, so it shifted VY and stored the result in VX. A ROM written for it shifts a register it has loaded into VY first. On MODERN the same ROM shifts the wrong register. With V1 = 0F and V2 = F0, SHR V1, V2 gives V1 = 78 on COSMAC (F0 shifted) and 07 on MODERN (0F shifted).
2. Store and load. Interpreters that incremented I let a ROM save registers and then keep going from where it stopped. Without it, I stays and the same code overwrites its own data.
3. Jump. BNNN as documented adds V0. SUPER-CHIP treated the second digit as a register: B2NN jumps to 2NN + V2. Jump tables then land in different places.
4. VF after logic. The VIP's implementation clobbered VF as a side effect of OR, AND and XOR, so ROMs for it assume VF is 0 afterwards.
5. Edges. A sprite drawn near an edge either has its overhang cut off or comes back on the other side. The start coordinate wraps in all profiles; only the pixels past the edge differ.
One flag per quirk, one profile per row
Do not scatter if profile == COSMAC through the interpreter. Turn the table into five booleans, look the profile up once, and let each instruction consult the flag it cares about. Adding a sixth quirk is then a one-line change.
Debugging a ROM that misbehaves
Symptoms point at quirks: sprites clipped or reappearing on the wrong side (5), wrong results after a shift (1), garbage after saving and restoring registers (2), a menu that jumps to nonsense (3), a flag that is set when it should not be (4). Change one flag at a time.
Common mistakes
- Shifting VY on every profile, or VX on every profile, instead of looking it up.
- Advancing I by X instead of X + 1.
- Resetting VF after the arithmetic instructions (8XY4 to 8XYE) as well; only 8XY1, 8XY2 and 8XY3 do it, and the others still report their own flags.
- Wrapping the start coordinate but then clipping when the profile says wrap.
Your exercise
The starter already holds the profile table and a PROFILE name command that selects a row. The interpreter ignores the flags; add them at the five places.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…