intro
One day I got really intrigued about emulators so I read a lot about it and implemented the Chip8. It is usually the first emulator someone would implement due to it's simplicity. I implemented it all from scratch based on the specs I found on the internet. It was one of the most interesting and fun programming project I did!
have you ever wanted to know how game emulator works? what magic is neede to run console games on a desktop computer?
I believe that writing a Chip8 emulator is one of the best project to quickly understand how computers work. I originally wrote one 10 years ago when I was still in high school? it really opened my eyes and expanded my understanding yet relatively few people know about it.
It can be done in a weekend. written in very few lines of code
In this article I will show you how to implement one in C using the SDL library for graphics.
emulator interpreter
simulate the behavior of a real CPU allow us to run programs made for one CPU on another CPU state: cpu,ram,rom.. I/O: kbd,display
At the core of computers is the CPU. cpu fetch,decode,exec instruction, opcode, asm formats big/little fixed size ISA regs clock,speed/freq synchronous manip bits,binary numbers,letters=ascii chips,transistors,bool algebra stored program = ROM -> RAM newman arch stack can comm with ext world via IO cpu ins io ports mmio
chip8 = VM ~soc emulation impl as real HW for fun, might simplify code RAM, mmap !MMIO -> IO ports
CPU | ~500 Hz |
RAM | 4 KB |
ROM | 3.5 KB |
Timers | 2 x timer (8-bit), 60 Hz |
Display | 64 x 32 monochrome (1-bpp), 8 x 15 sprites |
Keypad | 16 keys |
Speaker | Buzzer (1-bit) |
The memory map:
1 2 |
|
Notice that devices are not memory mapped, they can only be accessed via specific CPU instructions.
The Chip8 does not have any OS and can only run a single program at a time.
Programs never access the lower 512 bytes of RAM.
SDL hello file structure
User programs are stored in ROM chips (Read Only Memory) and loaded into RAM when the Chip8 resets.
As defined by the memory map above, programs are loaded into RAM at 0x200
and so the CPU will start fetching instructions from this address at reset.
load rom mmap
mmap [0-511] : font lower 512 bytes of memory (0x000-0x1FF), and it is common to store font data there.
1 2 3 4 5 6 |
|
rom load @ 0x200
Where can find ROMs? Which one is good to start implementing a Chip8 emulator? test-char pong blinky
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
chip8:
1 2 3 4 5 6 |
|
init:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
All the Chip8 instructions are straighforward and easy to implement but the FX33
one needs further explanations.
This instruction converts a number stored in the V[x]
register to its BCD representation. BCD stands for Binary Coded Decimal, which means that each digit of the number should be converted to its binary representation.
Ex: 234 -> 2,3,4 -> 0b010, 0b011, 0b100
The maximum value that can be stored in V[x]
is 255 since it is a 8-bit register, therefore we need at most 3 digits.
The BCD representation must be stored at memory locations:
I
: 100s digitI+1
: 10s digitI+2
: 1s digitThis instruction is almost always used to update the game's score to the screen to be easily understood by the player, as such it can be implemented at the very end after the rest of the Chip8 is implemented and working properly.
1 2 3 4 5 6 7 8 9 10 |
|
User inputs can be done via the keypad, consisting of 16 keys indexed from 0 to F. The keys are laid out in the following way:
1 2 3 4 |
|
Games will often use the 2,4,6,8 keys as arrow keys:
1 2 3 |
|
The keypad is only accessible via the following CPU instructions:
LD Vx, K
(FX0A) : wait for any key pressSKP Vx
(EX9E) : skip next instruction if key is pressedSKNP Vx
(EXA1) : skip next instruction if key is not pressedIn our implementation each device external to the Chip8 SoC such as the keypad has its own struct. Chip8 can refer to it via pointers. this is done to map the real hw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
impl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
stuff
1 2 3 4 5 |
|
Init: vhook to SDL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
update like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Two timers are available in the Chip8:
Both timers consist of a 8-bit register used as counter. The counter is automatically decremented at 60Hz until it reaches zero.
The only difference between the timers is that when ST
is greater than zero, a sound is emitted through the speaker.
Timers are controlled via three CPU instructions:
LD Vx, DT
(FX07) : get DTLD DT, Vx
(FX15) : set DTLD ST, Vx
(FX18) : set STIn our implementation the timers are part of the Chip8 SoC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
To increment the timers are the right rate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
For sound effects, the Chip8 includes a speaker that can only do one tone, a single "beep" sound (1-bit audio). The speaker is ON whenever ST
is greater than zero, otherwise it is OFF.
We use a simple finite state machine (FSM) to keep the speaker implementation decoupled from the SDL support code.
TODO: impl w FSM + SDL Mixer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Init it:
1 2 3 4 5 6 7 8 9 10 |
|
And tick it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
conclusion