Implemented a few more OpCodes. Manic Miner sounds great!

This commit is contained in:
2026-04-22 02:00:50 +01:00
parent a63152b04d
commit e52cdeac54
2 changed files with 125 additions and 1 deletions

View File

@@ -1776,6 +1776,45 @@ namespace Core.Cpu
return 21; // Looping return 21; // Looping
} }
return 16; return 16;
case 0xB1: // CPIR
// 1. Read the memory byte at HL
byte memValB1 = ReadMemory(HL.Word);
// 2. Calculate the difference (A - (HL)) to set the flags
byte resultB1 = (byte)(AF.High - memValB1);
// 3. Increment HL and Decrement BC
HL.Word++;
BC.Word--;
// 4. Update the Flags (F Register / AF.Low)
// CPIR modifies S, Z, H, P/V, and N, but strictly PRESERVES the C flag.
byte currentCarry = (byte)(AF.Low & 0x01);
byte newFlagsB1 = currentCarry;
newFlagsB1 |= 0x02; // N flag is always set to 1 for CPIR
if (BC.Word != 0) newFlagsB1 |= 0x04; // P/V is set if BC is not 0 (Counter not empty)
if (((AF.High ^ memValB1 ^ resultB1) & 0x10) != 0) newFlagsB1 |= 0x10; // H flag (Half-borrow)
if (resultB1 == 0) newFlagsB1 |= 0x40; // Z flag (Match found!)
if ((resultB1 & 0x80) != 0) newFlagsB1 |= 0x80; // S flag (Sign)
// (Note: Undocumented bits 3 and 5 are ignored here as they are highly esoteric for CPIR,
// but you can add `newFlagsB1 |= (byte)(resultB1 & 0x28);` if your engine tracks them strictly).
AF.Low = newFlagsB1;
// 5. The Repeat Check
// If we haven't hit 0 in BC, AND we didn't find a match (result != 0)...
if (BC.Word != 0 && resultB1 != 0)
{
// Rewind the Program Counter by 2 bytes (back to the 0xED prefix)
PC -= 2;
return 21; // 21 T-States for a repeating loop
}
// If we found the byte or BC hit 0, the loop ends.
return 16; // 16 T-States when the loop terminates
case 0xB8: // LDDR case 0xB8: // LDDR
// 1. Read byte from (HL) // 1. Read byte from (HL)
val = ReadMemory(HL.Word); val = ReadMemory(HL.Word);
@@ -2182,6 +2221,9 @@ namespace Core.Cpu
HL.Low = ReadMemory(address6E); HL.Low = ReadMemory(address6E);
return 19; return 19;
case 0x6F: // LD IXL, A
IX.Low = AF.High;
return 8;
case 0x70: // LD (IX+d), B case 0x70: // LD (IX+d), B
// 1. Fetch the displacement byte and cast it to a signed sbyte // 1. Fetch the displacement byte and cast it to a signed sbyte
sbyte offset70 = (sbyte)FetchByte(); sbyte offset70 = (sbyte)FetchByte();
@@ -2409,6 +2451,10 @@ namespace Core.Cpu
case 0x21: // LD IY, nn case 0x21: // LD IY, nn
IY.Word = FetchWord(); IY.Word = FetchWord();
return 14; return 14;
case 0x23: // INC IY
// Increment the full 16-bit register. The F register remains completely untouched.
IY.Word++;
return 10;
case 0x34: // INC (IY+d) case 0x34: // INC (IY+d)
// 1. Fetch displacement and calculate memory address // 1. Fetch displacement and calculate memory address
sbyte offset34 = (sbyte)FetchByte(); sbyte offset34 = (sbyte)FetchByte();
@@ -2556,6 +2602,17 @@ namespace Core.Cpu
targetAddress = (ushort)(IY.Word + offset75); targetAddress = (ushort)(IY.Word + offset75);
// Write the low byte of HL to memory // Write the low byte of HL to memory
WriteMemory(targetAddress, HL.Low); WriteMemory(targetAddress, HL.Low);
return 19;
case 0x7E: // LD A, (IY+d)
// 1. Fetch the displacement byte and cast it to a signed sbyte
sbyte offset7E = (sbyte)FetchByte();
// 2. Calculate the exact memory address (IY + offset)
ushort address7E = (ushort)(IY.Word + offset7E);
// 3. Read the byte from memory and drop it straight into the Accumulator (A)
AF.High = ReadMemory(address7E);
return 19; return 19;
case 0x86: // ADD A, (IY+d) case 0x86: // ADD A, (IY+d)
{ {
@@ -2604,6 +2661,43 @@ namespace Core.Cpu
AF.Low = newFlags; AF.Low = newFlags;
AF.High = (byte)result; AF.High = (byte)result;
return 19;
case 0xA6: // AND (IY+d)
// 1. Fetch the displacement byte and cast it to a signed sbyte
sbyte offsetA6 = (sbyte)FetchByte();
// 2. Calculate the exact memory address (IY + offset)
ushort addressA6 = (ushort)(IY.Word + offsetA6);
// 3. Read the operand from memory
byte operandA6 = ReadMemory(addressA6);
// 4. Perform the logical AND with the Accumulator
AF.High &= operandA6;
// 5. Update the Flags Register (F)
byte flagsA6 = 0;
if ((AF.High & 0x80) != 0) flagsA6 |= 0x80; // S: Sign flag (Set if result is negative)
if (AF.High == 0) flagsA6 |= 0x40; // Z: Zero flag (Set if result is 0)
flagsA6 |= 0x10; // H: Half-carry is ALWAYS set to 1 for Z80 AND instructions
// P/V: Parity flag. We collapse the bits to check if the number of 1s is even
byte p = AF.High;
p ^= (byte)(p >> 4);
p ^= (byte)(p >> 2);
p ^= (byte)(p >> 1);
if ((p & 1) == 0) flagsA6 |= 0x04; // Set if Parity is Even
// Undocumented bits 3 and 5 are copied directly from the resulting Accumulator
flagsA6 |= (byte)(AF.High & 0x28);
// N (Subtract) and C (Carry) are always reset to 0 for AND instructions,
// which happens naturally since we started with flagsA6 = 0.
AF.Low = flagsA6;
return 19; return 19;
case 0xBE: // CP (IY+d) case 0xBE: // CP (IY+d)
// 1. Fetch the displacement byte and calculate the address using IY // 1. Fetch the displacement byte and calculate the address using IY

View File

@@ -835,6 +835,11 @@ namespace Desktop
mnemonic = $"LD L, (IX{sign}{d})"; mnemonic = $"LD L, (IX{sign}{d})";
instructionLength = 3; instructionLength = 3;
} }
else if (ddOpcode == 0x6F) // LD IXL, A
{
mnemonic = $"LD IXL, A)";
instructionLength = 2;
}
else if (ddOpcode == 0x71) // LD (IX+d), B else if (ddOpcode == 0x71) // LD (IX+d), B
{ {
sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); sbyte offset = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
@@ -1058,6 +1063,10 @@ namespace Desktop
mnemonic = "LDIR"; mnemonic = "LDIR";
instructionLength = 2; instructionLength = 2;
break; break;
case 0xB1:
mnemonic = "CPIR";
instructionLength = 2;
break;
case 0xB8: case 0xB8:
mnemonic = "LDDR"; mnemonic = "LDDR";
instructionLength = 2; instructionLength = 2;
@@ -1102,6 +1111,13 @@ namespace Desktop
mnemonic = $"LD IY, 0x{iyVal:X4}"; mnemonic = $"LD IY, 0x{iyVal:X4}";
instructionLength = 4; instructionLength = 4;
} }
else if (fdOpcode == 0x34) // INC IY
{
//sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
//string sign = d >= 0 ? "+" : "";
mnemonic = $"INC IY";
instructionLength = 2;
}
else if (fdOpcode == 0x34) // INC (IY+d) else if (fdOpcode == 0x34) // INC (IY+d)
{ {
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
@@ -1182,6 +1198,13 @@ namespace Desktop
mnemonic = $"LD (IY{sign}{d}), H"; mnemonic = $"LD (IY{sign}{d}), H";
instructionLength = 3; instructionLength = 3;
} }
else if (fdOpcode == 0x7E) // LD A, (IY+d)
{
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string sign = d >= 0 ? "+" : "";
mnemonic = $"LD A, (IY{sign}{d})";
instructionLength = 3;
}
else if (fdOpcode == 0x86) // ADD A, (IY+d) else if (fdOpcode == 0x86) // ADD A, (IY+d)
{ {
sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
@@ -1197,6 +1220,13 @@ namespace Desktop
mnemonic = $"SUB (IY{sign}{d})"; mnemonic = $"SUB (IY{sign}{d})";
instructionLength = 3; instructionLength = 3;
} }
else if (fdOpcode == 0xA6) //AND (IY+d)
{
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string sign = d >= 0 ? "+" : "";
mnemonic = $"AND (IY{sign}{d})";
instructionLength = 3;
}
else if (fdOpcode == 0xCB) // FD CB prefix else if (fdOpcode == 0xCB) // FD CB prefix
{ {
cbOp = _memoryBus.Read((ushort)(currentPc + 1)); cbOp = _memoryBus.Read((ushort)(currentPc + 1));
@@ -1227,7 +1257,7 @@ namespace Desktop
string sign = d >= 0 ? "+" : ""; string sign = d >= 0 ? "+" : "";
mnemonic = $"LD (IY{sign}{d}), L"; mnemonic = $"LD (IY{sign}{d}), L";
instructionLength = 3; instructionLength = 3;
} }
else else
{ {
mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})"; mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})";