Source: chip8-cpu.js

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