From e52cdeac542ac1f6e86c2a523fafd57ff94482ce Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Wed, 22 Apr 2026 02:00:50 +0100 Subject: [PATCH] Implemented a few more OpCodes. Manic Miner sounds great! --- Core/Cpu/Z80.cs | 94 +++++++++++++++++++++++++++++++++++++++++ Desktop/DebuggerForm.cs | 32 +++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index f93ece9..a54bd32 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -1776,6 +1776,45 @@ namespace Core.Cpu 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); @@ -2182,6 +2221,9 @@ namespace Core.Cpu 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(); @@ -2409,6 +2451,10 @@ namespace Core.Cpu 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 0x34: // INC (IY+d) // 1. Fetch displacement and calculate memory address sbyte offset34 = (sbyte)FetchByte(); @@ -2556,6 +2602,17 @@ namespace Core.Cpu targetAddress = (ushort)(IY.Word + offset75); // Write the low byte of HL to memory WriteMemory(targetAddress, HL.Low); + 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 0x86: // ADD A, (IY+d) { @@ -2604,6 +2661,43 @@ namespace Core.Cpu 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 diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 6dab922..56d96d3 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -835,6 +835,11 @@ namespace Desktop mnemonic = $"LD L, (IX{sign}{d})"; instructionLength = 3; } + else if (ddOpcode == 0x6F) // LD IXL, A + { + mnemonic = $"LD IXL, A)"; + instructionLength = 2; + } else if (ddOpcode == 0x71) // LD (IX+d), B { sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -1058,6 +1063,10 @@ namespace Desktop mnemonic = "LDIR"; instructionLength = 2; break; + case 0xB1: + mnemonic = "CPIR"; + instructionLength = 2; + break; case 0xB8: mnemonic = "LDDR"; instructionLength = 2; @@ -1102,6 +1111,13 @@ namespace Desktop mnemonic = $"LD IY, 0x{iyVal:X4}"; instructionLength = 4; } + else if (fdOpcode == 0x34) // INC IY + { + //sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + //string sign = d >= 0 ? "+" : ""; + mnemonic = $"INC IY"; + instructionLength = 2; + } else if (fdOpcode == 0x34) // INC (IY+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -1182,6 +1198,13 @@ namespace Desktop mnemonic = $"LD (IY{sign}{d}), H"; instructionLength = 3; } + else if (fdOpcode == 0x7E) // LD A, (IY+d) + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD A, (IY{sign}{d})"; + instructionLength = 3; + } else if (fdOpcode == 0x86) // ADD A, (IY+d) { sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -1197,6 +1220,13 @@ namespace Desktop mnemonic = $"SUB (IY{sign}{d})"; instructionLength = 3; } + else if (fdOpcode == 0xA6) //AND (IY+d) + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"AND (IY{sign}{d})"; + instructionLength = 3; + } else if (fdOpcode == 0xCB) // FD CB prefix { cbOp = _memoryBus.Read((ushort)(currentPc + 1)); @@ -1227,7 +1257,7 @@ namespace Desktop string sign = d >= 0 ? "+" : ""; mnemonic = $"LD (IY{sign}{d}), L"; instructionLength = 3; - } + } else { mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})";