diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index eb197f9..9f85c39 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -8,6 +8,8 @@ namespace Core.Cpu //T-State counter public long TotalTStates { get; set; } + public int InterruptMode { get; private set; } = 0; // Defaults to 0 on power-up + // Interrupt Flip-Flops public bool IFF1; public bool IFF2; @@ -191,6 +193,36 @@ namespace Core.Cpu 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; @@ -247,10 +279,6 @@ namespace Core.Cpu // Update the HL register HL.Word = (ushort)result; - - // --- Update Flags (F Register) --- - // 16-bit ADD preserves S, Z, P/V (and the undocumented X/Y flags). - // We clear H (Bit 4), N (Bit 1), and C (Bit 0) using a bitwise AND mask (0xEC = 1110 1100) AF.Low &= 0xEC; // Half-Carry Flag (Bit 4) - Set if there is a carry from bit 11 @@ -277,6 +305,12 @@ namespace Core.Cpu { case 0x00: // NOP return 4; + case 0x01: // LD BC, nn + BC.Word = FetchWord(); + return 10; // Takes 10 T-States + case 0x04: // INC B + BC.High = Inc8(BC.High); + return 4; case 0x11: //LD DE, nn DE.Word = FetchWord(); return 10; @@ -291,6 +325,14 @@ namespace Core.Cpu return 12; } return 7; + case 0x21: // LD HL, nn + HL.Word = FetchWord(); + return 10; + case 0x22: // LD (nn), HL + ushort dest22 = FetchWord(); + _memory.Write(dest22, HL.Low); + _memory.Write((ushort)(dest22 + 1), HL.High); + return 16; case 0x23: // INC HL HL.Word++; return 6; @@ -304,6 +346,13 @@ namespace Core.Cpu return 12; // Jump taken } return 7; // Jump not taken + case 0x2A: // LD HL, (nn) + { + ushort srcAddress = FetchWord(); + HL.Low = _memory.Read(srcAddress); + HL.High = _memory.Read((ushort)(srcAddress + 1)); + return 16; // Takes 16 T-States + } case 0x2B: // DEC HL HL.Word--; return 6; @@ -334,10 +383,11 @@ namespace Core.Cpu return 10; case 0x3E: //LD A, n AF.High = FetchByte(); - return 7; + return 7; case 0x47: // LD B, A BC.High = AF.High; return 4; + case 0x62: // LD H, D HL.High = DE.High; return 4; @@ -347,6 +397,10 @@ namespace Core.Cpu case 0xA7: // AND A And(AF.High); return 4; + case 0xAF: // XOR A + AF.High = 0; + AF.Low = 0x44; + return 4; case 0xBC: // CP H Cp(HL.High); return 4; @@ -379,17 +433,20 @@ namespace Core.Cpu case 0xDE: // SBC A, n Sbc(FetchByte()); return 7; + 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 0xF3: // DI (Disable Interrupts) IFF1 = false; IFF2 = false; return 4; - case 0xAF: // XOR A - AF.High = 0; - AF.Low = 0x44; - return 4; - + case 0xF9: // LD SP, HL + SP = HL.Word; // (Use SP.Word = HL.Word if you made SP a RegisterPair) + return 6; default: throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); } @@ -401,12 +458,53 @@ namespace Core.Cpu switch (extendedOpcode) { + case 0x43: // LD (nn), BC + ushort dest43 = FetchWord(); + _memory.Write(dest43, BC.Low); + _memory.Write((ushort)(dest43 + 1), BC.High); + return 20; case 0x47: // LD I, A I = AF.High; return 9; case 0x52: // SBC HL, DE Sbc16(DE.Word); - return 15; // Takes 15 T-States + return 15; + case 0x53: // LD (nn), DE + ushort dest53 = FetchWord(); + _memory.Write(dest53, DE.Low); + _memory.Write((ushort)(dest53 + 1), DE.High); + return 20; + case 0x56: // IM 1 + InterruptMode = 1; + return 8; // Takes 8 T-States + case 0xB8: // LDDR + // 1. Read byte from (HL) + byte val = _memory.Read(HL.Word); + + // 2. Write byte to (DE) + _memory.Write(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."); } diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 4124c19..2c26139 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -222,6 +222,14 @@ namespace Desktop switch (opcode) { case 0x00: mnemonic = "NOP"; break; + case 0x01: + ushort bcVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + mnemonic = $"LD BC, 0x{bcVal:X4}"; + instructionLength = 3; + break; + case 0x04: + mnemonic = "INC B"; + break; case 0x11: // LD DE, nn byte deLow = _memoryBus.Read((ushort)(currentPc + 1)); @@ -238,6 +246,18 @@ namespace Desktop mnemonic = $"JR NZ, 0x{destination:X4}"; instructionLength = 2; break; + case 0x21: + { + ushort hlImm = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + mnemonic = $"LD HL, 0x{hlImm:X4}"; + instructionLength = 3; + break; + } + case 0x22: + ushort hlAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + mnemonic = $"LD (0x{hlAddr:X4}), HL"; + instructionLength = 3; + break; case 0x23: mnemonic = "INC HL"; break; @@ -247,6 +267,13 @@ namespace Desktop mnemonic = $"JR Z, 0x{jrZDest:X4}"; instructionLength = 2; break; + case 0x2A: + { + ushort addr2A = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + mnemonic = $"LD HL, (0x{addr2A:X4})"; + instructionLength = 3; + break; + } case 0x2B: mnemonic = "DEC HL"; break; @@ -306,12 +333,19 @@ namespace Desktop mnemonic = $"SBC A, 0x{sbcValue:X2}"; instructionLength = 2; break; + case 0xEB: + mnemonic = "EX DE, HL"; + break; case 0xED: byte extendedOp = _memoryBus.Read((ushort)(currentPc + 1)); switch (extendedOp) { - // Example: ED 47 is LD I, A + case 0x43: + ushort bcAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); + mnemonic = $"LD (0x{bcAddr:X4}), BC"; + instructionLength = 4; + break; case 0x47: mnemonic = "LD I, A"; instructionLength = 2; // 0xED + 0x47 @@ -320,11 +354,23 @@ namespace Desktop mnemonic = "SBC HL, DE"; instructionLength = 2; // ED 52 break; - // Example: ED B0 is LDIR (a massive block copy instruction) + case 0x53: + ushort deAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); + mnemonic = $"LD (0x{deAddr:X4}), DE"; + instructionLength = 4; + break; + case 0x56: + mnemonic = "IM 1"; + instructionLength = 2; + break; case 0xB0: mnemonic = "LDIR"; instructionLength = 2; break; + case 0xB8: + mnemonic = "LDDR"; + instructionLength = 2; + break; default: mnemonic = $"EXT UNKNOWN (ED {extendedOp:X2})"; @@ -335,6 +381,9 @@ namespace Desktop case 0xF3: mnemonic = "DI"; break; + case 0xF9: + mnemonic = "LD SP, HL"; + break; default: mnemonic = $"UNKNOWN (0x{opcode:X2})"; break;