Chippy8 - A Chip8 Emulator in Javascript

I wrote a Chip8 emulator in Javascript and learned a few things.

You can skip the rambling and take a look at the demo:

» Demo «

and take a look at the source.

_config.yml

Chippy8 running Space Invaders in a browser

I’ve always been interested in building an emulator from scratch. I remember as a kid using a Gameboy Color emulator to play Link’s Awakening and being unable to sleep because I was so excited to play more. 15 odd years later I was trapped inside due to COVID-19 with nothing better to do and wanted to take a crack at creating an emulator. Starting with the Gameboy seemed a bit ambitious so I did some research and compromised on the Chip8, an ancient virtual machine from the 70s which seems to be a rite of passing in the emudev community.

I chose Javascript because I knew essentially none, having not used it since the single project I did in University a few years ago. Javascript also provided an easy way to share the finished project with my friends and family by hosting it in a browser on the github pages site for my project.

Next I needed a mentor, and of all the guides and writeups and blogs I found Tania Rascia’s Writing an Emulator in JavaScript (and Interfacing with Multiple UIs) to be the easiest to digest for a novice of both emudev and Javascript. Seriously, this article and the backing repo was essential to getting this project off the ground for me. You’ll notice no shortage of similarities between Tania’s emulator and mine and I will admit I copied and pasted more than a few times (but usually self-commented or at least understood what it did!).

My first step to building a Chip8 emulator was understanding the architecture of the (virtual) machine. Before I started I already had a working knowledge of how a CPU works, fetch-decode-execute and even some of the more advance features of modern CPUs, but the Chip8 is really a simple little machine. You’ll almost always want to start from a technical document, and look no further than Cowgod’s Chip-8 Technical Reference.

From Cogwod’s technical reference we can see that the Chip8 has 15 8bit general purpose registers, a single flag register, a 32x64 display that it can draw to directly, 2 timers, a 16bit instruction size and around 35 opcodes. What’s more interesting is what it doesn’t have, and that’s any standard operating frequency or any memory mapped I/O. I guess that’s because the Chip8 is only a virtual machine, there is no original hardware to model this after. It’s up to the interpreter to choose how to interact with the display and keyboard.

_config.yml

Chippy8 running an Arkanoid clone in a terminal

With this basic knowledge in mind, I could start with the boilerplate code. The main logic of my emulator would be contained in a class that defined the CPU. The CPU class would hold the state of the emulator and contain all logic to execute the programs.

The states my CPU class would hold ended up looking like this:

class CPU {
    constructor(cpuInterface) {
        this.CPUInterface = cpuInterface;
        this.memory = new Uint8Array(4096);
        this.registers = new Uint8Array(16);
        this.stack = new Uint16Array(16);
        this.ST = 0;
        this.DT = 0;
        this.I = 0;
        this.SP = -1;
        this.PC = 0x200;
        this.instruction = undefined;
        this.frameBuffer = [];
        this.drawFlag = false;
        this.halted = true;
    
        // debug stuff
        this.instNum = 0;
    }

You can see the general purpose registers, the memory, the stack for calling functions and the special registers like the program counter (PC) and the stack pointer (SP).

Next I wanted to load a rom into the memory I had just created. I won’t get into the details, but it involved looping over the data in the program rom, chopping it into 16bit opcodes, and then storing each of those 16 bit opcodes into 2 8bit memory locations in my CPU. Tania’s emulator really helped out for this, it’s a bunch of details that I wasn’t really that interested in figuring out myself.

With the CPU states defined and the program in memory, we were ready to execute opcodes. The CPU will use the program counter (PC) to fetch an opcode from memory, then it will decode that opcode to figure out which instruction to execute and with what operands, and then finally we will actually execute that instruction, often updating our states. After we finish this ritual of fetch-decode-execute, the instruction we just executed will usually have changed the PC to point at a new opcode and we will repeat the process.

var opcode = this.fetch();

this.instruction = this.decode(opcode);

this.execute(this.instruction);

loop forever and ever

Fetch is the simplest step. All we do is fetch the next opcode from memory where the PC is pointing at.

 fetch()
 {
     // Memory is an array of Uint8 and an opcode is 16 bits, which means it occupies two
     // indices in memory. To return the full 16 bit opcode, we left shift the first byte
     // by a byte to make it the most significat 2 nibbles, then OR it with the next byte
     // to pick up the least significant two nibbles
     return (this.memory[this.PC] << 8 | this.memory[this.PC + 1]);
 }

The comment there is pretty self explanatory, but the trick here is that the Chip8 opcodes are made up of 16bits, but the memory array is only 8bits. This means an opcode occupies 2 indexes in memory. Once we’ve constructed our opcode, we can move on to decode.

An opcode is really just a number, like 0x1225. It’s up to the emulator to decode what the program wants us to do and then do it. From Cogwod’s technical reference, we can see that this could only be the JP or the jump instruction. We know this because it starts with 0x1, and no other opcode starts with 0x1. To decode this opcode into the JP instruction, we need to mask the bits we don’t care about and then check that the bits we do care about are set.

else if((opcode & 0xf000) == 0x1000)
{
   // 1nnn - Jp addr 
   // Jump to location nnn.
   var id = "JP"
   var args = (opcode & 0x0fff)

   return {id, args}
}

As part of the decode, we also determine what the operands are for this insturction. Again Cogwod’s technical reference tells us that the 3 least significant nibbles make up the address we want to jump to. We use a mask to mask out the bits we don’t care about and only save the ones we do. This instruction JP and it’s argument 225 are then returned as the instruction we want to execute.

Execute is really the cool part. It’s when we actually do what the program is telling us to do. These instructions and what the CPU is expected to do are documented and can be looked up in the technical reference. JP is probably the simplest instruction the Chip8 has. It simply sets the PC to the argument passed in.

case 'JP':
    // 1nnn - Jp addr 
    this.PC = args;

    break;

But a typical instruction usually does a bit more than this. Check out one of the ADD instructions:

case 'ADD_Vx_Vy':

    // Set Vx = Vx + Vy, set VF = carry.
    // The values of Vx and Vy are added together. If the result is greater than 8 bits (i.e., > 255,) VF is set to 1, otherwise 0. 
    var Vx = args.Vx
    var Vy = args.Vy

    this.checkRegister(Vx)
    this.checkRegister(Vy)

    var sum = this.registers[Vx] + this.registers[Vy]

    this.registers[Vx] = sum

    if(sum > 255)
    {
        this.registers[0xf] = 1
    }
    else
    {
        this.registers[0xf] = 0
    }

    this.advancePC()

    break;

This ADD instruction updates registers, sets the flag register and then increments the PC.

After this, the emulator will loop back to fetch and repeat the cycle. That’s really all there is to the fundamentals of this emulator. We need to implement the decode and execute of every single opcode. The most interesting opcodes will draw to the screen and read from the keyboard. It’s up to the emulator to determine how to action these commands, I chose to draw to an htlm5 canvas when we run into a draw instruction.

That’s all for now, I just wanted to share some of my thoughts and experiences creating Chippy8.

Nick.

Written on June 6, 2020