JavaScript Emulators

Introduction

A CPU is a device which takes instructions from RAM and performs actions based on those instructions.

There are a variety of instructions that a CPU can perform:

  • Arithmetic instructions - add, subtract, and on bigger CPUs multiply and divide
  • Logic instructions - and, or, exclusive or, shifts
  • Memory instructions - load a value from RAM into a register, store a register back into RAM
  • Control flow instructions - jump to another instruction, either always or only if a condition is met

The CPU keeps its place with a program counter, the address of the next instruction, and does its sums in registers, a few fast storage slots of its own.

Implementation

In theory a CPU can be very simple to implement - it is a for loop over a list of instructions and a switch statement to perform the required instruction each time.

There are many complexities which can be added but let's start simple. This CPU has one register, an accumulator, 256 bytes of RAM, and seven instructions, each two bytes long: the instruction code and an address.

//  Instruction codes
const LOAD = 1, STORE = 2, ADD = 3, SUB = 4, JUMP = 5, JUMPZ = 6, HALT = 7

function run( ram ) {
  let pc = 0   //  Program counter: where the next instruction is
  let a = 0    //  Accumulator: the one register

  for(;;) {
    const op = ram[pc++]
    const arg = ram[pc++]

    switch( op ) {
      case LOAD:  a = ram[arg]; break
      case STORE: ram[arg] = a; break
      case ADD:   a = (a + ram[arg]) & 255; break
      case SUB:   a = (a - ram[arg]) & 255; break
      case JUMP:  pc = arg; break
      case JUMPZ: if( a === 0 ) pc = arg; break
      case HALT:  return
    }
  }
}
The whole CPU: fetch an instruction and its address, then a switch to carry it out

The & 255 keeps the accumulator to 8 bits, so it wraps round like the real thing: 0 minus 1 is 255.

A program

This CPU can't multiply, so let's give it a program which does it by adding. It multiplies 6 by 7 by adding 6 to a total seven times, counting down as it goes:

const ram = new Uint8Array( 256 )

ram.set( [
  LOAD, 34,    //  0: a = total
  ADD, 32,     //  2: a += x
  STORE, 34,   //  4: total = a
  LOAD, 33,    //  6: a = count
  SUB, 35,     //  8: a -= 1
  STORE, 33,   // 10: count = a
  JUMPZ, 16,   // 12: if count is 0, finish
  JUMP, 0,     // 14: otherwise go round again
  HALT, 0      // 16: stop
] )

ram.set( [ 6, 7, 0, 1 ], 32 )   //  x, count, total, the number 1

run( ram )

console.log( ram[34] )   //  42
A program in RAM which multiplies 6 by 7 with a loop, and its data at address 32

The program and its data share the same RAM, just as on a real machine. Nothing stops the program writing over its own instructions, which is how a lot of old code saved a few bytes.

From here a real CPU emulator adds more registers, flags for zero, carry and negative, a stack for calling subroutines, and instructions of different lengths. But the heart of it stays the same loop and switch.