Skip to content

Writing a NES emulator in Javascript: Part 4 – Opcodes

In previous articles I talked about the initial process of getting the PRG ROM loaded and executing. Now comes the somewhat lengthy process of implementing all the opcodes that were available on the NES CPU. The fact that the NES brain is so well documented is a huge plus here, including the 6502 programming manual and this opcode list.

Despite what I mentioned in a prior article, I’m no longer using a giant switch statement for all the opcodes. They’re now stored in an object in a separate file, with the name of the opcode (as a constant) being the key. In another file (optable.js) I store information about the instructions, such as byte length and cycle count.

Implementing the instructions

The process I’ve been following for implementing the instructions worked as follows:

  1. Have a good debugger output for instructions as they execute
  2. Execute the instructions and crash when we encounter one that hasn’t been implemented yet, outputting the name of the unimplemented instruction. This can then be looked up in the reference doc and implemented
    while (this.running == true) {
      let opcode = this.fetch(this.registers.PC);

      if (!opcode) {
        /* we've reached the end of execution, here be dragons */
        break;
      }

      /* Debugging code, causes the unimplemented opcode to be written out and the program
         to bail. Useful during development. */

      if (
        typeof optable[opcode] === "undefined" ||
        typeof optable[opcode].op === "undefined"
      ) {
        console.log(
          "Unimplemented opcode: " +
            opcode.toString(16) +
            " at " +
            this.registers.PC.toString(16)
        );
       this.running = false;
       continue;
      }
      /* the bind() call makes the 'this' object the CPU when the opcode closure is executed */
      optable[opcode].op.bind(this).call();

I’ve omitted some code but you get the idea, the opcodes are being pulled from the memory using the Program Counter (CPU.registers.PC), and then get looked up in the optable module. The optable.js file contains entries such as this:

optable[0xEA] = {
  cycles: 2,
  bytes: 1,
  name: "NOP",
  op: opcodes["NOP"]
};

All the opcodes are contained within a file called ops.js, where each opcode function is stored as a value in an object with the key being the opcode byte. Instead of writing extra code to decode the addressing format, it seemed simpler to just implement a function for each addressing mode. The function calls are so small (often one line) that the duplication makes things simpler than writing an addressing mode decoder, but this may have turned out to be a bad choice further down the line. So taking an opcode like LDA in the Indirect addressing mode using the X register we’d have something like:

  LDA_IND_X: function() {
    this.log(
      "LDA ($" + this.next_byte().toString(16) + "), X",
      this.registers.PC
    );
    this.registers.A = this.fetch(this.next_byte() + this.registers.X);
  },

This approach works fine, but I needed some way to be able to test for emulation accuracy. In the next article I’ll discuss the testing system I set up to ensure fidelity in executing the NES code.

Published inemulatorjavascript

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *