5469 lines
227 KiB
C#
5469 lines
227 KiB
C#
using System;
|
|
using Core.Interfaces;
|
|
using Core.Io;
|
|
|
|
namespace Core.Cpu
|
|
{
|
|
public partial class Z80
|
|
{
|
|
private static readonly byte[] ParityTable = new byte[256];
|
|
|
|
// Static constructor to build the table once when the emulator starts
|
|
static Z80()
|
|
{
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
int ones = 0;
|
|
for (int b = 0; b < 8; b++) if ((i & (1 << b)) != 0) ones++;
|
|
ParityTable[i] = (byte)((ones % 2 == 0) ? 0x04 : 0x00); // 0x04 if Even Parity
|
|
}
|
|
}
|
|
|
|
//T-State counter
|
|
public long TotalTStates { get; set; }
|
|
|
|
public int InterruptMode { get; private set; } = 0;
|
|
|
|
// Interrupt Flip-Flops
|
|
public bool IFF1 { get; private set; } = false;
|
|
public bool IFF2 { get; private set; } = false;
|
|
public bool InterruptRequested { get; private set; } = false;
|
|
|
|
// Main Register Set
|
|
public RegisterPair AF;
|
|
public RegisterPair BC;
|
|
public RegisterPair DE;
|
|
public RegisterPair HL;
|
|
|
|
// Alternate Register Set
|
|
public RegisterPair AF_Prime;
|
|
public RegisterPair BC_Prime;
|
|
public RegisterPair DE_Prime;
|
|
public RegisterPair HL_Prime;
|
|
|
|
// Index Registers
|
|
public RegisterPair IX;
|
|
public RegisterPair IY;
|
|
|
|
// Special Purpose Registers
|
|
public ushort PC; // Program Counter
|
|
public ushort SP; // Stack Pointer
|
|
public byte I; // Interrupt Vector
|
|
public byte R; // Memory Refresh
|
|
|
|
// The Memory Bus
|
|
private readonly IMemory _memory;
|
|
private readonly IO_Bus _simpleIoBus;
|
|
|
|
//External Timing interface
|
|
public Func<ushort, long, int>? WaitStateCallback { get; set; }
|
|
|
|
public Z80(IMemory memory, IO_Bus ioBus)
|
|
{
|
|
_memory = memory;
|
|
_simpleIoBus = ioBus;
|
|
Reset();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
PC = 0x0000;
|
|
SP = 0xFFFF;
|
|
|
|
// Main Registers
|
|
AF.Word = 0;
|
|
BC.Word = 0;
|
|
DE.Word = 0;
|
|
HL.Word = 0;
|
|
|
|
// Alternate Registers
|
|
AF_Prime.Word = 0;
|
|
BC_Prime.Word = 0;
|
|
DE_Prime.Word = 0;
|
|
HL_Prime.Word = 0;
|
|
|
|
// Index Registers
|
|
IX.Word = 0;
|
|
IY.Word = 0;
|
|
|
|
// Internal Registers
|
|
I = 0;
|
|
R = 0;
|
|
|
|
// Hardware State
|
|
IFF1 = false;
|
|
IFF2 = false;
|
|
InterruptMode = 0;
|
|
TotalTStates = 0;
|
|
}
|
|
|
|
private void ApplyWaitStates(ushort address)
|
|
{
|
|
// If a system (like a ULA) is attached and listening, ask it for the delay
|
|
if (WaitStateCallback != null)
|
|
{
|
|
TotalTStates += WaitStateCallback(address, TotalTStates);
|
|
}
|
|
}
|
|
|
|
public int RequestInterrupt()
|
|
{
|
|
InterruptRequested = true;
|
|
// 1. If the ROM has disabled interrupts (DI), ignore the request
|
|
if (!IFF1) return 0;
|
|
|
|
// 2. Acknowledge the interrupt by immediately disabling further interrupts
|
|
IFF1 = false;
|
|
IFF2 = false;
|
|
|
|
// 3. Push the current Program Counter to the stack so we can return later
|
|
Push(PC);
|
|
|
|
// --- Interrupt Mode Dispatch ---
|
|
if (InterruptMode == 1)
|
|
{
|
|
// IM 1: Hardcoded jump to ROM address 0x0038
|
|
PC = 0x0038;
|
|
return 13; // IM 1 hardware call takes 13 T-States
|
|
}
|
|
else if (InterruptMode == 2)
|
|
{
|
|
// IM 2: Dynamic Vectored Interrupts
|
|
// A. Form the pointer address: High byte is 'I', Low byte is the floating bus (0xFF)
|
|
ushort vectorAddress = (ushort)((I << 8) | 0xFF);
|
|
|
|
// B. Read the actual 16-bit ISR address from that location in memory (Little-Endian)
|
|
byte pcLow = ReadMemory(vectorAddress);
|
|
byte pcHigh = ReadMemory((ushort)(vectorAddress + 1));
|
|
|
|
// C. Jump to the custom game routine!
|
|
PC = (ushort)((pcHigh << 8) | pcLow);
|
|
|
|
return 19; // IM 2 hardware call takes 19 T-States
|
|
}
|
|
else
|
|
{
|
|
// (IM 0 is theoretically possible but essentially unused on the standard Spectrum)
|
|
throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!");
|
|
}
|
|
}
|
|
|
|
// 1. For fetching opcodes and immediate values (Advances PC)
|
|
public byte FetchByte()
|
|
{
|
|
ApplyWaitStates(PC);
|
|
byte data = _memory.Read(PC);
|
|
PC++;
|
|
return data;
|
|
}
|
|
|
|
// 2. For fetching 16-bit immediate values
|
|
private ushort FetchWord()
|
|
{
|
|
// By using FetchByte twice, we perfectly apply wait states to BOTH memory reads!
|
|
byte low = FetchByte();
|
|
byte high = FetchByte();
|
|
return (ushort)((high << 8) | low);
|
|
}
|
|
|
|
// 3. For standard memory reads (e.g., LD A, (HL))
|
|
public byte ReadMemory(ushort address)
|
|
{
|
|
ApplyWaitStates(address);
|
|
return _memory.Read(address);
|
|
}
|
|
|
|
// 4. For standard memory writes (e.g., LD (HL), A)
|
|
public void WriteMemory(ushort address, byte data)
|
|
{
|
|
ApplyWaitStates(address);
|
|
_memory.Write(address, data);
|
|
}
|
|
|
|
// Placeholder for your hardware I/O
|
|
private byte ReadPort(ushort portAddress)
|
|
{
|
|
return _simpleIoBus.ReadPort(portAddress);
|
|
}
|
|
|
|
public int Step()
|
|
{
|
|
|
|
// Fetch the next opcode and increment the Program Counter
|
|
byte opcode = ReadMemory(PC++);
|
|
int tStates = ExecuteOpcode(opcode);
|
|
TotalTStates += tStates;
|
|
|
|
// Decode and execute
|
|
return tStates;
|
|
}
|
|
|
|
public void LoadSNA(byte[] snaData)
|
|
{
|
|
if (snaData.Length != 49179)
|
|
throw new Exception("Invalid 48K SNA File Size!");
|
|
|
|
// --- 1. Load CPU Registers ---
|
|
I = snaData[0];
|
|
HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8));
|
|
DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8));
|
|
BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8));
|
|
AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8));
|
|
|
|
HL.Word = (ushort)(snaData[9] | (snaData[10] << 8));
|
|
DE.Word = (ushort)(snaData[11] | (snaData[12] << 8));
|
|
BC.Word = (ushort)(snaData[13] | (snaData[14] << 8));
|
|
IY.Word = (ushort)(snaData[15] | (snaData[16] << 8));
|
|
IX.Word = (ushort)(snaData[17] | (snaData[18] << 8));
|
|
|
|
IFF2 = (snaData[19] & 0x04) != 0;
|
|
IFF1 = IFF2;
|
|
R = snaData[20];
|
|
AF.Word = (ushort)(snaData[21] | (snaData[22] << 8));
|
|
SP = (ushort)(snaData[23] | (snaData[24] << 8));
|
|
InterruptMode = snaData[25];
|
|
|
|
// --- 2. Load the 48K RAM Dump ---
|
|
// The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF
|
|
for (int i = 0; i < 49152; i++)
|
|
{
|
|
WriteMemory((ushort)(0x4000 + i), snaData[27 + i]);
|
|
}
|
|
|
|
// --- 3. The Magic Bullet ---
|
|
// In the SNA format, the Program Counter (PC) isn't in the header.
|
|
// It was PUSHED to the stack exactly 1 instruction before the snapshot was saved.
|
|
// So, we just pop it off the stack to resume execution!
|
|
PC = Pop();
|
|
}
|
|
|
|
|
|
|
|
public string GetFlagsString()
|
|
{
|
|
byte f = AF.Low;
|
|
return $"S:{(f >> 7) & 1} " +
|
|
$"Z:{(f >> 6) & 1} " +
|
|
$"Y:{(f >> 5) & 1} " + // Undocumented flag
|
|
$"H:{(f >> 4) & 1} " +
|
|
$"X:{(f >> 3) & 1} " + // Undocumented flag
|
|
$"P/V:{(f >> 2) & 1} " +
|
|
$"N:{(f >> 1) & 1} " +
|
|
$"C:{f & 1}";
|
|
}
|
|
|
|
// =========================================================================
|
|
// MATH AND LOGIC HELPERS
|
|
// =========================================================================
|
|
|
|
private void SubA(byte value, bool isCompare)
|
|
{
|
|
int result = AF.High - value;
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x80) != 0) flags |= 0x80;
|
|
if ((byte)result == 0) flags |= 0x40;
|
|
if (((AF.High & 0x0F) - (value & 0x0F)) < 0) flags |= 0x10;
|
|
if ((((AF.High ^ value) & 0x80) != 0) && (((AF.High ^ result) & 0x80) != 0)) flags |= 0x04;
|
|
|
|
flags |= 0x02; // N flag is always 1 for SUB and CP
|
|
if (result < 0) flags |= 0x01;
|
|
|
|
// The CP Trap: CP uses the operand, SUB uses the result
|
|
flags |= (byte)((isCompare ? value : result) & 0x28);
|
|
|
|
AF.Low = flags;
|
|
|
|
if (!isCompare) AF.High = (byte)result;
|
|
}
|
|
|
|
private void SbcA(byte value)
|
|
{
|
|
byte a = AF.High;
|
|
byte carry = (byte)(AF.Low & 0x01);
|
|
int result = a - value - carry;
|
|
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x80) != 0) flags |= 0x80;
|
|
if ((byte)result == 0) flags |= 0x40;
|
|
if (((a & 0x0F) - (value & 0x0F) - carry) < 0) flags |= 0x10;
|
|
if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) flags |= 0x04;
|
|
|
|
flags |= 0x02; // N is 1
|
|
if (result < 0) flags |= 0x01;
|
|
|
|
flags |= (byte)(result & 0x28);
|
|
|
|
AF.Low = flags;
|
|
AF.High = (byte)result;
|
|
}
|
|
|
|
private void Sbc16(ushort value)
|
|
{
|
|
int hl = HL.Word;
|
|
int carry = AF.Low & 0x01;
|
|
int result = hl - value - carry;
|
|
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x8000) != 0) flags |= 0x80;
|
|
if ((ushort)result == 0) flags |= 0x40;
|
|
if (((hl & 0x0FFF) - (value & 0x0FFF) - carry) < 0) flags |= 0x10;
|
|
if ((((hl ^ value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) flags |= 0x04;
|
|
|
|
flags |= 0x02; // N
|
|
if (result < 0) flags |= 0x01;
|
|
|
|
flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
AF.Low = flags;
|
|
HL.Word = (ushort)result;
|
|
}
|
|
|
|
private void SbcHl(ushort value)
|
|
{
|
|
int op1 = HL.Word;
|
|
int op2 = value;
|
|
int carry = AF.Low & 0x01;
|
|
int result = op1 - op2 - carry;
|
|
|
|
byte flags = 0x02; // N: Always 1
|
|
|
|
if (((result >> 8) & 0x80) != 0) flags |= 0x80;
|
|
if ((result & 0xFFFF) == 0) flags |= 0x40;
|
|
|
|
if ((((op1 & 0x0FFF) - (op2 & 0x0FFF) - carry) & 0x1000) != 0) flags |= 0x10;
|
|
if ((((op1 ^ op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04;
|
|
if (result < 0) flags |= 0x01;
|
|
|
|
flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
AF.Low = flags;
|
|
HL.Word = (ushort)(result & 0xFFFF);
|
|
}
|
|
|
|
private byte Dec8(byte value)
|
|
{
|
|
byte result = (byte)(value - 1);
|
|
byte carry = (byte)(AF.Low & 0x01);
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x80) != 0) flags |= 0x80;
|
|
if (result == 0) flags |= 0x40;
|
|
if ((value & 0x0F) == 0) flags |= 0x10;
|
|
if (value == 0x80) flags |= 0x04;
|
|
flags |= 0x02; // N flag
|
|
flags |= carry; // Preserve C flag
|
|
|
|
flags |= (byte)(result & 0x28); // Undocumented bits
|
|
|
|
AF.Low = flags;
|
|
return result;
|
|
}
|
|
|
|
private byte Inc8(byte value)
|
|
{
|
|
byte result = (byte)(value + 1);
|
|
byte carry = (byte)(AF.Low & 0x01);
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x80) != 0) flags |= 0x80;
|
|
if (result == 0) flags |= 0x40;
|
|
if ((value & 0x0F) == 0x0F) flags |= 0x10;
|
|
if (value == 0x7F) flags |= 0x04;
|
|
flags |= carry; // Preserve C flag
|
|
|
|
flags |= (byte)(result & 0x28); // Undocumented bits
|
|
|
|
AF.Low = flags;
|
|
return result;
|
|
}
|
|
|
|
private void And(byte value)
|
|
{
|
|
AF.High &= value;
|
|
AF.Low = 0x10; // AND forces H to 1, N to 0, C to 0
|
|
|
|
if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
if (AF.High == 0) AF.Low |= 0x40;
|
|
AF.Low |= ParityTable[AF.High];
|
|
AF.Low |= (byte)(AF.High & 0x28);
|
|
}
|
|
|
|
private void Or(byte value)
|
|
{
|
|
AF.High |= value;
|
|
AF.Low = 0; // OR forces H, N, C to 0
|
|
|
|
if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
if (AF.High == 0) AF.Low |= 0x40;
|
|
AF.Low |= ParityTable[AF.High];
|
|
AF.Low |= (byte)(AF.High & 0x28);
|
|
}
|
|
|
|
private void Xor(byte value)
|
|
{
|
|
AF.High ^= value;
|
|
AF.Low = 0; // XOR forces H, N, C to 0
|
|
|
|
if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
if (AF.High == 0) AF.Low |= 0x40;
|
|
AF.Low |= ParityTable[AF.High];
|
|
AF.Low |= (byte)(AF.High & 0x28);
|
|
}
|
|
|
|
private void Add16(ref RegisterPair dest, ushort value)
|
|
{
|
|
int result = dest.Word + value;
|
|
byte flags = (byte)(AF.Low & 0xC4); // Preserve S, Z, P/V
|
|
|
|
if (((dest.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10; // H
|
|
if (result > 0xFFFF) flags |= 0x01; // C
|
|
flags |= (byte)((result >> 8) & 0x28); // Undocumented bits 3 & 5
|
|
|
|
AF.Low = flags;
|
|
dest.Word = (ushort)result;
|
|
}
|
|
|
|
private void AddA(byte operand)
|
|
{
|
|
byte a = AF.High;
|
|
int result = a + operand;
|
|
|
|
AF.High = (byte)result;
|
|
byte flags = 0;
|
|
|
|
if ((AF.High & 0x80) != 0) flags |= 0x80;
|
|
if (AF.High == 0) flags |= 0x40;
|
|
if (((a & 0x0F) + (operand & 0x0F)) > 0x0F) flags |= 0x10;
|
|
|
|
bool sameSign = ((a ^ operand) & 0x80) == 0;
|
|
bool changedSign = ((a ^ AF.High) & 0x80) != 0;
|
|
if (sameSign && changedSign) flags |= 0x04;
|
|
|
|
if (result > 0xFF) flags |= 0x01;
|
|
flags |= (byte)(AF.High & 0x28);
|
|
|
|
AF.Low = flags;
|
|
}
|
|
|
|
private void AdcA(byte operand)
|
|
{
|
|
int aVal = AF.High;
|
|
int carryIn = AF.Low & 0x01;
|
|
int result = aVal + operand + carryIn;
|
|
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x80) != 0) flags |= 0x80;
|
|
if ((result & 0xFF) == 0) flags |= 0x40;
|
|
if (((aVal & 0x0F) + (operand & 0x0F) + carryIn) > 0x0F) flags |= 0x10;
|
|
if ((((aVal ^ ~operand) & (aVal ^ result)) & 0x80) != 0) flags |= 0x04;
|
|
if (result > 0xFF) flags |= 0x01;
|
|
|
|
flags |= (byte)(result & 0x28);
|
|
|
|
AF.Low = flags;
|
|
AF.High = (byte)result;
|
|
}
|
|
|
|
private void Adc16(ushort value)
|
|
{
|
|
int hl = HL.Word;
|
|
int carry = AF.Low & 0x01;
|
|
int result = hl + value + carry;
|
|
|
|
byte flags = 0;
|
|
|
|
if ((result & 0x8000) != 0) flags |= 0x80;
|
|
if ((result & 0xFFFF) == 0) flags |= 0x40;
|
|
if (((hl & 0x0FFF) + (value & 0x0FFF) + carry) > 0x0FFF) flags |= 0x10;
|
|
if ((((hl ^ ~value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) flags |= 0x04;
|
|
if (result > 0xFFFF) flags |= 0x01;
|
|
|
|
flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
AF.Low = flags;
|
|
HL.Word = (ushort)result;
|
|
}
|
|
|
|
private void AdcHl(ushort value)
|
|
{
|
|
int op1 = HL.Word;
|
|
int op2 = value;
|
|
int carry = AF.Low & 0x01;
|
|
int result = op1 + op2 + carry;
|
|
|
|
byte flags = 0;
|
|
|
|
if (((result >> 8) & 0x80) != 0) flags |= 0x80;
|
|
if ((result & 0xFFFF) == 0) flags |= 0x40;
|
|
|
|
if ((((op1 & 0x0FFF) + (op2 & 0x0FFF) + carry) & 0x1000) != 0) flags |= 0x10;
|
|
if ((((op1 ^ ~op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04;
|
|
if ((result & 0x10000) != 0) flags |= 0x01;
|
|
|
|
flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
AF.Low = flags;
|
|
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
|
|
SP--;
|
|
WriteMemory(SP, (byte)(value >> 8));
|
|
|
|
// Low byte goes second
|
|
SP--;
|
|
WriteMemory(SP, (byte)(value & 0xFF));
|
|
}
|
|
|
|
private ushort Pop()
|
|
{
|
|
// The Z80 is Little-Endian. Low byte comes off the stack first.
|
|
byte low = ReadMemory(SP++);
|
|
|
|
// High byte comes off second.
|
|
byte high = ReadMemory(SP++);
|
|
|
|
return (ushort)((high << 8) | low);
|
|
}
|
|
|
|
private int ExecuteOpcode(byte opcode)
|
|
{
|
|
sbyte offset = 0;
|
|
byte oldCarry = 0;
|
|
switch (opcode)
|
|
{
|
|
case 0x00: // NOP
|
|
return 4;
|
|
case 0x01: // LD BC, nn
|
|
BC.Word = FetchWord();
|
|
return 10;
|
|
case 0x02: // LD (BC), A
|
|
WriteMemory(BC.Word, AF.High);
|
|
return 7;
|
|
case 0x03: // INC BC
|
|
BC.Word++;
|
|
return 6;
|
|
// --- 8-Bit Increments ---
|
|
case 0x04: BC.High = Inc8(BC.High); return 4; // INC B
|
|
case 0x07: // RLCA
|
|
byte topBit = (byte)(AF.High >> 7);
|
|
AF.High = (byte)((AF.High << 1) | topBit);
|
|
|
|
byte flags07 = (byte)(AF.Low & 0xC4);
|
|
if (topBit != 0) flags07 |= 0x01;
|
|
flags07 |= (byte)(AF.High & 0x28);
|
|
|
|
AF.Low = flags07;
|
|
return 4;
|
|
case 0x08: // EX AF, AF'
|
|
ushort tempAF = AF.Word;
|
|
AF.Word = AF_Prime.Word;
|
|
AF_Prime.Word = tempAF;
|
|
return 4;
|
|
case 0x09: Add16(ref HL, BC.Word); return 11;
|
|
case 0x0A: //LD A (BC)
|
|
AF.High = ReadMemory(BC.Word);
|
|
return 7;
|
|
case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C
|
|
case 0x12: // LD (DE), A
|
|
WriteMemory(DE.Word, AF.High);
|
|
return 7;
|
|
case 0x14: DE.High = Inc8(DE.High); return 4; // INC D
|
|
case 0x19: Add16(ref HL, DE.Word); return 11;
|
|
case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E
|
|
case 0x1E:
|
|
DE.Low = FetchByte(); // LD E, n
|
|
return 7;
|
|
case 0x29: Add16(ref HL, HL.Word); return 11;
|
|
case 0x24: HL.High = Inc8(HL.High); return 4; // INC H
|
|
case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L
|
|
case 0x2E: // LD L, n
|
|
HL.Low = FetchByte();
|
|
return 7;
|
|
case 0x34:
|
|
WriteMemory(HL.Word, Inc8(ReadMemory(HL.Word)));
|
|
return 11; // INC (HL)
|
|
case 0x39: Add16(ref HL, SP); return 11;
|
|
case 0x3C: AF.High = Inc8(AF.High); return 4; // INC A
|
|
|
|
// --- 8-Bit Decrements ---
|
|
case 0x05: BC.High = Dec8(BC.High); return 4; // DEC B
|
|
case 0x0D: BC.Low = Dec8(BC.Low); return 4; // DEC C
|
|
case 0x15: DE.High = Dec8(DE.High); return 4; // DEC D
|
|
case 0x1D: DE.Low = Dec8(DE.Low); return 4; // DEC E
|
|
case 0x25: HL.High = Dec8(HL.High); return 4; // DEC H
|
|
case 0x2D: HL.Low = Dec8(HL.Low); return 4; // DEC L
|
|
case 0x2F: // CPL
|
|
AF.High = (byte)(~AF.High);
|
|
AF.Low |= 0x12;
|
|
AF.Low &= 0xD7; // Ensure undocumented bits follow A
|
|
AF.Low |= (byte)(AF.High & 0x28);
|
|
return 4;
|
|
case 0x35:
|
|
WriteMemory(HL.Word, Dec8(ReadMemory(HL.Word)));
|
|
return 11; // DEC (HL)
|
|
case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A
|
|
case 0x06: // LD B, n
|
|
BC.High = FetchByte();
|
|
return 7;
|
|
case 0x0B: // DEC BC
|
|
BC.Word--;
|
|
return 6;
|
|
case 0x0E: // LD C, n
|
|
BC.Low = FetchByte();
|
|
return 7;
|
|
case 0x0F: // RRCA
|
|
{
|
|
byte bit0 = (byte)(AF.High & 0x01);
|
|
AF.High = (byte)((AF.High >> 1) | (bit0 << 7));
|
|
byte flags0F = (byte)(AF.Low & 0xC4);
|
|
flags0F |= bit0;
|
|
flags0F |= (byte)(AF.High & 0x28);
|
|
AF.Low = flags0F;
|
|
return 4;
|
|
}
|
|
case 0x10: // DJNZ d
|
|
sbyte djnzOffset = (sbyte)FetchByte();
|
|
BC.High--; // Decrement the B register
|
|
if (BC.High != 0)
|
|
{
|
|
PC = (ushort)(PC + djnzOffset);
|
|
return 13; // Jump taken
|
|
}
|
|
return 8; // Loop finished, no jump
|
|
case 0x11: //LD DE, nn
|
|
DE.Word = FetchWord();
|
|
return 10;
|
|
case 0x13: // INC DE
|
|
DE.Word++;
|
|
return 6;
|
|
case 0x16: // LD D, n
|
|
DE.High = FetchByte();
|
|
return 7;
|
|
case 0x17: // RLA
|
|
oldCarry = (byte)(AF.Low & 0x01);
|
|
bool newCarry = (AF.High & 0x80) != 0;
|
|
AF.High = (byte)((AF.High << 1) | oldCarry);
|
|
byte flags17 = (byte)(AF.Low & 0xC4);
|
|
if (newCarry) flags17 |= 0x01;
|
|
flags17 |= (byte)(AF.High & 0x28);
|
|
AF.Low = flags17;
|
|
return 4;
|
|
case 0x18: // JR d
|
|
sbyte jumpDistance = (sbyte)FetchByte();
|
|
PC = (ushort)(PC + jumpDistance);
|
|
return 12;
|
|
case 0x1A: // LD A, (DE)
|
|
AF.High = ReadMemory(DE.Word);
|
|
return 7;
|
|
case 0x1B: // DEC DE
|
|
DE.Word--;
|
|
return 6;
|
|
case 0x1F: // RRA
|
|
{
|
|
oldCarry = (byte)(AF.Low & 0x01);
|
|
byte bit0 = (byte)(AF.High & 0x01);
|
|
AF.High = (byte)((AF.High >> 1) | (oldCarry << 7));
|
|
byte flags1F = (byte)(AF.Low & 0xC4);
|
|
flags1F |= bit0;
|
|
flags1F |= (byte)(AF.High & 0x28);
|
|
AF.Low = flags1F;
|
|
return 4;
|
|
}
|
|
case 0x20: // JR NZ, e
|
|
offset = (sbyte)FetchByte();
|
|
if ((AF.Low & 0x40) == 0)
|
|
{
|
|
PC = (ushort)(PC + offset);
|
|
return 12;
|
|
}
|
|
return 7;
|
|
case 0x21: // LD HL, nn
|
|
HL.Word = FetchWord();
|
|
return 10;
|
|
case 0x22: // LD (nn), HL
|
|
ushort dest22 = FetchWord();
|
|
WriteMemory(dest22, HL.Low);
|
|
WriteMemory((ushort)(dest22 + 1), HL.High);
|
|
return 16;
|
|
case 0x23: // INC HL
|
|
HL.Word++;
|
|
return 6;
|
|
case 0x26: // LD H, n
|
|
HL.High = FetchByte();
|
|
return 7;
|
|
case 0x27: // DAA
|
|
byte a = AF.High;
|
|
int correction = 0;
|
|
byte flags27 = AF.Low;
|
|
|
|
bool carry = (flags27 & 0x01) != 0;
|
|
bool halfCarry = (flags27 & 0x10) != 0;
|
|
bool isSub = (flags27 & 0x02) != 0;
|
|
|
|
if (halfCarry || (a & 0x0F) > 9) correction |= 0x06;
|
|
|
|
if (carry || a > 0x99 || (a >= 0x90 && (a & 0x0F) > 9))
|
|
{
|
|
correction |= 0x60;
|
|
carry = true;
|
|
}
|
|
|
|
bool newHalfCarry = false;
|
|
if (isSub)
|
|
{
|
|
newHalfCarry = halfCarry && (a & 0x0F) < 0x06;
|
|
a = (byte)(a - correction);
|
|
}
|
|
else
|
|
{
|
|
newHalfCarry = ((a & 0x0F) + (correction & 0x0F)) > 0x0F;
|
|
a = (byte)(a + correction);
|
|
}
|
|
|
|
AF.High = a;
|
|
|
|
flags27 &= 0x02;
|
|
if (carry) flags27 |= 0x01;
|
|
if (newHalfCarry) flags27 |= 0x10;
|
|
if ((a & 0x80) != 0) flags27 |= 0x80;
|
|
if (a == 0) flags27 |= 0x40;
|
|
flags27 |= ParityTable[a];
|
|
flags27 |= (byte)(a & 0x28);
|
|
|
|
AF.Low = flags27;
|
|
return 4;
|
|
case 0x28: // JR Z, e
|
|
offset = (sbyte)FetchByte();
|
|
if ((AF.Low & 0x40) != 0)
|
|
{
|
|
PC = (ushort)(PC + offset);
|
|
return 12;
|
|
}
|
|
return 7;
|
|
case 0x2A: // LD HL, (nn)
|
|
{
|
|
ushort srcAddress = FetchWord();
|
|
HL.Low = ReadMemory(srcAddress);
|
|
HL.High = ReadMemory((ushort)(srcAddress + 1));
|
|
return 16;
|
|
}
|
|
case 0x2B: // DEC HL
|
|
HL.Word--;
|
|
return 6;
|
|
case 0x30: // JR NC, e
|
|
offset = (sbyte)FetchByte();
|
|
if ((AF.Low & 0x01) == 0)
|
|
{
|
|
PC = (ushort)(PC + offset);
|
|
return 12;
|
|
}
|
|
return 7;
|
|
case 0x31: // LD SP, nn
|
|
{
|
|
SP = FetchWord();
|
|
return 10;
|
|
}
|
|
case 0x32: // LD (nn), A
|
|
{
|
|
ushort destAddress = FetchWord();
|
|
WriteMemory(destAddress, AF.High);
|
|
return 13;
|
|
}
|
|
case 0x33: // INC SP
|
|
SP++;
|
|
return 6;
|
|
case 0x36: // LD (HL), n
|
|
byte nValue = FetchByte();
|
|
WriteMemory(HL.Word, nValue);
|
|
return 10;
|
|
case 0x37: // SCF
|
|
AF.Low |= 0x01;
|
|
AF.Low &= 0xED;
|
|
AF.Low |= (byte)(AF.High & 0x28);
|
|
return 4;
|
|
case 0x38: // JR C, d
|
|
sbyte jrCOffset = (sbyte)FetchByte();
|
|
if ((AF.Low & 0x01) != 0)
|
|
{
|
|
PC = (ushort)(PC + jrCOffset);
|
|
return 12;
|
|
}
|
|
return 7;
|
|
case 0x3A: // LD A, (nn)
|
|
ushort address3A = FetchWord();
|
|
AF.High = ReadMemory(address3A);
|
|
return 13;
|
|
case 0x3B: // DEC SP
|
|
SP--;
|
|
return 6;
|
|
case 0x3E: //LD A, n
|
|
AF.High = FetchByte();
|
|
return 7;
|
|
case 0x3F: // CCF
|
|
bool previousCarry = (AF.Low & 0x01) != 0;
|
|
AF.Low ^= 0x01;
|
|
AF.Low &= 0xFD;
|
|
if (previousCarry) AF.Low |= 0x10;
|
|
else AF.Low &= 0xEF;
|
|
AF.Low &= 0xD7;
|
|
AF.Low |= (byte)(AF.High & 0x28);
|
|
return 4;
|
|
case 0x40: return 4; // LD B, B
|
|
case 0x41: BC.High = BC.Low; return 4;
|
|
case 0x42: BC.High = DE.High; return 4;
|
|
case 0x43: BC.High = DE.Low; return 4;
|
|
case 0x44: BC.High = HL.High; return 4;
|
|
case 0x45: BC.High = HL.Low; return 4;
|
|
case 0x46: BC.High = ReadMemory(HL.Word); return 7;
|
|
case 0x47: BC.High = AF.High; return 4;
|
|
|
|
// --- LD C, r ---
|
|
case 0x48: BC.Low = BC.High; return 4;
|
|
case 0x49: 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;
|
|
case 0x4D: BC.Low = HL.Low; return 4;
|
|
case 0x4E: BC.Low = ReadMemory(HL.Word); return 7;
|
|
case 0x4F: BC.Low = AF.High; return 4;
|
|
|
|
// --- LD D, r ---
|
|
case 0x50: DE.High = BC.High; return 4;
|
|
case 0x51: DE.High = BC.Low; return 4;
|
|
case 0x52: 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;
|
|
case 0x56: DE.High = ReadMemory(HL.Word); return 7;
|
|
case 0x57: DE.High = AF.High; return 4;
|
|
|
|
// --- LD E, r ---
|
|
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: return 4;
|
|
case 0x5C: DE.Low = HL.High; return 4;
|
|
case 0x5D: DE.Low = HL.Low; return 4;
|
|
case 0x5E: DE.Low = ReadMemory(HL.Word); return 7;
|
|
case 0x5F: DE.Low = AF.High; return 4;
|
|
|
|
// --- LD H, r ---
|
|
case 0x60: HL.High = BC.High; return 4;
|
|
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: return 4;
|
|
case 0x65: HL.High = HL.Low; return 4;
|
|
case 0x66: HL.High = ReadMemory(HL.Word); return 7;
|
|
case 0x67: HL.High = AF.High; return 4;
|
|
|
|
// --- LD L, r ---
|
|
case 0x68: HL.Low = BC.High; return 4;
|
|
case 0x69: HL.Low = BC.Low; return 4;
|
|
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: return 4;
|
|
case 0x6E: HL.Low = ReadMemory(HL.Word); return 7;
|
|
case 0x6F: HL.Low = AF.High; return 4;
|
|
|
|
// --- LD (HL), r ---
|
|
case 0x70: WriteMemory(HL.Word, BC.High); return 7;
|
|
case 0x71: WriteMemory(HL.Word, BC.Low); return 7;
|
|
case 0x72: WriteMemory(HL.Word, DE.High); return 7;
|
|
case 0x73: WriteMemory(HL.Word, DE.Low); return 7;
|
|
case 0x74: WriteMemory(HL.Word, HL.High); return 7;
|
|
case 0x75: WriteMemory(HL.Word, HL.Low); return 7;
|
|
case 0x76: //HALT
|
|
if (!InterruptRequested)
|
|
{
|
|
PC--;
|
|
return 4;
|
|
}
|
|
else
|
|
{
|
|
InterruptRequested = false;
|
|
return 4;
|
|
}
|
|
case 0x77: WriteMemory(HL.Word, AF.High); return 7;
|
|
|
|
// --- LD A, r ---
|
|
case 0x78: AF.High = BC.High; return 4;
|
|
case 0x79: AF.High = BC.Low; return 4;
|
|
case 0x7A: AF.High = DE.High; return 4;
|
|
case 0x7B: AF.High = DE.Low; return 4;
|
|
case 0x7C: AF.High = HL.High; return 4;
|
|
case 0x7D: AF.High = HL.Low; return 4;
|
|
case 0x7E: AF.High = ReadMemory(HL.Word); return 7;
|
|
case 0x7F: return 4;
|
|
case 0x80: AddA(BC.High); return 4; // ADD A, B
|
|
case 0x81: AddA(BC.Low); return 4; // ADD A, C
|
|
case 0x82: AddA(DE.High); return 4; // ADD A, D
|
|
case 0x83: AddA(DE.Low); return 4; // ADD A, E
|
|
case 0x84: AddA(HL.High); return 4; // ADD A, H
|
|
case 0x85: AddA(HL.Low); return 4; // ADD A, L
|
|
case 0x86: AddA(ReadMemory(HL.Word)); return 7; // ADD A, (HL)
|
|
case 0x87: AddA(AF.High); return 4; // ADD A, A
|
|
|
|
// --- ADC A, Register Family ---
|
|
case 0x88: AdcA(BC.High); return 4; // ADC A, B
|
|
case 0x89: AdcA(BC.Low); return 4; // ADC A, C
|
|
case 0x8A: AdcA(DE.High); return 4; // ADC A, D
|
|
case 0x8B: AdcA(DE.Low); return 4; // ADC A, E
|
|
case 0x8C: AdcA(HL.High); return 4; // ADC A, H
|
|
case 0x8D: AdcA(HL.Low); return 4; // ADC A, L
|
|
case 0x8F: AdcA(AF.High); return 4; // ADC A, A
|
|
|
|
case 0x8E: // ADC A, (HL)
|
|
AdcA(ReadMemory(HL.Word));
|
|
return 7;
|
|
|
|
case 0xCE: // ADC A, n
|
|
AdcA(FetchByte());
|
|
return 7;
|
|
|
|
// --- SUB A, r ---
|
|
case 0x90: SubA(BC.High, false); return 4;
|
|
case 0x91: SubA(BC.Low, false); return 4;
|
|
case 0x92: SubA(DE.High, false); return 4;
|
|
case 0x93: SubA(DE.Low, false); return 4;
|
|
case 0x94: SubA(HL.High, false); return 4;
|
|
case 0x95: SubA(HL.Low, false); return 4;
|
|
case 0x96: SubA(ReadMemory(HL.Word), false); return 7;
|
|
case 0x97: SubA(AF.High, false); return 4;
|
|
|
|
// --- SBC A, r ---
|
|
case 0x98: SbcA(BC.High); return 4;
|
|
case 0x99: SbcA(BC.Low); return 4;
|
|
case 0x9A: SbcA(DE.High); return 4;
|
|
case 0x9B: SbcA(DE.Low); return 4;
|
|
case 0x9C: SbcA(HL.High); return 4;
|
|
case 0x9D: SbcA(HL.Low); return 4;
|
|
case 0x9E: SbcA(ReadMemory(HL.Word)); return 7;
|
|
case 0x9F: SbcA(AF.High); return 4;
|
|
|
|
case 0xA0: And(BC.High); return 4; // AND B
|
|
case 0xA1: And(BC.Low); return 4; // AND C
|
|
case 0xA2: And(DE.High); return 4; // AND D
|
|
case 0xA3: And(DE.Low); return 4; // AND E
|
|
case 0xA4: And(HL.High); return 4; // AND H
|
|
case 0xA5: And(HL.Low); return 4; // AND L
|
|
case 0xA6: And(ReadMemory(HL.Word)); return 7; // AND (HL)
|
|
case 0xA7: And(AF.High); return 4; // AND A
|
|
case 0xA8: Xor(BC.High); return 4; // XOR B
|
|
case 0xA9: Xor(BC.Low); return 4; // XOR C
|
|
case 0xAA: Xor(DE.High); return 4; // XOR D
|
|
case 0xAB: Xor(DE.Low); return 4; // XOR E
|
|
case 0xAC: Xor(HL.High); return 4; // XOR H
|
|
case 0xAD: Xor(HL.Low); return 4; // XOR L
|
|
case 0xAE: Xor(ReadMemory(HL.Word)); return 7; // XOR (HL)
|
|
case 0xAF: Xor(AF.High); return 4; // XOR A
|
|
|
|
// --- OR r ---
|
|
case 0xB0: Or(BC.High); return 4; // OR B
|
|
case 0xB1: Or(BC.Low); return 4; // OR C
|
|
case 0xB2: Or(DE.High); return 4; // OR D
|
|
case 0xB3: Or(DE.Low); return 4; // OR E
|
|
case 0xB4: Or(HL.High); return 4; // OR H
|
|
case 0xB5: Or(HL.Low); return 4; // OR L
|
|
case 0xB6: Or(ReadMemory(HL.Word)); return 7; // OR (HL)
|
|
case 0xB7: Or(AF.High); return 4; // OR A
|
|
|
|
// --- CP r ---
|
|
case 0xB8: SubA(BC.High, true); return 4;
|
|
case 0xB9: SubA(BC.Low, true); return 4;
|
|
case 0xBA: SubA(DE.High, true); return 4;
|
|
case 0xBB: SubA(DE.Low, true); return 4;
|
|
case 0xBC: SubA(HL.High, true); return 4;
|
|
case 0xBD: SubA(HL.Low, true); return 4;
|
|
case 0xBE: SubA(ReadMemory(HL.Word), true); return 7;
|
|
case 0xBF: SubA(AF.High, true); return 4;
|
|
|
|
// --- Conditional Returns (11 T-States if taken, 5 if not) ---
|
|
case 0xC0: // RET NZ
|
|
if ((AF.Low & 0x40) == 0) { PC = Pop(); return 11; }
|
|
return 5;
|
|
case 0xE0: // RET PO (Parity Odd / No Overflow)
|
|
if ((AF.Low & 0x04) == 0) { PC = Pop(); return 11; }
|
|
return 5;
|
|
case 0xE8: // RET PE (Parity Even / Overflow)
|
|
if ((AF.Low & 0x04) != 0) { PC = Pop(); return 11; }
|
|
return 5;
|
|
case 0xF0: // RET P (Sign Positive)
|
|
if ((AF.Low & 0x80) == 0) { PC = Pop(); return 11; }
|
|
return 5;
|
|
case 0xF8: // RET M (Sign Minus)
|
|
if ((AF.Low & 0x80) != 0) { PC = Pop(); return 11; }
|
|
return 5;
|
|
case 0xC1: // POP BC
|
|
BC.Word = Pop();
|
|
return 10;
|
|
// --- Absolute Conditional Jumps (Always 10 T-States) ---
|
|
case 0xC2: // JP NZ, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x40) == 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xCA: // JP Z, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x40) != 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xD2: // JP NC, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x01) == 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xDA: // JP C, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x01) != 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xDB: // IN A, (n)
|
|
byte portOffsetDB = FetchByte();
|
|
ushort portAddressDB = (ushort)((AF.High << 8) | portOffsetDB);
|
|
AF.High = _simpleIoBus.ReadPort(portAddressDB);
|
|
return 11;
|
|
case 0xE2: // JP PO, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x04) == 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xEA: // JP PE, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x04) != 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xF2: // JP P, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x80) == 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xFA: // JP M, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x80) != 0) PC = addr;
|
|
return 10;
|
|
}
|
|
case 0xC3:
|
|
PC = FetchWord();
|
|
return 10;
|
|
// --- Absolute Conditional Calls (17 T-States if taken, 10 if not) ---
|
|
case 0xC4: // CALL NZ, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x40) == 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xCC: // CALL Z, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x40) != 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xD4: // CALL NC, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x01) == 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xDC: // CALL C, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x01) != 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xE4: // CALL PO, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x04) == 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xEC: // CALL PE, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x04) != 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xF4: // CALL P, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x80) == 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xFC: // CALL M, nn
|
|
{
|
|
ushort addr = FetchWord();
|
|
if ((AF.Low & 0x80) != 0) { Push(PC); PC = addr; return 17; }
|
|
return 10;
|
|
}
|
|
case 0xc5: //push bc
|
|
Push(BC.Word);
|
|
return 11;
|
|
case 0xC6: // ADD A, n
|
|
AddA(FetchByte());
|
|
return 7;
|
|
// --- RST Instructions (11 T-States) ---
|
|
case 0xC7: Push(PC); PC = 0x0000; return 11;
|
|
case 0xCF: Push(PC); PC = 0x0008; return 11;
|
|
case 0xD7: Push(PC); PC = 0x0010; return 11;
|
|
case 0xDF: Push(PC); PC = 0x0018; return 11;
|
|
case 0xE7: Push(PC); PC = 0x0020; return 11;
|
|
case 0xEF: Push(PC); PC = 0x0028; return 11;
|
|
case 0xF7: Push(PC); PC = 0x0030; return 11;
|
|
case 0xFF: Push(PC); PC = 0x0038; return 11;
|
|
case 0xC8: // RET Z
|
|
if ((AF.Low & 0x40) != 0)
|
|
{
|
|
PC = Pop();
|
|
return 11;
|
|
}
|
|
return 5;
|
|
case 0xC9: // RET
|
|
PC = Pop();
|
|
return 10;
|
|
case 0xCB:
|
|
return ExecuteCBPrefix();
|
|
case 0xCD: // CALL nn
|
|
ushort callAddress = FetchWord();
|
|
Push(PC);
|
|
PC = callAddress;
|
|
return 17;
|
|
case 0xD0: // RET NC
|
|
if ((AF.Low & 0x01) == 0)
|
|
{
|
|
PC = Pop();
|
|
return 11;
|
|
}
|
|
return 5;
|
|
case 0xD1: // POP DE
|
|
DE.Word = Pop();
|
|
return 10;
|
|
case 0xD3: // OUT (n), A
|
|
byte portOffset = FetchByte();
|
|
ushort portAddress = (ushort)((AF.High << 8) | portOffset);
|
|
_simpleIoBus.WritePort(portAddress, AF.High);
|
|
return 11;
|
|
case 0xd5: //push de
|
|
Push(DE.Word);
|
|
return 11;
|
|
case 0xD6: // SUB n
|
|
SubA(FetchByte(), false);
|
|
return 7;
|
|
case 0xD8: // RET C
|
|
if ((AF.Low & 0x01) != 0)
|
|
{
|
|
PC = Pop();
|
|
return 11;
|
|
}
|
|
return 5;
|
|
case 0xD9: // EXX
|
|
ushort tempBC = BC.Word;
|
|
BC.Word = BC_Prime.Word;
|
|
BC_Prime.Word = tempBC;
|
|
|
|
ushort tempDE = DE.Word;
|
|
DE.Word = DE_Prime.Word;
|
|
DE_Prime.Word = tempDE;
|
|
|
|
ushort tempHL = HL.Word;
|
|
HL.Word = HL_Prime.Word;
|
|
HL_Prime.Word = tempHL;
|
|
|
|
return 4;
|
|
case 0xDD:
|
|
return ExecuteDDPrefix();
|
|
case 0xDE: // SBC A, n
|
|
SbcA(FetchByte());
|
|
return 7;
|
|
case 0xE1: // POP HL
|
|
HL.Word = Pop();
|
|
return 10;
|
|
case 0xE3: // EX (SP), HL
|
|
byte spLow = ReadMemory(SP);
|
|
byte spHigh = ReadMemory((ushort)(SP + 1));
|
|
|
|
WriteMemory(SP, HL.Low);
|
|
WriteMemory((ushort)(SP + 1), HL.High);
|
|
|
|
HL.Low = spLow;
|
|
HL.High = spHigh;
|
|
|
|
return 19;
|
|
case 0xe5: //push hl
|
|
Push(HL.Word);
|
|
return 11;
|
|
case 0xE6: // AND n
|
|
And(FetchByte());
|
|
return 7;
|
|
case 0xE9: // JP (HL)
|
|
PC = HL.Word;
|
|
return 4;
|
|
case 0xEB: // EX DE, HL
|
|
ushort tempEx = DE.Word;
|
|
DE.Word = HL.Word;
|
|
HL.Word = tempEx;
|
|
return 4;
|
|
case 0xED:
|
|
return ExecuteExtendedPrefix();
|
|
case 0xEE: // XOR n
|
|
Xor(FetchByte());
|
|
return 7;
|
|
case 0xF1: // POP AF
|
|
AF.Word = Pop();
|
|
return 10;
|
|
case 0xF3: // DI
|
|
IFF1 = false;
|
|
IFF2 = false;
|
|
return 4;
|
|
case 0xf5: //push af
|
|
Push(AF.Word);
|
|
return 11;
|
|
case 0xF6: // OR n
|
|
Or(FetchByte());
|
|
return 7;
|
|
case 0xF9: // LD SP, HL
|
|
SP = HL.Word;
|
|
return 6;
|
|
case 0xFB: // EI
|
|
IFF1 = true;
|
|
IFF2 = true;
|
|
return 4;
|
|
case 0xFD:
|
|
return ExecuteFDPrefix();
|
|
case 0xFE: // CP n
|
|
SubA(FetchByte(), true);
|
|
return 7;
|
|
default:
|
|
throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
|
|
}
|
|
}
|
|
|
|
private int ExecuteExtendedPrefix() //ED
|
|
{
|
|
byte extendedOpcode = FetchByte();
|
|
//byte val = 0;
|
|
|
|
switch (extendedOpcode)
|
|
{
|
|
case 0x43: // LD (nn), BC
|
|
ushort dest43 = FetchWord();
|
|
WriteMemory(dest43, BC.Low);
|
|
WriteMemory((ushort)(dest43 + 1), BC.High);
|
|
return 20;
|
|
case 0x44: // NEG
|
|
{
|
|
int aBefore = AF.High;
|
|
int resultNeg = 0 - aBefore;
|
|
|
|
byte flagsNeg = 0;
|
|
|
|
if ((resultNeg & 0x80) != 0) flagsNeg |= 0x80;
|
|
if ((resultNeg & 0xFF) == 0) flagsNeg |= 0x40;
|
|
if ((0 - (aBefore & 0x0F)) < 0) flagsNeg |= 0x10;
|
|
if (aBefore == 0x80) flagsNeg |= 0x04;
|
|
flagsNeg |= 0x02;
|
|
if (aBefore != 0) flagsNeg |= 0x01;
|
|
|
|
flagsNeg |= (byte)(resultNeg & 0x28);
|
|
|
|
AF.Low = flagsNeg;
|
|
AF.High = (byte)resultNeg;
|
|
|
|
return 8;
|
|
}
|
|
case 0x47: // LD I, A
|
|
I = AF.High;
|
|
return 9;
|
|
case 0x4B: // LD BC, (nn)
|
|
ushort src4B = FetchWord();
|
|
BC.Low = ReadMemory(src4B);
|
|
BC.High = ReadMemory((ushort)(src4B + 1));
|
|
return 20;
|
|
case 0x4D: // RETI Does not affect IFF1 or IFF2
|
|
PC = Pop();
|
|
return 14;
|
|
case 0x53: // LD (nn), DE
|
|
ushort dest53 = FetchWord();
|
|
WriteMemory(dest53, DE.Low);
|
|
WriteMemory((ushort)(dest53 + 1), DE.High);
|
|
return 20;
|
|
case 0x56: // IM 1
|
|
InterruptMode = 1;
|
|
return 8;
|
|
case 0x58: // IN E, (C)
|
|
byte inVal58 = ReadPort(BC.Word);
|
|
DE.Low = inVal58;
|
|
|
|
byte flags58 = (byte)(AF.Low & 0x01);
|
|
if ((inVal58 & 0x80) != 0) flags58 |= 0x80;
|
|
if (inVal58 == 0) flags58 |= 0x40;
|
|
flags58 |= ParityTable[inVal58];
|
|
flags58 |= (byte)(inVal58 & 0x28);
|
|
|
|
AF.Low = flags58;
|
|
return 12;
|
|
case 0x5B: // LD DE, (nn)
|
|
ushort src5B = FetchWord();
|
|
DE.Low = ReadMemory(src5B);
|
|
DE.High = ReadMemory((ushort)(src5B + 1));
|
|
return 20;
|
|
case 0x5E: // IM 2
|
|
InterruptMode = 2;
|
|
return 8;
|
|
case 0x5F: // LD A, R
|
|
{
|
|
AF.High = R;
|
|
byte flags5F = (byte)(AF.Low & 0x01);
|
|
if ((AF.High & 0x80) != 0) flags5F |= 0x80;
|
|
if (AF.High == 0) flags5F |= 0x40;
|
|
if (IFF2) flags5F |= 0x04;
|
|
flags5F |= (byte)(AF.High & 0x28);
|
|
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;
|
|
case 0x62: SbcHl(HL.Word); return 15;
|
|
case 0x72: SbcHl(SP); return 15;
|
|
case 0x73: // LD (nn), SP
|
|
ushort dest73 = FetchWord();
|
|
WriteMemory(dest73, (byte)SP);
|
|
WriteMemory((ushort)(dest73 + 1), (byte)(SP >> 8));
|
|
return 20;
|
|
case 0x78: // IN A, (C)
|
|
byte portVal78 = ReadPort(BC.Word);
|
|
AF.High = portVal78;
|
|
|
|
byte flags78 = (byte)(AF.Low & 0x01);
|
|
if ((portVal78 & 0x80) != 0) flags78 |= 0x80;
|
|
if (portVal78 == 0) flags78 |= 0x40;
|
|
flags78 |= ParityTable[portVal78];
|
|
flags78 |= (byte)(portVal78 & 0x28);
|
|
|
|
AF.Low = flags78;
|
|
return 12;
|
|
case 0x79: // OUT (C), A
|
|
_simpleIoBus.WritePort(BC.Word, AF.High);
|
|
return 12;
|
|
// --- ADC HL, rr ---
|
|
case 0x4A: Adc16(BC.Word); return 15;
|
|
case 0x5A: Adc16(DE.Word); return 15;
|
|
case 0x6A: Adc16(HL.Word); return 15;
|
|
case 0x7A: Adc16(SP); return 15;
|
|
case 0x7B: // LD SP, (nn)
|
|
byte addrLow = FetchByte();
|
|
byte addrHigh = FetchByte();
|
|
ushort address7B = (ushort)((addrHigh << 8) | addrLow);
|
|
|
|
byte spLow = ReadMemory(address7B);
|
|
byte spHigh = ReadMemory((ushort)(address7B + 1));
|
|
|
|
SP = (ushort)((spHigh << 8) | spLow);
|
|
return 20;
|
|
// --- BLOCK LOADS ---
|
|
case 0xA0: // LDI
|
|
{
|
|
byte val0 = ReadMemory(HL.Word);
|
|
WriteMemory(DE.Word, val0);
|
|
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
|
|
|
|
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;
|
|
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;
|
|
}
|
|
case 0xB8: // LDDR
|
|
{
|
|
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;
|
|
}
|
|
default:
|
|
throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
|
|
}
|
|
}
|
|
|
|
private int ExecuteCBPrefix()
|
|
{
|
|
byte cbOpcode = FetchByte();
|
|
//bool oldCarry = false;
|
|
|
|
int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET
|
|
int bitIndex = (cbOpcode >> 3) & 0x07;
|
|
int regIndex = cbOpcode & 0x07;
|
|
|
|
byte bitMask = (byte)(1 << bitIndex);
|
|
|
|
// --- PHASE 1: Fetch the target value ---
|
|
byte val = 0;
|
|
switch (regIndex)
|
|
{
|
|
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 = ReadMemory(HL.Word); break; // The 0x110 (HL) exception
|
|
case 7: val = AF.High; break;
|
|
}
|
|
|
|
// --- 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
|
|
|
|
if ((val & bitMask) == 0)
|
|
{
|
|
AF.Low |= 0x44; // Set Zero and P/V
|
|
}
|
|
else if (bitIndex == 7)
|
|
{
|
|
AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign
|
|
}
|
|
|
|
// --- 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;
|
|
|
|
case 3: // ALL SET Instructions
|
|
val |= bitMask;
|
|
break;
|
|
default:
|
|
throw new Exception("Invalid CB operation.");
|
|
}
|
|
|
|
// --- PHASE 3: Write back the modified value ---
|
|
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: WriteMemory(HL.Word, val); break;
|
|
case 7: AF.High = val; break;
|
|
}
|
|
|
|
return (regIndex == 6) ? 15 : 8;
|
|
}
|
|
|
|
private int ExecuteDDPrefix()
|
|
{
|
|
byte ddOpcode = FetchByte();
|
|
|
|
switch (ddOpcode)
|
|
{
|
|
case 0x09: Add16(ref IX, BC.Word); return 15;
|
|
case 0x19: Add16(ref IX, DE.Word); return 15;
|
|
case 0x29: Add16(ref IX, IX.Word); return 15;
|
|
case 0x39: Add16(ref IX, SP); return 15;
|
|
case 0x21: // LD IX, nn
|
|
byte low = FetchByte();
|
|
byte high = FetchByte();
|
|
IX.Word = (ushort)((high << 8) | low);
|
|
return 14;
|
|
case 0x22: // LD (nn), IX
|
|
byte addrLow22 = FetchByte();
|
|
byte addrHigh22 = FetchByte();
|
|
ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22);
|
|
WriteMemory(address22, IX.Low);
|
|
WriteMemory((ushort)(address22 + 1), IX.High);
|
|
return 20;
|
|
case 0x23: // INC IX
|
|
IX.Word++;
|
|
return 10;
|
|
case 0x24: // INC IXH
|
|
IX.High = Inc8(IX.High);
|
|
return 8;
|
|
case 0x25: // DEC IXH
|
|
IX.High = Dec8(IX.High);
|
|
return 8;
|
|
case 0x26: // LD IXH, n
|
|
IX.High = FetchByte();
|
|
return 11;
|
|
case 0x2A: // LD IX, (nn)
|
|
byte addrLow2A = FetchByte();
|
|
byte addrHigh2A = FetchByte();
|
|
ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A);
|
|
byte ixLow = ReadMemory(address2A);
|
|
byte ixHigh = ReadMemory((ushort)(address2A + 1));
|
|
IX.Word = (ushort)((ixHigh << 8) | ixLow);
|
|
return 20;
|
|
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;
|
|
case 0x2E: // LD IXL, n
|
|
IX.Low = FetchByte();
|
|
return 11;
|
|
case 0x34: // INC (IX+d)
|
|
{
|
|
sbyte offset34 = (sbyte)FetchByte();
|
|
ushort address34 = (ushort)(IX.Word + offset34);
|
|
byte val34 = ReadMemory(address34);
|
|
byte result34 = Inc8(val34);
|
|
WriteMemory(address34, result34);
|
|
return 23;
|
|
}
|
|
case 0x35: // DEC (IX+d)
|
|
{
|
|
sbyte offset35 = (sbyte)FetchByte();
|
|
ushort address35 = (ushort)(IX.Word + offset35);
|
|
byte val35 = ReadMemory(address35);
|
|
byte result35 = Dec8(val35);
|
|
WriteMemory(address35, result35);
|
|
return 23;
|
|
}
|
|
case 0x36: // LD (IX+d), n
|
|
sbyte offset36 = (sbyte)FetchByte();
|
|
byte n36 = FetchByte();
|
|
ushort address36 = (ushort)(IX.Word + offset36);
|
|
WriteMemory(address36, n36);
|
|
return 19;
|
|
case 0x46: // LD B, (IX+d)
|
|
sbyte offset46 = (sbyte)FetchByte();
|
|
ushort address46 = (ushort)(IX.Word + offset46);
|
|
BC.High = ReadMemory(address46);
|
|
return 19;
|
|
case 0x4E: // LD C, (IX+d)
|
|
sbyte offset4E = (sbyte)FetchByte();
|
|
ushort address4E = (ushort)(IX.Word + offset4E);
|
|
BC.Low = ReadMemory(address4E);
|
|
return 19;
|
|
case 0x56: // LD D, (IX+d)
|
|
sbyte offset56 = (sbyte)FetchByte();
|
|
ushort address56 = (ushort)(IX.Word + offset56);
|
|
DE.High = ReadMemory(address56);
|
|
return 19;
|
|
case 0x5E: // LD E, (IX+d)
|
|
sbyte offset5E = (sbyte)FetchByte();
|
|
ushort address5E = (ushort)(IX.Word + offset5E);
|
|
DE.Low = ReadMemory(address5E);
|
|
return 19;
|
|
case 0x66: // LD H, (IX+d)
|
|
sbyte offset66 = (sbyte)FetchByte();
|
|
ushort address66 = (ushort)(IX.Word + offset66);
|
|
HL.High = ReadMemory(address66);
|
|
return 19;
|
|
case 0x67: // LD IXH, A
|
|
IX.High = AF.High;
|
|
return 8;
|
|
case 0x6E: // LD L, (IX+d)
|
|
sbyte offset6E = (sbyte)FetchByte();
|
|
ushort address6E = (ushort)(IX.Word + offset6E);
|
|
HL.Low = ReadMemory(address6E);
|
|
return 19;
|
|
case 0x6F: // LD IXL, A
|
|
IX.Low = AF.High;
|
|
return 8;
|
|
case 0x70: // LD (IX+d), B
|
|
sbyte offset70 = (sbyte)FetchByte();
|
|
ushort address70 = (ushort)(IX.Word + offset70);
|
|
WriteMemory(address70, BC.High);
|
|
return 19;
|
|
case 0x71: // LD (IX+d), C
|
|
sbyte offset71 = (sbyte)FetchByte();
|
|
ushort address71 = (ushort)(IX.Word + offset71);
|
|
WriteMemory(address71, BC.Low);
|
|
return 19;
|
|
case 0x72: // LD (IX+d), D
|
|
sbyte offset72 = (sbyte)FetchByte();
|
|
ushort address72 = (ushort)(IX.Word + offset72);
|
|
WriteMemory(address72, DE.High);
|
|
return 19;
|
|
case 0x73: // LD (IX+d), E
|
|
sbyte offset73 = (sbyte)FetchByte();
|
|
ushort address73 = (ushort)(IX.Word + offset73);
|
|
WriteMemory(address73, DE.Low);
|
|
return 19;
|
|
case 0x74: // LD (IX+d), H
|
|
sbyte offset74 = (sbyte)FetchByte();
|
|
ushort address74 = (ushort)(IX.Word + offset74);
|
|
WriteMemory(address74, HL.High);
|
|
return 19;
|
|
case 0x75: // LD (IX+d), L
|
|
sbyte offset75 = (sbyte)FetchByte();
|
|
ushort address75 = (ushort)(IX.Word + offset75);
|
|
WriteMemory(address75, HL.Low);
|
|
return 19;
|
|
case 0x77: // LD (IX+d), A
|
|
sbyte offset77 = (sbyte)FetchByte();
|
|
ushort address77 = (ushort)(IX.Word + offset77);
|
|
WriteMemory(address77, AF.High);
|
|
return 19;
|
|
case 0x7C: // LD A, IXH
|
|
AF.High = IX.High;
|
|
return 8;
|
|
case 0x7E: // LD A, (IX+d)
|
|
sbyte offset7E = (sbyte)FetchByte();
|
|
ushort address7E = (ushort)(IX.Word + offset7E);
|
|
AF.High = ReadMemory(address7E);
|
|
return 19;
|
|
case 0x84: // ADD A, IXH
|
|
AddA(IX.High);
|
|
return 8;
|
|
case 0x85: // ADD A, IXL
|
|
AddA(IX.Low);
|
|
return 8;
|
|
case 0x86: // ADD A, (IX+d)
|
|
{
|
|
sbyte displacementAdd = (sbyte)FetchByte();
|
|
ushort targetAddressAdd = (ushort)(IX.Word + displacementAdd);
|
|
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();
|
|
ushort address96 = (ushort)(IX.Word + offset96);
|
|
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();
|
|
ushort addressBE = (ushort)(IX.Word + offsetBE);
|
|
SubA(ReadMemory(addressBE), true);
|
|
return 19;
|
|
}
|
|
case 0xCB: // The DD CB nested prefix
|
|
{
|
|
sbyte displacement = (sbyte)FetchByte();
|
|
byte cbOpcode = FetchByte();
|
|
ushort targetAddress = (ushort)(IX.Word + displacement);
|
|
byte memVal = ReadMemory(targetAddress);
|
|
|
|
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;
|
|
|
|
if ((memVal & bitMask) == 0)
|
|
{
|
|
AF.Low |= 0x44;
|
|
}
|
|
else if (bitIndex == 7)
|
|
{
|
|
AF.Low |= 0x80;
|
|
}
|
|
return 20;
|
|
|
|
case 2: // ALL RES Instructions
|
|
memVal &= (byte)(~bitMask);
|
|
WriteMemory(targetAddress, memVal);
|
|
return 23;
|
|
|
|
case 3: // ALL SET Instructions
|
|
memVal |= bitMask;
|
|
WriteMemory(targetAddress, memVal);
|
|
return 23;
|
|
|
|
default:
|
|
throw new Exception("Invalid bitwise operation.");
|
|
}
|
|
}
|
|
case 0xE1: // POP IX
|
|
byte popLow = ReadMemory(SP);
|
|
SP++;
|
|
byte popHigh = ReadMemory(SP);
|
|
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);
|
|
SP--;
|
|
WriteMemory(SP, IX.Low);
|
|
return 15;
|
|
case 0xE9: // JP (IX)
|
|
PC = IX.Word;
|
|
return 8;
|
|
case 0xF9: // LD SP, IX
|
|
SP = IX.Word;
|
|
return 10;
|
|
default:
|
|
// 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 ddOpcode = FetchByte();
|
|
ushort targetAddress = 0;
|
|
byte memVal = 0;
|
|
|
|
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();
|
|
ushort address34 = (ushort)(IY.Word + offset34);
|
|
byte valBefore = ReadMemory(address34);
|
|
byte result34 = Inc8(valBefore);
|
|
WriteMemory(address34, result34);
|
|
return 23;
|
|
}
|
|
case 0x35: // DEC (IY+d)
|
|
sbyte offset = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset);
|
|
memVal = ReadMemory(targetAddress);
|
|
byte decVal = Dec8(memVal);
|
|
WriteMemory(targetAddress, decVal);
|
|
return 23;
|
|
case 0x36: // LD (IY+d), n
|
|
{
|
|
sbyte offset36 = (sbyte)FetchByte();
|
|
byte nValue = FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset36);
|
|
WriteMemory(targetAddress, nValue);
|
|
return 19;
|
|
}
|
|
case 0x39: Add16(ref IY, SP); return 15;
|
|
case 0x46: // LD B, (IY+d)
|
|
{
|
|
sbyte displacement = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + displacement);
|
|
BC.High = ReadMemory(targetAddress);
|
|
return 19;
|
|
}
|
|
case 0x4E: // LD C, (IY+d)
|
|
sbyte offset4E = (sbyte)FetchByte();
|
|
ushort address4E = (ushort)(IY.Word + offset4E);
|
|
BC.Low = ReadMemory(address4E);
|
|
return 19;
|
|
case 0x56: // LD D, (IY+d)
|
|
sbyte offset56 = (sbyte)FetchByte();
|
|
ushort address56 = (ushort)(IY.Word + offset56);
|
|
DE.High = ReadMemory(address56);
|
|
return 19;
|
|
case 0x5E: // LD E, (IY+d)
|
|
sbyte offset5E = (sbyte)FetchByte();
|
|
ushort address5E = (ushort)(IY.Word + offset5E);
|
|
DE.Low = ReadMemory(address5E);
|
|
return 19;
|
|
case 0x66: // LD H, (IY+d)
|
|
sbyte offset66 = (sbyte)FetchByte();
|
|
ushort address66 = (ushort)(IY.Word + offset66);
|
|
HL.High = ReadMemory(address66);
|
|
return 19;
|
|
case 0x6E: // LD L, (IY+d)
|
|
sbyte displacementVal = (sbyte)FetchByte();
|
|
ushort targetAddr = (ushort)(IY.Word + displacementVal);
|
|
HL.Low = ReadMemory(targetAddr);
|
|
return 19;
|
|
case 0x71: // LD (IY+d), C
|
|
{
|
|
sbyte offset71 = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset71);
|
|
WriteMemory(targetAddress, BC.Low);
|
|
return 19;
|
|
}
|
|
case 0x72: // LD (IY+d), D
|
|
sbyte offset72 = (sbyte)FetchByte();
|
|
ushort address72 = (ushort)(IY.Word + offset72);
|
|
WriteMemory(address72, DE.High);
|
|
return 19;
|
|
case 0x73: // LD (IY+d), E
|
|
sbyte offset73 = (sbyte)FetchByte();
|
|
ushort address73 = (ushort)(IY.Word + offset73);
|
|
WriteMemory(address73, DE.Low);
|
|
return 19;
|
|
case 0x74: // LD (IY+d), H
|
|
sbyte offset74 = (sbyte)FetchByte();
|
|
ushort address74 = (ushort)(IY.Word + offset74);
|
|
WriteMemory(address74, HL.High);
|
|
return 19;
|
|
case 0x75: // LD (IY+d), L
|
|
sbyte offset75 = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset75);
|
|
WriteMemory(targetAddress, HL.Low);
|
|
return 19;
|
|
case 0x77: // LD (IY+d), A
|
|
sbyte offset77 = (sbyte)FetchByte();
|
|
ushort address77 = (ushort)(IY.Word + offset77);
|
|
WriteMemory(address77, AF.High);
|
|
return 19;
|
|
case 0x7E: // LD A, (IY+d)
|
|
sbyte offset7E = (sbyte)FetchByte();
|
|
ushort address7E = (ushort)(IY.Word + offset7E);
|
|
AF.High = ReadMemory(address7E);
|
|
return 19;
|
|
case 0x84: // ADD A, IYH
|
|
AddA(IY.High);
|
|
return 8;
|
|
case 0x85: // ADD A, IYL
|
|
AddA(IY.Low);
|
|
return 8;
|
|
case 0x86: // ADD A, (IY+d)
|
|
{
|
|
sbyte displacementAdd = (sbyte)FetchByte();
|
|
ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd);
|
|
byte valueToAdd = ReadMemory(targetAddressAdd);
|
|
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();
|
|
ushort address96 = (ushort)(IY.Word + offset96);
|
|
SubA(ReadMemory(address96), false);
|
|
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();
|
|
ushort addressBE = (ushort)(IY.Word + offsetBE);
|
|
SubA(ReadMemory(addressBE), true);
|
|
return 19;
|
|
}
|
|
case 0xCB: // The FD CB nested prefix
|
|
{
|
|
sbyte displacement = (sbyte)FetchByte();
|
|
byte cbOpcode = FetchByte();
|
|
targetAddress = (ushort)(IY.Word + displacement);
|
|
memVal = ReadMemory(targetAddress);
|
|
|
|
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;
|
|
|
|
if ((memVal & bitMask) == 0)
|
|
{
|
|
AF.Low |= 0x44;
|
|
}
|
|
else if (bitIndex == 7)
|
|
{
|
|
AF.Low |= 0x80;
|
|
}
|
|
return 20;
|
|
|
|
case 2: // ALL RES Instructions
|
|
memVal &= (byte)(~bitMask);
|
|
WriteMemory(targetAddress, memVal);
|
|
return 23;
|
|
|
|
case 3: // ALL SET Instructions
|
|
memVal |= bitMask;
|
|
WriteMemory(targetAddress, memVal);
|
|
return 23;
|
|
|
|
default:
|
|
throw new Exception("Invalid bitwise operation.");
|
|
}
|
|
}
|
|
case 0xE1: // POP IY
|
|
IY.Low = ReadMemory(SP);
|
|
SP++;
|
|
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:
|
|
// The Z80 Ignored Prefix Quirk!
|
|
return ExecuteOpcode(ddOpcode) + 4; // Note: You named the fetched variable 'opcode' here instead of 'ddOpcode'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//using System;
|
|
//using Core.Interfaces;
|
|
//using Core.Io;
|
|
|
|
//namespace Core.Cpu
|
|
//{
|
|
// public partial class Z80
|
|
// {
|
|
// private static readonly byte[] ParityTable = new byte[256];
|
|
|
|
// // Static constructor to build the table once when the emulator starts
|
|
// static Z80()
|
|
// {
|
|
// for (int i = 0; i < 256; i++)
|
|
// {
|
|
// int ones = 0;
|
|
// for (int b = 0; b < 8; b++) if ((i & (1 << b)) != 0) ones++;
|
|
// ParityTable[i] = (byte)((ones % 2 == 0) ? 0x04 : 0x00); // 0x04 if Even Parity
|
|
// }
|
|
// }
|
|
|
|
// public bool IsZexDocMode { get; set; } = false;
|
|
// //T-State counter
|
|
// public long TotalTStates { get; set; }
|
|
|
|
// public int InterruptMode { get; private set; } = 0;
|
|
|
|
// // Interrupt Flip-Flops
|
|
// public bool IFF1 { get; private set; } = false;
|
|
// public bool IFF2 { get; private set; } = false;
|
|
// public bool InterruptRequested { get; private set; } = false;
|
|
|
|
// // Main Register Set
|
|
// public RegisterPair AF;
|
|
// public RegisterPair BC;
|
|
// public RegisterPair DE;
|
|
// public RegisterPair HL;
|
|
|
|
// // Alternate Register Set
|
|
// public RegisterPair AF_Prime;
|
|
// public RegisterPair BC_Prime;
|
|
// public RegisterPair DE_Prime;
|
|
// public RegisterPair HL_Prime;
|
|
|
|
// // Index Registers
|
|
// public RegisterPair IX;
|
|
// public RegisterPair IY;
|
|
|
|
// // Special Purpose Registers
|
|
// public ushort PC; // Program Counter
|
|
// public ushort SP; // Stack Pointer
|
|
// public byte I; // Interrupt Vector
|
|
// public byte R; // Memory Refresh
|
|
|
|
// // The Memory Bus
|
|
// private readonly IMemory _memory;
|
|
// private readonly IO_Bus _simpleIoBus;
|
|
// public TapManager _tapManager;
|
|
|
|
// //External Timing interface
|
|
// public Func<ushort, long, int>? WaitStateCallback { get; set; }
|
|
|
|
// //Misc Variables
|
|
// public bool EnableFastLoad { get; set; } = true;
|
|
// byte newFlags = 0;
|
|
// int result = 0;
|
|
|
|
// public Z80(IMemory memory, IO_Bus ioBus, TapManager tapManager)
|
|
// {
|
|
// _memory = memory;
|
|
// _simpleIoBus = ioBus;
|
|
// _tapManager = tapManager;
|
|
// Reset();
|
|
// }
|
|
|
|
// public void Reset()
|
|
// {
|
|
// PC = 0x0000;
|
|
// SP = 0xFFFF;
|
|
|
|
// // Main Registers
|
|
// AF.Word = 0;
|
|
// BC.Word = 0;
|
|
// DE.Word = 0;
|
|
// HL.Word = 0;
|
|
|
|
// // Alternate Registers
|
|
// AF_Prime.Word = 0;
|
|
// BC_Prime.Word = 0;
|
|
// DE_Prime.Word = 0;
|
|
// HL_Prime.Word = 0;
|
|
|
|
// // Index Registers
|
|
// IX.Word = 0;
|
|
// IY.Word = 0;
|
|
|
|
// // Internal Registers
|
|
// I = 0;
|
|
// R = 0;
|
|
|
|
// // Hardware State
|
|
// IFF1 = false;
|
|
// IFF2 = false;
|
|
// InterruptMode = 0;
|
|
// TotalTStates = 0;
|
|
// //_memory.CleanRAMData();
|
|
// }
|
|
|
|
// private void ApplyWaitStates(ushort address)
|
|
// {
|
|
// // If a system (like a ULA) is attached and listening, ask it for the delay
|
|
// if (WaitStateCallback != null)
|
|
// {
|
|
// TotalTStates += WaitStateCallback(address, TotalTStates);
|
|
// }
|
|
// }
|
|
|
|
// public int RequestInterrupt()
|
|
// {
|
|
// InterruptRequested = true;
|
|
// // 1. If the ROM has disabled interrupts (DI), ignore the request
|
|
// if (!IFF1) return 0;
|
|
|
|
// // 2. Acknowledge the interrupt by immediately disabling further interrupts
|
|
// IFF1 = false;
|
|
// IFF2 = false;
|
|
|
|
// // 3. Push the current Program Counter to the stack so we can return later
|
|
// Push(PC);
|
|
|
|
// // --- Interrupt Mode Dispatch ---
|
|
// if (InterruptMode == 1)
|
|
// {
|
|
// // IM 1: Hardcoded jump to ROM address 0x0038
|
|
// PC = 0x0038;
|
|
// return 13; // IM 1 hardware call takes 13 T-States
|
|
// }
|
|
// else if (InterruptMode == 2)
|
|
// {
|
|
// // IM 2: Dynamic Vectored Interrupts
|
|
|
|
// // A. Form the pointer address: High byte is 'I', Low byte is the floating bus (0xFF)
|
|
// ushort vectorAddress = (ushort)((I << 8) | 0xFF);
|
|
|
|
// // B. Read the actual 16-bit ISR address from that location in memory (Little-Endian)
|
|
// byte pcLow = ReadMemory(vectorAddress);
|
|
// byte pcHigh = ReadMemory((ushort)(vectorAddress + 1));
|
|
|
|
// // C. Jump to the custom game routine!
|
|
// PC = (ushort)((pcHigh << 8) | pcLow);
|
|
|
|
// return 19; // IM 2 hardware call takes 19 T-States
|
|
// }
|
|
// else
|
|
// {
|
|
// // (IM 0 is theoretically possible but essentially unused on the standard Spectrum)
|
|
// throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!");
|
|
// }
|
|
// }
|
|
|
|
// // 1. For fetching opcodes and immediate values (Advances PC)
|
|
// public byte FetchByte()
|
|
// {
|
|
// ApplyWaitStates(PC);
|
|
// byte data = _memory.Read(PC);
|
|
// PC++;
|
|
// return data;
|
|
// }
|
|
|
|
// // 2. For fetching 16-bit immediate values
|
|
// private ushort FetchWord()
|
|
// {
|
|
// // By using FetchByte twice, we perfectly apply wait states to BOTH memory reads!
|
|
// byte low = FetchByte();
|
|
// byte high = FetchByte();
|
|
// return (ushort)((high << 8) | low);
|
|
// }
|
|
|
|
// // 3. For standard memory reads (e.g., LD A, (HL))
|
|
// public byte ReadMemory(ushort address)
|
|
// {
|
|
// ApplyWaitStates(address);
|
|
// return _memory.Read(address);
|
|
// }
|
|
|
|
// // 4. For standard memory writes (e.g., LD (HL), A)
|
|
// public void WriteMemory(ushort address, byte data)
|
|
// {
|
|
// ApplyWaitStates(address);
|
|
// _memory.Write(address, data);
|
|
// }
|
|
|
|
// // Helper method to calculate if a byte has an Even Parity of 1s
|
|
// private bool CalculateParity(byte b)
|
|
// {
|
|
// int bits = 0;
|
|
// for (int i = 0; i < 8; i++)
|
|
// {
|
|
// if ((b & (1 << i)) != 0) bits++;
|
|
// }
|
|
// return (bits % 2) == 0;
|
|
// }
|
|
|
|
// // Placeholder for your hardware I/O
|
|
// private byte ReadPort(ushort portAddress)
|
|
// {
|
|
// return _simpleIoBus.ReadPort(portAddress);
|
|
// }
|
|
|
|
// public int Step()
|
|
// {
|
|
// if (IsZexDocMode && PC == 0x0005)
|
|
// {
|
|
// // CP/M System Call Hook
|
|
// if (BC.Low == 2) // C = 2: Print a single character stored in register E
|
|
// {
|
|
// System.Diagnostics.Debug.Write((char)DE.Low);
|
|
// }
|
|
// else if (BC.Low == 9) // C = 9: Print a string starting at memory address DE, terminated by '$'
|
|
// {
|
|
// ushort addr = DE.Word;
|
|
// while (true)
|
|
// {
|
|
// char c = (char)ReadMemory(addr++);
|
|
// if (c == '$') break;
|
|
// System.Diagnostics.Debug.Write(c);
|
|
// }
|
|
// }
|
|
|
|
// // Emulate a 'RET' instruction to return from the CALL 0x0005
|
|
// byte retLow = ReadMemory(SP);
|
|
// SP++;
|
|
// byte retHigh = ReadMemory(SP);
|
|
// SP++;
|
|
// PC = (ushort)((retHigh << 8) | retLow);
|
|
|
|
// return 10; // Skip normal execution for this cycle
|
|
// }
|
|
// if (PC == 0x0556)
|
|
// {
|
|
// if (EnableFastLoad)
|
|
// {
|
|
// HandleInstantTapeLoad();
|
|
// return 100;
|
|
// }
|
|
// else
|
|
// {
|
|
// _tapManager.Play();
|
|
// }
|
|
// }
|
|
|
|
// // Fetch the next opcode and increment the Program Counter
|
|
// byte opcode = ReadMemory(PC++);
|
|
// int tStates = ExecuteOpcode(opcode);
|
|
// TotalTStates += tStates;
|
|
|
|
// // Decode and execute
|
|
// return tStates;
|
|
// }
|
|
|
|
// public void LoadSNA(byte[] snaData)
|
|
// {
|
|
// if (snaData.Length != 49179)
|
|
// throw new Exception("Invalid 48K SNA File Size!");
|
|
|
|
// // --- 1. Load CPU Registers ---
|
|
// I = snaData[0];
|
|
// HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8));
|
|
// DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8));
|
|
// BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8));
|
|
// AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8));
|
|
|
|
// HL.Word = (ushort)(snaData[9] | (snaData[10] << 8));
|
|
// DE.Word = (ushort)(snaData[11] | (snaData[12] << 8));
|
|
// BC.Word = (ushort)(snaData[13] | (snaData[14] << 8));
|
|
// IY.Word = (ushort)(snaData[15] | (snaData[16] << 8));
|
|
// IX.Word = (ushort)(snaData[17] | (snaData[18] << 8));
|
|
|
|
// IFF2 = (snaData[19] & 0x04) != 0;
|
|
// IFF1 = IFF2;
|
|
// R = snaData[20];
|
|
// AF.Word = (ushort)(snaData[21] | (snaData[22] << 8));
|
|
// SP = (ushort)(snaData[23] | (snaData[24] << 8));
|
|
// InterruptMode = snaData[25];
|
|
|
|
// // --- 2. Load the 48K RAM Dump ---
|
|
// // The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF
|
|
// for (int i = 0; i < 49152; i++)
|
|
// {
|
|
// WriteMemory((ushort)(0x4000 + i), snaData[27 + i]);
|
|
// }
|
|
|
|
// // --- 3. The Magic Bullet ---
|
|
// // In the SNA format, the Program Counter (PC) isn't in the header.
|
|
// // It was PUSHED to the stack exactly 1 instruction before the snapshot was saved.
|
|
// // So, we just pop it off the stack to resume execution!
|
|
// PC = Pop();
|
|
// }
|
|
|
|
// private void HandleInstantTapeLoad()
|
|
// {
|
|
// // 1. Grab the next block from the virtual cassette
|
|
// byte[] block = _tapManager.GetNextBlock();
|
|
// if (block == null) return; // Tape ended unexpectedly
|
|
|
|
// // 2. Verify the block type.
|
|
// // The ROM passes the expected flag (0x00 for Header, 0xFF for Data) in the A register.
|
|
// byte expectedFlag = AF.High;
|
|
// if (block[0] != expectedFlag)
|
|
// {
|
|
// // Tape loading error! Simulate a failure by clearing the Carry flag and returning.
|
|
// AF.Low &= unchecked((byte)~0x01);
|
|
// ExecuteRet();
|
|
// return;
|
|
// }
|
|
|
|
// // 3. Copy the data block straight into the Spectrum's RAM!
|
|
// // DE holds the number of bytes the ROM *wants*. We copy that much, skipping the Flag byte.
|
|
// int bytesToCopy = DE.Word;
|
|
// for (int i = 0; i < bytesToCopy; i++)
|
|
// {
|
|
// WriteMemory((ushort)(IX.Word + i), block[i + 1]);
|
|
// }
|
|
|
|
// // 4. Update the registers exactly how the ROM would after a successful load
|
|
// IX.Word = (ushort)(IX.Word + bytesToCopy);
|
|
// DE.Word = 0;
|
|
|
|
// // 5. Simulate the Checksum Match (Accumulator becomes 0)
|
|
// AF.High = 0x00;
|
|
|
|
// // 6. Set Carry Flag (Bit 0) for Success, and Zero Flag (Bit 6) for Checksum Match
|
|
// AF.Low |= 0x41; // 0x41 is binary 0100 0001 (Zero and Carry both set)
|
|
|
|
// // 7. Simulate a standard 'RET' instruction to exit the LD-BYTES routine safely
|
|
// ExecuteRet();
|
|
// }
|
|
|
|
// // A quick helper to simulate a RET instruction manually
|
|
// private void ExecuteRet()
|
|
// {
|
|
// byte pcLow = ReadMemory(SP);
|
|
// SP++;
|
|
// byte pcHigh = ReadMemory(SP);
|
|
// SP++;
|
|
// PC = (ushort)((pcHigh << 8) | pcLow);
|
|
// }
|
|
|
|
// // Reads a 16-bit word from the current PC (Little-Endian) and advances PC by 2
|
|
|
|
|
|
// public string GetFlagsString()
|
|
// {
|
|
// byte f = AF.Low;
|
|
// return $"S:{(f >> 7) & 1} " +
|
|
// $"Z:{(f >> 6) & 1} " +
|
|
// $"Y:{(f >> 5) & 1} " + // Undocumented flag
|
|
// $"H:{(f >> 4) & 1} " +
|
|
// $"X:{(f >> 3) & 1} " + // Undocumented flag
|
|
// $"P/V:{(f >> 2) & 1} " +
|
|
// $"N:{(f >> 1) & 1} " +
|
|
// $"C:{f & 1}";
|
|
// }
|
|
|
|
// private void Sub(byte value)
|
|
// {
|
|
// byte a = AF.High;
|
|
// result = a - value;
|
|
|
|
// // Save the result back to the Accumulator
|
|
// AF.High = (byte)result;
|
|
|
|
// // --- Update Flags (F Register) ---
|
|
// AF.Low = 0; // Clear all flags
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((result & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if ((byte)result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if borrow from bit 4
|
|
// if (((a & 0x0F) - (value & 0x0F)) < 0) AF.Low |= 0x10;
|
|
|
|
// // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes
|
|
// if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (Bit 1) - ALWAYS set for CP/SUB
|
|
// AF.Low |= 0x02;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the overall result dropped below 0
|
|
// if (result < 0) AF.Low |= 0x01;
|
|
// }
|
|
|
|
// private void Sbc(byte value)
|
|
// {
|
|
// byte a = AF.High;
|
|
// byte carry = (byte)(AF.Low & 0x01); // Get the current Carry flag (Bit 0)
|
|
|
|
// // Calculate the raw integer result to check for borrows/underflows
|
|
// result = a - value - carry;
|
|
|
|
// // Update the Accumulator
|
|
// AF.High = (byte)result;
|
|
|
|
// // --- Update Flags (F Register) ---
|
|
// AF.Low = 0; // Clear all flags
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((result & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if ((byte)result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if borrow from bit 4
|
|
// if (((a & 0x0F) - (value & 0x0F) - carry) < 0) AF.Low |= 0x10;
|
|
|
|
// // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes
|
|
// if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (Bit 1) - ALWAYS set for subtraction
|
|
// AF.Low |= 0x02;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the overall result dropped below 0
|
|
// if (result < 0) AF.Low |= 0x01;
|
|
// }
|
|
|
|
// private void Sbc16(ushort value)
|
|
// {
|
|
// int hl = HL.Word;
|
|
// int carry = AF.Low & 0x01;
|
|
|
|
// // Calculate the raw integer result to check for underflows
|
|
// result = hl - value - carry;
|
|
|
|
// // Update the HL register
|
|
// HL.Word = (ushort)result;
|
|
|
|
// // --- Update Flags (F Register) ---
|
|
// AF.Low = 0; // Clear all flags
|
|
|
|
// // Sign Flag (Bit 7) - Set if the 16-bit result is negative (bit 15 is 1)
|
|
// if ((result & 0x8000) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6) - Set if the entire 16-bit result is exactly 0
|
|
// if ((ushort)result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if borrow from bit 11
|
|
// if (((hl & 0x0FFF) - (value & 0x0FFF) - carry) < 0) AF.Low |= 0x10;
|
|
|
|
// // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes
|
|
// if ((((hl ^ value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (Bit 1) - ALWAYS set for subtraction
|
|
// AF.Low |= 0x02;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the overall 16-bit result dropped below 0
|
|
// if (result < 0) AF.Low |= 0x01;
|
|
// }
|
|
|
|
// private void SbcHl(ushort value)
|
|
// {
|
|
// int op1 = HL.Word;
|
|
// int op2 = value;
|
|
// int carry = AF.Low & 0x01; // Current C flag
|
|
// int result = op1 - op2 - carry;
|
|
|
|
// byte flags = 0x02; // N: Always 1 (Subtract)
|
|
|
|
// if (((result >> 8) & 0x80) != 0) flags |= 0x80; // S: Sign flag (Bit 15)
|
|
// if ((result & 0xFFFF) == 0) flags |= 0x40; // Z: Zero flag
|
|
|
|
// // H: Half-borrow from Bit 12
|
|
// if ((((op1 & 0x0FFF) - (op2 & 0x0FFF) - carry) & 0x1000) != 0) flags |= 0x10;
|
|
|
|
// // P/V: 16-bit Overflow logic
|
|
// if ((((op1 ^ op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04;
|
|
|
|
// // C: Borrow from Bit 15
|
|
// if (result < 0) flags |= 0x01;
|
|
|
|
// // Undocumented bits 3 and 5 come from the High byte of the calculated result
|
|
// flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
// AF.Low = flags;
|
|
// HL.Word = (ushort)(result & 0xFFFF);
|
|
// }
|
|
|
|
// private byte Dec8(byte value)
|
|
// {
|
|
// byte result = (byte)(value - 1);
|
|
|
|
// // Store the existing Carry flag so we can preserve it
|
|
// byte carry = (byte)(AF.Low & 0x01);
|
|
|
|
// // Clear all flags
|
|
// AF.Low = 0;
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((result & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if (result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 (happens if the lower nibble was 0)
|
|
// if ((value & 0x0F) == 0) AF.Low |= 0x10;
|
|
|
|
// // Parity/Overflow Flag (Bit 2) - Set if the original value was 0x80 (maximum negative)
|
|
// if (value == 0x80) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (Bit 1) - ALWAYS SET for decrements
|
|
// AF.Low |= 0x02;
|
|
|
|
// // Restore the original Carry Flag (Bit 0)
|
|
// AF.Low |= carry;
|
|
|
|
// return result;
|
|
// }
|
|
|
|
// private byte Inc8(byte value)
|
|
// {
|
|
// byte result = (byte)(value + 1);
|
|
|
|
// // Store the existing Carry flag so we can preserve it
|
|
// byte carry = (byte)(AF.Low & 0x01);
|
|
|
|
// // Clear all flags
|
|
// AF.Low = 0;
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((result & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if (result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if carry from bit 3 (happens if lower nibble was 0x0F)
|
|
// if ((value & 0x0F) == 0x0F) AF.Low |= 0x10;
|
|
|
|
// // Parity/Overflow Flag (Bit 2) - Set if the original value was 0x7F (maximum positive)
|
|
// if (value == 0x7F) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (Bit 1) - ALWAYS 0 for increments (already 0 because we cleared AF.Low)
|
|
|
|
// // Restore the original Carry Flag (Bit 0)
|
|
// AF.Low |= carry;
|
|
|
|
// return result;
|
|
// }
|
|
|
|
// private void Cp(byte value)
|
|
// {
|
|
// byte a = AF.High;
|
|
// result = a - value;
|
|
|
|
// // --- Update Flags (F Register) ---
|
|
// AF.Low = 0; // Clear all flags
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((result & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if ((byte)result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if borrow from bit 4
|
|
// if (((a & 0x0F) - (value & 0x0F)) < 0) AF.Low |= 0x10;
|
|
|
|
// // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes
|
|
// if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (Bit 1) - ALWAYS set for CP/SUB
|
|
// AF.Low |= 0x02;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the overall result dropped below 0
|
|
// if (result < 0) AF.Low |= 0x01;
|
|
// }
|
|
|
|
// private void And(byte value)
|
|
// {
|
|
// AF.High = (byte)(AF.High & value);
|
|
|
|
// // --- Update Flags ---
|
|
// AF.Low = 0; // Clear all flags
|
|
|
|
// // Sign Flag (Bit 7) - Set if the highest bit is 1
|
|
// if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6) - Set if the result is 0
|
|
// if (AF.High == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - ALWAYS SET to 1 for Z80 AND instructions!
|
|
// AF.Low |= 0x10;
|
|
|
|
// // Parity Flag (Bit 2) - Set if the result has an even number of 1 bits
|
|
// if (HasEvenParity(AF.High)) AF.Low |= 0x04;
|
|
|
|
// // Subtract Flag (N) and Carry Flag (C) are ALWAYS 0
|
|
// }
|
|
|
|
// private void Or(byte value)
|
|
// {
|
|
// AF.High = (byte)(AF.High | value);
|
|
|
|
// // --- Update Flags ---
|
|
// AF.Low = 0; // Clear all flags (H, N, and C are always 0 for OR)
|
|
|
|
// // Sign Flag (Bit 7) - Set if the highest bit is 1
|
|
// if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6) - Set if the result is 0
|
|
// if (AF.High == 0) AF.Low |= 0x40;
|
|
|
|
// // Parity Flag (Bit 2) - Set if the result has an even number of 1 bits
|
|
// if (HasEvenParity(AF.High)) AF.Low |= 0x04;
|
|
// }
|
|
|
|
// private void Xor(byte value)
|
|
// {
|
|
// // The caret (^) is the C# Bitwise XOR operator
|
|
// AF.High = (byte)(AF.High ^ value);
|
|
|
|
// // --- Update Flags ---
|
|
// AF.Low = 0; // Clear all flags (H, N, and C are always 0 for XOR)
|
|
|
|
// // Sign Flag (Bit 7) - Set if the highest bit is 1
|
|
// if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6) - Set if the result is 0
|
|
// if (AF.High == 0) AF.Low |= 0x40;
|
|
|
|
// // Parity Flag (Bit 2) - Set if the result has an even number of 1 bits
|
|
// if (HasEvenParity(AF.High)) AF.Low |= 0x04;
|
|
// }
|
|
|
|
// private void Add16(ushort value)
|
|
// {
|
|
// int hl = HL.Word;
|
|
// result = hl + value;
|
|
|
|
// // Update the HL register
|
|
// HL.Word = (ushort)result;
|
|
// AF.Low &= 0xEC;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if there is a carry from bit 11
|
|
// if (((hl & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) AF.Low |= 0x10;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the result overflows 16 bits
|
|
// if (result > 0xFFFF) AF.Low |= 0x01;
|
|
// }
|
|
// private void Add16IX(ushort value)
|
|
// {
|
|
// int ixVal = IX.Word;
|
|
// result = ixVal + value;
|
|
|
|
// // --- 16-Bit ADD IX Flag Calculation ---
|
|
// // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2).
|
|
// // This perfectly resets N (Bit 1) to 0 at the same time.
|
|
// newFlags = (byte)(AF.Low & 0xC4);
|
|
|
|
// // Half-Carry (H - Bit 4): Set if carry from Bit 11
|
|
// if (((ixVal & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) newFlags |= 0x10;
|
|
|
|
// // Carry (C - Bit 0): Set if the total result overflows 16 bits
|
|
// if (result > 0xFFFF) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
// IX.Word = (ushort)result;
|
|
// }
|
|
|
|
// private void Add(byte value)
|
|
// {
|
|
// byte a = AF.High;
|
|
// result = a + value;
|
|
|
|
// // Save the result back to the Accumulator
|
|
// AF.High = (byte)result;
|
|
|
|
// // --- Update Flags (F Register) ---
|
|
// AF.Low = 0; // Clear all flags (This also correctly resets the N flag to 0)
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((result & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if ((byte)result == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if carry from bit 3
|
|
// if (((a & 0x0F) + (value & 0x0F)) > 0x0F) AF.Low |= 0x10;
|
|
|
|
// // Overflow/Parity Flag (Bit 2) - For addition, overflow happens if two numbers
|
|
// // with the SAME sign are added and produce a result with a DIFFERENT sign.
|
|
// if ((((a ^ ~value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the result is greater than 255
|
|
// if (result > 0xFF) AF.Low |= 0x01;
|
|
// }
|
|
|
|
// private void AdcA(byte operand)
|
|
// {
|
|
// int aVal = AF.High;
|
|
// int carryIn = AF.Low & 0x01;
|
|
|
|
// result = aVal + operand + carryIn;
|
|
|
|
// byte newFlags = 0;
|
|
|
|
// if ((result & 0x80) != 0) newFlags |= 0x80; // S Flag
|
|
// if ((result & 0xFF) == 0) newFlags |= 0x40; // Z Flag
|
|
// if (((aVal & 0x0F) + (operand & 0x0F) + carryIn) > 0x0F) newFlags |= 0x10; // H Flag
|
|
// if ((((aVal ^ ~operand) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; // P/V Flag
|
|
// if (result > 0xFF) newFlags |= 0x01; // C Flag
|
|
|
|
// AF.Low = newFlags;
|
|
// AF.High = (byte)result;
|
|
// }
|
|
|
|
// private void Adc16(ushort value)
|
|
// {
|
|
// int hl = HL.Word;
|
|
// int carry = AF.Low & 0x01;
|
|
|
|
// // Calculate the raw integer result to check for overflows
|
|
// result = hl + value + carry;
|
|
|
|
// // --- Update Flags (F Register) ---
|
|
// byte newFlags = 0; // Clear all flags (which forces N to 0, correctly!)
|
|
|
|
// // Sign Flag (Bit 7) - Set if the 16-bit result is negative (bit 15 is 1)
|
|
// if ((result & 0x8000) != 0) newFlags |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6) - Set if the entire 16-bit result is exactly 0
|
|
// if ((result & 0xFFFF) == 0) newFlags |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Set if there is a carry out of bit 11
|
|
// if (((hl & 0x0FFF) + (value & 0x0FFF) + carry) > 0x0FFF) newFlags |= 0x10;
|
|
|
|
// // Overflow Flag (Bit 2) - Set if operands have the SAME sign, but result sign changes
|
|
// if ((((hl ^ ~value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) newFlags |= 0x04;
|
|
|
|
// // Carry Flag (Bit 0) - Set if the overall 16-bit result overflowed 0xFFFF
|
|
// if (result > 0xFFFF) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
// HL.Word = (ushort)result;
|
|
// }
|
|
|
|
// private void AdcHl(ushort value)
|
|
// {
|
|
// int op1 = HL.Word;
|
|
// int op2 = value;
|
|
// int carry = AF.Low & 0x01; // Current C flag
|
|
// int result = op1 + op2 + carry;
|
|
|
|
// byte flags = 0;
|
|
|
|
// if (((result >> 8) & 0x80) != 0) flags |= 0x80; // S: Sign flag (Bit 15)
|
|
// if ((result & 0xFFFF) == 0) flags |= 0x40; // Z: Zero flag
|
|
|
|
// // H: Half-carry from Bit 11
|
|
// if ((((op1 & 0x0FFF) + (op2 & 0x0FFF) + carry) & 0x1000) != 0) flags |= 0x10;
|
|
|
|
// // P/V: 16-bit Overflow logic
|
|
// if ((((op1 ^ ~op2) & (op1 ^ result)) & 0x8000) != 0) flags |= 0x04;
|
|
|
|
// // N: Always 0 for Add
|
|
|
|
// // C: Carry from Bit 15
|
|
// if ((result & 0x10000) != 0) flags |= 0x01;
|
|
|
|
// // Undocumented bits 3 and 5 come from the High byte of the calculated result
|
|
// flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
// AF.Low = flags;
|
|
// HL.Word = (ushort)(result & 0xFFFF);
|
|
// }
|
|
|
|
// private void AddHl(ushort value)
|
|
// {
|
|
// int result = HL.Word + value;
|
|
|
|
// // 1. Preserve S (0x80), Z (0x40), and P/V (0x04) from the current flag register
|
|
// byte flags = (byte)(AF.Low & 0xC4);
|
|
|
|
// // 2. Calculate H (Half-carry from bit 11)
|
|
// if (((HL.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF)
|
|
// {
|
|
// flags |= 0x10;
|
|
// }
|
|
|
|
// // 3. Calculate C (Carry from bit 15)
|
|
// if (result > 0xFFFF)
|
|
// {
|
|
// flags |= 0x01;
|
|
// }
|
|
|
|
// // 4. Undocumented bits 3 and 5 come from the High byte of the calculated result
|
|
// flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
// // N is naturally left as 0 because of our initial bitmask
|
|
// AF.Low = flags;
|
|
// HL.Word = (ushort)result;
|
|
// }
|
|
|
|
// private void AddA(byte operand)
|
|
// {
|
|
// byte a = AF.High;
|
|
// int result = a + operand; // Use a local int to easily catch the carry
|
|
|
|
// AF.High = (byte)result;
|
|
|
|
// // --- Update Flags ---
|
|
// AF.Low = 0; // Clear all flags initially (Forces N to 0)
|
|
|
|
// // Sign Flag (Bit 7)
|
|
// if ((AF.High & 0x80) != 0) AF.Low |= 0x80;
|
|
|
|
// // Zero Flag (Bit 6)
|
|
// if (AF.High == 0) AF.Low |= 0x40;
|
|
|
|
// // Half-Carry Flag (Bit 4) - Check if bits 0-3 overflowed
|
|
// if (((a & 0x0F) + (operand & 0x0F)) > 0x0F) AF.Low |= 0x10;
|
|
|
|
// // Parity/Overflow Flag (Bit 2)
|
|
// bool sameSign = ((a ^ operand) & 0x80) == 0; // Did inputs have the same sign?
|
|
// bool changedSign = ((a ^ AF.High) & 0x80) != 0; // Did the result's sign flip?
|
|
// if (sameSign && changedSign) AF.Low |= 0x04;
|
|
|
|
// // Carry Flag (Bit 0) - Check if the whole 8-bit addition overflowed
|
|
// if (result > 0xFF) AF.Low |= 0x01;
|
|
|
|
// // UNDOCUMENTED FLAGS (Bits 3 and 5) - Copied directly from the result
|
|
// AF.Low |= (byte)(AF.High & 0x28);
|
|
// }
|
|
// private void AddIx(ushort value)
|
|
// {
|
|
// int result = IX.Word + value;
|
|
|
|
// // Preserve S, Z, and P/V
|
|
// byte flags = (byte)(AF.Low & 0xC4);
|
|
|
|
// // Calculate H (Half-carry from bit 11)
|
|
// if (((IX.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10;
|
|
|
|
// // Calculate C (Carry from bit 15)
|
|
// if (result > 0xFFFF) flags |= 0x01;
|
|
|
|
// // Undocumented bits 3 and 5 from the High byte of the result
|
|
// flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
// AF.Low = flags;
|
|
// IX.Word = (ushort)result;
|
|
// }
|
|
|
|
// private void AddIy(ushort value)
|
|
// {
|
|
// int result = IY.Word + value;
|
|
|
|
// // Preserve S, Z, and P/V
|
|
// byte flags = (byte)(AF.Low & 0xC4);
|
|
|
|
// // Calculate H (Half-carry from bit 11)
|
|
// if (((IY.Word & 0x0FFF) + (value & 0x0FFF)) > 0x0FFF) flags |= 0x10;
|
|
|
|
// // Calculate C (Carry from bit 15)
|
|
// if (result > 0xFFFF) flags |= 0x01;
|
|
|
|
// // Undocumented bits 3 and 5 from the High byte of the result
|
|
// flags |= (byte)((result >> 8) & 0x28);
|
|
|
|
// AF.Low = flags;
|
|
// IY.Word = (ushort)result;
|
|
// }
|
|
// private bool HasEvenParity(byte value)
|
|
// {
|
|
// int bits = 0;
|
|
// for (int i = 0; i < 8; i++)
|
|
// {
|
|
// if ((value & (1 << i)) != 0) bits++;
|
|
// }
|
|
// return (bits % 2) == 0;
|
|
// }
|
|
|
|
// private void Push(ushort value)
|
|
// {
|
|
// // High byte goes first
|
|
// SP--;
|
|
// WriteMemory(SP, (byte)(value >> 8));
|
|
|
|
// // Low byte goes second
|
|
// SP--;
|
|
// WriteMemory(SP, (byte)(value & 0xFF));
|
|
// }
|
|
|
|
// private ushort Pop()
|
|
// {
|
|
// // The Z80 is Little-Endian. Low byte comes off the stack first.
|
|
// byte low = ReadMemory(SP++);
|
|
|
|
// // High byte comes off second.
|
|
// byte high = ReadMemory(SP++);
|
|
|
|
// return (ushort)((high << 8) | low);
|
|
// }
|
|
|
|
// private int ExecuteOpcode(byte opcode)
|
|
// {
|
|
// sbyte offset = 0;
|
|
// byte oldCarry = 0;
|
|
// switch (opcode)
|
|
// {
|
|
// case 0x00: // NOP
|
|
// return 4;
|
|
// case 0x01: // LD BC, nn
|
|
// BC.Word = FetchWord();
|
|
// return 10;
|
|
// case 0x02: // LD (BC), A
|
|
// WriteMemory(BC.Word, AF.High);
|
|
// return 7;
|
|
// case 0x03: // INC BC
|
|
// BC.Word++;
|
|
// return 6;
|
|
// // --- 8-Bit Increments ---
|
|
// case 0x04: BC.High = Inc8(BC.High); return 4; // INC B
|
|
// case 0x07: // RLCA
|
|
// // 1. Grab the top bit (Bit 7) before it rotates
|
|
// byte topBit = (byte)(AF.High >> 7);
|
|
|
|
// // 2. Shift A left by 1, and drop the top bit into the newly empty Bit 0
|
|
// AF.High = (byte)((AF.High << 1) | topBit);
|
|
|
|
// // --- RLCA Specific Flag Rules ---
|
|
// // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2).
|
|
// byte newFlags = (byte)(AF.Low & 0xC4);
|
|
|
|
// // H (Bit 4) and N (Bit 1) are forcefully reset to 0 (which the bitwise AND above just did).
|
|
|
|
// // C (Bit 0): Set if the bit that rotated off the top was a 1
|
|
// if (topBit != 0) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// return 4; // 4 T-States
|
|
// case 0x08: // EX AF, AF'
|
|
// ushort tempAF = AF.Word;
|
|
// AF.Word = AF_Prime.Word;
|
|
// AF_Prime.Word = tempAF;
|
|
// return 4;
|
|
// // Inside your base switch(opcode) statement:
|
|
// case 0x09: AddHl(BC.Word); return 11;
|
|
// case 0x0A: //LD A (BC)
|
|
// AF.High = ReadMemory(BC.Word);
|
|
// return 7;
|
|
// case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C
|
|
// case 0x12: // LD (DE), A
|
|
// WriteMemory(DE.Word, AF.High);
|
|
// return 7;
|
|
// case 0x14: DE.High = Inc8(DE.High); return 4; // INC D
|
|
// case 0x19: AddHl(DE.Word); return 11;
|
|
// case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E
|
|
// case 0x1E: DE.Low = FetchByte(); // LD E, n
|
|
// return 7;
|
|
// case 0x29: AddHl(HL.Word); return 11;
|
|
// case 0x24: HL.High = Inc8(HL.High); return 4; // INC H
|
|
// case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L
|
|
// case 0x2E: // LD L, n
|
|
// HL.Low = FetchByte();
|
|
// return 7;
|
|
// case 0x34:
|
|
// WriteMemory(HL.Word, Inc8(ReadMemory(HL.Word)));
|
|
// return 11; // INC (HL) takes 11 T-States
|
|
// case 0x39: AddHl(SP); return 11;
|
|
// case 0x3C: AF.High = Inc8(AF.High); return 4; // INC A
|
|
|
|
// // --- 8-Bit Decrements ---
|
|
// case 0x05: BC.High = Dec8(BC.High); return 4; // DEC B
|
|
// case 0x0D: BC.Low = Dec8(BC.Low); return 4; // DEC C
|
|
// case 0x15: DE.High = Dec8(DE.High); return 4; // DEC D
|
|
// case 0x1D: DE.Low = Dec8(DE.Low); return 4; // DEC E
|
|
// case 0x25: HL.High = Dec8(HL.High); return 4; // DEC H
|
|
// case 0x2D: HL.Low = Dec8(HL.Low); return 4; // DEC L
|
|
// case 0x2F: // CPL
|
|
// // Flip all bits in the Accumulator
|
|
// AF.High = (byte)(~AF.High);
|
|
|
|
// // Set Half-Carry (Bit 4) and Subtract (Bit 1).
|
|
// // Bitwise OR forces them to 1 while perfectly preserving S, Z, P/V, and C.
|
|
// AF.Low |= 0x12;
|
|
|
|
// return 4;
|
|
// case 0x35:
|
|
// WriteMemory(HL.Word, Dec8(ReadMemory(HL.Word)));
|
|
// return 11; // DEC (HL) takes 11 T-States
|
|
// case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A
|
|
// case 0x06: // LD B, n
|
|
// BC.High = FetchByte();
|
|
// return 7;
|
|
// case 0x0B: // DEC BC
|
|
// BC.Word--;
|
|
// return 6;
|
|
// case 0x0E: // LD C, n
|
|
// BC.Low = FetchByte();
|
|
// return 7;
|
|
// case 0x0F: // RRCA
|
|
// {
|
|
// // 1. Grab the bit that is about to fall off
|
|
// byte bit0 = (byte)(AF.High & 0x01);
|
|
|
|
// // 2. Shift right, and force the old Bit 0 into the Bit 7 position
|
|
// AF.High = (byte)((AF.High >> 1) | (bit0 << 7));
|
|
|
|
// // 3. Update Flags
|
|
// // S (0x80), Z (0x40), and P/V (0x04) are completely PRESERVED.
|
|
// // H (0x10) and N (0x02) are forcefully RESET to 0.
|
|
// // ANDing with 0xC4 (Binary 1100 0100) does exactly this.
|
|
// AF.Low &= 0xC4;
|
|
|
|
// // Set the Carry Flag (Bit 0) to whatever fell off
|
|
// AF.Low |= bit0;
|
|
// return 4;
|
|
// }
|
|
// case 0x10: // DJNZ d
|
|
// sbyte djnzOffset = (sbyte)FetchByte();
|
|
|
|
// BC.High--; // Decrement the B register
|
|
|
|
// if (BC.High != 0)
|
|
// {
|
|
// PC = (ushort)(PC + djnzOffset);
|
|
// return 13; // Jump taken
|
|
// }
|
|
|
|
// return 8; // Loop finished, no jump
|
|
// case 0x11: //LD DE, nn
|
|
// DE.Word = FetchWord();
|
|
// return 10;
|
|
// case 0x13: // INC DE
|
|
// DE.Word++;
|
|
// return 6;
|
|
// case 0x16: // LD D, n
|
|
// DE.High = FetchByte();
|
|
// return 7;
|
|
// case 0x17: // RLA
|
|
// // 1. Grab the current Carry flag (Bit 0 of AF.Low)
|
|
// oldCarry = (byte)(AF.Low & 0x01);
|
|
|
|
// // 2. See if Bit 7 of the Accumulator is about to fall off
|
|
// bool newCarry = (AF.High & 0x80) != 0;
|
|
|
|
// // 3. Shift A left, and drop the OLD carry directly into Bit 0
|
|
// AF.High = (byte)((AF.High << 1) | oldCarry);
|
|
|
|
// // 4. Update the flags
|
|
// // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2) while wiping H and N.
|
|
// AF.Low &= 0xC4;
|
|
|
|
// // 5. Apply the new Carry flag if necessary
|
|
// if (newCarry) AF.Low |= 0x01;
|
|
|
|
// return 4; // 4 T-States
|
|
// case 0x18: // JR d
|
|
// sbyte jumpDistance = (sbyte)FetchByte();
|
|
|
|
// // PC has already been incremented by FetchByte(), so it is
|
|
// // pointing exactly where it needs to be for the relative addition.
|
|
// PC = (ushort)(PC + jumpDistance);
|
|
|
|
// return 12;
|
|
// case 0x1A: // LD A, (DE)
|
|
// AF.High = ReadMemory(DE.Word);
|
|
// return 7;
|
|
// case 0x1B: // DEC DE
|
|
// DE.Word--;
|
|
// return 6;
|
|
// case 0x1F: // RRA
|
|
// {
|
|
// // 1. Grab the current Carry Flag (0 or 1)
|
|
// oldCarry = (byte)(AF.Low & 0x01);
|
|
|
|
// // 2. Grab the bit that is about to fall off the Accumulator
|
|
// byte bit0 = (byte)(AF.High & 0x01);
|
|
|
|
// // 3. Shift right, and force the OLD Carry flag into the Bit 7 position
|
|
// AF.High = (byte)((AF.High >> 1) | (oldCarry << 7));
|
|
|
|
// // 4. Update Flags
|
|
// // S (0x80), Z (0x40), and P/V (0x04) are PRESERVED exactly as they are.
|
|
// // H (0x10) and N (0x02) are forcefully RESET to 0.
|
|
// AF.Low &= 0xC4;
|
|
|
|
// // Set the new Carry Flag (Bit 0) to whatever fell off the Accumulator
|
|
// AF.Low |= bit0;
|
|
|
|
// return 4;
|
|
// }
|
|
// case 0x20: // JR NZ, e
|
|
// offset = (sbyte)FetchByte();
|
|
// if ((AF.Low & 0x40) == 0)
|
|
// {
|
|
// PC = (ushort)(PC + offset);
|
|
// return 12;
|
|
// }
|
|
// return 7;
|
|
// case 0x21: // LD HL, nn
|
|
// HL.Word = FetchWord();
|
|
// return 10;
|
|
// case 0x22: // LD (nn), HL
|
|
// ushort dest22 = FetchWord();
|
|
// WriteMemory(dest22, HL.Low);
|
|
// WriteMemory((ushort)(dest22 + 1), HL.High);
|
|
// return 16;
|
|
// case 0x23: // INC HL
|
|
// HL.Word++;
|
|
// return 6;
|
|
// case 0x26: // LD H, n
|
|
// HL.High = FetchByte();
|
|
// return 7;
|
|
// case 0x27: // DAA
|
|
// byte a = AF.High;
|
|
// int correction = 0;
|
|
// byte flags = AF.Low;
|
|
|
|
// bool carry = (flags & 0x01) != 0;
|
|
// bool halfCarry = (flags & 0x10) != 0;
|
|
// bool isSub = (flags & 0x02) != 0; // The N flag tells us if we should add or subtract!
|
|
|
|
// // 1. Check if the lower nibble needs adjustment
|
|
// if (halfCarry || (a & 0x0F) > 9)
|
|
// {
|
|
// correction |= 0x06;
|
|
// }
|
|
|
|
// // 2. Check if the upper nibble needs adjustment
|
|
// if (carry || a > 0x99 || (a >= 0x90 && (a & 0x0F) > 9))
|
|
// {
|
|
// correction |= 0x60;
|
|
// carry = true; // The final carry flag will be true
|
|
// }
|
|
|
|
// // 3. Apply the correction and calculate the new Half-Carry
|
|
// bool newHalfCarry = false;
|
|
// if (isSub)
|
|
// {
|
|
// newHalfCarry = halfCarry && (a & 0x0F) < 0x06;
|
|
// a = (byte)(a - correction);
|
|
// }
|
|
// else
|
|
// {
|
|
// newHalfCarry = ((a & 0x0F) + (correction & 0x0F)) > 0x0F;
|
|
// a = (byte)(a + correction);
|
|
// }
|
|
|
|
// AF.High = a;
|
|
|
|
// // 4. Build the new flags
|
|
// flags &= 0x02; // Wipe everything except the N flag (which is strictly preserved)
|
|
// if (carry) flags |= 0x01;
|
|
// if (newHalfCarry) flags |= 0x10;
|
|
// if ((a & 0x80) != 0) flags |= 0x80; // S flag
|
|
// if (a == 0) flags |= 0x40; // Z flag
|
|
// if (CalculateParity(a)) flags |= 0x04; // P/V flag
|
|
|
|
// AF.Low = flags;
|
|
// return 4;
|
|
// case 0x28: // JR Z, e
|
|
// offset = (sbyte)FetchByte();
|
|
// // Check if the Zero Flag is set
|
|
// if ((AF.Low & 0x40) != 0)
|
|
// {
|
|
// PC = (ushort)(PC + offset);
|
|
// return 12;
|
|
// }
|
|
// return 7;
|
|
// case 0x2A: // LD HL, (nn)
|
|
// {
|
|
// ushort srcAddress = FetchWord();
|
|
// HL.Low = ReadMemory(srcAddress);
|
|
// HL.High = ReadMemory((ushort)(srcAddress + 1));
|
|
// return 16;
|
|
// }
|
|
// case 0x2B: // DEC HL
|
|
// HL.Word--;
|
|
// return 6;
|
|
// case 0x30: // JR NC, e
|
|
// offset = (sbyte)FetchByte();
|
|
|
|
// // Check if the Carry Flag (Bit 0) is NOT set
|
|
// if ((AF.Low & 0x01) == 0)
|
|
// {
|
|
// PC = (ushort)(PC + offset);
|
|
// return 12; // Jump taken
|
|
// }
|
|
// return 7;
|
|
// case 0x31: // LD SP, nn
|
|
// {
|
|
// SP = FetchWord();
|
|
// return 10;
|
|
// }
|
|
// case 0x32: // LD (nn), A
|
|
// {
|
|
// ushort destAddress = FetchWord();
|
|
// WriteMemory(destAddress, AF.High);
|
|
// return 13;
|
|
// }
|
|
// case 0x33: // INC SP
|
|
// SP++;
|
|
// return 6;
|
|
// case 0x36: // LD (HL), n
|
|
// byte nValue = FetchByte();
|
|
// WriteMemory(HL.Word, nValue);
|
|
// return 10;
|
|
// case 0x37: // SCF
|
|
// AF.Low |= 0x01; // Force Carry Flag (Bit 0) to 1
|
|
// AF.Low &= 0xED;
|
|
// return 4;
|
|
// case 0x38: // JR C, d
|
|
// sbyte jrCOffset = (sbyte)FetchByte();
|
|
// // Check if the Carry Flag (Bit 0) IS set (1)
|
|
// if ((AF.Low & 0x01) != 0)
|
|
// {
|
|
// PC = (ushort)(PC + jrCOffset);
|
|
// return 12;
|
|
// }
|
|
// return 7;
|
|
// case 0x3A: // LD A, (nn)
|
|
// ushort address3A = FetchWord();
|
|
// AF.High = ReadMemory(address3A);
|
|
// return 13;
|
|
// case 0x3B: // DEC SP
|
|
// SP--;
|
|
// return 6;
|
|
// case 0x3E: //LD A, n
|
|
// AF.High = FetchByte();
|
|
// return 7;
|
|
// case 0x3F: // CCF
|
|
// bool previousCarry = (AF.Low & 0x01) != 0;
|
|
// AF.Low ^= 0x01; // Invert Carry Flag (Bit 0)
|
|
// AF.Low &= 0xFD; // Force Subtract Flag (N, Bit 1) to 0
|
|
// // Set Half-Carry (H, Bit 4) to the previous Carry state
|
|
// if (previousCarry)
|
|
// AF.Low |= 0x10;
|
|
// else
|
|
// AF.Low &= 0xEF;
|
|
// 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;
|
|
// case 0x44: BC.High = HL.High; return 4;
|
|
// case 0x45: BC.High = HL.Low; return 4;
|
|
// case 0x46: BC.High = ReadMemory(HL.Word); return 7;
|
|
// case 0x47: BC.High = AF.High; return 4;
|
|
|
|
// // --- LD C, r ---
|
|
// case 0x48: BC.Low = BC.High; 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;
|
|
// case 0x4D: BC.Low = HL.Low; return 4;
|
|
// case 0x4E: BC.Low = ReadMemory(HL.Word); return 7;
|
|
// case 0x4F: BC.Low = AF.High; return 4;
|
|
|
|
// // --- 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 0x53: DE.High = DE.Low; return 4;
|
|
// case 0x54: DE.High = HL.High; return 4;
|
|
// case 0x55: DE.High = HL.Low; return 4;
|
|
// case 0x56: DE.High = ReadMemory(HL.Word); return 7;
|
|
// case 0x57: DE.High = AF.High; return 4;
|
|
|
|
// // --- LD E, r ---
|
|
// 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 0x5C: DE.Low = HL.High; return 4;
|
|
// case 0x5D: DE.Low = HL.Low; return 4;
|
|
// case 0x5E: DE.Low = ReadMemory(HL.Word); return 7;
|
|
// case 0x5F: DE.Low = AF.High; return 4;
|
|
|
|
// // --- LD H, r ---
|
|
// case 0x60: HL.High = BC.High; return 4;
|
|
// 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 0x65: HL.High = HL.Low; return 4;
|
|
// case 0x66: HL.High = ReadMemory(HL.Word); return 7;
|
|
// case 0x67: HL.High = AF.High; return 4;
|
|
|
|
// // --- LD L, r ---
|
|
// case 0x68: HL.Low = BC.High; return 4;
|
|
// case 0x69: HL.Low = BC.Low; return 4;
|
|
// 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 0x6E: HL.Low = ReadMemory(HL.Word); return 7;
|
|
// case 0x6F: HL.Low = AF.High; return 4;
|
|
|
|
// // --- LD (HL), r --- (Note: 0x76 is HALT, so it is skipped here)
|
|
// case 0x70: WriteMemory(HL.Word, BC.High); return 7;
|
|
// case 0x71: WriteMemory(HL.Word, BC.Low); return 7;
|
|
// case 0x72: WriteMemory(HL.Word, DE.High); return 7;
|
|
// case 0x73: WriteMemory(HL.Word, DE.Low); return 7;
|
|
// case 0x74: WriteMemory(HL.Word, HL.High); return 7;
|
|
// case 0x75: WriteMemory(HL.Word, HL.Low); return 7;
|
|
// case 0x76: //HALT
|
|
// if (!InterruptRequested)
|
|
// {
|
|
// PC--;
|
|
// return 4;
|
|
// }
|
|
// else
|
|
// {
|
|
// InterruptRequested = false;
|
|
// return 4;
|
|
// }
|
|
// case 0x77: WriteMemory(HL.Word, AF.High); return 7;
|
|
|
|
// // --- LD A, r ---
|
|
// case 0x78: AF.High = BC.High; return 4;
|
|
// case 0x79: AF.High = BC.Low; return 4;
|
|
// case 0x7A: AF.High = DE.High; return 4;
|
|
// case 0x7B: AF.High = DE.Low; return 4;
|
|
// case 0x7C: AF.High = HL.High; return 4;
|
|
// case 0x7D: AF.High = HL.Low; return 4;
|
|
// case 0x7E: AF.High = ReadMemory(HL.Word); return 7;
|
|
// 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
|
|
// case 0x83: Add(DE.Low); return 4; // ADD A, E
|
|
// case 0x84: Add(HL.High); return 4; // ADD A, H
|
|
// case 0x85: Add(HL.Low); return 4; // ADD A, L
|
|
// case 0x86: Add(ReadMemory(HL.Word)); return 7; // ADD A, (HL)
|
|
// case 0x87: Add(AF.High); return 4; // ADD A, A
|
|
// // --- ADC A, Register Family ---
|
|
// case 0x88: AdcA(BC.High); return 4; // ADC A, B
|
|
// case 0x89: AdcA(BC.Low); return 4; // ADC A, C
|
|
// case 0x8A: AdcA(DE.High); return 4; // ADC A, D
|
|
// case 0x8B: AdcA(DE.Low); return 4; // ADC A, E
|
|
// case 0x8C: AdcA(HL.High); return 4; // ADC A, H
|
|
// case 0x8D: AdcA(HL.Low); return 4; // ADC A, L
|
|
// case 0x8F: AdcA(AF.High); return 4; // ADC A, A
|
|
|
|
// // --- ADC A, Memory ---
|
|
// case 0x8E: // ADC A, (HL)
|
|
// AdcA(ReadMemory(HL.Word));
|
|
// return 7;
|
|
|
|
// // --- ADC A, Immediate ---
|
|
// case 0xCE: // ADC A, n
|
|
// AdcA(FetchByte());
|
|
// return 7;
|
|
// case 0x90: Sub(BC.High); return 4; // SUB B
|
|
// case 0x91: Sub(BC.Low); return 4; // SUB C
|
|
// case 0x92: Sub(DE.High); return 4; // SUB D
|
|
// case 0x93: Sub(DE.Low); return 4; // SUB E
|
|
// case 0x94: Sub(HL.High); return 4; // SUB H
|
|
// case 0x95: Sub(HL.Low); return 4; // SUB L
|
|
// case 0x96: Sub(ReadMemory(HL.Word)); return 7; // SUB (HL)
|
|
// case 0x97: Sub(AF.High); return 4; // SUB A
|
|
// // --- SBC A, r ---
|
|
// case 0x98: Sbc(BC.High); return 4; // SBC A, B
|
|
// case 0x99: Sbc(BC.Low); return 4; // SBC A, C
|
|
// case 0x9A: Sbc(DE.High); return 4; // SBC A, D
|
|
// case 0x9B: Sbc(DE.Low); return 4; // SBC A, E
|
|
// case 0x9C: Sbc(HL.High); return 4; // SBC A, H
|
|
// case 0x9D: Sbc(HL.Low); return 4; // SBC A, L
|
|
// case 0x9E: Sbc(ReadMemory(HL.Word)); return 7; // SBC A, (HL)
|
|
// case 0x9F: Sbc(AF.High); return 4; // SBC A, A
|
|
// case 0xA0: And(BC.High); return 4; // AND B
|
|
// case 0xA1: And(BC.Low); return 4; // AND C
|
|
// case 0xA2: And(DE.High); return 4; // AND D
|
|
// case 0xA3: And(DE.Low); return 4; // AND E
|
|
// case 0xA4: And(HL.High); return 4; // AND H
|
|
// case 0xA5: And(HL.Low); return 4; // AND L
|
|
// case 0xA6: And(ReadMemory(HL.Word)); return 7; // AND (HL)
|
|
// case 0xA7: And(AF.High); return 4; // AND A
|
|
// case 0xA8: Xor(BC.High); return 4; // XOR B
|
|
// case 0xA9: Xor(BC.Low); return 4; // XOR C
|
|
// case 0xAA: Xor(DE.High); return 4; // XOR D
|
|
// case 0xAB: Xor(DE.Low); return 4; // XOR E
|
|
// case 0xAC: Xor(HL.High); return 4; // XOR H
|
|
// case 0xAD: Xor(HL.Low); return 4; // XOR L
|
|
// case 0xAE: Xor(ReadMemory(HL.Word)); return 7; // XOR (HL)
|
|
// case 0xAF: Xor(AF.High); return 4; // XOR A
|
|
|
|
// // --- OR r ---
|
|
// case 0xB0: Or(BC.High); return 4; // OR B
|
|
// case 0xB1: Or(BC.Low); return 4; // OR C
|
|
// case 0xB2: Or(DE.High); return 4; // OR D
|
|
// case 0xB3: Or(DE.Low); return 4; // OR E
|
|
// case 0xB4: Or(HL.High); return 4; // OR H
|
|
// case 0xB5: Or(HL.Low); return 4; // OR L
|
|
// case 0xB6: Or(ReadMemory(HL.Word)); return 7; // OR (HL)
|
|
// case 0xB7: Or(AF.High); return 4; // OR A
|
|
|
|
// // --- CP r ---
|
|
// case 0xB8: Cp(BC.High); return 4; // CP B
|
|
// case 0xB9: Cp(BC.Low); return 4; // CP C
|
|
// case 0xBA: Cp(DE.High); return 4; // CP D
|
|
// case 0xBB: Cp(DE.Low); return 4; // CP E
|
|
// case 0xBC: Cp(HL.High); return 4; // CP H
|
|
// case 0xBD: Cp(HL.Low); return 4; // CP L
|
|
// case 0xBE: Cp(ReadMemory(HL.Word)); return 7; // CP (HL)
|
|
// case 0xBF: Cp(AF.High); return 4; // CP A
|
|
// // --- Conditional Returns (11 T-States if taken, 5 if not) ---
|
|
// case 0xC0: // RET NZ
|
|
// if ((AF.Low & 0x40) == 0) { PC = Pop(); return 11; }
|
|
// return 5;
|
|
// case 0xE0: // RET PO (Parity Odd / No Overflow)
|
|
// if ((AF.Low & 0x04) == 0) { PC = Pop(); return 11; }
|
|
// return 5;
|
|
// case 0xE8: // RET PE (Parity Even / Overflow)
|
|
// if ((AF.Low & 0x04) != 0) { PC = Pop(); return 11; }
|
|
// return 5;
|
|
// case 0xF0: // RET P (Sign Positive)
|
|
// if ((AF.Low & 0x80) == 0) { PC = Pop(); return 11; }
|
|
// return 5;
|
|
// case 0xF8: // RET M (Sign Minus)
|
|
// if ((AF.Low & 0x80) != 0) { PC = Pop(); return 11; }
|
|
// return 5;
|
|
// case 0xC1: // POP BC
|
|
// BC.Word = Pop();
|
|
// return 10;
|
|
// // --- Absolute Conditional Jumps (Always 10 T-States) ---
|
|
// case 0xC2: // JP NZ, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x40) == 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xCA: // JP Z, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x40) != 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xD2: // JP NC, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x01) == 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xDA: // JP C, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x01) != 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xDB: // IN A, (n)
|
|
// // 1. Fetch the immediate port offset byte
|
|
// byte portOffsetDB = FetchByte();
|
|
|
|
// // 2. The Z80 puts 'A' on the top 8 bits, and 'n' on the bottom 8 bits
|
|
// ushort portAddressDB = (ushort)((AF.High << 8) | portOffsetDB);
|
|
|
|
// // 3. Read from the I/O bus and store the result straight into the Accumulator
|
|
// AF.High = _simpleIoBus.ReadPort(portAddressDB);
|
|
|
|
// return 11;
|
|
// case 0xE2: // JP PO, nn (Parity Odd / No Overflow)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x04) == 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xEA: // JP PE, nn (Parity Even / Overflow)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x04) != 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xF2: // JP P, nn (Sign Positive)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x80) == 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xFA: // JP M, nn (Sign Minus)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x80) != 0) PC = addr;
|
|
// return 10;
|
|
// }
|
|
// case 0xC3:
|
|
// PC = FetchWord();
|
|
// return 10;
|
|
// // --- Absolute Conditional Calls (17 T-States if taken, 10 if not) ---
|
|
// case 0xC4: // CALL NZ, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x40) == 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xCC: // CALL Z, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x40) != 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xD4: // CALL NC, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x01) == 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xDC: // CALL C, nn
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x01) != 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xE4: // CALL PO, nn (Parity Odd)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x04) == 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xEC: // CALL PE, nn (Parity Even)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x04) != 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xF4: // CALL P, nn (Sign Positive)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x80) == 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xFC: // CALL M, nn (Sign Minus)
|
|
// {
|
|
// ushort addr = FetchWord();
|
|
// if ((AF.Low & 0x80) != 0) { Push(PC); PC = addr; return 17; }
|
|
// return 10;
|
|
// }
|
|
// case 0xc5: //push bc
|
|
// Push(BC.Word);
|
|
// return 11;
|
|
// case 0xC6: // ADD A, n
|
|
// Add(FetchByte());
|
|
// return 7;
|
|
// // --- RST Instructions (11 T-States) ---
|
|
// // An RST is effectively a 1-byte CALL to a fixed Page 0 address.
|
|
// case 0xC7: Push(PC); PC = 0x0000; return 11; // RST 00h (Equivalent to a hardware reset)
|
|
// case 0xCF: Push(PC); PC = 0x0008; return 11; // RST 08h (Spectrum Error handler)
|
|
// case 0xD7: Push(PC); PC = 0x0010; return 11; // RST 10h (Spectrum Print Character)
|
|
// case 0xDF: Push(PC); PC = 0x0018; return 11; // RST 18h (Spectrum Collect Next Char)
|
|
// case 0xE7: Push(PC); PC = 0x0020; return 11; // RST 20h (Spectrum Collect Next Char/Space)
|
|
// case 0xEF: Push(PC); PC = 0x0028; return 11; // RST 28h (Spectrum Floating Point Calculator)
|
|
// case 0xF7: Push(PC); PC = 0x0030; return 11; // RST 30h (Spectrum Make BC Spaces)
|
|
// case 0xFF: Push(PC); PC = 0x0038; return 11; // RST 38h (Maskable Interrupt Handler)
|
|
// case 0xC8: // RET Z
|
|
// // Check if the Zero Flag (Bit 6) IS set
|
|
// if ((AF.Low & 0x40) != 0)
|
|
// {
|
|
// PC = Pop();
|
|
// return 11; // Condition met, took the return
|
|
// }
|
|
// return 5; // Condition not met, skipped
|
|
// case 0xC9: // RET
|
|
// PC = Pop();
|
|
// return 10;
|
|
// case 0xCB:
|
|
// return ExecuteCBPrefix();
|
|
// case 0xCD: // CALL nn
|
|
// ushort callAddress = FetchWord();
|
|
// Push(PC);
|
|
// PC = callAddress;
|
|
// return 17;
|
|
// case 0xD0: // RET NC
|
|
// // Check if the Carry Flag (Bit 0) is NOT set (0)
|
|
// if ((AF.Low & 0x01) == 0)
|
|
// {
|
|
// PC = Pop();
|
|
// return 11; // Condition met, took the return
|
|
// }
|
|
// return 5; // Condition not met, skipped
|
|
// case 0xD1: // POP DE
|
|
// DE.Word = Pop();
|
|
// return 10;
|
|
// case 0xD3: // OUT (n), A
|
|
// byte portOffset = FetchByte();
|
|
|
|
// // The Z80 puts 'A' on the top 8 bits, and 'n' on the bottom 8 bits of the port address
|
|
// ushort portAddress = (ushort)((AF.High << 8) | portOffset);
|
|
|
|
// _simpleIoBus.WritePort(portAddress, AF.High);
|
|
|
|
// return 11;
|
|
// case 0xd5: //push bc
|
|
// Push(DE.Word);
|
|
// return 11;
|
|
// case 0xD6: // SUB n
|
|
// Sub(FetchByte());
|
|
// return 7;
|
|
// case 0xD8: // RET C
|
|
// // Check if the Carry Flag (Bit 0) IS set (1)
|
|
// if ((AF.Low & 0x01) != 0)
|
|
// {
|
|
// PC = Pop();
|
|
// return 11; // Condition met, took the return
|
|
// }
|
|
// return 5;
|
|
// case 0xD9: // EXX
|
|
// ushort tempBC = BC.Word;
|
|
// BC.Word = BC_Prime.Word;
|
|
// BC_Prime.Word = tempBC;
|
|
|
|
// ushort tempDE = DE.Word;
|
|
// DE.Word = DE_Prime.Word;
|
|
// DE_Prime.Word = tempDE;
|
|
|
|
// ushort tempHL = HL.Word;
|
|
// HL.Word = HL_Prime.Word;
|
|
// HL_Prime.Word = tempHL;
|
|
|
|
// return 4;
|
|
// case 0xDD:
|
|
// return ExecuteDDPrefix();
|
|
// case 0xDE: // SBC A, n
|
|
// Sbc(FetchByte());
|
|
// return 7;
|
|
// case 0xE1: // POP HL
|
|
// HL.Word = Pop();
|
|
// return 10;
|
|
// case 0xE3: // EX (SP), HL
|
|
// // 1. Read the 16-bit value currently on top of the stack
|
|
// byte spLow = ReadMemory(SP);
|
|
// byte spHigh = ReadMemory((ushort)(SP + 1));
|
|
|
|
// // 2. Write the current HL registers onto the stack in its place
|
|
// WriteMemory(SP, HL.Low);
|
|
// WriteMemory((ushort)(SP + 1), HL.High);
|
|
|
|
// // 3. Update HL with the data we pulled off the stack
|
|
// HL.Low = spLow;
|
|
// HL.High = spHigh;
|
|
|
|
// return 19;
|
|
// case 0xe5: //push bc
|
|
// Push(HL.Word);
|
|
// return 11;
|
|
// case 0xE6: // AND n
|
|
// And(FetchByte());
|
|
// return 7;
|
|
// case 0xE9: // JP (HL)
|
|
// PC = HL.Word;
|
|
// return 4; // Takes 4 T-States
|
|
// case 0xEB: // EX DE, HL
|
|
// ushort tempEx = DE.Word;
|
|
// DE.Word = HL.Word;
|
|
// HL.Word = tempEx;
|
|
// return 4; // Takes 4 T-States
|
|
// case 0xED:
|
|
// return ExecuteExtendedPrefix();
|
|
// case 0xEE: // XOR n
|
|
// byte xorImm = FetchByte();
|
|
|
|
// // Perform the bitwise XOR against the Accumulator
|
|
// AF.High = (byte)(AF.High ^ xorImm);
|
|
|
|
// // --- Update Flags ---
|
|
// // Start with a clean slate of 0 to perfectly clear C, H, and N!
|
|
// newFlags = 0;
|
|
|
|
// if ((AF.High & 0x80) != 0) newFlags |= 0x80; // Sign Flag
|
|
// if (AF.High == 0) newFlags |= 0x40; // Zero Flag
|
|
// if (CalculateParity(AF.High)) newFlags |= 0x04; // Parity/Overflow Flag
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// return 7; // Takes 7 T-States
|
|
// case 0xF1: // POP AF
|
|
// AF.Word = Pop();
|
|
// return 10;
|
|
// case 0xF3: // DI (Disable Interrupts)
|
|
// IFF1 = false;
|
|
// IFF2 = false;
|
|
// return 4;
|
|
// case 0xf5: //push bc
|
|
// Push(AF.Word);
|
|
// return 11;
|
|
// case 0xF6: // OR n
|
|
// Or(FetchByte());
|
|
// return 7;
|
|
// case 0xF9: // LD SP, HL
|
|
// SP = HL.Word; // (Use SP.Word = HL.Word if you made SP a RegisterPair)
|
|
// return 6;
|
|
// case 0xFB: // EI
|
|
// IFF1 = true;
|
|
// IFF2 = true;
|
|
// return 4;
|
|
// case 0xFD:
|
|
// return ExecuteFDPrefix();
|
|
// case 0xFE: // CP n
|
|
// Cp(FetchByte());
|
|
// return 7;
|
|
// default:
|
|
// throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
|
|
// }
|
|
// }
|
|
// private int ExecuteExtendedPrefix() //ED
|
|
// {
|
|
// // Fetch the actual extended instruction
|
|
// byte extendedOpcode = FetchByte();
|
|
// byte val = 0;
|
|
|
|
// switch (extendedOpcode)
|
|
// {
|
|
// case 0x43: // LD (nn), BC
|
|
// ushort dest43 = FetchWord();
|
|
// WriteMemory(dest43, BC.Low);
|
|
// WriteMemory((ushort)(dest43 + 1), BC.High);
|
|
// return 20;
|
|
// case 0x44: // NEG
|
|
// int aBefore = AF.High;
|
|
// result = 0 - aBefore;
|
|
|
|
// newFlags = 0;
|
|
|
|
// // S Flag (Bit 7): Set if the result is negative
|
|
// if ((result & 0x80) != 0) newFlags |= 0x80;
|
|
|
|
// // Z Flag (Bit 6): Set if the result is exactly zero
|
|
// if ((result & 0xFF) == 0) newFlags |= 0x40;
|
|
|
|
// // H Flag (Bit 4): Set if there was a borrow from Bit 3
|
|
// if ((0 - (aBefore & 0x0F)) < 0) newFlags |= 0x10;
|
|
|
|
// // P/V Flag (Bit 2): Overflow happens ONLY if A was 0x80 (-128)
|
|
// if (aBefore == 0x80) newFlags |= 0x04;
|
|
|
|
// // N Flag (Bit 1): Always set to 1 for NEG
|
|
// newFlags |= 0x02;
|
|
|
|
// // C Flag (Bit 0): Set if A was not 0 before the operation
|
|
// if (aBefore != 0) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
// AF.High = (byte)result;
|
|
|
|
// return 8; // 8 T-States
|
|
// case 0x47: // LD I, A
|
|
// I = AF.High;
|
|
// return 9;
|
|
// case 0x4B: // LD BC, (nn)
|
|
// ushort src4B = FetchWord();
|
|
// BC.Low = ReadMemory(src4B);
|
|
// BC.High = ReadMemory((ushort)(src4B + 1));
|
|
// return 20;
|
|
// case 0x4D: // RETI Does not affect IFF1 or IFF2
|
|
// PC = Pop();
|
|
// return 14;
|
|
// case 0x53: // LD (nn), DE
|
|
// ushort dest53 = FetchWord();
|
|
// WriteMemory(dest53, DE.Low);
|
|
// WriteMemory((ushort)(dest53 + 1), DE.High);
|
|
// return 20;
|
|
// case 0x56: // IM 1
|
|
// InterruptMode = 1;
|
|
// return 8;
|
|
// case 0x58: // IN E, (C)
|
|
// // 1. Read from the I/O port.
|
|
// // CRITICAL: We must pass the FULL BC register, not just C!
|
|
// byte inVal58 = ReadPort(BC.Word);
|
|
|
|
// // 2. Store the hardware data in register E (Low byte of DE)
|
|
// DE.Low = inVal58;
|
|
|
|
// // 3. Update the Flags Register (F)
|
|
// // The Carry flag (C) is strictly preserved. H and N are always reset to 0.
|
|
// byte flags58 = (byte)(AF.Low & 0x01);
|
|
|
|
// if ((inVal58 & 0x80) != 0) flags58 |= 0x80; // S: Sign flag
|
|
// if (inVal58 == 0) flags58 |= 0x40; // Z: Zero flag
|
|
|
|
// // P/V: Parity flag. Collapse the bits to check if the number of 1s is even
|
|
// byte p58 = inVal58;
|
|
// p58 ^= (byte)(p58 >> 4);
|
|
// p58 ^= (byte)(p58 >> 2);
|
|
// p58 ^= (byte)(p58 >> 1);
|
|
// if ((p58 & 1) == 0) flags58 |= 0x04; // Set if Parity is Even
|
|
|
|
// // Undocumented bits 3 and 5 are copied directly from the input byte
|
|
// flags58 |= (byte)(inVal58 & 0x28);
|
|
|
|
// AF.Low = flags58;
|
|
|
|
// return 12;
|
|
// case 0x5B: // LD DE, (nn)
|
|
// ushort src5B = FetchWord();
|
|
// DE.Low = ReadMemory(src5B);
|
|
// DE.High = ReadMemory((ushort)(src5B + 1));
|
|
// return 20;
|
|
// case 0x5E: // IM 2
|
|
// // Set the CPU's internal interrupt mode state
|
|
// InterruptMode = 2;
|
|
// return 8;
|
|
// case 0x5F: // LD A, R
|
|
// // 1. Load the Refresh register into the Accumulator
|
|
// AF.High = R;
|
|
|
|
// // 2. Calculate Flags
|
|
// // CRITICAL: Preserve the existing Carry flag (Bit 0).
|
|
// // H (Bit 4) and N (Bit 1) are forcefully reset to 0.
|
|
// newFlags = (byte)(AF.Low & 0x01);
|
|
|
|
// // S Flag (Bit 7): Set if the result is negative
|
|
// if ((AF.High & 0x80) != 0) newFlags |= 0x80;
|
|
|
|
// // Z Flag (Bit 6): Set if the result is zero
|
|
// if (AF.High == 0) newFlags |= 0x40;
|
|
|
|
// // P/V Flag (Bit 2): Set if IFF2 is true (This is the interrupt check hack!)
|
|
// if (IFF2) newFlags |= 0x04;
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// return 9;
|
|
// // --- SBC HL, rr ---
|
|
// case 0x42: SbcHl(BC.Word); return 15;
|
|
// case 0x52: SbcHl(DE.Word); return 15;
|
|
// case 0x62: SbcHl(HL.Word); return 15;
|
|
// case 0x72: SbcHl(SP); return 15;
|
|
// case 0x73: // LD (nn), SP
|
|
// ushort dest73 = FetchWord();
|
|
// WriteMemory(dest73, (byte)SP);
|
|
// WriteMemory((ushort)(dest73 + 1), (byte)(SP >> 8));
|
|
// return 20;
|
|
// case 0x78: // IN A, (C)
|
|
// // Read from the hardware port using the full BC register as the address
|
|
// byte portVal78 = ReadPort(BC.Word);
|
|
// AF.High = portVal78;
|
|
|
|
// // --- Update Flags ---
|
|
// // S (Bit 7), Z (Bit 6), P/V (Bit 2) are set based on the input.
|
|
// // H (Bit 4) and N (Bit 1) are RESET.
|
|
// // C (Bit 0) is PRESERVED.
|
|
|
|
// newFlags = (byte)(AF.Low & 0x01); // Preserve Carry
|
|
|
|
// if ((portVal78 & 0x80) != 0) newFlags |= 0x80; // Sign Flag
|
|
// if (portVal78 == 0) newFlags |= 0x40; // Zero Flag
|
|
// if (CalculateParity(portVal78)) newFlags |= 0x04; // Parity/Overflow Flag
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// return 12;
|
|
// case 0x79: // OUT (C), A
|
|
// _simpleIoBus.WritePort(BC.Word, AF.High);
|
|
// return 12;
|
|
// // --- ADC HL, rr ---
|
|
// case 0x4A: AdcHl(BC.Word); return 15;
|
|
// case 0x5A: AdcHl(DE.Word); return 15;
|
|
// case 0x6A: AdcHl(HL.Word); return 15;
|
|
// case 0x7A: AdcHl(SP); return 15;
|
|
// case 0x7B: // LD SP, (nn)
|
|
// // 1. Fetch the absolute 16-bit memory address from the instruction stream
|
|
// byte addrLow = FetchByte();
|
|
// byte addrHigh = FetchByte();
|
|
// ushort address7B = (ushort)((addrHigh << 8) | addrLow);
|
|
|
|
// // 2. Read the 16-bit value stored at that exact memory location (Little-Endian!)
|
|
// byte spLow = ReadMemory(address7B);
|
|
// byte spHigh = ReadMemory((ushort)(address7B + 1));
|
|
|
|
// // 3. Load the resulting 16-bit value directly into the Stack Pointer
|
|
// SP = (ushort)((spHigh << 8) | spLow);
|
|
|
|
// return 20;
|
|
// case 0xA0: // LDI
|
|
// // 1. Read byte from (HL)
|
|
// val = ReadMemory(HL.Word);
|
|
|
|
// // 2. Write byte to (DE)
|
|
// WriteMemory(DE.Word, val);
|
|
|
|
// // 3. Increment memory pointers, Decrement byte counter
|
|
// HL.Word++;
|
|
// DE.Word++;
|
|
// BC.Word--;
|
|
|
|
// // 4. Update Flags
|
|
// // Preserve S (0x80), Z (0x40), and C (0x01).
|
|
// // H (0x10) and N (0x02) are forcefully reset to 0.
|
|
// AF.Low &= 0xC1;
|
|
|
|
// // P/V Flag (Bit 2) is set to 1 if BC is not 0 after the decrement
|
|
// if (BC.Word != 0)
|
|
// {
|
|
// AF.Low |= 0x04;
|
|
// }
|
|
|
|
// return 16;
|
|
// case 0xB0: // LDIR
|
|
// // 1. Read byte from (HL)
|
|
// val = ReadMemory(HL.Word);
|
|
|
|
// // 2. Write byte to (DE)
|
|
// WriteMemory(DE.Word, val);
|
|
|
|
// // 3. Increment memory pointers, Decrement byte counter
|
|
// HL.Word++;
|
|
// DE.Word++;
|
|
// BC.Word--;
|
|
|
|
// // 4. Update Flags
|
|
// // Preserve S (0x80), Z (0x40), and C (0x01).
|
|
// // H (0x10) and N (0x02) are always reset to 0.
|
|
// AF.Low &= 0xC1;
|
|
|
|
// // P/V Flag (Bit 2) is set to 1 if BC is not 0
|
|
// if (BC.Word != 0)
|
|
// {
|
|
// AF.Low |= 0x04;
|
|
|
|
// // Rewind the PC so the CPU executes this instruction again!
|
|
// PC -= 2;
|
|
// return 21; // Looping
|
|
// }
|
|
// 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
|
|
// // 1. Read byte from (HL)
|
|
// val = ReadMemory(HL.Word);
|
|
|
|
// // 2. Write byte to (DE)
|
|
// WriteMemory(DE.Word, val);
|
|
|
|
// // 3. Decrement all three pointers
|
|
// HL.Word--;
|
|
// DE.Word--;
|
|
// BC.Word--;
|
|
|
|
// // 4. Update Flags
|
|
// // Preserve S (0x80), Z (0x40), and C (0x01).
|
|
// // H (0x10) and N (0x02) are always reset to 0.
|
|
// AF.Low &= 0xC1;
|
|
|
|
// // P/V Flag (Bit 2) is set to 1 if BC is not 0
|
|
// if (BC.Word != 0)
|
|
// {
|
|
// AF.Low |= 0x04;
|
|
|
|
// // Rewind the PC so the CPU executes this instruction again!
|
|
// PC -= 2;
|
|
// return 21; // Looping
|
|
// }
|
|
|
|
// return 16; // Finished!
|
|
// default:
|
|
// throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
|
|
// }
|
|
// }
|
|
|
|
// private int ExecuteCBPrefix()
|
|
// {
|
|
// byte cbOpcode = FetchByte();
|
|
// bool oldCarry = false;
|
|
|
|
// // 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 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 = ReadMemory(HL.Word); break; // The 0x110 (HL) exception
|
|
// case 7: val = AF.High; break;
|
|
// }
|
|
|
|
// // --- 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
|
|
|
|
// if ((val & bitMask) == 0)
|
|
// {
|
|
// AF.Low |= 0x44; // Set Zero and P/V
|
|
// }
|
|
// else if (bitIndex == 7)
|
|
// {
|
|
// AF.Low |= 0x80; // If testing Bit 7 and it is 1, set Sign
|
|
// }
|
|
|
|
// // BIT (HL) takes 12 T-States. Standard register BIT takes 8.
|
|
// return (regIndex == 6) ? 12 : 8;
|
|
|
|
// case 2: // ALL RES Instructions
|
|
// val &= (byte)(~bitMask);
|
|
// break; // Proceed to write-back
|
|
|
|
// case 3: // ALL SET Instructions
|
|
// val |= bitMask;
|
|
// break; // Proceed to write-back
|
|
|
|
// case 0: // ALL Shift/Rotate Instructions
|
|
// // The specific shift type is in the same bits we previously used for 'bitIndex'
|
|
// int shiftType = (cbOpcode >> 3) & 0x07;
|
|
// bool carryOut = false;
|
|
|
|
// switch (shiftType)
|
|
// {
|
|
// case 0: // RLC
|
|
// // Grab Bit 7 to see if it's going to fall off
|
|
// carryOut = (val & 0x80) != 0;
|
|
// // Shift left, and loop the falling bit back into Bit 0
|
|
// val = (byte)((val << 1) | (carryOut ? 1 : 0));
|
|
// break;
|
|
// case 1: // RRC (Rotate Right Circular)
|
|
// // 1. Grab Bit 0 before it falls off to set the Carry flag and loop to Bit 7
|
|
// carryOut = (val & 0x01) != 0;
|
|
|
|
// // 2. Shift right by 1, and loop the falling bit directly back into Bit 7
|
|
// val = (byte)((val >> 1) | (carryOut ? 0x80 : 0x00));
|
|
// break;
|
|
// case 2: // RL (Rotate Left through Carry)
|
|
// // 1. Grab the CURRENT Carry flag from the AF register
|
|
// oldCarry = (AF.Low & 0x01) != 0;
|
|
|
|
// // 2. Grab Bit 7 before it falls off to become the NEW Carry flag
|
|
// carryOut = (val & 0x80) != 0;
|
|
|
|
// // 3. Shift left by 1, and drop the OLD carry flag directly into Bit 0
|
|
// val = (byte)((val << 1) | (oldCarry ? 0x01 : 0x00));
|
|
// break;
|
|
// case 3: // RR (Rotate Right through Carry)
|
|
// // 1. Grab the CURRENT Carry flag from the AF register
|
|
// oldCarry = (AF.Low & 0x01) != 0;
|
|
|
|
// // 2. Grab Bit 0 before it falls off to become the NEW Carry flag
|
|
// carryOut = (val & 0x01) != 0;
|
|
|
|
// // 3. Shift right by 1, and drop the OLD carry flag into Bit 7
|
|
// val = (byte)((val >> 1) | (oldCarry ? 0x80 : 0x00));
|
|
// break;
|
|
// case 4: // SLA (Shift Left Arithmetic)
|
|
// // 1. Grab Bit 7 before it falls off to set the Carry flag
|
|
// carryOut = (val & 0x80) != 0;
|
|
|
|
// // 2. Shift the byte left by 1.
|
|
// // (In C#, a standard left shift automatically pads Bit 0 with a 0)
|
|
// val = (byte)(val << 1);
|
|
// break;
|
|
// case 5: // SRA (Shift Right Arithmetic)
|
|
// // 1. Grab Bit 0 before it falls off to set the Carry flag
|
|
// carryOut = (val & 0x01) != 0;
|
|
|
|
// // 2. Grab the current Sign bit (Bit 7) so we can preserve it
|
|
// byte signBit = (byte)(val & 0x80);
|
|
|
|
// // 3. Shift the byte right by 1.
|
|
// // (Because 'val' is unsigned, C# naturally pads the top with a 0)
|
|
// val = (byte)(val >> 1);
|
|
|
|
// // 4. Force the preserved sign bit back into Bit 7
|
|
// val |= signBit;
|
|
// break;
|
|
// case 7: // SRL (Shift Right Logical)
|
|
// // 1. Grab Bit 0 before it falls off to set the Carry flag
|
|
// carryOut = (val & 0x01) != 0;
|
|
|
|
// // 2. Shift the byte right by 1.
|
|
// // (In C#, a standard right shift on a positive byte automatically pads Bit 7 with a 0)
|
|
// val = (byte)(val >> 1);
|
|
// break;
|
|
// // (We will add RRC, RL, RR, SLA, SRA, here as the ROM asks for them!)
|
|
// default:
|
|
// throw new NotImplementedException($"CB Shift instruction type {shiftType} not implemented!");
|
|
// }
|
|
|
|
// // --- Update Flags ---
|
|
// // All CB Shift instructions calculate flags the exact same way!
|
|
// // They set S, Z, P/V, and C. They forcefully clear H and N.
|
|
// byte newFlags = 0;
|
|
|
|
// if (carryOut) newFlags |= 0x01; // C Flag
|
|
// if ((val & 0x80) != 0) newFlags |= 0x80; // S Flag
|
|
// if (val == 0) newFlags |= 0x40; // Z Flag
|
|
// if (CalculateParity(val)) newFlags |= 0x04; // P/V Flag
|
|
|
|
// AF.Low = newFlags; // Apply the new flags
|
|
|
|
// break; // Proceed to the write-back phase
|
|
|
|
// default:
|
|
// 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: WriteMemory(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 ExecuteDDPrefix()
|
|
// {
|
|
// byte ddOpcode = FetchByte(); // Fetch the actual instruction after 0xDD
|
|
|
|
// switch (ddOpcode)
|
|
// {
|
|
// case 0x09: // ADD IX, BC
|
|
// Add16IX(BC.Word);
|
|
// return 15;
|
|
// case 0x19: // ADD IX, DE
|
|
// Add16IX(DE.Word);
|
|
// return 15;
|
|
// case 0x29: // ADD IX, IX
|
|
// Add16IX(IX.Word); // Multiplies IX by 2!
|
|
// return 15;
|
|
// case 0x39: // ADD IX, SP
|
|
// Add16IX(SP);
|
|
// return 15;
|
|
// case 0x21: // LD IX, nn
|
|
// byte low = FetchByte();
|
|
// byte high = FetchByte();
|
|
// IX.Word = (ushort)((high << 8) | low);
|
|
|
|
// return 14;
|
|
// case 0x22: // LD (nn), IX
|
|
// // 1. Fetch the absolute 16-bit memory address from the instruction stream
|
|
// byte addrLow22 = FetchByte();
|
|
// byte addrHigh22 = FetchByte();
|
|
// ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22);
|
|
|
|
// // 2. Write the LOW byte of IX to the exact address
|
|
// WriteMemory(address22, IX.Low);
|
|
|
|
// // 3. Write the HIGH byte of IX to the address + 1
|
|
// WriteMemory((ushort)(address22 + 1), IX.High);
|
|
|
|
// return 20;
|
|
// case 0x23: // INC IX
|
|
// // Increment the full 16-bit register. Do NOT touch AF.Low!
|
|
// IX.Word++;
|
|
// return 10;
|
|
// case 0x24: // INC IXH
|
|
// // Increment the high byte of IX and let the helper perfectly map the flags
|
|
// IX.High = Inc8(IX.High);
|
|
// return 8;
|
|
// case 0x25: // DEC IXH
|
|
// // Decrement the high byte of IX and let the helper handle all the Z80 flags
|
|
// IX.High = Dec8(IX.High);
|
|
// return 8;
|
|
// case 0x26: // LD IXH, n
|
|
// // Fetch the immediate 8-bit value and drop it straight into the high byte of IX
|
|
// IX.High = FetchByte();
|
|
// return 11;
|
|
// case 0x2A: // LD IX, (nn)
|
|
// // 1. Fetch the absolute 16-bit memory address from the instruction stream
|
|
// byte addrLow2A = FetchByte();
|
|
// byte addrHigh2A = FetchByte();
|
|
// ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A);
|
|
|
|
// // 2. Read the LOW byte from that specific memory location
|
|
// byte ixLow = ReadMemory(address2A);
|
|
|
|
// // 3. Read the HIGH byte from the next consecutive memory location
|
|
// byte ixHigh = ReadMemory((ushort)(address2A + 1));
|
|
|
|
// // 4. Combine them and drop them into the IX register pair
|
|
// IX.Word = (ushort)((ixHigh << 8) | ixLow);
|
|
|
|
// return 20;
|
|
// case 0x2B: // DEC IX
|
|
// // Decrement the full 16-bit register. The F register remains completely untouched.
|
|
// IX.Word--;
|
|
// return 10;
|
|
// case 0x2D: // DEC IXL
|
|
// IX.Low = Dec8(IX.Low);
|
|
// return 8;
|
|
// case 0x2E: // LD IXL, n
|
|
// // Fetch the immediate 8-bit value and drop it straight into the low byte of IX
|
|
// IX.Low = FetchByte();
|
|
// return 11;
|
|
// case 0x34: // INC (IX+d)
|
|
// // 1. Fetch the displacement byte and cast to a signed sbyte
|
|
// sbyte offset34 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the target memory address
|
|
// ushort address34 = (ushort)(IX.Word + offset34);
|
|
|
|
// // 3. Read the value from memory
|
|
// byte val34 = ReadMemory(address34);
|
|
|
|
// // 4. Pass it through your helper to increment and set the flags perfectly
|
|
// byte result34 = Inc8(val34);
|
|
|
|
// // 5. Write the incremented value back to memory
|
|
// WriteMemory(address34, result34);
|
|
|
|
// return 23;
|
|
// case 0x35: // DEC (IX+d)
|
|
// // 1. Fetch the displacement byte and cast to a signed sbyte
|
|
// sbyte offset35 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the target memory address
|
|
// ushort address35 = (ushort)(IX.Word + offset35);
|
|
|
|
// // 3. Read the value from memory
|
|
// byte val35 = ReadMemory(address35);
|
|
|
|
// // 4. Pass it through your helper to decrement and set the flags
|
|
// byte result35 = Dec8(val35);
|
|
|
|
// // 5. Write the decremented value back to memory
|
|
// WriteMemory(address35, result35);
|
|
|
|
// return 23;
|
|
// case 0x36: // LD (IX+d), n
|
|
// // 1. Fetch the displacement byte first
|
|
// sbyte offset36 = (sbyte)FetchByte();
|
|
|
|
// // 2. Fetch the immediate 8-bit value second
|
|
// byte n36 = FetchByte();
|
|
|
|
// // 3. Calculate the exact memory address (IX + offset)
|
|
// ushort address36 = (ushort)(IX.Word + offset36);
|
|
|
|
// // 4. Write the immediate value directly into memory
|
|
// WriteMemory(address36, n36);
|
|
|
|
// return 19;
|
|
// case 0x46: // LD B, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset46 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address46 = (ushort)(IX.Word + offset46);
|
|
|
|
// // 3. Read the byte from memory and drop it into the C register (Low byte of BC)
|
|
// BC.High = ReadMemory(address46);
|
|
|
|
// return 19;
|
|
// case 0x4E: // LD C, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset4E = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address4E = (ushort)(IX.Word + offset4E);
|
|
|
|
// // 3. Read the byte from memory and drop it into the C register (Low byte of BC)
|
|
// BC.Low = ReadMemory(address4E);
|
|
|
|
// return 19;
|
|
// case 0x56: // LD D, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset56 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address56 = (ushort)(IX.Word + offset56);
|
|
|
|
// // 3. Read the byte from memory and drop it into the D register (High byte of DE)
|
|
// DE.High = ReadMemory(address56);
|
|
|
|
// return 19;
|
|
// case 0x5E: // LD E, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset5E = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address5E = (ushort)(IX.Word + offset5E);
|
|
|
|
// // 3. Read the byte from memory and drop it into the E register
|
|
// DE.Low = ReadMemory(address5E);
|
|
|
|
// return 19;
|
|
// case 0x66: // LD H, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset66 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address66 = (ushort)(IX.Word + offset66);
|
|
|
|
// // 3. Read the byte from memory and drop it into the H register (High byte of HL)
|
|
// HL.High = ReadMemory(address66);
|
|
|
|
// return 19;
|
|
// case 0x67: // LD IXH, A
|
|
// // Load the Accumulator (AF.High) directly into the high byte of IX
|
|
// IX.High = AF.High;
|
|
// return 8;
|
|
// case 0x68: // LD IXL, B
|
|
// IX.Low = BC.High;
|
|
// return 8;
|
|
// case 0x69: // LD IXL, C
|
|
// // Load the C register (BC.Low) into the low byte of IX
|
|
// IX.Low = BC.Low;
|
|
// return 8;
|
|
|
|
// case 0x6A: // LD IXL, D
|
|
// // Load the D register (DE.High) into the low byte of IX
|
|
// IX.Low = DE.High;
|
|
// return 8;
|
|
// case 0x6E: // LD L, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset6E = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address6E = (ushort)(IX.Word + offset6E);
|
|
|
|
// // 3. Read the byte from memory and drop it into the L register (Low byte of HL)
|
|
// HL.Low = ReadMemory(address6E);
|
|
|
|
// return 19;
|
|
// case 0x6F: // LD IXL, A
|
|
// IX.Low = AF.High;
|
|
// return 8;
|
|
// case 0x70: // LD (IX+d), B
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset70 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address70 = (ushort)(IX.Word + offset70);
|
|
|
|
// // 3. Write the B register (High byte of BC) into memory
|
|
// WriteMemory(address70, BC.High);
|
|
|
|
// return 19;
|
|
// case 0x71: // LD (IX+d), C
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset71 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address71 = (ushort)(IX.Word + offset71);
|
|
|
|
// // 3. Write the C register (Low byte of BC) into memory
|
|
// WriteMemory(address71, BC.Low);
|
|
|
|
// return 19;
|
|
// case 0x72: // LD (IX+d), D
|
|
// // 1. Fetch the displacement byte and cast to a signed sbyte
|
|
// sbyte offset72 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the target memory address
|
|
// ushort address72 = (ushort)(IX.Word + offset72);
|
|
|
|
// // 3. Write the D register (DE.High) to memory
|
|
// WriteMemory(address72, DE.High);
|
|
|
|
// return 19; // 19 T-States
|
|
// case 0x73: // LD (IX+d), E
|
|
// // 1. Fetch the displacement byte and cast to a signed sbyte
|
|
// sbyte offset73 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the target memory address
|
|
// ushort address73 = (ushort)(IX.Word + offset73);
|
|
|
|
// // 3. Write the E register (DE.Low) to memory
|
|
// WriteMemory(address73, DE.Low);
|
|
|
|
// return 19;
|
|
|
|
// case 0x74: // LD (IX+d), H
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset74 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address74 = (ushort)(IX.Word + offset74);
|
|
|
|
// // 3. Write the contents of the L register (Low byte of HL) into memory
|
|
// WriteMemory(address74, HL.High);
|
|
|
|
// return 19;
|
|
// case 0x75: // LD (IX+d), L
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset75 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address75 = (ushort)(IX.Word + offset75);
|
|
|
|
// // 3. Write the contents of the L register (Low byte of HL) into memory
|
|
// WriteMemory(address75, HL.Low);
|
|
|
|
// return 19;
|
|
// case 0x77: // LD (IX+d), A
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset77 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address77 = (ushort)(IX.Word + offset77);
|
|
|
|
// // 3. Write the Accumulator (AF.High) into memory at that address
|
|
// WriteMemory(address77, AF.High);
|
|
|
|
// return 19;
|
|
// case 0x7C: // LD A, IXH
|
|
// // Load the high byte of IX directly into the Accumulator
|
|
// AF.High = IX.High;
|
|
// return 8;
|
|
// case 0x7E: // LD A, (IX+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset7E = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IX + offset)
|
|
// ushort address7E = (ushort)(IX.Word + offset7E);
|
|
|
|
// // 3. Read the byte from memory and drop it straight into the Accumulator (A)
|
|
// AF.High = ReadMemory(address7E);
|
|
|
|
// return 19;
|
|
// // Inside ExecuteDDPrefix():
|
|
|
|
// case 0x84: // ADD A, IXH
|
|
// AddA(IX.High); // Assuming your 16-bit register struct has a .High property
|
|
// return 8;
|
|
|
|
// case 0x85: // ADD A, IXL
|
|
// AddA(IX.Low);
|
|
// return 8;
|
|
// case 0x86: // ADD A, (IX+d)
|
|
// sbyte offset86 = (sbyte)FetchByte();
|
|
// ushort address86 = (ushort)(IX.Word + offset86);
|
|
|
|
// // Read the memory and pass it straight into your flawless helper!
|
|
// Add(ReadMemory(address86));
|
|
// return 19;
|
|
|
|
// case 0x96: // SUB (IX+d)
|
|
// sbyte offset96 = (sbyte)FetchByte();
|
|
// ushort address96 = (ushort)(IX.Word + offset96);
|
|
|
|
// // Read the memory and pass it straight into your flawless helper!
|
|
// Sub(ReadMemory(address96));
|
|
// return 19;
|
|
// case 0xBE: // CP (IX+d)
|
|
// // 1. Fetch the displacement byte and calculate the address
|
|
// sbyte offsetBE = (sbyte)FetchByte();
|
|
// ushort addressBE = (ushort)(IX.Word + offsetBE);
|
|
|
|
// // 2. Read the value from memory
|
|
// byte cpVal = ReadMemory(addressBE);
|
|
|
|
// // 3. Perform the phantom subtraction
|
|
// int aVal = AF.High;
|
|
// result = aVal - cpVal;
|
|
|
|
// // --- 8-Bit Compare Flag Calculation (Identical to SUB) ---
|
|
// newFlags = 0;
|
|
|
|
// // S Flag (Bit 7): Set if the phantom result is negative
|
|
// if ((result & 0x80) != 0) newFlags |= 0x80;
|
|
|
|
// // Z Flag (Bit 6): Set if A perfectly matches the memory value (A - value == 0)
|
|
// if ((result & 0xFF) == 0) newFlags |= 0x40;
|
|
|
|
// // H Flag (Bit 4): Set if there was a borrow from Bit 3
|
|
// if (((aVal & 0x0F) - (cpVal & 0x0F)) < 0) newFlags |= 0x10;
|
|
|
|
// // P/V Flag (Bit 2): Set on Overflow
|
|
// if ((((aVal ^ cpVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04;
|
|
|
|
// // N Flag (Bit 1): Always set to 1 for Subtractions/Compares
|
|
// newFlags |= 0x02;
|
|
|
|
// // C Flag (Bit 0): Set if A was smaller than the memory value
|
|
// if (aVal < cpVal) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// // CRITICAL: Notice we do NOT update AF.High! The Accumulator is preserved.
|
|
|
|
// return 19;
|
|
// case 0xCB: // The DD CB nested prefix
|
|
// {
|
|
// // 1. Fetch the displacement byte first
|
|
// sbyte displacement = (sbyte)FetchByte();
|
|
|
|
// // 2. Fetch the actual operation opcode (like your 0x72) second
|
|
// byte cbOpcode = FetchByte();
|
|
|
|
// ushort targetAddress = (ushort)(IX.Word + displacement);
|
|
// byte memVal = ReadMemory(targetAddress);
|
|
|
|
// // 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
|
|
|
|
// switch (operation)
|
|
// {
|
|
// case 1: // ALL BIT Instructions
|
|
// AF.Low &= 0x01; // Preserve ONLY Carry
|
|
// AF.Low |= 0x10; // Set Half-Carry
|
|
|
|
// if ((memVal & bitMask) == 0)
|
|
// {
|
|
// 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)
|
|
// }
|
|
// return 20; // 20 T-States
|
|
|
|
// // (You can copy your RES and SET logic from ExecuteFDPrefix here later!)
|
|
|
|
// default:
|
|
// throw new NotImplementedException($"DD CB opcode {cbOpcode:X2} not fully implemented!");
|
|
// }
|
|
// }
|
|
// case 0xE1: // POP IX
|
|
// // 1. Read the low byte from the top of the stack
|
|
// byte popLow = ReadMemory(SP);
|
|
// SP++; // Move stack pointer up
|
|
|
|
// // 2. Read the high byte
|
|
// byte popHigh = ReadMemory(SP);
|
|
// SP++; // Move stack pointer up again
|
|
|
|
// // 3. Combine them and store in IX
|
|
// IX.Word = (ushort)((popHigh << 8) | popLow);
|
|
|
|
// return 14;
|
|
// case 0xE5: // PUSH IX
|
|
// // 1. Decrement the stack pointer and write the HIGH byte
|
|
// SP--;
|
|
// WriteMemory(SP, IX.High);
|
|
|
|
// // 2. Decrement the stack pointer again and write the LOW byte
|
|
// SP--;
|
|
// WriteMemory(SP, IX.Low);
|
|
|
|
// return 15;
|
|
// case 0xE9: // JP (IX)
|
|
// PC = IX.Word;
|
|
// return 8;
|
|
|
|
// default:
|
|
// throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} not implemented!");
|
|
// }
|
|
// }
|
|
|
|
// private int ExecuteFDPrefix()
|
|
// {
|
|
// byte opcode = FetchByte();
|
|
// ushort targetAddress = 0;
|
|
// byte memVal = 0;
|
|
|
|
// switch (opcode)
|
|
// {
|
|
// // Inside ExecuteFDPrefix()
|
|
// case 0x09: AddIy(BC.Word); return 15;
|
|
// case 0x19: AddIy(DE.Word); return 15; // This is the exact instruction that crashed!
|
|
// case 0x21: // LD IY, nn
|
|
// IY.Word = FetchWord();
|
|
// return 14;
|
|
// case 0x23: // INC IY
|
|
// // Increment the full 16-bit register. The F register remains completely untouched.
|
|
// IY.Word++;
|
|
// return 10;
|
|
|
|
// case 0x29: AddIy(IY.Word); return 15;
|
|
// case 0x34: // INC (IY+d)
|
|
// // 1. Fetch displacement and calculate memory address
|
|
// sbyte offset34 = (sbyte)FetchByte();
|
|
// ushort address34 = (ushort)(IY.Word + offset34);
|
|
|
|
// // 2. Read the value from memory
|
|
// byte valBefore = ReadMemory(address34);
|
|
|
|
// // 3. Increment the value
|
|
// int result = valBefore + 1;
|
|
|
|
// // --- 8-Bit Increment Flag Calculation ---
|
|
// // CRITICAL: We must preserve the existing Carry flag (Bit 0)!
|
|
// byte newFlags = (byte)(AF.Low & 0x01);
|
|
|
|
// // S Flag (Bit 7): Set if the result is negative
|
|
// if ((result & 0x80) != 0) newFlags |= 0x80;
|
|
|
|
// // Z Flag (Bit 6): Set if the result is exactly zero (wrapped from 255 to 0)
|
|
// if ((result & 0xFF) == 0) newFlags |= 0x40;
|
|
|
|
// // H Flag (Bit 4): Set if there was a carry out of Bit 3
|
|
// if ((valBefore & 0x0F) == 0x0F) newFlags |= 0x10;
|
|
|
|
// // P/V Flag (Bit 2): Set on Overflow
|
|
// // For INC, overflow ONLY happens if we increment 0x7F (+127) and it wraps to 0x80 (-128)
|
|
// if (valBefore == 0x7F) newFlags |= 0x04;
|
|
|
|
// // N Flag (Bit 1): Always reset to 0 for an increment
|
|
// // (Our bitwise AND at the top already cleared it)
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// // 4. Write the modified value back to memory
|
|
// WriteMemory(address34, (byte)result);
|
|
|
|
// return 23; // 23 T-States
|
|
// case 0x35: // DEC (IY+d)
|
|
// sbyte offset = (sbyte)FetchByte();
|
|
// targetAddress = (ushort)(IY.Word + offset);
|
|
|
|
// // Read, decrement using your existing helper, and write back
|
|
// memVal = ReadMemory(targetAddress);
|
|
// byte decVal = Dec8(memVal);
|
|
// WriteMemory(targetAddress, decVal);
|
|
// return 23;
|
|
// case 0x36: // LD (IY+d), n
|
|
// {
|
|
// sbyte offset36 = (sbyte)FetchByte();
|
|
// byte nValue = FetchByte();
|
|
// targetAddress = (ushort)(IY.Word + offset36);
|
|
|
|
// WriteMemory(targetAddress, nValue);
|
|
// return 19; // Takes 19 T-States
|
|
// }
|
|
|
|
// case 0x39: AddIy(SP); return 15;
|
|
// case 0x46: // LD B, (IY+d)
|
|
// {
|
|
// sbyte displacement = (sbyte)FetchByte();
|
|
// targetAddress = (ushort)(IY.Word + displacement);
|
|
|
|
// BC.High = ReadMemory(targetAddress);
|
|
// return 19; // Takes 19 T-States
|
|
// }
|
|
// case 0x4E: // LD C, (IY+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset4E = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the final address (IY + offset)
|
|
// ushort address4E = (ushort)(IY.Word + offset4E);
|
|
|
|
// // 3. Read the memory and store it in C
|
|
// BC.Low = ReadMemory(address4E);
|
|
|
|
// return 19;
|
|
// case 0x56: // LD D, (IY+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset56 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the final address (IY + offset)
|
|
// ushort address56 = (ushort)(IY.Word + offset56);
|
|
|
|
// // 3. Read the memory and store it in D (the high byte of DE)
|
|
// DE.High = ReadMemory(address56);
|
|
|
|
// return 19;
|
|
// case 0x5E: // LD E, (IY+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset5E = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the final address (IY + offset)
|
|
// ushort address5E = (ushort)(IY.Word + offset5E);
|
|
|
|
// // 3. Read the memory and store it in E (the low byte of DE)
|
|
// DE.Low = ReadMemory(address5E);
|
|
|
|
// return 19;
|
|
// case 0x66: // LD H, (IY+d)
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset66 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IY + offset)
|
|
// ushort address66 = (ushort)(IY.Word + offset66);
|
|
|
|
// // 3. Read the byte from memory and drop it into the H register (High byte of HL)
|
|
// HL.High = ReadMemory(address66);
|
|
|
|
// return 19;
|
|
// case 0x6E: // LD L, (IY+d)
|
|
// sbyte displacementVal = (sbyte)FetchByte();
|
|
// ushort targetAddr = (ushort)(IY.Word + displacementVal);
|
|
|
|
// HL.Low = ReadMemory(targetAddr);
|
|
// return 19;
|
|
// case 0x71: // LD (IY+d), C
|
|
// {
|
|
// sbyte offset71 = (sbyte)FetchByte();
|
|
// targetAddress = (ushort)(IY.Word + offset71);
|
|
|
|
// // Write the C register (low byte of BC) to memory
|
|
// WriteMemory(targetAddress, BC.Low);
|
|
// return 19; // Takes 19 T-States
|
|
// }
|
|
// case 0x72: // LD (IY+d), D
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset72 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IY + offset)
|
|
// ushort address72 = (ushort)(IY.Word + offset72);
|
|
|
|
// // 3. Write the contents of the D register (High byte of DE) into memory
|
|
// WriteMemory(address72, DE.High);
|
|
|
|
// return 19;
|
|
// case 0x73: // LD (IY+d), E
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset73 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IY + offset)
|
|
// ushort address73 = (ushort)(IY.Word + offset73);
|
|
|
|
// // 3. Write the contents of the E register (Low byte of DE) into memory
|
|
// WriteMemory(address73, DE.Low);
|
|
// return 19;
|
|
// case 0x74: // LD (IY+d), H
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset74 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IY + offset)
|
|
// ushort address74 = (ushort)(IY.Word + offset74);
|
|
|
|
// // 3. Write the contents of the H register into memory at that address
|
|
// WriteMemory(address74, HL.High);
|
|
// return 19;
|
|
// case 0x75: // LD (IY+d), L
|
|
// sbyte offset75 = (sbyte)FetchByte();
|
|
// targetAddress = (ushort)(IY.Word + offset75);
|
|
// // Write the low byte of HL to memory
|
|
// WriteMemory(targetAddress, HL.Low);
|
|
// return 19;
|
|
// case 0x77: // LD (IY+d), A
|
|
// // 1. Fetch the displacement byte and cast it to a signed sbyte
|
|
// sbyte offset77 = (sbyte)FetchByte();
|
|
|
|
// // 2. Calculate the exact memory address (IY + offset)
|
|
// ushort address77 = (ushort)(IY.Word + offset77);
|
|
|
|
// // 3. Write the Accumulator (A) into memory
|
|
// WriteMemory(address77, AF.High);
|
|
|
|
// 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;
|
|
// case 0x84: // ADD A, IYH
|
|
// AddA(IY.High);
|
|
// return 8;
|
|
|
|
// case 0x85: // ADD A, IYL
|
|
// AddA(IY.Low);
|
|
// return 8;
|
|
// case 0x86: // ADD A, (IY+d)
|
|
// {
|
|
// sbyte displacementAdd = (sbyte)FetchByte();
|
|
// ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd);
|
|
|
|
// byte valueToAdd = ReadMemory(targetAddressAdd);
|
|
// AddA(valueToAdd);
|
|
|
|
// return 19;
|
|
// }
|
|
// case 0x96: // SUB (IY+d)
|
|
// // 1. Fetch the displacement byte and calculate the address
|
|
// sbyte offset96 = (sbyte)FetchByte();
|
|
// ushort address96 = (ushort)(IY.Word + offset96);
|
|
|
|
// // 2. Read the value from memory
|
|
// byte subVal = ReadMemory(address96);
|
|
|
|
// // 3. Perform the subtraction from the Accumulator
|
|
// int aVal = AF.High;
|
|
// result = aVal - subVal;
|
|
|
|
// // --- 8-Bit Subtraction Flag Calculation ---
|
|
// newFlags = 0;
|
|
|
|
// // S Flag (Bit 7): Set if the result is negative
|
|
// if ((result & 0x80) != 0) newFlags |= 0x80;
|
|
|
|
// // Z Flag (Bit 6): Set if the result is exactly zero
|
|
// if ((result & 0xFF) == 0) newFlags |= 0x40;
|
|
|
|
// // H Flag (Bit 4): Set if there was a borrow from Bit 3
|
|
// if (((aVal & 0x0F) - (subVal & 0x0F)) < 0) newFlags |= 0x10;
|
|
|
|
// // P/V Flag (Bit 2): Set on Overflow
|
|
// // (Happens if subtracting a negative from a positive gives a negative, or vice versa)
|
|
// if ((((aVal ^ subVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04;
|
|
|
|
// // N Flag (Bit 1): Always set to 1 for a subtraction
|
|
// newFlags |= 0x02;
|
|
|
|
// // C Flag (Bit 0): Set if a borrow was needed (Accumulator was smaller than memory value)
|
|
// if (aVal < subVal) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
// 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;
|
|
// case 0xBE: // CP (IY+d)
|
|
// // 1. Fetch the displacement byte and calculate the address using IY
|
|
// sbyte offsetBE = (sbyte)FetchByte();
|
|
// ushort addressBE = (ushort)(IY.Word + offsetBE);
|
|
|
|
// // 2. Read the value from memory
|
|
// byte cpVal = ReadMemory(addressBE);
|
|
|
|
// // 3. Perform the phantom subtraction
|
|
// aVal = AF.High;
|
|
// result = aVal - cpVal;
|
|
|
|
// // --- 8-Bit Compare Flag Calculation (Identical to SUB) ---
|
|
// newFlags = 0;
|
|
|
|
// // S Flag (Bit 7): Set if the phantom result is negative
|
|
// if ((result & 0x80) != 0) newFlags |= 0x80;
|
|
|
|
// // Z Flag (Bit 6): Set if A perfectly matches the memory value
|
|
// if ((result & 0xFF) == 0) newFlags |= 0x40;
|
|
|
|
// // H Flag (Bit 4): Set if there was a borrow from Bit 3
|
|
// if (((aVal & 0x0F) - (cpVal & 0x0F)) < 0) newFlags |= 0x10;
|
|
|
|
// // P/V Flag (Bit 2): Set on Overflow
|
|
// if ((((aVal ^ cpVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04;
|
|
|
|
// // N Flag (Bit 1): Always set to 1 for Subtractions/Compares
|
|
// newFlags |= 0x02;
|
|
|
|
// // C Flag (Bit 0): Set if A was smaller than the memory value
|
|
// if (aVal < cpVal) newFlags |= 0x01;
|
|
|
|
// AF.Low = newFlags;
|
|
|
|
// return 19;
|
|
// case 0xCB: // The FD CB nested prefix
|
|
// {
|
|
// sbyte displacement = (sbyte)FetchByte();
|
|
// byte cbOpcode = FetchByte();
|
|
// targetAddress = (ushort)(IY.Word + displacement);
|
|
// memVal = ReadMemory(targetAddress);
|
|
|
|
// // 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 1: // ALL BIT Instructions
|
|
// AF.Low &= 0x01; // Preserve ONLY Carry
|
|
// AF.Low |= 0x10; // Set Half-Carry
|
|
|
|
// if ((memVal & bitMask) == 0)
|
|
// {
|
|
// 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)
|
|
// }
|
|
// return 20;
|
|
|
|
// case 2: // ALL RES Instructions
|
|
// memVal &= (byte)(~bitMask); // Invert mask and AND it to clear the bit
|
|
// WriteMemory(targetAddress, memVal);
|
|
// return 23;
|
|
|
|
// case 3: // ALL SET Instructions
|
|
// memVal |= bitMask; // OR the mask to force the bit to 1
|
|
// WriteMemory(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 Exception("Invalid bitwise operation.");
|
|
// }
|
|
// }
|
|
// case 0xE1: // POP IY
|
|
// // 1. Read the Low byte from the current Stack Pointer, then increment SP
|
|
// IY.Low = ReadMemory(SP);
|
|
// SP++;
|
|
|
|
// // 2. Read the High byte from the new Stack Pointer, then increment SP
|
|
// IY.High = ReadMemory(SP);
|
|
// SP++;
|
|
|
|
// return 14; // 14 T-States
|
|
// case 0xE5: // PUSH IY
|
|
// // 1. Decrement SP and write the High byte
|
|
// SP--;
|
|
// WriteMemory(SP, IY.High);
|
|
|
|
// // 2. Decrement SP again and write the Low byte
|
|
// SP--;
|
|
// WriteMemory(SP, IY.Low);
|
|
|
|
// return 15; // 15 T-States
|
|
// default:
|
|
// throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!");
|
|
// }
|
|
// }
|
|
// }
|
|
//} |