using System; using Core.Interfaces; using Core.Io; namespace Core.Cpu { public partial class Z80 { private static readonly byte[] ParityTable = new byte[256]; // Static constructor to build the table once when the emulator starts static Z80() { for (int i = 0; i < 256; i++) { int ones = 0; for (int b = 0; b < 8; b++) if ((i & (1 << b)) != 0) ones++; ParityTable[i] = (byte)((ones % 2 == 0) ? 0x04 : 0x00); // 0x04 if Even Parity } } //T-State counter public long TotalTStates { get; set; } public int InterruptMode { get; private set; } = 0; // Interrupt Flip-Flops public bool IFF1 { get; private set; } = false; public bool IFF2 { get; private set; } = false; public bool InterruptRequested { get; private set; } = false; // Main Register Set public RegisterPair AF; public RegisterPair BC; public RegisterPair DE; public RegisterPair HL; // Alternate Register Set public RegisterPair AF_Prime; public RegisterPair BC_Prime; public RegisterPair DE_Prime; public RegisterPair HL_Prime; // Index Registers public RegisterPair IX; public RegisterPair IY; // Special Purpose Registers public ushort PC; // Program Counter public ushort SP; // Stack Pointer public byte I; // Interrupt Vector public byte R; // Memory Refresh // The Memory Bus private readonly IMemory _memory; private readonly IO_Bus _simpleIoBus; //External Timing interface public Func? WaitStateCallback { get; set; } public Z80(IMemory memory, IO_Bus ioBus) { _memory = memory; _simpleIoBus = ioBus; Reset(); } public void Reset() { PC = 0x0000; SP = 0xFFFF; // Main Registers AF.Word = 0; BC.Word = 0; DE.Word = 0; HL.Word = 0; // Alternate Registers AF_Prime.Word = 0; BC_Prime.Word = 0; DE_Prime.Word = 0; HL_Prime.Word = 0; // Index Registers IX.Word = 0; IY.Word = 0; // Internal Registers I = 0; R = 0; // Hardware State IFF1 = false; IFF2 = false; InterruptMode = 0; TotalTStates = 0; } private void ApplyWaitStates(ushort address) { // If a system (like a ULA) is attached and listening, ask it for the delay if (WaitStateCallback != null) { TotalTStates += WaitStateCallback(address, TotalTStates); } } public int RequestInterrupt() { InterruptRequested = true; // 1. If the ROM has disabled interrupts (DI), ignore the request if (!IFF1) return 0; // 2. Acknowledge the interrupt by immediately disabling further interrupts IFF1 = false; IFF2 = false; // 3. Push the current Program Counter to the stack so we can return later Push(PC); // --- Interrupt Mode Dispatch --- if (InterruptMode == 1) { // IM 1: Hardcoded jump to ROM address 0x0038 PC = 0x0038; return 13; // IM 1 hardware call takes 13 T-States } else if (InterruptMode == 2) { // IM 2: Dynamic Vectored Interrupts // A. Form the pointer address: High byte is 'I', Low byte is the floating bus (0xFF) ushort vectorAddress = (ushort)((I << 8) | 0xFF); // B. Read the actual 16-bit ISR address from that location in memory (Little-Endian) byte pcLow = ReadMemory(vectorAddress); byte pcHigh = ReadMemory((ushort)(vectorAddress + 1)); // C. Jump to the custom game routine! PC = (ushort)((pcHigh << 8) | pcLow); return 19; // IM 2 hardware call takes 19 T-States } else { // (IM 0 is theoretically possible but essentially unused on the standard Spectrum) throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!"); } } // 1. For fetching opcodes and immediate values (Advances PC) public byte FetchByte() { ApplyWaitStates(PC); byte data = _memory.Read(PC); PC++; return data; } // 2. For fetching 16-bit immediate values private ushort FetchWord() { // By using FetchByte twice, we perfectly apply wait states to BOTH memory reads! byte low = FetchByte(); byte high = FetchByte(); return (ushort)((high << 8) | low); } // 3. For standard memory reads (e.g., LD A, (HL)) public byte ReadMemory(ushort address) { ApplyWaitStates(address); return _memory.Read(address); } // 4. For standard memory writes (e.g., LD (HL), A) public void WriteMemory(ushort address, byte data) { ApplyWaitStates(address); _memory.Write(address, data); } // Placeholder for your hardware I/O private byte ReadPort(ushort portAddress) { return _simpleIoBus.ReadPort(portAddress); } public int Step() { // Fetch the next opcode and increment the Program Counter byte opcode = ReadMemory(PC++); int tStates = ExecuteOpcode(opcode); TotalTStates += tStates; // Decode and execute return tStates; } public void LoadSNA(byte[] snaData) { if (snaData.Length != 49179) throw new Exception("Invalid 48K SNA File Size!"); // --- 1. Load CPU Registers --- I = snaData[0]; HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8)); DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8)); BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8)); AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8)); HL.Word = (ushort)(snaData[9] | (snaData[10] << 8)); DE.Word = (ushort)(snaData[11] | (snaData[12] << 8)); BC.Word = (ushort)(snaData[13] | (snaData[14] << 8)); IY.Word = (ushort)(snaData[15] | (snaData[16] << 8)); IX.Word = (ushort)(snaData[17] | (snaData[18] << 8)); IFF2 = (snaData[19] & 0x04) != 0; IFF1 = IFF2; R = snaData[20]; AF.Word = (ushort)(snaData[21] | (snaData[22] << 8)); SP = (ushort)(snaData[23] | (snaData[24] << 8)); InterruptMode = snaData[25]; // --- 2. Load the 48K RAM Dump --- // The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF for (int i = 0; i < 49152; i++) { WriteMemory((ushort)(0x4000 + i), snaData[27 + i]); } // --- 3. The Magic Bullet --- // In the SNA format, the Program Counter (PC) isn't in the header. // It was PUSHED to the stack exactly 1 instruction before the snapshot was saved. // So, we just pop it off the stack to resume execution! PC = Pop(); } public string GetFlagsString() { byte f = AF.Low; return $"S:{(f >> 7) & 1} " + $"Z:{(f >> 6) & 1} " + $"Y:{(f >> 5) & 1} " + // Undocumented flag $"H:{(f >> 4) & 1} " + $"X:{(f >> 3) & 1} " + // Undocumented flag $"P/V:{(f >> 2) & 1} " + $"N:{(f >> 1) & 1} " + $"C:{f & 1}"; } // ========================================================================= // MATH AND LOGIC HELPERS // ========================================================================= private void SubA(byte value, bool isCompare) { int result = AF.High - value; byte flags = 0; if ((result & 0x80) != 0) flags |= 0x80; if ((byte)result == 0) flags |= 0x40; if (((AF.High & 0x0F) - (value & 0x0F)) < 0) flags |= 0x10; if ((((AF.High ^ value) & 0x80) != 0) && (((AF.High ^ result) & 0x80) != 0)) flags |= 0x04; flags |= 0x02; // N flag is always 1 for SUB and CP if (result < 0) flags |= 0x01; // The CP Trap: CP uses the operand, SUB uses the result flags |= (byte)((isCompare ? value : result) & 0x28); AF.Low = flags; if (!isCompare) AF.High = (byte)result; } private void SbcA(byte value) { byte a = AF.High; byte carry = (byte)(AF.Low & 0x01); int result = a - value - carry; byte flags = 0; if ((result & 0x80) != 0) flags |= 0x80; if ((byte)result == 0) flags |= 0x40; if (((a & 0x0F) - (value & 0x0F) - carry) < 0) flags |= 0x10; if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) flags |= 0x04; flags |= 0x02; // N is 1 if (result < 0) flags |= 0x01; flags |= (byte)(result & 0x28); AF.Low = flags; AF.High = (byte)result; } private void Sbc16(ushort value) { int hl = HL.Word; int carry = AF.Low & 0x01; int result = hl - value - carry; byte flags = 0; if ((result & 0x8000) != 0) flags |= 0x80; if ((ushort)result == 0) flags |= 0x40; if (((hl & 0x0FFF) - (value & 0x0FFF) - carry) < 0) flags |= 0x10; if ((((hl ^ value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) flags |= 0x04; flags |= 0x02; // N if (result < 0) flags |= 0x01; flags |= (byte)((result >> 8) & 0x28); AF.Low = flags; HL.Word = (ushort)result; } private void SbcHl(ushort value) { int op1 = HL.Word; int op2 = value; int carry = AF.Low & 0x01; int result = op1 - op2 - carry; byte flags = 0x02; // N: Always 1 if (((result >> 8) & 0x80) != 0) flags |= 0x80; if ((result & 0xFFFF) == 0) flags |= 0x40; if ((((op1 & 0x0FFF) - (op2 & 0x0FFF) - carry) & 0x1000) != 0) flags |= 0x10; if ((((op1 ^ op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04; if (result < 0) flags |= 0x01; flags |= (byte)((result >> 8) & 0x28); AF.Low = flags; HL.Word = (ushort)(result & 0xFFFF); } private byte Dec8(byte value) { byte result = (byte)(value - 1); byte carry = (byte)(AF.Low & 0x01); byte flags = 0; if ((result & 0x80) != 0) flags |= 0x80; if (result == 0) flags |= 0x40; if ((value & 0x0F) == 0) flags |= 0x10; if (value == 0x80) flags |= 0x04; flags |= 0x02; // N flag flags |= carry; // Preserve C flag flags |= (byte)(result & 0x28); // Undocumented bits AF.Low = flags; return result; } private byte Inc8(byte value) { byte result = (byte)(value + 1); byte carry = (byte)(AF.Low & 0x01); byte flags = 0; if ((result & 0x80) != 0) flags |= 0x80; if (result == 0) flags |= 0x40; if ((value & 0x0F) == 0x0F) flags |= 0x10; if (value == 0x7F) flags |= 0x04; flags |= carry; // Preserve C flag flags |= (byte)(result & 0x28); // Undocumented bits AF.Low = flags; return result; } private void And(byte value) { AF.High &= value; AF.Low = 0x10; // AND forces H to 1, N to 0, C to 0 if ((AF.High & 0x80) != 0) AF.Low |= 0x80; if (AF.High == 0) AF.Low |= 0x40; AF.Low |= ParityTable[AF.High]; AF.Low |= (byte)(AF.High & 0x28); } private void Or(byte value) { AF.High |= value; AF.Low = 0; // OR forces H, N, C to 0 if ((AF.High & 0x80) != 0) AF.Low |= 0x80; if (AF.High == 0) AF.Low |= 0x40; AF.Low |= ParityTable[AF.High]; AF.Low |= (byte)(AF.High & 0x28); } private void Xor(byte value) { AF.High ^= value; AF.Low = 0; // XOR forces H, N, C to 0 if ((AF.High & 0x80) != 0) AF.Low |= 0x80; if (AF.High == 0) AF.Low |= 0x40; AF.Low |= ParityTable[AF.High]; AF.Low |= (byte)(AF.High & 0x28); } private void Add16(ref RegisterPair dest, ushort value) { int result = dest.Word + value; byte flags = (byte)(AF.Low & 0xC4); // Preserve S, Z, P/V if (((dest.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10; // H if (result > 0xFFFF) flags |= 0x01; // C flags |= (byte)((result >> 8) & 0x28); // Undocumented bits 3 & 5 AF.Low = flags; dest.Word = (ushort)result; } private void AddA(byte operand) { byte a = AF.High; int result = a + operand; AF.High = (byte)result; byte flags = 0; if ((AF.High & 0x80) != 0) flags |= 0x80; if (AF.High == 0) flags |= 0x40; if (((a & 0x0F) + (operand & 0x0F)) > 0x0F) flags |= 0x10; bool sameSign = ((a ^ operand) & 0x80) == 0; bool changedSign = ((a ^ AF.High) & 0x80) != 0; if (sameSign && changedSign) flags |= 0x04; if (result > 0xFF) flags |= 0x01; flags |= (byte)(AF.High & 0x28); AF.Low = flags; } private void AdcA(byte operand) { int aVal = AF.High; int carryIn = AF.Low & 0x01; int result = aVal + operand + carryIn; byte flags = 0; if ((result & 0x80) != 0) flags |= 0x80; if ((result & 0xFF) == 0) flags |= 0x40; if (((aVal & 0x0F) + (operand & 0x0F) + carryIn) > 0x0F) flags |= 0x10; if ((((aVal ^ ~operand) & (aVal ^ result)) & 0x80) != 0) flags |= 0x04; if (result > 0xFF) flags |= 0x01; flags |= (byte)(result & 0x28); AF.Low = flags; AF.High = (byte)result; } private void Adc16(ushort value) { int hl = HL.Word; int carry = AF.Low & 0x01; int result = hl + value + carry; byte flags = 0; if ((result & 0x8000) != 0) flags |= 0x80; if ((result & 0xFFFF) == 0) flags |= 0x40; if (((hl & 0x0FFF) + (value & 0x0FFF) + carry) > 0x0FFF) flags |= 0x10; if ((((hl ^ ~value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) flags |= 0x04; if (result > 0xFFFF) flags |= 0x01; flags |= (byte)((result >> 8) & 0x28); AF.Low = flags; HL.Word = (ushort)result; } private void AdcHl(ushort value) { int op1 = HL.Word; int op2 = value; int carry = AF.Low & 0x01; int result = op1 + op2 + carry; byte flags = 0; if (((result >> 8) & 0x80) != 0) flags |= 0x80; if ((result & 0xFFFF) == 0) flags |= 0x40; if ((((op1 & 0x0FFF) + (op2 & 0x0FFF) + carry) & 0x1000) != 0) flags |= 0x10; if ((((op1 ^ ~op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04; if ((result & 0x10000) != 0) flags |= 0x01; flags |= (byte)((result >> 8) & 0x28); AF.Low = flags; HL.Word = (ushort)(result & 0xFFFF); } private byte PerformShift(int shiftType, byte val) { bool carryOut = false; bool oldCarry = (AF.Low & 0x01) != 0; switch (shiftType) { case 0: // RLC carryOut = (val & 0x80) != 0; val = (byte)((val << 1) | (carryOut ? 1 : 0)); break; case 1: // RRC carryOut = (val & 0x01) != 0; val = (byte)((val >> 1) | (carryOut ? 0x80 : 0x00)); break; case 2: // RL carryOut = (val & 0x80) != 0; val = (byte)((val << 1) | (oldCarry ? 1 : 0)); break; case 3: // RR carryOut = (val & 0x01) != 0; val = (byte)((val >> 1) | (oldCarry ? 0x80 : 0x00)); break; case 4: // SLA carryOut = (val & 0x80) != 0; val = (byte)(val << 1); break; case 5: // SRA carryOut = (val & 0x01) != 0; byte signBit = (byte)(val & 0x80); val = (byte)((val >> 1) | signBit); break; case 6: // SLL (Undocumented - sometimes called SAA) carryOut = (val & 0x80) != 0; val = (byte)((val << 1) | 0x01); // SLL always shifts in a 1! break; case 7: // SRL carryOut = (val & 0x01) != 0; val = (byte)(val >> 1); break; default: throw new NotImplementedException($"Shift type {shiftType} not implemented!"); } byte newFlags = 0; if (carryOut) newFlags |= 0x01; if ((val & 0x80) != 0) newFlags |= 0x80; if (val == 0) newFlags |= 0x40; newFlags |= ParityTable[val]; newFlags |= (byte)(val & 0x28); AF.Low = newFlags; return val; } private void Push(ushort value) { // High byte goes first SP--; WriteMemory(SP, (byte)(value >> 8)); // Low byte goes second SP--; WriteMemory(SP, (byte)(value & 0xFF)); } private ushort Pop() { // The Z80 is Little-Endian. Low byte comes off the stack first. byte low = ReadMemory(SP++); // High byte comes off second. byte high = ReadMemory(SP++); return (ushort)((high << 8) | low); } private int ExecuteOpcode(byte opcode) { sbyte offset = 0; byte oldCarry = 0; switch (opcode) { case 0x00: // NOP return 4; case 0x01: // LD BC, nn BC.Word = FetchWord(); return 10; case 0x02: // LD (BC), A WriteMemory(BC.Word, AF.High); return 7; case 0x03: // INC BC BC.Word++; return 6; // --- 8-Bit Increments --- case 0x04: BC.High = Inc8(BC.High); return 4; // INC B case 0x07: // RLCA byte topBit = (byte)(AF.High >> 7); AF.High = (byte)((AF.High << 1) | topBit); byte flags07 = (byte)(AF.Low & 0xC4); if (topBit != 0) flags07 |= 0x01; flags07 |= (byte)(AF.High & 0x28); AF.Low = flags07; return 4; case 0x08: // EX AF, AF' ushort tempAF = AF.Word; AF.Word = AF_Prime.Word; AF_Prime.Word = tempAF; return 4; case 0x09: Add16(ref HL, BC.Word); return 11; case 0x0A: //LD A (BC) AF.High = ReadMemory(BC.Word); return 7; case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C case 0x12: // LD (DE), A WriteMemory(DE.Word, AF.High); return 7; case 0x14: DE.High = Inc8(DE.High); return 4; // INC D case 0x19: Add16(ref HL, DE.Word); return 11; case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E case 0x1E: DE.Low = FetchByte(); // LD E, n return 7; case 0x29: Add16(ref HL, HL.Word); return 11; case 0x24: HL.High = Inc8(HL.High); return 4; // INC H case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L case 0x2E: // LD L, n HL.Low = FetchByte(); return 7; case 0x34: WriteMemory(HL.Word, Inc8(ReadMemory(HL.Word))); return 11; // INC (HL) case 0x39: Add16(ref HL, SP); return 11; case 0x3C: AF.High = Inc8(AF.High); return 4; // INC A // --- 8-Bit Decrements --- case 0x05: BC.High = Dec8(BC.High); return 4; // DEC B case 0x0D: BC.Low = Dec8(BC.Low); return 4; // DEC C case 0x15: DE.High = Dec8(DE.High); return 4; // DEC D case 0x1D: DE.Low = Dec8(DE.Low); return 4; // DEC E case 0x25: HL.High = Dec8(HL.High); return 4; // DEC H case 0x2D: HL.Low = Dec8(HL.Low); return 4; // DEC L case 0x2F: // CPL AF.High = (byte)(~AF.High); AF.Low |= 0x12; AF.Low &= 0xD7; // Ensure undocumented bits follow A AF.Low |= (byte)(AF.High & 0x28); return 4; case 0x35: WriteMemory(HL.Word, Dec8(ReadMemory(HL.Word))); return 11; // DEC (HL) case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A case 0x06: // LD B, n BC.High = FetchByte(); return 7; case 0x0B: // DEC BC BC.Word--; return 6; case 0x0E: // LD C, n BC.Low = FetchByte(); return 7; case 0x0F: // RRCA { byte bit0 = (byte)(AF.High & 0x01); AF.High = (byte)((AF.High >> 1) | (bit0 << 7)); byte flags0F = (byte)(AF.Low & 0xC4); flags0F |= bit0; flags0F |= (byte)(AF.High & 0x28); AF.Low = flags0F; return 4; } case 0x10: // DJNZ d sbyte djnzOffset = (sbyte)FetchByte(); BC.High--; // Decrement the B register if (BC.High != 0) { PC = (ushort)(PC + djnzOffset); return 13; // Jump taken } return 8; // Loop finished, no jump case 0x11: //LD DE, nn DE.Word = FetchWord(); return 10; case 0x13: // INC DE DE.Word++; return 6; case 0x16: // LD D, n DE.High = FetchByte(); return 7; case 0x17: // RLA oldCarry = (byte)(AF.Low & 0x01); bool newCarry = (AF.High & 0x80) != 0; AF.High = (byte)((AF.High << 1) | oldCarry); byte flags17 = (byte)(AF.Low & 0xC4); if (newCarry) flags17 |= 0x01; flags17 |= (byte)(AF.High & 0x28); AF.Low = flags17; return 4; case 0x18: // JR d sbyte jumpDistance = (sbyte)FetchByte(); PC = (ushort)(PC + jumpDistance); return 12; case 0x1A: // LD A, (DE) AF.High = ReadMemory(DE.Word); return 7; case 0x1B: // DEC DE DE.Word--; return 6; case 0x1F: // RRA { oldCarry = (byte)(AF.Low & 0x01); byte bit0 = (byte)(AF.High & 0x01); AF.High = (byte)((AF.High >> 1) | (oldCarry << 7)); byte flags1F = (byte)(AF.Low & 0xC4); flags1F |= bit0; flags1F |= (byte)(AF.High & 0x28); AF.Low = flags1F; return 4; } case 0x20: // JR NZ, e offset = (sbyte)FetchByte(); if ((AF.Low & 0x40) == 0) { PC = (ushort)(PC + offset); return 12; } return 7; case 0x21: // LD HL, nn HL.Word = FetchWord(); return 10; case 0x22: // LD (nn), HL ushort dest22 = FetchWord(); WriteMemory(dest22, HL.Low); WriteMemory((ushort)(dest22 + 1), HL.High); return 16; case 0x23: // INC HL HL.Word++; return 6; case 0x26: // LD H, n HL.High = FetchByte(); return 7; case 0x27: // DAA byte a = AF.High; int correction = 0; byte flags27 = AF.Low; bool carry = (flags27 & 0x01) != 0; bool halfCarry = (flags27 & 0x10) != 0; bool isSub = (flags27 & 0x02) != 0; if (halfCarry || (a & 0x0F) > 9) correction |= 0x06; if (carry || a > 0x99 || (a >= 0x90 && (a & 0x0F) > 9)) { correction |= 0x60; carry = true; } bool newHalfCarry = false; if (isSub) { newHalfCarry = halfCarry && (a & 0x0F) < 0x06; a = (byte)(a - correction); } else { newHalfCarry = ((a & 0x0F) + (correction & 0x0F)) > 0x0F; a = (byte)(a + correction); } AF.High = a; flags27 &= 0x02; if (carry) flags27 |= 0x01; if (newHalfCarry) flags27 |= 0x10; if ((a & 0x80) != 0) flags27 |= 0x80; if (a == 0) flags27 |= 0x40; flags27 |= ParityTable[a]; flags27 |= (byte)(a & 0x28); AF.Low = flags27; return 4; case 0x28: // JR Z, e offset = (sbyte)FetchByte(); if ((AF.Low & 0x40) != 0) { PC = (ushort)(PC + offset); return 12; } return 7; case 0x2A: // LD HL, (nn) { ushort srcAddress = FetchWord(); HL.Low = ReadMemory(srcAddress); HL.High = ReadMemory((ushort)(srcAddress + 1)); return 16; } case 0x2B: // DEC HL HL.Word--; return 6; case 0x30: // JR NC, e offset = (sbyte)FetchByte(); if ((AF.Low & 0x01) == 0) { PC = (ushort)(PC + offset); return 12; } return 7; case 0x31: // LD SP, nn { SP = FetchWord(); return 10; } case 0x32: // LD (nn), A { ushort destAddress = FetchWord(); WriteMemory(destAddress, AF.High); return 13; } case 0x33: // INC SP SP++; return 6; case 0x36: // LD (HL), n byte nValue = FetchByte(); WriteMemory(HL.Word, nValue); return 10; case 0x37: // SCF AF.Low |= 0x01; AF.Low &= 0xED; AF.Low |= (byte)(AF.High & 0x28); return 4; case 0x38: // JR C, d sbyte jrCOffset = (sbyte)FetchByte(); if ((AF.Low & 0x01) != 0) { PC = (ushort)(PC + jrCOffset); return 12; } return 7; case 0x3A: // LD A, (nn) ushort address3A = FetchWord(); AF.High = ReadMemory(address3A); return 13; case 0x3B: // DEC SP SP--; return 6; case 0x3E: //LD A, n AF.High = FetchByte(); return 7; case 0x3F: // CCF bool previousCarry = (AF.Low & 0x01) != 0; AF.Low ^= 0x01; AF.Low &= 0xFD; if (previousCarry) AF.Low |= 0x10; else AF.Low &= 0xEF; AF.Low &= 0xD7; AF.Low |= (byte)(AF.High & 0x28); return 4; case 0x40: return 4; // LD B, B case 0x41: BC.High = BC.Low; return 4; case 0x42: BC.High = DE.High; return 4; case 0x43: BC.High = DE.Low; return 4; case 0x44: BC.High = HL.High; return 4; case 0x45: BC.High = HL.Low; return 4; case 0x46: BC.High = ReadMemory(HL.Word); return 7; case 0x47: BC.High = AF.High; return 4; // --- LD C, r --- case 0x48: BC.Low = BC.High; return 4; case 0x49: return 4; case 0x4A: BC.Low = DE.High; return 4; case 0x4B: BC.Low = DE.Low; return 4; case 0x4C: BC.Low = HL.High; return 4; case 0x4D: BC.Low = HL.Low; return 4; case 0x4E: BC.Low = ReadMemory(HL.Word); return 7; case 0x4F: BC.Low = AF.High; return 4; // --- LD D, r --- case 0x50: DE.High = BC.High; return 4; case 0x51: DE.High = BC.Low; return 4; case 0x52: return 4; case 0x53: DE.High = DE.Low; return 4; case 0x54: DE.High = HL.High; return 4; case 0x55: DE.High = HL.Low; return 4; case 0x56: DE.High = ReadMemory(HL.Word); return 7; case 0x57: DE.High = AF.High; return 4; // --- LD E, r --- case 0x58: DE.Low = BC.High; return 4; case 0x59: DE.Low = BC.Low; return 4; case 0x5A: DE.Low = DE.High; return 4; case 0x5B: return 4; case 0x5C: DE.Low = HL.High; return 4; case 0x5D: DE.Low = HL.Low; return 4; case 0x5E: DE.Low = ReadMemory(HL.Word); return 7; case 0x5F: DE.Low = AF.High; return 4; // --- LD H, r --- case 0x60: HL.High = BC.High; return 4; case 0x61: HL.High = BC.Low; return 4; case 0x62: HL.High = DE.High; return 4; case 0x63: HL.High = DE.Low; return 4; case 0x64: return 4; case 0x65: HL.High = HL.Low; return 4; case 0x66: HL.High = ReadMemory(HL.Word); return 7; case 0x67: HL.High = AF.High; return 4; // --- LD L, r --- case 0x68: HL.Low = BC.High; return 4; case 0x69: HL.Low = BC.Low; return 4; case 0x6A: HL.Low = DE.High; return 4; case 0x6B: HL.Low = DE.Low; return 4; case 0x6C: HL.Low = HL.High; return 4; case 0x6D: return 4; case 0x6E: HL.Low = ReadMemory(HL.Word); return 7; case 0x6F: HL.Low = AF.High; return 4; // --- LD (HL), r --- case 0x70: WriteMemory(HL.Word, BC.High); return 7; case 0x71: WriteMemory(HL.Word, BC.Low); return 7; case 0x72: WriteMemory(HL.Word, DE.High); return 7; case 0x73: WriteMemory(HL.Word, DE.Low); return 7; case 0x74: WriteMemory(HL.Word, HL.High); return 7; case 0x75: WriteMemory(HL.Word, HL.Low); return 7; case 0x76: //HALT if (!InterruptRequested) { PC--; return 4; } else { InterruptRequested = false; return 4; } case 0x77: WriteMemory(HL.Word, AF.High); return 7; // --- LD A, r --- case 0x78: AF.High = BC.High; return 4; case 0x79: AF.High = BC.Low; return 4; case 0x7A: AF.High = DE.High; return 4; case 0x7B: AF.High = DE.Low; return 4; case 0x7C: AF.High = HL.High; return 4; case 0x7D: AF.High = HL.Low; return 4; case 0x7E: AF.High = ReadMemory(HL.Word); return 7; case 0x7F: return 4; case 0x80: AddA(BC.High); return 4; // ADD A, B case 0x81: AddA(BC.Low); return 4; // ADD A, C case 0x82: AddA(DE.High); return 4; // ADD A, D case 0x83: AddA(DE.Low); return 4; // ADD A, E case 0x84: AddA(HL.High); return 4; // ADD A, H case 0x85: AddA(HL.Low); return 4; // ADD A, L case 0x86: AddA(ReadMemory(HL.Word)); return 7; // ADD A, (HL) case 0x87: AddA(AF.High); return 4; // ADD A, A // --- ADC A, Register Family --- case 0x88: AdcA(BC.High); return 4; // ADC A, B case 0x89: AdcA(BC.Low); return 4; // ADC A, C case 0x8A: AdcA(DE.High); return 4; // ADC A, D case 0x8B: AdcA(DE.Low); return 4; // ADC A, E case 0x8C: AdcA(HL.High); return 4; // ADC A, H case 0x8D: AdcA(HL.Low); return 4; // ADC A, L case 0x8F: AdcA(AF.High); return 4; // ADC A, A case 0x8E: // ADC A, (HL) AdcA(ReadMemory(HL.Word)); return 7; case 0xCE: // ADC A, n AdcA(FetchByte()); return 7; // --- SUB A, r --- case 0x90: SubA(BC.High, false); return 4; case 0x91: SubA(BC.Low, false); return 4; case 0x92: SubA(DE.High, false); return 4; case 0x93: SubA(DE.Low, false); return 4; case 0x94: SubA(HL.High, false); return 4; case 0x95: SubA(HL.Low, false); return 4; case 0x96: SubA(ReadMemory(HL.Word), false); return 7; case 0x97: SubA(AF.High, false); return 4; // --- SBC A, r --- case 0x98: SbcA(BC.High); return 4; case 0x99: SbcA(BC.Low); return 4; case 0x9A: SbcA(DE.High); return 4; case 0x9B: SbcA(DE.Low); return 4; case 0x9C: SbcA(HL.High); return 4; case 0x9D: SbcA(HL.Low); return 4; case 0x9E: SbcA(ReadMemory(HL.Word)); return 7; case 0x9F: SbcA(AF.High); return 4; case 0xA0: And(BC.High); return 4; // AND B case 0xA1: And(BC.Low); return 4; // AND C case 0xA2: And(DE.High); return 4; // AND D case 0xA3: And(DE.Low); return 4; // AND E case 0xA4: And(HL.High); return 4; // AND H case 0xA5: And(HL.Low); return 4; // AND L case 0xA6: And(ReadMemory(HL.Word)); return 7; // AND (HL) case 0xA7: And(AF.High); return 4; // AND A case 0xA8: Xor(BC.High); return 4; // XOR B case 0xA9: Xor(BC.Low); return 4; // XOR C case 0xAA: Xor(DE.High); return 4; // XOR D case 0xAB: Xor(DE.Low); return 4; // XOR E case 0xAC: Xor(HL.High); return 4; // XOR H case 0xAD: Xor(HL.Low); return 4; // XOR L case 0xAE: Xor(ReadMemory(HL.Word)); return 7; // XOR (HL) case 0xAF: Xor(AF.High); return 4; // XOR A // --- OR r --- case 0xB0: Or(BC.High); return 4; // OR B case 0xB1: Or(BC.Low); return 4; // OR C case 0xB2: Or(DE.High); return 4; // OR D case 0xB3: Or(DE.Low); return 4; // OR E case 0xB4: Or(HL.High); return 4; // OR H case 0xB5: Or(HL.Low); return 4; // OR L case 0xB6: Or(ReadMemory(HL.Word)); return 7; // OR (HL) case 0xB7: Or(AF.High); return 4; // OR A // --- CP r --- case 0xB8: SubA(BC.High, true); return 4; case 0xB9: SubA(BC.Low, true); return 4; case 0xBA: SubA(DE.High, true); return 4; case 0xBB: SubA(DE.Low, true); return 4; case 0xBC: SubA(HL.High, true); return 4; case 0xBD: SubA(HL.Low, true); return 4; case 0xBE: SubA(ReadMemory(HL.Word), true); return 7; case 0xBF: SubA(AF.High, true); return 4; // --- Conditional Returns (11 T-States if taken, 5 if not) --- case 0xC0: // RET NZ if ((AF.Low & 0x40) == 0) { PC = Pop(); return 11; } return 5; case 0xE0: // RET PO (Parity Odd / No Overflow) if ((AF.Low & 0x04) == 0) { PC = Pop(); return 11; } return 5; case 0xE8: // RET PE (Parity Even / Overflow) if ((AF.Low & 0x04) != 0) { PC = Pop(); return 11; } return 5; case 0xF0: // RET P (Sign Positive) if ((AF.Low & 0x80) == 0) { PC = Pop(); return 11; } return 5; case 0xF8: // RET M (Sign Minus) if ((AF.Low & 0x80) != 0) { PC = Pop(); return 11; } return 5; case 0xC1: // POP BC BC.Word = Pop(); return 10; // --- Absolute Conditional Jumps (Always 10 T-States) --- case 0xC2: // JP NZ, nn { ushort addr = FetchWord(); if ((AF.Low & 0x40) == 0) PC = addr; return 10; } case 0xCA: // JP Z, nn { ushort addr = FetchWord(); if ((AF.Low & 0x40) != 0) PC = addr; return 10; } case 0xD2: // JP NC, nn { ushort addr = FetchWord(); if ((AF.Low & 0x01) == 0) PC = addr; return 10; } case 0xDA: // JP C, nn { ushort addr = FetchWord(); if ((AF.Low & 0x01) != 0) PC = addr; return 10; } case 0xDB: // IN A, (n) byte portOffsetDB = FetchByte(); ushort portAddressDB = (ushort)((AF.High << 8) | portOffsetDB); AF.High = _simpleIoBus.ReadPort(portAddressDB); return 11; case 0xE2: // JP PO, nn { ushort addr = FetchWord(); if ((AF.Low & 0x04) == 0) PC = addr; return 10; } case 0xEA: // JP PE, nn { ushort addr = FetchWord(); if ((AF.Low & 0x04) != 0) PC = addr; return 10; } case 0xF2: // JP P, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) == 0) PC = addr; return 10; } case 0xFA: // JP M, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) != 0) PC = addr; return 10; } case 0xC3: PC = FetchWord(); return 10; // --- Absolute Conditional Calls (17 T-States if taken, 10 if not) --- case 0xC4: // CALL NZ, nn { ushort addr = FetchWord(); if ((AF.Low & 0x40) == 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xCC: // CALL Z, nn { ushort addr = FetchWord(); if ((AF.Low & 0x40) != 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xD4: // CALL NC, nn { ushort addr = FetchWord(); if ((AF.Low & 0x01) == 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xDC: // CALL C, nn { ushort addr = FetchWord(); if ((AF.Low & 0x01) != 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xE4: // CALL PO, nn { ushort addr = FetchWord(); if ((AF.Low & 0x04) == 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xEC: // CALL PE, nn { ushort addr = FetchWord(); if ((AF.Low & 0x04) != 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xF4: // CALL P, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) == 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xFC: // CALL M, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) != 0) { Push(PC); PC = addr; return 17; } return 10; } case 0xc5: //push bc Push(BC.Word); return 11; case 0xC6: // ADD A, n AddA(FetchByte()); return 7; // --- RST Instructions (11 T-States) --- case 0xC7: Push(PC); PC = 0x0000; return 11; case 0xCF: Push(PC); PC = 0x0008; return 11; case 0xD7: Push(PC); PC = 0x0010; return 11; case 0xDF: Push(PC); PC = 0x0018; return 11; case 0xE7: Push(PC); PC = 0x0020; return 11; case 0xEF: Push(PC); PC = 0x0028; return 11; case 0xF7: Push(PC); PC = 0x0030; return 11; case 0xFF: Push(PC); PC = 0x0038; return 11; case 0xC8: // RET Z if ((AF.Low & 0x40) != 0) { PC = Pop(); return 11; } return 5; case 0xC9: // RET PC = Pop(); return 10; case 0xCB: return ExecuteCBPrefix(); case 0xCD: // CALL nn ushort callAddress = FetchWord(); Push(PC); PC = callAddress; return 17; case 0xD0: // RET NC if ((AF.Low & 0x01) == 0) { PC = Pop(); return 11; } return 5; case 0xD1: // POP DE DE.Word = Pop(); return 10; case 0xD3: // OUT (n), A byte portOffset = FetchByte(); ushort portAddress = (ushort)((AF.High << 8) | portOffset); _simpleIoBus.WritePort(portAddress, AF.High); return 11; case 0xd5: //push de Push(DE.Word); return 11; case 0xD6: // SUB n SubA(FetchByte(), false); return 7; case 0xD8: // RET C if ((AF.Low & 0x01) != 0) { PC = Pop(); return 11; } return 5; case 0xD9: // EXX ushort tempBC = BC.Word; BC.Word = BC_Prime.Word; BC_Prime.Word = tempBC; ushort tempDE = DE.Word; DE.Word = DE_Prime.Word; DE_Prime.Word = tempDE; ushort tempHL = HL.Word; HL.Word = HL_Prime.Word; HL_Prime.Word = tempHL; return 4; case 0xDD: return ExecuteDDPrefix(); case 0xDE: // SBC A, n SbcA(FetchByte()); return 7; case 0xE1: // POP HL HL.Word = Pop(); return 10; case 0xE3: // EX (SP), HL byte spLow = ReadMemory(SP); byte spHigh = ReadMemory((ushort)(SP + 1)); WriteMemory(SP, HL.Low); WriteMemory((ushort)(SP + 1), HL.High); HL.Low = spLow; HL.High = spHigh; return 19; case 0xe5: //push hl Push(HL.Word); return 11; case 0xE6: // AND n And(FetchByte()); return 7; case 0xE9: // JP (HL) PC = HL.Word; return 4; case 0xEB: // EX DE, HL ushort tempEx = DE.Word; DE.Word = HL.Word; HL.Word = tempEx; return 4; case 0xED: return ExecuteExtendedPrefix(); case 0xEE: // XOR n Xor(FetchByte()); return 7; case 0xF1: // POP AF AF.Word = Pop(); return 10; case 0xF3: // DI IFF1 = false; IFF2 = false; return 4; case 0xf5: //push af Push(AF.Word); return 11; case 0xF6: // OR n Or(FetchByte()); return 7; case 0xF9: // LD SP, HL SP = HL.Word; return 6; case 0xFB: // EI IFF1 = true; IFF2 = true; return 4; case 0xFD: return ExecuteFDPrefix(); case 0xFE: // CP n SubA(FetchByte(), true); return 7; default: throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); } } private int ExecuteExtendedPrefix() //ED { byte extendedOpcode = FetchByte(); //byte val = 0; switch (extendedOpcode) { case 0x43: // LD (nn), BC ushort dest43 = FetchWord(); WriteMemory(dest43, BC.Low); WriteMemory((ushort)(dest43 + 1), BC.High); return 20; case 0x44: // NEG { int aBefore = AF.High; int resultNeg = 0 - aBefore; byte flagsNeg = 0; if ((resultNeg & 0x80) != 0) flagsNeg |= 0x80; if ((resultNeg & 0xFF) == 0) flagsNeg |= 0x40; if ((0 - (aBefore & 0x0F)) < 0) flagsNeg |= 0x10; if (aBefore == 0x80) flagsNeg |= 0x04; flagsNeg |= 0x02; if (aBefore != 0) flagsNeg |= 0x01; flagsNeg |= (byte)(resultNeg & 0x28); AF.Low = flagsNeg; AF.High = (byte)resultNeg; return 8; } case 0x47: // LD I, A I = AF.High; return 9; case 0x4B: // LD BC, (nn) ushort src4B = FetchWord(); BC.Low = ReadMemory(src4B); BC.High = ReadMemory((ushort)(src4B + 1)); return 20; case 0x4D: // RETI Does not affect IFF1 or IFF2 PC = Pop(); return 14; case 0x53: // LD (nn), DE ushort dest53 = FetchWord(); WriteMemory(dest53, DE.Low); WriteMemory((ushort)(dest53 + 1), DE.High); return 20; case 0x56: // IM 1 InterruptMode = 1; return 8; case 0x58: // IN E, (C) byte inVal58 = ReadPort(BC.Word); DE.Low = inVal58; byte flags58 = (byte)(AF.Low & 0x01); if ((inVal58 & 0x80) != 0) flags58 |= 0x80; if (inVal58 == 0) flags58 |= 0x40; flags58 |= ParityTable[inVal58]; flags58 |= (byte)(inVal58 & 0x28); AF.Low = flags58; return 12; case 0x5B: // LD DE, (nn) ushort src5B = FetchWord(); DE.Low = ReadMemory(src5B); DE.High = ReadMemory((ushort)(src5B + 1)); return 20; case 0x5E: // IM 2 InterruptMode = 2; return 8; case 0x5F: // LD A, R { AF.High = R; byte flags5F = (byte)(AF.Low & 0x01); if ((AF.High & 0x80) != 0) flags5F |= 0x80; if (AF.High == 0) flags5F |= 0x40; if (IFF2) flags5F |= 0x04; flags5F |= (byte)(AF.High & 0x28); AF.Low = flags5F; return 9; } case 0x67: // RRD (Rotate Right Decimal) { // 1. Fetch the operands byte memVal = ReadMemory(HL.Word); byte aVal = AF.High; // 2. Extract the three 4-bit nibbles we are rotating byte aLow = (byte)(aVal & 0x0F); byte memHigh = (byte)(memVal >> 4); byte memLow = (byte)(memVal & 0x0F); // 3. Perform the Right Rotation // The old Accumulator low nibble goes to the top of memory. // The old top of memory goes to the bottom of memory. byte newMemVal = (byte)((aLow << 4) | memHigh); // The old bottom of memory goes to the Accumulator low nibble. // The Accumulator high nibble is completely untouched. byte newAVal = (byte)((aVal & 0xF0) | memLow); // 4. Write the results back WriteMemory(HL.Word, newMemVal); AF.High = newAVal; // 5. Update Flags // Carry (Bit 0) is PRESERVED. Half-Carry (Bit 4) and Subtract (Bit 1) are RESET. byte flags = (byte)(AF.Low & 0x01); if ((newAVal & 0x80) != 0) flags |= 0x80; // S: Set if A is negative if (newAVal == 0) flags |= 0x40; // Z: Set if A is zero flags |= ParityTable[newAVal]; // P/V: Parity of A flags |= (byte)(newAVal & 0x28); // Undocumented bits 3 and 5 copy from A AF.Low = flags; return 18; // 18 T-States } case 0x6F: // RLD (Rotate Left Decimal) { // 1. Fetch the operands byte memVal = ReadMemory(HL.Word); byte aVal = AF.High; // 2. Extract the three 4-bit nibbles we are rotating byte aLow = (byte)(aVal & 0x0F); byte memHigh = (byte)(memVal >> 4); byte memLow = (byte)(memVal & 0x0F); // 3. Perform the Left Rotation // The old bottom of memory goes to the top of memory. // The old Accumulator low nibble goes to the bottom of memory. byte newMemVal = (byte)((memLow << 4) | aLow); // The old top of memory goes to the Accumulator low nibble. // The Accumulator high nibble is completely untouched. byte newAVal = (byte)((aVal & 0xF0) | memHigh); // 4. Write the results back WriteMemory(HL.Word, newMemVal); AF.High = newAVal; // 5. Update Flags // Carry (Bit 0) is PRESERVED. Half-Carry (Bit 4) and Subtract (Bit 1) are RESET. byte flags = (byte)(AF.Low & 0x01); if ((newAVal & 0x80) != 0) flags |= 0x80; // S: Set if A is negative if (newAVal == 0) flags |= 0x40; // Z: Set if A is zero flags |= ParityTable[newAVal]; // P/V: Parity of A flags |= (byte)(newAVal & 0x28); // Undocumented bits 3 and 5 copy from A AF.Low = flags; return 18; // 18 T-States } // --- SBC HL, rr --- case 0x42: SbcHl(BC.Word); return 15; case 0x52: SbcHl(DE.Word); return 15; case 0x62: SbcHl(HL.Word); return 15; case 0x72: SbcHl(SP); return 15; case 0x73: // LD (nn), SP ushort dest73 = FetchWord(); WriteMemory(dest73, (byte)SP); WriteMemory((ushort)(dest73 + 1), (byte)(SP >> 8)); return 20; case 0x78: // IN A, (C) byte portVal78 = ReadPort(BC.Word); AF.High = portVal78; byte flags78 = (byte)(AF.Low & 0x01); if ((portVal78 & 0x80) != 0) flags78 |= 0x80; if (portVal78 == 0) flags78 |= 0x40; flags78 |= ParityTable[portVal78]; flags78 |= (byte)(portVal78 & 0x28); AF.Low = flags78; return 12; case 0x79: // OUT (C), A _simpleIoBus.WritePort(BC.Word, AF.High); return 12; // --- ADC HL, rr --- case 0x4A: Adc16(BC.Word); return 15; case 0x5A: Adc16(DE.Word); return 15; case 0x6A: Adc16(HL.Word); return 15; case 0x7A: Adc16(SP); return 15; case 0x7B: // LD SP, (nn) byte addrLow = FetchByte(); byte addrHigh = FetchByte(); ushort address7B = (ushort)((addrHigh << 8) | addrLow); byte spLow = ReadMemory(address7B); byte spHigh = ReadMemory((ushort)(address7B + 1)); SP = (ushort)((spHigh << 8) | spLow); return 20; // --- BLOCK LOADS --- case 0xA0: // LDI { byte val0 = ReadMemory(HL.Word); WriteMemory(DE.Word, val0); HL.Word++; DE.Word++; BC.Word--; AF.Low &= 0xC1; // Preserve S, Z, C. Wipe H, N. if (BC.Word != 0) AF.Low |= 0x04; // P/V byte n = (byte)(AF.High + val0); AF.Low |= (byte)(n & 0x08); // Bit 3 if ((n & 0x02) != 0) AF.Low |= 0x20; // Bit 5 from bit 1 return 16; } case 0xB0: // LDIR { byte val00 = ReadMemory(HL.Word); WriteMemory(DE.Word, val00); HL.Word++; DE.Word++; BC.Word--; AF.Low &= 0xC1; if (BC.Word != 0) { AF.Low |= 0x04; PC -= 2; // Loop byte n1 = (byte)(AF.High + val00); AF.Low |= (byte)(n1 & 0x08); if ((n1 & 0x02) != 0) AF.Low |= 0x20; return 21; } byte n2 = (byte)(AF.High + val00); AF.Low |= (byte)(n2 & 0x08); if ((n2 & 0x02) != 0) AF.Low |= 0x20; return 16; } case 0xA1: // CPI { byte memVal = ReadMemory(HL.Word); byte result = (byte)(AF.High - memVal); HL.Word++; BC.Word--; byte flags = (byte)(AF.Low & 0x01); // Preserve Carry flags |= 0x02; // N is 1 if (BC.Word != 0) flags |= 0x04; if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; if (result == 0) flags |= 0x40; if ((result & 0x80) != 0) flags |= 0x80; byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); flags |= (byte)(n & 0x08); if ((n & 0x02) != 0) flags |= 0x20; // Bit 5 from Bit 1 AF.Low = flags; return 16; } case 0xB1: // CPIR { byte memVal = ReadMemory(HL.Word); byte result = (byte)(AF.High - memVal); HL.Word++; BC.Word--; byte flags = (byte)(AF.Low & 0x01); flags |= 0x02; if (BC.Word != 0) flags |= 0x04; if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; if (result == 0) flags |= 0x40; if ((result & 0x80) != 0) flags |= 0x80; byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); flags |= (byte)(n & 0x08); if ((n & 0x02) != 0) flags |= 0x20; AF.Low = flags; if (BC.Word != 0 && result != 0) { PC -= 2; return 21; } return 16; } case 0xA8: // LDD { byte val8 = ReadMemory(HL.Word); WriteMemory(DE.Word, val8); HL.Word--; DE.Word--; BC.Word--; AF.Low &= 0xC1; if (BC.Word != 0) AF.Low |= 0x04; byte n = (byte)(AF.High + val8); AF.Low |= (byte)(n & 0x08); if ((n & 0x02) != 0) AF.Low |= 0x20; return 16; } case 0xA9: // CPD (The opcode that just crashed!) { byte memVal = ReadMemory(HL.Word); byte result = (byte)(AF.High - memVal); HL.Word--; BC.Word--; // Decrement HL byte flags = (byte)(AF.Low & 0x01); flags |= 0x02; if (BC.Word != 0) flags |= 0x04; if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; if (result == 0) flags |= 0x40; if ((result & 0x80) != 0) flags |= 0x80; byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); flags |= (byte)(n & 0x08); if ((n & 0x02) != 0) flags |= 0x20; AF.Low = flags; return 16; } case 0xB9: // CPDR { byte memVal = ReadMemory(HL.Word); byte result = (byte)(AF.High - memVal); HL.Word--; BC.Word--; byte flags = (byte)(AF.Low & 0x01); flags |= 0x02; if (BC.Word != 0) flags |= 0x04; if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; if (result == 0) flags |= 0x40; if ((result & 0x80) != 0) flags |= 0x80; byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); flags |= (byte)(n & 0x08); if ((n & 0x02) != 0) flags |= 0x20; AF.Low = flags; if (BC.Word != 0 && result != 0) { PC -= 2; return 21; } return 16; } case 0xB8: // LDDR { byte val88 = ReadMemory(HL.Word); WriteMemory(DE.Word, val88); HL.Word--; DE.Word--; BC.Word--; AF.Low &= 0xC1; if (BC.Word != 0) { AF.Low |= 0x04; PC -= 2; // Loop byte n1 = (byte)(AF.High + val88); AF.Low |= (byte)(n1 & 0x08); if ((n1 & 0x02) != 0) AF.Low |= 0x20; return 21; } byte n2 = (byte)(AF.High + val88); AF.Low |= (byte)(n2 & 0x08); if ((n2 & 0x02) != 0) AF.Low |= 0x20; return 16; } default: throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); } } private int ExecuteCBPrefix() { byte cbOpcode = FetchByte(); //bool oldCarry = false; int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET int bitIndex = (cbOpcode >> 3) & 0x07; int regIndex = cbOpcode & 0x07; byte bitMask = (byte)(1 << bitIndex); // --- PHASE 1: Fetch the target value --- byte val = 0; switch (regIndex) { case 0: val = BC.High; break; case 1: val = BC.Low; break; case 2: val = DE.High; break; case 3: val = DE.Low; break; case 4: val = HL.High; break; case 5: val = HL.Low; break; case 6: val = ReadMemory(HL.Word); break; // The 0x110 (HL) exception case 7: val = AF.High; break; } // --- PHASE 2: Perform the bitwise math --- switch (operation) { case 0: // ALL Shift/Rotate Instructions int shiftType = (cbOpcode >> 3) & 0x07; // Pass the value to the helper, which handles all the math and flags val = PerformShift(shiftType, val); break; // Break to proceed to PHASE 3 and write the value back! case 1: // ALL BIT Instructions AF.Low &= 0x01; // Preserve ONLY Carry AF.Low |= 0x10; // Set Half-Carry if ((val & bitMask) == 0) { AF.Low |= 0x44; // Set Zero and P/V } else if (bitIndex == 7) { AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign } // --- ZEXALL Strict Undocumented Bits --- // For (HL) memory tests, bits 3/5 come from the High byte of the address (HL.High). // For all standard registers, bits 3/5 come from the register itself (val). byte undocumented = (regIndex == 6) ? HL.High : val; AF.Low |= (byte)(undocumented & 0x28); return (regIndex == 6) ? 12 : 8; case 2: // ALL RES Instructions val &= (byte)(~bitMask); break; case 3: // ALL SET Instructions val |= bitMask; break; default: throw new Exception("Invalid CB operation."); } // --- PHASE 3: Write back the modified value --- switch (regIndex) { case 0: BC.High = val; break; case 1: BC.Low = val; break; case 2: DE.High = val; break; case 3: DE.Low = val; break; case 4: HL.High = val; break; case 5: HL.Low = val; break; case 6: WriteMemory(HL.Word, val); break; case 7: AF.High = val; break; } return (regIndex == 6) ? 15 : 8; } private int ExecuteDDPrefix() { byte ddOpcode = FetchByte(); switch (ddOpcode) { case 0x09: Add16(ref IX, BC.Word); return 15; case 0x19: Add16(ref IX, DE.Word); return 15; case 0x29: Add16(ref IX, IX.Word); return 15; case 0x39: Add16(ref IX, SP); return 15; case 0x21: // LD IX, nn byte low = FetchByte(); byte high = FetchByte(); IX.Word = (ushort)((high << 8) | low); return 14; case 0x22: // LD (nn), IX byte addrLow22 = FetchByte(); byte addrHigh22 = FetchByte(); ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22); WriteMemory(address22, IX.Low); WriteMemory((ushort)(address22 + 1), IX.High); return 20; case 0x23: // INC IX IX.Word++; return 10; case 0x24: // INC IXH IX.High = Inc8(IX.High); return 8; case 0x25: // DEC IXH IX.High = Dec8(IX.High); return 8; case 0x26: // LD IXH, n IX.High = FetchByte(); return 11; case 0x2A: // LD IX, (nn) byte addrLow2A = FetchByte(); byte addrHigh2A = FetchByte(); ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A); byte ixLow = ReadMemory(address2A); byte ixHigh = ReadMemory((ushort)(address2A + 1)); IX.Word = (ushort)((ixHigh << 8) | ixLow); return 20; case 0x2B: // DEC IX IX.Word--; return 10; case 0x2C: // INC IXL IX.Low = Inc8(IX.Low); return 8; case 0x2D: // DEC IXL IX.Low = Dec8(IX.Low); return 8; case 0x2E: // LD IXL, n IX.Low = FetchByte(); return 11; case 0x34: // INC (IX+d) { sbyte offset34 = (sbyte)FetchByte(); ushort address34 = (ushort)(IX.Word + offset34); byte val34 = ReadMemory(address34); byte result34 = Inc8(val34); WriteMemory(address34, result34); return 23; } case 0x35: // DEC (IX+d) { sbyte offset35 = (sbyte)FetchByte(); ushort address35 = (ushort)(IX.Word + offset35); byte val35 = ReadMemory(address35); byte result35 = Dec8(val35); WriteMemory(address35, result35); return 23; } case 0x36: // LD (IX+d), n sbyte offset36 = (sbyte)FetchByte(); byte n36 = FetchByte(); ushort address36 = (ushort)(IX.Word + offset36); WriteMemory(address36, n36); return 19; case 0x46: // LD B, (IX+d) sbyte offset46 = (sbyte)FetchByte(); ushort address46 = (ushort)(IX.Word + offset46); BC.High = ReadMemory(address46); return 19; case 0x4E: // LD C, (IX+d) sbyte offset4E = (sbyte)FetchByte(); ushort address4E = (ushort)(IX.Word + offset4E); BC.Low = ReadMemory(address4E); return 19; case 0x56: // LD D, (IX+d) sbyte offset56 = (sbyte)FetchByte(); ushort address56 = (ushort)(IX.Word + offset56); DE.High = ReadMemory(address56); return 19; case 0x5E: // LD E, (IX+d) sbyte offset5E = (sbyte)FetchByte(); ushort address5E = (ushort)(IX.Word + offset5E); DE.Low = ReadMemory(address5E); return 19; case 0x66: // LD H, (IX+d) sbyte offset66 = (sbyte)FetchByte(); ushort address66 = (ushort)(IX.Word + offset66); HL.High = ReadMemory(address66); return 19; case 0x67: // LD IXH, A IX.High = AF.High; return 8; case 0x6E: // LD L, (IX+d) sbyte offset6E = (sbyte)FetchByte(); ushort address6E = (ushort)(IX.Word + offset6E); HL.Low = ReadMemory(address6E); return 19; case 0x6F: // LD IXL, A IX.Low = AF.High; return 8; case 0x70: // LD (IX+d), B sbyte offset70 = (sbyte)FetchByte(); ushort address70 = (ushort)(IX.Word + offset70); WriteMemory(address70, BC.High); return 19; case 0x71: // LD (IX+d), C sbyte offset71 = (sbyte)FetchByte(); ushort address71 = (ushort)(IX.Word + offset71); WriteMemory(address71, BC.Low); return 19; case 0x72: // LD (IX+d), D sbyte offset72 = (sbyte)FetchByte(); ushort address72 = (ushort)(IX.Word + offset72); WriteMemory(address72, DE.High); return 19; case 0x73: // LD (IX+d), E sbyte offset73 = (sbyte)FetchByte(); ushort address73 = (ushort)(IX.Word + offset73); WriteMemory(address73, DE.Low); return 19; case 0x74: // LD (IX+d), H sbyte offset74 = (sbyte)FetchByte(); ushort address74 = (ushort)(IX.Word + offset74); WriteMemory(address74, HL.High); return 19; case 0x75: // LD (IX+d), L sbyte offset75 = (sbyte)FetchByte(); ushort address75 = (ushort)(IX.Word + offset75); WriteMemory(address75, HL.Low); return 19; case 0x77: // LD (IX+d), A sbyte offset77 = (sbyte)FetchByte(); ushort address77 = (ushort)(IX.Word + offset77); WriteMemory(address77, AF.High); return 19; case 0x7C: // LD A, IXH AF.High = IX.High; return 8; case 0x7E: // LD A, (IX+d) sbyte offset7E = (sbyte)FetchByte(); ushort address7E = (ushort)(IX.Word + offset7E); AF.High = ReadMemory(address7E); return 19; case 0x84: // ADD A, IXH AddA(IX.High); return 8; case 0x85: // ADD A, IXL AddA(IX.Low); return 8; case 0x86: // ADD A, (IX+d) { sbyte displacementAdd = (sbyte)FetchByte(); ushort targetAddressAdd = (ushort)(IX.Word + displacementAdd); AddA(ReadMemory(targetAddressAdd)); return 19; } case 0x8E: // ADC A, (IX+d) { sbyte offset8E = (sbyte)FetchByte(); ushort address8E = (ushort)(IX.Word + offset8E); AdcA(ReadMemory(address8E)); return 19; } case 0x9E: // SBC A, (IX+d) { sbyte offset9E = (sbyte)FetchByte(); ushort address9E = (ushort)(IX.Word + offset9E); SbcA(ReadMemory(address9E)); return 19; } case 0xA6: // AND (IX+d) { sbyte offsetA6 = (sbyte)FetchByte(); ushort addressA6 = (ushort)(IX.Word + offsetA6); And(ReadMemory(addressA6)); return 19; } case 0xAE: // XOR (IX+d) { sbyte offsetAE = (sbyte)FetchByte(); ushort addressAE = (ushort)(IX.Word + offsetAE); Xor(ReadMemory(addressAE)); return 19; } // --- UNDOCUMENTED IX ALU OPERATIONS --- case 0x8C: AdcA(IX.High); return 8; case 0x8D: AdcA(IX.Low); return 8; case 0x94: SubA(IX.High, false); return 8; case 0x95: SubA(IX.Low, false); return 8; case 0x9C: SbcA(IX.High); return 8; case 0x9D: SbcA(IX.Low); return 8; case 0xA4: And(IX.High); return 8; case 0xA5: And(IX.Low); return 8; case 0xAC: Xor(IX.High); return 8; case 0xAD: Xor(IX.Low); return 8; case 0xB4: Or(IX.High); return 8; case 0xB5: Or(IX.Low); return 8; case 0xBC: SubA(IX.High, true); return 8; case 0xBD: SubA(IX.Low, true); return 8; // --- UNDOCUMENTED IX LOAD OPERATIONS --- case 0x44: BC.High = IX.High; return 8; // LD B, IXH case 0x45: BC.High = IX.Low; return 8; // LD B, IXL case 0x4C: BC.Low = IX.High; return 8; // LD C, IXH case 0x4D: BC.Low = IX.Low; return 8; // LD C, IXL case 0x54: DE.High = IX.High; return 8; // LD D, IXH case 0x55: DE.High = IX.Low; return 8; // LD D, IXL case 0x5C: DE.Low = IX.High; return 8; // LD E, IXH case 0x5D: DE.Low = IX.Low; return 8; // LD E, IXL case 0x60: IX.High = BC.High; return 8; // LD IXH, B case 0x61: IX.High = BC.Low; return 8; // LD IXH, C case 0x62: IX.High = DE.High; return 8; // LD IXH, D case 0x63: IX.High = DE.Low; return 8; // LD IXH, E case 0x64: return 8; // LD IXH, IXH case 0x65: IX.High = IX.Low; return 8; // LD IXH, IXL case 0x68: IX.Low = BC.High; return 8; // LD IXL, B case 0x69: IX.Low = BC.Low; return 8; // LD IXL, C case 0x6A: IX.Low = DE.High; return 8; // LD IXL, D case 0x6B: IX.Low = DE.Low; return 8; // LD IXL, E case 0x6C: IX.Low = IX.High; return 8; // LD IXL, IXH case 0x6D: return 8; // LD IXL, IXL case 0x96: // SUB (IX+d) { sbyte offset96 = (sbyte)FetchByte(); ushort address96 = (ushort)(IX.Word + offset96); SubA(ReadMemory(address96), false); return 19; } case 0xB6: // OR (IX+d) { sbyte offsetB6 = (sbyte)FetchByte(); ushort addressB6 = (ushort)(IX.Word + offsetB6); // Read the memory and pass it straight into your helper Or(ReadMemory(addressB6)); return 19; // Takes 19 T-States } case 0xBE: // CP (IX+d) { sbyte offsetBE = (sbyte)FetchByte(); ushort addressBE = (ushort)(IX.Word + offsetBE); SubA(ReadMemory(addressBE), true); return 19; } case 0xCB: // The DD CB nested prefix { sbyte displacement = (sbyte)FetchByte(); byte cbOpcode = FetchByte(); ushort targetAddress = (ushort)(IX.Word + displacement); byte memVal = ReadMemory(targetAddress); int operation = cbOpcode >> 6; int bitIndex = (cbOpcode >> 3) & 0x07; byte bitMask = (byte)(1 << bitIndex); int regIndex = cbOpcode & 0x07; switch (operation) { case 0: // ALL Shift/Rotate Instructions int shiftType = (cbOpcode >> 3) & 0x07; // Perform the shift and flag math memVal = PerformShift(shiftType, memVal); WriteMemory(targetAddress, memVal); // Z80 UNDOCUMENTED QUIRK: // DDCB and FDCB shift instructions ALSO copy the result into a standard register // unless the target register index is 6 (which is purely memory). if (regIndex != 6) { switch (regIndex) { case 0: BC.High = memVal; break; case 1: BC.Low = memVal; break; case 2: DE.High = memVal; break; case 3: DE.Low = memVal; break; case 4: HL.High = memVal; break; case 5: HL.Low = memVal; break; case 7: AF.High = memVal; break; } } return 23; case 1: // ALL BIT Instructions AF.Low &= 0x01; AF.Low |= 0x10; if ((memVal & bitMask) == 0) { AF.Low |= 0x44; } else if (bitIndex == 7) { AF.Low |= 0x80; } return 20; case 2: // ALL RES Instructions memVal &= (byte)(~bitMask); WriteMemory(targetAddress, memVal); return 23; case 3: // ALL SET Instructions memVal |= bitMask; WriteMemory(targetAddress, memVal); return 23; default: throw new Exception("Invalid bitwise operation."); } } case 0xE1: // POP IX byte popLow = ReadMemory(SP); SP++; byte popHigh = ReadMemory(SP); SP++; IX.Word = (ushort)((popHigh << 8) | popLow); return 14; case 0xE3: // EX (SP), IX byte spLowIX = ReadMemory(SP); byte spHighIX = ReadMemory((ushort)(SP + 1)); WriteMemory(SP, IX.Low); WriteMemory((ushort)(SP + 1), IX.High); IX.Low = spLowIX; IX.High = spHighIX; return 23; case 0xE5: // PUSH IX SP--; WriteMemory(SP, IX.High); SP--; WriteMemory(SP, IX.Low); return 15; case 0xE9: // JP (IX) PC = IX.Word; return 8; case 0xF9: // LD SP, IX SP = IX.Word; return 10; default: // The Z80 Ignored Prefix Quirk! // If the instruction doesn't involve IX, ignore the prefix, // run the base instruction, and charge 4 T-States for the delay. return ExecuteOpcode(ddOpcode) + 4; } } private int ExecuteFDPrefix() { byte ddOpcode = FetchByte(); ushort targetAddress = 0; byte memVal = 0; switch (ddOpcode) { case 0x09: Add16(ref IY, BC.Word); return 15; case 0x19: Add16(ref IY, DE.Word); return 15; case 0x21: // LD IY, nn IY.Word = FetchWord(); return 14; case 0x22: // LD (nn), IY { byte addrLow = FetchByte(); byte addrHigh = FetchByte(); ushort address = (ushort)((addrHigh << 8) | addrLow); WriteMemory(address, IY.Low); WriteMemory((ushort)(address + 1), IY.High); return 20; } case 0x23: // INC IY IY.Word++; return 10; case 0x24: // INC IYH IY.High = Inc8(IY.High); return 8; case 0x25: // DEC IYH IY.High = Dec8(IY.High); return 8; case 0x26: // LD IYH, n IY.High = FetchByte(); return 11; case 0x29: Add16(ref IY, IY.Word); return 15; case 0x2A: // LD IY, (nn) { byte addrLow = FetchByte(); byte addrHigh = FetchByte(); ushort address = (ushort)((addrHigh << 8) | addrLow); IY.Low = ReadMemory(address); IY.High = ReadMemory((ushort)(address + 1)); return 20; } case 0x2B: // DEC IY IY.Word--; return 10; case 0x2C: // INC IYL IY.Low = Inc8(IY.Low); return 8; case 0x2D: // DEC IYL IY.Low = Dec8(IY.Low); return 8; case 0x2E: // LD IYL, n IY.Low = FetchByte(); return 11; case 0x34: // INC (IY+d) { sbyte offset34 = (sbyte)FetchByte(); ushort address34 = (ushort)(IY.Word + offset34); byte valBefore = ReadMemory(address34); byte result34 = Inc8(valBefore); WriteMemory(address34, result34); return 23; } case 0x35: // DEC (IY+d) sbyte offset = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + offset); memVal = ReadMemory(targetAddress); byte decVal = Dec8(memVal); WriteMemory(targetAddress, decVal); return 23; case 0x36: // LD (IY+d), n { sbyte offset36 = (sbyte)FetchByte(); byte nValue = FetchByte(); targetAddress = (ushort)(IY.Word + offset36); WriteMemory(targetAddress, nValue); return 19; } case 0x39: Add16(ref IY, SP); return 15; case 0x46: // LD B, (IY+d) { sbyte displacement = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + displacement); BC.High = ReadMemory(targetAddress); return 19; } case 0x4E: // LD C, (IY+d) sbyte offset4E = (sbyte)FetchByte(); ushort address4E = (ushort)(IY.Word + offset4E); BC.Low = ReadMemory(address4E); return 19; case 0x56: // LD D, (IY+d) sbyte offset56 = (sbyte)FetchByte(); ushort address56 = (ushort)(IY.Word + offset56); DE.High = ReadMemory(address56); return 19; case 0x5E: // LD E, (IY+d) sbyte offset5E = (sbyte)FetchByte(); ushort address5E = (ushort)(IY.Word + offset5E); DE.Low = ReadMemory(address5E); return 19; case 0x66: // LD H, (IY+d) sbyte offset66 = (sbyte)FetchByte(); ushort address66 = (ushort)(IY.Word + offset66); HL.High = ReadMemory(address66); return 19; case 0x6E: // LD L, (IY+d) sbyte displacementVal = (sbyte)FetchByte(); ushort targetAddr = (ushort)(IY.Word + displacementVal); HL.Low = ReadMemory(targetAddr); return 19; case 0x71: // LD (IY+d), C { sbyte offset71 = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + offset71); WriteMemory(targetAddress, BC.Low); return 19; } case 0x72: // LD (IY+d), D sbyte offset72 = (sbyte)FetchByte(); ushort address72 = (ushort)(IY.Word + offset72); WriteMemory(address72, DE.High); return 19; case 0x73: // LD (IY+d), E sbyte offset73 = (sbyte)FetchByte(); ushort address73 = (ushort)(IY.Word + offset73); WriteMemory(address73, DE.Low); return 19; case 0x74: // LD (IY+d), H sbyte offset74 = (sbyte)FetchByte(); ushort address74 = (ushort)(IY.Word + offset74); WriteMemory(address74, HL.High); return 19; case 0x75: // LD (IY+d), L sbyte offset75 = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + offset75); WriteMemory(targetAddress, HL.Low); return 19; case 0x77: // LD (IY+d), A sbyte offset77 = (sbyte)FetchByte(); ushort address77 = (ushort)(IY.Word + offset77); WriteMemory(address77, AF.High); return 19; case 0x7E: // LD A, (IY+d) sbyte offset7E = (sbyte)FetchByte(); ushort address7E = (ushort)(IY.Word + offset7E); AF.High = ReadMemory(address7E); return 19; case 0x84: // ADD A, IYH AddA(IY.High); return 8; case 0x85: // ADD A, IYL AddA(IY.Low); return 8; case 0x86: // ADD A, (IY+d) { sbyte displacementAdd = (sbyte)FetchByte(); ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd); byte valueToAdd = ReadMemory(targetAddressAdd); AddA(valueToAdd); return 19; } case 0x8E: // ADC A, (IY+d) { sbyte offset8E = (sbyte)FetchByte(); ushort address8E = (ushort)(IY.Word + offset8E); AdcA(ReadMemory(address8E)); return 19; } case 0x9E: // SBC A, (IY+d) { sbyte offset9E = (sbyte)FetchByte(); ushort address9E = (ushort)(IY.Word + offset9E); SbcA(ReadMemory(address9E)); return 19; } case 0xA6: // AND (IY+d) { sbyte offsetA6 = (sbyte)FetchByte(); ushort addressA6 = (ushort)(IY.Word + offsetA6); And(ReadMemory(addressA6)); return 19; } case 0xAE: // XOR (IY+d) { sbyte offsetAE = (sbyte)FetchByte(); ushort addressAE = (ushort)(IY.Word + offsetAE); Xor(ReadMemory(addressAE)); return 19; } // --- UNDOCUMENTED IY ALU OPERATIONS --- case 0x8C: AdcA(IY.High); return 8; case 0x8D: AdcA(IY.Low); return 8; case 0x94: SubA(IY.High, false); return 8; case 0x95: SubA(IY.Low, false); return 8; case 0x9C: SbcA(IY.High); return 8; case 0x9D: SbcA(IY.Low); return 8; case 0xA4: And(IY.High); return 8; case 0xA5: And(IY.Low); return 8; case 0xAC: Xor(IY.High); return 8; case 0xAD: Xor(IY.Low); return 8; case 0xB4: Or(IY.High); return 8; case 0xB5: Or(IY.Low); return 8; case 0xBC: SubA(IY.High, true); return 8; case 0xBD: SubA(IY.Low, true); return 8; // --- UNDOCUMENTED IY LOAD OPERATIONS --- case 0x44: BC.High = IY.High; return 8; // LD B, IYH case 0x45: BC.High = IY.Low; return 8; // LD B, IYL case 0x4C: BC.Low = IY.High; return 8; // LD C, IYH case 0x4D: BC.Low = IY.Low; return 8; // LD C, IYL case 0x54: DE.High = IY.High; return 8; // LD D, IYH case 0x55: DE.High = IY.Low; return 8; // LD D, IYL case 0x5C: DE.Low = IY.High; return 8; // LD E, IYH case 0x5D: DE.Low = IY.Low; return 8; // LD E, IYL case 0x60: IY.High = BC.High; return 8; // LD IYH, B case 0x61: IY.High = BC.Low; return 8; // LD IYH, C case 0x62: IY.High = DE.High; return 8; // LD IYH, D case 0x63: IY.High = DE.Low; return 8; // LD IYH, E case 0x64: return 8; // LD IYH, IYH case 0x65: IY.High = IY.Low; return 8; // LD IYH, IYL case 0x68: IY.Low = BC.High; return 8; // LD IYL, B case 0x69: IY.Low = BC.Low; return 8; // LD IYL, C case 0x6A: IY.Low = DE.High; return 8; // LD IYL, D case 0x6B: IY.Low = DE.Low; return 8; // LD IYL, E case 0x6C: IY.Low = IY.High; return 8; // LD IYL, IYH case 0x6D: return 8; // LD IYL, IYL case 0x96: // SUB (IY+d) { sbyte offset96 = (sbyte)FetchByte(); ushort address96 = (ushort)(IY.Word + offset96); SubA(ReadMemory(address96), false); return 19; } case 0xB6: // OR (IY+d) { sbyte offsetB6 = (sbyte)FetchByte(); ushort addressB6 = (ushort)(IY.Word + offsetB6); // Read the memory and pass it straight into your helper Or(ReadMemory(addressB6)); return 19; // Takes 19 T-States } case 0xBE: // CP (IY+d) { sbyte offsetBE = (sbyte)FetchByte(); ushort addressBE = (ushort)(IY.Word + offsetBE); SubA(ReadMemory(addressBE), true); return 19; } case 0xCB: // The FD CB nested prefix { sbyte displacement = (sbyte)FetchByte(); byte cbOpcode = FetchByte(); targetAddress = (ushort)(IY.Word + displacement); memVal = ReadMemory(targetAddress); int operation = cbOpcode >> 6; int bitIndex = (cbOpcode >> 3) & 0x07; byte bitMask = (byte)(1 << bitIndex); int regIndex = cbOpcode & 0x07; switch (operation) { case 0: // ALL Shift/Rotate Instructions int shiftType = (cbOpcode >> 3) & 0x07; // Perform the shift and flag math memVal = PerformShift(shiftType, memVal); WriteMemory(targetAddress, memVal); // Z80 UNDOCUMENTED QUIRK: // DDCB and FDCB shift instructions ALSO copy the result into a standard register // unless the target register index is 6 (which is purely memory). if (regIndex != 6) { switch (regIndex) { case 0: BC.High = memVal; break; case 1: BC.Low = memVal; break; case 2: DE.High = memVal; break; case 3: DE.Low = memVal; break; case 4: HL.High = memVal; break; case 5: HL.Low = memVal; break; case 7: AF.High = memVal; break; } } return 23; case 1: // ALL BIT Instructions AF.Low &= 0x01; AF.Low |= 0x10; if ((memVal & bitMask) == 0) { AF.Low |= 0x44; } else if (bitIndex == 7) { AF.Low |= 0x80; } return 20; case 2: // ALL RES Instructions memVal &= (byte)(~bitMask); WriteMemory(targetAddress, memVal); return 23; case 3: // ALL SET Instructions memVal |= bitMask; WriteMemory(targetAddress, memVal); return 23; default: throw new Exception("Invalid bitwise operation."); } } case 0xE1: // POP IY IY.Low = ReadMemory(SP); SP++; IY.High = ReadMemory(SP); SP++; return 14; case 0xE3: // EX (SP), IY byte spLowIY = ReadMemory(SP); byte spHighIY = ReadMemory((ushort)(SP + 1)); WriteMemory(SP, IY.Low); WriteMemory((ushort)(SP + 1), IY.High); IY.Low = spLowIY; IY.High = spHighIY; return 23; case 0xE5: // PUSH IY SP--; WriteMemory(SP, IY.High); SP--; WriteMemory(SP, IY.Low); return 15; case 0xF9: // LD SP, IY SP = IY.Word; return 10; default: // The Z80 Ignored Prefix Quirk! return ExecuteOpcode(ddOpcode) + 4; // Note: You named the fetched variable 'opcode' here instead of 'ddOpcode' } } } } //using System; //using Core.Interfaces; //using Core.Io; //namespace Core.Cpu //{ // public partial class Z80 // { // private static readonly byte[] ParityTable = new byte[256]; // // Static constructor to build the table once when the emulator starts // static Z80() // { // for (int i = 0; i < 256; i++) // { // int ones = 0; // for (int b = 0; b < 8; b++) if ((i & (1 << b)) != 0) ones++; // ParityTable[i] = (byte)((ones % 2 == 0) ? 0x04 : 0x00); // 0x04 if Even Parity // } // } // public bool IsZexDocMode { get; set; } = false; // //T-State counter // public long TotalTStates { get; set; } // public int InterruptMode { get; private set; } = 0; // // Interrupt Flip-Flops // public bool IFF1 { get; private set; } = false; // public bool IFF2 { get; private set; } = false; // public bool InterruptRequested { get; private set; } = false; // // Main Register Set // public RegisterPair AF; // public RegisterPair BC; // public RegisterPair DE; // public RegisterPair HL; // // Alternate Register Set // public RegisterPair AF_Prime; // public RegisterPair BC_Prime; // public RegisterPair DE_Prime; // public RegisterPair HL_Prime; // // Index Registers // public RegisterPair IX; // public RegisterPair IY; // // Special Purpose Registers // public ushort PC; // Program Counter // public ushort SP; // Stack Pointer // public byte I; // Interrupt Vector // public byte R; // Memory Refresh // // The Memory Bus // private readonly IMemory _memory; // private readonly IO_Bus _simpleIoBus; // public TapManager _tapManager; // //External Timing interface // public Func? WaitStateCallback { get; set; } // //Misc Variables // public bool EnableFastLoad { get; set; } = true; // byte newFlags = 0; // int result = 0; // public Z80(IMemory memory, IO_Bus ioBus, TapManager tapManager) // { // _memory = memory; // _simpleIoBus = ioBus; // _tapManager = tapManager; // Reset(); // } // public void Reset() // { // PC = 0x0000; // SP = 0xFFFF; // // Main Registers // AF.Word = 0; // BC.Word = 0; // DE.Word = 0; // HL.Word = 0; // // Alternate Registers // AF_Prime.Word = 0; // BC_Prime.Word = 0; // DE_Prime.Word = 0; // HL_Prime.Word = 0; // // Index Registers // IX.Word = 0; // IY.Word = 0; // // Internal Registers // I = 0; // R = 0; // // Hardware State // IFF1 = false; // IFF2 = false; // InterruptMode = 0; // TotalTStates = 0; // //_memory.CleanRAMData(); // } // private void ApplyWaitStates(ushort address) // { // // If a system (like a ULA) is attached and listening, ask it for the delay // if (WaitStateCallback != null) // { // TotalTStates += WaitStateCallback(address, TotalTStates); // } // } // public int RequestInterrupt() // { // InterruptRequested = true; // // 1. If the ROM has disabled interrupts (DI), ignore the request // if (!IFF1) return 0; // // 2. Acknowledge the interrupt by immediately disabling further interrupts // IFF1 = false; // IFF2 = false; // // 3. Push the current Program Counter to the stack so we can return later // Push(PC); // // --- Interrupt Mode Dispatch --- // if (InterruptMode == 1) // { // // IM 1: Hardcoded jump to ROM address 0x0038 // PC = 0x0038; // return 13; // IM 1 hardware call takes 13 T-States // } // else if (InterruptMode == 2) // { // // IM 2: Dynamic Vectored Interrupts // // A. Form the pointer address: High byte is 'I', Low byte is the floating bus (0xFF) // ushort vectorAddress = (ushort)((I << 8) | 0xFF); // // B. Read the actual 16-bit ISR address from that location in memory (Little-Endian) // byte pcLow = ReadMemory(vectorAddress); // byte pcHigh = ReadMemory((ushort)(vectorAddress + 1)); // // C. Jump to the custom game routine! // PC = (ushort)((pcHigh << 8) | pcLow); // return 19; // IM 2 hardware call takes 19 T-States // } // else // { // // (IM 0 is theoretically possible but essentially unused on the standard Spectrum) // throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!"); // } // } // // 1. For fetching opcodes and immediate values (Advances PC) // public byte FetchByte() // { // ApplyWaitStates(PC); // byte data = _memory.Read(PC); // PC++; // return data; // } // // 2. For fetching 16-bit immediate values // private ushort FetchWord() // { // // By using FetchByte twice, we perfectly apply wait states to BOTH memory reads! // byte low = FetchByte(); // byte high = FetchByte(); // return (ushort)((high << 8) | low); // } // // 3. For standard memory reads (e.g., LD A, (HL)) // public byte ReadMemory(ushort address) // { // ApplyWaitStates(address); // return _memory.Read(address); // } // // 4. For standard memory writes (e.g., LD (HL), A) // public void WriteMemory(ushort address, byte data) // { // ApplyWaitStates(address); // _memory.Write(address, data); // } // // Helper method to calculate if a byte has an Even Parity of 1s // private bool CalculateParity(byte b) // { // int bits = 0; // for (int i = 0; i < 8; i++) // { // if ((b & (1 << i)) != 0) bits++; // } // return (bits % 2) == 0; // } // // Placeholder for your hardware I/O // private byte ReadPort(ushort portAddress) // { // return _simpleIoBus.ReadPort(portAddress); // } // public int Step() // { // if (IsZexDocMode && PC == 0x0005) // { // // CP/M System Call Hook // if (BC.Low == 2) // C = 2: Print a single character stored in register E // { // System.Diagnostics.Debug.Write((char)DE.Low); // } // else if (BC.Low == 9) // C = 9: Print a string starting at memory address DE, terminated by '$' // { // ushort addr = DE.Word; // while (true) // { // char c = (char)ReadMemory(addr++); // if (c == '$') break; // System.Diagnostics.Debug.Write(c); // } // } // // Emulate a 'RET' instruction to return from the CALL 0x0005 // byte retLow = ReadMemory(SP); // SP++; // byte retHigh = ReadMemory(SP); // SP++; // PC = (ushort)((retHigh << 8) | retLow); // return 10; // Skip normal execution for this cycle // } // if (PC == 0x0556) // { // if (EnableFastLoad) // { // HandleInstantTapeLoad(); // return 100; // } // else // { // _tapManager.Play(); // } // } // // Fetch the next opcode and increment the Program Counter // byte opcode = ReadMemory(PC++); // int tStates = ExecuteOpcode(opcode); // TotalTStates += tStates; // // Decode and execute // return tStates; // } // public void LoadSNA(byte[] snaData) // { // if (snaData.Length != 49179) // throw new Exception("Invalid 48K SNA File Size!"); // // --- 1. Load CPU Registers --- // I = snaData[0]; // HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8)); // DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8)); // BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8)); // AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8)); // HL.Word = (ushort)(snaData[9] | (snaData[10] << 8)); // DE.Word = (ushort)(snaData[11] | (snaData[12] << 8)); // BC.Word = (ushort)(snaData[13] | (snaData[14] << 8)); // IY.Word = (ushort)(snaData[15] | (snaData[16] << 8)); // IX.Word = (ushort)(snaData[17] | (snaData[18] << 8)); // IFF2 = (snaData[19] & 0x04) != 0; // IFF1 = IFF2; // R = snaData[20]; // AF.Word = (ushort)(snaData[21] | (snaData[22] << 8)); // SP = (ushort)(snaData[23] | (snaData[24] << 8)); // InterruptMode = snaData[25]; // // --- 2. Load the 48K RAM Dump --- // // The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF // for (int i = 0; i < 49152; i++) // { // WriteMemory((ushort)(0x4000 + i), snaData[27 + i]); // } // // --- 3. The Magic Bullet --- // // In the SNA format, the Program Counter (PC) isn't in the header. // // It was PUSHED to the stack exactly 1 instruction before the snapshot was saved. // // So, we just pop it off the stack to resume execution! // PC = Pop(); // } // private void HandleInstantTapeLoad() // { // // 1. Grab the next block from the virtual cassette // byte[] block = _tapManager.GetNextBlock(); // if (block == null) return; // Tape ended unexpectedly // // 2. Verify the block type. // // The ROM passes the expected flag (0x00 for Header, 0xFF for Data) in the A register. // byte expectedFlag = AF.High; // if (block[0] != expectedFlag) // { // // Tape loading error! Simulate a failure by clearing the Carry flag and returning. // AF.Low &= unchecked((byte)~0x01); // ExecuteRet(); // return; // } // // 3. Copy the data block straight into the Spectrum's RAM! // // DE holds the number of bytes the ROM *wants*. We copy that much, skipping the Flag byte. // int bytesToCopy = DE.Word; // for (int i = 0; i < bytesToCopy; i++) // { // WriteMemory((ushort)(IX.Word + i), block[i + 1]); // } // // 4. Update the registers exactly how the ROM would after a successful load // IX.Word = (ushort)(IX.Word + bytesToCopy); // DE.Word = 0; // // 5. Simulate the Checksum Match (Accumulator becomes 0) // AF.High = 0x00; // // 6. Set Carry Flag (Bit 0) for Success, and Zero Flag (Bit 6) for Checksum Match // AF.Low |= 0x41; // 0x41 is binary 0100 0001 (Zero and Carry both set) // // 7. Simulate a standard 'RET' instruction to exit the LD-BYTES routine safely // ExecuteRet(); // } // // A quick helper to simulate a RET instruction manually // private void ExecuteRet() // { // byte pcLow = ReadMemory(SP); // SP++; // byte pcHigh = ReadMemory(SP); // SP++; // PC = (ushort)((pcHigh << 8) | pcLow); // } // // Reads a 16-bit word from the current PC (Little-Endian) and advances PC by 2 // public string GetFlagsString() // { // byte f = AF.Low; // return $"S:{(f >> 7) & 1} " + // $"Z:{(f >> 6) & 1} " + // $"Y:{(f >> 5) & 1} " + // Undocumented flag // $"H:{(f >> 4) & 1} " + // $"X:{(f >> 3) & 1} " + // Undocumented flag // $"P/V:{(f >> 2) & 1} " + // $"N:{(f >> 1) & 1} " + // $"C:{f & 1}"; // } // private void Sub(byte value) // { // byte a = AF.High; // result = a - value; // // Save the result back to the Accumulator // AF.High = (byte)result; // // --- Update Flags (F Register) --- // AF.Low = 0; // Clear all flags // // Sign Flag (Bit 7) // if ((result & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if ((byte)result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 // if (((a & 0x0F) - (value & 0x0F)) < 0) AF.Low |= 0x10; // // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes // if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04; // // Subtract Flag (Bit 1) - ALWAYS set for CP/SUB // AF.Low |= 0x02; // // Carry Flag (Bit 0) - Set if the overall result dropped below 0 // if (result < 0) AF.Low |= 0x01; // } // private void Sbc(byte value) // { // byte a = AF.High; // byte carry = (byte)(AF.Low & 0x01); // Get the current Carry flag (Bit 0) // // Calculate the raw integer result to check for borrows/underflows // result = a - value - carry; // // Update the Accumulator // AF.High = (byte)result; // // --- Update Flags (F Register) --- // AF.Low = 0; // Clear all flags // // Sign Flag (Bit 7) // if ((result & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if ((byte)result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 // if (((a & 0x0F) - (value & 0x0F) - carry) < 0) AF.Low |= 0x10; // // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes // if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04; // // Subtract Flag (Bit 1) - ALWAYS set for subtraction // AF.Low |= 0x02; // // Carry Flag (Bit 0) - Set if the overall result dropped below 0 // if (result < 0) AF.Low |= 0x01; // } // private void Sbc16(ushort value) // { // int hl = HL.Word; // int carry = AF.Low & 0x01; // // Calculate the raw integer result to check for underflows // result = hl - value - carry; // // Update the HL register // HL.Word = (ushort)result; // // --- Update Flags (F Register) --- // AF.Low = 0; // Clear all flags // // Sign Flag (Bit 7) - Set if the 16-bit result is negative (bit 15 is 1) // if ((result & 0x8000) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) - Set if the entire 16-bit result is exactly 0 // if ((ushort)result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if borrow from bit 11 // if (((hl & 0x0FFF) - (value & 0x0FFF) - carry) < 0) AF.Low |= 0x10; // // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes // if ((((hl ^ value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) AF.Low |= 0x04; // // Subtract Flag (Bit 1) - ALWAYS set for subtraction // AF.Low |= 0x02; // // Carry Flag (Bit 0) - Set if the overall 16-bit result dropped below 0 // if (result < 0) AF.Low |= 0x01; // } // private void SbcHl(ushort value) // { // int op1 = HL.Word; // int op2 = value; // int carry = AF.Low & 0x01; // Current C flag // int result = op1 - op2 - carry; // byte flags = 0x02; // N: Always 1 (Subtract) // if (((result >> 8) & 0x80) != 0) flags |= 0x80; // S: Sign flag (Bit 15) // if ((result & 0xFFFF) == 0) flags |= 0x40; // Z: Zero flag // // H: Half-borrow from Bit 12 // if ((((op1 & 0x0FFF) - (op2 & 0x0FFF) - carry) & 0x1000) != 0) flags |= 0x10; // // P/V: 16-bit Overflow logic // if ((((op1 ^ op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04; // // C: Borrow from Bit 15 // if (result < 0) flags |= 0x01; // // Undocumented bits 3 and 5 come from the High byte of the calculated result // flags |= (byte)((result >> 8) & 0x28); // AF.Low = flags; // HL.Word = (ushort)(result & 0xFFFF); // } // private byte Dec8(byte value) // { // byte result = (byte)(value - 1); // // Store the existing Carry flag so we can preserve it // byte carry = (byte)(AF.Low & 0x01); // // Clear all flags // AF.Low = 0; // // Sign Flag (Bit 7) // if ((result & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if (result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 (happens if the lower nibble was 0) // if ((value & 0x0F) == 0) AF.Low |= 0x10; // // Parity/Overflow Flag (Bit 2) - Set if the original value was 0x80 (maximum negative) // if (value == 0x80) AF.Low |= 0x04; // // Subtract Flag (Bit 1) - ALWAYS SET for decrements // AF.Low |= 0x02; // // Restore the original Carry Flag (Bit 0) // AF.Low |= carry; // return result; // } // private byte Inc8(byte value) // { // byte result = (byte)(value + 1); // // Store the existing Carry flag so we can preserve it // byte carry = (byte)(AF.Low & 0x01); // // Clear all flags // AF.Low = 0; // // Sign Flag (Bit 7) // if ((result & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if (result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if carry from bit 3 (happens if lower nibble was 0x0F) // if ((value & 0x0F) == 0x0F) AF.Low |= 0x10; // // Parity/Overflow Flag (Bit 2) - Set if the original value was 0x7F (maximum positive) // if (value == 0x7F) AF.Low |= 0x04; // // Subtract Flag (Bit 1) - ALWAYS 0 for increments (already 0 because we cleared AF.Low) // // Restore the original Carry Flag (Bit 0) // AF.Low |= carry; // return result; // } // private void Cp(byte value) // { // byte a = AF.High; // result = a - value; // // --- Update Flags (F Register) --- // AF.Low = 0; // Clear all flags // // Sign Flag (Bit 7) // if ((result & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if ((byte)result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 // if (((a & 0x0F) - (value & 0x0F)) < 0) AF.Low |= 0x10; // // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes // if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04; // // Subtract Flag (Bit 1) - ALWAYS set for CP/SUB // AF.Low |= 0x02; // // Carry Flag (Bit 0) - Set if the overall result dropped below 0 // if (result < 0) AF.Low |= 0x01; // } // private void And(byte value) // { // AF.High = (byte)(AF.High & value); // // --- Update Flags --- // AF.Low = 0; // Clear all flags // // Sign Flag (Bit 7) - Set if the highest bit is 1 // if ((AF.High & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) - Set if the result is 0 // if (AF.High == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - ALWAYS SET to 1 for Z80 AND instructions! // AF.Low |= 0x10; // // Parity Flag (Bit 2) - Set if the result has an even number of 1 bits // if (HasEvenParity(AF.High)) AF.Low |= 0x04; // // Subtract Flag (N) and Carry Flag (C) are ALWAYS 0 // } // private void Or(byte value) // { // AF.High = (byte)(AF.High | value); // // --- Update Flags --- // AF.Low = 0; // Clear all flags (H, N, and C are always 0 for OR) // // Sign Flag (Bit 7) - Set if the highest bit is 1 // if ((AF.High & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) - Set if the result is 0 // if (AF.High == 0) AF.Low |= 0x40; // // Parity Flag (Bit 2) - Set if the result has an even number of 1 bits // if (HasEvenParity(AF.High)) AF.Low |= 0x04; // } // private void Xor(byte value) // { // // The caret (^) is the C# Bitwise XOR operator // AF.High = (byte)(AF.High ^ value); // // --- Update Flags --- // AF.Low = 0; // Clear all flags (H, N, and C are always 0 for XOR) // // Sign Flag (Bit 7) - Set if the highest bit is 1 // if ((AF.High & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) - Set if the result is 0 // if (AF.High == 0) AF.Low |= 0x40; // // Parity Flag (Bit 2) - Set if the result has an even number of 1 bits // if (HasEvenParity(AF.High)) AF.Low |= 0x04; // } // private void Add16(ushort value) // { // int hl = HL.Word; // result = hl + value; // // Update the HL register // HL.Word = (ushort)result; // AF.Low &= 0xEC; // // Half-Carry Flag (Bit 4) - Set if there is a carry from bit 11 // if (((hl & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) AF.Low |= 0x10; // // Carry Flag (Bit 0) - Set if the result overflows 16 bits // if (result > 0xFFFF) AF.Low |= 0x01; // } // private void Add16IX(ushort value) // { // int ixVal = IX.Word; // result = ixVal + value; // // --- 16-Bit ADD IX Flag Calculation --- // // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2). // // This perfectly resets N (Bit 1) to 0 at the same time. // newFlags = (byte)(AF.Low & 0xC4); // // Half-Carry (H - Bit 4): Set if carry from Bit 11 // if (((ixVal & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) newFlags |= 0x10; // // Carry (C - Bit 0): Set if the total result overflows 16 bits // if (result > 0xFFFF) newFlags |= 0x01; // AF.Low = newFlags; // IX.Word = (ushort)result; // } // private void Add(byte value) // { // byte a = AF.High; // result = a + value; // // Save the result back to the Accumulator // AF.High = (byte)result; // // --- Update Flags (F Register) --- // AF.Low = 0; // Clear all flags (This also correctly resets the N flag to 0) // // Sign Flag (Bit 7) // if ((result & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if ((byte)result == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Set if carry from bit 3 // if (((a & 0x0F) + (value & 0x0F)) > 0x0F) AF.Low |= 0x10; // // Overflow/Parity Flag (Bit 2) - For addition, overflow happens if two numbers // // with the SAME sign are added and produce a result with a DIFFERENT sign. // if ((((a ^ ~value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04; // // Carry Flag (Bit 0) - Set if the result is greater than 255 // if (result > 0xFF) AF.Low |= 0x01; // } // private void AdcA(byte operand) // { // int aVal = AF.High; // int carryIn = AF.Low & 0x01; // result = aVal + operand + carryIn; // byte newFlags = 0; // if ((result & 0x80) != 0) newFlags |= 0x80; // S Flag // if ((result & 0xFF) == 0) newFlags |= 0x40; // Z Flag // if (((aVal & 0x0F) + (operand & 0x0F) + carryIn) > 0x0F) newFlags |= 0x10; // H Flag // if ((((aVal ^ ~operand) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; // P/V Flag // if (result > 0xFF) newFlags |= 0x01; // C Flag // AF.Low = newFlags; // AF.High = (byte)result; // } // private void Adc16(ushort value) // { // int hl = HL.Word; // int carry = AF.Low & 0x01; // // Calculate the raw integer result to check for overflows // result = hl + value + carry; // // --- Update Flags (F Register) --- // byte newFlags = 0; // Clear all flags (which forces N to 0, correctly!) // // Sign Flag (Bit 7) - Set if the 16-bit result is negative (bit 15 is 1) // if ((result & 0x8000) != 0) newFlags |= 0x80; // // Zero Flag (Bit 6) - Set if the entire 16-bit result is exactly 0 // if ((result & 0xFFFF) == 0) newFlags |= 0x40; // // Half-Carry Flag (Bit 4) - Set if there is a carry out of bit 11 // if (((hl & 0x0FFF) + (value & 0x0FFF) + carry) > 0x0FFF) newFlags |= 0x10; // // Overflow Flag (Bit 2) - Set if operands have the SAME sign, but result sign changes // if ((((hl ^ ~value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) newFlags |= 0x04; // // Carry Flag (Bit 0) - Set if the overall 16-bit result overflowed 0xFFFF // if (result > 0xFFFF) newFlags |= 0x01; // AF.Low = newFlags; // HL.Word = (ushort)result; // } // private void AdcHl(ushort value) // { // int op1 = HL.Word; // int op2 = value; // int carry = AF.Low & 0x01; // Current C flag // int result = op1 + op2 + carry; // byte flags = 0; // if (((result >> 8) & 0x80) != 0) flags |= 0x80; // S: Sign flag (Bit 15) // if ((result & 0xFFFF) == 0) flags |= 0x40; // Z: Zero flag // // H: Half-carry from Bit 11 // if ((((op1 & 0x0FFF) + (op2 & 0x0FFF) + carry) & 0x1000) != 0) flags |= 0x10; // // P/V: 16-bit Overflow logic // if ((((op1 ^ ~op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04; // // N: Always 0 for Add // // C: Carry from Bit 15 // if ((result & 0x10000) != 0) flags |= 0x01; // // Undocumented bits 3 and 5 come from the High byte of the calculated result // flags |= (byte)((result >> 8) & 0x28); // AF.Low = flags; // HL.Word = (ushort)(result & 0xFFFF); // } // private void AddHl(ushort value) // { // int result = HL.Word + value; // // 1. Preserve S (0x80), Z (0x40), and P/V (0x04) from the current flag register // byte flags = (byte)(AF.Low & 0xC4); // // 2. Calculate H (Half-carry from bit 11) // if (((HL.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) // { // flags |= 0x10; // } // // 3. Calculate C (Carry from bit 15) // if (result > 0xFFFF) // { // flags |= 0x01; // } // // 4. Undocumented bits 3 and 5 come from the High byte of the calculated result // flags |= (byte)((result >> 8) & 0x28); // // N is naturally left as 0 because of our initial bitmask // AF.Low = flags; // HL.Word = (ushort)result; // } // private void AddA(byte operand) // { // byte a = AF.High; // int result = a + operand; // Use a local int to easily catch the carry // AF.High = (byte)result; // // --- Update Flags --- // AF.Low = 0; // Clear all flags initially (Forces N to 0) // // Sign Flag (Bit 7) // if ((AF.High & 0x80) != 0) AF.Low |= 0x80; // // Zero Flag (Bit 6) // if (AF.High == 0) AF.Low |= 0x40; // // Half-Carry Flag (Bit 4) - Check if bits 0-3 overflowed // if (((a & 0x0F) + (operand & 0x0F)) > 0x0F) AF.Low |= 0x10; // // Parity/Overflow Flag (Bit 2) // bool sameSign = ((a ^ operand) & 0x80) == 0; // Did inputs have the same sign? // bool changedSign = ((a ^ AF.High) & 0x80) != 0; // Did the result's sign flip? // if (sameSign && changedSign) AF.Low |= 0x04; // // Carry Flag (Bit 0) - Check if the whole 8-bit addition overflowed // if (result > 0xFF) AF.Low |= 0x01; // // UNDOCUMENTED FLAGS (Bits 3 and 5) - Copied directly from the result // AF.Low |= (byte)(AF.High & 0x28); // } // private void AddIx(ushort value) // { // int result = IX.Word + value; // // Preserve S, Z, and P/V // byte flags = (byte)(AF.Low & 0xC4); // // Calculate H (Half-carry from bit 11) // if (((IX.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10; // // Calculate C (Carry from bit 15) // if (result > 0xFFFF) flags |= 0x01; // // Undocumented bits 3 and 5 from the High byte of the result // flags |= (byte)((result >> 8) & 0x28); // AF.Low = flags; // IX.Word = (ushort)result; // } // private void AddIy(ushort value) // { // int result = IY.Word + value; // // Preserve S, Z, and P/V // byte flags = (byte)(AF.Low & 0xC4); // // Calculate H (Half-carry from bit 11) // if (((IY.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10; // // Calculate C (Carry from bit 15) // if (result > 0xFFFF) flags |= 0x01; // // Undocumented bits 3 and 5 from the High byte of the result // flags |= (byte)((result >> 8) & 0x28); // AF.Low = flags; // IY.Word = (ushort)result; // } // private bool HasEvenParity(byte value) // { // int bits = 0; // for (int i = 0; i < 8; i++) // { // if ((value & (1 << i)) != 0) bits++; // } // return (bits % 2) == 0; // } // private void Push(ushort value) // { // // High byte goes first // SP--; // WriteMemory(SP, (byte)(value >> 8)); // // Low byte goes second // SP--; // WriteMemory(SP, (byte)(value & 0xFF)); // } // private ushort Pop() // { // // The Z80 is Little-Endian. Low byte comes off the stack first. // byte low = ReadMemory(SP++); // // High byte comes off second. // byte high = ReadMemory(SP++); // return (ushort)((high << 8) | low); // } // private int ExecuteOpcode(byte opcode) // { // sbyte offset = 0; // byte oldCarry = 0; // switch (opcode) // { // case 0x00: // NOP // return 4; // case 0x01: // LD BC, nn // BC.Word = FetchWord(); // return 10; // case 0x02: // LD (BC), A // WriteMemory(BC.Word, AF.High); // return 7; // case 0x03: // INC BC // BC.Word++; // return 6; // // --- 8-Bit Increments --- // case 0x04: BC.High = Inc8(BC.High); return 4; // INC B // case 0x07: // RLCA // // 1. Grab the top bit (Bit 7) before it rotates // byte topBit = (byte)(AF.High >> 7); // // 2. Shift A left by 1, and drop the top bit into the newly empty Bit 0 // AF.High = (byte)((AF.High << 1) | topBit); // // --- RLCA Specific Flag Rules --- // // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2). // byte newFlags = (byte)(AF.Low & 0xC4); // // H (Bit 4) and N (Bit 1) are forcefully reset to 0 (which the bitwise AND above just did). // // C (Bit 0): Set if the bit that rotated off the top was a 1 // if (topBit != 0) newFlags |= 0x01; // AF.Low = newFlags; // return 4; // 4 T-States // case 0x08: // EX AF, AF' // ushort tempAF = AF.Word; // AF.Word = AF_Prime.Word; // AF_Prime.Word = tempAF; // return 4; // // Inside your base switch(opcode) statement: // case 0x09: AddHl(BC.Word); return 11; // case 0x0A: //LD A (BC) // AF.High = ReadMemory(BC.Word); // return 7; // case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C // case 0x12: // LD (DE), A // WriteMemory(DE.Word, AF.High); // return 7; // case 0x14: DE.High = Inc8(DE.High); return 4; // INC D // case 0x19: AddHl(DE.Word); return 11; // case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E // case 0x1E: DE.Low = FetchByte(); // LD E, n // return 7; // case 0x29: AddHl(HL.Word); return 11; // case 0x24: HL.High = Inc8(HL.High); return 4; // INC H // case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L // case 0x2E: // LD L, n // HL.Low = FetchByte(); // return 7; // case 0x34: // WriteMemory(HL.Word, Inc8(ReadMemory(HL.Word))); // return 11; // INC (HL) takes 11 T-States // case 0x39: AddHl(SP); return 11; // case 0x3C: AF.High = Inc8(AF.High); return 4; // INC A // // --- 8-Bit Decrements --- // case 0x05: BC.High = Dec8(BC.High); return 4; // DEC B // case 0x0D: BC.Low = Dec8(BC.Low); return 4; // DEC C // case 0x15: DE.High = Dec8(DE.High); return 4; // DEC D // case 0x1D: DE.Low = Dec8(DE.Low); return 4; // DEC E // case 0x25: HL.High = Dec8(HL.High); return 4; // DEC H // case 0x2D: HL.Low = Dec8(HL.Low); return 4; // DEC L // case 0x2F: // CPL // // Flip all bits in the Accumulator // AF.High = (byte)(~AF.High); // // Set Half-Carry (Bit 4) and Subtract (Bit 1). // // Bitwise OR forces them to 1 while perfectly preserving S, Z, P/V, and C. // AF.Low |= 0x12; // return 4; // case 0x35: // WriteMemory(HL.Word, Dec8(ReadMemory(HL.Word))); // return 11; // DEC (HL) takes 11 T-States // case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A // case 0x06: // LD B, n // BC.High = FetchByte(); // return 7; // case 0x0B: // DEC BC // BC.Word--; // return 6; // case 0x0E: // LD C, n // BC.Low = FetchByte(); // return 7; // case 0x0F: // RRCA // { // // 1. Grab the bit that is about to fall off // byte bit0 = (byte)(AF.High & 0x01); // // 2. Shift right, and force the old Bit 0 into the Bit 7 position // AF.High = (byte)((AF.High >> 1) | (bit0 << 7)); // // 3. Update Flags // // S (0x80), Z (0x40), and P/V (0x04) are completely PRESERVED. // // H (0x10) and N (0x02) are forcefully RESET to 0. // // ANDing with 0xC4 (Binary 1100 0100) does exactly this. // AF.Low &= 0xC4; // // Set the Carry Flag (Bit 0) to whatever fell off // AF.Low |= bit0; // return 4; // } // case 0x10: // DJNZ d // sbyte djnzOffset = (sbyte)FetchByte(); // BC.High--; // Decrement the B register // if (BC.High != 0) // { // PC = (ushort)(PC + djnzOffset); // return 13; // Jump taken // } // return 8; // Loop finished, no jump // case 0x11: //LD DE, nn // DE.Word = FetchWord(); // return 10; // case 0x13: // INC DE // DE.Word++; // return 6; // case 0x16: // LD D, n // DE.High = FetchByte(); // return 7; // case 0x17: // RLA // // 1. Grab the current Carry flag (Bit 0 of AF.Low) // oldCarry = (byte)(AF.Low & 0x01); // // 2. See if Bit 7 of the Accumulator is about to fall off // bool newCarry = (AF.High & 0x80) != 0; // // 3. Shift A left, and drop the OLD carry directly into Bit 0 // AF.High = (byte)((AF.High << 1) | oldCarry); // // 4. Update the flags // // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2) while wiping H and N. // AF.Low &= 0xC4; // // 5. Apply the new Carry flag if necessary // if (newCarry) AF.Low |= 0x01; // return 4; // 4 T-States // case 0x18: // JR d // sbyte jumpDistance = (sbyte)FetchByte(); // // PC has already been incremented by FetchByte(), so it is // // pointing exactly where it needs to be for the relative addition. // PC = (ushort)(PC + jumpDistance); // return 12; // case 0x1A: // LD A, (DE) // AF.High = ReadMemory(DE.Word); // return 7; // case 0x1B: // DEC DE // DE.Word--; // return 6; // case 0x1F: // RRA // { // // 1. Grab the current Carry Flag (0 or 1) // oldCarry = (byte)(AF.Low & 0x01); // // 2. Grab the bit that is about to fall off the Accumulator // byte bit0 = (byte)(AF.High & 0x01); // // 3. Shift right, and force the OLD Carry flag into the Bit 7 position // AF.High = (byte)((AF.High >> 1) | (oldCarry << 7)); // // 4. Update Flags // // S (0x80), Z (0x40), and P/V (0x04) are PRESERVED exactly as they are. // // H (0x10) and N (0x02) are forcefully RESET to 0. // AF.Low &= 0xC4; // // Set the new Carry Flag (Bit 0) to whatever fell off the Accumulator // AF.Low |= bit0; // return 4; // } // case 0x20: // JR NZ, e // offset = (sbyte)FetchByte(); // if ((AF.Low & 0x40) == 0) // { // PC = (ushort)(PC + offset); // return 12; // } // return 7; // case 0x21: // LD HL, nn // HL.Word = FetchWord(); // return 10; // case 0x22: // LD (nn), HL // ushort dest22 = FetchWord(); // WriteMemory(dest22, HL.Low); // WriteMemory((ushort)(dest22 + 1), HL.High); // return 16; // case 0x23: // INC HL // HL.Word++; // return 6; // case 0x26: // LD H, n // HL.High = FetchByte(); // return 7; // case 0x27: // DAA // byte a = AF.High; // int correction = 0; // byte flags = AF.Low; // bool carry = (flags & 0x01) != 0; // bool halfCarry = (flags & 0x10) != 0; // bool isSub = (flags & 0x02) != 0; // The N flag tells us if we should add or subtract! // // 1. Check if the lower nibble needs adjustment // if (halfCarry || (a & 0x0F) > 9) // { // correction |= 0x06; // } // // 2. Check if the upper nibble needs adjustment // if (carry || a > 0x99 || (a >= 0x90 && (a & 0x0F) > 9)) // { // correction |= 0x60; // carry = true; // The final carry flag will be true // } // // 3. Apply the correction and calculate the new Half-Carry // bool newHalfCarry = false; // if (isSub) // { // newHalfCarry = halfCarry && (a & 0x0F) < 0x06; // a = (byte)(a - correction); // } // else // { // newHalfCarry = ((a & 0x0F) + (correction & 0x0F)) > 0x0F; // a = (byte)(a + correction); // } // AF.High = a; // // 4. Build the new flags // flags &= 0x02; // Wipe everything except the N flag (which is strictly preserved) // if (carry) flags |= 0x01; // if (newHalfCarry) flags |= 0x10; // if ((a & 0x80) != 0) flags |= 0x80; // S flag // if (a == 0) flags |= 0x40; // Z flag // if (CalculateParity(a)) flags |= 0x04; // P/V flag // AF.Low = flags; // return 4; // case 0x28: // JR Z, e // offset = (sbyte)FetchByte(); // // Check if the Zero Flag is set // if ((AF.Low & 0x40) != 0) // { // PC = (ushort)(PC + offset); // return 12; // } // return 7; // case 0x2A: // LD HL, (nn) // { // ushort srcAddress = FetchWord(); // HL.Low = ReadMemory(srcAddress); // HL.High = ReadMemory((ushort)(srcAddress + 1)); // return 16; // } // case 0x2B: // DEC HL // HL.Word--; // return 6; // case 0x30: // JR NC, e // offset = (sbyte)FetchByte(); // // Check if the Carry Flag (Bit 0) is NOT set // if ((AF.Low & 0x01) == 0) // { // PC = (ushort)(PC + offset); // return 12; // Jump taken // } // return 7; // case 0x31: // LD SP, nn // { // SP = FetchWord(); // return 10; // } // case 0x32: // LD (nn), A // { // ushort destAddress = FetchWord(); // WriteMemory(destAddress, AF.High); // return 13; // } // case 0x33: // INC SP // SP++; // return 6; // case 0x36: // LD (HL), n // byte nValue = FetchByte(); // WriteMemory(HL.Word, nValue); // return 10; // case 0x37: // SCF // AF.Low |= 0x01; // Force Carry Flag (Bit 0) to 1 // AF.Low &= 0xED; // return 4; // case 0x38: // JR C, d // sbyte jrCOffset = (sbyte)FetchByte(); // // Check if the Carry Flag (Bit 0) IS set (1) // if ((AF.Low & 0x01) != 0) // { // PC = (ushort)(PC + jrCOffset); // return 12; // } // return 7; // case 0x3A: // LD A, (nn) // ushort address3A = FetchWord(); // AF.High = ReadMemory(address3A); // return 13; // case 0x3B: // DEC SP // SP--; // return 6; // case 0x3E: //LD A, n // AF.High = FetchByte(); // return 7; // case 0x3F: // CCF // bool previousCarry = (AF.Low & 0x01) != 0; // AF.Low ^= 0x01; // Invert Carry Flag (Bit 0) // AF.Low &= 0xFD; // Force Subtract Flag (N, Bit 1) to 0 // // Set Half-Carry (H, Bit 4) to the previous Carry state // if (previousCarry) // AF.Low |= 0x10; // else // AF.Low &= 0xEF; // return 4; // case 0x40: //BC.High = BC.High; // return 4; // case 0x41: BC.High = BC.Low; return 4; // case 0x42: BC.High = DE.High; return 4; // case 0x43: BC.High = DE.Low; return 4; // case 0x44: BC.High = HL.High; return 4; // case 0x45: BC.High = HL.Low; return 4; // case 0x46: BC.High = ReadMemory(HL.Word); return 7; // case 0x47: BC.High = AF.High; return 4; // // --- LD C, r --- // case 0x48: BC.Low = BC.High; return 4; // case 0x49: //BC.Low = BC.Low; // return 4; // case 0x4A: BC.Low = DE.High; return 4; // case 0x4B: BC.Low = DE.Low; return 4; // case 0x4C: BC.Low = HL.High; return 4; // case 0x4D: BC.Low = HL.Low; return 4; // case 0x4E: BC.Low = ReadMemory(HL.Word); return 7; // case 0x4F: BC.Low = AF.High; return 4; // // --- LD D, r --- // case 0x50: DE.High = BC.High; return 4; // case 0x51: DE.High = BC.Low; return 4; // case 0x52: //DE.High = DE.High; // return 4; // case 0x53: DE.High = DE.Low; return 4; // case 0x54: DE.High = HL.High; return 4; // case 0x55: DE.High = HL.Low; return 4; // case 0x56: DE.High = ReadMemory(HL.Word); return 7; // case 0x57: DE.High = AF.High; return 4; // // --- LD E, r --- // case 0x58: DE.Low = BC.High; return 4; // case 0x59: DE.Low = BC.Low; return 4; // case 0x5A: DE.Low = DE.High; return 4; // case 0x5B: //DE.Low = DE.Low; // return 4; // case 0x5C: DE.Low = HL.High; return 4; // case 0x5D: DE.Low = HL.Low; return 4; // case 0x5E: DE.Low = ReadMemory(HL.Word); return 7; // case 0x5F: DE.Low = AF.High; return 4; // // --- LD H, r --- // case 0x60: HL.High = BC.High; return 4; // case 0x61: HL.High = BC.Low; return 4; // case 0x62: HL.High = DE.High; return 4; // case 0x63: HL.High = DE.Low; return 4; // case 0x64: //HL.High = HL.High; // return 4; // case 0x65: HL.High = HL.Low; return 4; // case 0x66: HL.High = ReadMemory(HL.Word); return 7; // case 0x67: HL.High = AF.High; return 4; // // --- LD L, r --- // case 0x68: HL.Low = BC.High; return 4; // case 0x69: HL.Low = BC.Low; return 4; // case 0x6A: HL.Low = DE.High; return 4; // case 0x6B: HL.Low = DE.Low; return 4; // case 0x6C: HL.Low = HL.High; return 4; // case 0x6D: //HL.Low = HL.Low; // return 4; // case 0x6E: HL.Low = ReadMemory(HL.Word); return 7; // case 0x6F: HL.Low = AF.High; return 4; // // --- LD (HL), r --- (Note: 0x76 is HALT, so it is skipped here) // case 0x70: WriteMemory(HL.Word, BC.High); return 7; // case 0x71: WriteMemory(HL.Word, BC.Low); return 7; // case 0x72: WriteMemory(HL.Word, DE.High); return 7; // case 0x73: WriteMemory(HL.Word, DE.Low); return 7; // case 0x74: WriteMemory(HL.Word, HL.High); return 7; // case 0x75: WriteMemory(HL.Word, HL.Low); return 7; // case 0x76: //HALT // if (!InterruptRequested) // { // PC--; // return 4; // } // else // { // InterruptRequested = false; // return 4; // } // case 0x77: WriteMemory(HL.Word, AF.High); return 7; // // --- LD A, r --- // case 0x78: AF.High = BC.High; return 4; // case 0x79: AF.High = BC.Low; return 4; // case 0x7A: AF.High = DE.High; return 4; // case 0x7B: AF.High = DE.Low; return 4; // case 0x7C: AF.High = HL.High; return 4; // case 0x7D: AF.High = HL.Low; return 4; // case 0x7E: AF.High = ReadMemory(HL.Word); return 7; // case 0x7F: //AF.High = AF.High; // return 4; // case 0x80: Add(BC.High); return 4; // ADD A, B // case 0x81: Add(BC.Low); return 4; // ADD A, C // case 0x82: Add(DE.High); return 4; // ADD A, D // case 0x83: Add(DE.Low); return 4; // ADD A, E // case 0x84: Add(HL.High); return 4; // ADD A, H // case 0x85: Add(HL.Low); return 4; // ADD A, L // case 0x86: Add(ReadMemory(HL.Word)); return 7; // ADD A, (HL) // case 0x87: Add(AF.High); return 4; // ADD A, A // // --- ADC A, Register Family --- // case 0x88: AdcA(BC.High); return 4; // ADC A, B // case 0x89: AdcA(BC.Low); return 4; // ADC A, C // case 0x8A: AdcA(DE.High); return 4; // ADC A, D // case 0x8B: AdcA(DE.Low); return 4; // ADC A, E // case 0x8C: AdcA(HL.High); return 4; // ADC A, H // case 0x8D: AdcA(HL.Low); return 4; // ADC A, L // case 0x8F: AdcA(AF.High); return 4; // ADC A, A // // --- ADC A, Memory --- // case 0x8E: // ADC A, (HL) // AdcA(ReadMemory(HL.Word)); // return 7; // // --- ADC A, Immediate --- // case 0xCE: // ADC A, n // AdcA(FetchByte()); // return 7; // case 0x90: Sub(BC.High); return 4; // SUB B // case 0x91: Sub(BC.Low); return 4; // SUB C // case 0x92: Sub(DE.High); return 4; // SUB D // case 0x93: Sub(DE.Low); return 4; // SUB E // case 0x94: Sub(HL.High); return 4; // SUB H // case 0x95: Sub(HL.Low); return 4; // SUB L // case 0x96: Sub(ReadMemory(HL.Word)); return 7; // SUB (HL) // case 0x97: Sub(AF.High); return 4; // SUB A // // --- SBC A, r --- // case 0x98: Sbc(BC.High); return 4; // SBC A, B // case 0x99: Sbc(BC.Low); return 4; // SBC A, C // case 0x9A: Sbc(DE.High); return 4; // SBC A, D // case 0x9B: Sbc(DE.Low); return 4; // SBC A, E // case 0x9C: Sbc(HL.High); return 4; // SBC A, H // case 0x9D: Sbc(HL.Low); return 4; // SBC A, L // case 0x9E: Sbc(ReadMemory(HL.Word)); return 7; // SBC A, (HL) // case 0x9F: Sbc(AF.High); return 4; // SBC A, A // case 0xA0: And(BC.High); return 4; // AND B // case 0xA1: And(BC.Low); return 4; // AND C // case 0xA2: And(DE.High); return 4; // AND D // case 0xA3: And(DE.Low); return 4; // AND E // case 0xA4: And(HL.High); return 4; // AND H // case 0xA5: And(HL.Low); return 4; // AND L // case 0xA6: And(ReadMemory(HL.Word)); return 7; // AND (HL) // case 0xA7: And(AF.High); return 4; // AND A // case 0xA8: Xor(BC.High); return 4; // XOR B // case 0xA9: Xor(BC.Low); return 4; // XOR C // case 0xAA: Xor(DE.High); return 4; // XOR D // case 0xAB: Xor(DE.Low); return 4; // XOR E // case 0xAC: Xor(HL.High); return 4; // XOR H // case 0xAD: Xor(HL.Low); return 4; // XOR L // case 0xAE: Xor(ReadMemory(HL.Word)); return 7; // XOR (HL) // case 0xAF: Xor(AF.High); return 4; // XOR A // // --- OR r --- // case 0xB0: Or(BC.High); return 4; // OR B // case 0xB1: Or(BC.Low); return 4; // OR C // case 0xB2: Or(DE.High); return 4; // OR D // case 0xB3: Or(DE.Low); return 4; // OR E // case 0xB4: Or(HL.High); return 4; // OR H // case 0xB5: Or(HL.Low); return 4; // OR L // case 0xB6: Or(ReadMemory(HL.Word)); return 7; // OR (HL) // case 0xB7: Or(AF.High); return 4; // OR A // // --- CP r --- // case 0xB8: Cp(BC.High); return 4; // CP B // case 0xB9: Cp(BC.Low); return 4; // CP C // case 0xBA: Cp(DE.High); return 4; // CP D // case 0xBB: Cp(DE.Low); return 4; // CP E // case 0xBC: Cp(HL.High); return 4; // CP H // case 0xBD: Cp(HL.Low); return 4; // CP L // case 0xBE: Cp(ReadMemory(HL.Word)); return 7; // CP (HL) // case 0xBF: Cp(AF.High); return 4; // CP A // // --- Conditional Returns (11 T-States if taken, 5 if not) --- // case 0xC0: // RET NZ // if ((AF.Low & 0x40) == 0) { PC = Pop(); return 11; } // return 5; // case 0xE0: // RET PO (Parity Odd / No Overflow) // if ((AF.Low & 0x04) == 0) { PC = Pop(); return 11; } // return 5; // case 0xE8: // RET PE (Parity Even / Overflow) // if ((AF.Low & 0x04) != 0) { PC = Pop(); return 11; } // return 5; // case 0xF0: // RET P (Sign Positive) // if ((AF.Low & 0x80) == 0) { PC = Pop(); return 11; } // return 5; // case 0xF8: // RET M (Sign Minus) // if ((AF.Low & 0x80) != 0) { PC = Pop(); return 11; } // return 5; // case 0xC1: // POP BC // BC.Word = Pop(); // return 10; // // --- Absolute Conditional Jumps (Always 10 T-States) --- // case 0xC2: // JP NZ, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x40) == 0) PC = addr; // return 10; // } // case 0xCA: // JP Z, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x40) != 0) PC = addr; // return 10; // } // case 0xD2: // JP NC, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x01) == 0) PC = addr; // return 10; // } // case 0xDA: // JP C, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x01) != 0) PC = addr; // return 10; // } // case 0xDB: // IN A, (n) // // 1. Fetch the immediate port offset byte // byte portOffsetDB = FetchByte(); // // 2. The Z80 puts 'A' on the top 8 bits, and 'n' on the bottom 8 bits // ushort portAddressDB = (ushort)((AF.High << 8) | portOffsetDB); // // 3. Read from the I/O bus and store the result straight into the Accumulator // AF.High = _simpleIoBus.ReadPort(portAddressDB); // return 11; // case 0xE2: // JP PO, nn (Parity Odd / No Overflow) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x04) == 0) PC = addr; // return 10; // } // case 0xEA: // JP PE, nn (Parity Even / Overflow) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x04) != 0) PC = addr; // return 10; // } // case 0xF2: // JP P, nn (Sign Positive) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x80) == 0) PC = addr; // return 10; // } // case 0xFA: // JP M, nn (Sign Minus) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x80) != 0) PC = addr; // return 10; // } // case 0xC3: // PC = FetchWord(); // return 10; // // --- Absolute Conditional Calls (17 T-States if taken, 10 if not) --- // case 0xC4: // CALL NZ, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x40) == 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xCC: // CALL Z, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x40) != 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xD4: // CALL NC, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x01) == 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xDC: // CALL C, nn // { // ushort addr = FetchWord(); // if ((AF.Low & 0x01) != 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xE4: // CALL PO, nn (Parity Odd) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x04) == 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xEC: // CALL PE, nn (Parity Even) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x04) != 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xF4: // CALL P, nn (Sign Positive) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x80) == 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xFC: // CALL M, nn (Sign Minus) // { // ushort addr = FetchWord(); // if ((AF.Low & 0x80) != 0) { Push(PC); PC = addr; return 17; } // return 10; // } // case 0xc5: //push bc // Push(BC.Word); // return 11; // case 0xC6: // ADD A, n // Add(FetchByte()); // return 7; // // --- RST Instructions (11 T-States) --- // // An RST is effectively a 1-byte CALL to a fixed Page 0 address. // case 0xC7: Push(PC); PC = 0x0000; return 11; // RST 00h (Equivalent to a hardware reset) // case 0xCF: Push(PC); PC = 0x0008; return 11; // RST 08h (Spectrum Error handler) // case 0xD7: Push(PC); PC = 0x0010; return 11; // RST 10h (Spectrum Print Character) // case 0xDF: Push(PC); PC = 0x0018; return 11; // RST 18h (Spectrum Collect Next Char) // case 0xE7: Push(PC); PC = 0x0020; return 11; // RST 20h (Spectrum Collect Next Char/Space) // case 0xEF: Push(PC); PC = 0x0028; return 11; // RST 28h (Spectrum Floating Point Calculator) // case 0xF7: Push(PC); PC = 0x0030; return 11; // RST 30h (Spectrum Make BC Spaces) // case 0xFF: Push(PC); PC = 0x0038; return 11; // RST 38h (Maskable Interrupt Handler) // case 0xC8: // RET Z // // Check if the Zero Flag (Bit 6) IS set // if ((AF.Low & 0x40) != 0) // { // PC = Pop(); // return 11; // Condition met, took the return // } // return 5; // Condition not met, skipped // case 0xC9: // RET // PC = Pop(); // return 10; // case 0xCB: // return ExecuteCBPrefix(); // case 0xCD: // CALL nn // ushort callAddress = FetchWord(); // Push(PC); // PC = callAddress; // return 17; // case 0xD0: // RET NC // // Check if the Carry Flag (Bit 0) is NOT set (0) // if ((AF.Low & 0x01) == 0) // { // PC = Pop(); // return 11; // Condition met, took the return // } // return 5; // Condition not met, skipped // case 0xD1: // POP DE // DE.Word = Pop(); // return 10; // case 0xD3: // OUT (n), A // byte portOffset = FetchByte(); // // The Z80 puts 'A' on the top 8 bits, and 'n' on the bottom 8 bits of the port address // ushort portAddress = (ushort)((AF.High << 8) | portOffset); // _simpleIoBus.WritePort(portAddress, AF.High); // return 11; // case 0xd5: //push bc // Push(DE.Word); // return 11; // case 0xD6: // SUB n // Sub(FetchByte()); // return 7; // case 0xD8: // RET C // // Check if the Carry Flag (Bit 0) IS set (1) // if ((AF.Low & 0x01) != 0) // { // PC = Pop(); // return 11; // Condition met, took the return // } // return 5; // case 0xD9: // EXX // ushort tempBC = BC.Word; // BC.Word = BC_Prime.Word; // BC_Prime.Word = tempBC; // ushort tempDE = DE.Word; // DE.Word = DE_Prime.Word; // DE_Prime.Word = tempDE; // ushort tempHL = HL.Word; // HL.Word = HL_Prime.Word; // HL_Prime.Word = tempHL; // return 4; // case 0xDD: // return ExecuteDDPrefix(); // case 0xDE: // SBC A, n // Sbc(FetchByte()); // return 7; // case 0xE1: // POP HL // HL.Word = Pop(); // return 10; // case 0xE3: // EX (SP), HL // // 1. Read the 16-bit value currently on top of the stack // byte spLow = ReadMemory(SP); // byte spHigh = ReadMemory((ushort)(SP + 1)); // // 2. Write the current HL registers onto the stack in its place // WriteMemory(SP, HL.Low); // WriteMemory((ushort)(SP + 1), HL.High); // // 3. Update HL with the data we pulled off the stack // HL.Low = spLow; // HL.High = spHigh; // return 19; // case 0xe5: //push bc // Push(HL.Word); // return 11; // case 0xE6: // AND n // And(FetchByte()); // return 7; // case 0xE9: // JP (HL) // PC = HL.Word; // return 4; // Takes 4 T-States // case 0xEB: // EX DE, HL // ushort tempEx = DE.Word; // DE.Word = HL.Word; // HL.Word = tempEx; // return 4; // Takes 4 T-States // case 0xED: // return ExecuteExtendedPrefix(); // case 0xEE: // XOR n // byte xorImm = FetchByte(); // // Perform the bitwise XOR against the Accumulator // AF.High = (byte)(AF.High ^ xorImm); // // --- Update Flags --- // // Start with a clean slate of 0 to perfectly clear C, H, and N! // newFlags = 0; // if ((AF.High & 0x80) != 0) newFlags |= 0x80; // Sign Flag // if (AF.High == 0) newFlags |= 0x40; // Zero Flag // if (CalculateParity(AF.High)) newFlags |= 0x04; // Parity/Overflow Flag // AF.Low = newFlags; // return 7; // Takes 7 T-States // case 0xF1: // POP AF // AF.Word = Pop(); // return 10; // case 0xF3: // DI (Disable Interrupts) // IFF1 = false; // IFF2 = false; // return 4; // case 0xf5: //push bc // Push(AF.Word); // return 11; // case 0xF6: // OR n // Or(FetchByte()); // return 7; // case 0xF9: // LD SP, HL // SP = HL.Word; // (Use SP.Word = HL.Word if you made SP a RegisterPair) // return 6; // case 0xFB: // EI // IFF1 = true; // IFF2 = true; // return 4; // case 0xFD: // return ExecuteFDPrefix(); // case 0xFE: // CP n // Cp(FetchByte()); // return 7; // default: // throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); // } // } // private int ExecuteExtendedPrefix() //ED // { // // Fetch the actual extended instruction // byte extendedOpcode = FetchByte(); // byte val = 0; // switch (extendedOpcode) // { // case 0x43: // LD (nn), BC // ushort dest43 = FetchWord(); // WriteMemory(dest43, BC.Low); // WriteMemory((ushort)(dest43 + 1), BC.High); // return 20; // case 0x44: // NEG // int aBefore = AF.High; // result = 0 - aBefore; // newFlags = 0; // // S Flag (Bit 7): Set if the result is negative // if ((result & 0x80) != 0) newFlags |= 0x80; // // Z Flag (Bit 6): Set if the result is exactly zero // if ((result & 0xFF) == 0) newFlags |= 0x40; // // H Flag (Bit 4): Set if there was a borrow from Bit 3 // if ((0 - (aBefore & 0x0F)) < 0) newFlags |= 0x10; // // P/V Flag (Bit 2): Overflow happens ONLY if A was 0x80 (-128) // if (aBefore == 0x80) newFlags |= 0x04; // // N Flag (Bit 1): Always set to 1 for NEG // newFlags |= 0x02; // // C Flag (Bit 0): Set if A was not 0 before the operation // if (aBefore != 0) newFlags |= 0x01; // AF.Low = newFlags; // AF.High = (byte)result; // return 8; // 8 T-States // case 0x47: // LD I, A // I = AF.High; // return 9; // case 0x4B: // LD BC, (nn) // ushort src4B = FetchWord(); // BC.Low = ReadMemory(src4B); // BC.High = ReadMemory((ushort)(src4B + 1)); // return 20; // case 0x4D: // RETI Does not affect IFF1 or IFF2 // PC = Pop(); // return 14; // case 0x53: // LD (nn), DE // ushort dest53 = FetchWord(); // WriteMemory(dest53, DE.Low); // WriteMemory((ushort)(dest53 + 1), DE.High); // return 20; // case 0x56: // IM 1 // InterruptMode = 1; // return 8; // case 0x58: // IN E, (C) // // 1. Read from the I/O port. // // CRITICAL: We must pass the FULL BC register, not just C! // byte inVal58 = ReadPort(BC.Word); // // 2. Store the hardware data in register E (Low byte of DE) // DE.Low = inVal58; // // 3. Update the Flags Register (F) // // The Carry flag (C) is strictly preserved. H and N are always reset to 0. // byte flags58 = (byte)(AF.Low & 0x01); // if ((inVal58 & 0x80) != 0) flags58 |= 0x80; // S: Sign flag // if (inVal58 == 0) flags58 |= 0x40; // Z: Zero flag // // P/V: Parity flag. Collapse the bits to check if the number of 1s is even // byte p58 = inVal58; // p58 ^= (byte)(p58 >> 4); // p58 ^= (byte)(p58 >> 2); // p58 ^= (byte)(p58 >> 1); // if ((p58 & 1) == 0) flags58 |= 0x04; // Set if Parity is Even // // Undocumented bits 3 and 5 are copied directly from the input byte // flags58 |= (byte)(inVal58 & 0x28); // AF.Low = flags58; // return 12; // case 0x5B: // LD DE, (nn) // ushort src5B = FetchWord(); // DE.Low = ReadMemory(src5B); // DE.High = ReadMemory((ushort)(src5B + 1)); // return 20; // case 0x5E: // IM 2 // // Set the CPU's internal interrupt mode state // InterruptMode = 2; // return 8; // case 0x5F: // LD A, R // // 1. Load the Refresh register into the Accumulator // AF.High = R; // // 2. Calculate Flags // // CRITICAL: Preserve the existing Carry flag (Bit 0). // // H (Bit 4) and N (Bit 1) are forcefully reset to 0. // newFlags = (byte)(AF.Low & 0x01); // // S Flag (Bit 7): Set if the result is negative // if ((AF.High & 0x80) != 0) newFlags |= 0x80; // // Z Flag (Bit 6): Set if the result is zero // if (AF.High == 0) newFlags |= 0x40; // // P/V Flag (Bit 2): Set if IFF2 is true (This is the interrupt check hack!) // if (IFF2) newFlags |= 0x04; // AF.Low = newFlags; // return 9; // // --- SBC HL, rr --- // case 0x42: SbcHl(BC.Word); return 15; // case 0x52: SbcHl(DE.Word); return 15; // case 0x62: SbcHl(HL.Word); return 15; // case 0x72: SbcHl(SP); return 15; // case 0x73: // LD (nn), SP // ushort dest73 = FetchWord(); // WriteMemory(dest73, (byte)SP); // WriteMemory((ushort)(dest73 + 1), (byte)(SP >> 8)); // return 20; // case 0x78: // IN A, (C) // // Read from the hardware port using the full BC register as the address // byte portVal78 = ReadPort(BC.Word); // AF.High = portVal78; // // --- Update Flags --- // // S (Bit 7), Z (Bit 6), P/V (Bit 2) are set based on the input. // // H (Bit 4) and N (Bit 1) are RESET. // // C (Bit 0) is PRESERVED. // newFlags = (byte)(AF.Low & 0x01); // Preserve Carry // if ((portVal78 & 0x80) != 0) newFlags |= 0x80; // Sign Flag // if (portVal78 == 0) newFlags |= 0x40; // Zero Flag // if (CalculateParity(portVal78)) newFlags |= 0x04; // Parity/Overflow Flag // AF.Low = newFlags; // return 12; // case 0x79: // OUT (C), A // _simpleIoBus.WritePort(BC.Word, AF.High); // return 12; // // --- ADC HL, rr --- // case 0x4A: AdcHl(BC.Word); return 15; // case 0x5A: AdcHl(DE.Word); return 15; // case 0x6A: AdcHl(HL.Word); return 15; // case 0x7A: AdcHl(SP); return 15; // case 0x7B: // LD SP, (nn) // // 1. Fetch the absolute 16-bit memory address from the instruction stream // byte addrLow = FetchByte(); // byte addrHigh = FetchByte(); // ushort address7B = (ushort)((addrHigh << 8) | addrLow); // // 2. Read the 16-bit value stored at that exact memory location (Little-Endian!) // byte spLow = ReadMemory(address7B); // byte spHigh = ReadMemory((ushort)(address7B + 1)); // // 3. Load the resulting 16-bit value directly into the Stack Pointer // SP = (ushort)((spHigh << 8) | spLow); // return 20; // case 0xA0: // LDI // // 1. Read byte from (HL) // val = ReadMemory(HL.Word); // // 2. Write byte to (DE) // WriteMemory(DE.Word, val); // // 3. Increment memory pointers, Decrement byte counter // HL.Word++; // DE.Word++; // BC.Word--; // // 4. Update Flags // // Preserve S (0x80), Z (0x40), and C (0x01). // // H (0x10) and N (0x02) are forcefully reset to 0. // AF.Low &= 0xC1; // // P/V Flag (Bit 2) is set to 1 if BC is not 0 after the decrement // if (BC.Word != 0) // { // AF.Low |= 0x04; // } // return 16; // case 0xB0: // LDIR // // 1. Read byte from (HL) // val = ReadMemory(HL.Word); // // 2. Write byte to (DE) // WriteMemory(DE.Word, val); // // 3. Increment memory pointers, Decrement byte counter // HL.Word++; // DE.Word++; // BC.Word--; // // 4. Update Flags // // Preserve S (0x80), Z (0x40), and C (0x01). // // H (0x10) and N (0x02) are always reset to 0. // AF.Low &= 0xC1; // // P/V Flag (Bit 2) is set to 1 if BC is not 0 // if (BC.Word != 0) // { // AF.Low |= 0x04; // // Rewind the PC so the CPU executes this instruction again! // PC -= 2; // return 21; // Looping // } // return 16; // case 0xB1: // CPIR // // 1. Read the memory byte at HL // byte memValB1 = ReadMemory(HL.Word); // // 2. Calculate the difference (A - (HL)) to set the flags // byte resultB1 = (byte)(AF.High - memValB1); // // 3. Increment HL and Decrement BC // HL.Word++; // BC.Word--; // // 4. Update the Flags (F Register / AF.Low) // // CPIR modifies S, Z, H, P/V, and N, but strictly PRESERVES the C flag. // byte currentCarry = (byte)(AF.Low & 0x01); // byte newFlagsB1 = currentCarry; // newFlagsB1 |= 0x02; // N flag is always set to 1 for CPIR // if (BC.Word != 0) newFlagsB1 |= 0x04; // P/V is set if BC is not 0 (Counter not empty) // if (((AF.High ^ memValB1 ^ resultB1) & 0x10) != 0) newFlagsB1 |= 0x10; // H flag (Half-borrow) // if (resultB1 == 0) newFlagsB1 |= 0x40; // Z flag (Match found!) // if ((resultB1 & 0x80) != 0) newFlagsB1 |= 0x80; // S flag (Sign) // // (Note: Undocumented bits 3 and 5 are ignored here as they are highly esoteric for CPIR, // // but you can add `newFlagsB1 |= (byte)(resultB1 & 0x28);` if your engine tracks them strictly). // AF.Low = newFlagsB1; // // 5. The Repeat Check // // If we haven't hit 0 in BC, AND we didn't find a match (result != 0)... // if (BC.Word != 0 && resultB1 != 0) // { // // Rewind the Program Counter by 2 bytes (back to the 0xED prefix) // PC -= 2; // return 21; // 21 T-States for a repeating loop // } // // If we found the byte or BC hit 0, the loop ends. // return 16; // 16 T-States when the loop terminates // case 0xB8: // LDDR // // 1. Read byte from (HL) // val = ReadMemory(HL.Word); // // 2. Write byte to (DE) // WriteMemory(DE.Word, val); // // 3. Decrement all three pointers // HL.Word--; // DE.Word--; // BC.Word--; // // 4. Update Flags // // Preserve S (0x80), Z (0x40), and C (0x01). // // H (0x10) and N (0x02) are always reset to 0. // AF.Low &= 0xC1; // // P/V Flag (Bit 2) is set to 1 if BC is not 0 // if (BC.Word != 0) // { // AF.Low |= 0x04; // // Rewind the PC so the CPU executes this instruction again! // PC -= 2; // return 21; // Looping // } // return 16; // Finished! // default: // throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); // } // } // private int ExecuteCBPrefix() // { // byte cbOpcode = FetchByte(); // bool oldCarry = false; // // Extract the exact same mathematical properties // int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET // int bitIndex = (cbOpcode >> 3) & 0x07; // Extracts a number 0-7 // int regIndex = cbOpcode & 0x07; // Extracts register index 0-7 // byte bitMask = (byte)(1 << bitIndex); // // --- PHASE 1: Fetch the target value --- // byte val = 0; // switch (regIndex) // { // case 0: val = BC.High; break; // case 1: val = BC.Low; break; // case 2: val = DE.High; break; // case 3: val = DE.Low; break; // case 4: val = HL.High; break; // case 5: val = HL.Low; break; // case 6: val = ReadMemory(HL.Word); break; // The 0x110 (HL) exception // case 7: val = AF.High; break; // } // // --- PHASE 2: Perform the bitwise math --- // switch (operation) // { // case 1: // ALL BIT Instructions // AF.Low &= 0x01; // Preserve ONLY Carry // AF.Low |= 0x10; // Set Half-Carry // if ((val & bitMask) == 0) // { // AF.Low |= 0x44; // Set Zero and P/V // } // else if (bitIndex == 7) // { // AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign // } // // BIT (HL) takes 12 T-States. Standard register BIT takes 8. // return (regIndex == 6) ? 12 : 8; // case 2: // ALL RES Instructions // val &= (byte)(~bitMask); // break; // Proceed to write-back // case 3: // ALL SET Instructions // val |= bitMask; // break; // Proceed to write-back // case 0: // ALL Shift/Rotate Instructions // // The specific shift type is in the same bits we previously used for 'bitIndex' // int shiftType = (cbOpcode >> 3) & 0x07; // bool carryOut = false; // switch (shiftType) // { // case 0: // RLC // // Grab Bit 7 to see if it's going to fall off // carryOut = (val & 0x80) != 0; // // Shift left, and loop the falling bit back into Bit 0 // val = (byte)((val << 1) | (carryOut ? 1 : 0)); // break; // case 1: // RRC (Rotate Right Circular) // // 1. Grab Bit 0 before it falls off to set the Carry flag and loop to Bit 7 // carryOut = (val & 0x01) != 0; // // 2. Shift right by 1, and loop the falling bit directly back into Bit 7 // val = (byte)((val >> 1) | (carryOut ? 0x80 : 0x00)); // break; // case 2: // RL (Rotate Left through Carry) // // 1. Grab the CURRENT Carry flag from the AF register // oldCarry = (AF.Low & 0x01) != 0; // // 2. Grab Bit 7 before it falls off to become the NEW Carry flag // carryOut = (val & 0x80) != 0; // // 3. Shift left by 1, and drop the OLD carry flag directly into Bit 0 // val = (byte)((val << 1) | (oldCarry ? 0x01 : 0x00)); // break; // case 3: // RR (Rotate Right through Carry) // // 1. Grab the CURRENT Carry flag from the AF register // oldCarry = (AF.Low & 0x01) != 0; // // 2. Grab Bit 0 before it falls off to become the NEW Carry flag // carryOut = (val & 0x01) != 0; // // 3. Shift right by 1, and drop the OLD carry flag into Bit 7 // val = (byte)((val >> 1) | (oldCarry ? 0x80 : 0x00)); // break; // case 4: // SLA (Shift Left Arithmetic) // // 1. Grab Bit 7 before it falls off to set the Carry flag // carryOut = (val & 0x80) != 0; // // 2. Shift the byte left by 1. // // (In C#, a standard left shift automatically pads Bit 0 with a 0) // val = (byte)(val << 1); // break; // case 5: // SRA (Shift Right Arithmetic) // // 1. Grab Bit 0 before it falls off to set the Carry flag // carryOut = (val & 0x01) != 0; // // 2. Grab the current Sign bit (Bit 7) so we can preserve it // byte signBit = (byte)(val & 0x80); // // 3. Shift the byte right by 1. // // (Because 'val' is unsigned, C# naturally pads the top with a 0) // val = (byte)(val >> 1); // // 4. Force the preserved sign bit back into Bit 7 // val |= signBit; // break; // case 7: // SRL (Shift Right Logical) // // 1. Grab Bit 0 before it falls off to set the Carry flag // carryOut = (val & 0x01) != 0; // // 2. Shift the byte right by 1. // // (In C#, a standard right shift on a positive byte automatically pads Bit 7 with a 0) // val = (byte)(val >> 1); // break; // // (We will add RRC, RL, RR, SLA, SRA, here as the ROM asks for them!) // default: // throw new NotImplementedException($"CB Shift instruction type {shiftType} not implemented!"); // } // // --- Update Flags --- // // All CB Shift instructions calculate flags the exact same way! // // They set S, Z, P/V, and C. They forcefully clear H and N. // byte newFlags = 0; // if (carryOut) newFlags |= 0x01; // C Flag // if ((val & 0x80) != 0) newFlags |= 0x80; // S Flag // if (val == 0) newFlags |= 0x40; // Z Flag // if (CalculateParity(val)) newFlags |= 0x04; // P/V Flag // AF.Low = newFlags; // Apply the new flags // break; // Proceed to the write-back phase // default: // throw new Exception("Invalid CB operation."); // } // // --- PHASE 3: Write back the modified value (RES and SET only) --- // switch (regIndex) // { // case 0: BC.High = val; break; // case 1: BC.Low = val; break; // case 2: DE.High = val; break; // case 3: DE.Low = val; break; // case 4: HL.High = val; break; // case 5: HL.Low = val; break; // case 6: WriteMemory(HL.Word, val); break; // case 7: AF.High = val; break; // } // // RES/SET (HL) takes 15 T-States. Standard register RES/SET takes 8. // return (regIndex == 6) ? 15 : 8; // } // private int ExecuteDDPrefix() // { // byte ddOpcode = FetchByte(); // Fetch the actual instruction after 0xDD // switch (ddOpcode) // { // case 0x09: // ADD IX, BC // Add16IX(BC.Word); // return 15; // case 0x19: // ADD IX, DE // Add16IX(DE.Word); // return 15; // case 0x29: // ADD IX, IX // Add16IX(IX.Word); // Multiplies IX by 2! // return 15; // case 0x39: // ADD IX, SP // Add16IX(SP); // return 15; // case 0x21: // LD IX, nn // byte low = FetchByte(); // byte high = FetchByte(); // IX.Word = (ushort)((high << 8) | low); // return 14; // case 0x22: // LD (nn), IX // // 1. Fetch the absolute 16-bit memory address from the instruction stream // byte addrLow22 = FetchByte(); // byte addrHigh22 = FetchByte(); // ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22); // // 2. Write the LOW byte of IX to the exact address // WriteMemory(address22, IX.Low); // // 3. Write the HIGH byte of IX to the address + 1 // WriteMemory((ushort)(address22 + 1), IX.High); // return 20; // case 0x23: // INC IX // // Increment the full 16-bit register. Do NOT touch AF.Low! // IX.Word++; // return 10; // case 0x24: // INC IXH // // Increment the high byte of IX and let the helper perfectly map the flags // IX.High = Inc8(IX.High); // return 8; // case 0x25: // DEC IXH // // Decrement the high byte of IX and let the helper handle all the Z80 flags // IX.High = Dec8(IX.High); // return 8; // case 0x26: // LD IXH, n // // Fetch the immediate 8-bit value and drop it straight into the high byte of IX // IX.High = FetchByte(); // return 11; // case 0x2A: // LD IX, (nn) // // 1. Fetch the absolute 16-bit memory address from the instruction stream // byte addrLow2A = FetchByte(); // byte addrHigh2A = FetchByte(); // ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A); // // 2. Read the LOW byte from that specific memory location // byte ixLow = ReadMemory(address2A); // // 3. Read the HIGH byte from the next consecutive memory location // byte ixHigh = ReadMemory((ushort)(address2A + 1)); // // 4. Combine them and drop them into the IX register pair // IX.Word = (ushort)((ixHigh << 8) | ixLow); // return 20; // case 0x2B: // DEC IX // // Decrement the full 16-bit register. The F register remains completely untouched. // IX.Word--; // return 10; // case 0x2D: // DEC IXL // IX.Low = Dec8(IX.Low); // return 8; // case 0x2E: // LD IXL, n // // Fetch the immediate 8-bit value and drop it straight into the low byte of IX // IX.Low = FetchByte(); // return 11; // case 0x34: // INC (IX+d) // // 1. Fetch the displacement byte and cast to a signed sbyte // sbyte offset34 = (sbyte)FetchByte(); // // 2. Calculate the target memory address // ushort address34 = (ushort)(IX.Word + offset34); // // 3. Read the value from memory // byte val34 = ReadMemory(address34); // // 4. Pass it through your helper to increment and set the flags perfectly // byte result34 = Inc8(val34); // // 5. Write the incremented value back to memory // WriteMemory(address34, result34); // return 23; // case 0x35: // DEC (IX+d) // // 1. Fetch the displacement byte and cast to a signed sbyte // sbyte offset35 = (sbyte)FetchByte(); // // 2. Calculate the target memory address // ushort address35 = (ushort)(IX.Word + offset35); // // 3. Read the value from memory // byte val35 = ReadMemory(address35); // // 4. Pass it through your helper to decrement and set the flags // byte result35 = Dec8(val35); // // 5. Write the decremented value back to memory // WriteMemory(address35, result35); // return 23; // case 0x36: // LD (IX+d), n // // 1. Fetch the displacement byte first // sbyte offset36 = (sbyte)FetchByte(); // // 2. Fetch the immediate 8-bit value second // byte n36 = FetchByte(); // // 3. Calculate the exact memory address (IX + offset) // ushort address36 = (ushort)(IX.Word + offset36); // // 4. Write the immediate value directly into memory // WriteMemory(address36, n36); // return 19; // case 0x46: // LD B, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset46 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address46 = (ushort)(IX.Word + offset46); // // 3. Read the byte from memory and drop it into the C register (Low byte of BC) // BC.High = ReadMemory(address46); // return 19; // case 0x4E: // LD C, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset4E = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address4E = (ushort)(IX.Word + offset4E); // // 3. Read the byte from memory and drop it into the C register (Low byte of BC) // BC.Low = ReadMemory(address4E); // return 19; // case 0x56: // LD D, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset56 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address56 = (ushort)(IX.Word + offset56); // // 3. Read the byte from memory and drop it into the D register (High byte of DE) // DE.High = ReadMemory(address56); // return 19; // case 0x5E: // LD E, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset5E = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address5E = (ushort)(IX.Word + offset5E); // // 3. Read the byte from memory and drop it into the E register // DE.Low = ReadMemory(address5E); // return 19; // case 0x66: // LD H, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset66 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address66 = (ushort)(IX.Word + offset66); // // 3. Read the byte from memory and drop it into the H register (High byte of HL) // HL.High = ReadMemory(address66); // return 19; // case 0x67: // LD IXH, A // // Load the Accumulator (AF.High) directly into the high byte of IX // IX.High = AF.High; // return 8; // case 0x68: // LD IXL, B // IX.Low = BC.High; // return 8; // case 0x69: // LD IXL, C // // Load the C register (BC.Low) into the low byte of IX // IX.Low = BC.Low; // return 8; // case 0x6A: // LD IXL, D // // Load the D register (DE.High) into the low byte of IX // IX.Low = DE.High; // return 8; // case 0x6E: // LD L, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset6E = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address6E = (ushort)(IX.Word + offset6E); // // 3. Read the byte from memory and drop it into the L register (Low byte of HL) // HL.Low = ReadMemory(address6E); // return 19; // case 0x6F: // LD IXL, A // IX.Low = AF.High; // return 8; // case 0x70: // LD (IX+d), B // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset70 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address70 = (ushort)(IX.Word + offset70); // // 3. Write the B register (High byte of BC) into memory // WriteMemory(address70, BC.High); // return 19; // case 0x71: // LD (IX+d), C // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset71 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address71 = (ushort)(IX.Word + offset71); // // 3. Write the C register (Low byte of BC) into memory // WriteMemory(address71, BC.Low); // return 19; // case 0x72: // LD (IX+d), D // // 1. Fetch the displacement byte and cast to a signed sbyte // sbyte offset72 = (sbyte)FetchByte(); // // 2. Calculate the target memory address // ushort address72 = (ushort)(IX.Word + offset72); // // 3. Write the D register (DE.High) to memory // WriteMemory(address72, DE.High); // return 19; // 19 T-States // case 0x73: // LD (IX+d), E // // 1. Fetch the displacement byte and cast to a signed sbyte // sbyte offset73 = (sbyte)FetchByte(); // // 2. Calculate the target memory address // ushort address73 = (ushort)(IX.Word + offset73); // // 3. Write the E register (DE.Low) to memory // WriteMemory(address73, DE.Low); // return 19; // case 0x74: // LD (IX+d), H // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset74 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address74 = (ushort)(IX.Word + offset74); // // 3. Write the contents of the L register (Low byte of HL) into memory // WriteMemory(address74, HL.High); // return 19; // case 0x75: // LD (IX+d), L // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset75 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address75 = (ushort)(IX.Word + offset75); // // 3. Write the contents of the L register (Low byte of HL) into memory // WriteMemory(address75, HL.Low); // return 19; // case 0x77: // LD (IX+d), A // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset77 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address77 = (ushort)(IX.Word + offset77); // // 3. Write the Accumulator (AF.High) into memory at that address // WriteMemory(address77, AF.High); // return 19; // case 0x7C: // LD A, IXH // // Load the high byte of IX directly into the Accumulator // AF.High = IX.High; // return 8; // case 0x7E: // LD A, (IX+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset7E = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IX + offset) // ushort address7E = (ushort)(IX.Word + offset7E); // // 3. Read the byte from memory and drop it straight into the Accumulator (A) // AF.High = ReadMemory(address7E); // return 19; // // Inside ExecuteDDPrefix(): // case 0x84: // ADD A, IXH // AddA(IX.High); // Assuming your 16-bit register struct has a .High property // return 8; // case 0x85: // ADD A, IXL // AddA(IX.Low); // return 8; // case 0x86: // ADD A, (IX+d) // sbyte offset86 = (sbyte)FetchByte(); // ushort address86 = (ushort)(IX.Word + offset86); // // Read the memory and pass it straight into your flawless helper! // Add(ReadMemory(address86)); // return 19; // case 0x96: // SUB (IX+d) // sbyte offset96 = (sbyte)FetchByte(); // ushort address96 = (ushort)(IX.Word + offset96); // // Read the memory and pass it straight into your flawless helper! // Sub(ReadMemory(address96)); // return 19; // case 0xBE: // CP (IX+d) // // 1. Fetch the displacement byte and calculate the address // sbyte offsetBE = (sbyte)FetchByte(); // ushort addressBE = (ushort)(IX.Word + offsetBE); // // 2. Read the value from memory // byte cpVal = ReadMemory(addressBE); // // 3. Perform the phantom subtraction // int aVal = AF.High; // result = aVal - cpVal; // // --- 8-Bit Compare Flag Calculation (Identical to SUB) --- // newFlags = 0; // // S Flag (Bit 7): Set if the phantom result is negative // if ((result & 0x80) != 0) newFlags |= 0x80; // // Z Flag (Bit 6): Set if A perfectly matches the memory value (A - value == 0) // if ((result & 0xFF) == 0) newFlags |= 0x40; // // H Flag (Bit 4): Set if there was a borrow from Bit 3 // if (((aVal & 0x0F) - (cpVal & 0x0F)) < 0) newFlags |= 0x10; // // P/V Flag (Bit 2): Set on Overflow // if ((((aVal ^ cpVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; // // N Flag (Bit 1): Always set to 1 for Subtractions/Compares // newFlags |= 0x02; // // C Flag (Bit 0): Set if A was smaller than the memory value // if (aVal < cpVal) newFlags |= 0x01; // AF.Low = newFlags; // // CRITICAL: Notice we do NOT update AF.High! The Accumulator is preserved. // return 19; // case 0xCB: // The DD CB nested prefix // { // // 1. Fetch the displacement byte first // sbyte displacement = (sbyte)FetchByte(); // // 2. Fetch the actual operation opcode (like your 0x72) second // byte cbOpcode = FetchByte(); // ushort targetAddress = (ushort)(IX.Word + displacement); // byte memVal = ReadMemory(targetAddress); // // Extract the mathematical properties of the opcode // int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET // int bitIndex = (cbOpcode >> 3) & 0x07; // Extracts a number 0-7 // byte bitMask = (byte)(1 << bitIndex); // Creates the bitmask // switch (operation) // { // case 1: // ALL BIT Instructions // AF.Low &= 0x01; // Preserve ONLY Carry // AF.Low |= 0x10; // Set Half-Carry // if ((memVal & bitMask) == 0) // { // AF.Low |= 0x44; // Set Zero (Bit 6) and P/V (Bit 2) // } // else if (bitIndex == 7) // { // AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign (Bit 7) // } // return 20; // 20 T-States // // (You can copy your RES and SET logic from ExecuteFDPrefix here later!) // default: // throw new NotImplementedException($"DD CB opcode {cbOpcode:X2} not fully implemented!"); // } // } // case 0xE1: // POP IX // // 1. Read the low byte from the top of the stack // byte popLow = ReadMemory(SP); // SP++; // Move stack pointer up // // 2. Read the high byte // byte popHigh = ReadMemory(SP); // SP++; // Move stack pointer up again // // 3. Combine them and store in IX // IX.Word = (ushort)((popHigh << 8) | popLow); // return 14; // case 0xE5: // PUSH IX // // 1. Decrement the stack pointer and write the HIGH byte // SP--; // WriteMemory(SP, IX.High); // // 2. Decrement the stack pointer again and write the LOW byte // SP--; // WriteMemory(SP, IX.Low); // return 15; // case 0xE9: // JP (IX) // PC = IX.Word; // return 8; // default: // throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} not implemented!"); // } // } // private int ExecuteFDPrefix() // { // byte opcode = FetchByte(); // ushort targetAddress = 0; // byte memVal = 0; // switch (opcode) // { // // Inside ExecuteFDPrefix() // case 0x09: AddIy(BC.Word); return 15; // case 0x19: AddIy(DE.Word); return 15; // This is the exact instruction that crashed! // case 0x21: // LD IY, nn // IY.Word = FetchWord(); // return 14; // case 0x23: // INC IY // // Increment the full 16-bit register. The F register remains completely untouched. // IY.Word++; // return 10; // case 0x29: AddIy(IY.Word); return 15; // case 0x34: // INC (IY+d) // // 1. Fetch displacement and calculate memory address // sbyte offset34 = (sbyte)FetchByte(); // ushort address34 = (ushort)(IY.Word + offset34); // // 2. Read the value from memory // byte valBefore = ReadMemory(address34); // // 3. Increment the value // int result = valBefore + 1; // // --- 8-Bit Increment Flag Calculation --- // // CRITICAL: We must preserve the existing Carry flag (Bit 0)! // byte newFlags = (byte)(AF.Low & 0x01); // // S Flag (Bit 7): Set if the result is negative // if ((result & 0x80) != 0) newFlags |= 0x80; // // Z Flag (Bit 6): Set if the result is exactly zero (wrapped from 255 to 0) // if ((result & 0xFF) == 0) newFlags |= 0x40; // // H Flag (Bit 4): Set if there was a carry out of Bit 3 // if ((valBefore & 0x0F) == 0x0F) newFlags |= 0x10; // // P/V Flag (Bit 2): Set on Overflow // // For INC, overflow ONLY happens if we increment 0x7F (+127) and it wraps to 0x80 (-128) // if (valBefore == 0x7F) newFlags |= 0x04; // // N Flag (Bit 1): Always reset to 0 for an increment // // (Our bitwise AND at the top already cleared it) // AF.Low = newFlags; // // 4. Write the modified value back to memory // WriteMemory(address34, (byte)result); // return 23; // 23 T-States // case 0x35: // DEC (IY+d) // sbyte offset = (sbyte)FetchByte(); // targetAddress = (ushort)(IY.Word + offset); // // Read, decrement using your existing helper, and write back // memVal = ReadMemory(targetAddress); // byte decVal = Dec8(memVal); // WriteMemory(targetAddress, decVal); // return 23; // case 0x36: // LD (IY+d), n // { // sbyte offset36 = (sbyte)FetchByte(); // byte nValue = FetchByte(); // targetAddress = (ushort)(IY.Word + offset36); // WriteMemory(targetAddress, nValue); // return 19; // Takes 19 T-States // } // case 0x39: AddIy(SP); return 15; // case 0x46: // LD B, (IY+d) // { // sbyte displacement = (sbyte)FetchByte(); // targetAddress = (ushort)(IY.Word + displacement); // BC.High = ReadMemory(targetAddress); // return 19; // Takes 19 T-States // } // case 0x4E: // LD C, (IY+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset4E = (sbyte)FetchByte(); // // 2. Calculate the final address (IY + offset) // ushort address4E = (ushort)(IY.Word + offset4E); // // 3. Read the memory and store it in C // BC.Low = ReadMemory(address4E); // return 19; // case 0x56: // LD D, (IY+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset56 = (sbyte)FetchByte(); // // 2. Calculate the final address (IY + offset) // ushort address56 = (ushort)(IY.Word + offset56); // // 3. Read the memory and store it in D (the high byte of DE) // DE.High = ReadMemory(address56); // return 19; // case 0x5E: // LD E, (IY+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset5E = (sbyte)FetchByte(); // // 2. Calculate the final address (IY + offset) // ushort address5E = (ushort)(IY.Word + offset5E); // // 3. Read the memory and store it in E (the low byte of DE) // DE.Low = ReadMemory(address5E); // return 19; // case 0x66: // LD H, (IY+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset66 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort address66 = (ushort)(IY.Word + offset66); // // 3. Read the byte from memory and drop it into the H register (High byte of HL) // HL.High = ReadMemory(address66); // return 19; // case 0x6E: // LD L, (IY+d) // sbyte displacementVal = (sbyte)FetchByte(); // ushort targetAddr = (ushort)(IY.Word + displacementVal); // HL.Low = ReadMemory(targetAddr); // return 19; // case 0x71: // LD (IY+d), C // { // sbyte offset71 = (sbyte)FetchByte(); // targetAddress = (ushort)(IY.Word + offset71); // // Write the C register (low byte of BC) to memory // WriteMemory(targetAddress, BC.Low); // return 19; // Takes 19 T-States // } // case 0x72: // LD (IY+d), D // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset72 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort address72 = (ushort)(IY.Word + offset72); // // 3. Write the contents of the D register (High byte of DE) into memory // WriteMemory(address72, DE.High); // return 19; // case 0x73: // LD (IY+d), E // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset73 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort address73 = (ushort)(IY.Word + offset73); // // 3. Write the contents of the E register (Low byte of DE) into memory // WriteMemory(address73, DE.Low); // return 19; // case 0x74: // LD (IY+d), H // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset74 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort address74 = (ushort)(IY.Word + offset74); // // 3. Write the contents of the H register into memory at that address // WriteMemory(address74, HL.High); // return 19; // case 0x75: // LD (IY+d), L // sbyte offset75 = (sbyte)FetchByte(); // targetAddress = (ushort)(IY.Word + offset75); // // Write the low byte of HL to memory // WriteMemory(targetAddress, HL.Low); // return 19; // case 0x77: // LD (IY+d), A // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset77 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort address77 = (ushort)(IY.Word + offset77); // // 3. Write the Accumulator (A) into memory // WriteMemory(address77, AF.High); // return 19; // case 0x7E: // LD A, (IY+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offset7E = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort address7E = (ushort)(IY.Word + offset7E); // // 3. Read the byte from memory and drop it straight into the Accumulator (A) // AF.High = ReadMemory(address7E); // return 19; // case 0x84: // ADD A, IYH // AddA(IY.High); // return 8; // case 0x85: // ADD A, IYL // AddA(IY.Low); // return 8; // case 0x86: // ADD A, (IY+d) // { // sbyte displacementAdd = (sbyte)FetchByte(); // ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd); // byte valueToAdd = ReadMemory(targetAddressAdd); // AddA(valueToAdd); // return 19; // } // case 0x96: // SUB (IY+d) // // 1. Fetch the displacement byte and calculate the address // sbyte offset96 = (sbyte)FetchByte(); // ushort address96 = (ushort)(IY.Word + offset96); // // 2. Read the value from memory // byte subVal = ReadMemory(address96); // // 3. Perform the subtraction from the Accumulator // int aVal = AF.High; // result = aVal - subVal; // // --- 8-Bit Subtraction Flag Calculation --- // newFlags = 0; // // S Flag (Bit 7): Set if the result is negative // if ((result & 0x80) != 0) newFlags |= 0x80; // // Z Flag (Bit 6): Set if the result is exactly zero // if ((result & 0xFF) == 0) newFlags |= 0x40; // // H Flag (Bit 4): Set if there was a borrow from Bit 3 // if (((aVal & 0x0F) - (subVal & 0x0F)) < 0) newFlags |= 0x10; // // P/V Flag (Bit 2): Set on Overflow // // (Happens if subtracting a negative from a positive gives a negative, or vice versa) // if ((((aVal ^ subVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; // // N Flag (Bit 1): Always set to 1 for a subtraction // newFlags |= 0x02; // // C Flag (Bit 0): Set if a borrow was needed (Accumulator was smaller than memory value) // if (aVal < subVal) newFlags |= 0x01; // AF.Low = newFlags; // AF.High = (byte)result; // return 19; // case 0xA6: // AND (IY+d) // // 1. Fetch the displacement byte and cast it to a signed sbyte // sbyte offsetA6 = (sbyte)FetchByte(); // // 2. Calculate the exact memory address (IY + offset) // ushort addressA6 = (ushort)(IY.Word + offsetA6); // // 3. Read the operand from memory // byte operandA6 = ReadMemory(addressA6); // // 4. Perform the logical AND with the Accumulator // AF.High &= operandA6; // // 5. Update the Flags Register (F) // byte flagsA6 = 0; // if ((AF.High & 0x80) != 0) flagsA6 |= 0x80; // S: Sign flag (Set if result is negative) // if (AF.High == 0) flagsA6 |= 0x40; // Z: Zero flag (Set if result is 0) // flagsA6 |= 0x10; // H: Half-carry is ALWAYS set to 1 for Z80 AND instructions // // P/V: Parity flag. We collapse the bits to check if the number of 1s is even // byte p = AF.High; // p ^= (byte)(p >> 4); // p ^= (byte)(p >> 2); // p ^= (byte)(p >> 1); // if ((p & 1) == 0) flagsA6 |= 0x04; // Set if Parity is Even // // Undocumented bits 3 and 5 are copied directly from the resulting Accumulator // flagsA6 |= (byte)(AF.High & 0x28); // // N (Subtract) and C (Carry) are always reset to 0 for AND instructions, // // which happens naturally since we started with flagsA6 = 0. // AF.Low = flagsA6; // return 19; // case 0xBE: // CP (IY+d) // // 1. Fetch the displacement byte and calculate the address using IY // sbyte offsetBE = (sbyte)FetchByte(); // ushort addressBE = (ushort)(IY.Word + offsetBE); // // 2. Read the value from memory // byte cpVal = ReadMemory(addressBE); // // 3. Perform the phantom subtraction // aVal = AF.High; // result = aVal - cpVal; // // --- 8-Bit Compare Flag Calculation (Identical to SUB) --- // newFlags = 0; // // S Flag (Bit 7): Set if the phantom result is negative // if ((result & 0x80) != 0) newFlags |= 0x80; // // Z Flag (Bit 6): Set if A perfectly matches the memory value // if ((result & 0xFF) == 0) newFlags |= 0x40; // // H Flag (Bit 4): Set if there was a borrow from Bit 3 // if (((aVal & 0x0F) - (cpVal & 0x0F)) < 0) newFlags |= 0x10; // // P/V Flag (Bit 2): Set on Overflow // if ((((aVal ^ cpVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; // // N Flag (Bit 1): Always set to 1 for Subtractions/Compares // newFlags |= 0x02; // // C Flag (Bit 0): Set if A was smaller than the memory value // if (aVal < cpVal) newFlags |= 0x01; // AF.Low = newFlags; // return 19; // case 0xCB: // The FD CB nested prefix // { // sbyte displacement = (sbyte)FetchByte(); // byte cbOpcode = FetchByte(); // targetAddress = (ushort)(IY.Word + displacement); // memVal = ReadMemory(targetAddress); // // Extract the mathematical properties of the opcode // int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET // int bitIndex = (cbOpcode >> 3) & 0x07; // Extracts a number 0-7 // byte bitMask = (byte)(1 << bitIndex); // Creates the bitmask (e.g., 0x01, 0x02, 0x80) // switch (operation) // { // case 1: // ALL BIT Instructions // AF.Low &= 0x01; // Preserve ONLY Carry // AF.Low |= 0x10; // Set Half-Carry // if ((memVal & bitMask) == 0) // { // AF.Low |= 0x44; // Set Zero (Bit 6) and P/V (Bit 2) // } // else if (bitIndex == 7) // { // AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign (Bit 7) // } // return 20; // case 2: // ALL RES Instructions // memVal &= (byte)(~bitMask); // Invert mask and AND it to clear the bit // WriteMemory(targetAddress, memVal); // return 23; // case 3: // ALL SET Instructions // memVal |= bitMask; // OR the mask to force the bit to 1 // WriteMemory(targetAddress, memVal); // return 23; // case 0: // // Shift/Rotate instructions will go here later // throw new NotImplementedException($"FD CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!"); // default: // throw new Exception("Invalid bitwise operation."); // } // } // case 0xE1: // POP IY // // 1. Read the Low byte from the current Stack Pointer, then increment SP // IY.Low = ReadMemory(SP); // SP++; // // 2. Read the High byte from the new Stack Pointer, then increment SP // IY.High = ReadMemory(SP); // SP++; // return 14; // 14 T-States // case 0xE5: // PUSH IY // // 1. Decrement SP and write the High byte // SP--; // WriteMemory(SP, IY.High); // // 2. Decrement SP again and write the Low byte // SP--; // WriteMemory(SP, IY.Low); // return 15; // 15 T-States // default: // throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!"); // } // } // } //}