// 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; }; /////////////////////////////////////////////////////////////////////////////// /// 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,BC,DE,HL,AF_,BC_,DE_,HL_,IX,IY,SP,PC,IR : number; iff1,iff2,im : number; halted : boolean; do_delayed_di,do_delayed_ei : boolean; cycle_counter : number; } export class Z80 implements CPU, InstructionBased, IOBusConnected, SavesState, Interruptable { 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 }