From 5892f7e491165fcb614133d6ab3b5ed9cfb109a7 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Thu, 23 Apr 2026 16:51:21 +0100 Subject: [PATCH] 5 ZEXALL tests now complete. Going to implement ALL remaining OpCOdes --- Core/Cpu/Z80.cs | 4775 +++++++++++++++++++++++++++---------- Desktop/DebuggerForm.cs | 11 + Desktop/Form1.Designer.cs | 16 +- Desktop/Form1.cs | 103 +- 4 files changed, 3532 insertions(+), 1373 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index ccc07a3..63193cb 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -6,6 +6,19 @@ 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; } @@ -49,15 +62,13 @@ namespace Core.Cpu //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(); + Reset(); } public void Reset() @@ -90,7 +101,6 @@ namespace Core.Cpu IFF2 = false; InterruptMode = 0; TotalTStates = 0; - //_memory.CleanRAMData(); } private void ApplyWaitStates(ushort address) @@ -125,7 +135,6 @@ namespace Core.Cpu 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); @@ -177,17 +186,6 @@ namespace Core.Cpu _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) { @@ -333,9 +331,6 @@ namespace Core.Cpu 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; @@ -349,124 +344,91 @@ namespace Core.Cpu $"C:{f & 1}"; } - private void Sub(byte value) + // ========================================================================= + // MATH AND LOGIC HELPERS + // ========================================================================= + + private void SubA(byte value, bool isCompare) { - byte a = AF.High; - result = a - value; + int result = AF.High - value; + byte flags = 0; - // Save the result back to the Accumulator - AF.High = (byte)result; + 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; - // --- Update Flags (F Register) --- - AF.Low = 0; // Clear all flags + flags |= 0x02; // N flag is always 1 for SUB and CP + if (result < 0) flags |= 0x01; - // Sign Flag (Bit 7) - if ((result & 0x80) != 0) AF.Low |= 0x80; + // The CP Trap: CP uses the operand, SUB uses the result + flags |= (byte)((isCompare ? value : result) & 0x28); - // Zero Flag (Bit 6) - if ((byte)result == 0) AF.Low |= 0x40; + AF.Low = flags; - // 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; + if (!isCompare) AF.High = (byte)result; } - private void Sbc(byte value) + private void SbcA(byte value) { byte a = AF.High; - byte carry = (byte)(AF.Low & 0x01); // Get the current Carry flag (Bit 0) + byte carry = (byte)(AF.Low & 0x01); + int result = a - value - carry; - // Calculate the raw integer result to check for borrows/underflows - result = a - value - carry; + byte flags = 0; - // Update the Accumulator + 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; - - // --- 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; + int result = hl - value - carry; - // Calculate the raw integer result to check for underflows - result = hl - value - carry; + byte flags = 0; - // Update the HL register + 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; - - // --- 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 carry = AF.Low & 0x01; int result = op1 - op2 - carry; - byte flags = 0x02; // N: Always 1 (Subtract) + byte flags = 0x02; // N: Always 1 - if (((result >> 8) & 0x80) != 0) flags |= 0x80; // S: Sign flag (Bit 15) - if ((result & 0xFFFF) == 0) flags |= 0x40; // Z: Zero flag + if (((result >> 8) & 0x80) != 0) flags |= 0x80; + if ((result & 0xFFFF) == 0) flags |= 0x40; - // 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; @@ -476,227 +438,125 @@ namespace Core.Cpu 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); + byte flags = 0; - // Clear all flags - AF.Low = 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 - // 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; + flags |= (byte)(result & 0x28); // Undocumented bits + AF.Low = flags; 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); + byte flags = 0; - // Clear all flags - AF.Low = 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 - // 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; + flags |= (byte)(result & 0x28); // Undocumented bits + AF.Low = flags; 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); + AF.High &= value; + AF.Low = 0x10; // AND forces H to 1, N to 0, C to 0 - // --- 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 + AF.Low |= ParityTable[AF.High]; + AF.Low |= (byte)(AF.High & 0x28); } private void Or(byte value) { - AF.High = (byte)(AF.High | value); + AF.High |= value; + AF.Low = 0; // OR forces H, N, C to 0 - // --- 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; + AF.Low |= ParityTable[AF.High]; + AF.Low |= (byte)(AF.High & 0x28); } private void Xor(byte value) { - // The caret (^) is the C# Bitwise XOR operator - AF.High = (byte)(AF.High ^ value); + AF.High ^= value; + AF.Low = 0; // XOR forces H, N, C to 0 - // --- 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; + AF.Low |= ParityTable[AF.High]; + AF.Low |= (byte)(AF.High & 0x28); } - private void Add16(ushort value) + private void Add16(ref RegisterPair dest, ushort value) { - int hl = HL.Word; - result = hl + value; + int result = dest.Word + value; + byte flags = (byte)(AF.Low & 0xC4); // Preserve S, Z, P/V - // Update the HL register - HL.Word = (ushort)result; - AF.Low &= 0xEC; + 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 - // 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; + AF.Low = flags; + dest.Word = (ushort)result; } - private void Add(byte value) + private void AddA(byte operand) { byte a = AF.High; - result = a + value; + int result = a + operand; - // Save the result back to the Accumulator AF.High = (byte)result; + byte flags = 0; - // --- Update Flags (F Register) --- - AF.Low = 0; // Clear all flags (This also correctly resets the N flag to 0) + if ((AF.High & 0x80) != 0) flags |= 0x80; + if (AF.High == 0) flags |= 0x40; + if (((a & 0x0F) + (operand & 0x0F)) > 0x0F) flags |= 0x10; - // Sign Flag (Bit 7) - if ((result & 0x80) != 0) AF.Low |= 0x80; + bool sameSign = ((a ^ operand) & 0x80) == 0; + bool changedSign = ((a ^ AF.High) & 0x80) != 0; + if (sameSign && changedSign) flags |= 0x04; - // Zero Flag (Bit 6) - if ((byte)result == 0) AF.Low |= 0x40; + if (result > 0xFF) flags |= 0x01; + flags |= (byte)(AF.High & 0x28); - // 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; + AF.Low = flags; } private void AdcA(byte operand) { int aVal = AF.High; int carryIn = AF.Low & 0x01; + int result = aVal + operand + carryIn; - result = aVal + operand + carryIn; + byte flags = 0; - byte newFlags = 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; - 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 + flags |= (byte)(result & 0x28); - AF.Low = newFlags; + AF.Low = flags; AF.High = (byte)result; } @@ -704,29 +564,19 @@ namespace Core.Cpu { int hl = HL.Word; int carry = AF.Low & 0x01; + int result = hl + value + carry; - // Calculate the raw integer result to check for overflows - result = hl + value + carry; + byte flags = 0; - // --- Update Flags (F Register) --- - byte newFlags = 0; // Clear all flags (which forces N to 0, correctly!) + 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; - // Sign Flag (Bit 7) - Set if the 16-bit result is negative (bit 15 is 1) - if ((result & 0x8000) != 0) newFlags |= 0x80; + flags |= (byte)((result >> 8) & 0x28); - // 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; + AF.Low = flags; HL.Word = (ushort)result; } @@ -734,138 +584,24 @@ namespace Core.Cpu { int op1 = HL.Word; int op2 = value; - int carry = AF.Low & 0x01; // Current C flag + int carry = AF.Low & 0x01; 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 + if (((result >> 8) & 0x80) != 0) flags |= 0x80; + if ((result & 0xFFFF) == 0) flags |= 0x40; - // 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 @@ -908,31 +644,21 @@ namespace Core.Cpu // --- 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); + byte flags07 = (byte)(AF.Low & 0xC4); + if (topBit != 0) flags07 |= 0x01; + flags07 |= (byte)(AF.High & 0x28); - // 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 + 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; - // Inside your base switch(opcode) statement: - case 0x09: AddHl(BC.Word); return 11; + case 0x09: Add16(ref HL, BC.Word); return 11; case 0x0A: //LD A (BC) AF.High = ReadMemory(BC.Word); return 7; @@ -941,11 +667,12 @@ namespace Core.Cpu 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 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 + case 0x1E: + DE.Low = FetchByte(); // LD E, n return 7; - case 0x29: AddHl(HL.Word); return 11; + 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 @@ -953,8 +680,8 @@ namespace Core.Cpu 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; + 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 --- @@ -965,17 +692,14 @@ namespace Core.Cpu 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; - + 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) takes 11 T-States + return 11; // DEC (HL) case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A case 0x06: // LD B, n BC.High = FetchByte(); @@ -988,33 +712,22 @@ namespace Core.Cpu 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; + 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(); @@ -1026,30 +739,17 @@ namespace Core.Cpu 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 + 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 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); @@ -1059,23 +759,13 @@ namespace Core.Cpu 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; - + byte flags1F = (byte)(AF.Low & 0xC4); + flags1F |= bit0; + flags1F |= (byte)(AF.High & 0x28); + AF.Low = flags1F; return 4; } case 0x20: // JR NZ, e @@ -1083,7 +773,7 @@ namespace Core.Cpu if ((AF.Low & 0x40) == 0) { PC = (ushort)(PC + offset); - return 12; + return 12; } return 7; case 0x21: // LD HL, nn @@ -1103,26 +793,20 @@ namespace Core.Cpu case 0x27: // DAA byte a = AF.High; int correction = 0; - byte flags = AF.Low; + byte flags27 = 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! + bool carry = (flags27 & 0x01) != 0; + bool halfCarry = (flags27 & 0x10) != 0; + bool isSub = (flags27 & 0x02) != 0; - // 1. Check if the lower nibble needs adjustment - if (halfCarry || (a & 0x0F) > 9) - { - correction |= 0x06; - } + 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 + carry = true; } - // 3. Apply the correction and calculate the new Half-Carry bool newHalfCarry = false; if (isSub) { @@ -1137,19 +821,18 @@ namespace Core.Cpu 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 + 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 = flags; + AF.Low = flags27; 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); @@ -1168,12 +851,10 @@ namespace Core.Cpu 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 12; } return 7; case 0x31: // LD SP, nn @@ -1195,12 +876,12 @@ namespace Core.Cpu WriteMemory(HL.Word, nValue); return 10; case 0x37: // SCF - AF.Low |= 0x01; // Force Carry Flag (Bit 0) to 1 + AF.Low |= 0x01; AF.Low &= 0xED; + AF.Low |= (byte)(AF.High & 0x28); 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); @@ -1219,16 +900,14 @@ namespace Core.Cpu 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; + 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: //BC.High = BC.High; - 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; @@ -1239,8 +918,7 @@ namespace Core.Cpu // --- LD C, r --- case 0x48: BC.Low = BC.High; return 4; - case 0x49: //BC.Low = BC.Low; - 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; @@ -1251,8 +929,7 @@ namespace Core.Cpu // --- 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 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; @@ -1263,8 +940,7 @@ namespace Core.Cpu 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 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; @@ -1275,8 +951,7 @@ namespace Core.Cpu 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 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; @@ -1287,12 +962,11 @@ namespace Core.Cpu 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 0x6D: 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) + // --- 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; @@ -1309,7 +983,7 @@ namespace Core.Cpu { InterruptRequested = false; return 4; - } + } case 0x77: WriteMemory(HL.Word, AF.High); return 7; // --- LD A, r --- @@ -1320,17 +994,17 @@ namespace Core.Cpu 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 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 @@ -1339,32 +1013,34 @@ namespace Core.Cpu 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 + + // --- 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: 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 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 @@ -1393,15 +1069,16 @@ namespace Core.Cpu 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 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; @@ -1446,35 +1123,29 @@ namespace Core.Cpu 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) + case 0xE2: // JP PO, nn { ushort addr = FetchWord(); if ((AF.Low & 0x04) == 0) PC = addr; return 10; } - case 0xEA: // JP PE, nn (Parity Even / Overflow) + case 0xEA: // JP PE, nn { ushort addr = FetchWord(); if ((AF.Low & 0x04) != 0) PC = addr; return 10; } - case 0xF2: // JP P, nn (Sign Positive) + case 0xF2: // JP P, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) == 0) PC = addr; return 10; } - case 0xFA: // JP M, nn (Sign Minus) + case 0xFA: // JP M, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) != 0) PC = addr; @@ -1508,25 +1179,25 @@ namespace Core.Cpu if ((AF.Low & 0x01) != 0) { Push(PC); PC = addr; return 17; } return 10; } - case 0xE4: // CALL PO, nn (Parity Odd) + 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 (Parity Even) + 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 (Sign Positive) + 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 (Sign Minus) + case 0xFC: // CALL M, nn { ushort addr = FetchWord(); if ((AF.Low & 0x80) != 0) { Push(PC); PC = addr; return 17; } @@ -1536,26 +1207,24 @@ namespace Core.Cpu Push(BC.Word); return 11; case 0xC6: // ADD A, n - Add(FetchByte()); + AddA(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 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 - // Check if the Zero Flag (Bit 6) IS set if ((AF.Low & 0x40) != 0) { PC = Pop(); - return 11; // Condition met, took the return + return 11; } - return 5; // Condition not met, skipped + return 5; case 0xC9: // RET PC = Pop(); return 10; @@ -1567,37 +1236,31 @@ namespace Core.Cpu 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 11; } - return 5; // Condition not met, skipped + return 5; 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 + case 0xd5: //push de Push(DE.Word); return 11; case 0xD6: // SUB n - Sub(FetchByte()); + SubA(FetchByte(), false); 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 11; } return 5; case 0xD9: // EXX @@ -1617,26 +1280,23 @@ namespace Core.Cpu case 0xDD: return ExecuteDDPrefix(); case 0xDE: // SBC A, n - Sbc(FetchByte()); + SbcA(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 + case 0xe5: //push hl Push(HL.Word); return 11; case 0xE6: // AND n @@ -1644,46 +1304,32 @@ namespace Core.Cpu return 7; case 0xE9: // JP (HL) PC = HL.Word; - return 4; // Takes 4 T-States + return 4; case 0xEB: // EX DE, HL ushort tempEx = DE.Word; DE.Word = HL.Word; HL.Word = tempEx; - return 4; // Takes 4 T-States + return 4; 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 + Xor(FetchByte()); + return 7; case 0xF1: // POP AF AF.Word = Pop(); return 10; - case 0xF3: // DI (Disable Interrupts) + case 0xF3: // DI IFF1 = false; IFF2 = false; return 4; - case 0xf5: //push bc + 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; // (Use SP.Word = HL.Word if you made SP a RegisterPair) + SP = HL.Word; return 6; case 0xFB: // EI IFF1 = true; @@ -1692,15 +1338,15 @@ namespace Core.Cpu case 0xFD: return ExecuteFDPrefix(); case 0xFE: // CP n - Cp(FetchByte()); + 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 { - // Fetch the actual extended instruction byte extendedOpcode = FetchByte(); byte val = 0; @@ -1712,36 +1358,29 @@ namespace Core.Cpu WriteMemory((ushort)(dest43 + 1), BC.High); return 20; case 0x44: // NEG - int aBefore = AF.High; - result = 0 - aBefore; + { + int aBefore = AF.High; + int resultNeg = 0 - aBefore; - newFlags = 0; + byte flagsNeg = 0; - // S Flag (Bit 7): Set if the result is negative - if ((result & 0x80) != 0) newFlags |= 0x80; + 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; - // Z Flag (Bit 6): Set if the result is exactly zero - if ((result & 0xFF) == 0) newFlags |= 0x40; + flagsNeg |= (byte)(resultNeg & 0x28); - // H Flag (Bit 4): Set if there was a borrow from Bit 3 - if ((0 - (aBefore & 0x0F)) < 0) newFlags |= 0x10; + AF.Low = flagsNeg; + AF.High = (byte)resultNeg; - // 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 + return 8; + } case 0x47: // LD I, A I = AF.High; - return 9; + return 9; case 0x4B: // LD BC, (nn) ushort src4B = FetchWord(); BC.Low = ReadMemory(src4B); @@ -1759,32 +1398,16 @@ namespace Core.Cpu 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 + 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(); @@ -1792,30 +1415,19 @@ namespace Core.Cpu 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; + { + 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; + } // --- SBC HL, rr --- case 0x42: SbcHl(BC.Word); return 15; case 0x52: SbcHl(DE.Word); return 15; @@ -1827,164 +1439,102 @@ namespace Core.Cpu 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; + 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: 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 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) - // 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; - } - + 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 21; } 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). + newFlagsB1 |= 0x02; + if (BC.Word != 0) newFlagsB1 |= 0x04; + if (((AF.High ^ memValB1 ^ resultB1) & 0x10) != 0) newFlagsB1 |= 0x10; + if (resultB1 == 0) newFlagsB1 |= 0x40; + if ((resultB1 & 0x80) != 0) newFlagsB1 |= 0x80; 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 + return 21; } - - // If we found the byte or BC hit 0, the loop ends. - return 16; // 16 T-States when the loop terminates + return 16; 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 21; } - - return 16; // Finished! + return 16; default: throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); } @@ -1995,10 +1545,9 @@ namespace Core.Cpu 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 + int bitIndex = (cbOpcode >> 3) & 0x07; + int regIndex = cbOpcode & 0x07; byte bitMask = (byte)(1 << bitIndex); @@ -2032,111 +1581,75 @@ namespace Core.Cpu AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign } - // BIT (HL) takes 12 T-States. Standard register BIT takes 8. + // Undocumented bits 3 and 5 are generally unpredictable here or take memory values, but keeping it robust for now return (regIndex == 6) ? 12 : 8; case 2: // ALL RES Instructions val &= (byte)(~bitMask); - break; // Proceed to write-back + break; case 3: // ALL SET Instructions val |= bitMask; - break; // Proceed to write-back + break; 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 + case 1: // RRC 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 + case 2: // RL 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 + case 3: // RR 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 + case 4: // SLA 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 + case 5: // SRA 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 + case 7: // SRL 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; + if ((val & 0x80) != 0) newFlags |= 0x80; + if (val == 0) newFlags |= 0x40; + newFlags |= ParityTable[val]; + newFlags |= (byte)(val & 0x28); - 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 + AF.Low = newFlags; + break; default: throw new Exception("Invalid CB operation."); } - // --- PHASE 3: Write back the modified value (RES and SET only) --- + // --- PHASE 3: Write back the modified value --- switch (regIndex) { case 0: BC.High = val; break; @@ -2149,445 +1662,261 @@ namespace Core.Cpu 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 + byte ddOpcode = FetchByte(); 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 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 - // 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; + { + 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) - // 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; + { + 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 - // 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 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 - // 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 + AddA(IX.High); 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; - + { + sbyte displacementAdd = (sbyte)FetchByte(); + ushort targetAddressAdd = (ushort)(IX.Word + displacementAdd); + AddA(ReadMemory(targetAddressAdd)); + 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; + { + sbyte offset96 = (sbyte)FetchByte(); + ushort address96 = (ushort)(IX.Word + offset96); + SubA(ReadMemory(address96), false); + 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; + { + sbyte offsetBE = (sbyte)FetchByte(); + ushort addressBE = (ushort)(IX.Word + offsetBE); + SubA(ReadMemory(addressBE), true); + 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 + int operation = cbOpcode >> 6; + int bitIndex = (cbOpcode >> 3) & 0x07; + byte bitMask = (byte)(1 << bitIndex); switch (operation) { case 1: // ALL BIT Instructions - AF.Low &= 0x01; // Preserve ONLY Carry - AF.Low |= 0x10; // Set Half-Carry + AF.Low &= 0x01; + AF.Low |= 0x10; if ((memVal & bitMask) == 0) { - AF.Low |= 0x44; // Set Zero (Bit 6) and P/V (Bit 2) + AF.Low |= 0x44; } else if (bitIndex == 7) { - AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign (Bit 7) + AF.Low |= 0x80; } - return 20; // 20 T-States + return 20; - // (You can copy your RES and SET logic from ExecuteFDPrefix here later!) + 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; + + case 0: + throw new NotImplementedException($"DD CB Shift/Rotate opcode {cbOpcode:X2} not implemented!"); default: - throw new NotImplementedException($"DD CB opcode {cbOpcode:X2} not fully implemented!"); + throw new Exception("Invalid bitwise operation."); } } 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 + SP++; byte popHigh = ReadMemory(SP); - SP++; // Move stack pointer up again - - // 3. Combine them and store in IX + SP++; 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!"); + throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} at PC 0x{(PC - 2):X4} not implemented!"); } } @@ -2599,60 +1928,27 @@ namespace Core.Cpu 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 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 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 0x29: Add16(ref IY, 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 + { + 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); - - // Read, decrement using your existing helper, and write back memVal = ReadMemory(targetAddress); byte decVal = Dec8(memVal); WriteMemory(targetAddress, decVal); @@ -2662,259 +1958,113 @@ namespace Core.Cpu sbyte offset36 = (sbyte)FetchByte(); byte nValue = FetchByte(); targetAddress = (ushort)(IY.Word + offset36); - WriteMemory(targetAddress, nValue); - return 19; // Takes 19 T-States + return 19; } - - case 0x39: AddIy(SP); return 15; + 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; // Takes 19 T-States + return 19; } 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 + return 19; } 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; + { + sbyte offset96 = (sbyte)FetchByte(); + ushort address96 = (ushort)(IY.Word + offset96); + SubA(ReadMemory(address96), false); + 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; - + And(operandA6); 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; + { + 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(); @@ -2922,39 +2072,37 @@ namespace Core.Cpu 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) + int operation = cbOpcode >> 6; + int bitIndex = (cbOpcode >> 3) & 0x07; + byte bitMask = (byte)(1 << bitIndex); switch (operation) { case 1: // ALL BIT Instructions - AF.Low &= 0x01; // Preserve ONLY Carry - AF.Low |= 0x10; // Set Half-Carry + AF.Low &= 0x01; + AF.Low |= 0x10; if ((memVal & bitMask) == 0) { - AF.Low |= 0x44; // Set Zero (Bit 6) and P/V (Bit 2) + AF.Low |= 0x44; } else if (bitIndex == 7) { - AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign (Bit 7) + AF.Low |= 0x80; } return 20; case 2: // ALL RES Instructions - memVal &= (byte)(~bitMask); // Invert mask and AND it to clear the bit + memVal &= (byte)(~bitMask); WriteMemory(targetAddress, memVal); return 23; case 3: // ALL SET Instructions - memVal |= bitMask; // OR the mask to force the bit to 1 + memVal |= bitMask; 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: @@ -2962,28 +2110,3029 @@ namespace Core.Cpu } } 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 + return 14; 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 + return 15; default: throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!"); } } } -} \ No newline at end of file +} +//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!"); +// } +// } +// } +//} \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 8327f9f..2602a3e 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -1241,6 +1241,17 @@ namespace Desktop mnemonic = $"LD A, (IY{sign}{d})"; instructionLength = 3; } + else if (fdOpcode == 0x84) // ADD A, IYH + { + + mnemonic = $"ADD A, IYH"; + instructionLength = 2; + } + else if (fdOpcode == 0x85) // ADD A, IYL + { + mnemonic = $"ADD A, IYL"; + instructionLength = 2; + } else if (fdOpcode == 0x86) // ADD A, (IY+d) { sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index 74d67b8..cdf4e35 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -44,7 +44,7 @@ resetToolStripMenuItem1 = new ToolStripMenuItem(); optionsToolStripMenuItem = new ToolStripMenuItem(); fastLoadingToolStripMenuItem = new ToolStripMenuItem(); - runZEXDOCToolStripMenuItem = new ToolStripMenuItem(); + HighSpeedToolStripMenuItem = new ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)picScreen).BeginInit(); menuStrip1.SuspendLayout(); SuspendLayout(); @@ -156,7 +156,7 @@ // // optionsToolStripMenuItem // - optionsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { fastLoadingToolStripMenuItem, runZEXDOCToolStripMenuItem }); + optionsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { fastLoadingToolStripMenuItem, HighSpeedToolStripMenuItem }); optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; optionsToolStripMenuItem.Size = new Size(75, 24); optionsToolStripMenuItem.Text = "Options"; @@ -170,12 +170,12 @@ fastLoadingToolStripMenuItem.Text = "Fast TAP Loading"; fastLoadingToolStripMenuItem.Click += fastLoadingToolStripMenuItem_Click; // - // runZEXDOCToolStripMenuItem + // HighSpeedToolStripMenuItem // - runZEXDOCToolStripMenuItem.Name = "runZEXDOCToolStripMenuItem"; - runZEXDOCToolStripMenuItem.Size = new Size(224, 26); - runZEXDOCToolStripMenuItem.Text = "Run ZEXDOC"; - runZEXDOCToolStripMenuItem.Click += btnRunZexDoc_Click; + HighSpeedToolStripMenuItem.Name = "HighSpeedToolStripMenuItem"; + HighSpeedToolStripMenuItem.Size = new Size(224, 26); + HighSpeedToolStripMenuItem.Text = "High Speed"; + HighSpeedToolStripMenuItem.Click += btnHighSpeedToggle_Click; // // Form1 // @@ -213,6 +213,6 @@ private ToolStripMenuItem resetToolStripMenuItem1; private ToolStripMenuItem optionsToolStripMenuItem; private ToolStripMenuItem fastLoadingToolStripMenuItem; - private ToolStripMenuItem runZEXDOCToolStripMenuItem; + private ToolStripMenuItem HighSpeedToolStripMenuItem; } } diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 78c4216..85a77ff 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -26,7 +26,8 @@ namespace Desktop public double FramesPerSecond = 0; public double TotalFrameTime = 0; public double FrameTime = 0; - public bool exceptionRaised = false; + public bool highSpeed = false; + public bool tapeLoaded = false; public Form1() @@ -102,18 +103,24 @@ namespace Desktop _tapManager.Update(elapsedTStates); //Process audio at the correct time - //while (_cpu.TotalTStates >= (long)(audioSampleCount * 79.365)) - //{ - // bool finalAudioOutput = _simpleIoBus.BeeperState ^ _tapManager.EarBit; - // _beeper.AddSample(finalAudioOutput); - // audioSampleCount++; - //} + if (!highSpeed) + { + while (_cpu.TotalTStates >= (long)(audioSampleCount * 79.365)) + { + bool finalAudioOutput = _simpleIoBus.BeeperState ^ _tapManager.EarBit; + _beeper.AddSample(finalAudioOutput); + audioSampleCount++; + } + } // --- Check for End of Frame --- if (_cpu.TotalTStates >= nextScanlineTarget) { // Tell the ULA to draw one line of pixels - _ula.RenderScanline((int)scanlineCount % 312); + if (!highSpeed || (TotalFrameCount % 10 == 0)) + { + _ula.RenderScanline((int)scanlineCount % 312); + } nextScanlineTarget += 224; // Advance target by ONE line (224 T-States) scanlineCount++; @@ -122,22 +129,41 @@ namespace Desktop if (scanlineCount % 312 == 0) { _cpu.RequestInterrupt(); // 50Hz interrupt - - this.Invoke((MethodInvoker)delegate - { - UpdateScreenBitmap(); - this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1} - Fast Loading: {_cpu.EnableFastLoad.ToString()}"; - }); TotalFrameCount++; - // Throttle to real-time (50 FPS = 20ms) - //long targetTimeMs = (scanlineCount / 312) * 20; - //long elapsedMs = stopwatch.ElapsedMilliseconds; + if (highSpeed) + { + if (TotalFrameCount % 10 == 0) + { + this.BeginInvoke((MethodInvoker)delegate + { + UpdateScreenBitmap(); + this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}"; + }); + } + + } + else + { + this.Invoke((MethodInvoker)delegate + { + UpdateScreenBitmap(); + this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1} - Tape Loaded: {tapeLoaded.ToString()}"; + }); + } - //if (elapsedMs < targetTimeMs) - //{ - // Thread.Sleep((int)(targetTimeMs - elapsedMs)); - //} + + // Throttle to real-time (50 FPS = 20ms) + if (!highSpeed) + { + long targetTimeMs = (scanlineCount / 312) * 20; + long elapsedMs = stopwatch.ElapsedMilliseconds; + + if (elapsedMs < targetTimeMs) + { + Thread.Sleep((int)(targetTimeMs - elapsedMs)); + } + } TotalFrameTime += fpsStopwatch.Elapsed.TotalMilliseconds; if (TotalFrameCount % 50 == 0) { @@ -192,6 +218,7 @@ namespace Desktop { byte[] tapBytes = File.ReadAllBytes(ofd.FileName); _cpu._tapManager.LoadTapData(tapBytes); + tapeLoaded = true; } } _isPaused = false; @@ -220,38 +247,10 @@ namespace Desktop _isPaused = false; } - private void btnRunZexDoc_Click(object sender, EventArgs e) + private void btnHighSpeedToggle_Click(object sender, EventArgs e) { - _isPaused = true; - - using (OpenFileDialog ofd = new OpenFileDialog()) - { - ofd.Filter = "CP/M Binaries (*.com, *.bin)|*.com;*.bin"; - if (ofd.ShowDialog() == DialogResult.OK) - { - byte[] zexdocBytes = System.IO.File.ReadAllBytes(ofd.FileName); - - // 1. Wipe the RAM completely clean - _memoryBus.CleanRAMData(); - - // 2. Load ZEXDOC exactly at CP/M start address 0x0100 - for (int i = 0; i < zexdocBytes.Length; i++) - { - _memoryBus.Write((ushort)(0x0100 + i), zexdocBytes[i]); - } - - // 3. Configure the CPU for CP/M - _cpu.IsZexDocMode = true; - _cpu.PC = 0x0100; // Execution starts here - _cpu.SP = 0xFFFF; // Put the stack at the very top of memory - - // 4. Fake a RET address at 0x0000 so the test terminates gracefully when finished - _memoryBus.Write(0x0000, 0xD3); // OUT (n), A (A fake halt sequence) - - // Unpause and watch the Visual Studio Output window! - _isPaused = false; - } - } + this.HighSpeedToolStripMenuItem.Checked = !this.HighSpeedToolStripMenuItem.Checked; + highSpeed = this.HighSpeedToolStripMenuItem.Checked ? true : false; } private void btnRun_Click(object sender, EventArgs e) => _isPaused = false;