- Write a Chip8 retro gaming emulator in one day. Introduction.
- Write a Chip8 retro gaming emulator in one day. Loading the first ROM.
- Write a Chip8 retro gaming emulator in one day. Controller and Display.
- Write a Chip8 retro gaming emulator in one day. The CPU. And it’s done.
- How to make a SuperCHIP emulator
It took me three days in total to have this thing completely running and bug free. But the emulator itself really took only one day. And this is the result at the end of day one
See the complete code on GitHub
Writing an emulator is not the simplest task. But it’s a very good exercise and the result is very rewarding, trust me you’ll feel a lot cooler after this. I used Scala for this project, and so the code here will be in Scala. However I didn’t do crazy stuff with it and it should be easily understandable by anyone. If you know java you should be fine. Also, you can use any language you want, the ideas of course are the same.
The Chip8 platform
Joseph Weisbecker, a researcher and designer of microprocessors, created Chip8 during the 70s to allow easy game development for some early 8 bit hardware platform. Chip8 was never an hardware device itself. It was a language that could be compiled and run on a virtual machine. It looks like assembly code, in fact it is
LD boxx, 1 LD dirx, 1 LD boxy, 10 LD I, sprite1 DRW boxx, boxy, 8
This code is assembled into a binary format which runs on the Chip8 virtual machine. As you can see there are opcodes such as
DRW. Chip8 only has 35 opcodes in total, this is one of the main reasons why it is so easy to emulate.
The virtual machine is register based, meaning that all computations happen between values stored inside registers. There are 16 value registers, each of them can hold one byte. The last value register (the sixteenth) is used as carry flag and for collision detection, see the graphics section for more on this. Chip8 also uses a 16 bit index register for instructions that read and store values in memory.
There are two time based registers, both of them are 8 bit. The first one is the delay register and the second one is the sound register. The programmer uses an opcode to set the delay register to some value, the system then decrements this register sixty times per second until it reaches zero. This is very convenient for programs to have precise timing regardless of the cpu frequency, which might change depending on the actual machine the interpreter is running on. The sound register works in a similar way, the system decrements is sixty times per second until it reaches zero. The difference is that while the value of this register is greater than zero, the system plays a tone. An that is the whole audio capability of this system. I still did not implement the tone in my emulator and I’m not sure I ever will.
Chip8 draws sprites on a 64×32 pixels screen. The screen is black and white, so each pixel is one bit. It really doesn’t get much easier than that. Chip8 can only draw sprites, never single pixels, unless a sprite is made by only one pixel being turned on. All the sprites are always 8 bits wide (so 8 pixels wide) and their height is specified in the instruction used to draw the sprite. The maximum height of a sprite is 8 pixels.
If a sprite of 4 pixels by 4 pixels is needed Chip8 still has to draw it 8 bit wide so the last four bits of each of its rows will be zero.
When drawing sprites on the screen each pixel is xorred with the existing content of the screen, the result of the xor is set as the new value of the pixel. If the previous value of the pixel was 1 and the new value of the pixel is also 1, the Chip8 sets the last value register to 1. This is how games detect collisions between sprites. The actual pixel on the screen in case of a collision will be zero, as a result of
1 XOR 1.
The controller is quite simple, it provides 16 buttons and the classic layout is the following
1 | 2 | 3 | C --+---+---+-- 4 | 5 | 6 | D --+---+---+-- 7 | 8 | 9 | E --+---+---+-- A | 0 | B | F
There are two opcodes to deal with key presses. One opcode skips the next instruction if a certain key is pressed, another opcode skips the next instruction if a certain key is not pressed. All games use these two instructions to figure out what the user is doing.
Chip8 memory layout
When the Chip8 interpreter starts it loads the program byte by byte starting from memory address 0x200. This means that programs never use addresses from 0x000 to 0x1FF as this is where the interpreter lives. The total addressable memory space is 4kb, or 4096 bytes. With an emulator the interpreter lives outside the Chip8 memory so we could in theory use these initial 512 bytes, however we are going to run old roms that are still thinking that there is an interpreter there, so they won’t use that address range. However there is still some use for this memory space, more on this later. As you can see the memory layout is pretty easy, first 512 bytes are reserved, the program lives starting at byte 512 and that’s it.
Finally there is the stack. Chip8 only uses the stack to store return addresses when it calls subroutines, therefore every entry in the stack is 16 bits, or two bytes. There is a limitation on how many calls programs can do. The stack should be limited to a maximum of 16 entries. This means that after 16 calls to subroutines without a return we should error out with a stack overflow.
A Chip8 interpreter must provide fonts. These are just sprites like any other sprite and they only range from 0 to F, meaning 16 characters, or “0123456789ABCDEF”. Games usually come with their own font and do not use these. At startup the interpreter copies the font sprites in memory at some arbitrary location. Chip8 has one opcode to point the index register at the location of any character, so the interpreter can put these sprites anywhere in memory. This is where the first 512 bytes of memory come in handy.
End of the introduction
So this is the system in a nutshell. I prefer to split things up into different focused posts to avoid having a gigantic article so in the next articles I will explain the steps required to create the emulator, and how to debug it. Stay tuned!