Skip to content

Writing a NES emulator in Javascript: Part 2 (starting on the CPU)

Let’s start with the most obvious choice: The CPU. The NES’s 2A03 CPU is a modified 6502 processor. There’s an interesting story behind the choice of processor:

“The Nintendo core processor was a 6502 designed with the patented technology scraped off, We actually skimmed off the top of the chip inside of it to see what it was, and it was exactly a 6502. We looked at where we had the patents and they had gone in and deleted the circuitry where our patents were.”

Although there were changes, the NES microprocessor ran 99% of the 6502 instruction set. “Some things didn’t work quite right or took extra cycles,”

One important difference is the removal of the decimal mode, which was patented technology. The flag still exists, but is permanently disabled.

The focus of this implementation was to get code working, and so no effort is being made to optimise anything. For instance, some of the bitwise function are implemented using string operations and will be switched over later.

In order to test against a reference, I’m using nestest.nes which the docs describe as follows:

This here is a pretty much all inclusive test suite for a NES CPU. It was designed to test almost every combination of flags, instructions, and registers. Some of these tests are very difficult, and so far, Nesten and Nesticle failed it. Nintendulator passes, as does a real NES (naturally). I haven’t tested it with any more emulators yet.

I attempted to check the states of all flags after most instructions. For example, CPY and CMP shouldn’t affect the overflow flag, while SBC and ADC should. Likewise, all forms of wrapping ARE tested for- zeropage wrapping being the tests most emulators fail.

Starting the code

The log output for nestest.nes from Nintendulator is used as a reference. The CPU is represented by a class that contains the processor logic and attributes for the various parts of the CPU:

 

class CPU {

  constructor() {
    this.memory = new Memory;
    this.registers = {PC: 0, SP: 0, P:0, A:0, X: 0, Y: 0};
    this.flags = {carry: false, zero: false,
      interrupt_disable: true, decimal_mode: false,
      break_command: true, overflow: false, negative: false};
    this.running = false;
    this.cycles = 0;
    this.logger = new Logger;
    this.stack = new Array;
    this.log("CPU Initialized");
  }

Memory access is split out into its own class which contains functionality for getting and setting memory. The NES includes memory mapped I/O Implementing memory mapping in the CPU involves mapping particular addresses to different hardware elements. The fetch() function of the memory class accomplishes the memory mapping for retrieval detailed here:

 

  fetch(addr) {
    if(addr >= 0 && addr <= 0x7FF) {
      // 0000-00FF Zero-paged region
      // 0100-01FF - Stack Memory
      return this.ram[addr];
    } else if (addr >= 0x800 && addr < 0x0FFF) {
      // mirrors RAM
      return this.ram[addr-0x800];
    } else if (addr >= 0x1000 && addr < 0x17FF) {
      // mirrors RAM
      return this.ram[addr-0x1000];
    } else if (addr >= 0x1800 && addr < 0x1FFF) {
      // mirrors RAM
      return this.ram[addr-0x1800];
    } else if (addr >= 0x2000 && addr < 0x2007) {
      // NES PPU registers
    } else if (addr >= 0x2008 && addr < 0x3FFF) {
      // Mirrors of $2000-2007
    } else if (addr >= 0x4000 && addr < 0x4017) {
      // NES APU and I/O registers
    } else if (addr >= 0x4018 && addr < 0x401F) {
      // APU and I/O functionality
    } else if (addr > 0x4020 && addr < 0xFFFF) {
      if (addr >= 0xC000 && addr < 0xFFFF) {
        return this.rom[(addr-0xC000)];
      }
    }

  }

We'll get to how the ROM part works in the next article.

I opted to implement the opcodes as a giant switch() statement, which while not optimal will get the job done. An alternative approach I could've used was the one Imran Nezar used of creating an object containing all the operations as values, with the opcodes as the keys.:

  execute() {

    this.log("Beginning execution at " + this.registers.PC);
    this.running = true;

    while(this.running == true) {

      let opcode = this.memory.fetch(this.registers.PC);
      switch(opcode) {

      case ops.NOOP:
        let nb1 = this.memory.fetch(this.registers.PC+1);
        let nb2 = this.memory.fetch(this.registers.PC+2);
        let j = utility.Utility.merge_bytes(nb1, nb2);
        this.log("JSR " + j, this.registers.PC);
        let bytes = utility.Utility.split_byte(this.registers.PC+3);
        this.stack.push(bytes[0]);
        this.stack.push(bytes[1]);
        this.registers.SP -= 2;
        this.registers.PC = j;
      break;
      case ops.TSX:
          [...]
      break;
      case ops.TXS:
          [...]
      break;
      case ops.TYA:
          [...]
      break;

In the next article I'll talk a little about how to load a ROM file and how to get the code booting.

Published inemulatorjavascript

Be First to Comment

Leave a Reply

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