All ZEXALL tests now pass!

This commit is contained in:
2026-04-24 00:52:17 +01:00
parent 5892f7e491
commit a8271d30a2

View File

@@ -602,6 +602,61 @@ namespace Core.Cpu
HL.Word = (ushort)(result & 0xFFFF); 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) private void Push(ushort value)
{ {
// High byte goes first // High byte goes first
@@ -1348,7 +1403,7 @@ namespace Core.Cpu
private int ExecuteExtendedPrefix() //ED private int ExecuteExtendedPrefix() //ED
{ {
byte extendedOpcode = FetchByte(); byte extendedOpcode = FetchByte();
byte val = 0; //byte val = 0;
switch (extendedOpcode) switch (extendedOpcode)
{ {
@@ -1428,6 +1483,79 @@ namespace Core.Cpu
AF.Low = flags5F; AF.Low = flags5F;
return 9; 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 --- // --- SBC HL, rr ---
case 0x42: SbcHl(BC.Word); return 15; case 0x42: SbcHl(BC.Word); return 15;
case 0x52: SbcHl(DE.Word); return 15; case 0x52: SbcHl(DE.Word); return 15;
@@ -1468,73 +1596,161 @@ namespace Core.Cpu
SP = (ushort)((spHigh << 8) | spLow); SP = (ushort)((spHigh << 8) | spLow);
return 20; return 20;
// --- BLOCK LOADS ---
case 0xA0: // LDI 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++; AF.Low &= 0xC1; // Preserve S, Z, C. Wipe H, N.
DE.Word++; if (BC.Word != 0) AF.Low |= 0x04; // P/V
BC.Word--;
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; AF.Low &= 0xC1;
if (BC.Word != 0) AF.Low |= 0x04; 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; 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;
} }
return 16; case 0xA9: // CPD (The opcode that just crashed!)
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; byte memVal = ReadMemory(HL.Word);
return 21; 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; 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;
}
case 0xB8: // LDDR case 0xB8: // LDDR
val = ReadMemory(HL.Word); {
WriteMemory(DE.Word, val); byte val88 = ReadMemory(HL.Word);
WriteMemory(DE.Word, val88);
HL.Word--; HL.Word--; DE.Word--; BC.Word--;
DE.Word--;
BC.Word--;
AF.Low &= 0xC1; AF.Low &= 0xC1;
if (BC.Word != 0) if (BC.Word != 0)
{ {
AF.Low |= 0x04; AF.Low |= 0x04;
PC -= 2; PC -= 2; // Loop
byte n1 = (byte)(AF.High + val88);
AF.Low |= (byte)(n1 & 0x08);
if ((n1 & 0x02) != 0) AF.Low |= 0x20;
return 21; 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: default:
throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); 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() private int ExecuteCBPrefix()
{ {
byte cbOpcode = FetchByte(); byte cbOpcode = FetchByte();
bool oldCarry = false; //bool oldCarry = false;
int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET
int bitIndex = (cbOpcode >> 3) & 0x07; int bitIndex = (cbOpcode >> 3) & 0x07;
@@ -1568,6 +1784,13 @@ namespace Core.Cpu
// --- PHASE 2: Perform the bitwise math --- // --- PHASE 2: Perform the bitwise math ---
switch (operation) 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 case 1: // ALL BIT Instructions
AF.Low &= 0x01; // Preserve ONLY Carry AF.Low &= 0x01; // Preserve ONLY Carry
AF.Low |= 0x10; // Set Half-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 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; return (regIndex == 6) ? 12 : 8;
case 2: // ALL RES Instructions case 2: // ALL RES Instructions
val &= (byte)(~bitMask); val &= (byte)(~bitMask);
break; break;
@@ -1591,60 +1817,6 @@ namespace Core.Cpu
case 3: // ALL SET Instructions case 3: // ALL SET Instructions
val |= bitMask; val |= bitMask;
break; 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: default:
throw new Exception("Invalid CB operation."); throw new Exception("Invalid CB operation.");
} }
@@ -1710,6 +1882,9 @@ namespace Core.Cpu
case 0x2B: // DEC IX case 0x2B: // DEC IX
IX.Word--; IX.Word--;
return 10; return 10;
case 0x2C: // INC IXL
IX.Low = Inc8(IX.Low);
return 8;
case 0x2D: // DEC IXL case 0x2D: // DEC IXL
IX.Low = Dec8(IX.Low); IX.Low = Dec8(IX.Low);
return 8; return 8;
@@ -1768,15 +1943,6 @@ namespace Core.Cpu
case 0x67: // LD IXH, A case 0x67: // LD IXH, A
IX.High = AF.High; IX.High = AF.High;
return 8; 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) case 0x6E: // LD L, (IX+d)
sbyte offset6E = (sbyte)FetchByte(); sbyte offset6E = (sbyte)FetchByte();
ushort address6E = (ushort)(IX.Word + offset6E); ushort address6E = (ushort)(IX.Word + offset6E);
@@ -1841,6 +2007,71 @@ namespace Core.Cpu
AddA(ReadMemory(targetAddressAdd)); AddA(ReadMemory(targetAddressAdd));
return 19; 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) case 0x96: // SUB (IX+d)
{ {
sbyte offset96 = (sbyte)FetchByte(); sbyte offset96 = (sbyte)FetchByte();
@@ -1848,6 +2079,16 @@ namespace Core.Cpu
SubA(ReadMemory(address96), false); SubA(ReadMemory(address96), false);
return 19; 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) case 0xBE: // CP (IX+d)
{ {
sbyte offsetBE = (sbyte)FetchByte(); sbyte offsetBE = (sbyte)FetchByte();
@@ -1865,9 +2106,34 @@ namespace Core.Cpu
int operation = cbOpcode >> 6; int operation = cbOpcode >> 6;
int bitIndex = (cbOpcode >> 3) & 0x07; int bitIndex = (cbOpcode >> 3) & 0x07;
byte bitMask = (byte)(1 << bitIndex); byte bitMask = (byte)(1 << bitIndex);
int regIndex = cbOpcode & 0x07;
switch (operation) 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 case 1: // ALL BIT Instructions
AF.Low &= 0x01; AF.Low &= 0x01;
AF.Low |= 0x10; AF.Low |= 0x10;
@@ -1892,9 +2158,6 @@ namespace Core.Cpu
WriteMemory(targetAddress, memVal); WriteMemory(targetAddress, memVal);
return 23; return 23;
case 0:
throw new NotImplementedException($"DD CB Shift/Rotate opcode {cbOpcode:X2} not implemented!");
default: default:
throw new Exception("Invalid bitwise operation."); throw new Exception("Invalid bitwise operation.");
} }
@@ -1906,6 +2169,14 @@ namespace Core.Cpu
SP++; SP++;
IX.Word = (ushort)((popHigh << 8) | popLow); IX.Word = (ushort)((popHigh << 8) | popLow);
return 14; 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 case 0xE5: // PUSH IX
SP--; SP--;
WriteMemory(SP, IX.High); WriteMemory(SP, IX.High);
@@ -1915,28 +2186,77 @@ namespace Core.Cpu
case 0xE9: // JP (IX) case 0xE9: // JP (IX)
PC = IX.Word; PC = IX.Word;
return 8; return 8;
case 0xF9: // LD SP, IX
SP = IX.Word;
return 10;
default: 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() private int ExecuteFDPrefix()
{ {
byte opcode = FetchByte(); byte ddOpcode = FetchByte();
ushort targetAddress = 0; ushort targetAddress = 0;
byte memVal = 0; byte memVal = 0;
switch (opcode) switch (ddOpcode)
{ {
case 0x09: Add16(ref IY, BC.Word); return 15; case 0x09: Add16(ref IY, BC.Word); return 15;
case 0x19: Add16(ref IY, DE.Word); return 15; case 0x19: Add16(ref IY, DE.Word); return 15;
case 0x21: // LD IY, nn case 0x21: // LD IY, nn
IY.Word = FetchWord(); IY.Word = FetchWord();
return 14; 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 case 0x23: // INC IY
IY.Word++; IY.Word++;
return 10; 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 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) case 0x34: // INC (IY+d)
{ {
sbyte offset34 = (sbyte)FetchByte(); sbyte offset34 = (sbyte)FetchByte();
@@ -2045,6 +2365,71 @@ namespace Core.Cpu
AddA(valueToAdd); AddA(valueToAdd);
return 19; 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) case 0x96: // SUB (IY+d)
{ {
sbyte offset96 = (sbyte)FetchByte(); sbyte offset96 = (sbyte)FetchByte();
@@ -2052,12 +2437,16 @@ namespace Core.Cpu
SubA(ReadMemory(address96), false); SubA(ReadMemory(address96), false);
return 19; return 19;
} }
case 0xA6: // AND (IY+d) case 0xB6: // OR (IY+d)
sbyte offsetA6 = (sbyte)FetchByte(); {
ushort addressA6 = (ushort)(IY.Word + offsetA6); sbyte offsetB6 = (sbyte)FetchByte();
byte operandA6 = ReadMemory(addressA6); ushort addressB6 = (ushort)(IY.Word + offsetB6);
And(operandA6);
return 19; // Read the memory and pass it straight into your helper
Or(ReadMemory(addressB6));
return 19; // Takes 19 T-States
}
case 0xBE: // CP (IY+d) case 0xBE: // CP (IY+d)
{ {
sbyte offsetBE = (sbyte)FetchByte(); sbyte offsetBE = (sbyte)FetchByte();
@@ -2075,9 +2464,34 @@ namespace Core.Cpu
int operation = cbOpcode >> 6; int operation = cbOpcode >> 6;
int bitIndex = (cbOpcode >> 3) & 0x07; int bitIndex = (cbOpcode >> 3) & 0x07;
byte bitMask = (byte)(1 << bitIndex); byte bitMask = (byte)(1 << bitIndex);
int regIndex = cbOpcode & 0x07;
switch (operation) 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 case 1: // ALL BIT Instructions
AF.Low &= 0x01; AF.Low &= 0x01;
AF.Low |= 0x10; AF.Low |= 0x10;
@@ -2102,9 +2516,6 @@ namespace Core.Cpu
WriteMemory(targetAddress, memVal); WriteMemory(targetAddress, memVal);
return 23; return 23;
case 0:
throw new NotImplementedException($"FD CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
default: default:
throw new Exception("Invalid bitwise operation."); throw new Exception("Invalid bitwise operation.");
} }
@@ -2115,14 +2526,26 @@ namespace Core.Cpu
IY.High = ReadMemory(SP); IY.High = ReadMemory(SP);
SP++; SP++;
return 14; 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 case 0xE5: // PUSH IY
SP--; SP--;
WriteMemory(SP, IY.High); WriteMemory(SP, IY.High);
SP--; SP--;
WriteMemory(SP, IY.Low); WriteMemory(SP, IY.Low);
return 15; return 15;
case 0xF9: // LD SP, IY
SP = IY.Word;
return 10;
default: 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'
} }
} }
} }