diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 63193cb..6c765bc 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -602,6 +602,61 @@ namespace Core.Cpu 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 @@ -1348,7 +1403,7 @@ namespace Core.Cpu private int ExecuteExtendedPrefix() //ED { byte extendedOpcode = FetchByte(); - byte val = 0; + //byte val = 0; switch (extendedOpcode) { @@ -1428,6 +1483,79 @@ namespace Core.Cpu 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 0x52: SbcHl(DE.Word); return 15; @@ -1468,73 +1596,161 @@ namespace Core.Cpu SP = (ushort)((spHigh << 8) | spLow); return 20; + // --- BLOCK LOADS --- case 0xA0: // LDI - val = ReadMemory(HL.Word); - WriteMemory(DE.Word, val); + { + byte val0 = ReadMemory(HL.Word); + WriteMemory(DE.Word, val0); + HL.Word++; DE.Word++; BC.Word--; - 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 - AF.Low &= 0xC1; - if (BC.Word != 0) AF.Low |= 0x04; - return 16; + 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 - val = ReadMemory(HL.Word); - WriteMemory(DE.Word, val); - - HL.Word++; - DE.Word++; - BC.Word--; - - AF.Low &= 0xC1; - if (BC.Word != 0) { - AF.Low |= 0x04; - PC -= 2; - return 21; + 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; } - return 16; case 0xB1: // CPIR - byte memValB1 = ReadMemory(HL.Word); - byte resultB1 = (byte)(AF.High - memValB1); - - HL.Word++; - BC.Word--; - - byte currentCarry = (byte)(AF.Low & 0x01); - byte newFlagsB1 = currentCarry; - - newFlagsB1 |= 0x02; - if (BC.Word != 0) newFlagsB1 |= 0x04; - if (((AF.High ^ memValB1 ^ resultB1) & 0x10) != 0) newFlagsB1 |= 0x10; - if (resultB1 == 0) newFlagsB1 |= 0x40; - if ((resultB1 & 0x80) != 0) newFlagsB1 |= 0x80; - - AF.Low = newFlagsB1; - - if (BC.Word != 0 && resultB1 != 0) { - PC -= 2; - return 21; + 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 (The opcode that just crashed!) + { + 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 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; } - return 16; case 0xB8: // LDDR - val = ReadMemory(HL.Word); - WriteMemory(DE.Word, val); - - HL.Word--; - DE.Word--; - BC.Word--; - - AF.Low &= 0xC1; - if (BC.Word != 0) { - AF.Low |= 0x04; - PC -= 2; - return 21; + 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; } - return 16; default: throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); } @@ -1543,7 +1759,7 @@ namespace Core.Cpu private int ExecuteCBPrefix() { byte cbOpcode = FetchByte(); - bool oldCarry = false; + //bool oldCarry = false; int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET int bitIndex = (cbOpcode >> 3) & 0x07; @@ -1568,6 +1784,13 @@ namespace Core.Cpu // --- 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 @@ -1581,9 +1804,12 @@ namespace Core.Cpu AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign } - // Undocumented bits 3 and 5 are generally unpredictable here or take memory values, but keeping it robust for now + // --- 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; @@ -1591,60 +1817,6 @@ namespace Core.Cpu case 3: // ALL SET Instructions val |= bitMask; break; - - case 0: // ALL Shift/Rotate Instructions - int shiftType = (cbOpcode >> 3) & 0x07; - bool carryOut = false; - - 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 - oldCarry = (AF.Low & 0x01) != 0; - carryOut = (val & 0x80) != 0; - val = (byte)((val << 1) | (oldCarry ? 0x01 : 0x00)); - break; - case 3: // RR - oldCarry = (AF.Low & 0x01) != 0; - 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); - val |= signBit; - break; - case 7: // SRL - carryOut = (val & 0x01) != 0; - val = (byte)(val >> 1); - break; - default: - throw new NotImplementedException($"CB Shift instruction type {shiftType} not implemented!"); - } - - // --- Update Flags --- - 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; - break; - default: throw new Exception("Invalid CB operation."); } @@ -1710,6 +1882,9 @@ namespace Core.Cpu 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; @@ -1768,15 +1943,6 @@ namespace Core.Cpu case 0x67: // LD IXH, A IX.High = AF.High; return 8; - case 0x68: // LD IXL, B - IX.Low = BC.High; - return 8; - case 0x69: // LD IXL, C - IX.Low = BC.Low; - return 8; - case 0x6A: // LD IXL, D - IX.Low = DE.High; - return 8; case 0x6E: // LD L, (IX+d) sbyte offset6E = (sbyte)FetchByte(); ushort address6E = (ushort)(IX.Word + offset6E); @@ -1841,6 +2007,71 @@ namespace Core.Cpu 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(); @@ -1848,6 +2079,16 @@ namespace Core.Cpu 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(); @@ -1865,9 +2106,34 @@ namespace Core.Cpu 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; @@ -1892,9 +2158,6 @@ namespace Core.Cpu WriteMemory(targetAddress, memVal); return 23; - case 0: - throw new NotImplementedException($"DD CB Shift/Rotate opcode {cbOpcode:X2} not implemented!"); - default: throw new Exception("Invalid bitwise operation."); } @@ -1906,6 +2169,14 @@ namespace Core.Cpu 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); @@ -1915,28 +2186,77 @@ namespace Core.Cpu case 0xE9: // JP (IX) PC = IX.Word; return 8; + case 0xF9: // LD SP, IX + SP = IX.Word; + return 10; default: - throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} at PC 0x{(PC - 2):X4} not implemented!"); + // 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 opcode = FetchByte(); + byte ddOpcode = FetchByte(); ushort targetAddress = 0; byte memVal = 0; - switch (opcode) + 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(); @@ -2045,6 +2365,71 @@ namespace Core.Cpu 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 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(); @@ -2052,12 +2437,16 @@ namespace Core.Cpu SubA(ReadMemory(address96), false); return 19; } - case 0xA6: // AND (IY+d) - sbyte offsetA6 = (sbyte)FetchByte(); - ushort addressA6 = (ushort)(IY.Word + offsetA6); - byte operandA6 = ReadMemory(addressA6); - And(operandA6); - 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(); @@ -2075,9 +2464,34 @@ namespace Core.Cpu 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; @@ -2102,9 +2516,6 @@ namespace Core.Cpu WriteMemory(targetAddress, memVal); return 23; - case 0: - throw new NotImplementedException($"FD CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!"); - default: throw new Exception("Invalid bitwise operation."); } @@ -2115,14 +2526,26 @@ namespace Core.Cpu 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: - throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!"); + // The Z80 Ignored Prefix Quirk! + return ExecuteOpcode(ddOpcode) + 4; // Note: You named the fetched variable 'opcode' here instead of 'ddOpcode' } } }