Implemented a load of Z80 OpCodes. Added SimpleIOBus.

This commit is contained in:
2026-04-09 14:35:38 +01:00
parent f14d2c4ccc
commit 340583d663
8 changed files with 292 additions and 38 deletions

View File

@@ -8,6 +8,10 @@ namespace Core.Cpu
//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;
@@ -32,11 +36,13 @@ namespace Core.Cpu
// The Memory Bus
private readonly IMemory _memory;
private readonly IIoBus _ioBus;
public Z80(IMemory memory)
public Z80(IMemory memory, IIoBus ioBus)
{
_memory = memory;
Reset();
_ioBus = ioBus;
Reset();
}
public void Reset()
@@ -62,6 +68,19 @@ namespace Core.Cpu
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;
@@ -75,18 +94,110 @@ namespace Core.Cpu
$"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 int ExecuteOpcode(byte opcode)
{
switch (opcode)
{
case 0x00: // NOP (No Operation)
return 4; // Takes 4 T-states
case 0x00: // NOP
return 4;
case 0x11: //LD DE, nn
DE.Word = FetchWord();
return 10;
case 0x2B: // DEC HL
HL.Word--;
return 6;
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 0xC3:
PC = FetchWord();
return 10;
case 0xD3: // OUT (n), A
byte portOffset = FetchByte();
// We will expand this massive list soon!
// 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;
default:
throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Core.Interfaces
{
public interface IIoBus
{
byte Read(ushort port);
void Write(ushort port, byte value);
}
}

20
Core/Io/SimpleIoBus.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.Diagnostics;
using Core.Interfaces;
namespace Core.Io
{
public class SimpleIoBus : IIoBus
{
public byte Read(ushort port)
{
// If the CPU reads an unconnected port, the Z80 usually sees 0xFF
return 0xFF;
}
public void Write(ushort port, byte value)
{
// For now, let's just log it to the Visual Studio Output window
Debug.WriteLine($"Hardware I/O Write -> Port: 0x{port:X4}, Value: 0x{value:X2}");
}
}
}

View File

@@ -24,15 +24,14 @@ namespace Core.Memory
if (address < 0x4000)
{
// Attempted to write to the ROM area.
// We simply ignore the write command, just like real hardware.
// Cannot write to ROM - Do nothing - maybe throw an exception
return;
}
_memory[address] = value;
}
// Helper method to load the original Sinclair ROM file
// Load the ROM file
public void LoadRom(byte[] romData)
{
if (romData.Length > 0x4000)
@@ -40,7 +39,7 @@ namespace Core.Memory
throw new ArgumentException("ROM file exceeds the 16KB capacity of Bank 0.");
}
// Copy the ROM data into the very beginning of the memory array
// Copy the ROM
Array.Copy(romData, 0, _memory, 0, romData.Length);
}
}