From f52180aeb3baddba40f3d45a0bc9131cc915163a Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Wed, 15 Apr 2026 01:47:23 +0100 Subject: [PATCH] Implemented a shit load more OpCodes --- Core/Cpu/Z80.cs | 191 ++++++++++++++++++++++++++++++++++++---- Desktop/DebuggerForm.cs | 105 ++++++++++++++++++++-- 2 files changed, 270 insertions(+), 26 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index c342cf3..6b5d020 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -474,15 +474,67 @@ namespace Core.Cpu case 0x03: // INC BC BC.Word++; return 6; - case 0x04: // INC B - BC.High = Inc8(BC.High); - return 4; + // --- 8-Bit Increments --- + case 0x04: BC.High = Inc8(BC.High); return 4; // INC B + case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C + 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 + case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L + case 0x34: + _memory.Write(HL.Word, Inc8(_memory.Read(HL.Word))); + return 11; // INC (HL) takes 11 T-States + 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 0x35: + _memory.Write(HL.Word, Dec8(_memory.Read(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; + // --- ADD HL, rr (16-bit Addition) --- + case 0x09: + Add16(BC.Word); + return 11; + case 0x19: + Add16(DE.Word); + return 11; + case 0x29: + Add16(HL.Word); // This perfectly multiplies HL by 2! + return 11; + case 0x39: + Add16(SP); + return 11; case 0x0B: // DEC BC BC.Word--; return 6; case 0x0E: // LD C, n BC.Low = FetchByte(); - return 7; // Takes 7 T-States + 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(); @@ -512,9 +564,9 @@ namespace Core.Cpu PC = (ushort)(PC + jumpDistance); return 12; - case 0x19: // ADD HL, DE - Add16(DE.Word); - return 11; + case 0x1A: // LD A, (DE) + AF.High = _memory.Read(DE.Word); + return 7; case 0x1B: // DEC DE DE.Word--; return 6; @@ -579,17 +631,6 @@ namespace Core.Cpu case 0x33: // INC SP SP++; return 6; - case 0x35: // DEC (HL) - // Read the current byte from memory - byte memValue = _memory.Read(HL.Word); - - // Decrement it and update flags - byte decremented = Dec8(memValue); - - // Write the new value back to memory - _memory.Write(HL.Word, decremented); - - return 11; // Takes 11 T-States case 0x36: // LD (HL), n byte nValue = FetchByte(); _memory.Write(HL.Word, nValue); @@ -607,6 +648,10 @@ namespace Core.Cpu return 12; } return 7; + case 0x3A: // LD A, (nn) + ushort address3A = FetchWord(); + AF.High = _memory.Read(address3A); + return 13; case 0x3B: // DEC SP SP--; return 6; @@ -764,6 +809,16 @@ namespace Core.Cpu 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) @@ -775,6 +830,8 @@ namespace Core.Cpu case 0xC9: // RET PC = Pop(); return 10; + case 0xCB: + return ExecuteCBPrefix(); case 0xCD: // CALL nn ushort callAddress = FetchWord(); Push(PC); @@ -806,6 +863,14 @@ namespace Core.Cpu 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; @@ -826,6 +891,20 @@ namespace Core.Cpu 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 = _memory.Read(SP); + byte spHigh = _memory.Read((ushort)(SP + 1)); + + // 2. Write the current HL registers onto the stack in its place + _memory.Write(SP, HL.Low); + _memory.Write((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; @@ -852,6 +931,9 @@ namespace Core.Cpu 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; @@ -861,6 +943,9 @@ namespace Core.Cpu 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."); } @@ -881,6 +966,11 @@ namespace Core.Cpu case 0x47: // LD I, A I = AF.High; return 9; + case 0x4B: // LD BC, (nn) + ushort src4B = FetchWord(); + BC.Low = _memory.Read(src4B); + BC.High = _memory.Read((ushort)(src4B + 1)); + return 20; // Takes 20 T-States case 0x52: // SBC HL, DE Sbc16(DE.Word); return 15; @@ -952,6 +1042,63 @@ namespace Core.Cpu } } + private int ExecuteCBPrefix() + { + byte cbOpcode = FetchByte(); + byte memVal; + + switch (cbOpcode) + { + case 0x7E: // BIT 7, (HL) + byte memValBit7 = _memory.Read(HL.Word); + + // Preserve ONLY the Carry Flag (Bit 0). This clears N and everything else. + AF.Low &= 0x01; + + // Half-Carry (Bit 4) is ALWAYS set to 1 for Z80 BIT instructions + AF.Low |= 0x10; + + // Test Bit 7 (0x80 is Binary 1000 0000) + if ((memValBit7 & 0x80) == 0) + { + // If the bit is 0, turn ON the Zero Flag (Bit 6) + AF.Low |= 0x40; + + // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT + AF.Low |= 0x04; + } + else + { + // If testing Bit 7 and it is 1, the Sign Flag (Bit 7) perfectly mirrors it + AF.Low |= 0x80; + } + + return 12; + case 0xAE: // RES 5, (HL) + memVal = _memory.Read(HL.Word); + + // 0xDF is Binary 1101 1111 + // ANDing preserves all other bits while forcing Bit 5 to 0 + memVal &= 0xDF; + + _memory.Write(HL.Word, memVal); + return 15; + + case 0xC6: // SET 0, (HL) + memVal = _memory.Read(HL.Word); + + // 0x01 is Binary 0000 0001 + // ORing forces Bit 0 to 1 and perfectly preserves all other bits + memVal |= 0x01; + + _memory.Write(HL.Word, memVal); + return 15; + + default: + throw new NotImplementedException($"CB prefix opcode 0x{cbOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); + } + } + private int ExecuteFDPrefix() { byte opcode = FetchByte(); @@ -981,6 +1128,14 @@ namespace Core.Cpu _memory.Write(targetAddress, nValue); return 19; // Takes 19 T-States } + case 0x46: // LD B, (IY+d) + { + sbyte displacement = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + displacement); + + BC.High = _memory.Read(targetAddress); + return 19; // Takes 19 T-States + } case 0x6E: // LD L, (IY+d) sbyte displacementVal = (sbyte)FetchByte(); ushort targetAddr = (ushort)(IY.Word + displacementVal); diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 8eb2f47..889bf52 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -264,14 +264,43 @@ namespace Desktop // --- 16-Bit Decrements --- case 0x0B: mnemonic = "DEC BC"; break; case 0x3B: mnemonic = "DEC SP"; break; - case 0x04: - mnemonic = "INC B"; + // --- 8-Bit Increments --- + case 0x04: mnemonic = "INC B"; break; + case 0x0C: mnemonic = "INC C"; break; + case 0x14: mnemonic = "INC D"; break; + case 0x1C: mnemonic = "INC E"; break; + case 0x24: mnemonic = "INC H"; break; + case 0x2C: mnemonic = "INC L"; break; + case 0x34: mnemonic = "INC (HL)"; break; + case 0x3C: mnemonic = "INC A"; break; + + // --- 8-Bit Decrements --- + case 0x05: mnemonic = "DEC B"; break; + case 0x0D: mnemonic = "DEC C"; break; + case 0x15: mnemonic = "DEC D"; break; + case 0x1D: mnemonic = "DEC E"; break; + case 0x25: mnemonic = "DEC H"; break; + case 0x2D: mnemonic = "DEC L"; break; + case 0x35: mnemonic = "DEC (HL)"; break; + case 0x3D: mnemonic = "DEC A"; break; + case 0x06: + byte bImm = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"LD B, 0x{bImm:X2}"; + instructionLength = 2; break; + // --- ADD HL, rr --- + case 0x09: mnemonic = "ADD HL, BC"; break; + case 0x19: mnemonic = "ADD HL, DE"; break; + case 0x29: mnemonic = "ADD HL, HL"; break; + case 0x39: mnemonic = "ADD HL, SP"; break; case 0x0E: byte cImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD C, 0x{cImm:X2}"; instructionLength = 2; break; + case 0x0F: + mnemonic = "RRCA"; + break; case 0x10: sbyte djnzOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); ushort djnzDest = (ushort)(currentPc + 2 + djnzOffset); @@ -298,8 +327,8 @@ namespace Desktop mnemonic = $"JR 0x{targetAddressUnconditional:X4}"; instructionLength = 2; break; - case 0x19: - mnemonic = "ADD HL, DE"; + case 0x1A: + mnemonic = "LD A, (DE)"; break; case 0x1B: mnemonic = "DEC DE"; @@ -359,9 +388,6 @@ namespace Desktop instructionLength = 3; break; } - case 0x35: - mnemonic = "DEC (HL)"; - break; case 0x36: byte memValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD (HL), 0x{memValue:X2}"; @@ -377,6 +403,11 @@ namespace Desktop mnemonic = $"JR C, 0x{targetC:X4}"; instructionLength = 2; break; + case 0x3A: + ushort addr3A = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + mnemonic = $"LD A, (0x{addr3A:X4})"; + instructionLength = 3; + break; case 0x3E: mnemonic = $"LD A, 0x{_memoryBus.Read((ushort)(currentPc + 1)):X2}"; instructionLength = 2; @@ -540,12 +571,42 @@ namespace Desktop instructionLength = 2; break; } + // --- RST Instructions --- + case 0xC7: mnemonic = "RST 00h"; break; + case 0xCF: mnemonic = "RST 08h"; break; + case 0xD7: mnemonic = "RST 10h"; break; + case 0xDF: mnemonic = "RST 18h"; break; + case 0xE7: mnemonic = "RST 20h"; break; + case 0xEF: mnemonic = "RST 28h"; break; + case 0xF7: mnemonic = "RST 30h"; break; + case 0xFF: mnemonic = "RST 38h"; break; case 0xC8: mnemonic = "RET Z"; break; case 0xC9: mnemonic = "RET"; break; + case 0xCB: + byte cbOp = _memoryBus.Read((ushort)(currentPc + 1)); + if (cbOp == 0x7E) + { + mnemonic = "BIT 7, (HL)"; + } + else if (cbOp == 0xAE) + { + mnemonic = "RES 5, (HL)"; + } + else if (cbOp == 0xC6) + { + mnemonic = "SET 0, (HL)"; + } + else + { + mnemonic = $"CB UNKNOWN (0x{cbOp:X2})"; + } + + instructionLength = 2; + break; case 0xCD: ushort callDest = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"CALL 0x{callDest:X4}"; @@ -572,12 +633,18 @@ namespace Desktop mnemonic = $"SUB 0x{subImm:X2}"; instructionLength = 2; break; + case 0xD8: + mnemonic = "RET C"; + break; case 0xDE: byte sbcValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"SBC A, 0x{sbcValue:X2}"; instructionLength = 2; break; case 0xE1: mnemonic = "POP HL"; break; + case 0xE3: + mnemonic = "EX (SP), HL"; + break; case 0xE5: mnemonic = "PUSH HL"; break; @@ -606,6 +673,11 @@ namespace Desktop mnemonic = "LD I, A"; instructionLength = 2; // 0xED + 0x47 break; + case 0x4B: + ushort addr4B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); + mnemonic = $"LD BC, (0x{addr4B:X4})"; + instructionLength = 4; + break; case 0x52: mnemonic = "SBC HL, DE"; instructionLength = 2; // ED 52 @@ -638,10 +710,14 @@ namespace Desktop case 0xF3: mnemonic = "DI"; break; - break; case 0xf5: mnemonic = "PUSH AF"; break; + case 0xF6: + byte orImm = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"OR 0x{orImm:X2}"; + instructionLength = 2; + break; case 0xF9: mnemonic = "LD SP, HL"; break; @@ -673,6 +749,14 @@ namespace Desktop mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}"; instructionLength = 4; } + else if (fdOpcode == 0x46) // LD B, (IY+d) + { + sbyte dB = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string signB = dB >= 0 ? "+" : ""; + + mnemonic = $"LD B, (IY{signB}{dB})"; + instructionLength = 3; + } else if (fdOpcode == 0x6E) { sbyte offsetL = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -772,6 +856,11 @@ namespace Desktop } break; } + case 0xFE: + byte cpImm = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"CP 0x{cpImm:X2}"; + instructionLength = 2; + break; default: mnemonic = $"UNKNOWN (0x{opcode:X2})"; break;