From 7511e17c3eff045c79a25f44d3368ca88b3bf0db Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Fri, 8 May 2026 20:25:51 +0100 Subject: [PATCH] Add project files. --- Core/Core.csproj | 17 + Core/Cpu/RegisterPair.cs | 17 + Core/Cpu/Z80.cs | 2483 +++++++++++++++++++++++++++++++++++ Desktop/Desktop.csproj | 19 + Desktop/Form1.Designer.cs | 39 + Desktop/Form1.cs | 10 + Desktop/Form1.resx | 120 ++ Desktop/Program.cs | 17 + ParsonsMasterSystem2026.sln | 31 + 9 files changed, 2753 insertions(+) create mode 100644 Core/Core.csproj create mode 100644 Core/Cpu/RegisterPair.cs create mode 100644 Core/Cpu/Z80.cs create mode 100644 Desktop/Desktop.csproj create mode 100644 Desktop/Form1.Designer.cs create mode 100644 Desktop/Form1.cs create mode 100644 Desktop/Form1.resx create mode 100644 Desktop/Program.cs create mode 100644 ParsonsMasterSystem2026.sln diff --git a/Core/Core.csproj b/Core/Core.csproj new file mode 100644 index 0000000..7be6fbe --- /dev/null +++ b/Core/Core.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Core/Cpu/RegisterPair.cs b/Core/Cpu/RegisterPair.cs new file mode 100644 index 0000000..30bf18f --- /dev/null +++ b/Core/Cpu/RegisterPair.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Core.Cpu +{ + [StructLayout(LayoutKind.Explicit)] + public struct RegisterPair + { + [FieldOffset(0)] + public ushort Word; + + [FieldOffset(0)] + public byte Low; + + [FieldOffset(1)] + public byte High; + } +} \ No newline at end of file diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs new file mode 100644 index 0000000..8ff61be --- /dev/null +++ b/Core/Cpu/Z80.cs @@ -0,0 +1,2483 @@ +using System; +using Core.Interfaces; +using Core.Io; + +namespace Core.Cpu +{ + public partial class Z80 + { + private static readonly byte[] ParityTable = new byte[256]; + + // Static constructor to build the table once when the emulator starts + static Z80() + { + for (int i = 0; i < 256; i++) + { + int ones = 0; + for (int b = 0; b < 8; b++) if ((i & (1 << b)) != 0) ones++; + ParityTable[i] = (byte)((ones % 2 == 0) ? 0x04 : 0x00); // 0x04 if Even Parity + } + } + + //T-State counter + public long TotalTStates { get; set; } + + public int InterruptMode { get; private set; } = 0; + + // Interrupt Flip-Flops + public bool IFF1 { get; private set; } = false; + public bool IFF2 { get; private set; } = false; + public bool InterruptRequested { get; private set; } = false; + + // Main Register Set + public RegisterPair AF; + public RegisterPair BC; + public RegisterPair DE; + public RegisterPair HL; + + // Alternate Register Set + public RegisterPair AF_Prime; + public RegisterPair BC_Prime; + public RegisterPair DE_Prime; + public RegisterPair HL_Prime; + + // Index Registers + public RegisterPair IX; + public RegisterPair IY; + + // Special Purpose Registers + public ushort PC; // Program Counter + public ushort SP; // Stack Pointer + public byte I; // Interrupt Vector + public byte R; // Memory Refresh + + // The Memory Bus + private readonly IMemory _memory; + private readonly IO_Bus _simpleIoBus; + + //External Timing interface + public Func? WaitStateCallback { get; set; } + + public Z80(IMemory memory, IO_Bus ioBus) + { + _memory = memory; + _simpleIoBus = ioBus; + Reset(); + } + + public void Reset() + { + PC = 0x0000; + SP = 0xFFFF; + + // Main Registers + AF.Word = 0; + BC.Word = 0; + DE.Word = 0; + HL.Word = 0; + + // Alternate Registers + AF_Prime.Word = 0; + BC_Prime.Word = 0; + DE_Prime.Word = 0; + HL_Prime.Word = 0; + + // Index Registers + IX.Word = 0; + IY.Word = 0; + + // Internal Registers + I = 0; + R = 0; + + // Hardware State + IFF1 = false; + IFF2 = false; + InterruptMode = 0; + TotalTStates = 0; + } + + private void ApplyWaitStates(ushort address) + { + // If a system (like a ULA) is attached and listening, ask it for the delay + if (WaitStateCallback != null) + { + TotalTStates += WaitStateCallback(address, TotalTStates); + } + } + + public int RequestInterrupt() + { + InterruptRequested = true; + // 1. If the ROM has disabled interrupts (DI), ignore the request + if (!IFF1) return 0; + + // 2. Acknowledge the interrupt by immediately disabling further interrupts + IFF1 = false; + IFF2 = false; + + // 3. Push the current Program Counter to the stack so we can return later + Push(PC); + + // --- Interrupt Mode Dispatch --- + if (InterruptMode == 0 || InterruptMode == 1) + { + // IM 1: Hardcoded jump to ROM address 0x0038 + PC = 0x0038; + return 13; // IM 1 hardware call takes 13 T-States + } + else if (InterruptMode == 2) + { + // IM 2: Dynamic Vectored Interrupts + // A. Form the pointer address: High byte is 'I', Low byte is the floating bus (0xFF) + ushort vectorAddress = (ushort)((I << 8) | 0xFF); + + // B. Read the actual 16-bit ISR address from that location in memory (Little-Endian) + byte pcLow = ReadMemory(vectorAddress); + byte pcHigh = ReadMemory((ushort)(vectorAddress + 1)); + + // C. Jump to the custom game routine! + PC = (ushort)((pcHigh << 8) | pcLow); + + return 19; // IM 2 hardware call takes 19 T-States + } + else + { + throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!"); + } + } + + // 1. For fetching opcodes and immediate values (Advances PC) + public byte FetchByte() + { + ApplyWaitStates(PC); + byte data = _memory.Read(PC); + PC++; + return data; + } + + // 2. For fetching 16-bit immediate values + private ushort FetchWord() + { + // By using FetchByte twice, we perfectly apply wait states to BOTH memory reads! + byte low = FetchByte(); + byte high = FetchByte(); + return (ushort)((high << 8) | low); + } + + // 3. For standard memory reads (e.g., LD A, (HL)) + public byte ReadMemory(ushort address) + { + ApplyWaitStates(address); + return _memory.Read(address); + } + + // 4. For standard memory writes (e.g., LD (HL), A) + public void WriteMemory(ushort address, byte data) + { + ApplyWaitStates(address); + _memory.Write(address, data); + } + + // Placeholder for your hardware I/O + private byte ReadPort(ushort portAddress) + { + return _simpleIoBus.ReadPort(portAddress); + } + + public int Step() + { + + // Fetch the next opcode and increment the Program Counter + byte opcode = ReadMemory(PC++); + int tStates = ExecuteOpcode(opcode); + TotalTStates += tStates; + + // Decode and execute + return tStates; + } + + + + public string GetFlagsString() + { + byte f = AF.Low; + return $"S:{(f >> 7) & 1} " + + $"Z:{(f >> 6) & 1} " + + $"Y:{(f >> 5) & 1} " + // Undocumented flag + $"H:{(f >> 4) & 1} " + + $"X:{(f >> 3) & 1} " + // Undocumented flag + $"P/V:{(f >> 2) & 1} " + + $"N:{(f >> 1) & 1} " + + $"C:{f & 1}"; + } + + // ========================================================================= + // MATH AND LOGIC HELPERS + // ========================================================================= + + private void SubA(byte value, bool isCompare) + { + int result = AF.High - value; + byte flags = 0; + + if ((result & 0x80) != 0) flags |= 0x80; + if ((byte)result == 0) flags |= 0x40; + if (((AF.High & 0x0F) - (value & 0x0F)) < 0) flags |= 0x10; + if ((((AF.High ^ value) & 0x80) != 0) && (((AF.High ^ result) & 0x80) != 0)) flags |= 0x04; + + flags |= 0x02; // N flag is always 1 for SUB and CP + if (result < 0) flags |= 0x01; + + // The CP Trap: CP uses the operand, SUB uses the result + flags |= (byte)((isCompare ? value : result) & 0x28); + + AF.Low = flags; + + if (!isCompare) AF.High = (byte)result; + } + + private void SbcA(byte value) + { + byte a = AF.High; + byte carry = (byte)(AF.Low & 0x01); + int result = a - value - carry; + + byte flags = 0; + + if ((result & 0x80) != 0) flags |= 0x80; + if ((byte)result == 0) flags |= 0x40; + if (((a & 0x0F) - (value & 0x0F) - carry) < 0) flags |= 0x10; + if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) flags |= 0x04; + + flags |= 0x02; // N is 1 + if (result < 0) flags |= 0x01; + + flags |= (byte)(result & 0x28); + + AF.Low = flags; + AF.High = (byte)result; + } + + private void Sbc16(ushort value) + { + int hl = HL.Word; + int carry = AF.Low & 0x01; + int result = hl - value - carry; + + byte flags = 0; + + if ((result & 0x8000) != 0) flags |= 0x80; + if ((ushort)result == 0) flags |= 0x40; + if (((hl & 0x0FFF) - (value & 0x0FFF) - carry) < 0) flags |= 0x10; + if ((((hl ^ value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) flags |= 0x04; + + flags |= 0x02; // N + if (result < 0) flags |= 0x01; + + flags |= (byte)((result >> 8) & 0x28); + + AF.Low = flags; + HL.Word = (ushort)result; + } + + private void SbcHl(ushort value) + { + int op1 = HL.Word; + int op2 = value; + int carry = AF.Low & 0x01; + int result = op1 - op2 - carry; + + byte flags = 0x02; // N: Always 1 + + if (((result >> 8) & 0x80) != 0) flags |= 0x80; + if ((result & 0xFFFF) == 0) flags |= 0x40; + + if ((((op1 & 0x0FFF) - (op2 & 0x0FFF) - carry) & 0x1000) != 0) flags |= 0x10; + if ((((op1 ^ op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04; + if (result < 0) flags |= 0x01; + + flags |= (byte)((result >> 8) & 0x28); + + AF.Low = flags; + HL.Word = (ushort)(result & 0xFFFF); + } + + private byte Dec8(byte value) + { + byte result = (byte)(value - 1); + byte carry = (byte)(AF.Low & 0x01); + byte flags = 0; + + if ((result & 0x80) != 0) flags |= 0x80; + if (result == 0) flags |= 0x40; + if ((value & 0x0F) == 0) flags |= 0x10; + if (value == 0x80) flags |= 0x04; + flags |= 0x02; // N flag + flags |= carry; // Preserve C flag + + flags |= (byte)(result & 0x28); // Undocumented bits + + AF.Low = flags; + return result; + } + + private byte Inc8(byte value) + { + byte result = (byte)(value + 1); + byte carry = (byte)(AF.Low & 0x01); + byte flags = 0; + + if ((result & 0x80) != 0) flags |= 0x80; + if (result == 0) flags |= 0x40; + if ((value & 0x0F) == 0x0F) flags |= 0x10; + if (value == 0x7F) flags |= 0x04; + flags |= carry; // Preserve C flag + + flags |= (byte)(result & 0x28); // Undocumented bits + + AF.Low = flags; + return result; + } + + private void And(byte value) + { + AF.High &= value; + AF.Low = 0x10; // AND forces H to 1, N to 0, C to 0 + + if ((AF.High & 0x80) != 0) AF.Low |= 0x80; + if (AF.High == 0) AF.Low |= 0x40; + AF.Low |= ParityTable[AF.High]; + AF.Low |= (byte)(AF.High & 0x28); + } + + private void Or(byte value) + { + AF.High |= value; + AF.Low = 0; // OR forces H, N, C to 0 + + if ((AF.High & 0x80) != 0) AF.Low |= 0x80; + if (AF.High == 0) AF.Low |= 0x40; + AF.Low |= ParityTable[AF.High]; + AF.Low |= (byte)(AF.High & 0x28); + } + + private void Xor(byte value) + { + AF.High ^= value; + AF.Low = 0; // XOR forces H, N, C to 0 + + if ((AF.High & 0x80) != 0) AF.Low |= 0x80; + if (AF.High == 0) AF.Low |= 0x40; + AF.Low |= ParityTable[AF.High]; + AF.Low |= (byte)(AF.High & 0x28); + } + + private void Add16(ref RegisterPair dest, ushort value) + { + int result = dest.Word + value; + byte flags = (byte)(AF.Low & 0xC4); // Preserve S, Z, P/V + + if (((dest.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10; // H + if (result > 0xFFFF) flags |= 0x01; // C + flags |= (byte)((result >> 8) & 0x28); // Undocumented bits 3 & 5 + + AF.Low = flags; + dest.Word = (ushort)result; + } + + private void AddA(byte operand) + { + byte a = AF.High; + int result = a + operand; + + AF.High = (byte)result; + byte flags = 0; + + if ((AF.High & 0x80) != 0) flags |= 0x80; + if (AF.High == 0) flags |= 0x40; + if (((a & 0x0F) + (operand & 0x0F)) > 0x0F) flags |= 0x10; + + bool sameSign = ((a ^ operand) & 0x80) == 0; + bool changedSign = ((a ^ AF.High) & 0x80) != 0; + if (sameSign && changedSign) flags |= 0x04; + + if (result > 0xFF) flags |= 0x01; + flags |= (byte)(AF.High & 0x28); + + AF.Low = flags; + } + + private void AdcA(byte operand) + { + int aVal = AF.High; + int carryIn = AF.Low & 0x01; + int result = aVal + operand + carryIn; + + byte flags = 0; + + if ((result & 0x80) != 0) flags |= 0x80; + if ((result & 0xFF) == 0) flags |= 0x40; + if (((aVal & 0x0F) + (operand & 0x0F) + carryIn) > 0x0F) flags |= 0x10; + if ((((aVal ^ ~operand) & (aVal ^ result)) & 0x80) != 0) flags |= 0x04; + if (result > 0xFF) flags |= 0x01; + + flags |= (byte)(result & 0x28); + + AF.Low = flags; + AF.High = (byte)result; + } + + private void Adc16(ushort value) + { + int hl = HL.Word; + int carry = AF.Low & 0x01; + int result = hl + value + carry; + + byte flags = 0; + + if ((result & 0x8000) != 0) flags |= 0x80; + if ((result & 0xFFFF) == 0) flags |= 0x40; + if (((hl & 0x0FFF) + (value & 0x0FFF) + carry) > 0x0FFF) flags |= 0x10; + if ((((hl ^ ~value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) flags |= 0x04; + if (result > 0xFFFF) flags |= 0x01; + + flags |= (byte)((result >> 8) & 0x28); + + AF.Low = flags; + HL.Word = (ushort)result; + } + + private void AdcHl(ushort value) + { + int op1 = HL.Word; + int op2 = value; + int carry = AF.Low & 0x01; + int result = op1 + op2 + carry; + + byte flags = 0; + + if (((result >> 8) & 0x80) != 0) flags |= 0x80; + if ((result & 0xFFFF) == 0) flags |= 0x40; + + if ((((op1 & 0x0FFF) + (op2 & 0x0FFF) + carry) & 0x1000) != 0) flags |= 0x10; + if ((((op1 ^ ~op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04; + if ((result & 0x10000) != 0) flags |= 0x01; + + flags |= (byte)((result >> 8) & 0x28); + + AF.Low = flags; + HL.Word = (ushort)(result & 0xFFFF); + } + + private byte PerformShift(int shiftType, byte val) + { + bool carryOut = false; + bool oldCarry = (AF.Low & 0x01) != 0; + + switch (shiftType) + { + case 0: // RLC + carryOut = (val & 0x80) != 0; + val = (byte)((val << 1) | (carryOut ? 1 : 0)); + break; + case 1: // RRC + carryOut = (val & 0x01) != 0; + val = (byte)((val >> 1) | (carryOut ? 0x80 : 0x00)); + break; + case 2: // RL + carryOut = (val & 0x80) != 0; + val = (byte)((val << 1) | (oldCarry ? 1 : 0)); + break; + case 3: // RR + carryOut = (val & 0x01) != 0; + val = (byte)((val >> 1) | (oldCarry ? 0x80 : 0x00)); + break; + case 4: // SLA + carryOut = (val & 0x80) != 0; + val = (byte)(val << 1); + break; + case 5: // SRA + carryOut = (val & 0x01) != 0; + byte signBit = (byte)(val & 0x80); + val = (byte)((val >> 1) | signBit); + break; + case 6: // SLL (Undocumented - sometimes called SAA) + carryOut = (val & 0x80) != 0; + val = (byte)((val << 1) | 0x01); // SLL always shifts in a 1! + break; + case 7: // SRL + carryOut = (val & 0x01) != 0; + val = (byte)(val >> 1); + break; + default: + throw new NotImplementedException($"Shift type {shiftType} not implemented!"); + } + + byte newFlags = 0; + if (carryOut) newFlags |= 0x01; + if ((val & 0x80) != 0) newFlags |= 0x80; + if (val == 0) newFlags |= 0x40; + newFlags |= ParityTable[val]; + newFlags |= (byte)(val & 0x28); + AF.Low = newFlags; + + return val; + } + + private void Push(ushort value) + { + // High byte goes first + SP--; + WriteMemory(SP, (byte)(value >> 8)); + + // Low byte goes second + SP--; + WriteMemory(SP, (byte)(value & 0xFF)); + } + + private ushort Pop() + { + // The Z80 is Little-Endian. Low byte comes off the stack first. + byte low = ReadMemory(SP++); + + // High byte comes off second. + byte high = ReadMemory(SP++); + + return (ushort)((high << 8) | low); + } + + internal int ExecuteOpcode(byte opcode) + { + sbyte offset = 0; + byte oldCarry = 0; + switch (opcode) + { + case 0x00: // NOP + return 4; + case 0x01: // LD BC, nn + BC.Word = FetchWord(); + return 10; + case 0x02: // LD (BC), A + WriteMemory(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 0x07: // RLCA + byte topBit = (byte)(AF.High >> 7); + AF.High = (byte)((AF.High << 1) | topBit); + + byte flags07 = (byte)(AF.Low & 0xC4); + if (topBit != 0) flags07 |= 0x01; + flags07 |= (byte)(AF.High & 0x28); + + AF.Low = flags07; + return 4; + case 0x08: // EX AF, AF' + ushort tempAF = AF.Word; + AF.Word = AF_Prime.Word; + AF_Prime.Word = tempAF; + return 4; + case 0x09: Add16(ref HL, BC.Word); return 11; + case 0x0A: //LD A (BC) + AF.High = ReadMemory(BC.Word); + return 7; + case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C + case 0x12: // LD (DE), A + WriteMemory(DE.Word, AF.High); + return 7; + case 0x14: DE.High = Inc8(DE.High); return 4; // INC D + case 0x19: Add16(ref HL, DE.Word); return 11; + case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E + case 0x1E: + DE.Low = FetchByte(); // LD E, n + return 7; + case 0x29: Add16(ref HL, HL.Word); return 11; + case 0x24: HL.High = Inc8(HL.High); return 4; // INC H + case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L + case 0x2E: // LD L, n + HL.Low = FetchByte(); + return 7; + case 0x34: + WriteMemory(HL.Word, Inc8(ReadMemory(HL.Word))); + return 11; // INC (HL) + case 0x39: Add16(ref HL, SP); return 11; + 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 0x2F: // CPL + AF.High = (byte)(~AF.High); + AF.Low |= 0x12; + AF.Low &= 0xD7; // Ensure undocumented bits follow A + AF.Low |= (byte)(AF.High & 0x28); + return 4; + case 0x35: + WriteMemory(HL.Word, Dec8(ReadMemory(HL.Word))); + return 11; // DEC (HL) + case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A + case 0x06: // LD B, n + BC.High = FetchByte(); + return 7; + case 0x0B: // DEC BC + BC.Word--; + return 6; + case 0x0E: // LD C, n + BC.Low = FetchByte(); + return 7; + case 0x0F: // RRCA + { + byte bit0 = (byte)(AF.High & 0x01); + AF.High = (byte)((AF.High >> 1) | (bit0 << 7)); + byte flags0F = (byte)(AF.Low & 0xC4); + flags0F |= bit0; + flags0F |= (byte)(AF.High & 0x28); + AF.Low = flags0F; + return 4; + } + case 0x10: // DJNZ d + sbyte djnzOffset = (sbyte)FetchByte(); + BC.High--; // Decrement the B register + if (BC.High != 0) + { + PC = (ushort)(PC + djnzOffset); + return 13; // Jump taken + } + return 8; // Loop finished, no jump + case 0x11: //LD DE, nn + DE.Word = FetchWord(); + return 10; + case 0x13: // INC DE + DE.Word++; + return 6; + case 0x16: // LD D, n + DE.High = FetchByte(); + return 7; + case 0x17: // RLA + oldCarry = (byte)(AF.Low & 0x01); + bool newCarry = (AF.High & 0x80) != 0; + AF.High = (byte)((AF.High << 1) | oldCarry); + byte flags17 = (byte)(AF.Low & 0xC4); + if (newCarry) flags17 |= 0x01; + flags17 |= (byte)(AF.High & 0x28); + AF.Low = flags17; + return 4; + case 0x18: // JR d + sbyte jumpDistance = (sbyte)FetchByte(); + PC = (ushort)(PC + jumpDistance); + return 12; + case 0x1A: // LD A, (DE) + AF.High = ReadMemory(DE.Word); + return 7; + case 0x1B: // DEC DE + DE.Word--; + return 6; + case 0x1F: // RRA + { + oldCarry = (byte)(AF.Low & 0x01); + byte bit0 = (byte)(AF.High & 0x01); + AF.High = (byte)((AF.High >> 1) | (oldCarry << 7)); + byte flags1F = (byte)(AF.Low & 0xC4); + flags1F |= bit0; + flags1F |= (byte)(AF.High & 0x28); + AF.Low = flags1F; + return 4; + } + case 0x20: // JR NZ, e + offset = (sbyte)FetchByte(); + if ((AF.Low & 0x40) == 0) + { + PC = (ushort)(PC + offset); + return 12; + } + return 7; + case 0x21: // LD HL, nn + HL.Word = FetchWord(); + return 10; + case 0x22: // LD (nn), HL + ushort dest22 = FetchWord(); + WriteMemory(dest22, HL.Low); + WriteMemory((ushort)(dest22 + 1), HL.High); + return 16; + case 0x23: // INC HL + HL.Word++; + return 6; + case 0x26: // LD H, n + HL.High = FetchByte(); + return 7; + case 0x27: // DAA + byte a = AF.High; + int correction = 0; + byte flags27 = AF.Low; + + bool carry = (flags27 & 0x01) != 0; + bool halfCarry = (flags27 & 0x10) != 0; + bool isSub = (flags27 & 0x02) != 0; + + if (halfCarry || (a & 0x0F) > 9) correction |= 0x06; + + if (carry || a > 0x99 || (a >= 0x90 && (a & 0x0F) > 9)) + { + correction |= 0x60; + carry = true; + } + + bool newHalfCarry = false; + if (isSub) + { + newHalfCarry = halfCarry && (a & 0x0F) < 0x06; + a = (byte)(a - correction); + } + else + { + newHalfCarry = ((a & 0x0F) + (correction & 0x0F)) > 0x0F; + a = (byte)(a + correction); + } + + AF.High = a; + + flags27 &= 0x02; + if (carry) flags27 |= 0x01; + if (newHalfCarry) flags27 |= 0x10; + if ((a & 0x80) != 0) flags27 |= 0x80; + if (a == 0) flags27 |= 0x40; + flags27 |= ParityTable[a]; + flags27 |= (byte)(a & 0x28); + + AF.Low = flags27; + return 4; + case 0x28: // JR Z, e + offset = (sbyte)FetchByte(); + if ((AF.Low & 0x40) != 0) + { + PC = (ushort)(PC + offset); + return 12; + } + return 7; + case 0x2A: // LD HL, (nn) + { + ushort srcAddress = FetchWord(); + HL.Low = ReadMemory(srcAddress); + HL.High = ReadMemory((ushort)(srcAddress + 1)); + return 16; + } + case 0x2B: // DEC HL + HL.Word--; + return 6; + case 0x30: // JR NC, e + offset = (sbyte)FetchByte(); + if ((AF.Low & 0x01) == 0) + { + PC = (ushort)(PC + offset); + return 12; + } + return 7; + case 0x31: // LD SP, nn + { + SP = FetchWord(); + return 10; + } + case 0x32: // LD (nn), A + { + ushort destAddress = FetchWord(); + WriteMemory(destAddress, AF.High); + return 13; + } + case 0x33: // INC SP + SP++; + return 6; + case 0x36: // LD (HL), n + byte nValue = FetchByte(); + WriteMemory(HL.Word, nValue); + return 10; + case 0x37: // SCF + AF.Low |= 0x01; + AF.Low &= 0xED; + AF.Low |= (byte)(AF.High & 0x28); + return 4; + case 0x38: // JR C, d + sbyte jrCOffset = (sbyte)FetchByte(); + if ((AF.Low & 0x01) != 0) + { + PC = (ushort)(PC + jrCOffset); + return 12; + } + return 7; + case 0x3A: // LD A, (nn) + ushort address3A = FetchWord(); + AF.High = ReadMemory(address3A); + return 13; + case 0x3B: // DEC SP + SP--; + return 6; + case 0x3E: //LD A, n + AF.High = FetchByte(); + return 7; + case 0x3F: // CCF + bool previousCarry = (AF.Low & 0x01) != 0; + AF.Low ^= 0x01; + AF.Low &= 0xFD; + if (previousCarry) AF.Low |= 0x10; + else AF.Low &= 0xEF; + AF.Low &= 0xD7; + AF.Low |= (byte)(AF.High & 0x28); + return 4; + case 0x40: return 4; // LD B, B + case 0x41: BC.High = BC.Low; return 4; + case 0x42: BC.High = DE.High; return 4; + case 0x43: BC.High = DE.Low; return 4; + case 0x44: BC.High = HL.High; return 4; + case 0x45: BC.High = HL.Low; return 4; + case 0x46: BC.High = ReadMemory(HL.Word); return 7; + case 0x47: BC.High = AF.High; return 4; + + // --- LD C, r --- + case 0x48: BC.Low = BC.High; return 4; + case 0x49: return 4; + case 0x4A: BC.Low = DE.High; return 4; + case 0x4B: BC.Low = DE.Low; return 4; + case 0x4C: BC.Low = HL.High; return 4; + case 0x4D: BC.Low = HL.Low; return 4; + case 0x4E: BC.Low = ReadMemory(HL.Word); return 7; + case 0x4F: BC.Low = AF.High; return 4; + + // --- LD D, r --- + case 0x50: DE.High = BC.High; return 4; + case 0x51: DE.High = BC.Low; return 4; + case 0x52: return 4; + case 0x53: DE.High = DE.Low; return 4; + case 0x54: DE.High = HL.High; return 4; + case 0x55: DE.High = HL.Low; return 4; + case 0x56: DE.High = ReadMemory(HL.Word); return 7; + case 0x57: DE.High = AF.High; return 4; + + // --- LD E, r --- + case 0x58: DE.Low = BC.High; return 4; + case 0x59: DE.Low = BC.Low; return 4; + case 0x5A: DE.Low = DE.High; return 4; + case 0x5B: return 4; + case 0x5C: DE.Low = HL.High; return 4; + case 0x5D: DE.Low = HL.Low; return 4; + case 0x5E: DE.Low = ReadMemory(HL.Word); return 7; + case 0x5F: DE.Low = AF.High; return 4; + + // --- LD H, r --- + case 0x60: HL.High = BC.High; return 4; + case 0x61: HL.High = BC.Low; return 4; + case 0x62: HL.High = DE.High; return 4; + case 0x63: HL.High = DE.Low; return 4; + case 0x64: return 4; + case 0x65: HL.High = HL.Low; return 4; + case 0x66: HL.High = ReadMemory(HL.Word); return 7; + case 0x67: HL.High = AF.High; return 4; + + // --- LD L, r --- + case 0x68: HL.Low = BC.High; return 4; + case 0x69: HL.Low = BC.Low; return 4; + case 0x6A: HL.Low = DE.High; return 4; + case 0x6B: HL.Low = DE.Low; return 4; + case 0x6C: HL.Low = HL.High; return 4; + case 0x6D: return 4; + case 0x6E: HL.Low = ReadMemory(HL.Word); return 7; + case 0x6F: HL.Low = AF.High; return 4; + + // --- LD (HL), r --- + case 0x70: WriteMemory(HL.Word, BC.High); return 7; + case 0x71: WriteMemory(HL.Word, BC.Low); return 7; + case 0x72: WriteMemory(HL.Word, DE.High); return 7; + case 0x73: WriteMemory(HL.Word, DE.Low); return 7; + case 0x74: WriteMemory(HL.Word, HL.High); return 7; + case 0x75: WriteMemory(HL.Word, HL.Low); return 7; + case 0x76: //HALT + if (!InterruptRequested) + { + PC--; + return 4; + } + else + { + InterruptRequested = false; + return 4; + } + case 0x77: WriteMemory(HL.Word, AF.High); return 7; + + // --- LD A, r --- + case 0x78: AF.High = BC.High; return 4; + case 0x79: AF.High = BC.Low; return 4; + case 0x7A: AF.High = DE.High; return 4; + case 0x7B: AF.High = DE.Low; return 4; + case 0x7C: AF.High = HL.High; return 4; + case 0x7D: AF.High = HL.Low; return 4; + case 0x7E: AF.High = ReadMemory(HL.Word); return 7; + case 0x7F: return 4; + case 0x80: AddA(BC.High); return 4; // ADD A, B + case 0x81: AddA(BC.Low); return 4; // ADD A, C + case 0x82: AddA(DE.High); return 4; // ADD A, D + case 0x83: AddA(DE.Low); return 4; // ADD A, E + case 0x84: AddA(HL.High); return 4; // ADD A, H + case 0x85: AddA(HL.Low); return 4; // ADD A, L + case 0x86: AddA(ReadMemory(HL.Word)); return 7; // ADD A, (HL) + case 0x87: AddA(AF.High); return 4; // ADD A, A + + // --- ADC A, Register Family --- + case 0x88: AdcA(BC.High); return 4; // ADC A, B + case 0x89: AdcA(BC.Low); return 4; // ADC A, C + case 0x8A: AdcA(DE.High); return 4; // ADC A, D + case 0x8B: AdcA(DE.Low); return 4; // ADC A, E + case 0x8C: AdcA(HL.High); return 4; // ADC A, H + case 0x8D: AdcA(HL.Low); return 4; // ADC A, L + case 0x8F: AdcA(AF.High); return 4; // ADC A, A + + case 0x8E: // ADC A, (HL) + AdcA(ReadMemory(HL.Word)); + return 7; + + case 0xCE: // ADC A, n + AdcA(FetchByte()); + return 7; + + // --- SUB A, r --- + case 0x90: SubA(BC.High, false); return 4; + case 0x91: SubA(BC.Low, false); return 4; + case 0x92: SubA(DE.High, false); return 4; + case 0x93: SubA(DE.Low, false); return 4; + case 0x94: SubA(HL.High, false); return 4; + case 0x95: SubA(HL.Low, false); return 4; + case 0x96: SubA(ReadMemory(HL.Word), false); return 7; + case 0x97: SubA(AF.High, false); return 4; + + // --- SBC A, r --- + case 0x98: SbcA(BC.High); return 4; + case 0x99: SbcA(BC.Low); return 4; + case 0x9A: SbcA(DE.High); return 4; + case 0x9B: SbcA(DE.Low); return 4; + case 0x9C: SbcA(HL.High); return 4; + case 0x9D: SbcA(HL.Low); return 4; + case 0x9E: SbcA(ReadMemory(HL.Word)); return 7; + case 0x9F: SbcA(AF.High); return 4; + + 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 + case 0xA3: And(DE.Low); return 4; // AND E + case 0xA4: And(HL.High); return 4; // AND H + case 0xA5: And(HL.Low); return 4; // AND L + case 0xA6: And(ReadMemory(HL.Word)); return 7; // AND (HL) + case 0xA7: And(AF.High); return 4; // AND A + case 0xA8: Xor(BC.High); return 4; // XOR B + case 0xA9: Xor(BC.Low); return 4; // XOR C + case 0xAA: Xor(DE.High); return 4; // XOR D + case 0xAB: Xor(DE.Low); return 4; // XOR E + case 0xAC: Xor(HL.High); return 4; // XOR H + case 0xAD: Xor(HL.Low); return 4; // XOR L + case 0xAE: Xor(ReadMemory(HL.Word)); return 7; // XOR (HL) + case 0xAF: Xor(AF.High); return 4; // XOR A + + // --- OR r --- + case 0xB0: Or(BC.High); return 4; // OR B + case 0xB1: Or(BC.Low); return 4; // OR C + case 0xB2: Or(DE.High); return 4; // OR D + case 0xB3: Or(DE.Low); return 4; // OR E + case 0xB4: Or(HL.High); return 4; // OR H + case 0xB5: Or(HL.Low); return 4; // OR L + case 0xB6: Or(ReadMemory(HL.Word)); return 7; // OR (HL) + case 0xB7: Or(AF.High); return 4; // OR A + + // --- CP r --- + case 0xB8: SubA(BC.High, true); return 4; + case 0xB9: SubA(BC.Low, true); return 4; + case 0xBA: SubA(DE.High, true); return 4; + case 0xBB: SubA(DE.Low, true); return 4; + case 0xBC: SubA(HL.High, true); return 4; + case 0xBD: SubA(HL.Low, true); return 4; + case 0xBE: SubA(ReadMemory(HL.Word), true); return 7; + case 0xBF: SubA(AF.High, true); return 4; + + // --- 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 0xDB: // IN A, (n) + byte portOffsetDB = FetchByte(); + ushort portAddressDB = (ushort)((AF.High << 8) | portOffsetDB); + AF.High = _simpleIoBus.ReadPort(portAddressDB); + return 11; + case 0xE2: // JP PO, nn + { + ushort addr = FetchWord(); + if ((AF.Low & 0x04) == 0) PC = addr; + return 10; + } + case 0xEA: // JP PE, nn + { + ushort addr = FetchWord(); + if ((AF.Low & 0x04) != 0) PC = addr; + return 10; + } + case 0xF2: // JP P, nn + { + ushort addr = FetchWord(); + if ((AF.Low & 0x80) == 0) PC = addr; + return 10; + } + case 0xFA: // JP M, nn + { + 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 + { + ushort addr = FetchWord(); + if ((AF.Low & 0x04) == 0) { Push(PC); PC = addr; return 17; } + return 10; + } + case 0xEC: // CALL PE, nn + { + ushort addr = FetchWord(); + if ((AF.Low & 0x04) != 0) { Push(PC); PC = addr; return 17; } + return 10; + } + case 0xF4: // CALL P, nn + { + ushort addr = FetchWord(); + if ((AF.Low & 0x80) == 0) { Push(PC); PC = addr; return 17; } + return 10; + } + case 0xFC: // CALL M, nn + { + 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; + case 0xC6: // ADD A, n + AddA(FetchByte()); + return 7; + // --- RST Instructions (11 T-States) --- + case 0xC7: Push(PC); PC = 0x0000; return 11; + case 0xCF: Push(PC); PC = 0x0008; return 11; + case 0xD7: Push(PC); PC = 0x0010; return 11; + case 0xDF: Push(PC); PC = 0x0018; return 11; + case 0xE7: Push(PC); PC = 0x0020; return 11; + case 0xEF: Push(PC); PC = 0x0028; return 11; + case 0xF7: Push(PC); PC = 0x0030; return 11; + case 0xFF: Push(PC); PC = 0x0038; return 11; + case 0xC8: // RET Z + if ((AF.Low & 0x40) != 0) + { + PC = Pop(); + return 11; + } + return 5; + case 0xC9: // RET + PC = Pop(); + return 10; + case 0xCB: + return ExecuteCBPrefix(); + case 0xCD: // CALL nn + ushort callAddress = FetchWord(); + Push(PC); + PC = callAddress; + return 17; + case 0xD0: // RET NC + if ((AF.Low & 0x01) == 0) + { + PC = Pop(); + return 11; + } + return 5; + case 0xD1: // POP DE + DE.Word = Pop(); + return 10; + case 0xD3: // OUT (n), A + byte portOffset = FetchByte(); + ushort portAddress = (ushort)((AF.High << 8) | portOffset); + _simpleIoBus.WritePort(portAddress, AF.High); + return 11; + case 0xd5: //push de + Push(DE.Word); + return 11; + case 0xD6: // SUB n + SubA(FetchByte(), false); + return 7; + case 0xD8: // RET C + if ((AF.Low & 0x01) != 0) + { + PC = Pop(); + return 11; + } + return 5; + case 0xD9: // EXX + ushort tempBC = BC.Word; + BC.Word = BC_Prime.Word; + BC_Prime.Word = tempBC; + + ushort tempDE = DE.Word; + DE.Word = DE_Prime.Word; + DE_Prime.Word = tempDE; + + ushort tempHL = HL.Word; + HL.Word = HL_Prime.Word; + HL_Prime.Word = tempHL; + + return 4; + case 0xDD: + return ExecuteDDPrefix(); + case 0xDE: // SBC A, n + SbcA(FetchByte()); + return 7; + case 0xE1: // POP HL + HL.Word = Pop(); + return 10; + case 0xE3: // EX (SP), HL + byte spLow = ReadMemory(SP); + byte spHigh = ReadMemory((ushort)(SP + 1)); + + WriteMemory(SP, HL.Low); + WriteMemory((ushort)(SP + 1), HL.High); + + HL.Low = spLow; + HL.High = spHigh; + + return 19; + case 0xe5: //push hl + Push(HL.Word); + return 11; + case 0xE6: // AND n + And(FetchByte()); + return 7; + case 0xE9: // JP (HL) + PC = HL.Word; + return 4; + case 0xEB: // EX DE, HL + ushort tempEx = DE.Word; + DE.Word = HL.Word; + HL.Word = tempEx; + return 4; + case 0xED: + return ExecuteExtendedPrefix(); + case 0xEE: // XOR n + Xor(FetchByte()); + return 7; + case 0xF1: // POP AF + AF.Word = Pop(); + return 10; + case 0xF3: // DI + IFF1 = false; + IFF2 = false; + return 4; + case 0xf5: //push af + Push(AF.Word); + return 11; + case 0xF6: // OR n + Or(FetchByte()); + return 7; + case 0xF9: // LD SP, HL + SP = HL.Word; + return 6; + case 0xFB: // EI + IFF1 = true; + IFF2 = true; + return 4; + case 0xFD: + return ExecuteFDPrefix(); + case 0xFE: // CP n + SubA(FetchByte(), true); + return 7; + default: + throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); + } + } + + private int ExecuteExtendedPrefix() //ED + { + byte extendedOpcode = FetchByte(); + //byte val = 0; + + switch (extendedOpcode) + { + case 0x43: // LD (nn), BC + ushort dest43 = FetchWord(); + WriteMemory(dest43, BC.Low); + WriteMemory((ushort)(dest43 + 1), BC.High); + return 20; + case 0x44: // NEG + { + int aBefore = AF.High; + int resultNeg = 0 - aBefore; + + byte flagsNeg = 0; + + if ((resultNeg & 0x80) != 0) flagsNeg |= 0x80; + if ((resultNeg & 0xFF) == 0) flagsNeg |= 0x40; + if ((0 - (aBefore & 0x0F)) < 0) flagsNeg |= 0x10; + if (aBefore == 0x80) flagsNeg |= 0x04; + flagsNeg |= 0x02; + if (aBefore != 0) flagsNeg |= 0x01; + + flagsNeg |= (byte)(resultNeg & 0x28); + + AF.Low = flagsNeg; + AF.High = (byte)resultNeg; + + return 8; + } + case 0x47: // LD I, A + I = AF.High; + return 9; + case 0x4B: // LD BC, (nn) + ushort src4B = FetchWord(); + BC.Low = ReadMemory(src4B); + BC.High = ReadMemory((ushort)(src4B + 1)); + return 20; + case 0x4D: // RETI Does not affect IFF1 or IFF2 + PC = Pop(); + return 14; + case 0x4F: // LD R, A + R = AF.High; + return 9; + case 0x51: // OUT (C), D + // BC.Word goes to the address bus, D (DE.High) goes to the data bus + _simpleIoBus.WritePort(BC.Word, DE.High); + return 12; // 2 M-cycles, 12 T-States + case 0x53: // LD (nn), DE + ushort dest53 = FetchWord(); + WriteMemory(dest53, DE.Low); + WriteMemory((ushort)(dest53 + 1), DE.High); + return 20; + case 0x56: // IM 1 + InterruptMode = 1; + return 8; + case 0x58: // IN E, (C) + byte inVal58 = ReadPort(BC.Word); + DE.Low = inVal58; + + byte flags58 = (byte)(AF.Low & 0x01); + if ((inVal58 & 0x80) != 0) flags58 |= 0x80; + if (inVal58 == 0) flags58 |= 0x40; + flags58 |= ParityTable[inVal58]; + flags58 |= (byte)(inVal58 & 0x28); + + AF.Low = flags58; + return 12; + case 0x59: // OUT (C), E + // BC.Word goes to the address bus, E (DE.Low) goes to the data bus + _simpleIoBus.WritePort(BC.Word, DE.Low); + return 12; // 2 M-cycles, 12 T-States + case 0x5B: // LD DE, (nn) + ushort src5B = FetchWord(); + DE.Low = ReadMemory(src5B); + DE.High = ReadMemory((ushort)(src5B + 1)); + return 20; + case 0x5E: // IM 2 + InterruptMode = 2; + return 8; + case 0x5F: // LD A, R + { + AF.High = R; + byte flags5F = (byte)(AF.Low & 0x01); + if ((AF.High & 0x80) != 0) flags5F |= 0x80; + if (AF.High == 0) flags5F |= 0x40; + if (IFF2) flags5F |= 0x04; + flags5F |= (byte)(AF.High & 0x28); + AF.Low = flags5F; + return 9; + } + case 0x67: // RRD (Rotate Right Decimal) + { + // 1. Fetch the operands + byte memVal = ReadMemory(HL.Word); + byte aVal = AF.High; + + // 2. Extract the three 4-bit nibbles we are rotating + byte aLow = (byte)(aVal & 0x0F); + byte memHigh = (byte)(memVal >> 4); + byte memLow = (byte)(memVal & 0x0F); + + // 3. Perform the Right Rotation + // The old Accumulator low nibble goes to the top of memory. + // The old top of memory goes to the bottom of memory. + byte newMemVal = (byte)((aLow << 4) | memHigh); + + // The old bottom of memory goes to the Accumulator low nibble. + // The Accumulator high nibble is completely untouched. + byte newAVal = (byte)((aVal & 0xF0) | memLow); + + // 4. Write the results back + WriteMemory(HL.Word, newMemVal); + AF.High = newAVal; + + // 5. Update Flags + // Carry (Bit 0) is PRESERVED. Half-Carry (Bit 4) and Subtract (Bit 1) are RESET. + byte flags = (byte)(AF.Low & 0x01); + + if ((newAVal & 0x80) != 0) flags |= 0x80; // S: Set if A is negative + if (newAVal == 0) flags |= 0x40; // Z: Set if A is zero + flags |= ParityTable[newAVal]; // P/V: Parity of A + flags |= (byte)(newAVal & 0x28); // Undocumented bits 3 and 5 copy from A + + AF.Low = flags; + return 18; // 18 T-States + } + + case 0x6F: // RLD (Rotate Left Decimal) + { + // 1. Fetch the operands + byte memVal = ReadMemory(HL.Word); + byte aVal = AF.High; + + // 2. Extract the three 4-bit nibbles we are rotating + byte aLow = (byte)(aVal & 0x0F); + byte memHigh = (byte)(memVal >> 4); + byte memLow = (byte)(memVal & 0x0F); + + // 3. Perform the Left Rotation + // The old bottom of memory goes to the top of memory. + // The old Accumulator low nibble goes to the bottom of memory. + byte newMemVal = (byte)((memLow << 4) | aLow); + + // The old top of memory goes to the Accumulator low nibble. + // The Accumulator high nibble is completely untouched. + byte newAVal = (byte)((aVal & 0xF0) | memHigh); + + // 4. Write the results back + WriteMemory(HL.Word, newMemVal); + AF.High = newAVal; + + // 5. Update Flags + // Carry (Bit 0) is PRESERVED. Half-Carry (Bit 4) and Subtract (Bit 1) are RESET. + byte flags = (byte)(AF.Low & 0x01); + + if ((newAVal & 0x80) != 0) flags |= 0x80; // S: Set if A is negative + if (newAVal == 0) flags |= 0x40; // Z: Set if A is zero + flags |= ParityTable[newAVal]; // P/V: Parity of A + flags |= (byte)(newAVal & 0x28); // Undocumented bits 3 and 5 copy from A + + AF.Low = flags; + return 18; // 18 T-States + } + // --- SBC HL, rr --- + case 0x42: SbcHl(BC.Word); return 15; + case 0x45: // RETN (Return from NMI) + IFF1 = IFF2; // Restore the interrupt state + PC = Pop(); // Jump back to where we came from + return 14; + case 0x52: SbcHl(DE.Word); return 15; + case 0x62: SbcHl(HL.Word); return 15; + case 0x72: SbcHl(SP); return 15; + case 0x73: // LD (nn), SP + ushort dest73 = FetchWord(); + WriteMemory(dest73, (byte)SP); + WriteMemory((ushort)(dest73 + 1), (byte)(SP >> 8)); + return 20; + case 0x78: // IN A, (C) + byte portVal78 = ReadPort(BC.Word); + AF.High = portVal78; + + byte flags78 = (byte)(AF.Low & 0x01); + if ((portVal78 & 0x80) != 0) flags78 |= 0x80; + if (portVal78 == 0) flags78 |= 0x40; + flags78 |= ParityTable[portVal78]; + flags78 |= (byte)(portVal78 & 0x28); + + AF.Low = flags78; + return 12; + case 0x79: // OUT (C), A + _simpleIoBus.WritePort(BC.Word, AF.High); + return 12; + // --- ADC HL, rr --- + case 0x4A: Adc16(BC.Word); return 15; + case 0x5A: Adc16(DE.Word); return 15; + case 0x6A: Adc16(HL.Word); return 15; + case 0x7A: Adc16(SP); return 15; + case 0x7B: // LD SP, (nn) + byte addrLow = FetchByte(); + byte addrHigh = FetchByte(); + ushort address7B = (ushort)((addrHigh << 8) | addrLow); + + byte spLow = ReadMemory(address7B); + byte spHigh = ReadMemory((ushort)(address7B + 1)); + + SP = (ushort)((spHigh << 8) | spLow); + return 20; + // --- BLOCK LOADS --- + case 0xA0: // LDI + { + byte val0 = ReadMemory(HL.Word); + WriteMemory(DE.Word, val0); + HL.Word++; DE.Word++; BC.Word--; + + AF.Low &= 0xC1; // Preserve S, Z, C. Wipe H, N. + if (BC.Word != 0) AF.Low |= 0x04; // P/V + + byte n = (byte)(AF.High + val0); + AF.Low |= (byte)(n & 0x08); // Bit 3 + if ((n & 0x02) != 0) AF.Low |= 0x20; // Bit 5 from bit 1 + return 16; + } + case 0xB0: // LDIR + { + byte val00 = ReadMemory(HL.Word); + WriteMemory(DE.Word, val00); + HL.Word++; DE.Word++; BC.Word--; + + AF.Low &= 0xC1; + if (BC.Word != 0) + { + AF.Low |= 0x04; + PC -= 2; // Loop + + byte n1 = (byte)(AF.High + val00); + AF.Low |= (byte)(n1 & 0x08); + if ((n1 & 0x02) != 0) AF.Low |= 0x20; + return 21; + } + byte n2 = (byte)(AF.High + val00); + AF.Low |= (byte)(n2 & 0x08); + if ((n2 & 0x02) != 0) AF.Low |= 0x20; + return 16; + } + case 0xA1: // CPI + { + byte memVal = ReadMemory(HL.Word); + byte result = (byte)(AF.High - memVal); + HL.Word++; BC.Word--; + + byte flags = (byte)(AF.Low & 0x01); // Preserve Carry + flags |= 0x02; // N is 1 + if (BC.Word != 0) flags |= 0x04; + if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; + if (result == 0) flags |= 0x40; + if ((result & 0x80) != 0) flags |= 0x80; + + byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); + flags |= (byte)(n & 0x08); + if ((n & 0x02) != 0) flags |= 0x20; // Bit 5 from Bit 1 + + AF.Low = flags; + return 16; + } + case 0xB1: // CPIR + { + byte memVal = ReadMemory(HL.Word); + byte result = (byte)(AF.High - memVal); + HL.Word++; BC.Word--; + + byte flags = (byte)(AF.Low & 0x01); + flags |= 0x02; + if (BC.Word != 0) flags |= 0x04; + if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; + if (result == 0) flags |= 0x40; + if ((result & 0x80) != 0) flags |= 0x80; + + byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); + flags |= (byte)(n & 0x08); + if ((n & 0x02) != 0) flags |= 0x20; + + AF.Low = flags; + if (BC.Word != 0 && result != 0) { PC -= 2; return 21; } + return 16; + } + case 0xA8: // LDD + { + byte val8 = ReadMemory(HL.Word); + WriteMemory(DE.Word, val8); + HL.Word--; DE.Word--; BC.Word--; + + AF.Low &= 0xC1; + if (BC.Word != 0) AF.Low |= 0x04; + + byte n = (byte)(AF.High + val8); + AF.Low |= (byte)(n & 0x08); + if ((n & 0x02) != 0) AF.Low |= 0x20; + return 16; + } + case 0xA9: // CPD + { + byte memVal = ReadMemory(HL.Word); + byte result = (byte)(AF.High - memVal); + HL.Word--; BC.Word--; // Decrement HL + + byte flags = (byte)(AF.Low & 0x01); + flags |= 0x02; + if (BC.Word != 0) flags |= 0x04; + if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; + if (result == 0) flags |= 0x40; + if ((result & 0x80) != 0) flags |= 0x80; + + byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); + flags |= (byte)(n & 0x08); + if ((n & 0x02) != 0) flags |= 0x20; + + AF.Low = flags; + return 16; + } + case 0xAB: // OUTD + { + // 1. Read the byte from memory at the HL address (Applying Wait States!) + byte outdVal = ReadMemory(HL.Word); + + // 2. Decrement the B register + BC.High--; + + // 3. Output that byte to the hardware port stored in BC + _simpleIoBus.WritePort(BC.Word, outdVal); + + // 4. Decrement HL + HL.Word--; + + // 5. Update Flags + // The N flag (Bit 1) is always set. + AF.Low |= 0x02; + + // The Z flag (Bit 6) is set if B reaches 0, otherwise cleared. + if (BC.High == 0) + AF.Low |= 0x40; + else + AF.Low &= 0xBF; + + return 16; // Takes 16 T-States + } + case 0xB9: // CPDR + { + byte memVal = ReadMemory(HL.Word); + byte result = (byte)(AF.High - memVal); + HL.Word--; BC.Word--; + + byte flags = (byte)(AF.Low & 0x01); + flags |= 0x02; + if (BC.Word != 0) flags |= 0x04; + if (((AF.High ^ memVal ^ result) & 0x10) != 0) flags |= 0x10; + if (result == 0) flags |= 0x40; + if ((result & 0x80) != 0) flags |= 0x80; + + byte n = (byte)(AF.High - memVal - ((flags & 0x10) != 0 ? 1 : 0)); + flags |= (byte)(n & 0x08); + if ((n & 0x02) != 0) flags |= 0x20; + + AF.Low = flags; + if (BC.Word != 0 && result != 0) { PC -= 2; return 21; } + return 16; + } + case 0xB8: // LDDR + { + byte val88 = ReadMemory(HL.Word); + WriteMemory(DE.Word, val88); + HL.Word--; DE.Word--; BC.Word--; + + AF.Low &= 0xC1; + if (BC.Word != 0) + { + AF.Low |= 0x04; + PC -= 2; // Loop + + byte n1 = (byte)(AF.High + val88); + AF.Low |= (byte)(n1 & 0x08); + if ((n1 & 0x02) != 0) AF.Low |= 0x20; + return 21; + } + byte n2 = (byte)(AF.High + val88); + AF.Low |= (byte)(n2 & 0x08); + if ((n2 & 0x02) != 0) AF.Low |= 0x20; + return 16; + } + default: + throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); + } + } + + private int ExecuteCBPrefix() + { + byte cbOpcode = FetchByte(); + //bool oldCarry = false; + + int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET + int bitIndex = (cbOpcode >> 3) & 0x07; + int regIndex = cbOpcode & 0x07; + + byte bitMask = (byte)(1 << bitIndex); + + // --- PHASE 1: Fetch the target value --- + byte val = 0; + switch (regIndex) + { + case 0: val = BC.High; break; + case 1: val = BC.Low; break; + case 2: val = DE.High; break; + case 3: val = DE.Low; break; + case 4: val = HL.High; break; + case 5: val = HL.Low; break; + case 6: val = ReadMemory(HL.Word); break; // The 0x110 (HL) exception + case 7: val = AF.High; break; + } + + // --- PHASE 2: Perform the bitwise math --- + switch (operation) + { + case 0: // ALL Shift/Rotate Instructions + int shiftType = (cbOpcode >> 3) & 0x07; + + // Pass the value to the helper, which handles all the math and flags + val = PerformShift(shiftType, val); + + break; // Break to proceed to PHASE 3 and write the value back! + case 1: // ALL BIT Instructions + AF.Low &= 0x01; // Preserve ONLY Carry + AF.Low |= 0x10; // Set Half-Carry + + if ((val & bitMask) == 0) + { + AF.Low |= 0x44; // Set Zero and P/V + } + else if (bitIndex == 7) + { + AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign + } + + // --- ZEXALL Strict Undocumented Bits --- + // For (HL) memory tests, bits 3/5 come from the High byte of the address (HL.High). + // For all standard registers, bits 3/5 come from the register itself (val). + byte undocumented = (regIndex == 6) ? HL.High : val; + AF.Low |= (byte)(undocumented & 0x28); + return (regIndex == 6) ? 12 : 8; + case 2: // ALL RES Instructions + val &= (byte)(~bitMask); + break; + + case 3: // ALL SET Instructions + val |= bitMask; + break; + default: + throw new Exception("Invalid CB operation."); + } + + // --- PHASE 3: Write back the modified value --- + switch (regIndex) + { + case 0: BC.High = val; break; + case 1: BC.Low = val; break; + case 2: DE.High = val; break; + case 3: DE.Low = val; break; + case 4: HL.High = val; break; + case 5: HL.Low = val; break; + case 6: WriteMemory(HL.Word, val); break; + case 7: AF.High = val; break; + } + + return (regIndex == 6) ? 15 : 8; + } + + private int ExecuteDDPrefix() + { + byte ddOpcode = FetchByte(); + + switch (ddOpcode) + { + case 0x09: Add16(ref IX, BC.Word); return 15; + case 0x19: Add16(ref IX, DE.Word); return 15; + case 0x29: Add16(ref IX, IX.Word); return 15; + case 0x39: Add16(ref IX, SP); return 15; + case 0x21: // LD IX, nn + byte low = FetchByte(); + byte high = FetchByte(); + IX.Word = (ushort)((high << 8) | low); + return 14; + case 0x22: // LD (nn), IX + byte addrLow22 = FetchByte(); + byte addrHigh22 = FetchByte(); + ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22); + WriteMemory(address22, IX.Low); + WriteMemory((ushort)(address22 + 1), IX.High); + return 20; + case 0x23: // INC IX + IX.Word++; + return 10; + case 0x24: // INC IXH + IX.High = Inc8(IX.High); + return 8; + case 0x25: // DEC IXH + IX.High = Dec8(IX.High); + return 8; + case 0x26: // LD IXH, n + IX.High = FetchByte(); + return 11; + case 0x2A: // LD IX, (nn) + byte addrLow2A = FetchByte(); + byte addrHigh2A = FetchByte(); + ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A); + byte ixLow = ReadMemory(address2A); + byte ixHigh = ReadMemory((ushort)(address2A + 1)); + IX.Word = (ushort)((ixHigh << 8) | ixLow); + return 20; + case 0x2B: // DEC IX + IX.Word--; + return 10; + case 0x2C: // INC IXL + IX.Low = Inc8(IX.Low); + return 8; + case 0x2D: // DEC IXL + IX.Low = Dec8(IX.Low); + return 8; + case 0x2E: // LD IXL, n + IX.Low = FetchByte(); + return 11; + case 0x34: // INC (IX+d) + { + sbyte offset34 = (sbyte)FetchByte(); + ushort address34 = (ushort)(IX.Word + offset34); + byte val34 = ReadMemory(address34); + byte result34 = Inc8(val34); + WriteMemory(address34, result34); + return 23; + } + case 0x35: // DEC (IX+d) + { + sbyte offset35 = (sbyte)FetchByte(); + ushort address35 = (ushort)(IX.Word + offset35); + byte val35 = ReadMemory(address35); + byte result35 = Dec8(val35); + WriteMemory(address35, result35); + return 23; + } + case 0x36: // LD (IX+d), n + sbyte offset36 = (sbyte)FetchByte(); + byte n36 = FetchByte(); + ushort address36 = (ushort)(IX.Word + offset36); + WriteMemory(address36, n36); + return 19; + case 0x46: // LD B, (IX+d) + sbyte offset46 = (sbyte)FetchByte(); + ushort address46 = (ushort)(IX.Word + offset46); + BC.High = ReadMemory(address46); + return 19; + case 0x4E: // LD C, (IX+d) + sbyte offset4E = (sbyte)FetchByte(); + ushort address4E = (ushort)(IX.Word + offset4E); + BC.Low = ReadMemory(address4E); + return 19; + case 0x56: // LD D, (IX+d) + sbyte offset56 = (sbyte)FetchByte(); + ushort address56 = (ushort)(IX.Word + offset56); + DE.High = ReadMemory(address56); + return 19; + case 0x5E: // LD E, (IX+d) + sbyte offset5E = (sbyte)FetchByte(); + ushort address5E = (ushort)(IX.Word + offset5E); + DE.Low = ReadMemory(address5E); + return 19; + case 0x66: // LD H, (IX+d) + sbyte offset66 = (sbyte)FetchByte(); + ushort address66 = (ushort)(IX.Word + offset66); + HL.High = ReadMemory(address66); + return 19; + case 0x67: // LD IXH, A + IX.High = AF.High; + return 8; + case 0x6E: // LD L, (IX+d) + sbyte offset6E = (sbyte)FetchByte(); + ushort address6E = (ushort)(IX.Word + offset6E); + HL.Low = ReadMemory(address6E); + return 19; + case 0x6F: // LD IXL, A + IX.Low = AF.High; + return 8; + case 0x70: // LD (IX+d), B + sbyte offset70 = (sbyte)FetchByte(); + ushort address70 = (ushort)(IX.Word + offset70); + WriteMemory(address70, BC.High); + return 19; + case 0x71: // LD (IX+d), C + sbyte offset71 = (sbyte)FetchByte(); + ushort address71 = (ushort)(IX.Word + offset71); + WriteMemory(address71, BC.Low); + return 19; + case 0x72: // LD (IX+d), D + sbyte offset72 = (sbyte)FetchByte(); + ushort address72 = (ushort)(IX.Word + offset72); + WriteMemory(address72, DE.High); + return 19; + case 0x73: // LD (IX+d), E + sbyte offset73 = (sbyte)FetchByte(); + ushort address73 = (ushort)(IX.Word + offset73); + WriteMemory(address73, DE.Low); + return 19; + case 0x74: // LD (IX+d), H + sbyte offset74 = (sbyte)FetchByte(); + ushort address74 = (ushort)(IX.Word + offset74); + WriteMemory(address74, HL.High); + return 19; + case 0x75: // LD (IX+d), L + sbyte offset75 = (sbyte)FetchByte(); + ushort address75 = (ushort)(IX.Word + offset75); + WriteMemory(address75, HL.Low); + return 19; + case 0x77: // LD (IX+d), A + sbyte offset77 = (sbyte)FetchByte(); + ushort address77 = (ushort)(IX.Word + offset77); + WriteMemory(address77, AF.High); + return 19; + case 0x7C: // LD A, IXH + AF.High = IX.High; + return 8; + case 0x7D: // LD A, IXL + AF.High = IX.Low; + return 8; + case 0x7E: // LD A, (IX+d) + sbyte offset7E = (sbyte)FetchByte(); + ushort address7E = (ushort)(IX.Word + offset7E); + AF.High = ReadMemory(address7E); + return 19; + case 0x84: // ADD A, IXH + AddA(IX.High); + return 8; + case 0x85: // ADD A, IXL + AddA(IX.Low); + return 8; + case 0x86: // ADD A, (IX+d) + { + sbyte displacementAdd = (sbyte)FetchByte(); + ushort targetAddressAdd = (ushort)(IX.Word + displacementAdd); + AddA(ReadMemory(targetAddressAdd)); + return 19; + } + case 0x8E: // ADC A, (IX+d) + { + sbyte offset8E = (sbyte)FetchByte(); + ushort address8E = (ushort)(IX.Word + offset8E); + AdcA(ReadMemory(address8E)); + return 19; + } + case 0x9E: // SBC A, (IX+d) + { + sbyte offset9E = (sbyte)FetchByte(); + ushort address9E = (ushort)(IX.Word + offset9E); + SbcA(ReadMemory(address9E)); + return 19; + } + case 0xA6: // AND (IX+d) + { + sbyte offsetA6 = (sbyte)FetchByte(); + ushort addressA6 = (ushort)(IX.Word + offsetA6); + And(ReadMemory(addressA6)); + return 19; + } + case 0xAE: // XOR (IX+d) + { + sbyte offsetAE = (sbyte)FetchByte(); + ushort addressAE = (ushort)(IX.Word + offsetAE); + Xor(ReadMemory(addressAE)); + return 19; + } + // --- UNDOCUMENTED IX ALU OPERATIONS --- + case 0x8C: AdcA(IX.High); return 8; + case 0x8D: AdcA(IX.Low); return 8; + case 0x94: SubA(IX.High, false); return 8; + case 0x95: SubA(IX.Low, false); return 8; + case 0x9C: SbcA(IX.High); return 8; + case 0x9D: SbcA(IX.Low); return 8; + case 0xA4: And(IX.High); return 8; + case 0xA5: And(IX.Low); return 8; + case 0xAC: Xor(IX.High); return 8; + case 0xAD: Xor(IX.Low); return 8; + case 0xB4: Or(IX.High); return 8; + case 0xB5: Or(IX.Low); return 8; + case 0xBC: SubA(IX.High, true); return 8; + case 0xBD: SubA(IX.Low, true); return 8; + + // --- UNDOCUMENTED IX LOAD OPERATIONS --- + case 0x44: BC.High = IX.High; return 8; // LD B, IXH + case 0x45: BC.High = IX.Low; return 8; // LD B, IXL + case 0x4C: BC.Low = IX.High; return 8; // LD C, IXH + case 0x4D: BC.Low = IX.Low; return 8; // LD C, IXL + case 0x54: DE.High = IX.High; return 8; // LD D, IXH + case 0x55: DE.High = IX.Low; return 8; // LD D, IXL + case 0x5C: DE.Low = IX.High; return 8; // LD E, IXH + case 0x5D: DE.Low = IX.Low; return 8; // LD E, IXL + case 0x60: IX.High = BC.High; return 8; // LD IXH, B + case 0x61: IX.High = BC.Low; return 8; // LD IXH, C + case 0x62: IX.High = DE.High; return 8; // LD IXH, D + case 0x63: IX.High = DE.Low; return 8; // LD IXH, E + case 0x64: return 8; // LD IXH, IXH + case 0x65: IX.High = IX.Low; return 8; // LD IXH, IXL + case 0x68: IX.Low = BC.High; return 8; // LD IXL, B + case 0x69: IX.Low = BC.Low; return 8; // LD IXL, C + case 0x6A: IX.Low = DE.High; return 8; // LD IXL, D + case 0x6B: IX.Low = DE.Low; return 8; // LD IXL, E + case 0x6C: IX.Low = IX.High; return 8; // LD IXL, IXH + case 0x6D: return 8; // LD IXL, IXL + case 0x96: // SUB (IX+d) + { + sbyte offset96 = (sbyte)FetchByte(); + ushort address96 = (ushort)(IX.Word + offset96); + SubA(ReadMemory(address96), false); + return 19; + } + case 0xB6: // OR (IX+d) + { + sbyte offsetB6 = (sbyte)FetchByte(); + ushort addressB6 = (ushort)(IX.Word + offsetB6); + + // Read the memory and pass it straight into your helper + Or(ReadMemory(addressB6)); + + return 19; // Takes 19 T-States + } + case 0xBE: // CP (IX+d) + { + sbyte offsetBE = (sbyte)FetchByte(); + ushort addressBE = (ushort)(IX.Word + offsetBE); + SubA(ReadMemory(addressBE), true); + return 19; + } + case 0xCB: // The DD CB nested prefix + { + sbyte displacement = (sbyte)FetchByte(); + byte cbOpcode = FetchByte(); + ushort targetAddress = (ushort)(IX.Word + displacement); + byte memVal = ReadMemory(targetAddress); + + int operation = cbOpcode >> 6; + int bitIndex = (cbOpcode >> 3) & 0x07; + byte bitMask = (byte)(1 << bitIndex); + int regIndex = cbOpcode & 0x07; + + switch (operation) + { + case 0: // ALL Shift/Rotate Instructions + int shiftType = (cbOpcode >> 3) & 0x07; + + // Perform the shift and flag math + memVal = PerformShift(shiftType, memVal); + WriteMemory(targetAddress, memVal); + + // Z80 UNDOCUMENTED QUIRK: + // DDCB and FDCB shift instructions ALSO copy the result into a standard register + // unless the target register index is 6 (which is purely memory). + if (regIndex != 6) + { + switch (regIndex) + { + case 0: BC.High = memVal; break; + case 1: BC.Low = memVal; break; + case 2: DE.High = memVal; break; + case 3: DE.Low = memVal; break; + case 4: HL.High = memVal; break; + case 5: HL.Low = memVal; break; + case 7: AF.High = memVal; break; + } + } + return 23; + case 1: // ALL BIT Instructions + AF.Low &= 0x01; + AF.Low |= 0x10; + + if ((memVal & bitMask) == 0) + { + AF.Low |= 0x44; + } + else if (bitIndex == 7) + { + AF.Low |= 0x80; + } + return 20; + + case 2: // ALL RES Instructions + memVal &= (byte)(~bitMask); + WriteMemory(targetAddress, memVal); + return 23; + + case 3: // ALL SET Instructions + memVal |= bitMask; + WriteMemory(targetAddress, memVal); + return 23; + + default: + throw new Exception("Invalid bitwise operation."); + } + } + case 0xE1: // POP IX + byte popLow = ReadMemory(SP); + SP++; + byte popHigh = ReadMemory(SP); + SP++; + IX.Word = (ushort)((popHigh << 8) | popLow); + return 14; + case 0xE3: // EX (SP), IX + byte spLowIX = ReadMemory(SP); + byte spHighIX = ReadMemory((ushort)(SP + 1)); + WriteMemory(SP, IX.Low); + WriteMemory((ushort)(SP + 1), IX.High); + IX.Low = spLowIX; + IX.High = spHighIX; + return 23; + case 0xE5: // PUSH IX + SP--; + WriteMemory(SP, IX.High); + SP--; + WriteMemory(SP, IX.Low); + return 15; + case 0xE9: // JP (IX) + PC = IX.Word; + return 8; + case 0xF9: // LD SP, IX + SP = IX.Word; + return 10; + default: + // The Z80 Ignored Prefix Quirk! + // If the instruction doesn't involve IX, ignore the prefix, + // run the base instruction, and charge 4 T-States for the delay. + return ExecuteOpcode(ddOpcode) + 4; + } + } + + private int ExecuteFDPrefix() + { + byte ddOpcode = FetchByte(); + ushort targetAddress = 0; + byte memVal = 0; + + switch (ddOpcode) + { + case 0x09: Add16(ref IY, BC.Word); return 15; + case 0x19: Add16(ref IY, DE.Word); return 15; + case 0x21: // LD IY, nn + IY.Word = FetchWord(); + return 14; + case 0x22: // LD (nn), IY + { + byte addrLow = FetchByte(); + byte addrHigh = FetchByte(); + ushort address = (ushort)((addrHigh << 8) | addrLow); + + WriteMemory(address, IY.Low); + WriteMemory((ushort)(address + 1), IY.High); + + return 20; + } + case 0x23: // INC IY + IY.Word++; + return 10; + case 0x24: // INC IYH + IY.High = Inc8(IY.High); + return 8; + case 0x25: // DEC IYH + IY.High = Dec8(IY.High); + return 8; + case 0x26: // LD IYH, n + IY.High = FetchByte(); + return 11; + case 0x29: Add16(ref IY, IY.Word); return 15; + case 0x2A: // LD IY, (nn) + { + byte addrLow = FetchByte(); + byte addrHigh = FetchByte(); + ushort address = (ushort)((addrHigh << 8) | addrLow); + + IY.Low = ReadMemory(address); + IY.High = ReadMemory((ushort)(address + 1)); + + return 20; + } + case 0x2B: // DEC IY + IY.Word--; + return 10; + case 0x2C: // INC IYL + IY.Low = Inc8(IY.Low); + return 8; + case 0x2D: // DEC IYL + IY.Low = Dec8(IY.Low); + return 8; + case 0x2E: // LD IYL, n + IY.Low = FetchByte(); + return 11; + case 0x34: // INC (IY+d) + { + sbyte offset34 = (sbyte)FetchByte(); + ushort address34 = (ushort)(IY.Word + offset34); + byte valBefore = ReadMemory(address34); + byte result34 = Inc8(valBefore); + WriteMemory(address34, result34); + return 23; + } + case 0x35: // DEC (IY+d) + sbyte offset = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + offset); + memVal = ReadMemory(targetAddress); + byte decVal = Dec8(memVal); + WriteMemory(targetAddress, decVal); + return 23; + case 0x36: // LD (IY+d), n + { + sbyte offset36 = (sbyte)FetchByte(); + byte nValue = FetchByte(); + targetAddress = (ushort)(IY.Word + offset36); + WriteMemory(targetAddress, nValue); + return 19; + } + case 0x39: Add16(ref IY, SP); return 15; + case 0x46: // LD B, (IY+d) + { + sbyte displacement = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + displacement); + BC.High = ReadMemory(targetAddress); + return 19; + } + case 0x4E: // LD C, (IY+d) + sbyte offset4E = (sbyte)FetchByte(); + ushort address4E = (ushort)(IY.Word + offset4E); + BC.Low = ReadMemory(address4E); + return 19; + case 0x56: // LD D, (IY+d) + sbyte offset56 = (sbyte)FetchByte(); + ushort address56 = (ushort)(IY.Word + offset56); + DE.High = ReadMemory(address56); + return 19; + case 0x5E: // LD E, (IY+d) + sbyte offset5E = (sbyte)FetchByte(); + ushort address5E = (ushort)(IY.Word + offset5E); + DE.Low = ReadMemory(address5E); + return 19; + case 0x66: // LD H, (IY+d) + sbyte offset66 = (sbyte)FetchByte(); + ushort address66 = (ushort)(IY.Word + offset66); + HL.High = ReadMemory(address66); + return 19; + case 0x6E: // LD L, (IY+d) + sbyte displacementVal = (sbyte)FetchByte(); + ushort targetAddr = (ushort)(IY.Word + displacementVal); + HL.Low = ReadMemory(targetAddr); + return 19; + case 0x70: // LD (IY+d), B + { + sbyte offset70 = (sbyte)FetchByte(); + ushort address70 = (ushort)(IY.Word + offset70); + WriteMemory(address70, BC.High); + return 19; + } + case 0x71: // LD (IY+d), C + { + sbyte offset71 = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + offset71); + WriteMemory(targetAddress, BC.Low); + return 19; + } + case 0x72: // LD (IY+d), D + sbyte offset72 = (sbyte)FetchByte(); + ushort address72 = (ushort)(IY.Word + offset72); + WriteMemory(address72, DE.High); + return 19; + case 0x73: // LD (IY+d), E + sbyte offset73 = (sbyte)FetchByte(); + ushort address73 = (ushort)(IY.Word + offset73); + WriteMemory(address73, DE.Low); + return 19; + case 0x74: // LD (IY+d), H + sbyte offset74 = (sbyte)FetchByte(); + ushort address74 = (ushort)(IY.Word + offset74); + WriteMemory(address74, HL.High); + return 19; + case 0x75: // LD (IY+d), L + sbyte offset75 = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + offset75); + WriteMemory(targetAddress, HL.Low); + return 19; + case 0x77: // LD (IY+d), A + sbyte offset77 = (sbyte)FetchByte(); + ushort address77 = (ushort)(IY.Word + offset77); + WriteMemory(address77, AF.High); + return 19; + case 0x7E: // LD A, (IY+d) + sbyte offset7E = (sbyte)FetchByte(); + ushort address7E = (ushort)(IY.Word + offset7E); + AF.High = ReadMemory(address7E); + return 19; + case 0x84: // ADD A, IYH + AddA(IY.High); + return 8; + case 0x85: // ADD A, IYL + AddA(IY.Low); + return 8; + case 0x86: // ADD A, (IY+d) + { + sbyte displacementAdd = (sbyte)FetchByte(); + ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd); + byte valueToAdd = ReadMemory(targetAddressAdd); + AddA(valueToAdd); + return 19; + } + case 0x8E: // ADC A, (IY+d) + { + sbyte offset8E = (sbyte)FetchByte(); + ushort address8E = (ushort)(IY.Word + offset8E); + AdcA(ReadMemory(address8E)); + return 19; + } + case 0x9E: // SBC A, (IY+d) + { + sbyte offset9E = (sbyte)FetchByte(); + ushort address9E = (ushort)(IY.Word + offset9E); + SbcA(ReadMemory(address9E)); + return 19; + } + case 0xA6: // AND (IY+d) + { + sbyte offsetA6 = (sbyte)FetchByte(); + ushort addressA6 = (ushort)(IY.Word + offsetA6); + And(ReadMemory(addressA6)); + return 19; + } + case 0xAE: // XOR (IY+d) + { + sbyte offsetAE = (sbyte)FetchByte(); + ushort addressAE = (ushort)(IY.Word + offsetAE); + Xor(ReadMemory(addressAE)); + return 19; + } + // --- UNDOCUMENTED IY ALU OPERATIONS --- + case 0x8C: AdcA(IY.High); return 8; + case 0x8D: AdcA(IY.Low); return 8; + case 0x94: SubA(IY.High, false); return 8; + case 0x95: SubA(IY.Low, false); return 8; + case 0x9C: SbcA(IY.High); return 8; + case 0x9D: SbcA(IY.Low); return 8; + case 0xA4: And(IY.High); return 8; + case 0xA5: And(IY.Low); return 8; + case 0xAC: Xor(IY.High); return 8; + case 0xAD: Xor(IY.Low); return 8; + case 0xB4: Or(IY.High); return 8; + case 0xB5: Or(IY.Low); return 8; + case 0xBC: SubA(IY.High, true); return 8; + case 0xBD: SubA(IY.Low, true); return 8; + + // --- UNDOCUMENTED IY LOAD OPERATIONS --- + case 0x44: BC.High = IY.High; return 8; // LD B, IYH + case 0x45: BC.High = IY.Low; return 8; // LD B, IYL + case 0x4C: BC.Low = IY.High; return 8; // LD C, IYH + case 0x4D: BC.Low = IY.Low; return 8; // LD C, IYL + case 0x54: DE.High = IY.High; return 8; // LD D, IYH + case 0x55: DE.High = IY.Low; return 8; // LD D, IYL + case 0x5C: DE.Low = IY.High; return 8; // LD E, IYH + case 0x5D: DE.Low = IY.Low; return 8; // LD E, IYL + case 0x60: IY.High = BC.High; return 8; // LD IYH, B + case 0x61: IY.High = BC.Low; return 8; // LD IYH, C + case 0x62: IY.High = DE.High; return 8; // LD IYH, D + case 0x63: IY.High = DE.Low; return 8; // LD IYH, E + case 0x64: return 8; // LD IYH, IYH + case 0x65: IY.High = IY.Low; return 8; // LD IYH, IYL + case 0x67: // LD IYH, A + IY.High = AF.High; + return 8; + case 0x6F: // LD IYL, A + IY.Low = AF.High; + return 8; + case 0x7C: // LD A, IYH + AF.High = IY.High; + return 8; + case 0x7D: // LD A, IYL + AF.High = IY.Low; + return 8; + case 0x68: IY.Low = BC.High; return 8; // LD IYL, B + case 0x69: IY.Low = BC.Low; return 8; // LD IYL, C + case 0x6A: IY.Low = DE.High; return 8; // LD IYL, D + case 0x6B: IY.Low = DE.Low; return 8; // LD IYL, E + case 0x6C: IY.Low = IY.High; return 8; // LD IYL, IYH + case 0x6D: return 8; // LD IYL, IYL + case 0x96: // SUB (IY+d) + { + sbyte offset96 = (sbyte)FetchByte(); + ushort address96 = (ushort)(IY.Word + offset96); + SubA(ReadMemory(address96), false); + return 19; + } + case 0xB6: // OR (IY+d) + { + sbyte offsetB6 = (sbyte)FetchByte(); + ushort addressB6 = (ushort)(IY.Word + offsetB6); + + // Read the memory and pass it straight into your helper + Or(ReadMemory(addressB6)); + + return 19; // Takes 19 T-States + } + case 0xBE: // CP (IY+d) + { + sbyte offsetBE = (sbyte)FetchByte(); + ushort addressBE = (ushort)(IY.Word + offsetBE); + SubA(ReadMemory(addressBE), true); + return 19; + } + case 0xCB: // The FD CB nested prefix + { + sbyte displacement = (sbyte)FetchByte(); + byte cbOpcode = FetchByte(); + targetAddress = (ushort)(IY.Word + displacement); + memVal = ReadMemory(targetAddress); + + int operation = cbOpcode >> 6; + int bitIndex = (cbOpcode >> 3) & 0x07; + byte bitMask = (byte)(1 << bitIndex); + int regIndex = cbOpcode & 0x07; + + switch (operation) + { + case 0: // ALL Shift/Rotate Instructions + int shiftType = (cbOpcode >> 3) & 0x07; + + // Perform the shift and flag math + memVal = PerformShift(shiftType, memVal); + WriteMemory(targetAddress, memVal); + + // Z80 UNDOCUMENTED QUIRK: + // DDCB and FDCB shift instructions ALSO copy the result into a standard register + // unless the target register index is 6 (which is purely memory). + if (regIndex != 6) + { + switch (regIndex) + { + case 0: BC.High = memVal; break; + case 1: BC.Low = memVal; break; + case 2: DE.High = memVal; break; + case 3: DE.Low = memVal; break; + case 4: HL.High = memVal; break; + case 5: HL.Low = memVal; break; + case 7: AF.High = memVal; break; + } + } + return 23; + case 1: // ALL BIT Instructions + AF.Low &= 0x01; + AF.Low |= 0x10; + + if ((memVal & bitMask) == 0) + { + AF.Low |= 0x44; + } + else if (bitIndex == 7) + { + AF.Low |= 0x80; + } + return 20; + + case 2: // ALL RES Instructions + memVal &= (byte)(~bitMask); + WriteMemory(targetAddress, memVal); + return 23; + + case 3: // ALL SET Instructions + memVal |= bitMask; + WriteMemory(targetAddress, memVal); + return 23; + + default: + throw new Exception("Invalid bitwise operation."); + } + } + case 0xE1: // POP IY + IY.Low = ReadMemory(SP); + SP++; + IY.High = ReadMemory(SP); + SP++; + return 14; + case 0xE3: // EX (SP), IY + byte spLowIY = ReadMemory(SP); + byte spHighIY = ReadMemory((ushort)(SP + 1)); + WriteMemory(SP, IY.Low); + WriteMemory((ushort)(SP + 1), IY.High); + IY.Low = spLowIY; + IY.High = spHighIY; + return 23; + case 0xE5: // PUSH IY + SP--; + WriteMemory(SP, IY.High); + SP--; + WriteMemory(SP, IY.Low); + return 15; + case 0xF9: // LD SP, IY + SP = IY.Word; + return 10; + default: + // Z80 nonsense loopback bug + return ExecuteOpcode(ddOpcode) + 4; + } + } + } +} diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj new file mode 100644 index 0000000..1d41958 --- /dev/null +++ b/Desktop/Desktop.csproj @@ -0,0 +1,19 @@ + + + + WinExe + net8.0-windows + enable + true + enable + + + + + + + + + + + \ No newline at end of file diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs new file mode 100644 index 0000000..7cf6955 --- /dev/null +++ b/Desktop/Form1.Designer.cs @@ -0,0 +1,39 @@ +namespace Desktop +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form1"; + } + + #endregion + } +} diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs new file mode 100644 index 0000000..3612dfe --- /dev/null +++ b/Desktop/Form1.cs @@ -0,0 +1,10 @@ +namespace Desktop +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + } +} diff --git a/Desktop/Form1.resx b/Desktop/Form1.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Desktop/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Desktop/Program.cs b/Desktop/Program.cs new file mode 100644 index 0000000..804fa5c --- /dev/null +++ b/Desktop/Program.cs @@ -0,0 +1,17 @@ +namespace Desktop +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + } +} \ No newline at end of file diff --git a/ParsonsMasterSystem2026.sln b/ParsonsMasterSystem2026.sln new file mode 100644 index 0000000..8613677 --- /dev/null +++ b/ParsonsMasterSystem2026.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.37216.2 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{B0A741E4-CE3E-4DF0-96B2-E314973F9201}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}.Release|Any CPU.Build.0 = Release|Any CPU + {B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {03E5131A-66F6-496F-89B4-4B2DB21DFF47} + EndGlobalSection +EndGlobal