CHIP-8 emulator in C
note by author @Jia Hao: This was an attempt to dig into low-level programming and to work towards creating a NES/Gameboy Advance emulator as it is my first time writing code in a low-level programming language like C (the code can be found here!). I hope you enjoy reading this as much as I did working on this!
another note by author @Jia Hao: I intend to make this into a blog series (and maybe video series). These are just the rough notes that I had taken and the process I took to implement chirp!
specifications
we have to start somewhere
CHIP-8 has the following components, which have to all be implemented in this chirp:
- memory: direct access to up to 4KB of RAM memory
- display: 64 by 32 pixels; monochrome rendering
- 16-bit program counter (
PC): points to the current instruction in memory
- 16-bit index register (
I): point at locations in memory
- stack for 16-bit addresses: used to call subroutines/functions and return from them stack
- 8-bit delay timer: decremented at a rate of 60 times per second until it reaches 0 timers
- 8-bit sound timer: similar to delay timer but it gives off a beeping sound as long as it's not 0 timers
- 16 8-bit general-purpose variable registers number
0 to F
bits & bobs
understanding how the various components of CHIP-8 works (based on the specifications)
rendering
memory
stack
keypad
timers
instructions
emulator loop
ROM file format
process
implementing the actual emulator in C
core
- implement memory
- implemented as a
uint8_t mem[4096] array
- indexed via hexadecimal to directly reference the notes above, e.g. starting instruction at
0x200
- visualized memory using string joins
- implement stack
- implemented as a regular stack with
push/ pop/ peek/ is_empty/ is_full operations
- using a fixed size of
64 since the originally recommended minimum size is 16
- implement registers
- same exact logic as the memory, it could in-fact be a shared
struct that is used across for both
- implement core struct for emulator state
- relatively simple since it’s just a single
struct with the previously implemented components
- need to make sure that the state is properly initialized
- including some boolean states for whether the emulator is running since instructions will modify the emulator state directly (such as telling it to stop running)
- read ROM file contents
- when loading the rom, we use the following C pattern:
fseek to the end of the file using SEEK_END
- fetch the size of the file using
ftell
rewind the file to the beginning
- allocate a buffer of the size of the file using
malloc
- read the contents of the file using
fread into the buffer
- for every section of the file, load into the emulator state
- close the file
- free the allocated buffer
- load fonts into memory
- storing the fonts as a hard-coding constant
- load them into the intended memory buffer area
- implement game loop
- largely similar to the emulator loop described with two problems:
- care for timers (must be separate from the core game loop)
- the native machine’s clock speed might be way faster than the CHIP-8 original clock speed
- algorithm to solve these described below
- implement CPU + timer ticks
- see clocks for information about algorithm, but essentially we map a duration of the system clock to the number of actual ticks to occur
- interleaved with instruction processing to avoid bulk processing instructions then timers
- parse instructions
- execute instructions
- render contents
- using SDL
- play audio through the sound timer
clocks
understanding how to build clocks in emulators and games to understand frame rate and clock speeds
not this, but sure
not this, but sure
-
two general problems with the emulator loop:
how do we set the delay and sound timers to be agnostic of the game loop? they should be ticking at a rate of 60 a second (60 Hz)
and
how do we mimic the processor speed of the old CHIP-8 computers?
- both are actually the same problem, just framed slightly differently
-
there are two clocks: system clock and emulator’s clock
- 1 tick in the game might not be the same as 1 tick by the system
- faster machines might run instructions faster so the games go very quickly
-
mechanism to run instructions and timers at a different rate relative to the speed of the actual emulator loop
-
detailed explanation: https://gameprogrammingpatterns.com/game-loop.html
- standard emulator loop:
- take the number of actual actions perform a second
- start with:
- during every iteration of the emulator loop, record the current system clock time
C
- in doing the above, we’re able to essentially aggregate the speed of the emulator against the speed of the system
- “but won’t this mean that if we’re waiting for user input, we’ll block on the user’s input and not progress the ticks?”
- “but if we execute all the CPU instructions first, then update the ticker, wouldn’t there be some considerable delay?”
grokking SDL
learning how SDL works (probably the most technical part of the project since I have no clue what SDL is or does)