I built a CHIP-8 Emulator that runs 50-year-old games

It all started a year ago. I was obsessed with Zig, and I was sweating to find Zig-related stuff in the universe. One of the repos that was sitting still on my WSL2 instance back then was a CHIP-8 emulator written in… Zig, of course. I ran the emulator. It opened up. I searched for CHIP-8 ROMs, tried plenty of them. They worked. The emulator worked.

Fast-forward a year, I discovered a lot in this time. One of them being “Zig is effectively dead”, which I’ll probably write more about later. In one sentence, I believe the lack of memory safety in the languages we use has cost us enough to realize you can’t trust humans, just like you don’t trust an LLM.

Apart from that, I started using Rust. I’m currently working on a few Rust-written personal projects, but as of today, I’m the director of them; not the labor force. I wanted to choose a project and write it myself. The project had to meet these conditions:

It had to finish fast. I didn’t want to spend a year on a “side-project”. The output had to be meaningfully useful; the outcome mattered to me as much as the learning process as I was progressing through it. It had to teach me a lot. It had to be dense in knowledge I don’t know well, so writing the code solely forces me to learn them.

I recalled that Zig-written emulator, searched a bit, and yeah; It was my project.

CHIP-8 is one of the interesting “programming languages”. Tiny enough to be finishable in a weekend, yet still teaches you numerous systems programming concepts: memory layout and addressing, bitwise operations (masking, shifting, XOR), fixed-width integer overflow and wraparound, byte-order/endianness, the fetch-decode-execute cycle every CPU runs, stack-based subroutine calls, and flat-array representations of multidimensional data. It’s… a lot to learn in a weekend.

I decided to call my emulator “Fortress”. It didn’t have a name at the beginning. I came up with this name when I named the struct that was holding CHIP-8’s functionally and logic fortress.

My reference document was this, which had all the info a systems programmer needed to know to build the emulator. The problem was that… I wasn’t a systems programmer. Here’s what I didn’t know, and had to know to be able to even read and understand the document itself:

8xy6 - SHR Vx {, Vy}. This instruction shifts the value in the register Vx (where x is a variable itself, read from the opcode) to the right by one. What’s that {, Vy} doing there? Well…The

{, Vy}notation is a compatibility marker for a historical fork: original RCA CHIP-88xy6shiftedVxright in place and ignoredVy, but later CHIP-48/SUPER-CHIP reinterpreted it asVx = Vy >> 1, so docs writeSHR Vx {, Vy}to show thatVyis optional depending on the interpreter.

I had to fix a few quite hard-to-spot bugs, hiding inside the deepest internals of the opcode interpreter itself. But when I launched it for the first time after the bug fixes, and loaded 1 Player Pong… I was like, “Say my name!” I made it. I built a bridge between games older than my father and the latest hardware. I made a time machine. A quite small one, but small doesn’t mean simple.

Is it something I use every day? Probably not. Was it worth making it? Definitely. I recommend everyone try this challenge, bare hands (LSP doesn’t count), as an exercise for not forgetting how to code, or to have a rewarding intro to systems programming.

If you want to try the emulator right now, jump into here or clone and run the repo at https://github.com/1matin/fortress.git and run cargo install —path ., then launch it using fortress … in the terminal.