Implemented more OpCodes again!

This commit is contained in:
2026-04-15 14:16:41 +01:00
parent deeb7e3c61
commit e48b2226f4
2 changed files with 313 additions and 212 deletions

View File

@@ -702,7 +702,8 @@ namespace Core.Cpu
else
AF.Low &= 0xEF;
return 4;
case 0x40: BC.High = BC.High; return 4;
case 0x40: //BC.High = BC.High;
return 4;
case 0x41: BC.High = BC.Low; return 4;
case 0x42: BC.High = DE.High; return 4;
case 0x43: BC.High = DE.Low; return 4;
@@ -713,7 +714,8 @@ namespace Core.Cpu
// --- LD C, r ---
case 0x48: BC.Low = BC.High; return 4;
case 0x49: BC.Low = BC.Low; return 4;
case 0x49: //BC.Low = BC.Low;
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;
@@ -724,7 +726,8 @@ namespace Core.Cpu
// --- LD D, r ---
case 0x50: DE.High = BC.High; return 4;
case 0x51: DE.High = BC.Low; return 4;
case 0x52: DE.High = DE.High; return 4;
case 0x52: //DE.High = DE.High;
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;
@@ -735,7 +738,8 @@ namespace Core.Cpu
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: DE.Low = DE.Low; return 4;
case 0x5B: //DE.Low = DE.Low;
return 4;
case 0x5C: DE.Low = HL.High; return 4;
case 0x5D: DE.Low = HL.Low; return 4;
case 0x5E: DE.Low = _memory.Read(HL.Word); return 7;
@@ -746,7 +750,8 @@ namespace Core.Cpu
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: HL.High = HL.High; return 4;
case 0x64: //HL.High = HL.High;
return 4;
case 0x65: HL.High = HL.Low; return 4;
case 0x66: HL.High = _memory.Read(HL.Word); return 7;
case 0x67: HL.High = AF.High; return 4;
@@ -757,7 +762,8 @@ namespace Core.Cpu
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: HL.Low = HL.Low; return 4;
case 0x6D: //HL.Low = HL.Low;
return 4;
case 0x6E: HL.Low = _memory.Read(HL.Word); return 7;
case 0x6F: HL.Low = AF.High; return 4;
@@ -778,7 +784,8 @@ namespace Core.Cpu
case 0x7C: AF.High = HL.High; return 4;
case 0x7D: AF.High = HL.Low; return 4;
case 0x7E: AF.High = _memory.Read(HL.Word); return 7;
case 0x7F: AF.High = AF.High; return 4;
case 0x7F: //AF.High = AF.High;
return 4;
case 0x80: Add(BC.High); return 4; // ADD A, B
case 0x81: Add(BC.Low); return 4; // ADD A, C
case 0x82: Add(DE.High); return 4; // ADD A, D
@@ -1144,6 +1151,11 @@ namespace Core.Cpu
DE.Low = _memory.Read(src5B);
DE.High = _memory.Read((ushort)(src5B + 1));
return 20;
case 0x73: // LD (nn), SP
ushort dest73 = FetchWord();
_memory.Write(dest73, (byte)SP);
_memory.Write((ushort)(dest73 + 1), (byte)(SP >> 8));
return 20;
case 0xB0: // LDIR
// 1. Read byte from (HL)
val = _memory.Read(HL.Word);
@@ -1207,67 +1219,77 @@ namespace Core.Cpu
private int ExecuteCBPrefix()
{
byte cbOpcode = FetchByte();
byte memVal;
switch (cbOpcode)
// Extract the exact same mathematical properties
int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET
int bitIndex = (cbOpcode >> 3) & 0x07; // Extracts a number 0-7
int regIndex = cbOpcode & 0x07; // Extracts register index 0-7
byte bitMask = (byte)(1 << bitIndex);
// --- PHASE 1: Fetch the target value ---
byte val = 0;
switch (regIndex)
{
case 0x7E: // BIT 7, (HL)
byte memValBit7 = _memory.Read(HL.Word);
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 = _memory.Read(HL.Word); break; // The 0x110 (HL) exception
case 7: val = AF.High; break;
}
// Preserve ONLY the Carry Flag (Bit 0). This clears N and everything else.
AF.Low &= 0x01;
// --- PHASE 2: Perform the bitwise math ---
switch (operation)
{
case 1: // ALL BIT Instructions
AF.Low &= 0x01; // Preserve ONLY Carry
AF.Low |= 0x10; // Set Half-Carry
// Half-Carry (Bit 4) is ALWAYS set to 1 for Z80 BIT instructions
AF.Low |= 0x10;
// Test Bit 7 (0x80 is Binary 1000 0000)
if ((memValBit7 & 0x80) == 0)
if ((val & bitMask) == 0)
{
// If the bit is 0, turn ON the Zero Flag (Bit 6)
AF.Low |= 0x40;
// Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
AF.Low |= 0x04;
AF.Low |= 0x44; // Set Zero and P/V
}
else
else if (bitIndex == 7)
{
// If testing Bit 7 and it is 1, the Sign Flag (Bit 7) perfectly mirrors it
AF.Low |= 0x80;
AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign
}
return 12;
case 0x86: // RES 0, (HL)
memVal = _memory.Read(HL.Word);
// BIT (HL) takes 12 T-States. Standard register BIT takes 8.
return (regIndex == 6) ? 12 : 8;
// 0xFE is Binary 1111 1110.
// ANDing preserves all bits except Bit 0, which becomes 0.
memVal &= 0xFE;
case 2: // ALL RES Instructions
val &= (byte)(~bitMask);
break; // Proceed to write-back
_memory.Write(HL.Word, memVal);
return 15;
case 0xAE: // RES 5, (HL)
memVal = _memory.Read(HL.Word);
case 3: // ALL SET Instructions
val |= bitMask;
break; // Proceed to write-back
// 0xDF is Binary 1101 1111
// ANDing preserves all other bits while forcing Bit 5 to 0
memVal &= 0xDF;
_memory.Write(HL.Word, memVal);
return 15;
case 0xC6: // SET 0, (HL)
memVal = _memory.Read(HL.Word);
// 0x01 is Binary 0000 0001
// ORing forces Bit 0 to 1 and perfectly preserves all other bits
memVal |= 0x01;
_memory.Write(HL.Word, memVal);
return 15;
case 0: // ALL Shift/Rotate Instructions
throw new NotImplementedException($"CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
default:
throw new NotImplementedException($"CB prefix opcode 0x{cbOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
throw new Exception("Invalid CB operation.");
}
// --- PHASE 3: Write back the modified value (RES and SET only) ---
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: _memory.Write(HL.Word, val); break;
case 7: AF.High = val; break;
}
// RES/SET (HL) takes 15 T-States. Standard register RES/SET takes 8.
return (regIndex == 6) ? 15 : 8;
}
private int ExecuteFDPrefix()
@@ -1323,11 +1345,11 @@ namespace Core.Cpu
return 19; // Takes 19 T-States
}
case 0x75: // LD (IY+d), L
sbyte offset75 = (sbyte)FetchByte();
targetAddress = (ushort)(IY.Word + offset75);
// Write the low byte of HL to memory
_memory.Write(targetAddress, HL.Low);
return 19;
sbyte offset75 = (sbyte)FetchByte();
targetAddress = (ushort)(IY.Word + offset75);
// Write the low byte of HL to memory
_memory.Write(targetAddress, HL.Low);
return 19;
case 0x86: // ADD A, (IY+d)
{
sbyte displacementAdd = (sbyte)FetchByte();
@@ -1340,110 +1362,48 @@ namespace Core.Cpu
}
case 0xCB: // The FD CB nested prefix
{
sbyte offsetCB = (sbyte)FetchByte(); // This is the '01'
byte bitOpcode = FetchByte(); // This is the 'CE'
targetAddress = (ushort)(IY.Word + offsetCB);
sbyte displacement = (sbyte)FetchByte();
byte cbOpcode = FetchByte();
targetAddress = (ushort)(IY.Word + displacement);
memVal = _memory.Read(targetAddress);
switch (bitOpcode)
// Extract the mathematical properties of the opcode
int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET
int bitIndex = (cbOpcode >> 3) & 0x07; // Extracts a number 0-7
byte bitMask = (byte)(1 << bitIndex); // Creates the bitmask (e.g., 0x01, 0x02, 0x80)
switch (operation)
{
case 0x46: // BIT 0, (IY+d)
byte memValBit0 = _memory.Read(targetAddress);
case 1: // ALL BIT Instructions
AF.Low &= 0x01; // Preserve ONLY Carry
AF.Low |= 0x10; // Set Half-Carry
// Preserve the existing Carry Flag (Bit 0)
byte newFlags = (byte)(AF.Low & 0x01);
newFlags |= 0x10; // Force Half-Carry (Bit 4) to 1
// Test Bit 0. If it is 0, turn ON the Zero Flag (Bit 6)
if ((memValBit0 & 0x01) == 0)
if ((memVal & bitMask) == 0)
{
newFlags |= 0x40;
AF.Low |= 0x44; // Set Zero (Bit 6) and P/V (Bit 2)
}
else if (bitIndex == 7)
{
AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign (Bit 7)
}
AF.Low = newFlags;
return 20;
case 0x4E: // BIT 1, (IY+d)
{
byte memValBit = _memory.Read(targetAddress);
// Check if bit 1 is 0
bool bitIsZero = (memValBit & 0x02) == 0;
// Preserve the Carry flag (Bit 0), clear everything else
AF.Low &= 0x01;
// Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT
AF.Low |= 0x10;
if (bitIsZero)
{
AF.Low |= 0x40; // Set Zero Flag (Bit 6)
AF.Low |= 0x04; // Set P/V Flag (Bit 2)
}
return 20;
}
case 0x86: // RES 0, (IY+d)
byte memValRes0 = _memory.Read(targetAddress);
// 0xFE is Binary 1111 1110.
// ANDing preserves all bits except Bit 0, which becomes 0.
memValRes0 &= 0xFE;
_memory.Write(targetAddress, memValRes0);
return 23; // Takes 23 T-States
case 0x8E: // RES 1, (IY+d)
byte memValRes = _memory.Read(targetAddress);
// 0xFD is Binary 1111 1101.
// ANDing with this preserves all bits except Bit 1, which becomes 0.
memValRes &= 0xFD;
_memory.Write(targetAddress, memValRes);
return 23;
case 0xA6: // RES 4, (IY+d)
byte memValRes4 = _memory.Read(targetAddress);
// 0xEF is Binary 1110 1111
// ANDing preserves all bits except Bit 4, which becomes 0.
memValRes4 &= 0xEF;
_memory.Write(targetAddress, memValRes4);
return 23;
case 0xAE: // RES 5, (IY+d)
byte memValRes5 = _memory.Read(targetAddress);
// 0xDF is Binary 1101 1111
// ANDing perfectly preserves all other bits while forcing Bit 5 to 0
memValRes5 &= 0xDF;
_memory.Write(targetAddress, memValRes5);
return 23;
case 0xC6: // SET 0, (IY+d)
byte memValSet0 = _memory.Read(targetAddress);
// 0x01 is Binary 0000 0001
// ORing forces Bit 0 to 1 and perfectly preserves all other bits
memValSet0 |= 0x01;
_memory.Write(targetAddress, memValSet0);
return 23; // Takes 23 T-States
case 0xCE: // SET 1, (IY+d)
memVal = _memory.Read(targetAddress);
memVal |= 0x02; // 0x02 is Binary 0000 0010 (Bit 1)
case 2: // ALL RES Instructions
memVal &= (byte)(~bitMask); // Invert mask and AND it to clear the bit
_memory.Write(targetAddress, memVal);
return 23;
case 0xE6: // SET 4, (IY+d)
byte memValSet4 = _memory.Read(targetAddress);
// 0x10 is Binary 0001 0000
// ORing perfectly preserves all other bits while forcing Bit 4 to 1
memValSet4 |= 0x10;
_memory.Write(targetAddress, memValSet4);
case 3: // ALL SET Instructions
memVal |= bitMask; // OR the mask to force the bit to 1
_memory.Write(targetAddress, memVal);
return 23;
case 0:
// Shift/Rotate instructions will go here later
throw new NotImplementedException($"FD CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
default:
throw new NotImplementedException($"FD CB opcode {bitOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
throw new Exception("Invalid bitwise operation.");
}
}
default:
@@ -1451,4 +1411,183 @@ namespace Core.Cpu
}
}
}
}
}
//sbyte offsetCB = (sbyte)FetchByte(); // This is the '01'
//byte bitOpcode = FetchByte(); // This is the 'CE'
//targetAddress = (ushort)(IY.Word + offsetCB);
//switch (bitOpcode)
//{
// case 0x46: // BIT 0, (IY+d)
// byte memValBit0 = _memory.Read(targetAddress);
// // Preserve the existing Carry Flag (Bit 0)
// byte newFlags = (byte)(AF.Low & 0x01);
// newFlags |= 0x10; // Force Half-Carry (Bit 4) to 1
// // Test Bit 0. If it is 0, turn ON the Zero Flag (Bit 6)
// if ((memValBit0 & 0x01) == 0)
// {
// newFlags |= 0x40;
// }
// AF.Low = newFlags;
// return 20;
// case 0x4E: // BIT 1, (IY+d)
// {
// byte memValBit = _memory.Read(targetAddress);
// // Check if bit 1 is 0
// bool bitIsZero = (memValBit & 0x02) == 0;
// // Preserve the Carry flag (Bit 0), clear everything else
// AF.Low &= 0x01;
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT
// AF.Low |= 0x10;
// if (bitIsZero)
// {
// AF.Low |= 0x40; // Set Zero Flag (Bit 6)
// AF.Low |= 0x04; // Set P/V Flag (Bit 2)
// }
// return 20;
// }
// case 0x66: // BIT 4, (IY+d)
// byte memValBit4 = _memory.Read(targetAddress);
// // Preserve ONLY the Carry flag (Bit 0). This clears N and everything else.
// AF.Low &= 0x01;
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT instructions
// AF.Low |= 0x10;
// // Test Bit 4 (0x10 is Binary 0001 0000)
// if ((memValBit4 & 0x10) == 0)
// {
// // If the bit is 0, turn ON the Zero Flag (Bit 6)
// AF.Low |= 0x40;
// // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
// AF.Low |= 0x04;
// }
// // (Note: The Sign Flag remains 0 because we are not testing Bit 7)
// return 20;
// case 0x6E: // BIT 5, (IY+d)
// byte memValBit5 = _memory.Read(targetAddress);
// // Preserve ONLY the Carry flag (Bit 0). This clears N and everything else.
// AF.Low &= 0x01;
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT instructions
// AF.Low |= 0x10;
// // Test Bit 5 (0x20 is Binary 0010 0000)
// if ((memValBit5 & 0x20) == 0)
// {
// // If the bit is 0, turn ON the Zero Flag (Bit 6)
// AF.Low |= 0x40;
// // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
// AF.Low |= 0x04;
// }
// // (Note: The Sign Flag remains 0 because we are not testing Bit 7)
// return 20;
// case 0x76: // BIT 6, (IY+d)
// byte memValBit6 = _memory.Read(targetAddress);
// // Preserve ONLY the Carry flag (Bit 0). This clears N and everything else.
// AF.Low &= 0x01;
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT
// AF.Low |= 0x10;
// // Test Bit 6 (0x40 is Binary 0100 0000)
// if ((memValBit6 & 0x40) == 0)
// {
// // If the bit is 0, turn ON the Zero Flag (Bit 6)
// AF.Low |= 0x40;
// // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
// AF.Low |= 0x04;
// }
// // (Note: The Sign Flag remains 0 because we are not testing Bit 7)
// return 20;
// case 0x86: // RES 0, (IY+d)
// byte memValRes0 = _memory.Read(targetAddress);
// // 0xFE is Binary 1111 1110.
// // ANDing preserves all bits except Bit 0, which becomes 0.
// memValRes0 &= 0xFE;
// _memory.Write(targetAddress, memValRes0);
// return 23; // Takes 23 T-States
// case 0x8E: // RES 1, (IY+d)
// byte memValRes = _memory.Read(targetAddress);
// // 0xFD is Binary 1111 1101.
// // ANDing with this preserves all bits except Bit 1, which becomes 0.
// memValRes &= 0xFD;
// _memory.Write(targetAddress, memValRes);
// return 23;
// case 0xA6: // RES 4, (IY+d)
// byte memValRes4 = _memory.Read(targetAddress);
// // 0xEF is Binary 1110 1111
// // ANDing preserves all bits except Bit 4, which becomes 0.
// memValRes4 &= 0xEF;
// _memory.Write(targetAddress, memValRes4);
// return 23;
// case 0xAE: // RES 5, (IY+d)
// byte memValRes5 = _memory.Read(targetAddress);
// // 0xDF is Binary 1101 1111
// // ANDing perfectly preserves all other bits while forcing Bit 5 to 0
// memValRes5 &= 0xDF;
// _memory.Write(targetAddress, memValRes5);
// return 23;
// case 0xC6: // SET 0, (IY+d)
// byte memValSet0 = _memory.Read(targetAddress);
// // 0x01 is Binary 0000 0001
// // ORing forces Bit 0 to 1 and perfectly preserves all other bits
// memValSet0 |= 0x01;
// _memory.Write(targetAddress, memValSet0);
// return 23; // Takes 23 T-States
// case 0xCE: // SET 1, (IY+d)
// memVal = _memory.Read(targetAddress);
// memVal |= 0x02; // 0x02 is Binary 0000 0010 (Bit 1)
// _memory.Write(targetAddress, memVal);
// return 23;
// case 0xE6: // SET 4, (IY+d)
// byte memValSet4 = _memory.Read(targetAddress);
// // 0x10 is Binary 0001 0000
// // ORing perfectly preserves all other bits while forcing Bit 4 to 1
// memValSet4 |= 0x10;
// _memory.Write(targetAddress, memValSet4);
// return 23;
// case 0xEE: // SET 5, (IY+d)
// byte memValSet5 = _memory.Read(targetAddress);
// // 0x20 is Binary 0010 0000
// // ORing perfectly preserves all other bits while forcing Bit 5 to 1
// memValSet5 |= 0x20;
// _memory.Write(targetAddress, memValSet5);
// return 23;
// default:
// throw new NotImplementedException($"FD CB opcode {bitOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
//}