CHIP-8 is an interpreted programming language which was initially used in 8-bit microcomputers. Nowadays it serves as a great project in order to understand the architecture and inner workings of a computer. This post series will share the development of an interpreter for CHIP-8 written in C++ with SFML and Qt user interface. Furthermore, we will latter port this interpreter into real hardware so we can carry it outside in our pocket!
The source code can be found in the following GitHub repository:
The interpreter was based on the documentation provided by:
The first stage of the project was to digest the information and to split the work. The system architecture can be divided in the following blocks:
We need to keep a modular approach in order to facilitate the replacement of a building block. For the desktop version of the interpreter, the SFML library was used to implement the keyboard input, speakers and display. Later we decided to provide a Qt interface to load roams and change different options. We ended up porting the screen to Qt too.
This modular approach is key in order to be able to replace the building blocks with a different implementation when we port our simulator to hardware. The idea is to replace the emulator display with a real OLED, as well as the keypad input and speaker with real buttons and a buzzer.
Once we had implemented the blocks and written test cases to ensure they were all working as intended, we implemented the instruction cycle and the different operational codes of the interpreter.
After many hours of development, we finally were able to press the play button. The result; a completely not working CHIP-8 interpreter!
And here is where the fun begins. The most challenging part of implement an emulator is figuring out why it is not working!
If you try to implement your own version, you will most likely find your self in this situation.
The best approach to tackle this problems is to set test cases that isolate the different functionality. We first started with the simplest ROM we could find, a maze generator. We can use the
xxd utility to display the binary content of the file as hexadecimal values
xxd -c 2 maze_rom.ch8
00000000: 6000 `. 00000002: 6100 a. 00000004: a222 ." 00000006: c201 .. 00000008: 3201 2. 0000000a: a21e .. 0000000c: d014 .. 0000000e: 7004 p. 00000010: 3040 0@ 00000012: 1204 .. 00000014: 6000 `. 00000016: 7104 q. 00000018: 3120 1 0000001a: 1204 .. 0000001c: 121c .. 0000001e: 8040 .@ 00000020: 2010 . 00000022: 2040 @ 00000024: 8010 .. 00000026: 0a .
It’s a quite short program, but still, translating each opcode manually is quite a cumbersome task. The way to go is to develop a disassembler, luckily we found a very nice one which you can get from:
0x0200 mov v0, 0x00 0x0202 mov v1, 0x00 0x0204 mov I, 0x0222 0x0206 rand v2, 0x01 0x0208 skipnext_eq v2, 0x01 0x020a mov I, 0x021e 0x020c draw_spirte xreg=v0, yreg=v1, h=4 0x020e add v0, 0x04 0x0210 skipnext_eq v0, 0x40 0x0212 jmp 0x0204 0x0214 mov v0, 0x00 0x0216 add v1, 0x04 0x0218 skipnext_eq v1, 0x20 0x021a jmp 0x0204 0x021c jmp 0x021c 0x021e mov v0, v4 0x0220 call 0x0010 0x0222 call 0x0040 0x0224 mov v0, v1
Much easier to read. Finally we spot that the draw sprite instruction wasn’t correctly implemented. After fixing this issue we finally got our maze:
Time to test something a little bit more demanding. We tried to run the Space Invaders ROM, which can be found here:
The game was running perfectly fine, the title screen shown up and we could start the game. But… something funky happened. If you shoot the second enemy, the third one and forth one would disappear, and if you would shoot the first one… they would all revive!
How can we find what is wrong… We had to add additional debugging tools to our simulator, and so we added controls to our Qt interface to stop the interpreter and to step its execution while also providing information of the different registers.
This way, we translated the disassembly into more readable pseudo-code and figured out what the game was trying to do when the collision of the bullet was happening.
This way we were able to translate the logic of the game and figure out why the wrong enemies were being deleted when the bullet hit.
We figured out that the problem was the shift instruction, which its definition in the documentation we were using was wrong. So we compared with other documentations and updated the code appropriately.
So far, the different ROMs that we have tested work perfectly fine! We observed some flicker of the pixels in most of the games. This might be due to the fact that pixels in old screens had a pixel response delay, so the flickering didn’t occur in those screens. In order to emulate the effect, we added a delay during which the pixel fades out to the off state instead of instantaneously switching off. The result was great, and so we finalized our first version of the emulator. Time to move into porting it to hardware so we can carry it around.