Coding into the Void

Coding into the Void

A blog that I’ll probably forget about after making thirty-five posts.

Yet Another CHIP-8 Emulator: Super-Chip

Having gotten my CHIP-8 emulator working to my satisfaction, I decided to move onto a commonly used extension of CHIP-8: Super-Chip.1 It was created by Erik Bryntse in 1991, and it adds nine new instructions and modifies one existing instruction.2

The official source is here, although the details are sparse: this was a document made for people making games for the Super-Chip, not making their own implementations.

Eagle-eyed viewers will see that this spec gets the description of 8xy6 and 8xyE “incorrect”, same as Cowgod’s. They likely both pulled from the same source. I take the same approach here that I did with CHIP-8: stick with what Octo does and have quirks for everything else.

It’s hard to get implementation details for some of the instructions, but the quirks laid out in this document by Chromatophore are useful in determining what the common emulator behavior is.

I took the approach of attempting to run the opcode as a Super-Chip instruction and, if none handled it, falling back to CHIP-8. By separating these two it made the distinctions between the two easier to understand. There weren’t really any overlapping instructions, so this didn’t result in code duplication.

What Does it Add?

Scrolling

There are three commands related to scrolling the screen: scroll display N lines down (00Cn), scroll display 4 pixels right (00FB), and scroll display 4 pixels left (00FC). Any pixels pulled in from offscreen will be empty. No wrapping here.

It was here that I ran into my first dilemma: what does scrolling right mean? Does it mean that each pixel moves to the right, or that the camera moves to the right, causing each pixel to move to the left?

While a modern reading of the text would suggest to me that the latter is correct, the former is the correct behavior.

Under the scrolling instructions section, it indicates that scroll offsets are halved in low-res mode,3 but modern emulators seem to scroll both by the same amount.

Strangely, the halved behavior isn’t configurable via a quirks flag in Octo. I assume that indicates that any pre-existing Super-Chip programs didn’t depend on scrolling in low-res mode.

A scroll up command is strangely absent. I assume the hardware of the time made that more difficult to implement.

Exiting

There’s an exit command now: 00FD. Don’t ask me why. I just have the program hang if this happens. Seems useless, but at least it was easy.

Extended Screen Mode

Super-Chip allows swapping between a new 2x high resolution mode (128x64) and the normal CHIP-8 mode (64x32). 00FE enters high resolution mode, and 00FF enters low resolution mode.

These commands do not clear the screen on original hardware,4 but modern emulators do clear the screen. Once again, no quirks flag in Octo. I clear the screen, even if the instruction matches the mode it’s currently in.

I considered always using a 128x64 graphics buffer, but in the end I decided to just recreate the graphics buffer and the texture pointing to it for the new resolution. This made it so I didn’t need to complicate my CHIP-8 draw calls, and made the code simpler. I have no regrets about choosing that option.

Draw 16x16 Sprite

This is the one that Erik said was modified. He repurposes the existing draw command (Dxyn) if the value of n is zero. In CHIP-8, a call of DXY0 is effectively a no-op, as it draws 0 bytes of a sprite.

In this case, it draws a 16x16 sprite, with two bytes being read for each row. Simple enough. There are a few caveats on the original hardware: collision detection can set vF to numbers beyond 1, and it functions oddly in low-res mode. Similarly, it doesn’t wrap and collisions behave differently.5

Modern emulators treat it as if it were behaving the same as a normal Dxyn command, which I stuck with. Sprites not wrapping is covered in a quirks mode (which covers the CHIP-8 command and this one), but none of the others are, continuing the trend.

Access Hi-Res Font

Super-Chip contains a hi-res font that you can point at using the FX30 instruction, similar to the FX29 instruction in CHIP-8. The original machine only has digits 0-9, although modern emulators also include A-F. This does raise questions as to whether there are any programs that rely on collisions with font characters, but let’s just assume there aren’t.

Another easy one.

Storing and Reading from Cross-Program Registers

The Super-Chip adds 8 registers that remain the same across program runs and even separate programs. You can save (FX75) and load (FX85) from these registers to the normal ones. Trivial to implement, although depending on the platform, it might just serve as a extra registers that are fiddly to use.

Documentation is unclear as to what to do if X exceeds 7, so I just read the lower three bits of x.6

Pitfalls

Modified Commands

You know how I said none of the commands were really modified? Well, that’s partly true. If your clear screen command (00E0) had 64x32 hardcoded, you’d need to change that. Thankfully, mine didn’t.

16-pixel Sprite Mode

It should go without saying,7 but since you’re reading two bytes to get the full image, you need to multiply your y offset by 2 to reflect that.

Conclusion

Despite an even more vague initial document than the first time, this one went surprisingly smoothly for me. Fewer lines of code means fewer bugs as well, I suppose. Or at least, subtle bugs that I haven’t found yet.

Interestingly, while writing the Super-Chip extensions instructions, I found more bugs in my original CHIP-8 code than in my Super-Chip code.8

Sadly, fewer people have written Super-Chip emulators, so I couldn’t find any good test ROMs. I should write unit tests for all the instructions, but eh. Maybe later.

Next up, XO-Chip. Unless I get burned out.9


  1. Specifically, Super-Chip version 1.1. ↩︎

  2. In my implementation, I treat it as a new instruction, as it effectively is. The instruction that it’s overriding shouldn’t happen in a CHIP-8 program. ↩︎

  3. Coming up soon. ↩︎

  4. According to this. ↩︎

  5. See here for more details. ↩︎

  6. This happened to be the same as Octo, so I felt vindicated. ↩︎

  7. Which is to say—it had to be said to me. Well, I had to figure it out, at least. ↩︎

  8. All already documented in the previous blog post. ↩︎

  9. Which I probably will. ↩︎