Skip to content

Writing a NES emulator in Javascript: Part 5 – Debugging the CPU

I’ll be honest with you, I’m really not used to working at this low a level. Bitwise math hasn’t been my strength, so it has been important to ensure I have some sort of reference to work from. The popular choice when working with NES emulators is the so-called “golden log” produced by Nintendulator, a particularly accurate NES emulator. By reading through the log and writing a similar output system for my own CPU, it’s easily possible to hand-check the accuracy of the opcodes. After doing this for about a week I found it pretty tedious however, and decided I could speed things up a bit

Automated Testing

A simple solution seemed to be to parse the nestest log and turn it into a javascript-friendly object, and then run the CPU instruction-by-instruction and check the reference. I opted to just check A, X, Y, P and SP, as I still really am not sure how the cycle count works (more on that in another article). First step was to write a simple class that read/parses the nestest log:

class NEStest {
  constructor() {
    this.code = [];
    var fs = require("fs");
    var nt = fs
      .readFileSync("nestest.log", "utf-8")
      .filter(x => x != "");
    nt.forEach(line => {
      let segments = line.split(/[ ]{2,}/);
      let line_hash = { flags: { PC: segments[0] } };
      let flags = segments[3].split(/ /);

      flags.forEach(flag_str => {
        var parts = flag_str.split(":");
        line_hash.flags[parts[0]] = parts[1];

      this.code[parseInt(segments[0], 16)] = line_hash;

I then added a test() function to the emulator class to be an alternative to boot(). In order to make this code work, it was necessary to modify the CPU.execute() function to take an optional argument that specified how many instructions to execute. Allowing a step-by-step CPU execution enabled checking against the log entries. The code tests against the nestest reference line-by-line until one of the registers doesn’t match the expected value and then bails:

  test() {
    let test_suite = new nestest();

    // put the CPU into the default state

    let running = true;

    while (running) {
      /* executing with a numerical argument executes a limited number of instructions */

      /* look up the comparison line in the nestest object */
      let test_line = test_suite["code"][this.cpu.registers.PC];

      if (!test_line) {
        /* there's no nestest.log entry with this address
        it's safe to assume something went really wrong */
        console.log("CPU address not expected!");

      Object.entries(this.cpu.registers).forEach(register => {
        if (
          parseInt(this.cpu.registers[register[0]]) !=
          parseInt(test_line.flags[register[0]], 16)
        ) {
          let output_message = `Registers ${register[0]} mismatch\n`;
          output_message += `Got ${this.cpu.registers[register[0]].toString(16)}, expected ${test_line.flags[register[0]]}`;

          running = false;

This really has made it easier to debug the CPU functionality, as it results in output such as this where there’s an implementation issue:

c812 Beginning execution at 51218			[A: 80 X: 0 Y: 0 P: ad SP: fb CYC: 374]
c812 LDA #$0			[A: 80 X: 0 Y: 0 P: ad SP: fb CYC: 374]
c814 Beginning execution at 51220			[A: 0 X: 0 Y: 0 P: 2f SP: fb CYC: 378]
c814 SEC			[A: 0 X: 0 Y: 0 P: 2f SP: fb CYC: 378]
c815 Beginning execution at 51221			[A: 0 X: 0 Y: 0 P: 2f SP: fb CYC: 381]
c815 PHP			[A: 0 X: 0 Y: 0 P: 2f SP: fb CYC: 381]
c816 Beginning execution at 51222			[A: 0 X: 0 Y: 0 P: 2f SP: fa CYC: 385]
c816 PLA			[A: 0 X: 0 Y: 0 P: 2f SP: fa CYC: 385]
c817 Beginning execution at 51223			[A: 3f X: 0 Y: 0 P: 2d SP: fb CYC: 390]
c817 AND #239			[A: 2f X: 0 Y: 0 P: 2d SP: fb CYC: 390]
Registers P mismatch
Got ad, expected 2D
{ PC: 51225, SP: 251, P: 173, A: 47, X: 0, Y: 0 }
{ carry: true,
  zero: false,
  interrupt_disable: true,
  decimal_mode: true,
  break_command: false,
  overflow: false,
  negative: true,
  decimal: false }
Published inemulatorjavascript

Be First to Comment

Leave a Reply

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