// Generated by CoffeeScript 1.9.3

import { CPU, Bus, InstructionBased, IOBusConnected, SavesState, Interruptable } from "../devices";

///////////////////////////////////////////////////////////////////////////////
/// @file Z80.js
///
/// @brief Emulator for the Zilog Z80 microprocessor
///
/// @author Matthew Howell
///
/// @remarks
///  This module is a simple, straightforward instruction interpreter.
///   There is no fancy dynamic recompilation or cycle-accurate emulation.
///   The author believes that this should be sufficient for any emulator that
///   would be feasible to write in JavaScript anyway.
///  The code and the comments in this file assume that the reader is familiar
///   with the Z80 architecture. If you're not, here are some references I use:
///  http://clrhome.org/table/ - Z80 instruction set tables
///  http://www.zilog.com/docs/z80/um0080.pdf - The official manual
///  http://www.myquest.nl/z80undocumented/z80-documented-v0.91.pdf
///   - The Undocumented Z80, Documented
///
/// @copyright (c) 2013 Matthew Howell
///  This code is released under the MIT license,
///  a copy of which is available in the associated README.md file,
///  or at http://opensource.org/licenses/MIT
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
/// We'll begin with the object constructor and the public API functions.
///////////////////////////////////////////////////////////////////////////////
function FastZ80(coreParameter)
{
   // Obviously we'll be needing the core object's functions again.
   const core = coreParameter;
   
   // The argument to this constructor should be an object containing 4 functions:
   // mem_read(address) should return the byte at the given memory address,
   // mem_write(address, value) should write the given value to the given memory address,
   // io_read(port) should read a return a byte read from the given I/O port,
   // io_write(port, value) should write the given byte to the given I/O port.
   // If any of those functions is missing, this module cannot run.
   if (!core || (typeof core.mem_read !== "function") || (typeof core.mem_write !== "function") ||
                (typeof core.io_read !== "function")  || (typeof core.io_write !== "function"))
      throw("Z80: Core object is missing required functions.");
   
   // All right, let's initialize the registers.
   // First, the standard 8080 registers.
   let a = 0x00;
   let b = 0x00;
   let c = 0x00;
   let d = 0x00;
   let e = 0x00;
   let h = 0x00;
   let l = 0x00;
   // Now the special Z80 copies of the 8080 registers
   //  (the ones used for the SWAP instruction and such).
   let a_prime = 0x00;
   let b_prime = 0x00;
   let c_prime = 0x00;
   let d_prime = 0x00;
   let e_prime = 0x00;
   let h_prime = 0x00;
   let l_prime = 0x00;
   // And now the Z80 index registers.
   let ix = 0x0000;
   let iy = 0x0000;
   // Then the "utility" registers: the interrupt vector,
   //  the memory refresh, the stack pointer, and the program counter.
   let i = 0x00;
   let r = 0x00;
   let sp = 0xdff0;
   let pc = 0x0000;
   // We don't keep an F register for the flags,
   //  because most of the time we're only accessing a single flag,
   //  so we optimize for that case and use utility functions
   //  for the rarer occasions when we need to access the whole register.
   let flags = {S:0, Z:0, Y:0, H:0, X:0, P:0, N:0, C:0};
   let flags_prime = {S:0, Z:0, Y:0, H:0, X:0, P:0, N:0, C:0};
   // And finally we have the interrupt mode and flip-flop registers.
   let imode = 0;
   let iff1 = 0;
   let iff2 = 0;
   
   // These are all specific to this implementation, not Z80 features.
   // Keep track of whether we've had a HALT instruction called.
   let halted = false;
   // EI and DI wait one instruction before they take effect;
   //  these flags tell us when we're in that wait state.
   let do_delayed_di = false;
   let do_delayed_ei = false;
   // This tracks the number of cycles spent in a single instruction run,
   //  including processing any prefixes and handling interrupts.
   let cycle_counter = 0;
   
   function getState():Z80State {
      return {
         PC:pc,
         SP:sp,
         IX:ix,
         IY:iy,
         AF:(a<<8)+get_flags_register(),
         BC:(b<<8)+c,
         DE:(d<<8)+e,
         HL:(h<<8)+l,
         AF_:(a_prime<<8)+get_flags_prime(),
         BC_:(b_prime<<8)+c_prime,
         DE_:(d_prime<<8)+e_prime,
         HL_:(h_prime<<8)+l_prime,
         IR:(i<<8)+r,
         im            : imode,
         iff1          : iff1,
         iff2          : iff2,
         halted        : halted,
         do_delayed_di : do_delayed_di,
         do_delayed_ei : do_delayed_ei,
         cycle_counter : cycle_counter
      };   
   }

   function setState(state:Z80State) {
    pc = state.PC;
    sp = state.SP;
    ix = state.IX;
    iy = state.IY;
    a = (state.AF >> 8) & 0xff;
    set_flags_register(state.AF);
    b = (state.BC >> 8) & 0xff;
    c = state.BC & 0xff;
    d = (state.DE >> 8) & 0xff;
    e = state.DE & 0xff;
    h = (state.HL >> 8) & 0xff;
    l = state.HL & 0xff;
    a_prime = (state.AF_ >> 8) & 0xff;
    set_flags_prime(state.AF_);
    b_prime = (state.BC_ >> 8) & 0xff;
    c_prime = state.BC_ & 0xff;
    d_prime = (state.DE_ >> 8) & 0xff;
    e_prime = state.DE_ & 0xff;
    h_prime = (state.HL_ >> 8) & 0xff;
    l_prime = state.HL_ & 0xff;
    i = (state.IR >> 8) & 0xff;
    r = state.IR & 0xff;
    imode = state.im;
    iff1 = state.iff1;
    iff2 = state.iff2;
    halted = state.halted;
    do_delayed_di = state.do_delayed_di;
    do_delayed_ei = state.do_delayed_ei;
    cycle_counter = state.cycle_counter;
   }

///////////////////////////////////////////////////////////////////////////////
/// @public reset
///
/// @brief Re-initialize the processor as if a reset or power on had occured
///////////////////////////////////////////////////////////////////////////////
let reset = function()
{
   // These registers are the ones that have predictable states
   //  immediately following a power-on or a reset.
   // The others are left alone, because their states are unpredictable.
   sp = 0xdff0;
   pc = 0x0000;
   a = 0x00;
   r = 0x00;
   set_flags_register(0);
   // Start up with interrupts disabled.
   imode = 0;
   iff1 = 0;
   iff2 = 0;
   // Don't start halted or in a delayed DI or EI.
   halted = false;
   do_delayed_di = false;
   do_delayed_ei = false;
   // Obviously we've not used any cycles yet.
   cycle_counter = 0;
};

///////////////////////////////////////////////////////////////////////////////
/// @public run_instruction
///
/// @brief Runs a single instruction
///
/// @return The number of T cycles the instruction took to run,
///          plus any time that went into handling interrupts that fired
///          while this instruction was executing
///////////////////////////////////////////////////////////////////////////////
let run_instruction = function()
{
   if (!halted)
   {
      // If the previous instruction was a DI or an EI,
      //  we'll need to disable or enable interrupts
      //  after whatever instruction we're about to run is finished.
      var doing_delayed_di = false, doing_delayed_ei = false;
      if (do_delayed_di)
      {
         do_delayed_di = false;
         doing_delayed_di = true;
      }
      else if (do_delayed_ei)
      {
         do_delayed_ei = false;
         doing_delayed_ei = true;
      }

      // R is incremented at the start of every instruction cycle,
      //  before the instruction actually runs.
      // The high bit of R is not affected by this increment,
      //  it can only be changed using the LD R, A instruction.
      r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);
      
      // Read the byte at the PC and run the instruction it encodes.
      var opcode = core.mem_read(pc);
      decode_instruction(opcode);
      pc = (pc + 1) & 0xffff;
      
      // Actually do the delayed interrupt disable/enable if we have one.
      if (doing_delayed_di)
      {
         iff1 = 0;
         iff2 = 0;
         //console.log("DI",pc);
      }
      else if (doing_delayed_ei)
      {
         iff1 = 1;
         iff2 = 1;
         //console.log("EI",pc);
      }
      
      // And finally clear out the cycle counter for the next instruction
      //  before returning it to the emulator core.
      var retval = cycle_counter;
      cycle_counter = 0;
      return retval;
   }
   else
   {
      // While we're halted, claim that we spent a cycle doing nothing,
      //  so that the rest of the emulator can still proceed.
      return 1;
   }
};

///////////////////////////////////////////////////////////////////////////////
/// @public interrupt
///
/// @brief Simulates pulsing the processor's INT (or NMI) pin
///
/// @param non_maskable - true if this is a non-maskable interrupt
/// @param data - the value to be placed on the data bus, if needed
///////////////////////////////////////////////////////////////////////////////
let interrupt = function(non_maskable:boolean, data:number) : boolean
{
   //console.log(non_maskable, data, iff1, iff2, do_delayed_ei, do_delayed_di);
   if (non_maskable)
   {
      // The high bit of R is not affected by this increment,
      //  it can only be changed using the LD R, A instruction.
      r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);
      // Non-maskable interrupts are always handled the same way;
      //  clear IFF1 and then do a CALL 0x0066.
      // Also, all interrupts reset the HALT state.
      halted = false;
      iff2 = iff1;
      iff1 = 0;
      push_word(pc);
      pc = 0x66;
      cycle_counter += 11;
      return true;
   }
   else if (iff1)
   {
      // The high bit of R is not affected by this increment,
      //  it can only be changed using the LD R, A instruction.
      r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);
   
      halted = false;
      iff1 = 0;
      iff2 = 0;
   
      if (imode === 0)
      {
         // In the 8080-compatible interrupt mode,
         //  decode the content of the data bus as an instruction and run it.
         pc = (pc - 1) & 0xffff; //SEH: so do_reset() pushes right value
         decode_instruction(data);
         pc = (pc + 1) & 0xffff; //SEH: so do_reset() pushes right value
         cycle_counter += 2;
      }
      else if (imode === 1)
      {
         // Mode 1 is always just RST 0x38.
         push_word(pc);
         pc = 0x38;
         cycle_counter += 13;
      }
      else if (imode === 2)
      {
         // Mode 2 uses the value on the data bus as in index
         //  into the vector table pointer to by the I register.
         push_word(pc);
         // The Z80 manual says that this address must be 2-byte aligned,
         //  but it doesn't appear that this is actually the case on the hardware,
         //  so we don't attempt to enforce that here.
         var vector_address = ((i << 8) | data);
         pc = core.mem_read(vector_address) | 
                   (core.mem_read((vector_address + 1) & 0xffff) << 8);
         
         cycle_counter += 19;
      }
      //console.log(imode,data,pc);
      return true;
   }
};

///////////////////////////////////////////////////////////////////////////////
/// The public API functions end here.
///
/// What begins here are just general utility functions, used variously.
///////////////////////////////////////////////////////////////////////////////
let decode_instruction = function(opcode)
{
   // The register-to-register loads and ALU instructions
   //  are all so uniform that we can decode them directly
   //  instead of going into the instruction array for them.
   // This function gets the operand for all of these instructions.
   var get_operand = function(opcode)
   {
      return ((opcode & 0x07) === 0) ? b :
             ((opcode & 0x07) === 1) ? c :
             ((opcode & 0x07) === 2) ? d :
             ((opcode & 0x07) === 3) ? e :
             ((opcode & 0x07) === 4) ? h :
             ((opcode & 0x07) === 5) ? l :
             ((opcode & 0x07) === 6) ? core.mem_read(l | (h << 8)) : a;
   };

   // Handle HALT right up front, because it fouls up our LD decoding
   //  by falling where LD (HL), (HL) ought to be.
   if (opcode === 0x76)
   {
      halted = true;
   }
   else if ((opcode >= 0x40) && (opcode < 0x80))
   {
      // This entire range is all 8-bit register loads.
      // Get the operand and assign it to the correct destination.
      var operand = get_operand(opcode);
         
      if (((opcode & 0x38) >>> 3) === 0)
         b = operand;
      else if (((opcode & 0x38) >>> 3) === 1)
         c = operand;
      else if (((opcode & 0x38) >>> 3) === 2)
         d = operand;
      else if (((opcode & 0x38) >>> 3) === 3)
         e = operand;
      else if (((opcode & 0x38) >>> 3) === 4)
         h = operand;
      else if (((opcode & 0x38) >>> 3) === 5)
         l = operand;
      else if (((opcode & 0x38) >>> 3) === 6)
         core.mem_write(l | (h << 8), operand);
      else if (((opcode & 0x38) >>> 3) === 7)
         a = operand;
   }
   else if ((opcode >= 0x80) && (opcode < 0xc0))
   {
      // These are the 8-bit register ALU instructions.
      // We'll get the operand and then use this "jump table"
      //  to call the correct utility function for the instruction.
      var operand = get_operand(opcode),
          op_array = [do_add, do_adc, do_sub, do_sbc,
                      do_and, do_xor, do_or, do_cp];
      
      op_array[(opcode & 0x38) >>> 3]( operand);
   }
   else
   {
      // This is one of the less formulaic instructions;
      //  we'll get the specific function for it from our array.
      var func = instructions[opcode];
      func();
   }
   
   // Update the cycle counter with however many cycles
   //  the base instruction took.
   // If this was a prefixed instruction, then
   //  the prefix handler has added its extra cycles already.
   cycle_counter += cycle_counts[opcode];
};

let get_signed_offset_byte = function(value)
{
   // This function requires some explanation.
   // We just use JavaScript Number variables for our registers,
   //  not like a typed array or anything.
   // That means that, when we have a byte value that's supposed
   //  to represent a signed offset, the value we actually see
   //  isn't signed at all, it's just a small integer.
   // So, this function converts that byte into something JavaScript
   //  will recognize as signed, so we can easily do arithmetic with it.
   // First, we clamp the value to a single byte, just in case.
   value &= 0xff;
   // We don't have to do anything if the value is positive.
   if (value & 0x80)
   {
      // But if the value is negative, we need to manually un-two's-compliment it.
      // I'm going to assume you can figure out what I meant by that,
      //  because I don't know how else to explain it.
      // We could also just do value |= 0xffffff00, but I prefer
      //  not caring how many bits are in the integer representation
      //  of a JavaScript number in the currently running browser.
      value = -((0xff & ~value) + 1);
   }
   return value;
};

let get_flags_register = function()
{
   // We need the whole F register for some reason.
   //  probably a PUSH AF instruction,
   //  so make the F register out of our separate flags.
   return (flags.S << 7) |
          (flags.Z << 6) |
          (flags.Y << 5) |
          (flags.H << 4) |
          (flags.X << 3) |
          (flags.P << 2) |
          (flags.N << 1) |
          (flags.C);
};

let get_flags_prime = function()
{
   // This is the same as the above for the F' register.
   return (flags_prime.S << 7) |
          (flags_prime.Z << 6) |
          (flags_prime.Y << 5) |
          (flags_prime.H << 4) |
          (flags_prime.X << 3) |
          (flags_prime.P << 2) |
          (flags_prime.N << 1) |
          (flags_prime.C);
};

let set_flags_register = function(operand)
{
   // We need to set the F register, probably for a POP AF,
   //  so break out the given value into our separate flags.
   flags.S = (operand & 0x80) >>> 7;
   flags.Z = (operand & 0x40) >>> 6;
   flags.Y = (operand & 0x20) >>> 5;
   flags.H = (operand & 0x10) >>> 4;
   flags.X = (operand & 0x08) >>> 3;
   flags.P = (operand & 0x04) >>> 2;
   flags.N = (operand & 0x02) >>> 1;
   flags.C = (operand & 0x01);
};

let set_flags_prime = function(operand)
{
   // Again, this is the same as the above for F'.
   flags_prime.S = (operand & 0x80) >>> 7;
   flags_prime.Z = (operand & 0x40) >>> 6;
   flags_prime.Y = (operand & 0x20) >>> 5;
   flags_prime.H = (operand & 0x10) >>> 4;
   flags_prime.X = (operand & 0x08) >>> 3;
   flags_prime.P = (operand & 0x04) >>> 2;
   flags_prime.N = (operand & 0x02) >>> 1;
   flags_prime.C = (operand & 0x01);
};

let update_xy_flags = function(result)
{
   // Most of the time, the undocumented flags
   //  (sometimes called X and Y, or 3 and 5),
   //  take their values from the corresponding bits
   //  of the result of the instruction,
   //  or from some other related value.
   // This is a utility function to set those flags based on those bits.
   flags.Y = (result & 0x20) >>> 5;
   flags.X = (result & 0x08) >>> 3;
};

let get_parity = function(value)
{
   // We could try to actually calculate the parity every time,
   //  but why calculate what you can pre-calculate?
   var parity_bits = [
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 
      1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
   ];
   return parity_bits[value];
};

let push_word = function(operand)
{
   // Pretty obvious what this function does; given a 16-bit value,
   //  decrement the stack pointer, write the high byte to the new
   //  stack pointer location, then repeat for the low byte.
   sp = (sp - 1) & 0xffff;
   core.mem_write(sp, (operand & 0xff00) >>> 8);
   sp = (sp - 1) & 0xffff;
   core.mem_write(sp, operand & 0x00ff);
};

let pop_word = function()
{
   // Again, not complicated; read a byte off the top of the stack,
   //  increment the stack pointer, rinse and repeat.
   var retval = core.mem_read(sp) & 0xff;
   sp = (sp + 1) & 0xffff;
   retval |= core.mem_read(sp) << 8;
   sp = (sp + 1) & 0xffff;
   return retval;
};

///////////////////////////////////////////////////////////////////////////////
/// Now, the way most instructions work in this emulator is that they set up
///  their operands according to their addressing mode, and then they call a
///  utility function that handles all variations of that instruction.
/// Those utility functions begin here.
///////////////////////////////////////////////////////////////////////////////
let do_conditional_absolute_jump = function(condition)
{
   // This function implements the JP [condition],nn instructions.
   if (condition)
   {
      // We're taking this jump, so write the new PC,
      //  and then decrement the thing we just wrote,
      //  because the instruction decoder increments the PC
      //  unconditionally at the end of every instruction
      //  and we need to counteract that so we end up at the jump target.
      pc =  core.mem_read((pc + 1) & 0xffff) |
                (core.mem_read((pc + 2) & 0xffff) << 8);
      pc = (pc - 1) & 0xffff;
   }
   else
   {
      // We're not taking this jump, just move the PC past the operand.
      pc = (pc + 2) & 0xffff;
   }
};

let do_conditional_relative_jump = function(condition)
{
   // This function implements the JR [condition],n instructions.
   if (condition)
   {
      // We need a few more cycles to actually take the jump.
      cycle_counter += 5;
      // Calculate the offset specified by our operand.
      var offset = get_signed_offset_byte(core.mem_read((pc + 1) & 0xffff));
      // Add the offset to the PC, also skipping past this instruction.
      pc = (pc + offset + 1) & 0xffff;
   }
   else
   {
      // No jump happening, just skip the operand.
      pc = (pc + 1) & 0xffff;
   }
};

let do_conditional_call = function(condition)
{
   // This function is the CALL [condition],nn instructions.
   // If you've seen the previous functions, you know this drill.
   if (condition)
   {
      cycle_counter += 7;
      push_word((pc + 3) & 0xffff);
      pc =  core.mem_read((pc + 1) & 0xffff) |
                (core.mem_read((pc + 2) & 0xffff) << 8);
      pc = (pc - 1) & 0xffff;
   }
   else
   {
      pc = (pc + 2) & 0xffff;
   }
};

let do_conditional_return = function(condition)
{
   if (condition)
   {
      cycle_counter += 6;
      pc = (pop_word() - 1) & 0xffff;
   }
};

let do_reset = function(address)
{
   // The RST [address] instructions go through here.
   push_word((pc + 1) & 0xffff);
   pc = (address - 1) & 0xffff;
};

let do_add = function(operand)
{
   // This is the ADD A, [operand] instructions.
   // We'll do the literal addition, which includes any overflow,
   //  so that we can more easily figure out whether we had
   //  an overflow or a carry and set the flags accordingly.
   var result = a + operand;
   
   // The great majority of the work for the arithmetic instructions
   //  turns out to be setting the flags rather than the actual operation.
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = !(result & 0xff) ? 1 : 0;
   flags.H = (((operand & 0x0f) + (a & 0x0f)) & 0x10) ? 1 : 0;
   // An overflow has happened if the sign bits of the accumulator and the operand
   //  don't match the sign bit of the result value.
   flags.P = ((a & 0x80) === (operand & 0x80)) && ((a & 0x80) !== (result & 0x80)) ? 1 : 0;
   flags.N = 0;
   flags.C = (result & 0x100) ? 1 : 0;
   
   a = result & 0xff;
   update_xy_flags(a);
};

let do_adc = function(operand)
{
   var result = a + operand + flags.C;
   
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = !(result & 0xff) ? 1 : 0;
   flags.H = (((operand & 0x0f) + (a & 0x0f) + flags.C) & 0x10) ? 1 : 0;
   flags.P = ((a & 0x80) === (operand & 0x80)) && ((a & 0x80) !== (result & 0x80)) ? 1 : 0;
   flags.N = 0;
   flags.C = (result & 0x100) ? 1 : 0;
   
   a = result & 0xff;
   update_xy_flags(a);
};

let do_sub = function(operand)
{
   var result = a - operand;
   
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = !(result & 0xff) ? 1 : 0;
   flags.H = (((a & 0x0f) - (operand & 0x0f)) & 0x10) ? 1 : 0;
   flags.P = ((a & 0x80) !== (operand & 0x80)) && ((a & 0x80) !== (result & 0x80)) ? 1 : 0;
   flags.N = 1;
   flags.C = (result & 0x100) ? 1 : 0;
   
   a = result & 0xff;
   update_xy_flags(a);
};

let do_sbc = function(operand)
{
   var result = a - operand - flags.C;
   
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = !(result & 0xff) ? 1 : 0;
   flags.H = (((a & 0x0f) - (operand & 0x0f) - flags.C) & 0x10) ? 1 : 0;
   flags.P = ((a & 0x80) !== (operand & 0x80)) && ((a & 0x80) !== (result & 0x80)) ? 1 : 0;
   flags.N = 1;
   flags.C = (result & 0x100) ? 1 : 0;
   
   a = result & 0xff;
   update_xy_flags(a);
};

let do_cp = function(operand)
{
   // A compare instruction is just a subtraction that doesn't save the value,
   //  so we implement it as... a subtraction that doesn't save the value.
   var temp = a;
   do_sub(operand);
   a = temp;
   // Since this instruction has no "result" value, the undocumented flags
   //  are set based on the operand instead.
   update_xy_flags(operand);
};

let do_and = function(operand)
{
   // The logic instructions are all pretty straightforward.
   a &= operand & 0xff;
   flags.S = (a & 0x80) ? 1 : 0;
   flags.Z = !a ? 1 : 0;
   flags.H = 1;
   flags.P = get_parity(a);
   flags.N = 0;
   flags.C = 0;
   update_xy_flags(a);
};

let do_or = function(operand)
{
   a = (operand | a) & 0xff;
   flags.S = (a & 0x80) ? 1 : 0;
   flags.Z = !a ? 1 : 0;
   flags.H = 0;
   flags.P = get_parity(a);
   flags.N = 0;
   flags.C = 0;
   update_xy_flags(a);
};

let do_xor = function(operand)
{
   a = (operand ^ a) & 0xff;
   flags.S = (a & 0x80) ? 1 : 0;
   flags.Z = !a ? 1 : 0;
   flags.H = 0;
   flags.P = get_parity(a);
   flags.N = 0;
   flags.C = 0;
   update_xy_flags(a);
};

let do_inc = function(operand)
{
   var result = operand + 1;
   
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = !(result & 0xff) ? 1 : 0;
   flags.H = ((operand & 0x0f) === 0x0f) ? 1 : 0;
   // It's a good deal easier to detect overflow for an increment/decrement.
   flags.P = (operand === 0x7f) ? 1 : 0;
   flags.N = 0;
   
   result &= 0xff;
   update_xy_flags(result);
   
   return result;
};

let do_dec = function(operand)
{
   var result = operand - 1;
   
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = !(result & 0xff) ? 1 : 0;
   flags.H = ((operand & 0x0f) === 0x00) ? 1 : 0;
   flags.P = (operand === 0x80) ? 1 : 0;
   flags.N = 1;
   
   result &= 0xff;
   update_xy_flags(result);
   
   return result;
};

let do_hl_add = function(operand)
{
   // The HL arithmetic instructions are the same as the A ones,
   //  just with twice as many bits happening.
   var hl = l | (h << 8), result = hl + operand;
   
   flags.N = 0;
   flags.C = (result & 0x10000) ? 1 : 0;
   flags.H = (((hl & 0x0fff) + (operand & 0x0fff)) & 0x1000) ? 1 : 0;
   
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;

   update_xy_flags(h);
};

let do_hl_adc = function(operand)
{
   operand += flags.C;
   var hl = l | (h << 8), result = hl + operand;
   
   flags.S = (result & 0x8000) ? 1 : 0;
   flags.Z = !(result & 0xffff) ? 1 : 0;
   flags.H = (((hl & 0x0fff) + (operand & 0x0fff)) & 0x1000) ? 1 : 0;
   flags.P = ((hl & 0x8000) === (operand & 0x8000)) && ((result & 0x8000) !== (hl & 0x8000)) ? 1 : 0;
   flags.N = 0;
   flags.C = (result & 0x10000) ? 1 : 0;
   
   l = result & 0xff;
   h = (result >>> 8) & 0xff;
   
   update_xy_flags(h);
};

let do_hl_sbc = function(operand)
{
   operand += flags.C;
   var hl = l | (h << 8), result = hl - operand;
   
   flags.S = (result & 0x8000) ? 1 : 0;
   flags.Z = !(result & 0xffff) ? 1 : 0;
   flags.H = (((hl & 0x0fff) - (operand & 0x0fff)) & 0x1000) ? 1 : 0;
   flags.P = ((hl & 0x8000) !== (operand & 0x8000)) && ((result & 0x8000) !== (hl & 0x8000)) ? 1 : 0;
   flags.N = 1;
   flags.C = (result & 0x10000) ? 1 : 0;
   
   l = result & 0xff;
   h = (result >>> 8) & 0xff;
   
   update_xy_flags(h);
};

let do_in = function(port)
{
   var result = core.io_read(port);
   
   flags.S = (result & 0x80) ? 1 : 0;
   flags.Z = result ? 0 : 1;
   flags.H = 0;
   flags.P = get_parity(result) ? 1 : 0;
   flags.N = 0;
   update_xy_flags(result);
   
   return result;
};

let do_neg = function()
{
   // This instruction is defined to not alter the register if it === 0x80.
   if (a !== 0x80)
   {
      // This is a signed operation, so convert A to a signed value.
      a = get_signed_offset_byte(a);
      
      a = (-a) & 0xff;
   }
   
   flags.S = (a & 0x80) ? 1 : 0;
   flags.Z = !a ? 1 : 0;
   flags.H = (((-a) & 0x0f) > 0) ? 1 : 0;
   flags.P = (a === 0x80) ? 1 : 0;
   flags.N = 1;
   flags.C = a ? 1 : 0;
   update_xy_flags(a);
};

let do_ldi = function()
{
   // Copy the value that we're supposed to copy.
   var read_value = core.mem_read(l | (h << 8));
   core.mem_write(e | (d << 8), read_value);
   
   // Increment DE and HL, and decrement BC.
   var result = (e | (d << 8)) + 1;
   e = result & 0xff;
   d = (result & 0xff00) >>> 8;
   result = (l | (h << 8)) + 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   result = (c | (b << 8)) - 1;
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
   
   flags.H = 0;
   flags.P = (c || b) ? 1 : 0;
   flags.N = 0;
   flags.Y = ((a + read_value) & 0x02) >>> 1;
   flags.X = ((a + read_value) & 0x08) >>> 3;
};

let do_cpi = function()
{
   var temp_carry = flags.C;
   var read_value = core.mem_read(l | (h << 8))
   do_cp(read_value);
   flags.C = temp_carry;
   flags.Y = ((a - read_value - flags.H) & 0x02) >>> 1;
   flags.X = ((a - read_value - flags.H) & 0x08) >>> 3;
   
   var result = (l | (h << 8)) + 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   result = (c | (b << 8)) - 1;
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
   
   flags.P = result ? 1 : 0;
};

let do_ini = function()
{
   b = do_dec(b);
   
   core.mem_write(l | (h << 8), core.io_read((b << 8) | c));
   
   var result = (l | (h << 8)) + 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;

   flags.N = 1;
};

let do_outi = function()
{
   core.io_write((b << 8) | c, core.mem_read(l | (h << 8)));
   
   var result = (l | (h << 8)) + 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   
   b = do_dec(b);
   flags.N = 1;
};

let do_ldd = function()
{
   flags.N = 0;
   flags.H = 0;
   
   var read_value = core.mem_read(l | (h << 8));
   core.mem_write(e | (d << 8), read_value);
   
   var result = (e | (d << 8)) - 1;
   e = result & 0xff;
   d = (result & 0xff00) >>> 8;
   result = (l | (h << 8)) - 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   result = (c | (b << 8)) - 1;
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
   
   flags.P = (c || b) ? 1 : 0;
   flags.Y = ((a + read_value) & 0x02) >>> 1;
   flags.X = ((a + read_value) & 0x08) >>> 3;
};

let do_cpd = function()
{
   var temp_carry = flags.C
   var read_value = core.mem_read(l | (h << 8))
   do_cp(read_value);
   flags.C = temp_carry;
   flags.Y = ((a - read_value - flags.H) & 0x02) >>> 1;
   flags.X = ((a - read_value - flags.H) & 0x08) >>> 3;
   
   var result = (l | (h << 8)) - 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   result = (c | (b << 8)) - 1;
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
   
   flags.P = result ? 1 : 0;
};

let do_ind = function()
{
   b = do_dec(b);
   
   core.mem_write(l | (h << 8), core.io_read((b << 8) | c));
   
   var result = (l | (h << 8)) - 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   
   flags.N = 1;
};

let do_outd = function()
{
   core.io_write((b << 8) | c, core.mem_read(l | (h << 8)));
   
   var result = (l | (h << 8)) - 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
   
   b = do_dec(b);
   flags.N = 1;
};

let do_rlc = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   flags.C = (operand & 0x80) >>> 7;
   operand = ((operand << 1) | flags.C) & 0xff;
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);
   
   return operand;
};

let do_rrc = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   flags.C = operand & 1;
   operand = ((operand >>> 1) & 0x7f) | (flags.C << 7);
   
   flags.Z = !(operand & 0xff) ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);
   
   return operand & 0xff;
};

let do_rl = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   var temp = flags.C;
   flags.C = (operand & 0x80) >>> 7;
   operand = ((operand << 1) | temp) & 0xff;
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);

   return operand;
};

let do_rr = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   var temp = flags.C;
   flags.C = operand & 1;
   operand = ((operand >>> 1) & 0x7f) | (temp << 7);
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);

   return operand;
};

let do_sla = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   flags.C = (operand & 0x80) >>> 7;
   operand = (operand << 1) & 0xff;
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);
   
   return operand;
};

let do_sra = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   flags.C = operand & 1;
   operand = ((operand >>> 1) & 0x7f) | (operand & 0x80);
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);
   
   return operand;
};

let do_sll = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   flags.C = (operand & 0x80) >>> 7;
   operand = ((operand << 1) & 0xff) | 1;
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = (operand & 0x80) ? 1 : 0;
   update_xy_flags(operand);
   
   return operand;
};

let do_srl = function(operand)
{
   flags.N = 0;
   flags.H = 0;
   
   flags.C = operand & 1;
   operand = (operand >>> 1) & 0x7f;
   
   flags.Z = !operand ? 1 : 0;
   flags.P = get_parity(operand);
   flags.S = 0;
   update_xy_flags(operand);
   
   return operand;
};

let do_ix_add = function(operand)
{
   flags.N = 0;
   
   var result = ix + operand;
   
   flags.C = (result & 0x10000) ? 1 : 0;
   flags.H = (((ix & 0xfff) + (operand & 0xfff)) & 0x1000) ? 1 : 0;
   update_xy_flags((result & 0xff00) >>> 8);
   
   ix = result & 0xffff;
};


///////////////////////////////////////////////////////////////////////////////
/// This table contains the implementations for the instructions that weren't
///  implemented directly in the decoder function (everything but the 8-bit
///  register loads and the accumulator ALU instructions, in other words).
/// Similar tables for the ED and DD/FD prefixes follow this one.
///////////////////////////////////////////////////////////////////////////////
let instructions = [];

// 0x00 : NOP
instructions[0x00] = function() { };
// 0x01 : LD BC, nn
instructions[0x01] = function()
{
   pc = (pc + 1) & 0xffff;
   c = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   b = core.mem_read(pc);
};
// 0x02 : LD (BC), A
instructions[0x02] = function()
{
   core.mem_write(c | (b << 8), a);
};
// 0x03 : INC BC
instructions[0x03] = function()
{
   var result = (c | (b << 8));
   result += 1;
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
};
// 0x04 : INC B
instructions[0x04] = function()
{
   b = do_inc(b);
};
// 0x05 : DEC B
instructions[0x05] = function()
{
   b = do_dec(b);
};
// 0x06 : LD B, n
instructions[0x06] = function()
{
   pc = (pc + 1) & 0xffff;
   b = core.mem_read(pc);
};
// 0x07 : RLCA
instructions[0x07] = function()
{
   // This instruction is implemented as a special case of the
   //  more general Z80-specific RLC instruction.
   // Specifially, RLCA is a version of RLC A that affects fewer flags.
   // The same applies to RRCA, RLA, and RRA.
   var temp_s = flags.S, temp_z = flags.Z, temp_p = flags.P;
   a = do_rlc(a);
   flags.S = temp_s;
   flags.Z = temp_z;
   flags.P = temp_p;
};
// 0x08 : EX AF, AF'
instructions[0x08] = function()
{
   var temp = a;
   a = a_prime;
   a_prime = temp;
   
   temp = get_flags_register();
   set_flags_register(get_flags_prime());
   set_flags_prime(temp);
};
// 0x09 : ADD HL, BC
instructions[0x09] = function()
{
   do_hl_add(c | (b << 8));
};
// 0x0a : LD A, (BC)
instructions[0x0a] = function()
{
   a = core.mem_read(c | (b << 8));
};
// 0x0b : DEC BC
instructions[0x0b] = function()
{
   var result = (c | (b << 8));
   result -= 1;
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
};
// 0x0c : INC C
instructions[0x0c] = function()
{
   c = do_inc(c);
};
// 0x0d : DEC C
instructions[0x0d] = function()
{
   c = do_dec(c);
};
// 0x0e : LD C, n
instructions[0x0e] = function()
{
   pc = (pc + 1) & 0xffff;
   c = core.mem_read(pc);
};
// 0x0f : RRCA
instructions[0x0f] = function()
{
   var temp_s = flags.S, temp_z = flags.Z, temp_p = flags.P;
   a = do_rrc(a);
   flags.S = temp_s;
   flags.Z = temp_z;
   flags.P = temp_p;
};
// 0x10 : DJNZ nn
instructions[0x10] = function()
{
   b = (b - 1) & 0xff;
   do_conditional_relative_jump(b !== 0);
};
// 0x11 : LD DE, nn
instructions[0x11] = function()
{
   pc = (pc + 1) & 0xffff;
   e = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   d = core.mem_read(pc);
};
// 0x12 : LD (DE), A
instructions[0x12] = function()
{
   core.mem_write(e | (d << 8), a);
};
// 0x13 : INC DE
instructions[0x13] = function()
{
   var result = (e | (d << 8));
   result += 1;
   e = result & 0xff;
   d = (result & 0xff00) >>> 8;
};
// 0x14 : INC D
instructions[0x14] = function()
{
   d = do_inc(d);
};
// 0x15 : DEC D
instructions[0x15] = function()
{
   d = do_dec(d);
};
// 0x16 : LD D, n
instructions[0x16] = function()
{
   pc = (pc + 1) & 0xffff;
   d = core.mem_read(pc);
};
// 0x17 : RLA
instructions[0x17] = function()
{
   var temp_s = flags.S, temp_z = flags.Z, temp_p = flags.P;
   a = do_rl(a);
   flags.S = temp_s;
   flags.Z = temp_z;
   flags.P = temp_p;
};
// 0x18 : JR n
instructions[0x18] = function()
{
   var offset = get_signed_offset_byte(core.mem_read((pc + 1) & 0xffff));
   pc = (pc + offset + 1) & 0xffff;
};
// 0x19 : ADD HL, DE
instructions[0x19] = function()
{
   do_hl_add(e | (d << 8));
};
// 0x1a : LD A, (DE)
instructions[0x1a] = function()
{
   a = core.mem_read(e | (d << 8));
};
// 0x1b : DEC DE
instructions[0x1b] = function()
{
   var result = (e | (d << 8));
   result -= 1;
   e = result & 0xff;
   d = (result & 0xff00) >>> 8;
};
// 0x1c : INC E
instructions[0x1c] = function()
{
   e = do_inc(e);
};
// 0x1d : DEC E
instructions[0x1d] = function()
{
   e = do_dec(e);
};
// 0x1e : LD E, n
instructions[0x1e] = function()
{
   pc = (pc + 1) & 0xffff;
   e = core.mem_read(pc);
};
// 0x1f : RRA
instructions[0x1f] = function()
{
   var temp_s = flags.S, temp_z = flags.Z, temp_p = flags.P;
   a = do_rr(a);
   flags.S = temp_s;
   flags.Z = temp_z;
   flags.P = temp_p;
};
// 0x20 : JR NZ, n
instructions[0x20] = function()
{
   do_conditional_relative_jump(!flags.Z);
};
// 0x21 : LD HL, nn
instructions[0x21] = function()
{
   pc = (pc + 1) & 0xffff;
   l = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   h = core.mem_read(pc);
};
// 0x22 : LD (nn), HL
instructions[0x22] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   core.mem_write(address, l);
   core.mem_write((address + 1) & 0xffff, h);
};
// 0x23 : INC HL
instructions[0x23] = function()
{
   var result = (l | (h << 8));
   result += 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
};
// 0x24 : INC H
instructions[0x24] = function()
{
   h = do_inc(h);
};
// 0x25 : DEC H
instructions[0x25] = function()
{
   h = do_dec(h);
};
// 0x26 : LD H, n
instructions[0x26] = function()
{
   pc = (pc + 1) & 0xffff;
   h = core.mem_read(pc);
};
// 0x27 : DAA
instructions[0x27] = function()
{
   var temp = a;
   if (!flags.N)
   {
      if (flags.H || ((a & 0x0f) > 9))
         temp += 0x06;
      if (flags.C || (a > 0x99))
         temp += 0x60;
   }
   else
   {
      if (flags.H || ((a & 0x0f) > 9))
         temp -= 0x06;
      if (flags.C || (a > 0x99))
         temp -= 0x60;
   }
   
   flags.S = (temp & 0x80) ? 1 : 0;
   flags.Z = !(temp & 0xff) ? 1 : 0;
   flags.H = ((a & 0x10) ^ (temp & 0x10)) ? 1 : 0;
   flags.P = get_parity(temp & 0xff);
   // DAA never clears the carry flag if it was already set,
   //  but it is able to set the carry flag if it was clear.
   // Don't ask me, I don't know.
   // Note also that we check for a BCD carry, instead of the usual.
   flags.C = (flags.C || (a > 0x99)) ? 1 : 0;
   
   a = temp & 0xff;
   
   update_xy_flags(a);
};
// 0x28 : JR Z, n
instructions[0x28] = function()
{
   do_conditional_relative_jump(!!flags.Z);
};
// 0x29 : ADD HL, HL
instructions[0x29] = function()
{
   do_hl_add(l | (h << 8));
};
// 0x2a : LD HL, (nn)
instructions[0x2a] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   l = core.mem_read(address);
   h = core.mem_read((address + 1) & 0xffff);
};
// 0x2b : DEC HL
instructions[0x2b] = function()
{
   var result = (l | (h << 8));
   result -= 1;
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
};
// 0x2c : INC L
instructions[0x2c] = function()
{
   l = do_inc(l);
};
// 0x2d : DEC L
instructions[0x2d] = function()
{
   l = do_dec(l);
};
// 0x2e : LD L, n
instructions[0x2e] = function()
{
   pc = (pc + 1) & 0xffff;
   l = core.mem_read(pc);
};
// 0x2f : CPL
instructions[0x2f] = function()
{
   a = (~a) & 0xff;
   flags.N = 1;
   flags.H = 1;
   update_xy_flags(a);
};
// 0x30 : JR NC, n
instructions[0x30] = function()
{
   do_conditional_relative_jump(!flags.C);
};
// 0x31 : LD SP, nn
instructions[0x31] = function()
{
   sp =  core.mem_read((pc + 1) & 0xffff) | 
            (core.mem_read((pc + 2) & 0xffff) << 8);
   pc = (pc + 2) & 0xffff;
};
// 0x32 : LD (nn), A
instructions[0x32] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   core.mem_write(address, a);
};
// 0x33 : INC SP
instructions[0x33] = function()
{
   sp = (sp + 1) & 0xffff;
};
// 0x34 : INC (HL)
instructions[0x34] = function()
{
   var address = l | (h << 8);
   core.mem_write(address, do_inc(core.mem_read(address)));
};
// 0x35 : DEC (HL)
instructions[0x35] = function()
{
   var address = l | (h << 8);
   core.mem_write(address, do_dec(core.mem_read(address)));
};
// 0x36 : LD (HL), n
instructions[0x36] = function()
{
   pc = (pc + 1) & 0xffff;
   core.mem_write(l | (h << 8), core.mem_read(pc));
};
// 0x37 : SCF
instructions[0x37] = function()
{
   flags.N = 0;
   flags.H = 0;
   flags.C = 1;
   update_xy_flags(a);
};
// 0x38 : JR C, n
instructions[0x38] = function()
{
   do_conditional_relative_jump(!!flags.C);
};
// 0x39 : ADD HL, SP
instructions[0x39] = function()
{
   do_hl_add(sp);
};
// 0x3a : LD A, (nn)
instructions[0x3a] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   a = core.mem_read(address);
};
// 0x3b : DEC SP
instructions[0x3b] = function()
{
   sp = (sp - 1) & 0xffff;
};
// 0x3c : INC A
instructions[0x3c] = function()
{
   a = do_inc(a);
};
// 0x3d : DEC A
instructions[0x3d] = function()
{
   a = do_dec(a);
};
// 0x3e : LD A, n
instructions[0x3e] = function()
{
   a = core.mem_read((pc + 1) & 0xffff);
   pc = (pc + 1) & 0xffff;
};
// 0x3f : CCF
instructions[0x3f] = function()
{
   flags.N = 0;
   flags.H = flags.C;
   flags.C = flags.C ? 0 : 1;
   update_xy_flags(a);
};
// 0xc0 : RET NZ
instructions[0xc0] = function()
{
   do_conditional_return(!flags.Z);
};
// 0xc1 : POP BC
instructions[0xc1] = function()
{
   var result = pop_word();
   c = result & 0xff;
   b = (result & 0xff00) >>> 8;
};
// 0xc2 : JP NZ, nn
instructions[0xc2] = function()
{
   do_conditional_absolute_jump(!flags.Z);
};
// 0xc3 : JP nn
instructions[0xc3] = function()
{
   pc =  core.mem_read((pc + 1) & 0xffff) |
            (core.mem_read((pc + 2) & 0xffff) << 8);
   pc = (pc - 1) & 0xffff;
};
// 0xc4 : CALL NZ, nn
instructions[0xc4] = function()
{
   do_conditional_call(!flags.Z);
};
// 0xc5 : PUSH BC
instructions[0xc5] = function()
{
   push_word(c | (b << 8));
};
// 0xc6 : ADD A, n
instructions[0xc6] = function()
{
   pc = (pc + 1) & 0xffff;
   do_add(core.mem_read(pc));
};
// 0xc7 : RST 00h
instructions[0xc7] = function()
{
   do_reset(0x00);
};
// 0xc8 : RET Z
instructions[0xc8] = function()
{
   do_conditional_return(!!flags.Z);
};
// 0xc9 : RET
instructions[0xc9] = function()
{
   pc = (pop_word() - 1) & 0xffff;
};
// 0xca : JP Z, nn
instructions[0xca] = function()
{
   do_conditional_absolute_jump(!!flags.Z);
};
// 0xcb : CB Prefix
instructions[0xcb] = function()
{
   // R is incremented at the start of the second instruction cycle,
   //  before the instruction actually runs.
   // The high bit of R is not affected by this increment,
   //  it can only be changed using the LD R, A instruction.
   r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);

   // We don't have a table for this prefix,
   //  the instructions are all so uniform that we can directly decode them.
   pc = (pc + 1) & 0xffff;
   var opcode = core.mem_read(pc),
       bit_number = (opcode & 0x38) >>> 3,
       reg_code = opcode & 0x07;
   
   if (opcode < 0x40)
   {
      // Shift/rotate instructions
      var op_array = [do_rlc, do_rrc, do_rl, do_rr,
                      do_sla, do_sra, do_sll, do_srl];
      
      if (reg_code === 0)
         b = op_array[bit_number]( b);
      else if (reg_code === 1)
         c = op_array[bit_number]( c);
      else if (reg_code === 2)
         d = op_array[bit_number]( d);
      else if (reg_code === 3)
         e = op_array[bit_number]( e);
      else if (reg_code === 4)
         h = op_array[bit_number]( h);
      else if (reg_code === 5)
         l = op_array[bit_number]( l);
      else if (reg_code === 6)
         core.mem_write(l | (h << 8),
                            op_array[bit_number]( core.mem_read(l | (h << 8))));
      else if (reg_code === 7)
         a = op_array[bit_number]( a);
   }
   else if (opcode < 0x80)
   {
      // BIT instructions
      if (reg_code === 0)
         flags.Z = !(b & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 1)
         flags.Z = !(c & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 2)
         flags.Z = !(d & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 3)
         flags.Z = !(e & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 4)
         flags.Z = !(h & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 5)
         flags.Z = !(l & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 6)
         flags.Z = !((core.mem_read(l | (h << 8))) & (1 << bit_number)) ? 1 : 0;
      else if (reg_code === 7)
         flags.Z = !(a & (1 << bit_number)) ? 1 : 0;
         
      flags.N = 0;
      flags.H = 1;
      flags.P = flags.Z;
      flags.S = ((bit_number === 7) && !flags.Z) ? 1 : 0;
      // For the BIT n, (HL) instruction, the X and Y flags are obtained
      //  from what is apparently an internal temporary register used for
      //  some of the 16-bit arithmetic instructions.
      // I haven't implemented that register here,
      //  so for now we'll set X and Y the same way for every BIT opcode,
      //  which means that they will usually be wrong for BIT n, (HL).
      flags.Y = ((bit_number === 5) && !flags.Z) ? 1 : 0;
      flags.X = ((bit_number === 3) && !flags.Z) ? 1 : 0;
   }
   else if (opcode < 0xc0)
   {
      // RES instructions
      if (reg_code === 0)
         b &= (0xff & ~(1 << bit_number));
      else if (reg_code === 1)
         c &= (0xff & ~(1 << bit_number));
      else if (reg_code === 2)
         d &= (0xff & ~(1 << bit_number));
      else if (reg_code === 3)
         e &= (0xff & ~(1 << bit_number));
      else if (reg_code === 4)
         h &= (0xff & ~(1 << bit_number));
      else if (reg_code === 5)
         l &= (0xff & ~(1 << bit_number));
      else if (reg_code === 6)
         core.mem_write(l | (h << 8),
                            core.mem_read(l | (h << 8)) & ~(1 << bit_number));
      else if (reg_code === 7)
         a &= (0xff & ~(1 << bit_number));
   }
   else
   {
      // SET instructions
      if (reg_code === 0)
         b |= (1 << bit_number);
      else if (reg_code === 1)
         c |= (1 << bit_number);
      else if (reg_code === 2)
         d |= (1 << bit_number);
      else if (reg_code === 3)
         e |= (1 << bit_number);
      else if (reg_code === 4)
         h |= (1 << bit_number);
      else if (reg_code === 5)
         l |= (1 << bit_number);
      else if (reg_code === 6)
         core.mem_write(l | (h << 8),
                            core.mem_read(l | (h << 8)) | (1 << bit_number));
      else if (reg_code === 7)
         a |= (1 << bit_number);
   }
   
   cycle_counter += cycle_counts_cb[opcode];
};
// 0xcc : CALL Z, nn
instructions[0xcc] = function()
{
   do_conditional_call(!!flags.Z);
};
// 0xcd : CALL nn
instructions[0xcd] = function()
{
   push_word((pc + 3) & 0xffff);
   pc =  core.mem_read((pc + 1) & 0xffff) |
            (core.mem_read((pc + 2) & 0xffff) << 8);
   pc = (pc - 1) & 0xffff;
};
// 0xce : ADC A, n
instructions[0xce] = function()
{
   pc = (pc + 1) & 0xffff;
   do_adc(core.mem_read(pc));
};
// 0xcf : RST 08h
instructions[0xcf] = function()
{
   do_reset(0x08);
};
// 0xd0 : RET NC
instructions[0xd0] = function()
{
   do_conditional_return(!flags.C);
};
// 0xd1 : POP DE
instructions[0xd1] = function()
{
   var result = pop_word();
   e = result & 0xff;
   d = (result & 0xff00) >>> 8;
};
// 0xd2 : JP NC, nn
instructions[0xd2] = function()
{
   do_conditional_absolute_jump(!flags.C);
};
// 0xd3 : OUT (n), A
instructions[0xd3] = function()
{
   pc = (pc + 1) & 0xffff;
   core.io_write((a << 8) | core.mem_read(pc), a);
};
// 0xd4 : CALL NC, nn
instructions[0xd4] = function()
{
   do_conditional_call(!flags.C);
};
// 0xd5 : PUSH DE
instructions[0xd5] = function()
{
   push_word(e | (d << 8));
};
// 0xd6 : SUB n
instructions[0xd6] = function()
{
   pc = (pc + 1) & 0xffff;
   do_sub(core.mem_read(pc));
};
// 0xd7 : RST 10h
instructions[0xd7] = function()
{
   do_reset(0x10);
};
// 0xd8 : RET C
instructions[0xd8] = function()
{
   do_conditional_return(!!flags.C);
};
// 0xd9 : EXX
instructions[0xd9] = function()
{
   var temp = b;
   b = b_prime;
   b_prime = temp;
   temp = c;
   c = c_prime;
   c_prime = temp;
   temp = d;
   d = d_prime;
   d_prime = temp;
   temp = e;
   e = e_prime;
   e_prime = temp;
   temp = h;
   h = h_prime;
   h_prime = temp;
   temp = l;
   l = l_prime;
   l_prime = temp;
};
// 0xda : JP C, nn
instructions[0xda] = function()
{
   do_conditional_absolute_jump(!!flags.C);
};
// 0xdb : IN A, (n)
instructions[0xdb] = function()
{
   pc = (pc + 1) & 0xffff;
   a = core.io_read((a << 8) | core.mem_read(pc));
};
// 0xdc : CALL C, nn
instructions[0xdc] = function()
{
   do_conditional_call(!!flags.C);
};
// 0xdd : DD Prefix (IX instructions)
instructions[0xdd] = function()
{
   // R is incremented at the start of the second instruction cycle,
   //  before the instruction actually runs.
   // The high bit of R is not affected by this increment,
   //  it can only be changed using the LD R, A instruction.
   r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);

   pc = (pc + 1) & 0xffff;
   var opcode = core.mem_read(pc),
       func = dd_instructions[opcode];
       
   if (func)
   {
      //func = func.bind(this);
      func();
      cycle_counter += cycle_counts_dd[opcode];
   }
   else
   {
      // Apparently if a DD opcode doesn't exist,
      //  it gets treated as an unprefixed opcode.
      // What we'll do to handle that is just back up the 
      //  program counter, so that this byte gets decoded
      //  as a normal instruction.
      pc = (pc - 1) & 0xffff;
      // And we'll add in the cycle count for a NOP.
      cycle_counter += cycle_counts[0];
   }
};
// 0xde : SBC n
instructions[0xde] = function()
{
   pc = (pc + 1) & 0xffff;
   do_sbc(core.mem_read(pc));
};
// 0xdf : RST 18h
instructions[0xdf] = function()
{
   do_reset(0x18);
};
// 0xe0 : RET PO
instructions[0xe0] = function()
{
   do_conditional_return(!flags.P);
};
// 0xe1 : POP HL
instructions[0xe1] = function()
{
   var result = pop_word();
   l = result & 0xff;
   h = (result & 0xff00) >>> 8;
};
// 0xe2 : JP PO, (nn)
instructions[0xe2] = function()
{
   do_conditional_absolute_jump(!flags.P);
};
// 0xe3 : EX (SP), HL
instructions[0xe3] = function()
{
   var temp = core.mem_read(sp);
   core.mem_write(sp, l);
   l = temp;
   temp = core.mem_read((sp + 1) & 0xffff);
   core.mem_write((sp + 1) & 0xffff, h);
   h = temp;
};
// 0xe4 : CALL PO, nn
instructions[0xe4] = function()
{
   do_conditional_call(!flags.P);
};
// 0xe5 : PUSH HL
instructions[0xe5] = function()
{
   push_word(l | (h << 8));
};
// 0xe6 : AND n
instructions[0xe6] = function()
{
   pc = (pc + 1) & 0xffff;
   do_and(core.mem_read(pc));
};
// 0xe7 : RST 20h
instructions[0xe7] = function()
{
   do_reset(0x20);
};
// 0xe8 : RET PE
instructions[0xe8] = function()
{
   do_conditional_return(!!flags.P);
};
// 0xe9 : JP (HL)
instructions[0xe9] = function()
{
   pc = l | (h << 8);
   pc = (pc - 1) & 0xffff;
};
// 0xea : JP PE, nn
instructions[0xea] = function()
{
   do_conditional_absolute_jump(!!flags.P);
};
// 0xeb : EX DE, HL
instructions[0xeb] = function()
{
   var temp = d;
   d = h;
   h = temp;
   temp = e;
   e = l;
   l = temp;
};
// 0xec : CALL PE, nn
instructions[0xec] = function()
{
   do_conditional_call(!!flags.P);
};
// 0xed : ED Prefix
instructions[0xed] = function()
{
   // R is incremented at the start of the second instruction cycle,
   //  before the instruction actually runs.
   // The high bit of R is not affected by this increment,
   //  it can only be changed using the LD R, A instruction.
   r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);

   pc = (pc + 1) & 0xffff;
   var opcode = core.mem_read(pc),
       func = ed_instructions[opcode];
       
   if (func)
   {
      //func = func.bind(this);
      func();
      cycle_counter += cycle_counts_ed[opcode];
   }
   else
   {
      // If the opcode didn't exist, the whole thing is a two-byte NOP.
      cycle_counter += cycle_counts[0];
   }
};
// 0xee : XOR n
instructions[0xee] = function()
{
   pc = (pc + 1) & 0xffff;
   do_xor(core.mem_read(pc));
};
// 0xef : RST 28h
instructions[0xef] = function()
{
   do_reset(0x28);
};
// 0xf0 : RET P
instructions[0xf0] = function()
{
   do_conditional_return(!flags.S);
};
// 0xf1 : POP AF
instructions[0xf1] = function()
{
   var result = pop_word();
   set_flags_register(result & 0xff);
   a = (result & 0xff00) >>> 8;
};
// 0xf2 : JP P, nn
instructions[0xf2] = function()
{
   do_conditional_absolute_jump(!flags.S);
};
// 0xf3 : DI
instructions[0xf3] = function()
{
   // DI doesn't actually take effect until after the next instruction.
   do_delayed_di = true;
};
// 0xf4 : CALL P, nn
instructions[0xf4] = function()
{
   do_conditional_call(!flags.S);
};
// 0xf5 : PUSH AF
instructions[0xf5] = function()
{
   push_word(get_flags_register() | (a << 8));
};
// 0xf6 : OR n
instructions[0xf6] = function()
{
   pc = (pc + 1) & 0xffff;
   do_or(core.mem_read(pc));
};
// 0xf7 : RST 30h
instructions[0xf7] = function()
{
   do_reset(0x30);
};
// 0xf8 : RET M
instructions[0xf8] = function()
{
   do_conditional_return(!!flags.S);
};
// 0xf9 : LD SP, HL
instructions[0xf9] = function()
{
   sp = l | (h << 8);
};
// 0xfa : JP M, nn
instructions[0xfa] = function()
{
   do_conditional_absolute_jump(!!flags.S);
};
// 0xfb : EI
instructions[0xfb] = function()
{
   // EI doesn't actually take effect until after the next instruction.
   do_delayed_ei = true;
};
// 0xfc : CALL M, nn
instructions[0xfc] = function()
{
   do_conditional_call(!!flags.S);
};
// 0xfd : FD Prefix (IY instructions)
instructions[0xfd] = function()
{
   // R is incremented at the start of the second instruction cycle,
   //  before the instruction actually runs.
   // The high bit of R is not affected by this increment,
   //  it can only be changed using the LD R, A instruction.
   r = (r & 0x80) | (((r & 0x7f) + 1) & 0x7f);
   
   pc = (pc + 1) & 0xffff;
   var opcode = core.mem_read(pc),
       func = dd_instructions[opcode];
       
   if (func)
   {
      // Rather than copy and paste all the IX instructions into IY instructions,
      //  what we'll do is sneakily copy IY into IX, run the IX instruction,
      //  and then copy the result into IY and restore the old IX.
      var temp = ix;
      ix = iy;
      //func = func.bind(this);
      func();
      iy = ix;
      ix = temp;
      
      cycle_counter += cycle_counts_dd[opcode];
   }
   else
   {
      // Apparently if an FD opcode doesn't exist,
      //  it gets treated as an unprefixed opcode.
      // What we'll do to handle that is just back up the 
      //  program counter, so that this byte gets decoded
      //  as a normal instruction.
      pc = (pc - 1) & 0xffff;
      // And we'll add in the cycle count for a NOP.
      cycle_counter += cycle_counts[0];
   }
};
// 0xfe : CP n
instructions[0xfe] = function()
{
   pc = (pc + 1) & 0xffff;
   do_cp(core.mem_read(pc));
};
// 0xff : RST 38h
instructions[0xff] = function()
{
   do_reset(0x38);
};


///////////////////////////////////////////////////////////////////////////////
/// This table of ED opcodes is pretty sparse;
///  there are not very many valid ED-prefixed opcodes in the Z80,
///  and many of the ones that are valid are not documented.
///////////////////////////////////////////////////////////////////////////////
let ed_instructions = [];
// 0x40 : IN B, (C)
ed_instructions[0x40] = function()
{
   b = do_in((b << 8) | c);
};
// 0x41 : OUT (C), B
ed_instructions[0x41] = function()
{
   core.io_write((b << 8) | c, b);
};
// 0x42 : SBC HL, BC
ed_instructions[0x42] = function()
{
   do_hl_sbc(c | (b << 8));
};
// 0x43 : LD (nn), BC
ed_instructions[0x43] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   core.mem_write(address, c);
   core.mem_write((address + 1) & 0xffff, b);
};
// 0x44 : NEG
ed_instructions[0x44] = function()
{
   do_neg();
};
// 0x45 : RETN
ed_instructions[0x45] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x46 : IM 0
ed_instructions[0x46] = function()
{
   imode = 0;
};
// 0x47 : LD I, A
ed_instructions[0x47] = function()
{
   i = a
};
// 0x48 : IN C, (C)
ed_instructions[0x48] = function()
{
   c = do_in((b << 8) | c);
};
// 0x49 : OUT (C), C
ed_instructions[0x49] = function()
{
   core.io_write((b << 8) | c, c);
};
// 0x4a : ADC HL, BC
ed_instructions[0x4a] = function()
{
   do_hl_adc(c | (b << 8));
};
// 0x4b : LD BC, (nn)
ed_instructions[0x4b] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   c = core.mem_read(address);
   b = core.mem_read((address + 1) & 0xffff);
};
// 0x4c : NEG (Undocumented)
ed_instructions[0x4c] = function()
{
   do_neg();
};
// 0x4d : RETI
ed_instructions[0x4d] = function()
{
   pc = (pop_word() - 1) & 0xffff;
};
// 0x4e : IM 0 (Undocumented)
ed_instructions[0x4e] = function()
{
   imode = 0;
};
// 0x4f : LD R, A
ed_instructions[0x4f] = function()
{
   r = a;
};
// 0x50 : IN D, (C)
ed_instructions[0x50] = function()
{
   d = do_in((b << 8) | c);
};
// 0x51 : OUT (C), D
ed_instructions[0x51] = function()
{
   core.io_write((b << 8) | c, d);
};
// 0x52 : SBC HL, DE
ed_instructions[0x52] = function()
{
   do_hl_sbc(e | (d << 8));
};
// 0x53 : LD (nn), DE
ed_instructions[0x53] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   core.mem_write(address, e);
   core.mem_write((address + 1) & 0xffff, d);
};
// 0x54 : NEG (Undocumented)
ed_instructions[0x54] = function()
{
   do_neg();
};
// 0x55 : RETN
ed_instructions[0x55] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x56 : IM 1
ed_instructions[0x56] = function()
{
   imode = 1;
};
// 0x57 : LD A, I
ed_instructions[0x57] = function()
{
   a = i;
   flags.S = a & 0x80 ? 1 : 0;
   flags.Z = a ? 0 : 1;
   flags.H = 0;
   flags.P = iff2;
   flags.N = 0;
   update_xy_flags(a);
};
// 0x58 : IN E, (C)
ed_instructions[0x58] = function()
{
   e = do_in((b << 8) | c);
};
// 0x59 : OUT (C), E
ed_instructions[0x59] = function()
{
   core.io_write((b << 8) | c, e);
};
// 0x5a : ADC HL, DE
ed_instructions[0x5a] = function()
{
   do_hl_adc(e | (d << 8));
};
// 0x5b : LD DE, (nn)
ed_instructions[0x5b] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   e = core.mem_read(address);
   d = core.mem_read((address + 1) & 0xffff);
};
// 0x5c : NEG (Undocumented)
ed_instructions[0x5c] = function()
{
   do_neg();
};
// 0x5d : RETN
ed_instructions[0x5d] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x5e : IM 2
ed_instructions[0x5e] = function()
{
   imode = 2;
};
// 0x5f : LD A, R
ed_instructions[0x5f] = function()
{
   a = r;
   flags.S = a & 0x80 ? 1 : 0;
   flags.Z = a ? 0 : 1;
   flags.H = 0;
   flags.P = iff2;
   flags.N = 0;
   update_xy_flags(a);
};
// 0x60 : IN H, (C)
ed_instructions[0x60] = function()
{
   h = do_in((b << 8) | c);
};
// 0x61 : OUT (C), H
ed_instructions[0x61] = function()
{
   core.io_write((b << 8) | c, h);
};
// 0x62 : SBC HL, HL
ed_instructions[0x62] = function()
{
   do_hl_sbc(l | (h << 8));
};
// 0x63 : LD (nn), HL (Undocumented)
ed_instructions[0x63] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   core.mem_write(address, l);
   core.mem_write((address + 1) & 0xffff, h);
};
// 0x64 : NEG (Undocumented)
ed_instructions[0x64] = function()
{
   do_neg();
};
// 0x65 : RETN
ed_instructions[0x65] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x66 : IM 0
ed_instructions[0x66] = function()
{
   imode = 0;
};
// 0x67 : RRD
ed_instructions[0x67] = function()
{
   var hl_value = core.mem_read(l | (h << 8));
   var temp1 = hl_value & 0x0f, temp2 = a & 0x0f;
   hl_value = ((hl_value & 0xf0) >>> 4) | (temp2 << 4);
   a = (a & 0xf0) | temp1;
   core.mem_write(l | (h << 8), hl_value);
   
   flags.S = (a & 0x80) ? 1 : 0;
   flags.Z = a ? 0 : 1;
   flags.H = 0;
   flags.P = get_parity(a) ? 1 : 0;
   flags.N = 0;
   update_xy_flags(a);
};
// 0x68 : IN L, (C)
ed_instructions[0x68] = function()
{
   l = do_in((b << 8) | c);
};
// 0x69 : OUT (C), L
ed_instructions[0x69] = function()
{
   core.io_write((b << 8) | c, l);
};
// 0x6a : ADC HL, HL
ed_instructions[0x6a] = function()
{
   do_hl_adc(l | (h << 8));
};
// 0x6b : LD HL, (nn) (Undocumented)
ed_instructions[0x6b] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   l = core.mem_read(address);
   h = core.mem_read((address + 1) & 0xffff);
};
// 0x6c : NEG (Undocumented)
ed_instructions[0x6c] = function()
{
   do_neg();
};
// 0x6d : RETN
ed_instructions[0x6d] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x6e : IM 0 (Undocumented)
ed_instructions[0x6e] = function()
{
   imode = 0;
};
// 0x6f : RLD
ed_instructions[0x6f] = function()
{
   var hl_value = core.mem_read(l | (h << 8));
   var temp1 = hl_value & 0xf0, temp2 = a & 0x0f;
   hl_value = ((hl_value & 0x0f) << 4) | temp2;
   a = (a & 0xf0) | (temp1 >>> 4);
   core.mem_write(l | (h << 8), hl_value);
   
   flags.S = (a & 0x80) ? 1 : 0;
   flags.Z = a ? 0 : 1;
   flags.H = 0;
   flags.P = get_parity(a) ? 1 : 0;
   flags.N = 0;
   update_xy_flags(a);
};
// 0x70 : IN (C) (Undocumented)
ed_instructions[0x70] = function()
{
   do_in((b << 8) | c);
};
// 0x71 : OUT (C), 0 (Undocumented)
ed_instructions[0x71] = function()
{
   core.io_write((b << 8) | c, 0);
};
// 0x72 : SBC HL, SP
ed_instructions[0x72] = function()
{
   do_hl_sbc(sp);
};
// 0x73 : LD (nn), SP
ed_instructions[0x73] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   core.mem_write(address, sp & 0xff);
   core.mem_write((address + 1) & 0xffff, (sp >>> 8) & 0xff);
};
// 0x74 : NEG (Undocumented)
ed_instructions[0x74] = function()
{
   do_neg();
};
// 0x75 : RETN
ed_instructions[0x75] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x76 : IM 1
ed_instructions[0x76] = function()
{
   imode = 1;
};
// 0x78 : IN A, (C)
ed_instructions[0x78] = function()
{
   a = do_in((b << 8) | c);
};
// 0x79 : OUT (C), A
ed_instructions[0x79] = function()
{
   core.io_write((b << 8) | c, a);
};
// 0x7a : ADC HL, SP
ed_instructions[0x7a] = function()
{
   do_hl_adc(sp);
};
// 0x7b : LD SP, (nn)
ed_instructions[0x7b] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= core.mem_read(pc) << 8;
   
   sp = core.mem_read(address);
   sp |= core.mem_read((address + 1) & 0xffff) << 8;
};
// 0x7c : NEG (Undocumented)
ed_instructions[0x7c] = function()
{
   do_neg();
};
// 0x7d : RETN
ed_instructions[0x7d] = function()
{
   pc = (pop_word() - 1) & 0xffff;
   iff1 = iff2;
};
// 0x7e : IM 2
ed_instructions[0x7e] = function()
{
   imode = 2;
};
// 0xa0 : LDI
ed_instructions[0xa0] = function()
{
   do_ldi();
};
// 0xa1 : CPI
ed_instructions[0xa1] = function()
{
   do_cpi();
};
// 0xa2 : INI
ed_instructions[0xa2] = function()
{
   do_ini();
};
// 0xa3 : OUTI
ed_instructions[0xa3] = function()
{
   do_outi();
};
// 0xa8 : LDD
ed_instructions[0xa8] = function()
{
   do_ldd();
};
// 0xa9 : CPD
ed_instructions[0xa9] = function()
{
   do_cpd();
};
// 0xaa : IND
ed_instructions[0xaa] = function()
{
   do_ind();
};
// 0xab : OUTD
ed_instructions[0xab] = function()
{
   do_outd();
};
// 0xb0 : LDIR
ed_instructions[0xb0] = function()
{
   do_ldi();
   if (b || c)
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xb1 : CPIR
ed_instructions[0xb1] = function()
{
   do_cpi();
   if (!flags.Z && (b || c))
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xb2 : INIR
ed_instructions[0xb2] = function()
{
   do_ini();
   if (b)
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xb3 : OTIR
ed_instructions[0xb3] = function()
{
   do_outi();
   if (b)
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xb8 : LDDR
ed_instructions[0xb8] = function()
{
   do_ldd();
   if (b || c)
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xb9 : CPDR
ed_instructions[0xb9] = function()
{
   do_cpd();
   if (!flags.Z && (b || c))
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xba : INDR
ed_instructions[0xba] = function()
{
   do_ind();
   if (b)
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};
// 0xbb : OTDR
ed_instructions[0xbb] = function()
{
   do_outd();
   if (b)
   {
      cycle_counter += 5;
      pc = (pc - 2) & 0xffff;
   }
};


///////////////////////////////////////////////////////////////////////////////
/// Like ED, this table is quite sparse,
///  and many of the opcodes here are also undocumented.
/// The undocumented instructions here are those that deal with only one byte
///  of the two-byte IX register; the bytes are designed IXH and IXL here.
///////////////////////////////////////////////////////////////////////////////
let dd_instructions = [];
// 0x09 : ADD IX, BC
dd_instructions[0x09] = function()
{
   do_ix_add(c | (b << 8));
};
// 0x19 : ADD IX, DE
dd_instructions[0x19] = function()
{
   do_ix_add(e | (d << 8));
};
// 0x21 : LD IX, nn
dd_instructions[0x21] = function()
{
   pc = (pc + 1) & 0xffff;
   ix = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   ix |= (core.mem_read(pc) << 8);
};
// 0x22 : LD (nn), IX
dd_instructions[0x22] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= (core.mem_read(pc) << 8);
   
   core.mem_write(address, ix & 0xff);
   core.mem_write((address + 1) & 0xffff, (ix >>> 8) & 0xff);
};
// 0x23 : INC IX
dd_instructions[0x23] = function()
{
   ix = (ix + 1) & 0xffff;
};
// 0x24 : INC IXH (Undocumented)
dd_instructions[0x24] = function()
{
   ix = (do_inc(ix >>> 8) << 8) | (ix & 0xff);
};
// 0x25 : DEC IXH (Undocumented)
dd_instructions[0x25] = function()
{
   ix = (do_dec(ix >>> 8) << 8) | (ix & 0xff);
};
// 0x26 : LD IXH, n (Undocumented)
dd_instructions[0x26] = function()
{
   pc = (pc + 1) & 0xffff;
   ix = (core.mem_read(pc) << 8) | (ix & 0xff);
};
// 0x29 : ADD IX, IX
dd_instructions[0x29] = function()
{
   do_ix_add(ix);
};
// 0x2a : LD IX, (nn)
dd_instructions[0x2a] = function()
{
   pc = (pc + 1) & 0xffff;
   var address = core.mem_read(pc);
   pc = (pc + 1) & 0xffff;
   address |= (core.mem_read(pc) << 8);
   
   ix = core.mem_read(address);
   ix |= (core.mem_read((address + 1) & 0xffff) << 8);
};
// 0x2b : DEC IX
dd_instructions[0x2b] = function()
{
   ix = (ix - 1) & 0xffff;
};
// 0x2c : INC IXL (Undocumented)
dd_instructions[0x2c] = function()
{
   ix = do_inc(ix & 0xff) | (ix & 0xff00);
};
// 0x2d : DEC IXL (Undocumented)
dd_instructions[0x2d] = function()
{
   ix = do_dec(ix & 0xff) | (ix & 0xff00);
};
// 0x2e : LD IXL, n (Undocumented)
dd_instructions[0x2e] = function()
{
   pc = (pc + 1) & 0xffff;
   ix = (core.mem_read(pc) & 0xff) | (ix & 0xff00);
};
// 0x34 : INC (IX+n)
dd_instructions[0x34] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc)),
       value = core.mem_read((offset + ix) & 0xffff);
   core.mem_write((offset + ix) & 0xffff, do_inc(value));
};
// 0x35 : DEC (IX+n)
dd_instructions[0x35] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc)),
       value = core.mem_read((offset + ix) & 0xffff);
   core.mem_write((offset + ix) & 0xffff, do_dec(value));
};
// 0x36 : LD (IX+n), n
dd_instructions[0x36] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   pc = (pc + 1) & 0xffff;
   core.mem_write((ix + offset) & 0xffff, core.mem_read(pc));   
};
// 0x39 : ADD IX, SP
dd_instructions[0x39] = function()
{
   do_ix_add(sp);
};
// 0x44 : LD B, IXH (Undocumented)
dd_instructions[0x44] = function()
{
   b = (ix >>> 8) & 0xff;
};
// 0x45 : LD B, IXL (Undocumented)
dd_instructions[0x45] = function()
{
   b = ix & 0xff;
};
// 0x46 : LD B, (IX+n)
dd_instructions[0x46] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   b = core.mem_read((ix + offset) & 0xffff);
};
// 0x4c : LD C, IXH (Undocumented)
dd_instructions[0x4c] = function()
{
   c = (ix >>> 8) & 0xff;
};
// 0x4d : LD C, IXL (Undocumented)
dd_instructions[0x4d] = function()
{
   c = ix & 0xff;
};
// 0x4e : LD C, (IX+n)
dd_instructions[0x4e] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   c = core.mem_read((ix + offset) & 0xffff);
};
// 0x54 : LD D, IXH (Undocumented)
dd_instructions[0x54] = function()
{
   d = (ix >>> 8) & 0xff;
};
// 0x55 : LD D, IXL (Undocumented)
dd_instructions[0x55] = function()
{
   d = ix & 0xff;
};
// 0x56 : LD D, (IX+n)
dd_instructions[0x56] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   d = core.mem_read((ix + offset) & 0xffff);
};
// 0x5c : LD E, IXH (Undocumented)
dd_instructions[0x5c] = function()
{
   e = (ix >>> 8) & 0xff;
};
// 0x5d : LD E, IXL (Undocumented)
dd_instructions[0x5d] = function()
{
   e = ix & 0xff;
};
// 0x5e : LD E, (IX+n)
dd_instructions[0x5e] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   e = core.mem_read((ix + offset) & 0xffff);
};
// 0x60 : LD IXH, B (Undocumented)
dd_instructions[0x60] = function()
{
   ix = (ix & 0xff) | (b << 8);
};
// 0x61 : LD IXH, C (Undocumented)
dd_instructions[0x61] = function()
{
   ix = (ix & 0xff) | (c << 8);
};
// 0x62 : LD IXH, D (Undocumented)
dd_instructions[0x62] = function()
{
   ix = (ix & 0xff) | (d << 8);
};
// 0x63 : LD IXH, E (Undocumented)
dd_instructions[0x63] = function()
{
   ix = (ix & 0xff) | (e << 8);
};
// 0x64 : LD IXH, IXH (Undocumented)
dd_instructions[0x64] = function()
{
   // No-op.
};
// 0x65 : LD IXH, IXL (Undocumented)
dd_instructions[0x65] = function()
{
   ix = (ix & 0xff) | ((ix & 0xff) << 8);
};
// 0x66 : LD H, (IX+n)
dd_instructions[0x66] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   h = core.mem_read((ix + offset) & 0xffff);
};
// 0x67 : LD IXH, A (Undocumented)
dd_instructions[0x67] = function()
{
   ix = (ix & 0xff) | (a << 8);
};
// 0x68 : LD IXL, B (Undocumented)
dd_instructions[0x68] = function()
{
   ix = (ix & 0xff00) | b;
};
// 0x69 : LD IXL, C (Undocumented)
dd_instructions[0x69] = function()
{
   ix = (ix & 0xff00) | c;
};
// 0x6a : LD IXL, D (Undocumented)
dd_instructions[0x6a] = function()
{
   ix = (ix & 0xff00) | d;
};
// 0x6b : LD IXL, E (Undocumented)
dd_instructions[0x6b] = function()
{
   ix = (ix & 0xff00) | e;
};
// 0x6c : LD IXL, IXH (Undocumented)
dd_instructions[0x6c] = function()
{
   ix = (ix & 0xff00) | (ix >>> 8);
};
// 0x6d : LD IXL, IXL (Undocumented)
dd_instructions[0x6d] = function()
{
   // No-op.
};
// 0x6e : LD L, (IX+n)
dd_instructions[0x6e] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   l = core.mem_read((ix + offset) & 0xffff);
};
// 0x6f : LD IXL, A (Undocumented)
dd_instructions[0x6f] = function()
{
   ix = (ix & 0xff00) | a;
};
// 0x70 : LD (IX+n), B
dd_instructions[0x70] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, b);
};
// 0x71 : LD (IX+n), C
dd_instructions[0x71] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, c);
};
// 0x72 : LD (IX+n), D
dd_instructions[0x72] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, d);
};
// 0x73 : LD (IX+n), E
dd_instructions[0x73] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, e);
};
// 0x74 : LD (IX+n), H
dd_instructions[0x74] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, h);
};
// 0x75 : LD (IX+n), L
dd_instructions[0x75] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, l);
};
// 0x77 : LD (IX+n), A
dd_instructions[0x77] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   core.mem_write((ix + offset) & 0xffff, a);
};
// 0x7c : LD A, IXH (Undocumented)
dd_instructions[0x7c] = function()
{
   a = (ix >>> 8) & 0xff;
};
// 0x7d : LD A, IXL (Undocumented)
dd_instructions[0x7d] = function()
{
   a = ix & 0xff;
};
// 0x7e : LD A, (IX+n)
dd_instructions[0x7e] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   a = core.mem_read((ix + offset) & 0xffff);
};
// 0x84 : ADD A, IXH (Undocumented)
dd_instructions[0x84] = function()
{
   do_add((ix >>> 8) & 0xff);
};
// 0x85 : ADD A, IXL (Undocumented)
dd_instructions[0x85] = function()
{
   do_add(ix & 0xff);
};
// 0x86 : ADD A, (IX+n)
dd_instructions[0x86] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_add(core.mem_read((ix + offset) & 0xffff));
};
// 0x8c : ADC A, IXH (Undocumented)
dd_instructions[0x8c] = function()
{
   do_adc((ix >>> 8) & 0xff);
};
// 0x8d : ADC A, IXL (Undocumented)
dd_instructions[0x8d] = function()
{
   do_adc(ix & 0xff);
};
// 0x8e : ADC A, (IX+n)
dd_instructions[0x8e] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_adc(core.mem_read((ix + offset) & 0xffff));
};
// 0x94 : SUB IXH (Undocumented)
dd_instructions[0x94] = function()
{
   do_sub((ix >>> 8) & 0xff);
};
// 0x95 : SUB IXL (Undocumented)
dd_instructions[0x95] = function()
{
   do_sub(ix & 0xff);
};
// 0x96 : SUB A, (IX+n)
dd_instructions[0x96] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_sub(core.mem_read((ix + offset) & 0xffff));
};
// 0x9c : SBC IXH (Undocumented)
dd_instructions[0x9c] = function()
{
   do_sbc((ix >>> 8) & 0xff);
};
// 0x9d : SBC IXL (Undocumented)
dd_instructions[0x9d] = function()
{
   do_sbc(ix & 0xff);
};
// 0x9e : SBC A, (IX+n)
dd_instructions[0x9e] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_sbc(core.mem_read((ix + offset) & 0xffff));
};
// 0xa4 : AND IXH (Undocumented)
dd_instructions[0xa4] = function()
{
   do_and((ix >>> 8) & 0xff);
};
// 0xa5 : AND IXL (Undocumented)
dd_instructions[0xa5] = function()
{
   do_and(ix & 0xff);
};
// 0xa6 : AND A, (IX+n)
dd_instructions[0xa6] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_and(core.mem_read((ix + offset) & 0xffff));
};
// 0xac : XOR IXH (Undocumented)
dd_instructions[0xac] = function()
{
   do_xor((ix >>> 8) & 0xff);
};
// 0xad : XOR IXL (Undocumented)
dd_instructions[0xad] = function()
{
   do_xor(ix & 0xff);
};
// 0xae : XOR A, (IX+n)
dd_instructions[0xae] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_xor(core.mem_read((ix + offset) & 0xffff));
};
// 0xb4 : OR IXH (Undocumented)
dd_instructions[0xb4] = function()
{
   do_or((ix >>> 8) & 0xff);
};
// 0xb5 : OR IXL (Undocumented)
dd_instructions[0xb5] = function()
{
   do_or(ix & 0xff);
};
// 0xb6 : OR A, (IX+n)
dd_instructions[0xb6] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_or(core.mem_read((ix + offset) & 0xffff));
};
// 0xbc : CP IXH (Undocumented)
dd_instructions[0xbc] = function()
{
   do_cp((ix >>> 8) & 0xff);
};
// 0xbd : CP IXL (Undocumented)
dd_instructions[0xbd] = function()
{
   do_cp(ix & 0xff);
};
// 0xbe : CP A, (IX+n)
dd_instructions[0xbe] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   do_cp(core.mem_read((ix + offset) & 0xffff));
};
// 0xcb : CB Prefix (IX bit instructions)
dd_instructions[0xcb] = function()
{
   pc = (pc + 1) & 0xffff;
   var offset = get_signed_offset_byte(core.mem_read(pc));
   pc = (pc + 1) & 0xffff;
   var opcode = core.mem_read(pc), value;
   
   // As with the "normal" CB prefix, we implement the DDCB prefix
   //  by decoding the opcode directly, rather than using a table.
   if (opcode < 0x40)
   {
      // Shift and rotate instructions.
      var ddcb_functions = [do_rlc, do_rrc, do_rl, do_rr,
                            do_sla, do_sra, do_sll, do_srl];
      
      // Most of the opcodes in this range are not valid,
      //  so we map this opcode onto one of the ones that is.
      var func = ddcb_functions[(opcode & 0x38) >>> 3],
      value = func( core.mem_read((ix + offset) & 0xffff));
      
      core.mem_write((ix + offset) & 0xffff, value);
   }
   else
   {
      var bit_number = (opcode & 0x38) >>> 3;
      
      if (opcode < 0x80)
      {
         // BIT
         flags.N = 0;
         flags.H = 1;
         flags.Z = !(core.mem_read((ix + offset) & 0xffff) & (1 << bit_number)) ? 1 : 0;
         flags.P = flags.Z;
         flags.S = ((bit_number === 7) && !flags.Z) ? 1 : 0;
      }
      else if (opcode < 0xc0)
      {
         // RES
         value = core.mem_read((ix + offset) & 0xffff) & ~(1 << bit_number) & 0xff;
         core.mem_write((ix + offset) & 0xffff, value);
      }
      else
      {
         // SET
         value = core.mem_read((ix + offset) & 0xffff) | (1 << bit_number);
         core.mem_write((ix + offset) & 0xffff, value);
      }
   }
   
   // This implements the undocumented shift, RES, and SET opcodes,
   //  which write their result to memory and also to an 8080 register.
   if (value !== undefined)
   {
      if ((opcode & 0x07) === 0)
         b = value;
      else if ((opcode & 0x07) === 1)
         c = value;
      else if ((opcode & 0x07) === 2)
         d = value;
      else if ((opcode & 0x07) === 3)
         e = value;
      else if ((opcode & 0x07) === 4)
         h = value;
      else if ((opcode & 0x07) === 5)
         l = value;
      // 6 is the documented opcode, which doesn't set a register.
      else if ((opcode & 0x07) === 7)
         a = value;
   }
   
   cycle_counter += cycle_counts_cb[opcode] + 8;
};
// 0xe1 : POP IX
dd_instructions[0xe1] = function()
{
   ix = pop_word();
};
// 0xe3 : EX (SP), IX
dd_instructions[0xe3] = function()
{
   var temp = ix;
   ix = core.mem_read(sp);
   ix |= core.mem_read((sp + 1) & 0xffff) << 8;
   core.mem_write(sp, temp & 0xff);
   core.mem_write((sp + 1) & 0xffff, (temp >>> 8) & 0xff);
};
// 0xe5 : PUSH IX
dd_instructions[0xe5] = function()
{
   push_word(ix);
};
// 0xe9 : JP (IX)
dd_instructions[0xe9] = function()
{
   pc = (ix - 1) & 0xffff;
};
// 0xf9 : LD SP, IX
dd_instructions[0xf9] = function()
{
   sp = ix;
};


///////////////////////////////////////////////////////////////////////////////
/// These tables contain the number of T cycles used for each instruction.
/// In a few special cases, such as conditional control flow instructions,
///  additional cycles might be added to these values.
/// The total number of cycles is the return value of run_instruction().
///////////////////////////////////////////////////////////////////////////////
let cycle_counts = [
    4, 10,  7,  6,  4,  4,  7,  4,  4, 11,  7,  6,  4,  4,  7,  4,
    8, 10,  7,  6,  4,  4,  7,  4, 12, 11,  7,  6,  4,  4,  7,  4,
    7, 10, 16,  6,  4,  4,  7,  4,  7, 11, 16,  6,  4,  4,  7,  4,
    7, 10, 13,  6, 11, 11, 10,  4,  7, 11, 13,  6,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    7,  7,  7,  7,  7,  7,  4,  7,  4,  4,  4,  4,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    4,  4,  4,  4,  4,  4,  7,  4,  4,  4,  4,  4,  4,  4,  7,  4,
    5, 10, 10, 10, 10, 11,  7, 11,  5, 10, 10,  0, 10, 17,  7, 11,
    5, 10, 10, 11, 10, 11,  7, 11,  5,  4, 10, 11, 10,  0,  7, 11,
    5, 10, 10, 19, 10, 11,  7, 11,  5,  4, 10,  4, 10,  0,  7, 11,
    5, 10, 10,  4, 10, 11,  7, 11,  5,  6, 10,  4, 10,  0,  7, 11
];

let cycle_counts_ed = [
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   12, 12, 15, 20,  8, 14,  8,  9, 12, 12, 15, 20,  8, 14,  8,  9,
   12, 12, 15, 20,  8, 14,  8,  9, 12, 12, 15, 20,  8, 14,  8,  9,
   12, 12, 15, 20,  8, 14,  8, 18, 12, 12, 15, 20,  8, 14,  8, 18,
   12, 12, 15, 20,  8, 14,  8,  0, 12, 12, 15, 20,  8, 14,  8,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
   16, 16, 16, 16,  0,  0,  0,  0, 16, 16, 16, 16,  0,  0,  0,  0,
   16, 16, 16, 16,  0,  0,  0,  0, 16, 16, 16, 16,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
];

let cycle_counts_cb = [
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 12,  8,  8,  8,  8,  8,  8,  8, 12,  8,
    8,  8,  8,  8,  8,  8, 12,  8,  8,  8,  8,  8,  8,  8, 12,  8,
    8,  8,  8,  8,  8,  8, 12,  8,  8,  8,  8,  8,  8,  8, 12,  8,
    8,  8,  8,  8,  8,  8, 12,  8,  8,  8,  8,  8,  8,  8, 12,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8,
    8,  8,  8,  8,  8,  8, 15,  8,  8,  8,  8,  8,  8,  8, 15,  8
];

let cycle_counts_dd = [
    0,  0,  0,  0,  0,  0,  0,  0,  0, 15,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0, 15,  0,  0,  0,  0,  0,  0,
    0, 14, 20, 10,  8,  8, 11,  0,  0, 15, 20, 10,  8,  8, 11,  0,
    0,  0,  0,  0, 23, 23, 19,  0,  0, 15,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  8,  8, 19,  0,  0,  0,  0,  0,  8,  8, 19,  0,
    0,  0,  0,  0,  8,  8, 19,  0,  0,  0,  0,  0,  8,  8, 19,  0,
    8,  8,  8,  8,  8,  8, 19,  8,  8,  8,  8,  8,  8,  8, 19,  8,
   19, 19, 19, 19, 19, 19,  0, 19,  0,  0,  0,  0,  8,  8, 19,  0,
    0,  0,  0,  0,  8,  8, 19,  0,  0,  0,  0,  0,  8,  8, 19,  0,
    0,  0,  0,  0,  8,  8, 19,  0,  0,  0,  0,  0,  8,  8, 19,  0,
    0,  0,  0,  0,  8,  8, 19,  0,  0,  0,  0,  0,  8,  8, 19,  0,
    0,  0,  0,  0,  8,  8, 19,  0,  0,  0,  0,  0,  8,  8, 19,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0, 14,  0, 23,  0, 15,  0,  0,  0,  8,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0
];

   // There's tons of stuff in this object,
   //  but only these three functions are the public API.
   this.saveState = getState;
   this.loadState = setState;
   this.reset = reset;
   this.advanceInsn = run_instruction;
   this.interrupt = interrupt;
   this.getPC = ():number => { return pc; }
   this.getSP = ():number => { return sp; }
   this.getHalted = ():boolean => { return halted; }
}

export interface Z80State {
AF : number;
BC : number;
DE : number;
HL : number;
AF_ : number;
BC_ : number;
DE_ : number;
HL_ : number;
IX : number;
IY : number;
SP : number;
PC : number;
IR : number;
iff1 : number;
iff2 : number;
im : number;
halted : boolean;
do_delayed_di : boolean;
do_delayed_ei : boolean;
cycle_counter : number;
}

export class Z80 implements CPU, InstructionBased, IOBusConnected, SavesState<Z80State>, Interruptable<number> {

  cpu;
  interruptType;
  memBus : Bus;
  ioBus : Bus;
  retryInterrupts : boolean = false;
  retryData : number = -1;
  
  private buildCPU() {
    if (this.memBus && this.ioBus) {
      this.cpu = new FastZ80({
        mem_read: this.memBus.read.bind(this.memBus),
        mem_write: this.memBus.write.bind(this.memBus),
        io_read: this.ioBus.read.bind(this.ioBus),
        io_write: this.ioBus.write.bind(this.ioBus),
      });
    }
  }
  connectMemoryBus(bus:Bus) {
    this.memBus = bus;
    this.buildCPU();
  }
  connectIOBus(bus:Bus) {
    this.ioBus = bus;
    this.buildCPU();
  }
  advanceInsn() {
    if (this.retryInterrupts && this.retryData >= 0 && this.cpu.interrupt(false, this.retryData)) {
     this.retryData = -1;
    }
    return this.cpu.advanceInsn();
  }
  reset() {
    this.cpu.reset();
  }
  interrupt(data:number) {
    if (!this.cpu.interrupt(false, data) && this.retryInterrupts) {
     this.retryData = data;
    }
  }
  NMI() {
    this.cpu.interrupt(true, 0);
  }
  getSP() {
    return this.cpu.getSP();
  }
  getPC() {
    return this.cpu.getPC();
  }
  isHalted() {
   return this.cpu.getHalted();
  }
  saveState() {
    return this.cpu.saveState();
  }
  loadState(s) {
    this.cpu.loadState(s);
  }
  isStable() { return true; }
  // TODO: metadata
  // TODO: disassembler
}
