From 7464b29fca31d8e31dbede4fcbac33448c9882f3 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Mon, 20 Apr 2026 12:51:04 +0100 Subject: [PATCH] Chuckie Egg runs fully from a snapshot file! Fully playable! --- Core/Cpu/Z80.cs | 206 +++++++++++++++++++++++++++++++++------- Desktop/DebuggerForm.cs | 87 ++++++++++++++++- 2 files changed, 256 insertions(+), 37 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index a619d52..ae338d8 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -43,6 +43,10 @@ namespace Core.Cpu private readonly IO_Bus _simpleIoBus; public TapManager _tapManager; + //Misc Variables + byte newFlags = 0; + int result = 0; + public Z80(IMemory memory, IO_Bus ioBus, TapManager tapManager) { _memory = memory; @@ -278,7 +282,7 @@ namespace Core.Cpu private void Sub(byte value) { byte a = AF.High; - int result = a - value; + result = a - value; // Save the result back to the Accumulator AF.High = (byte)result; @@ -311,7 +315,7 @@ namespace Core.Cpu byte carry = (byte)(AF.Low & 0x01); // Get the current Carry flag (Bit 0) // Calculate the raw integer result to check for borrows/underflows - int result = a - value - carry; + result = a - value - carry; // Update the Accumulator AF.High = (byte)result; @@ -344,7 +348,7 @@ namespace Core.Cpu int carry = AF.Low & 0x01; // Calculate the raw integer result to check for underflows - int result = hl - value - carry; + result = hl - value - carry; // Update the HL register HL.Word = (ushort)result; @@ -435,7 +439,7 @@ namespace Core.Cpu private void Cp(byte value) { byte a = AF.High; - int result = a - value; + result = a - value; // --- Update Flags (F Register) --- AF.Low = 0; // Clear all flags @@ -519,7 +523,7 @@ namespace Core.Cpu private void Add16(ushort value) { int hl = HL.Word; - int result = hl + value; + result = hl + value; // Update the HL register HL.Word = (ushort)result; @@ -531,11 +535,30 @@ namespace Core.Cpu // 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; - int result = a + value; + result = a + value; // Save the result back to the Accumulator AF.High = (byte)result; @@ -565,7 +588,7 @@ namespace Core.Cpu int aVal = AF.High; int carryIn = AF.Low & 0x01; - int result = aVal + operand + carryIn; + result = aVal + operand + carryIn; byte newFlags = 0; @@ -585,7 +608,7 @@ namespace Core.Cpu int carry = AF.Low & 0x01; // Calculate the raw integer result to check for overflows - int result = hl + value + carry; + result = hl + value + carry; // --- Update Flags (F Register) --- byte newFlags = 0; // Clear all flags (which forces N to 0, correctly!) @@ -612,7 +635,7 @@ namespace Core.Cpu private void AddA(byte operand) { byte a = AF.High; - int result = a + operand; + result = a + operand; AF.High = (byte)result; @@ -1487,8 +1510,6 @@ namespace Core.Cpu // Fetch the actual extended instruction byte extendedOpcode = _memory.Read(PC++); byte val = 0; - byte newFlags = 0; - int result = 0; switch (extendedOpcode) { @@ -1561,6 +1582,10 @@ namespace Core.Cpu DE.Low = _memory.Read(src5B); DE.High = _memory.Read((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; @@ -1920,25 +1945,16 @@ namespace Core.Cpu switch (ddOpcode) { case 0x09: // ADD IX, BC - int result = IX.Word + BC.Word; - - // --- 16-Bit Flag Calculation --- - // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2). - byte newFlags = (byte)(AF.Low & 0xC4); - - // Half-Carry (H - Bit 4): Set if carry from Bit 11 - if (((IX.Word & 0x0FFF) + (BC.Word & 0x0FFF)) > 0x0FFF) - newFlags |= 0x10; - - // Carry (C - Bit 0): Set if the total result overflows 16 bits - if (result > 0xFFFF) - newFlags |= 0x01; - - // (N - Bit 1 is left at 0 because the bitwise AND above cleared it) - - AF.Low = newFlags; - IX.Word = (ushort)result; - + 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(); @@ -2002,6 +2018,40 @@ namespace Core.Cpu // 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 = _memory.Read(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 + _memory.Write(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 = _memory.Read(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 + _memory.Write(address35, result35); + + return 23; case 0x36: // LD (IX+d), n // 1. Fetch the displacement byte first sbyte offset36 = (sbyte)FetchByte(); @@ -2071,9 +2121,22 @@ namespace Core.Cpu HL.High = _memory.Read(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(); @@ -2085,7 +2148,29 @@ namespace Core.Cpu HL.Low = _memory.Read(address6E); 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 + _memory.Write(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 + _memory.Write(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(); @@ -2119,6 +2204,10 @@ namespace Core.Cpu _memory.Write(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(); @@ -2129,6 +2218,21 @@ namespace Core.Cpu // 3. Read the byte from memory and drop it straight into the Accumulator (A) AF.High = _memory.Read(address7E); + return 19; + 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(_memory.Read(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(_memory.Read(address96)); return 19; case 0xBE: // CP (IX+d) // 1. Fetch the displacement byte and calculate the address @@ -2167,7 +2271,45 @@ namespace Core.Cpu // CRITICAL: Notice we do NOT update AF.High! The Accumulator is preserved. - return 19; // 19 T-States + 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 = _memory.Read(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 = _memory.Read(SP); diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index d4f989b..0a2fce5 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -832,11 +832,10 @@ namespace Desktop case 0xDD: { byte ddOpcode = _memoryBus.Read((ushort)(currentPc + 1)); - if (ddOpcode == 0x09) // ADD IX, BC - { - mnemonic = "ADD IX, BC"; - instructionLength = 2; - } + if (ddOpcode == 0x09) { mnemonic = "ADD IX, BC"; instructionLength = 2; } + else if (ddOpcode == 0x19) { mnemonic = "ADD IX, DE"; instructionLength = 2; } + else if (ddOpcode == 0x29) { mnemonic = "ADD IX, IX"; instructionLength = 2; } + else if (ddOpcode == 0x39) { mnemonic = "ADD IX, SP"; instructionLength = 2; } else if (ddOpcode == 0x21) // LD IX, nn { ushort ixVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); @@ -892,6 +891,20 @@ namespace Desktop mnemonic = $"LD IXL, 0x{nValue:X2}"; instructionLength = 3; } + else if (ddOpcode == 0x34) // INC (IX+d) + { + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = offset >= 0 ? "+" : ""; + mnemonic = $"INC (IX{sign}{offset})"; + instructionLength = 3; + } + else if (ddOpcode == 0x35) // DEC (IX+d) + { + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = offset >= 0 ? "+" : ""; + mnemonic = $"DEC (IX{sign}{offset})"; + instructionLength = 3; + } else if (ddOpcode == 0x36) // LD (IX+d), n { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -936,11 +949,26 @@ namespace Desktop mnemonic = $"LD H, (IX{sign}{d})"; instructionLength = 3; } + else if (ddOpcode == 0x67) // LD IXH, A + { + mnemonic = "LD IXH, A"; + instructionLength = 2; + } else if (ddOpcode == 0x68) // LD IXL, B { mnemonic = "LD IXL, B"; instructionLength = 2; } + else if (ddOpcode == 0x69) // LD IXL, C + { + mnemonic = "LD IXL, C"; + instructionLength = 2; + } + else if (ddOpcode == 0x6A) // LD IXL, D + { + mnemonic = "LD IXL, D"; + instructionLength = 2; + } else if (ddOpcode == 0x6E) // LD L, (IX+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -948,6 +976,20 @@ namespace Desktop mnemonic = $"LD L, (IX{sign}{d})"; instructionLength = 3; } + else if (ddOpcode == 0x72) // LD (IX+d), D + { + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = offset >= 0 ? "+" : ""; + mnemonic = $"LD (IX{sign}{offset}), D"; + instructionLength = 3; + } + else if (ddOpcode == 0x73) // LD (IX+d), E + { + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = offset >= 0 ? "+" : ""; + mnemonic = $"LD (IX{sign}{offset}), E"; + instructionLength = 3; + } else if (ddOpcode == 0x74) // LD (IX+d), H { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -973,6 +1015,11 @@ namespace Desktop instructionLength = 3; } + else if (ddOpcode == 0x7C) // LD A, IXH + { + mnemonic = "LD A, IXH"; + instructionLength = 2; + } else if (ddOpcode == 0x7E) // LD A, (IX+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -980,6 +1027,35 @@ namespace Desktop mnemonic = $"LD A, (IX{sign}{d})"; instructionLength = 3; } + else if (ddOpcode == 0x86) + { + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = offset >= 0 ? "+" : ""; + mnemonic = $"ADD A, (IX{sign}{offset})"; + instructionLength = 3; + } + else if (ddOpcode == 0x96) + { + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = offset >= 0 ? "+" : ""; + mnemonic = $"SUB (IX{sign}{offset})"; + instructionLength = 3; + } + else if (ddOpcode == 0xCB) + { + // DD CB instructions are 4 bytes long! + sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + cbOp = _memoryBus.Read((ushort)(currentPc + 3)); + + int operation = cbOp >> 6; + int bitIndex = (cbOp >> 3) & 0x07; + string sign = offset >= 0 ? "+" : ""; + + if (operation == 1) mnemonic = $"BIT {bitIndex}, (IX{sign}{offset})"; + else mnemonic = $"DD CB (IX{sign}{offset}) {cbOp:X2}"; // Fallback + + instructionLength = 4; + } else if (ddOpcode == 0xBE) // CP (IX+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -1081,6 +1157,7 @@ namespace Desktop mnemonic = $"LD DE, (0x{addr5B:X4})"; instructionLength = 4; break; + case 0x5E: mnemonic = "IM 2"; instructionLength = 2; break; case 0x5F: mnemonic = "LD A, R"; instructionLength = 2; break; case 0x6A: mnemonic = "ADC HL, HL"; instructionLength = 2; break; case 0x62: mnemonic = "SBC HL, HL"; instructionLength = 2; break;