1595 lines
62 KiB
C#
1595 lines
62 KiB
C#
using System;
|
|
using Core.Interfaces;
|
|
|
|
namespace Core.Cpu
|
|
{
|
|
public partial class Z80
|
|
{
|
|
//T-State counter
|
|
public long TotalTStates { get; set; }
|
|
|
|
public int InterruptMode { get; private set; } = 0; // Defaults to 0 on power-up
|
|
|
|
// Interrupt Flip-Flops
|
|
public bool IFF1 { get; private set; } = false;
|
|
public bool IFF2 { 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 IIoBus _ioBus;
|
|
|
|
public Z80(IMemory memory, IIoBus ioBus)
|
|
{
|
|
_memory = memory;
|
|
_ioBus = ioBus;
|
|
Reset();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
PC = 0x0000;
|
|
SP = 0xFFFF; // The Z80 initializes SP to 0xFFFF on boot
|
|
|
|
// 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; // Reset the system clock!
|
|
}
|
|
|
|
public int Step()
|
|
{
|
|
// Fetch the next opcode and increment the Program Counter
|
|
byte opcode = _memory.Read(PC++);
|
|
int tStates = ExecuteOpcode(opcode);
|
|
TotalTStates += tStates;
|
|
|
|
// Decode and execute
|
|
return tStates;
|
|
}
|
|
|
|
// Reads a 16-bit word from the current PC (Little-Endian) and advances PC by 2
|
|
private ushort FetchWord()
|
|
{
|
|
byte low = _memory.Read(PC++);
|
|
byte high = _memory.Read(PC++);
|
|
return (ushort)((high << 8) | low);
|
|
}
|
|
|
|
private byte FetchByte()
|
|
{
|
|
return _memory.Read(PC++);
|
|
}
|
|
|
|
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;
|
|
int 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
|
|
int 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
|
|
int 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 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;
|
|
int 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;
|
|
int 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 Add(byte value)
|
|
{
|
|
byte a = AF.High;
|
|
int 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 AddA(byte operand)
|
|
{
|
|
byte a = AF.High;
|
|
int result = a + operand;
|
|
|
|
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;
|
|
}
|
|
|
|
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--;
|
|
_memory.Write(SP, (byte)(value >> 8));
|
|
|
|
// Low byte goes second
|
|
SP--;
|
|
_memory.Write(SP, (byte)(value & 0xFF));
|
|
}
|
|
|
|
private ushort Pop()
|
|
{
|
|
// The Z80 is Little-Endian. Low byte comes off the stack first.
|
|
byte low = _memory.Read(SP++);
|
|
|
|
// High byte comes off second.
|
|
byte high = _memory.Read(SP++);
|
|
|
|
return (ushort)((high << 8) | low);
|
|
}
|
|
|
|
private int ExecuteOpcode(byte opcode)
|
|
{
|
|
sbyte offset = 0;
|
|
switch (opcode)
|
|
{
|
|
case 0x00: // NOP
|
|
return 4;
|
|
case 0x01: // LD BC, nn
|
|
BC.Word = FetchWord();
|
|
return 10;
|
|
case 0x02: // LD (BC), A
|
|
_memory.Write(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 0x08: // EX AF, AF'
|
|
ushort tempAF = AF.Word;
|
|
AF.Word = AF_Prime.Word;
|
|
AF_Prime.Word = tempAF;
|
|
return 4;
|
|
case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C
|
|
case 0x12: // LD (DE), A
|
|
_memory.Write(DE.Word, AF.High);
|
|
return 7;
|
|
case 0x14: DE.High = Inc8(DE.High); return 4; // INC D
|
|
case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E
|
|
case 0x1E: DE.Low = FetchByte(); // LD E, n
|
|
return 7;
|
|
case 0x24: HL.High = Inc8(HL.High); return 4; // INC H
|
|
case 0x2C: HL.Low = Inc8(HL.Low); return 4; // INC L
|
|
case 0x34:
|
|
_memory.Write(HL.Word, Inc8(_memory.Read(HL.Word)));
|
|
return 11; // INC (HL) takes 11 T-States
|
|
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 0x35:
|
|
_memory.Write(HL.Word, Dec8(_memory.Read(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;
|
|
// --- ADD HL, rr (16-bit Addition) ---
|
|
case 0x09:
|
|
Add16(BC.Word);
|
|
return 11;
|
|
case 0x19:
|
|
Add16(DE.Word);
|
|
return 11;
|
|
case 0x29:
|
|
Add16(HL.Word); // This perfectly multiplies HL by 2!
|
|
return 11;
|
|
case 0x39:
|
|
Add16(SP);
|
|
return 11;
|
|
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 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 = _memory.Read(DE.Word);
|
|
return 7;
|
|
case 0x1B: // DEC DE
|
|
DE.Word--;
|
|
return 6;
|
|
case 0x1F: // RRA
|
|
{
|
|
// 1. Grab the current Carry Flag (0 or 1)
|
|
byte 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();
|
|
_memory.Write(dest22, HL.Low);
|
|
_memory.Write((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 0x28: // JR Z, e
|
|
offset = (sbyte)FetchByte();
|
|
|
|
// Check if the Zero Flag (Bit 6) IS set
|
|
if ((AF.Low & 0x40) != 0)
|
|
{
|
|
PC = (ushort)(PC + offset);
|
|
return 12; // Jump taken
|
|
}
|
|
return 7; // Jump not taken
|
|
case 0x2A: // LD HL, (nn)
|
|
{
|
|
ushort srcAddress = FetchWord();
|
|
HL.Low = _memory.Read(srcAddress);
|
|
HL.High = _memory.Read((ushort)(srcAddress + 1));
|
|
return 16; // Takes 16 T-States
|
|
}
|
|
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; // Jump not taken
|
|
case 0x32: // LD (nn), A
|
|
{
|
|
ushort destAddress = FetchWord();
|
|
_memory.Write(destAddress, AF.High);
|
|
return 13;
|
|
}
|
|
case 0x33: // INC SP
|
|
SP++;
|
|
return 6;
|
|
case 0x36: // LD (HL), n
|
|
byte nValue = FetchByte();
|
|
_memory.Write(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 = _memory.Read(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 = _memory.Read(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 = _memory.Read(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 = _memory.Read(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 = _memory.Read(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 = _memory.Read(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 = _memory.Read(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: _memory.Write(HL.Word, BC.High); return 7;
|
|
case 0x71: _memory.Write(HL.Word, BC.Low); return 7;
|
|
case 0x72: _memory.Write(HL.Word, DE.High); return 7;
|
|
case 0x73: _memory.Write(HL.Word, DE.Low); return 7;
|
|
case 0x74: _memory.Write(HL.Word, HL.High); return 7;
|
|
case 0x75: _memory.Write(HL.Word, HL.Low); return 7;
|
|
case 0x77: _memory.Write(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 = _memory.Read(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(_memory.Read(HL.Word)); return 7; // ADD A, (HL)
|
|
case 0x87: Add(AF.High); return 4; // ADD A, A
|
|
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(_memory.Read(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(_memory.Read(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(_memory.Read(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(_memory.Read(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(_memory.Read(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(_memory.Read(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 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);
|
|
|
|
_ioBus.Write(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; // Takes 4 T-States
|
|
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 = _memory.Read(SP);
|
|
byte spHigh = _memory.Read((ushort)(SP + 1));
|
|
|
|
// 2. Write the current HL registers onto the stack in its place
|
|
_memory.Write(SP, HL.Low);
|
|
_memory.Write((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 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()
|
|
{
|
|
// Fetch the actual extended instruction
|
|
byte extendedOpcode = _memory.Read(PC++);
|
|
byte val = 0;
|
|
|
|
switch (extendedOpcode)
|
|
{
|
|
case 0x43: // LD (nn), BC
|
|
ushort dest43 = FetchWord();
|
|
_memory.Write(dest43, BC.Low);
|
|
_memory.Write((ushort)(dest43 + 1), BC.High);
|
|
return 20;
|
|
case 0x47: // LD I, A
|
|
I = AF.High;
|
|
return 9;
|
|
case 0x4B: // LD BC, (nn)
|
|
ushort src4B = FetchWord();
|
|
BC.Low = _memory.Read(src4B);
|
|
BC.High = _memory.Read((ushort)(src4B + 1));
|
|
return 20; // Takes 20 T-States
|
|
case 0x52: // SBC HL, DE
|
|
Sbc16(DE.Word);
|
|
return 15;
|
|
case 0x53: // LD (nn), DE
|
|
ushort dest53 = FetchWord();
|
|
_memory.Write(dest53, DE.Low);
|
|
_memory.Write((ushort)(dest53 + 1), DE.High);
|
|
return 20;
|
|
case 0x56: // IM 1
|
|
InterruptMode = 1;
|
|
return 8;
|
|
case 0x5B: // LD DE, (nn)
|
|
ushort src5B = FetchWord();
|
|
DE.Low = _memory.Read(src5B);
|
|
DE.High = _memory.Read((ushort)(src5B + 1));
|
|
return 20;
|
|
case 0x73: // LD (nn), SP
|
|
ushort dest73 = FetchWord();
|
|
_memory.Write(dest73, (byte)SP);
|
|
_memory.Write((ushort)(dest73 + 1), (byte)(SP >> 8));
|
|
return 20;
|
|
case 0xB0: // LDIR
|
|
// 1. Read byte from (HL)
|
|
val = _memory.Read(HL.Word);
|
|
|
|
// 2. Write byte to (DE)
|
|
_memory.Write(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 0xB8: // LDDR
|
|
// 1. Read byte from (HL)
|
|
val = _memory.Read(HL.Word);
|
|
|
|
// 2. Write byte to (DE)
|
|
_memory.Write(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();
|
|
|
|
// 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 = _memory.Read(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
|
|
throw new NotImplementedException($"CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
|
|
|
|
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: _memory.Write(HL.Word, val); break;
|
|
case 7: AF.High = val; break;
|
|
}
|
|
|
|
// RES/SET (HL) takes 15 T-States. Standard register RES/SET takes 8.
|
|
return (regIndex == 6) ? 15 : 8;
|
|
}
|
|
|
|
private int ExecuteFDPrefix()
|
|
{
|
|
byte opcode = FetchByte();
|
|
ushort targetAddress = 0;
|
|
byte memVal = 0;
|
|
|
|
switch (opcode)
|
|
{
|
|
case 0x21: // LD IY, nn
|
|
IY.Word = FetchWord();
|
|
return 14;
|
|
case 0x35: // DEC (IY+d)
|
|
sbyte offset = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset);
|
|
|
|
// Read, decrement using your existing helper, and write back
|
|
memVal = _memory.Read(targetAddress);
|
|
byte decVal = Dec8(memVal);
|
|
_memory.Write(targetAddress, decVal);
|
|
return 23;
|
|
case 0x36: // LD (IY+d), n
|
|
{
|
|
sbyte offset36 = (sbyte)FetchByte();
|
|
byte nValue = FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset36);
|
|
|
|
_memory.Write(targetAddress, nValue);
|
|
return 19; // Takes 19 T-States
|
|
}
|
|
case 0x46: // LD B, (IY+d)
|
|
{
|
|
sbyte displacement = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + displacement);
|
|
|
|
BC.High = _memory.Read(targetAddress);
|
|
return 19; // Takes 19 T-States
|
|
}
|
|
case 0x6E: // LD L, (IY+d)
|
|
sbyte displacementVal = (sbyte)FetchByte();
|
|
ushort targetAddr = (ushort)(IY.Word + displacementVal);
|
|
|
|
HL.Low = _memory.Read(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
|
|
_memory.Write(targetAddress, BC.Low);
|
|
return 19; // Takes 19 T-States
|
|
}
|
|
case 0x75: // LD (IY+d), L
|
|
sbyte offset75 = (sbyte)FetchByte();
|
|
targetAddress = (ushort)(IY.Word + offset75);
|
|
// Write the low byte of HL to memory
|
|
_memory.Write(targetAddress, HL.Low);
|
|
return 19;
|
|
case 0x86: // ADD A, (IY+d)
|
|
{
|
|
sbyte displacementAdd = (sbyte)FetchByte();
|
|
ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd);
|
|
|
|
byte valueToAdd = _memory.Read(targetAddressAdd);
|
|
AddA(valueToAdd);
|
|
|
|
return 19;
|
|
}
|
|
case 0xCB: // The FD CB nested prefix
|
|
{
|
|
sbyte displacement = (sbyte)FetchByte();
|
|
byte cbOpcode = FetchByte();
|
|
targetAddress = (ushort)(IY.Word + displacement);
|
|
memVal = _memory.Read(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
|
|
_memory.Write(targetAddress, memVal);
|
|
return 23;
|
|
|
|
case 3: // ALL SET Instructions
|
|
memVal |= bitMask; // OR the mask to force the bit to 1
|
|
_memory.Write(targetAddress, memVal);
|
|
return 23;
|
|
|
|
case 0:
|
|
// Shift/Rotate instructions will go here later
|
|
throw new NotImplementedException($"FD CB Shift/Rotate opcode {cbOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
|
|
|
|
default:
|
|
throw new Exception("Invalid bitwise operation.");
|
|
}
|
|
}
|
|
default:
|
|
throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//sbyte offsetCB = (sbyte)FetchByte(); // This is the '01'
|
|
//byte bitOpcode = FetchByte(); // This is the 'CE'
|
|
//targetAddress = (ushort)(IY.Word + offsetCB);
|
|
|
|
//switch (bitOpcode)
|
|
//{
|
|
// case 0x46: // BIT 0, (IY+d)
|
|
// byte memValBit0 = _memory.Read(targetAddress);
|
|
|
|
// // Preserve the existing Carry Flag (Bit 0)
|
|
// byte newFlags = (byte)(AF.Low & 0x01);
|
|
|
|
// newFlags |= 0x10; // Force Half-Carry (Bit 4) to 1
|
|
|
|
// // Test Bit 0. If it is 0, turn ON the Zero Flag (Bit 6)
|
|
// if ((memValBit0 & 0x01) == 0)
|
|
// {
|
|
// newFlags |= 0x40;
|
|
// }
|
|
|
|
// AF.Low = newFlags;
|
|
// return 20;
|
|
// case 0x4E: // BIT 1, (IY+d)
|
|
// {
|
|
// byte memValBit = _memory.Read(targetAddress);
|
|
|
|
// // Check if bit 1 is 0
|
|
// bool bitIsZero = (memValBit & 0x02) == 0;
|
|
|
|
// // Preserve the Carry flag (Bit 0), clear everything else
|
|
// AF.Low &= 0x01;
|
|
|
|
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT
|
|
// AF.Low |= 0x10;
|
|
|
|
// if (bitIsZero)
|
|
// {
|
|
// AF.Low |= 0x40; // Set Zero Flag (Bit 6)
|
|
// AF.Low |= 0x04; // Set P/V Flag (Bit 2)
|
|
// }
|
|
|
|
// return 20;
|
|
// }
|
|
// case 0x66: // BIT 4, (IY+d)
|
|
// byte memValBit4 = _memory.Read(targetAddress);
|
|
|
|
// // Preserve ONLY the Carry flag (Bit 0). This clears N and everything else.
|
|
// AF.Low &= 0x01;
|
|
|
|
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT instructions
|
|
// AF.Low |= 0x10;
|
|
|
|
// // Test Bit 4 (0x10 is Binary 0001 0000)
|
|
// if ((memValBit4 & 0x10) == 0)
|
|
// {
|
|
// // If the bit is 0, turn ON the Zero Flag (Bit 6)
|
|
// AF.Low |= 0x40;
|
|
|
|
// // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
|
|
// AF.Low |= 0x04;
|
|
// }
|
|
// // (Note: The Sign Flag remains 0 because we are not testing Bit 7)
|
|
|
|
// return 20;
|
|
// case 0x6E: // BIT 5, (IY+d)
|
|
// byte memValBit5 = _memory.Read(targetAddress);
|
|
|
|
// // Preserve ONLY the Carry flag (Bit 0). This clears N and everything else.
|
|
// AF.Low &= 0x01;
|
|
|
|
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT instructions
|
|
// AF.Low |= 0x10;
|
|
|
|
// // Test Bit 5 (0x20 is Binary 0010 0000)
|
|
// if ((memValBit5 & 0x20) == 0)
|
|
// {
|
|
// // If the bit is 0, turn ON the Zero Flag (Bit 6)
|
|
// AF.Low |= 0x40;
|
|
|
|
// // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
|
|
// AF.Low |= 0x04;
|
|
// }
|
|
// // (Note: The Sign Flag remains 0 because we are not testing Bit 7)
|
|
|
|
// return 20;
|
|
// case 0x76: // BIT 6, (IY+d)
|
|
// byte memValBit6 = _memory.Read(targetAddress);
|
|
|
|
// // Preserve ONLY the Carry flag (Bit 0). This clears N and everything else.
|
|
// AF.Low &= 0x01;
|
|
|
|
// // Set Half-Carry (Bit 4) - Standard Z80 behavior for BIT
|
|
// AF.Low |= 0x10;
|
|
|
|
// // Test Bit 6 (0x40 is Binary 0100 0000)
|
|
// if ((memValBit6 & 0x40) == 0)
|
|
// {
|
|
// // If the bit is 0, turn ON the Zero Flag (Bit 6)
|
|
// AF.Low |= 0x40;
|
|
|
|
// // Parity/Overflow (Bit 2) is historically set along with the Zero flag on BIT
|
|
// AF.Low |= 0x04;
|
|
// }
|
|
// // (Note: The Sign Flag remains 0 because we are not testing Bit 7)
|
|
|
|
// return 20;
|
|
// case 0x86: // RES 0, (IY+d)
|
|
// byte memValRes0 = _memory.Read(targetAddress);
|
|
|
|
// // 0xFE is Binary 1111 1110.
|
|
// // ANDing preserves all bits except Bit 0, which becomes 0.
|
|
// memValRes0 &= 0xFE;
|
|
|
|
// _memory.Write(targetAddress, memValRes0);
|
|
// return 23; // Takes 23 T-States
|
|
// case 0x8E: // RES 1, (IY+d)
|
|
// byte memValRes = _memory.Read(targetAddress);
|
|
|
|
// // 0xFD is Binary 1111 1101.
|
|
// // ANDing with this preserves all bits except Bit 1, which becomes 0.
|
|
// memValRes &= 0xFD;
|
|
// _memory.Write(targetAddress, memValRes);
|
|
// return 23;
|
|
// case 0xA6: // RES 4, (IY+d)
|
|
// byte memValRes4 = _memory.Read(targetAddress);
|
|
|
|
// // 0xEF is Binary 1110 1111
|
|
// // ANDing preserves all bits except Bit 4, which becomes 0.
|
|
// memValRes4 &= 0xEF;
|
|
|
|
// _memory.Write(targetAddress, memValRes4);
|
|
// return 23;
|
|
// case 0xAE: // RES 5, (IY+d)
|
|
// byte memValRes5 = _memory.Read(targetAddress);
|
|
|
|
// // 0xDF is Binary 1101 1111
|
|
// // ANDing perfectly preserves all other bits while forcing Bit 5 to 0
|
|
// memValRes5 &= 0xDF;
|
|
|
|
// _memory.Write(targetAddress, memValRes5);
|
|
// return 23;
|
|
// case 0xC6: // SET 0, (IY+d)
|
|
// byte memValSet0 = _memory.Read(targetAddress);
|
|
|
|
// // 0x01 is Binary 0000 0001
|
|
// // ORing forces Bit 0 to 1 and perfectly preserves all other bits
|
|
// memValSet0 |= 0x01;
|
|
|
|
// _memory.Write(targetAddress, memValSet0);
|
|
// return 23; // Takes 23 T-States
|
|
// case 0xCE: // SET 1, (IY+d)
|
|
// memVal = _memory.Read(targetAddress);
|
|
// memVal |= 0x02; // 0x02 is Binary 0000 0010 (Bit 1)
|
|
// _memory.Write(targetAddress, memVal);
|
|
// return 23;
|
|
// case 0xE6: // SET 4, (IY+d)
|
|
// byte memValSet4 = _memory.Read(targetAddress);
|
|
|
|
// // 0x10 is Binary 0001 0000
|
|
// // ORing perfectly preserves all other bits while forcing Bit 4 to 1
|
|
// memValSet4 |= 0x10;
|
|
|
|
// _memory.Write(targetAddress, memValSet4);
|
|
// return 23;
|
|
// case 0xEE: // SET 5, (IY+d)
|
|
// byte memValSet5 = _memory.Read(targetAddress);
|
|
|
|
// // 0x20 is Binary 0010 0000
|
|
// // ORing perfectly preserves all other bits while forcing Bit 5 to 1
|
|
// memValSet5 |= 0x20;
|
|
|
|
// _memory.Write(targetAddress, memValSet5);
|
|
// return 23;
|
|
|
|
// default:
|
|
// throw new NotImplementedException($"FD CB opcode {bitOpcode:X2} at PC 0x{(PC - 1):X4} not implemented!");
|
|
//} |