Source: emulator/emulator.js

if (typeof yasp == 'undefined') yasp = { };

(function() {
  var debug = false;

  /**
   * Emulator is responsible for running the bytecode from the assembler.
   * Additional documentation can be found in the {@link https://github.com/yasp/yasp/blob/master/doc/emulator/emulator.md|GitHub repository}.
   * @constructor
   */
  yasp.Emulator = function() {

    /** the program code including strings, interrupt-table, etc
     * @member {Uint8Array} */
    this.rom = new Uint8Array(512);
    /** contains the registers (first 63 bytes), stack and RAM accessible via ReadRAM/WriteRAM
     * @member {Uint8Array} */
    this.ram = new Uint8Array(512);
    /** emulator flags set by arithmetic and PIN-Instructions
     * @member {Object}
     * @property {boolean} c carry flag
     * @property {boolean} z zero flag
     */
    this.flags = { c: false, z: false };

    /** number of milliseconds between two tickWrapper-calls
     * @member {Number} */
    this.tickDelay = 100;
    /** number of ticks inside one tickWrapper call
     * @member {Number} */
    this.ticksPerTick = 300;
    /** timeout id of the last tickWrapper-setTimeout
     * @member {Number} */
    this.tickTimeout = 0;

    /** cache for already disassembled commands
     * @member {Object} */
    this.commandCache = {};

    /** initial value of the stackpointer
     * @member {Number}
     * @default */
    this.initialSP = 0x50;
    /** offset of the top, last pushed byte in the stack. Points to a byte in the ram.
     * @member {Number}
     */
    this.sp = this.initialSP;

    /** offset of the last executed instruction in the {@link yasp.Emulator#rom ROM}
     * @member {Number} */
    this.pc = 0;

    /** indicator of the running-state
     * Number = execute a number of instructions
     * true = execute unlimited number of instructions
     * false = execution halted
     * @member {Number|Boolean}
     * @see yasp.Emulator#continue
     * @see yasp.Emulator#break
     * @default */
    this.running = false;

    /** reason for the current break, if any.
     * @member {String}
     * @see yasp.Emulator#continue
     * @see yasp.Emulator#break
     * @default */
    this.breakReason = null;

    /** number of ticks executed in total, used by PWM for timing
     * @member {Number}
     * @see yasp.Emulator#tick */
    this.ticks = 0;

    /** time in milliseconds to wait for the next tick
     * @member {Number}
     * @see yasp.Emulator#wait
     * @see yasp.Emulator#tickWrapper */
    this.waitTime = 0; // time to wait in ms

    /** bits of the interrupt-mask
     * @member {boolean[]}
     * @see yasp.Emulator#setInterruptMask
     * @see yasp.Emulator#scheduleInterrupt */
    this.interruptMask = [
      false,
      false,
      false,
      false,
      false,
      false,
      false,
      false
    ];

    /** interrupt to execute on the next tick
     * @member {Number}
     * @see yasp.Emulator#tick
     * @see yasp.Emulator#setIO
     * @see yasp.Emulator#scheduleInterrupt */
    this.interruptToServe = -1;

    /** do not ever execute a tick unless {@link yasp.Emulator#tick} is called directly. This is used by the test-suite and should not be used by anything else.
     * @member {boolean}
     * @see yasp.Emulator#setTickWrapperTimeout */
    this.forceStep = false;

    this.skipBreakpoint = false;

    // pin definitions (see setIO, getIO and interrupts)
    this.iobank = new yasp.IOBank();
    this.iobank.addPins([
      // buttons
      new yasp.Pin(1, "gpio", "in", false, this),
      new yasp.Pin(2, "gpio", "in", false, this),
      // LEDs
      new yasp.Pin(3, "gpio", "out", true, this),
      new yasp.Pin(4, "gpio", "out", true, this),
      new yasp.Pin(5, "gpio", "out", true, this),
      // ADC0 - ADC2
      new yasp.Pin(10, "adc", "in", false, this),
      new yasp.Pin(11, "adc", "in", false, this),
      new yasp.Pin(12, "adc", "in", false, this)
    ]);

    this.iobank.setStateChangedEvent((function (pin) {
        this.events.IO_CHANGED(pin.nr, pin.state, pin.mode, pin.type);
        if(this.changeBreakpoints.io === true) this.changeBreakpointData.io.push(pin.nr);
      }).bind(this)
    );

    // events
    this.noop = function () {};
    this.events = {
      'CONTINUE': this.noop,
      'BREAK': this.noop,
      'LOADED': this.noop,
      'DEBUG': this.noop,
      'IO_CHANGED': this.noop
    };

    /** offsets in ROM which have a breakpoint set
     * @member {boolean[]}
     * @see yasp.Emulator#setBreakpoints */
    this.breakpoints = new Array(this.rom.length);
    for (var i = 0; i < this.breakpoints.length; i++) {
      this.breakpoints[i] = false;
    }

    /** Flags for 'change'-breakpoints. If one of these is true write-actions to the specific value will be recored.
     */
    this.changeBreakpoints = {
      rbyte: false,
      rword: false,
      zflag: false,
      cflag: false,
      io: false,
      ram: false,
      rom: false
    };

    /** stores the changes to registers, flags, io and so on. Only written if a change-breakpoint for the specific value is set.
     * @member {object}
     */
    this.changeBreakpointData = {};

    for (var c in this.changeBreakpoints) {
      this.changeBreakpointData[c] = new Array(10);
      this.changeBreakpointData[c].length = 0;
    }

    /** conditions for breakpoints, null means no condition.
     * {@link https://github.com/yasp/yasp/blob/master/doc/emulator/data.md#breakpoints|Additional documentation}.
     * @member {object[]}
     * @see yasp.Emulator#setBreakpoints */
    this.breakpointConditions = new Array(this.rom.length);
    for (var i = 0; i < this.breakpointConditions.length; i++) {
      this.breakpointConditions[i] = false;
    }

    /** all breakpoins which do not have a offset-value.
     * {@link https://github.com/yasp/yasp/blob/master/doc/emulator/data.md#breakpoints|Additional documentation}.
     * @member {object[]}
     * @see yasp.Emulator#setBreakpoints */
    this.globalBreakpoints = [];

    this.setTickWrapperTimeout();
  };

  /** fired when the execution is started or resumed.
   * @event yasp.Emulator~CONTINUE
   * @param running {Number}
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/messages.md#broadcast-continue|CONTINUE-Broadcast}
   */

  /** fired when the execution is halted.
   * @event yasp.Emulator~BREAK
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/messages.md#broadcast-break|BREAK-Broadcast}
   */

  /** fired when code has been loaded into the rom for execution.
   * @event yasp.Emulator~LOADED
   * @param start {Number}
   * @param length {Number}
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/messages.md#broadcast-loaded|LOADED-Broadcast}
   */

  /** fired when a DEBUG or ECHO instruction was executed.
   * @event yasp.Emulator~DEBUG
   * @param type {String}
   * @param subtype {String}
   * @param addr {Number}
   * @param val {Number}
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/messages.md#broadcast-debug|DEBUG-Broadcast}
   */

  /** fired when the state of an output-pin changed.
   * @event yasp.Emulator~IO_CHANGED
   * @param pin {Number}
   * @param state {Number}
   * @param mode {String}
   * @param type {String}
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/messages.md#broadcast-io_changed|IO_CHANGED-Broadcast}
   */

  /** Registers the callback for an event. It is not possible to register multiple callbacks for one event for performance reasons.
   * Possible events are {@link yasp.Emulator~event:CONTINUED}, {@link yasp.Emulator~event:BREAK}, {@link yasp.Emulator~event:LOADED}, {@link yasp.Emulator~event:DEBUG}, {@link yasp.Emulator~event:IO_CHANGED}
   * @param evt {String} the event name
   * @param func {function} the event callback
   * @private
   */
  yasp.Emulator.prototype.registerCallback = function (evt, func) {
    if(typeof func === "function") {
      this.events[evt] = func;
      return true;
    } else {
      return false;
    }
  };

  /** Loads the given bitcode into the ROM
   * @param bitcode {Uint8Array} bitcode to load
   * @param start {Number} address to start loading into
   * @returns {Number|Boolean}
   * @fires yasp.Emulator~LOADED
   * @private
   */
  yasp.Emulator.prototype.load = function(bitcode, start) {
    if(typeof start !== "number")
      return 3;
    if(start < 0 || start >= this.rom.length)
      return 0;
    if(!(bitcode instanceof Uint8Array))
      return 2;
    if(start + bitcode.length >= this.rom.length)
      return 1;

    this.rom.set(bitcode, start);
    this.clearCommandCache(start, bitcode.length);
    this.events.LOADED(start, bitcode.length);
    return true;
  };

  /** Continues the execution
   * @param count {Number|boolean} number of instructions to execute or null
   * @param skipBreakpoint {boolean} if the next (and only that one) breakpoint should be skipped
   * @returns {Number|boolean}
   * @fires yasp.Emulator~CONTINUE
   * @private
   */
  yasp.Emulator.prototype.continue = function (count, skipBreakpoint) {
    if(count === null) {
      this.running = true;
    } else if(typeof count === "number") {
      if(count < 0)
        return 0;
      this.running = +count;
    } else {
      return 1;
    }

    this.breakReason = null;
    this.skipBreakpoint = !!skipBreakpoint;
    this.setTickWrapperTimeout(0);

    this.events.CONTINUE(this.running);
    return true;
  };

  /** Stops the execution
   * @fires yasp.Emulator~BREAK
   */
  yasp.Emulator.prototype.break = function (reason) {
    this.running = false;
    this.breakReason = reason;
    this.events.BREAK(reason);
  };

  /** updates the internal list of breakpoints
   * @param breakpoints {Breakpoint[]} the breakpoints to save.
   *        {@link https://github.com/yasp/yasp/blob/master/doc/emulator/data.md#breakpoints|Additional documentation}.
   * @returns {Number|Boolean}
   */
  yasp.Emulator.prototype.setBreakpoints = function (breakpoints) {
    if(!(breakpoints instanceof Array))
      return 0;

    this.globalBreakpoints.length = 0;

    for (var i = 0; i < this.breakpoints.length; i++) {
      this.breakpoints[i] = false;
      this.breakpointConditions[i] = null;
    }

    for (var r in this.changeBreakpoints) {
      this.changeBreakpoints[r] = false;
    }


    for (var i = 0; i < breakpoints.length; i++) {
      var brk = breakpoints[i];

      if(brk.offset !== null && brk.offset !== undefined) {
        this.breakpoints[brk.offset] = true;
      }

      if(brk.condition === null || brk.condition === undefined)
        continue;

      var condition = {};

      condition.boolValue = (brk.condition.value === true);
      condition.numValue = +brk.condition.value;
      condition.uintArrayValue = (brk.condition.value instanceof Uint8Array) ? brk.condition.value : new Uint8Array();

      condition.isBoolValue = false;
      condition.isNumValue = false;
      condition.isUintArrayValue = false;

      if(brk.condition.type === "register") {
        condition.isByteRegister = (brk.condition.param.charAt(0) === "b");
        condition.isWordRegister = (brk.condition.param.charAt(0) === "w");
        condition.registerNumber = +brk.condition.param.substring(1);
        condition.isNumValue = true;
      }

      if(brk.condition.type === "flag") {
        condition.isCarryFlag = (brk.condition.param === "c");
        condition.isZeroFlag = (brk.condition.param === "z");
        condition.isBoolValue = true;
      }

      if(brk.condition.type === "io") {
        condition.isIO = true;
        condition.ioPinNumber = +brk.condition.param;
        condition.isNumValue = true;
      }

      if(brk.condition.type === "ram" || brk.condition.type === "rom") {
        condition.isRamOffset = (brk.condition.type === "ram");
        condition.isRomOffset = (brk.condition.type === "rom");
        condition.memoryOffset = +brk.condition.param;

        if(brk.condition.value instanceof Uint8Array)
          condition.isUintArrayValue = true;
        else
          condition.isNumValue = true;
      }

      condition.isEquals        = (brk.condition.operator === "=");
      condition.isNotEquals     = (brk.condition.operator === "!=");
      condition.isSmaller       = (brk.condition.operator === "<");
      condition.isBigger        = (brk.condition.operator === ">");
      condition.isSmallerEquals = (brk.condition.operator === "<=");
      condition.isBiggerEquals  = (brk.condition.operator === ">=");
      condition.isChange        = (brk.condition.operator === "change");

      if(condition.isChange) {
        if(condition.isByteRegister === true) this.changeBreakpoints["rbyte"] = true;
        if(condition.isWordRegister === true) this.changeBreakpoints["rword"] = true;
        if(condition.isCarryFlag    === true) this.changeBreakpoints["cflag"] = true;
        if(condition.isZeroFlag     === true) this.changeBreakpoints["zflag"] = true;
        if(condition.isIO           === true) this.changeBreakpoints["io"]    = true;
        if(condition.isRamOffset    === true) this.changeBreakpoints["ram"]   = true;
        if(condition.isRomOffset    === true) this.changeBreakpoints["rom"]   = true;
      }


      if(brk.offset !== null && brk.offset !== undefined) {
        this.breakpointConditions[brk.offset] = condition;
      } else {
        this.globalBreakpoints.push(condition);
      }
    }

    return true;
  };

  /** sends a register to the debugger
   * @param type {String} the register-type (w or b)
   * @param addr {Number} the register-number
   * @fires yasp.Emulator~DEBUG
   */
  yasp.Emulator.prototype.debugRegister = function (type, addr) {
    var val = 0;
    if(type === "w")
      val = this.readWordRegister(addr);
    if(type === "b")
      val = this.readByteRegister(addr);
    this.events.DEBUG("register", type, addr, val);
  };

  /** sends a string to the debugger
   * @param addr {Number} ROM-address to read a null-terminated string from
   * @fires yasp.Emulator~DEBUG
   */
  yasp.Emulator.prototype.debugString = function (addr) {
    var str = "";

    while(this.rom[addr] !== 0) {
      str += String.fromCharCode(this.rom[addr]);
      addr++;
    }

    this.events.DEBUG("string", null, addr, str);
  };

  /** Writes the given value into the given byte register
   * @param r {Number} the byte-register to write to (must be between 0 and 31)
   * @param v {Number} the value to write (must be one byte)
   * @returns {Number|Boolean}
   */
  yasp.Emulator.prototype.writeByteRegister = function (r, v) {
    if(r < 0 || r > 32)
      return 0;
    if(v < 0 || v > 255)
      return 1;

    this.ram[r] = v;

    if(debug) console.log("b" + r + "=" + v);
    if(this.changeBreakpoints.rbyte === true) this.changeBreakpointData.rbyte.push(r);

    return true;
  };

  /** Reads the value of the given byte-register
   * @param r {Number} the byte-register to read (must be between 0 and 31)
   * @returns {Number} the registers value
   */
  yasp.Emulator.prototype.readByteRegister = function (r) {
    if(r < 0 || r > 31)
      return -1;
    return this.ram[r];
  };

  /** Writes the given value into the given word register
   * @param r {Number} the word-register to write to (must be between 0 and 31)
   * @param v {Number} the value to write (must be one word)
   * @returns {Number|Boolean}
   */
  yasp.Emulator.prototype.writeWordRegister = function (r, v) {
    if(r < 0 || r > 32)
      return 0;
    if(v < 0 || v > 65535)
      return 1;

    if(debug) console.log("w" + r + "=" + v);
    if(this.changeBreakpoints.rword === true) this.changeBreakpointData.rword.push(r);

    r = r * 2;
    yasp.bitutils.bytesFromWord(v, this.ram, r);
    return true;
  };

  /** Reads the value of the given word-register
   * @param r {Number} the word-register to read (must be between 0 and 31)
   * @returns {Number} the registers value
   */
  yasp.Emulator.prototype.readWordRegister = function (r) {
    if(r < 0 || r > 32)
      return -1;

    r = r * 2;
    return yasp.bitutils.wordFromBytes(this.ram[r], this.ram[r + 1]);
  };

  /** Reads the flags
   * @returns {object} values of both flags
   * @deprecated for speed reasons. Use {@link yasp.Emulator#isCarryFlagSet} and {@link yasp.Emulator#isZeroFlagSet}.
   */
  yasp.Emulator.prototype.readFlags = function () {
    return {
      c: this.flags.c,
      z: this.flags.z
    };
  };

  /** Reads the carry flag
   * @returns {boolean} true if the carry flag is set, otherwise false
   */
  yasp.Emulator.prototype.isCarryFlagSet = function () {
    return this.flags.c;
  };

  /** Reads the zero flag
   * @returns {boolean} true if the zero flag is set, otherwise false
   */
  yasp.Emulator.prototype.isZeroFlagSet = function () {
    return this.flags.z;
  };

  /** Write the carry and zero flag
   * @param c {?boolean} the carry flag to be set (or null to not change it)
   * @param z {?boolean} the zero flag to be set (or null to not change it)
   * @see yasp.Emulator#isCarryFlagSet
   * @see yasp.Emulator#isZeroFlagSet
   */
  yasp.Emulator.prototype.writeFlags = function (c, z) {
    if(c === true || c === false)
    {
      if(debug) console.log("c=" + c);
      if(this.changeBreakpoints.cflag === true) this.changeBreakpointData.cflag.push(true);

      this.flags.c = c;
    }
    if(z === true || z === false)
    {
      if(debug) console.log("z=" + z);
      if(this.changeBreakpoints.zflag === true) this.changeBreakpointData.zflag.push(true);

      this.flags.z = z;
    }
  };

  /** splits the given word into two bytes and pushes them onto the stack. The most significant byte will be pushed last.
   * @param v {Number} word to push onto the stack
   * @see yasp.Emulator#popWord
   */
  yasp.Emulator.prototype.pushWord = function (v) {
    if(debug) console.log("push word: " + v);
    this.ram[++this.sp] = v & 0xFF;
    this.ram[++this.sp] = v >> 8;
  };

  /** pushes one byte onto the stack
   * @param v {Number} the byte to push onto the stack
   * @see yasp.Emulator#popByte
   */
  yasp.Emulator.prototype.pushByte = function (v) {
    if(debug) console.log("push byte: " + v);
    this.ram[++this.sp] = v;
  };

  /** gets two bytes from the stack, combines and removes them
   * @returns {Number} a word from the top of stack
   * @see yasp.Emulator#pushWord
   */
  yasp.Emulator.prototype.popWord = function () {
    return yasp.bitutils.wordFromBytes(this.popByte(), this.popByte());
  };

  /** gets one byte from the stack and removes it
   * @returns {Number} a byte from the top of stack or 0 if the stack is empty
   * @see yasp.Emulator#pushByte
   */
  yasp.Emulator.prototype.popByte = function () {
    if(this.sp === this.initialSP)
      return 0;
    return this.ram[this.sp--];
  };

  /** sets the program counter
   * @param pc {Number} the new value to set
   */
  yasp.Emulator.prototype.writePC = function (pc) {
    if(debug) console.log("pc=" + pc);
    this.pc = pc;
  };

  /** gets the program counter
   * @returns {Number} the current value of the program counter
   */
  yasp.Emulator.prototype.readPC = function () {
    return this.pc;
  };

  /** writes one byte to the ram
   * @param o {Number} the position to write the byte to
   * @param v {Number} the byte to write
   * @returns {Number} 0 = success, 1 = o was out of bounds
   */
  yasp.Emulator.prototype.writeRAM = function (o, v) {
    if(o < 0 || o >= this.ram.length)
      return 1;
    this.ram[o] = v;

    if(debug) console.log("ram[" + o + "] = " + v);
    if(this.changeBreakpoints.ram === true) this.changeBreakpointData.ram.push(o);

    return 0;
  };

  /** writes one byte to the rom. This also clears the {@link yasp.Emulator#commandCache} for the affected bytes.
   * @param o {Number} the position to write the byte to
   * @param v {Number} the byte to write
   * @returns {Number} 0 = success, 1 = o was out of bounds
   * @see yasp.Emulator#clearCommandCache
   */
  yasp.Emulator.prototype.writeROM = function (o, v) {
    if(o < 0 || o >= this.rom.length)
      return 1;

    this.rom[o] = v;

    if(debug) console.log("rom[" + o + "] = " + v);
    if(this.changeBreakpoints.rom === true) this.changeBreakpointData.rom.push(o);

    this.clearCommandCache(o, 1);

    return 0;
  };

  /** checks if the given byte is the start of a command in ROM
   * @param pos {Number} the position in ROM to check
   * @returns {Boolean} true, if pos is the start of a command, otherwise false
   */
  yasp.Emulator.prototype.isCacheCommand = function (pos) {
    return (this.commandCache[pos] !== undefined && this.commandCache[pos] !== true);
  };

  /** clears the command cache for all bytes in ROM affected by the change of the len
   * bytes starting at pos. An affected byte is either within that range, or of any
   * command, which is partly within that range.
   * @param pos {Number} starting position to clear
   * @param len {Number} number of types to clear
   */
  yasp.Emulator.prototype.clearCommandCache = function (pos, len) {
    var endPos = pos + len;
    var cmd = null;
    var firstCmdPos = pos;

    // backtrack to the first command before pos
    for (; firstCmdPos > 0; firstCmdPos--) {
      if (this.isCacheCommand(firstCmdPos)) {
        cmd = this.commandCache[firstCmdPos];
        break;
      }
    }

    // check if an command has been found and if yes, if it is affected
    if(cmd === null || firstCmdPos + (cmd.neededBytes - 1) < pos) {
      // case 1: found command is before the area to clear
      //     ----CMD--000000------
      // case 2: there was no command before pos
      //
      // => do nothing and keep pos as is
    } else {
      // case 3: found command is intersects with the area to clear
      //     ----CM000000---------
      // => set pos to the start of found command, since we're
      //    clearing part of its bytes

      pos = firstCmdPos;
      endPos = Math.max(endPos, pos + (cmd.neededBytes - 1));
    }

    // clear bytes and keep looking for commands within range.
    // If there is a command found, which starts in the range
    // but is longer than the range itself, the range is updated
    // accordingly.
    for(; pos < endPos; pos++) {
      if (this.isCacheCommand(pos)) {
        cmd = this.commandCache[pos];
        endPos = Math.max(endPos, pos + cmd.neededBytes);
      }

      this.commandCache[pos] = undefined;
    }
  };

  /** sets the state of a pin
   * @param p {Number} pin-number
   * @param s {Number} new state to set
   * @param [outside=false] {boolean} true if the pin was set by external hardware
   * @returns {Number} 0 if success, 1 if the pin does not exist, 2 if the pin is an input pin, 3 if s is invalid
   * @fires yasp.Emulator~IO_CHANGED
   */
  yasp.Emulator.prototype.setIO = function (p, s, outside) {
    var pin = this.iobank.getPin(p);

    if(typeof s !== "number" || (s < 0 || s > 255))
      return 3;
    if(pin === undefined)
      return 1;
    if(pin.mode === "in" && outside !== true)
      return 2;

    if(s === 1 && pin.mode === "in") {
      this.scheduleInterrupt(p);
    }

    if(debug) console.log("p" + p + "=" + s + " (outside: " + (!!outside) + ")");

    pin.setState(s, outside);

    return 0;
  };

  /** gets the state of a pin
   * @returns {?number} the pins state, or null if the pin does not exist
   */
  yasp.Emulator.prototype.getIO = function (p) {
    var pin = this.iobank.getPin(p);

    if(pin === undefined)
      return null;

    return pin.state;
  };

  /** reads one byte from the ram
   * @param o {Number} the offset to read from
   * @returns {Number} the bytes value, or 0 if o was out of bounds
   */
  yasp.Emulator.prototype.readRAM = function (o) {
    if(o < 0 || o >= this.ram.length)
      return 0;
    return this.ram[o];
  };

  /** reads one byte from the rom
   * @param o {Number} the offset to read from
   * @returns {Number} the bytes value, or 0 if o was out of bounds
   */
  yasp.Emulator.prototype.readROM = function (o) {
    if(o < 0 || o >= this.rom.length)
      return 0;
    return this.rom[o];
  };

  /** schedules an interrupt for the next tick
   * @param i {Number} the interrupt to schedule
   * @returns {boolean} true if the interrupt is going to served, false otherwise (depends on the active interrupt-mask)
   */
  yasp.Emulator.prototype.scheduleInterrupt = function (i) {
    if(this.interruptMask[i] === false)
      return false;
    if(debug) console.log("interrupt triggered: " + i);
    this.interruptToServe = i;
    this.setTickWrapperTimeout(0);
    return true;
  };

  /** returns to byte to jump to for a given interrupt
   * @param i {Number} the interrupt-number (pin-number)
   * @private
   */
  yasp.Emulator.prototype.getInterruptAddress = function (i) {
    i = 0x100 + (i * 2); // interrupt table starts at 0x100, each entry is 2 bytes long
    return yasp.bitutils.wordFromBytes(this.rom[i], this.rom[i + 1]);
  };

  /** sets the interrupt-mask
   * @param mask {Number} mask to set (one byte)
   * @see yasp.Emulator#interruptMask
   */
  yasp.Emulator.prototype.setInterruptMask = function (mask) {
    for (var i = 0; i < 8; i++) {
      this.interruptMask[i] = (mask & 1) === 1;
      mask = mask >> 1;
    }
  };

  /** halts the execution for a given time.
   * @param ticks {Number} number of ticks to wait (1 tick ~= 0.015ms; 60000 ~= 900ms)
   */
  yasp.Emulator.prototype.wait = function (ticks) {
    var ms = ticks * 0.015;
    if(debug) console.log("wait " + ms + "ms");
    this.waitTime = ms;
  };

  /** set the timeout for the next tickWrapper-call. This also cancels all running timeouts.
   * @param delay {Number} milliseconds to wait before the next tick
   * @private
   * @see yasp.Emulator#tickTimeout
   * @see yasp.Emulator#tickDelay
   * @see yasp.Emulator#tickWrapper
   */
  yasp.Emulator.prototype.setTickWrapperTimeout = function (delay) {
    if(this.forceStep === true)
      return;
    if(delay === undefined)
      delay = this.tickDelay;
    clearTimeout(this.tickTimeout);
    this.tickTimeout = setTimeout(this.tickWrapper.bind(this), delay);
  };

  /**
   * @returns {Number} number of ticks executed, since the emulator has been created. This also takes sleeps into account.
   */
  yasp.Emulator.prototype.getTicks = function () {
    return this.ticks;
  };

  yasp.Emulator.prototype.getTimePerTick = function () {
    return this.tickDelay / this.ticksPerTick;
  };

  /** helper function, invoked by setTimeout, calls {@link yasp.Emulator#tick}. Do not call manually.
   * @private
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/emulator.md#ticks|Additional documentation}
   * @see yasp.Emulator#ticksPerTick
   * @see yasp.Emulator#running
   * @see yasp.Emulator#break
   * @see yasp.Emulator#continue
   */
  yasp.Emulator.prototype.tickWrapper = function () {

    for(var jj = 0; jj < this.ticksPerTick; jj++) {

      if(this.running === false) {
        break;
      }

      if(this.globalBreakpoints.length !== 0) {
        for (var i = 0; i < this.globalBreakpoints.length; i++) {
          var condition = this.globalBreakpoints[i];
          var shouldBreak = this.checkBreakpointCondition(condition);

          if(shouldBreak) {
            this.resetChangeBreakpointData();
            this.break("breakpoint");
            break;
          }
        }
      }

      if(this.breakpoints[this.pc] === true && this.skipBreakpoint === false) {
        var condition = this.breakpointConditions[this.pc];
        var shouldBreak = this.checkBreakpointCondition(condition);

        if(shouldBreak) {
          this.resetChangeBreakpointData();
          this.break("breakpoint");
          break;
        }
      }

      this.resetChangeBreakpointData();

      this.skipBreakpoint = false;

      if(typeof this.running === "number") {
        this.running--;

        if(this.running < 0) {
          this.break("count");
          break;
        }
      }

      if(this.waitTime !== 0) {
        if(this.running === 0 || this.forceStep === true) { // ignore WAIT/PAUSE when stepping or running in test-suite
          this.running = 1;
          this.setTickWrapperTimeout();
        } else {
          // don't set the normal timeout but wait for the desired time
          this.setTickWrapperTimeout(this.waitTime);
        }

        // fix the number of ticks executed. This has to be accurate since PWM relies on the tick-count
        // to calculate for how long a pin was high or low.
        var ticksSkipped = (this.waitTime / this.getTimePerTick());
        this.ticks += ticksSkipped;
        this.waitTime = 0;
        return;
      }

      // interrupts
      if(this.interruptToServe !== -1) {
        if(debug) console.log("interrupt jumped: " + this.interruptToServe);
        this.pushWord(this.pc); // for RETI
        this.pc = this.getInterruptAddress(this.interruptToServe);
        this.interruptToServe = -1;
        break;
      }

      this.tick();
    }

    this.setTickWrapperTimeout();
  };

  yasp.Emulator.prototype.resetChangeBreakpointData = function () {
    if(this.changeBreakpoints.rbyte === true)
      this.changeBreakpointData.rbyte.length = 0;
    if(this.changeBreakpoints.rword === true)
      this.changeBreakpointData.rword.length = 0;
    if(this.changeBreakpoints.zflag === true)
      this.changeBreakpointData.zflag.length = 0;
    if(this.changeBreakpoints.cflag === true)
      this.changeBreakpointData.cflag.length = 0;
    if(this.changeBreakpoints.io === true)
      this.changeBreakpointData.io.length = 0;
    if(this.changeBreakpoints.ram === true)
      this.changeBreakpointData.ram.length = 0;
    if(this.changeBreakpoints.rom === true)
      this.changeBreakpointData.rom.length = 0;
  };

  /** check if a conditional breakpoint should be triggered with the current state
   * @param cond {object} optimized breakpoint condition, see {@link yasp.Emulator#setBreakpoints}
   * @private
   */
  yasp.Emulator.prototype.checkBreakpointCondition = function (cond) {
    var actualBoolValue = false;
    var actualNumValue = 0;
    var actualUintArrayValue = null;

    // no checking needed if there is no condition..
    if(cond === null)
      return true;

    // ========================
    // check for changed values
    if(cond.isChange) {
      if(cond.isByteRegister)
        return this.changeBreakpointData.rbyte.indexOf(cond.registerNumber) !== -1;
      else if(cond.isWordRegister)
        return this.changeBreakpointData.rword.indexOf(cond.registerNumber) !== -1;
      else if(cond.isCarryFlag)
        return this.changeBreakpointData.cflag.length !== 0;
      else if(cond.isZeroFlag)
        return this.changeBreakpointData.zflag.length !== 0;
      else if(cond.isIO)
        return this.changeBreakpointData.io.indexOf(cond.ioPinNumber) !== -1;
      else if(cond.isRamOffset)
        return this.changeBreakpointData.ram.indexOf(cond.memoryOffset) !== -1;
      else if(cond.isRomOffset)
        return this.changeBreakpointData.rom.indexOf(cond.memoryOffset) !== -1;

      return false;
    }

    // ===================
    // read current values

    if(cond.isByteRegister) {
      actualNumValue = this.readByteRegister(cond.registerNumber);
    } else if(cond.isWordRegister) {
      actualNumValue = this.readWordRegister(cond.registerNumber);
    } else if(cond.isCarryFlag) {
      actualBoolValue = this.isCarryFlagSet();
    } else if(cond.isZeroFlag) {
      actualBoolValue = this.isZeroFlagSet();
    } else if(cond.isIO) {
      actualNumValue = this.getIO(cond.ioPinNumber);
    } else if(cond.isRamOffset) {
      if(cond.isUintArrayValue) {
        actualUintArrayValue = this.ram.subarray(cond.memoryOffset, cond.memoryOffset + cond.uintArrayValue.length);
      } else {
        actualNumValue = this.readRAM(cond.memoryOffset);
      }
    } else if(cond.isRomOffset) {
      if(cond.isUintArrayValue) {
        actualUintArrayValue = this.rom.subarray(cond.memoryOffset, cond.memoryOffset + cond.uintArrayValue.length);
      } else {
        actualNumValue = this.readROM(cond.memoryOffset);
      }
    }

    // ============
    // check values

    if(cond.isEquals) {
      if(cond.isNumValue)
        return (actualNumValue  === cond.numValue);
      else if(cond.isBoolValue)
        return (actualBoolValue === cond.boolValue);
      else if(cond.isUintArrayValue)
        return actualUintArrayValue.equals(cond.uintArrayValue);

    } else if(cond.isNotEquals) {
      if(cond.isNumValue)
        return (actualNumValue  !== cond.numValue);
      else if(cond.isBoolValue)
        return (actualBoolValue !== cond.boolValue);
      else if(cond.isUintArrayValue)
        return !actualUintArrayValue.equals(cond.uintArrayValue);

    } else if(cond.isSmaller && cond.isNumValue) {
      return (actualNumValue < cond.numValue);
    } else if(cond.isBigger && cond.isNumValue) {
      return (actualNumValue > cond.numValue);
    } else if(cond.isSmallerEquals && cond.isNumValue) {
      return (actualNumValue <= cond.numValue);
    } else if(cond.isBiggerEquals && cond.isNumValue) {
      return (actualNumValue >= cond.numValue);
    }

    console.log("Invalid condition: " + JSON.stringify(cond));
    return false;
  };

  /** Executes instructions. Do not call manually.
   * @private
   * @see {@link https://github.com/yasp/yasp/blob/master/doc/emulator/emulator.md#ticks|Additional documentation}
   */
  yasp.Emulator.prototype.tick = function () {
    this.ticks++;

    // fetch instruction
    if(this.commandCache[this.pc] === undefined) {
      var cmd = yasp.disasm.getCommand(this.rom, this.pc);

      if(cmd !== null) {
        this.commandCache[this.pc] = cmd;

        var lastByte = this.pc + cmd.neededBytes;
        for (var i = this.pc + 1; i < lastByte; i++) {
          this.commandCache[i] = true;
        }
      }
    }

    var ccmd = this.commandCache[this.pc];
    if(ccmd === undefined) {
      this.break("invalid_instr");
      return;
    }
    var cmd = ccmd.cmd;

    // increment the program counter by the length of the instruction in the ROM
    this.writePC(this.pc + ccmd.neededBytes);

    if(debug) console.log(ccmd.str);

    // the loaded value of p0 is needed by the zero-flag-checking
    var p0 = ccmd.params[0];

    // load the values of register and pin-parameters. This is an unrolled loop, limited to two parameters for speed
    // reasons. In addition it is possible to skip loading of certain values by adding valueNeeded=false to the
    // instruction file. Refer to the instruction documentation for further details:
    //                   https://github.com/yasp/yasp/blob/master/doc/instructions.md
    if(ccmd.params.length === 0) {
      cmd.exec.call(this);
    } else {
      if(p0.valueNeeded) {
        // isRByte, isRWord and isPin are added by yasp.disasm.getCommand
        // checking the booleans is way faster than comparing the type-string of each instruction.
        if(p0.isRByte === true)
          p0.value = this.readByteRegister(p0.address);
        else if(p0.isRWord === true)
          p0.value = this.readWordRegister(p0.address);
        else if(p0.isPin === true)
          p0.value = this.getIO(p0.address);
      }

      if(ccmd.params.length === 1) {
        cmd.exec.call(this, p0);
      } else if(ccmd.params.length === 2) {
        var p1 = ccmd.params[1];

        if(p1.valueNeeded) {
          if(p1.isRByte === true)
            p1.value = this.readByteRegister(p1.address);
          else if(p1.isRWord === true)
            p1.value = this.readWordRegister(p1.address);
          else if(p1.isPin === true)
            p1.value = this.getIO(p1.address);
        }

        cmd.exec.call(this, p0, p1);
      } else {
        throw "Instructions with more than 2 parameters are not supported.";
      }
    }

    // check the zero-flag if specified in the instruction-file
    if(cmd.checkFlags !== undefined && p0 !== undefined) {
      var newVal;

      if(p0.isRByte === true)
        newVal = this.readByteRegister(p0.address);
      else if(p0.isRWord === true)
        newVal = this.readWordRegister(p0.address);

      var z = null;

      if(cmd.checkFlags.z !== undefined) {
        z = (newVal === 0);
      }

      this.writeFlags(null, z);
    }
  };
})();