All ZEXALL tests now pass!
This commit is contained in:
699
Core/Cpu/Z80.cs
699
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user