var chip8;
(function (chip8) {
'use strict';
var
/**
* Memory size in bytes.
* @type {number}
* @private
*/
MEM_SIZE = 4096, // 4K
/**
* Memory location at which programs will be loaded.
* @type {number}
* @private
*/
PROGRAM_START_ADDR = 0x0200,
/**
* Timers update frequency.
* @type {number}
* @private
*/
SPEED = 60, // Hz
/**
* Number of steps performed in one cycle.
* @type {number}
* @private
*/
STEPS_PER_CYCLE = 10,
/**
* Stack size in bytes
* @type {number}
* @private
*/
STACK_SIZE = 16;
/**
* Defines font sprites.
* @type {number[]}
* @private
*/
var FONTS = [
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
0x20, 0x60, 0x20, 0x20, 0x70, // 1
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
];
/**
* Creates CPU instance.
* @param params
* @param {chip8.Keyboard} params.keyboard - Keyboard which handles input.
* @param {chip8.Screen} params.screen - Screen which handles output.
* @param {chip8.Audio} params.audio - Audio module which handles emitting sounds.
* @class chip8.CPU
*/
function CPU(params) {
this.keyboard = params.keyboard;
this.screen = params.screen;
this.audio = params.audio;
this.reset();
}
/**
* Loads font set into memory.
* @function loadFontSet
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.loadFontSet = function () {
for (var i = 0; i < FONTS.length; ++i) {
this.mem[i] = FONTS[i];
}
};
/**
* Loads binary data into memory starting at location 0x0200
* @param {ArrayBuffer} binData - data to load
* @param {number} [offset] - offset added to starting address
* @function loadToMemory
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.loadToMemory = function (binData, offset) {
for (var i = 0; i < binData.byteLength; ++i) {
this.mem[PROGRAM_START_ADDR + (offset || 0) + i] = binData[i];
}
};
/**
* Resets CPU state.
* Clears memory and loads font set, clears stack, registers and pointers.
* Resets state of keyboard, screen and audio.
* @function reset
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.reset = function () {
this.mem = new Uint8Array(MEM_SIZE);
this.stack = new Array(STACK_SIZE);
/**
* Enables/disabled CPU quirks
* @memberof chip8.CPU
* @instance
* @type {object}
* @name chip8.CPU#quirks
* @property {boolean} shift - If enabled, VX is shifted and VY remains unchanged (default: false)
* @property {boolean} loadStore - If enabled, I is not incremented during load/store (default: false)
*/
this.quirks = {
shift: false,
loadStore: false
};
this.V = new Uint8Array(16);
this.i = 0;
this.pc = PROGRAM_START_ADDR;
this.dt = 0;
this.st = 0;
this.sp = 0;
this.repaint = false;
this.halted = false;
this.screen.clear();
this.screen.repaint();
this.keyboard.reset();
this.loadFontSet();
};
/**
* Runs CPU.
* @function run
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.run = function () {
var self = this;
function tick() {
self.timeoutHandle = setTimeout(function() {
self.rafHandle = window.requestAnimationFrame(tick);
self.cycle();
}, 1000 / SPEED);
}
tick();
};
/**
* Stops CPU.
* @function stop
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.stop = function () {
if (this.timeoutHandle) {
window.clearTimeout(this.timeoutHandle);
}
if (this.rafHandle) {
window.cancelAnimationFrame(this.rafHandle);
}
};
/**
* Halts CPU.
* Halted CPU differs from stopped CPU in a way that halted CPU performs idle loop,
* and stopped CPU does nothing.
* @function halt
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.halt = function () {
this.halted = true;
};
/**
* Resumes CPU from halted state.
* @function resume
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.resume = function () {
this.halted = false;
};
/**
* Performs one CPU cycle.
* @function cycle
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.cycle = function () {
if (this.halted) {
return;
}
for (var i = 0; i < STEPS_PER_CYCLE; i++) {
this.step();
}
this.updateTimers();
if (this.st > 0) {
this.audio.play();
} else {
this.audio.stop();
}
};
/**
* Updates CPU timers.
* @function updateTimers
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.updateTimers = function () {
if (this.dt > 0) {
this.dt--;
}
if (this.st > 0) {
this.st--;
}
};
/**
* Performs one CPU step.
* One step means one fetch-decode-execute cycle.
* @function step
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.step = function () {
this.execute(this.decode(this.fetch()));
if (this.repaint) {
this.screen.repaint();
this.repaint = false;
}
};
/**
* Fetches next instruction code
* @returns {number} instruction code
* @function fetch
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.fetch = function () {
return this.mem[this.pc] << 8 | this.mem[this.pc + 1];
};
/**
* Decodes given instruction code and returns function which performs that instruction.
* Throws exception if can't decode code.
* @param {number} opCode - instruction code
* @returns {function|undefined} function performing operation
* @function decode
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.decode = function (opCode) {
var x = (opCode & 0x0F00) >> 8,
y = (opCode & 0x00F0) >> 4,
kk = opCode & 0x00FF,
nnn = opCode & 0x0FFF,
n = opCode & 0x000F;
switch (opCode & 0xF000) {
case 0x0000:
switch (opCode) {
case 0x00E0: return CPU.IS.CLS(); // 0x00E0: CLS
case 0x00EE: return CPU.IS.RET(); // 0x00EE: RET
default: return CPU.IS.SYS(); // 0x0nnn: SYS
}
break;
case 0x1000:
return CPU.IS.JP_nnn(nnn); // 0x1xxx: JP nnn
case 0x2000:
return CPU.IS.CALL_nnn(nnn); // 0x2nnn: CALL nnn
case 0x3000:
return CPU.IS.SE_Vx_kk(x, kk); // 0x3xkk: SE Vx, kk
case 0x4000:
return CPU.IS.SNE_Vx_kk(x, kk); // 0x4xkk: SNE Vx, kk
case 0x5000:
return CPU.IS.SE_Vx_Vy(x, y); // 0x5xy0: SE Vx, Vy
case 0x6000:
return CPU.IS.LD_Vx_kk(x, kk); // 0x6xkk: LD Vx, kk
case 0x7000:
return CPU.IS.ADD_Vx_kk(x, kk); // 0x7xkk: ADD Vx, kk
case 0x8000:
switch (opCode & 0x000F) {
case 0x0000: return CPU.IS.LD_Vx_Vy(x, y); // 0x8xy0: LD Vx, Vy
case 0x0001: return CPU.IS.OR_Vx_Vy(x, y); // 0x8xy1: OR Vx, Vy
case 0x0002: return CPU.IS.AND_Vx_Vy(x, y); // 0x8xy2: AND Vx, Vy
case 0x0003: return CPU.IS.XOR_Vx_Vy(x, y); // 0x8xy3: XOR Vx, Vy
case 0x0004: return CPU.IS.ADD_Vx_Vy(x, y); // 0x8xy4: ADD Vx, Vy
case 0x0005: return CPU.IS.SUB_Vx_Vy(x, y); // 0x8xy5: SUB Vx, Vy
case 0x0006: return CPU.IS.SHR_Vx_Vy(x, y); // 0x8xy6: SHR Vx, Vy
case 0x0007: return CPU.IS.SUBN_Vx_Vy(x, y); // 0x8xy5: SUB Vx, Vy
case 0x000E: return CPU.IS.SHL_Vx_Vy(x,y); // 0x8xyE: SHL Vx, Vy
}
break;
case 0x9000:
return CPU.IS.SNE_Vx_Vy(x, y); // 0x9xy0: SNE Vx, Vy
case 0xA000:
return CPU.IS.LD_I_nnn(nnn); // 0xAnnn: LD I, nnn
case 0xB000:
return CPU.IS.JP_V0_nnn(nnn); // 0xBnnn: JP V0, nnn
case 0xC000:
return CPU.IS.RND_Vx_kk(x, kk); // 0xCxkk: RND Vx, kk
case 0xD000:
return CPU.IS.DRW_Vx_Vy_n(x, y, n); // 0xDxyn: DRW Vx, Vy, n
case 0xE000:
switch (opCode & 0x00FF) {
case 0x009E: return CPU.IS.SKP_Vx(x); // 0xEx9E: SKP Vx
case 0x00A1: return CPU.IS.SKNP_Vx(x); // 0xExA1: SKNP Vx
}
break;
case 0xF000:
switch (opCode & 0x00FF) {
case 0x0007: return CPU.IS.LD_Vx_DT(x); // 0xFx07: LD Vx, DT
case 0x000A: return CPU.IS.LD_Vx_K(x); // 0xFx0A: LD Vx, k
case 0x0015: return CPU.IS.LD_DT_Vx(x); // 0xFx15: LD DT, Vx
case 0x0018: return CPU.IS.LD_ST_Vx(x); // 0xFx18: LD ST, Vx
case 0x001E: return CPU.IS.ADD_I_Vx(x); // 0xFx1E: ADD I, Vx
case 0x0029: return CPU.IS.LD_F_Vx(x); // 0xFx29: LD F, Vx
case 0x0033: return CPU.IS.LD_B_Vx(x); // 0xFx33: LD B, Vx
case 0x0055: return CPU.IS.LD_I_Vx(x); // 0xFx55: LD [I], Vx
case 0x0065: return CPU.IS.LD_Vx_I(x); // 0xFx65: LD Vx, [I]
}
break;
}
this.stop();
throw 'Unknown opcode: 0x' + opCode.toString(16);
};
/**
* Executes given operation.
* @param {function} op - operation to execute.
* @function execute
* @memberof chip8.CPU
* @instance
*/
CPU.prototype.execute = function (op) {
op(this);
};
chip8.CPU = CPU;
})(chip8 || (chip8 = {}));