349 lines
11 KiB
C#
349 lines
11 KiB
C#
using System;
|
|
using Core.Interfaces;
|
|
|
|
namespace Core.Cpu
|
|
{
|
|
public partial class Z80
|
|
{
|
|
//T-State counter
|
|
public long TotalTStates { get; set; }
|
|
|
|
// Interrupt Flip-Flops
|
|
public bool IFF1;
|
|
public bool IFF2;
|
|
|
|
// 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;
|
|
// The Z80 initializes SP to 0xFFFF on boot
|
|
SP = 0xFFFF;
|
|
|
|
AF.Word = 0;
|
|
BC.Word = 0;
|
|
DE.Word = 0;
|
|
HL.Word = 0;
|
|
}
|
|
|
|
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 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 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 Add16(ushort value)
|
|
{
|
|
int hl = HL.Word;
|
|
int result = hl + value;
|
|
|
|
// Update the HL register
|
|
HL.Word = (ushort)result;
|
|
|
|
// --- Update Flags (F Register) ---
|
|
// 16-bit ADD preserves S, Z, P/V (and the undocumented X/Y flags).
|
|
// We clear H (Bit 4), N (Bit 1), and C (Bit 0) using a bitwise AND mask (0xEC = 1110 1100)
|
|
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 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 int ExecuteOpcode(byte opcode)
|
|
{
|
|
sbyte offset = 0;
|
|
switch (opcode)
|
|
{
|
|
case 0x00: // NOP
|
|
return 4;
|
|
case 0x11: //LD DE, nn
|
|
DE.Word = FetchWord();
|
|
return 10;
|
|
case 0x19: // ADD HL, DE
|
|
Add16(DE.Word);
|
|
return 11;
|
|
case 0x20: // JR NZ, e
|
|
offset = (sbyte)FetchByte();
|
|
if ((AF.Low & 0x40) == 0)
|
|
{
|
|
PC = (ushort)(PC + offset);
|
|
return 12;
|
|
}
|
|
return 7;
|
|
case 0x23: // INC HL
|
|
HL.Word++;
|
|
return 6;
|
|
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 0x36: // LD (HL), n
|
|
byte nValue = FetchByte();
|
|
_memory.Write(HL.Word, nValue);
|
|
return 10;
|
|
case 0x3E: //LD A, n
|
|
AF.High = FetchByte();
|
|
return 7;
|
|
case 0x47: // LD B, A
|
|
BC.High = AF.High;
|
|
return 4;
|
|
case 0x62: // LD H, D
|
|
HL.High = DE.High;
|
|
return 4;
|
|
case 0x6B: // LD L, E
|
|
HL.Low = DE.Low;
|
|
return 4;
|
|
case 0xA7: // AND A
|
|
And(AF.High);
|
|
return 4;
|
|
case 0xBC: // CP H
|
|
Cp(HL.High);
|
|
return 4;
|
|
case 0xC3:
|
|
PC = FetchWord();
|
|
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; // Takes 11 T-States
|
|
case 0xDE: // SBC A, n
|
|
Sbc(FetchByte());
|
|
return 7;
|
|
case 0xED:
|
|
return ExecuteExtendedPrefix();
|
|
case 0xF3: // DI (Disable Interrupts)
|
|
IFF1 = false;
|
|
IFF2 = false;
|
|
return 4;
|
|
case 0xAF: // XOR A
|
|
AF.High = 0;
|
|
AF.Low = 0x44;
|
|
return 4;
|
|
|
|
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++);
|
|
|
|
switch (extendedOpcode)
|
|
{
|
|
case 0x47: // LD I, A
|
|
I = AF.High;
|
|
return 9;
|
|
case 0x52: // SBC HL, DE
|
|
Sbc16(DE.Word);
|
|
return 15; // Takes 15 T-States
|
|
default:
|
|
throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
|
|
}
|
|
}
|
|
}
|
|
} |