From deeb7e3c611bae3f1ae2cf69f3445550729b3f54 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Wed, 15 Apr 2026 11:20:13 +0100 Subject: [PATCH] Implemented more OpCodes --- Core/Cpu/Z80.cs | 197 +++++++++++++++++++++++++++++++++++++--- Desktop/DebuggerForm.cs | 92 ++++++++++++++++++- 2 files changed, 275 insertions(+), 14 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 6b5d020..1588a6d 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -470,13 +470,24 @@ namespace Core.Cpu return 4; case 0x01: // LD BC, nn BC.Word = FetchWord(); - return 10; // Takes 10 T-States + return 10; + case 0x02: // LD (BC), A + _memory.Write(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 0x08: // EX AF, AF' + ushort tempAF = AF.Word; + AF.Word = AF_Prime.Word; + AF_Prime.Word = tempAF; + return 4; case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C + case 0x12: // LD (DE), A + _memory.Write(DE.Word, AF.High); + return 7; case 0x14: DE.High = Inc8(DE.High); return 4; // INC D case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E case 0x24: HL.High = Inc8(HL.High); return 4; // INC H @@ -520,21 +531,23 @@ namespace Core.Cpu BC.Low = FetchByte(); return 7; case 0x0F: // RRCA - // 1. Grab the bit that is about to fall off - byte bit0 = (byte)(AF.High & 0x01); + { + // 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)); + // 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; + // 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; + // Set the Carry Flag (Bit 0) to whatever fell off + AF.Low |= bit0; + return 4; + } case 0x10: // DJNZ d sbyte djnzOffset = (sbyte)FetchByte(); @@ -570,6 +583,27 @@ namespace Core.Cpu case 0x1B: // DEC DE DE.Word--; return 6; + case 0x1F: // RRA + { + // 1. Grab the current Carry Flag (0 or 1) + byte 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) @@ -761,6 +795,15 @@ namespace Core.Cpu case 0x95: Sub(HL.Low); return 4; // SUB L case 0x96: Sub(_memory.Read(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(_memory.Read(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 @@ -797,12 +840,126 @@ namespace Core.Cpu case 0xBD: Cp(HL.Low); return 4; // CP L case 0xBE: Cp(_memory.Read(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 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; @@ -982,6 +1139,11 @@ namespace Core.Cpu case 0x56: // IM 1 InterruptMode = 1; return 8; + case 0x5B: // LD DE, (nn) + ushort src5B = FetchWord(); + DE.Low = _memory.Read(src5B); + DE.High = _memory.Read((ushort)(src5B + 1)); + return 20; case 0xB0: // LDIR // 1. Read byte from (HL) val = _memory.Read(HL.Word); @@ -1074,6 +1236,15 @@ namespace Core.Cpu } return 12; + case 0x86: // RES 0, (HL) + memVal = _memory.Read(HL.Word); + + // 0xFE is Binary 1111 1110. + // ANDing preserves all bits except Bit 0, which becomes 0. + memVal &= 0xFE; + + _memory.Write(HL.Word, memVal); + return 15; case 0xAE: // RES 5, (HL) memVal = _memory.Read(HL.Word); diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 889bf52..c2b50d9 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -81,6 +81,8 @@ namespace Desktop _isRunning = true; btnRun.Text = "Stop"; + + // Fire up a background thread await Task.Run(() => { @@ -256,8 +258,13 @@ namespace Desktop mnemonic = $"LD BC, 0x{bcVal:X4}"; instructionLength = 3; break; + case 0x02: mnemonic = "LD (BC), A"; break; // --- 16-Bit Increments --- case 0x03: mnemonic = "INC BC"; break; + case 0x08: + mnemonic = "EX AF, AF'"; + break; + case 0x12: mnemonic = "LD (DE), A"; break; case 0x13: mnemonic = "INC DE"; break; case 0x33: mnemonic = "INC SP"; break; @@ -514,6 +521,16 @@ namespace Desktop case 0x96: mnemonic = "SUB (HL)"; break; case 0x97: mnemonic = "SUB A"; break; + // --- SBC A, r --- + case 0x98: mnemonic = "SBC A, B"; break; + case 0x99: mnemonic = "SBC A, C"; break; + case 0x9A: mnemonic = "SBC A, D"; break; + case 0x9B: mnemonic = "SBC A, E"; break; + case 0x9C: mnemonic = "SBC A, H"; break; + case 0x9D: mnemonic = "SBC A, L"; break; + case 0x9E: mnemonic = "SBC A, (HL)"; break; + case 0x9F: mnemonic = "SBC A, A"; break; + // --- AND r --- case 0xA0: mnemonic = "AND B"; break; case 0xA1: mnemonic = "AND C"; break; @@ -553,7 +570,42 @@ namespace Desktop case 0xBD: mnemonic = "CP L"; break; case 0xBE: mnemonic = "CP (HL)"; break; case 0xBF: mnemonic = "CP A"; break; - case 0xC1: mnemonic = "POP BC"; break; + case 0xC1: mnemonic = "POP BC"; break; + case 0xC2: + case 0xCA: + case 0xD2: + case 0xDA: + case 0xE2: + case 0xEA: + case 0xF2: + case 0xFA: + { + // Read the 16-bit target address + ushort jumpAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + string condition = ""; + + switch (opcode) + { + case 0xC2: condition = "NZ"; break; + case 0xCA: condition = "Z"; break; + case 0xD2: condition = "NC"; break; + case 0xDA: condition = "C"; break; + case 0xE2: condition = "PO"; break; + case 0xEA: condition = "PE"; break; + case 0xF2: condition = "P"; break; + case 0xFA: condition = "M"; break; + } + + mnemonic = $"JP {condition}, 0x{jumpAddr:X4}"; + instructionLength = 3; + break; + } + // --- Conditional Returns --- + case 0xC0: mnemonic = "RET NZ"; break; + case 0xE0: mnemonic = "RET PO"; break; + case 0xE8: mnemonic = "RET PE"; break; + case 0xF0: mnemonic = "RET P"; break; + case 0xF8: mnemonic = "RET M"; break; case 0xC3: // JP nn byte jpLow = _memoryBus.Read((ushort)(currentPc + 1)); @@ -561,6 +613,35 @@ namespace Desktop mnemonic = $"JP 0x{jpHigh:X2}{jpLow:X2}"; instructionLength = 3; break; + case 0xC4: + case 0xCC: + case 0xD4: + case 0xDC: + case 0xE4: + case 0xEC: + case 0xF4: + case 0xFC: + { + // Read the 16-bit target address + ushort callAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + string condition = ""; + + switch (opcode) + { + case 0xC4: condition = "NZ"; break; + case 0xCC: condition = "Z"; break; + case 0xD4: condition = "NC"; break; + case 0xDC: condition = "C"; break; + case 0xE4: condition = "PO"; break; + case 0xEC: condition = "PE"; break; + case 0xF4: condition = "P"; break; + case 0xFC: condition = "M"; break; + } + + mnemonic = $"CALL {condition}, 0x{callAddr:X4}"; + instructionLength = 3; + break; + } case 0xc5: mnemonic = "PUSH BC"; break; @@ -592,6 +673,10 @@ namespace Desktop { mnemonic = "BIT 7, (HL)"; } + else if (cbOp == 0x86) + { + mnemonic = "RES 0, (HL)"; + } else if (cbOp == 0xAE) { mnemonic = "RES 5, (HL)"; @@ -691,6 +776,11 @@ namespace Desktop mnemonic = "IM 1"; instructionLength = 2; break; + case 0x5B: + ushort addr5B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); + mnemonic = $"LD DE, (0x{addr5B:X4})"; + instructionLength = 4; + break; case 0xB0: mnemonic = "LDIR"; instructionLength = 2;