UNPKG

zx-generation

Version:

A high-fidelity ZX Spectrum emulator in JavaScript — fully generated by a large language model (LLM) to explore the boundaries of AI in systems programming.

1,026 lines (907 loc) 46.4 kB
/** * Complete Instruction Decoder * Comprehensive opcode decoding and dispatch system for Z80 emulator * * Timing philosophy: Each instruction handler returns its TOTAL cycle count. * The decoder does NOT add extra cycles - it trusts the instruction's return value. */ class InstructionDecoder { constructor(registers, flags, memory, io, instructions) { this.registers = registers; this.flags = flags; this.memory = memory; this.io = io; this.instructions = instructions; // Build comprehensive instruction lookup tables this.buildAllInstructionTables(); } buildAllInstructionTables() { this.mainTable = {}; this.cbTable = {}; this.edTable = {}; this.ddTable = {}; this.fdTable = {}; this.ddcbTable = {}; this.fdcbTable = {}; this.buildMainTable(); this.buildCBTable(); this.buildEDTable(); this.buildDDTable(); this.buildFDTable(); } buildMainTable() { const { logical, load } = this.instructions; // 8x8 grid of register-to-register loads (0x40-0x7F) const regMap = ['B', 'C', 'D', 'E', 'H', 'L', null, 'A']; for (let dest = 0; dest < 8; dest++) { for (let src = 0; src < 8; src++) { if (dest === 6 && src === 6) { this.mainTable[0x76] = cpu => this.instructions.misc.halt(cpu); // HALT continue; } const opcode = 0x40 + (dest << 3) + src; if (dest === 6) { // LD (HL), reg this.mainTable[opcode] = () => load.loadHLFromReg(regMap[src]); } else if (src === 6) { // LD reg, (HL) this.mainTable[opcode] = () => load.loadRegFromHL(regMap[dest]); } else { // LD reg, reg this.mainTable[opcode] = () => load.loadRegReg(regMap[dest], regMap[src]); } } } // Arithmetic operations (0x80-0xBF) for (let i = 0; i < 8; i++) { const reg = regMap[i]; if (reg) { // Arithmetic with registers - handlers return their own cycle count this.mainTable[0x80 + i] = () => this.instructions.arithmetic.addA(this.registers.get(reg)); this.mainTable[0x88 + i] = () => this.instructions.arithmetic.adcA(this.registers.get(reg)); this.mainTable[0x90 + i] = () => this.instructions.arithmetic.subA(this.registers.get(reg)); this.mainTable[0x98 + i] = () => this.instructions.arithmetic.sbcA(this.registers.get(reg)); this.mainTable[0xa0 + i] = () => logical.andA(this.registers.get(reg)); this.mainTable[0xa8 + i] = () => logical.xorA(this.registers.get(reg)); this.mainTable[0xb0 + i] = () => logical.orA(this.registers.get(reg)); this.mainTable[0xb8 + i] = () => this.instructions.arithmetic.cpA(this.registers.get(reg)); } else if (i === 6) { // Arithmetic with (HL) this.mainTable[0x86] = () => { this.instructions.arithmetic.addA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0x8e] = () => { this.instructions.arithmetic.adcA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0x96] = () => { this.instructions.arithmetic.subA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0x9e] = () => { this.instructions.arithmetic.sbcA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0xa6] = () => { logical.andA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0xae] = () => { logical.xorA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0xb6] = () => { logical.orA(this.memory.readByte(this.registers.getHL())); return 7; }; this.mainTable[0xbe] = () => { this.instructions.arithmetic.cpA(this.memory.readByte(this.registers.getHL())); return 7; }; } } // All other main instructions this.buildMainInstructions(); } buildMainInstructions() { const { logical, load, jump, bit } = this.instructions; // Basic instructions this.mainTable[0x00] = () => this.instructions.misc.nop(); // NOP // 16-bit loads this.mainTable[0x01] = () => load.loadReg16Immediate('BC', this.memory.fetchWord(this.registers)); this.mainTable[0x11] = () => load.loadReg16Immediate('DE', this.memory.fetchWord(this.registers)); this.mainTable[0x21] = () => load.loadReg16Immediate('HL', this.memory.fetchWord(this.registers)); this.mainTable[0x31] = () => load.loadReg16Immediate('SP', this.memory.fetchWord(this.registers)); // 8-bit immediate loads this.mainTable[0x06] = () => load.loadRegImmediate('B', this.memory.fetchByte(this.registers)); this.mainTable[0x0e] = () => load.loadRegImmediate('C', this.memory.fetchByte(this.registers)); this.mainTable[0x16] = () => load.loadRegImmediate('D', this.memory.fetchByte(this.registers)); this.mainTable[0x1e] = () => load.loadRegImmediate('E', this.memory.fetchByte(this.registers)); this.mainTable[0x26] = () => load.loadRegImmediate('H', this.memory.fetchByte(this.registers)); this.mainTable[0x2e] = () => load.loadRegImmediate('L', this.memory.fetchByte(this.registers)); this.mainTable[0x3e] = () => load.loadRegImmediate('A', this.memory.fetchByte(this.registers)); this.mainTable[0x36] = () => load.loadHLImmediate(this.memory.fetchByte(this.registers)); // Increments and decrements this.mainTable[0x04] = () => this.instructions.arithmetic.incReg('B'); this.mainTable[0x0c] = () => this.instructions.arithmetic.incReg('C'); this.mainTable[0x14] = () => this.instructions.arithmetic.incReg('D'); this.mainTable[0x1c] = () => this.instructions.arithmetic.incReg('E'); this.mainTable[0x24] = () => this.instructions.arithmetic.incReg('H'); this.mainTable[0x2c] = () => this.instructions.arithmetic.incReg('L'); this.mainTable[0x3c] = () => this.instructions.arithmetic.incReg('A'); this.mainTable[0x34] = () => this.instructions.arithmetic.incHL(); this.mainTable[0x05] = () => this.instructions.arithmetic.decReg('B'); this.mainTable[0x0d] = () => this.instructions.arithmetic.decReg('C'); this.mainTable[0x15] = () => this.instructions.arithmetic.decReg('D'); this.mainTable[0x1d] = () => this.instructions.arithmetic.decReg('E'); this.mainTable[0x25] = () => this.instructions.arithmetic.decReg('H'); this.mainTable[0x2d] = () => this.instructions.arithmetic.decReg('L'); this.mainTable[0x3d] = () => this.instructions.arithmetic.decReg('A'); this.mainTable[0x35] = () => this.instructions.arithmetic.decHL(); // 16-bit arithmetic this.mainTable[0x03] = () => { this.registers.inc16('BC'); return 6; }; this.mainTable[0x13] = () => { this.registers.inc16('DE'); return 6; }; this.mainTable[0x23] = () => { this.registers.inc16('HL'); return 6; }; this.mainTable[0x33] = () => { this.registers.inc16('SP'); return 6; }; this.mainTable[0x0b] = () => { this.registers.dec16('BC'); return 6; }; this.mainTable[0x1b] = () => { this.registers.dec16('DE'); return 6; }; this.mainTable[0x2b] = () => { this.registers.dec16('HL'); return 6; }; this.mainTable[0x3b] = () => { this.registers.dec16('SP'); return 6; }; // ADD HL, reg16 this.mainTable[0x09] = () => this.instructions.arithmetic.addHL(this.registers.getBC()); this.mainTable[0x19] = () => this.instructions.arithmetic.addHL(this.registers.getDE()); this.mainTable[0x29] = () => this.instructions.arithmetic.addHL(this.registers.getHL()); this.mainTable[0x39] = () => this.instructions.arithmetic.addHL(this.registers.get16('SP')); // Immediate arithmetic - handlers return base cycles, we add fetch cycles this.mainTable[0xc6] = () => { this.instructions.arithmetic.addA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xce] = () => { this.instructions.arithmetic.adcA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xd6] = () => { this.instructions.arithmetic.subA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xde] = () => { this.instructions.arithmetic.sbcA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xe6] = () => { logical.andA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xee] = () => { logical.xorA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xf6] = () => { logical.orA(this.memory.fetchByte(this.registers)); return 7; }; this.mainTable[0xfe] = () => { this.instructions.arithmetic.cpA(this.memory.fetchByte(this.registers)); return 7; }; // Jumps and calls this.mainTable[0xc3] = () => jump.jump(this.memory.fetchWord(this.registers)); this.mainTable[0x18] = () => jump.jumpRelative(this.memory.fetchByte(this.registers)); this.mainTable[0xe9] = () => jump.jumpHL(); this.mainTable[0xcd] = () => jump.call(this.memory.fetchWord(this.registers)); this.mainTable[0xc9] = () => jump.ret(); // Conditional operations this.addConditionalInstructions(); // Stack operations this.mainTable[0xc5] = () => this.instructions.misc.push('BC'); this.mainTable[0xd5] = () => this.instructions.misc.push('DE'); this.mainTable[0xe5] = () => this.instructions.misc.push('HL'); this.mainTable[0xf5] = () => this.instructions.misc.push('AF'); this.mainTable[0xc1] = () => this.instructions.misc.pop('BC'); this.mainTable[0xd1] = () => this.instructions.misc.pop('DE'); this.mainTable[0xe1] = () => this.instructions.misc.pop('HL'); this.mainTable[0xf1] = () => this.instructions.misc.pop('AF'); // Rotates this.mainTable[0x07] = () => bit.rlca(); this.mainTable[0x0f] = () => bit.rrca(); this.mainTable[0x17] = () => bit.rla(); this.mainTable[0x1f] = () => bit.rra(); // Misc operations this.mainTable[0x37] = () => logical.scf(); this.mainTable[0x3f] = () => logical.ccf(); this.mainTable[0x27] = () => logical.daa(); this.mainTable[0x2f] = () => logical.cpl(); // Interrupt control this.mainTable[0xf3] = cpu => this.instructions.misc.di(cpu); this.mainTable[0xfb] = cpu => this.instructions.misc.ei(cpu); // Exchange operations this.mainTable[0x08] = () => this.instructions.misc.exAF(); this.mainTable[0xd9] = () => this.instructions.misc.exx(); this.mainTable[0xeb] = () => this.instructions.misc.exDEHL(); this.mainTable[0xe3] = () => this.instructions.misc.exSPHL(); // I/O this.mainTable[0xdb] = () => this.instructions.misc.inAImmediate(this.memory.fetchByte(this.registers), this.io); this.mainTable[0xd3] = () => this.instructions.misc.outAImmediate(this.memory.fetchByte(this.registers), this.io); // Memory operations this.addMemoryInstructions(); // RST instructions this.addRSTInstructions(); // DJNZ this.mainTable[0x10] = () => jump.djnz(this.memory.fetchByte(this.registers)); // LD SP, HL this.mainTable[0xf9] = () => load.loadSPFromHL(); // Prefixed instructions this.mainTable[0xcb] = cpu => this.executeCBInstruction(cpu); this.mainTable[0xed] = cpu => this.executeEDInstruction(cpu); this.mainTable[0xdd] = cpu => this.executeDDInstruction(cpu); this.mainTable[0xfd] = cpu => this.executeFDInstruction(cpu); } addConditionalInstructions() { const { jump } = this.instructions; // Conditional jumps this.mainTable[0xc2] = () => jump.jumpConditional('NZ', this.memory.fetchWord(this.registers)); this.mainTable[0xca] = () => jump.jumpConditional('Z', this.memory.fetchWord(this.registers)); this.mainTable[0xd2] = () => jump.jumpConditional('NC', this.memory.fetchWord(this.registers)); this.mainTable[0xda] = () => jump.jumpConditional('C', this.memory.fetchWord(this.registers)); this.mainTable[0xe2] = () => jump.jumpConditional('PO', this.memory.fetchWord(this.registers)); this.mainTable[0xea] = () => jump.jumpConditional('PE', this.memory.fetchWord(this.registers)); this.mainTable[0xf2] = () => jump.jumpConditional('P', this.memory.fetchWord(this.registers)); this.mainTable[0xfa] = () => jump.jumpConditional('M', this.memory.fetchWord(this.registers)); // Conditional relative jumps this.mainTable[0x20] = () => jump.jumpRelativeConditional('NZ', this.memory.fetchByte(this.registers)); this.mainTable[0x28] = () => jump.jumpRelativeConditional('Z', this.memory.fetchByte(this.registers)); this.mainTable[0x30] = () => jump.jumpRelativeConditional('NC', this.memory.fetchByte(this.registers)); this.mainTable[0x38] = () => jump.jumpRelativeConditional('C', this.memory.fetchByte(this.registers)); // Conditional calls this.mainTable[0xc4] = () => jump.callConditional('NZ', this.memory.fetchWord(this.registers)); this.mainTable[0xcc] = () => jump.callConditional('Z', this.memory.fetchWord(this.registers)); this.mainTable[0xd4] = () => jump.callConditional('NC', this.memory.fetchWord(this.registers)); this.mainTable[0xdc] = () => jump.callConditional('C', this.memory.fetchWord(this.registers)); this.mainTable[0xe4] = () => jump.callConditional('PO', this.memory.fetchWord(this.registers)); this.mainTable[0xec] = () => jump.callConditional('PE', this.memory.fetchWord(this.registers)); this.mainTable[0xf4] = () => jump.callConditional('P', this.memory.fetchWord(this.registers)); this.mainTable[0xfc] = () => jump.callConditional('M', this.memory.fetchWord(this.registers)); // Conditional returns this.mainTable[0xc0] = () => jump.retConditional('NZ'); this.mainTable[0xc8] = () => jump.retConditional('Z'); this.mainTable[0xd0] = () => jump.retConditional('NC'); this.mainTable[0xd8] = () => jump.retConditional('C'); this.mainTable[0xe0] = () => jump.retConditional('PO'); this.mainTable[0xe8] = () => jump.retConditional('PE'); this.mainTable[0xf0] = () => jump.retConditional('P'); this.mainTable[0xf8] = () => jump.retConditional('M'); } addMemoryInstructions() { const { load } = this.instructions; this.mainTable[0x02] = () => load.loadBCFromA(); this.mainTable[0x12] = () => load.loadDEFromA(); this.mainTable[0x0a] = () => load.loadAFromBC(); this.mainTable[0x1a] = () => load.loadAFromDE(); this.mainTable[0x22] = () => load.loadAddressFromHL(this.memory.fetchWord(this.registers)); this.mainTable[0x2a] = () => load.loadHLFromAddress(this.memory.fetchWord(this.registers)); this.mainTable[0x32] = () => load.loadAddressFromA(this.memory.fetchWord(this.registers)); this.mainTable[0x3a] = () => load.loadAFromAddress(this.memory.fetchWord(this.registers)); this.mainTable[0x77] = () => load.loadHLFromReg('A'); this.mainTable[0x7e] = () => load.loadRegFromHL('A'); } addRSTInstructions() { const { jump } = this.instructions; this.mainTable[0xc7] = () => jump.rst(0x00); this.mainTable[0xcf] = () => jump.rst(0x08); this.mainTable[0xd7] = () => jump.rst(0x10); this.mainTable[0xdf] = () => jump.rst(0x18); this.mainTable[0xe7] = () => jump.rst(0x20); this.mainTable[0xef] = () => jump.rst(0x28); this.mainTable[0xf7] = () => jump.rst(0x30); this.mainTable[0xff] = () => jump.rst(0x38); } buildCBTable() { const { bit } = this.instructions; const regMap = ['B', 'C', 'D', 'E', 'H', 'L', null, 'A']; // Build all CB instructions systematically for (let opcode = 0x00; opcode <= 0xff; opcode++) { const reg = regMap[opcode & 0x07]; const isHL = (opcode & 0x07) === 6; if ((opcode & 0xc0) === 0x40) { // BIT operations (0x40-0x7F) const bitNum = (opcode >> 3) & 0x07; if (isHL) { this.cbTable[opcode] = () => { return bit.bitTest(bitNum, this.memory.readByte(this.registers.getHL()), true); }; } else { this.cbTable[opcode] = () => { return bit.bitTest(bitNum, this.registers.get(reg), false); }; } } else if ((opcode & 0xc0) === 0x80) { // RES operations (0x80-0xBF) const bitNum = (opcode >> 3) & 0x07; if (isHL) { this.cbTable[opcode] = () => bit.resBitHL(bitNum); } else { this.cbTable[opcode] = () => bit.resBitReg(bitNum, reg); } } else if ((opcode & 0xc0) === 0xc0) { // SET operations (0xC0-0xFF) const bitNum = (opcode >> 3) & 0x07; if (isHL) { this.cbTable[opcode] = () => bit.setBitHL(bitNum); } else { this.cbTable[opcode] = () => bit.setBitReg(bitNum, reg); } } else { // Rotate/Shift operations (0x00-0x3F) const operation = opcode & 0xf8; let opName; switch (operation) { case 0x00: opName = 'RLC'; break; case 0x08: opName = 'RRC'; break; case 0x10: opName = 'RL'; break; case 0x18: opName = 'RR'; break; case 0x20: opName = 'SLA'; break; case 0x28: opName = 'SRA'; break; case 0x30: opName = 'SLL'; break; case 0x38: opName = 'SRL'; break; default: continue; } if (isHL) { this.cbTable[opcode] = () => bit.processHL(opName); } else { this.cbTable[opcode] = () => bit.processRegister(opName, reg); } } } } buildEDTable() { const { extended } = this.instructions; // Block operations this.edTable[0xa0] = () => extended.ldi(); this.edTable[0xb0] = () => extended.ldir(); this.edTable[0xa8] = () => extended.ldd(); this.edTable[0xb8] = () => extended.lddr(); this.edTable[0xa1] = () => extended.cpi(); this.edTable[0xb1] = () => extended.cpir(); this.edTable[0xa9] = () => extended.cpd(); this.edTable[0xb9] = () => extended.cpdr(); // 16-bit arithmetic this.edTable[0x42] = () => extended.sbcHL(this.registers.getBC()); this.edTable[0x52] = () => extended.sbcHL(this.registers.getDE()); this.edTable[0x62] = () => extended.sbcHL(this.registers.getHL()); this.edTable[0x72] = () => extended.sbcHL(this.registers.get16('SP')); this.edTable[0x4a] = () => extended.adcHL(this.registers.getBC()); this.edTable[0x5a] = () => extended.adcHL(this.registers.getDE()); this.edTable[0x6a] = () => extended.adcHL(this.registers.getHL()); this.edTable[0x7a] = () => extended.adcHL(this.registers.get16('SP')); // Decimal operations this.edTable[0x6f] = () => extended.rld(); this.edTable[0x67] = () => extended.rrd(); // NEG for (let i = 0x44; i <= 0x7c; i += 8) { this.edTable[i] = () => this.instructions.arithmetic.neg(); } // I/O operations const regMap = ['B', 'C', 'D', 'E', 'H', 'L', null, 'A']; for (let i = 0; i < 8; i++) { const reg = regMap[i]; if (reg) { this.edTable[0x40 + (i << 3)] = () => this.instructions.misc.inRegC(reg, this.io, this.flags); this.edTable[0x41 + (i << 3)] = () => this.instructions.misc.outRegC(reg, this.io); } else { // Special case for F register this.edTable[0x70] = () => this.instructions.misc.inRegC(null, this.io, this.flags); this.edTable[0x71] = () => this.instructions.misc.outRegC(null, this.io); } } // I/O block operations this.edTable[0xa2] = () => extended.ini(); this.edTable[0xb2] = () => extended.inir(); this.edTable[0xa3] = () => extended.outi(); this.edTable[0xb3] = () => extended.otir(); this.edTable[0xaa] = () => extended.ind(); this.edTable[0xba] = () => extended.indr(); this.edTable[0xab] = () => extended.outd(); this.edTable[0xbb] = () => extended.otdr(); // Interrupt mode this.addInterruptModeInstructions(); // Return instructions this.addEDReturnInstructions(); // 16-bit load operations this.addED16BitLoads(); // Add undocumented ED instructions this.addUndocumentedEDInstructions(); } addInterruptModeInstructions() { // IM 0 this.edTable[0x46] = this.edTable[0x4e] = this.edTable[0x66] = this.edTable[0x6e] = cpu => this.instructions.misc.setInterruptMode(0, cpu); // IM 1 this.edTable[0x56] = this.edTable[0x76] = cpu => this.instructions.misc.setInterruptMode(1, cpu); // IM 2 this.edTable[0x5e] = this.edTable[0x7e] = cpu => this.instructions.misc.setInterruptMode(2, cpu); } addEDReturnInstructions() { const { jump } = this.instructions; // RETI this.edTable[0x4d] = () => jump.reti(); // RETN this.edTable[0x45] = this.edTable[0x55] = this.edTable[0x5d] = this.edTable[0x65] = this.edTable[0x6d] = this.edTable[0x75] = this.edTable[0x7d] = cpu => this.instructions.jump.retn(cpu); } addED16BitLoads() { const { load } = this.instructions; // LD (nn), reg16 this.edTable[0x43] = () => load.loadAddressFromReg16(this.memory.fetchWord(this.registers), 'BC'); this.edTable[0x53] = () => load.loadAddressFromReg16(this.memory.fetchWord(this.registers), 'DE'); this.edTable[0x63] = () => load.loadAddressFromReg16(this.memory.fetchWord(this.registers), 'HL'); this.edTable[0x73] = () => load.loadAddressFromReg16(this.memory.fetchWord(this.registers), 'SP'); // LD reg16, (nn) this.edTable[0x4b] = () => load.loadReg16FromAddress('BC', this.memory.fetchWord(this.registers)); this.edTable[0x5b] = () => load.loadReg16FromAddress('DE', this.memory.fetchWord(this.registers)); this.edTable[0x6b] = () => load.loadReg16FromAddress('HL', this.memory.fetchWord(this.registers)); this.edTable[0x7b] = () => load.loadReg16FromAddress('SP', this.memory.fetchWord(this.registers)); // LD I/R operations this.edTable[0x47] = () => load.loadIFromA(); this.edTable[0x4f] = () => load.loadRFromA(); this.edTable[0x57] = () => load.loadAFromI(); this.edTable[0x5f] = () => load.loadAFromR(); } addUndocumentedEDInstructions() { // Add all undocumented ED instructions as NOPs const undocumentedOpcodes = [ // Row 0x0X - all undocumented 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // Row 0x1X - all undocumented 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // Row 0x2X - all undocumented 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, // Row 0x3X - all undocumented 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, // Additional undocumented opcodes 0x77, 0x7f, // Row 0x8X - all undocumented 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, // Row 0x9X - all undocumented 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, // Row 0xAX gaps 0xa4, 0xa5, 0xa6, 0xa7, 0xac, 0xad, 0xae, 0xaf, // Row 0xBX gaps 0xb4, 0xb5, 0xb6, 0xb7, 0xbc, 0xbd, 0xbe, 0xbf, // Row 0xCX - all undocumented 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, // Row 0xDX - all undocumented 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, // Row 0xEX - all undocumented 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, // Row 0xFX - all undocumented 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, ]; // Add all undocumented opcodes as NOPs (they don't modify state) undocumentedOpcodes.forEach(opcode => { if (!this.edTable[opcode]) { this.edTable[opcode] = _cpu => { // Undocumented ED instruction - acts as 2-byte NOP return 8; // Standard ED instruction timing }; } }); } buildDDTable() { // DD prefix instructions for IX operations this.buildIndexedTable('IX', this.ddTable); } buildFDTable() { // FD prefix instructions for IY operations this.buildIndexedTable('IY', this.fdTable); } buildIndexedTable(indexReg, table) { const { indexed, load } = this.instructions; // Basic IX/IY operations table[0x21] = () => load.loadReg16Immediate(indexReg, this.memory.fetchWord(this.registers)); table[0x22] = () => load.loadAddressFromReg16(this.memory.fetchWord(this.registers), indexReg); table[0x2a] = () => load.loadReg16FromAddress(indexReg, this.memory.fetchWord(this.registers)); table[0x23] = () => { this.registers.inc16(indexReg); return 10; }; table[0x2b] = () => { this.registers.dec16(indexReg); return 10; }; // ADD IX/IY, reg16 table[0x09] = () => indexed.addIndex(indexReg, this.registers.getBC()); table[0x19] = () => indexed.addIndex(indexReg, this.registers.getDE()); table[0x29] = () => indexed.addIndex(indexReg, this.registers.get16(indexReg)); table[0x39] = () => indexed.addIndex(indexReg, this.registers.get16('SP')); // Indexed memory operations table[0x7e] = () => indexed.loadRegFromIndexed('A', indexReg, this.memory.fetchByte(this.registers)); table[0x77] = () => indexed.loadIndexedFromReg(indexReg, this.memory.fetchByte(this.registers), 'A'); table[0x36] = () => { const disp = this.memory.fetchByte(this.registers); const value = this.memory.fetchByte(this.registers); return indexed.loadIndexedImmediate(indexReg, disp, value); }; // More indexed loads this.addIndexedLoads(table, indexReg, indexed); // Indexed arithmetic this.addIndexedArithmetic(table, indexReg, indexed); // Misc indexed operations table[0xe9] = () => indexed.jumpIndexed(indexReg); table[0xe3] = () => indexed.exchangeSPIndexed(indexReg); table[0xe5] = () => indexed.pushIndexed(indexReg); table[0xe1] = () => indexed.popIndexed(indexReg); // CB prefix for indexed bit operations table[0xcb] = _cpu => this.executeIndexedCBInstruction(_cpu, indexReg); } addIndexedLoads(table, indexReg, indexed) { const regMap = ['B', 'C', 'D', 'E', 'H', 'L', null, 'A']; // LD reg, (IX/IY+d) const loadOpcodes = [0x46, 0x4e, 0x56, 0x5e, 0x66, 0x6e, null, 0x7e]; for (let i = 0; i < loadOpcodes.length; i++) { if (loadOpcodes[i] && regMap[i]) { table[loadOpcodes[i]] = () => { const disp = this.memory.fetchByte(this.registers); return indexed.loadRegFromIndexed(regMap[i], indexReg, disp); }; } } // LD (IX/IY+d), reg const storeOpcodes = [0x70, 0x71, 0x72, 0x73, 0x74, 0x75, null, 0x77]; for (let i = 0; i < storeOpcodes.length; i++) { if (storeOpcodes[i] && regMap[i]) { table[storeOpcodes[i]] = () => { const disp = this.memory.fetchByte(this.registers); return indexed.loadIndexedFromReg(indexReg, disp, regMap[i]); }; } } } addIndexedArithmetic(table, indexReg, indexed) { // Arithmetic with (IX/IY+d) table[0x86] = () => indexed.addAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0x8e] = () => indexed.adcAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0x96] = () => indexed.subAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0x9e] = () => indexed.sbcAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0xa6] = () => indexed.andAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0xae] = () => indexed.xorAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0xb6] = () => indexed.orAIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0xbe] = () => indexed.cpAIndexed(indexReg, this.memory.fetchByte(this.registers)); // INC/DEC (IX/IY+d) table[0x34] = () => indexed.incIndexed(indexReg, this.memory.fetchByte(this.registers)); table[0x35] = () => indexed.decIndexed(indexReg, this.memory.fetchByte(this.registers)); } // Main instruction execution execute(opcode, cpu = null) { const handler = this.mainTable[opcode]; if (handler) { return handler(cpu); } else { console.warn( `Unimplemented opcode: 0x${opcode.toString(16).padStart(2, '0')} at PC: 0x${(this.registers.getPC() - 1).toString(16).padStart(4, '0')}` ); return 4; // Default cycles } } // Prefixed instruction handlers executeCBInstruction() { this.registers.incrementR(); const cbOpcode = this.memory.fetchByte(this.registers); const handler = this.cbTable[cbOpcode]; if (handler) { return 4 + handler(); // 4 cycles for CB prefix + instruction cycles } else { console.warn(`Unimplemented CB opcode: 0x${cbOpcode.toString(16).padStart(2, '0')}`); return 8; } } executeEDInstruction(cpu) { this.registers.incrementR(); const edOpcode = this.memory.fetchByte(this.registers); const handler = this.edTable[edOpcode]; if (handler) { return 4 + handler(cpu); // 4 cycles for ED prefix + instruction cycles } else { // Many ED opcodes act as NOPs return 8; } } executeDDInstruction(cpu) { const ddOpcode = this.memory.fetchByte(this.registers); const handler = this.ddTable[ddOpcode]; if (handler) { this.registers.incrementR(); // Only increment R if we handle the instruction return 4 + handler(cpu); // 4 cycles for DD prefix + instruction cycles } else { // Handle undocumented IXH/IXL opcodes switch (ddOpcode) { // LD reg,IXH case 0x44: this.registers.set('B', this.registers.getIXH()); return 8; case 0x4C: this.registers.set('C', this.registers.getIXH()); return 8; case 0x54: this.registers.set('D', this.registers.getIXH()); return 8; case 0x5C: this.registers.set('E', this.registers.getIXH()); return 8; case 0x7C: this.registers.set('A', this.registers.getIXH()); return 8; // LD reg,IXL case 0x45: this.registers.set('B', this.registers.getIXL()); return 8; case 0x4D: this.registers.set('C', this.registers.getIXL()); return 8; case 0x55: this.registers.set('D', this.registers.getIXL()); return 8; case 0x5D: this.registers.set('E', this.registers.getIXL()); return 8; case 0x7D: this.registers.set('A', this.registers.getIXL()); return 8; // LD IXH,reg case 0x60: this.registers.setIXH(this.registers.get('B')); return 8; case 0x61: this.registers.setIXH(this.registers.get('C')); return 8; case 0x62: this.registers.setIXH(this.registers.get('D')); return 8; case 0x63: this.registers.setIXH(this.registers.get('E')); return 8; case 0x67: this.registers.setIXH(this.registers.get('A')); return 8; // LD IXL,reg case 0x68: this.registers.setIXL(this.registers.get('B')); return 8; case 0x69: this.registers.setIXL(this.registers.get('C')); return 8; case 0x6A: this.registers.setIXL(this.registers.get('D')); return 8; case 0x6B: this.registers.setIXL(this.registers.get('E')); return 8; case 0x6F: this.registers.setIXL(this.registers.get('A')); return 8; // LD IXH,IXH / LD IXL,IXL (NOPs effectively) case 0x64: return 8; // LD IXH,IXH case 0x6D: return 8; // LD IXL,IXL // LD IXH,IXL / LD IXL,IXH case 0x65: this.registers.setIXH(this.registers.getIXL()); return 8; case 0x6C: this.registers.setIXL(this.registers.getIXH()); return 8; // Arithmetic with IXH case 0x84: this.instructions.arithmetic.addA(this.registers.getIXH()); return 8; case 0x85: this.instructions.arithmetic.addA(this.registers.getIXL()); return 8; case 0x8C: this.instructions.arithmetic.adcA(this.registers.getIXH()); return 8; case 0x8D: this.instructions.arithmetic.adcA(this.registers.getIXL()); return 8; case 0x94: this.instructions.arithmetic.subA(this.registers.getIXH()); return 8; case 0x95: this.instructions.arithmetic.subA(this.registers.getIXL()); return 8; case 0x9C: this.instructions.arithmetic.sbcA(this.registers.getIXH()); return 8; case 0x9D: this.instructions.arithmetic.sbcA(this.registers.getIXL()); return 8; // Logical with IXH/IXL case 0xA4: this.instructions.logical.andA(this.registers.getIXH()); return 8; case 0xA5: this.instructions.logical.andA(this.registers.getIXL()); return 8; case 0xAC: this.instructions.logical.xorA(this.registers.getIXH()); return 8; case 0xAD: this.instructions.logical.xorA(this.registers.getIXL()); return 8; case 0xB4: this.instructions.logical.orA(this.registers.getIXH()); return 8; case 0xB5: this.instructions.logical.orA(this.registers.getIXL()); return 8; case 0xBC: this.instructions.arithmetic.cpA(this.registers.getIXH()); return 8; case 0xBD: this.instructions.arithmetic.cpA(this.registers.getIXL()); return 8; // INC/DEC IXH/IXL case 0x24: { const result = this.instructions.arithmetic.inc8(this.registers.getIXH()); this.registers.setIXH(result); return 8; } case 0x25: { const result = this.instructions.arithmetic.dec8(this.registers.getIXH()); this.registers.setIXH(result); return 8; } case 0x2C: { const result = this.instructions.arithmetic.inc8(this.registers.getIXL()); this.registers.setIXL(result); return 8; } case 0x2D: { const result = this.instructions.arithmetic.dec8(this.registers.getIXL()); this.registers.setIXL(result); return 8; } // LD IXH/IXL,n case 0x26: this.registers.setIXH(this.memory.fetchByte(this.registers)); return 11; case 0x2E: this.registers.setIXL(this.memory.fetchByte(this.registers)); return 11; default: // If no DD handler, execute as normal instruction WITHOUT the prefix // Don't increment R again since main execute will do it return this.execute(ddOpcode, cpu); } } } executeFDInstruction(cpu) { const fdOpcode = this.memory.fetchByte(this.registers); const handler = this.fdTable[fdOpcode]; if (handler) { this.registers.incrementR(); // Only increment R if we handle the instruction return 4 + handler(cpu); // 4 cycles for FD prefix + instruction cycles } else { // Handle undocumented IYH/IYL opcodes switch (fdOpcode) { // LD reg,IYH case 0x44: this.registers.set('B', this.registers.getIYH()); return 8; case 0x4C: this.registers.set('C', this.registers.getIYH()); return 8; case 0x54: this.registers.set('D', this.registers.getIYH()); return 8; case 0x5C: this.registers.set('E', this.registers.getIYH()); return 8; case 0x7C: this.registers.set('A', this.registers.getIYH()); return 8; // LD reg,IYL case 0x45: this.registers.set('B', this.registers.getIYL()); return 8; case 0x4D: this.registers.set('C', this.registers.getIYL()); return 8; case 0x55: this.registers.set('D', this.registers.getIYL()); return 8; case 0x5D: this.registers.set('E', this.registers.getIYL()); return 8; case 0x7D: this.registers.set('A', this.registers.getIYL()); return 8; // LD IYH,reg case 0x60: this.registers.setIYH(this.registers.get('B')); return 8; case 0x61: this.registers.setIYH(this.registers.get('C')); return 8; case 0x62: this.registers.setIYH(this.registers.get('D')); return 8; case 0x63: this.registers.setIYH(this.registers.get('E')); return 8; case 0x67: this.registers.setIYH(this.registers.get('A')); return 8; // LD IYL,reg case 0x68: this.registers.setIYL(this.registers.get('B')); return 8; case 0x69: this.registers.setIYL(this.registers.get('C')); return 8; case 0x6A: this.registers.setIYL(this.registers.get('D')); return 8; case 0x6B: this.registers.setIYL(this.registers.get('E')); return 8; case 0x6F: this.registers.setIYL(this.registers.get('A')); return 8; // LD IYH,IYH / LD IYL,IYL (NOPs effectively) case 0x64: return 8; // LD IYH,IYH case 0x6D: return 8; // LD IYL,IYL // LD IYH,IYL / LD IYL,IYH case 0x65: this.registers.setIYH(this.registers.getIYL()); return 8; case 0x6C: this.registers.setIYL(this.registers.getIYH()); return 8; // Arithmetic with IYH case 0x84: this.instructions.arithmetic.addA(this.registers.getIYH()); return 8; case 0x85: this.instructions.arithmetic.addA(this.registers.getIYL()); return 8; case 0x8C: this.instructions.arithmetic.adcA(this.registers.getIYH()); return 8; case 0x8D: this.instructions.arithmetic.adcA(this.registers.getIYL()); return 8; case 0x94: this.instructions.arithmetic.subA(this.registers.getIYH()); return 8; case 0x95: this.instructions.arithmetic.subA(this.registers.getIYL()); return 8; case 0x9C: this.instructions.arithmetic.sbcA(this.registers.getIYH()); return 8; case 0x9D: this.instructions.arithmetic.sbcA(this.registers.getIYL()); return 8; // Logical with IYH/IYL case 0xA4: this.instructions.logical.andA(this.registers.getIYH()); return 8; case 0xA5: this.instructions.logical.andA(this.registers.getIYL()); return 8; case 0xAC: this.instructions.logical.xorA(this.registers.getIYH()); return 8; case 0xAD: this.instructions.logical.xorA(this.registers.getIYL()); return 8; case 0xB4: this.instructions.logical.orA(this.registers.getIYH()); return 8; case 0xB5: this.instructions.logical.orA(this.registers.getIYL()); return 8; case 0xBC: this.instructions.arithmetic.cpA(this.registers.getIYH()); return 8; case 0xBD: this.instructions.arithmetic.cpA(this.registers.getIYL()); return 8; // INC/DEC IYH/IYL case 0x24: { const result = this.instructions.arithmetic.inc8(this.registers.getIYH()); this.registers.setIYH(result); return 8; } case 0x25: { const result = this.instructions.arithmetic.dec8(this.registers.getIYH()); this.registers.setIYH(result); return 8; } case 0x2C: { const result = this.instructions.arithmetic.inc8(this.registers.getIYL()); this.registers.setIYL(result); return 8; } case 0x2D: { const result = this.instructions.arithmetic.dec8(this.registers.getIYL()); this.registers.setIYL(result); return 8; } // LD IYH/IYL,n case 0x26: this.registers.setIYH(this.memory.fetchByte(this.registers)); return 11; case 0x2E: this.registers.setIYL(this.memory.fetchByte(this.registers)); return 11; default: // If no FD handler, execute as normal instruction WITHOUT the prefix // Don't increment R again since main execute will do it return this.execute(fdOpcode, cpu); } } } executeIndexedCBInstruction(_cpu, indexReg) { this.registers.incrementR(); const displacement = this.memory.fetchByte(this.registers); const cbOpcode = this.memory.fetchByte(this.registers); return 4 + this.instructions.indexed.processIndexedCB(indexReg, displacement, cbOpcode); } } export { InstructionDecoder };