Chip8 emulator – part 1

October 7, 2015

Let's write a simple emulator in JavaScript. Chip8 CPU has a very minimal architecture which makes it a great emulation project for beginners.

Here are basic hardware stats:

  • 4096 bytes of memory
  • 35 opcodes
  • 16 registers: V0, V1, ..., VF
  • Address register, I is 16 bits wide

And to start, let's disassemble a small demo ROM (from Chip8-rom-pack) and get a feel for what opcodes look like and general flow of programs.
$ hexdump Maze.ch8 
0000000 60 00 61 00 a2 22 c2 01 32 01 a2 1e d0 14 70 04
0000010 30 40 12 04 60 00 71 04 31 20 12 04 12 1c 80 40
0000020 20 10 20 40 80 10                              
0000026
A bit of formatting.
var rom = [ 
    0x6000, 0x6100, 0xa222, 0xc201, 0x3201, 0xa21e, 0xd014, 0x7004,
    0x3040, 0x1204, 0x6000, 0x7104, 0x3120, 0x1204, 0x121c, 0x8040,
    0x2010, 0x2040, 0x8010, 0xa21e, 0xc201, 0x3201, 0xa21a, 0xd014,
    0x7004, 0x3040, 0x1200, 0x6000, 0x7104, 0x3120, 0x1200, 0x1218,
    0x8040, 0x2010, 0x2040, 0x8010 
];
Looking up first opcode in the reference: 6XNN - 'Sets VX to NN' which means first opcode in the rom, 0x6000, sets register V0 to 0. Next opcode 0x6100 does a similar thing, setting register V1 to 0.

This is already enough to setup a quick disassembler v0.01
// roms are loaded at 0x200 in the 4096 bytes of memory space
pc = 0x200; 

// registers
v = new Uint8Array(16); 

rom.forEach(
  function (b) {
    log(pc.toString(16) + ":" + b.toString(16) + " " + run(b));

    // increment program-counter to point to the next opcode
    pc += 0x08;
  });

function run(opcode) {
  // 6XNN  Sets VX to NN.
  if (opcode >> 12 == 0x6) {
    v[(opcode & 0xf00) >> 8)] = (opcode & 0xff);
    return "Sets V" + ((opcode & 0xf00) >> 8).toString(16) + 
             " to " + (opcode & 0xff).toString(16);
  }
};
And disassemble 4 opcodes from the rom.


 
In the next part, we'll add the rest of opcodes needed that disassemble entire rom.