Files
ZXSpectrum48K/Core/Cpu/Z80.cs

633 lines
22 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;
// 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 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 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 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 int ExecuteOpcode(byte opcode)
{
sbyte offset = 0;
switch (opcode)
{
case 0x00: // NOP
return 4;
case 0x01: // LD BC, nn
BC.Word = FetchWord();
return 10; // Takes 10 T-States
case 0x04: // INC B
BC.High = Inc8(BC.High);
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 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 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 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 0x35: // DEC (HL)
// Read the current byte from memory
byte memValue = _memory.Read(HL.Word);
// Decrement it and update flags
byte decremented = Dec8(memValue);
// Write the new value back to memory
_memory.Write(HL.Word, decremented);
return 11; // Takes 11 T-States
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 0x77: // LD (HL), A
_memory.Write(HL.Word, AF.High);
return 7;
case 0xA7: // AND A
And(AF.High);
return 4;
case 0xAF: // XOR A
AF.High = 0;
AF.Low = 0x44;
return 4;
case 0xBC: // CP H
Cp(HL.High);
return 4;
case 0xC3:
PC = FetchWord();
return 10;
case 0xCD: // CALL nn
ushort callAddress = FetchWord();
Push(PC);
PC = callAddress;
return 17;
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 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 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 0xF3: // DI (Disable Interrupts)
IFF1 = false;
IFF2 = false;
return 4;
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();
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 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 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 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 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 0xCB: // The FD CB nested prefix
{
sbyte offsetCB = (sbyte)FetchByte(); // This is the '01'
byte bitOpcode = FetchByte(); // This is the 'CE'
targetAddress = (ushort)(IY.Word + offsetCB);
switch (bitOpcode)
{
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; // Takes 23 T-States
default:
throw new NotImplementedException($"FD CB opcode {bitOpcode:X2} not implemented!");
}
}
default:
throw new NotImplementedException($"FD prefix opcode {opcode:X2} not implemented!");
}
}
}
}