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 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 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 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 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 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 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 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 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."); } } } }