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 = {}));