Interrupts added at 50fps. Dummy keyboard. Ready for graphics!
This commit is contained in:
117
Core/Cpu/Z80.cs
117
Core/Cpu/Z80.cs
@@ -79,6 +79,60 @@ namespace Core.Cpu
|
||||
TotalTStates = 0; // Reset the system clock!
|
||||
}
|
||||
|
||||
public int RequestInterrupt()
|
||||
{
|
||||
// 1. If the ROM has disabled interrupts (DI), ignore the request
|
||||
if (!IFF1) return 0;
|
||||
|
||||
// 2. Acknowledge the interrupt by immediately disabling further interrupts
|
||||
// This prevents an endless loop of interrupts triggering each other!
|
||||
IFF1 = false;
|
||||
IFF2 = false;
|
||||
|
||||
// 3. Push the current Program Counter to the stack so we can return
|
||||
Push(PC);
|
||||
|
||||
// 4. Jump to the Interrupt Service Routine
|
||||
// The ZX Spectrum standard is Mode 1, which maps directly to 0x0038
|
||||
if (InterruptMode == 1)
|
||||
{
|
||||
PC = 0x0038;
|
||||
}
|
||||
else
|
||||
{
|
||||
// (Games will use Mode 2 later, but the ROM uses Mode 1)
|
||||
throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!");
|
||||
}
|
||||
|
||||
// 5. The CPU takes exactly 13 T-States to process this hardware CALL
|
||||
return 13;
|
||||
}
|
||||
|
||||
// Helper method to calculate if a byte has an Even Parity of 1s
|
||||
private bool CalculateParity(byte b)
|
||||
{
|
||||
int bits = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if ((b & (1 << i)) != 0) bits++;
|
||||
}
|
||||
return (bits % 2) == 0;
|
||||
}
|
||||
|
||||
// Placeholder for your hardware I/O
|
||||
private byte ReadPort(ushort portAddress)
|
||||
{
|
||||
// If the port is 0xFE, the ROM is asking for keyboard/tape/ULA data!
|
||||
// For now, we will return 0xFF (meaning "No keys are currently pressed")
|
||||
if ((portAddress & 0xFF) == 0xFE)
|
||||
{
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// Default floating bus return
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
public int Step()
|
||||
{
|
||||
// Fetch the next opcode and increment the Program Counter
|
||||
@@ -494,6 +548,9 @@ namespace Core.Cpu
|
||||
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 0x2E: // LD L, n
|
||||
HL.Low = FetchByte();
|
||||
return 7;
|
||||
case 0x34:
|
||||
_memory.Write(HL.Word, Inc8(_memory.Read(HL.Word)));
|
||||
return 11; // INC (HL) takes 11 T-States
|
||||
@@ -506,6 +563,15 @@ namespace Core.Cpu
|
||||
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 0x2F: // CPL
|
||||
// Flip all bits in the Accumulator
|
||||
AF.High = (byte)(~AF.High);
|
||||
|
||||
// Set Half-Carry (Bit 4) and Subtract (Bit 1).
|
||||
// Bitwise OR forces them to 1 while perfectly preserving S, Z, P/V, and C.
|
||||
AF.Low |= 0x12;
|
||||
|
||||
return 4;
|
||||
case 0x35:
|
||||
_memory.Write(HL.Word, Dec8(_memory.Read(HL.Word)));
|
||||
return 11; // DEC (HL) takes 11 T-States
|
||||
@@ -1158,6 +1224,25 @@ namespace Core.Cpu
|
||||
_memory.Write(dest73, (byte)SP);
|
||||
_memory.Write((ushort)(dest73 + 1), (byte)(SP >> 8));
|
||||
return 20;
|
||||
case 0x78: // IN A, (C)
|
||||
// Read from the hardware port using the full BC register as the address
|
||||
byte portVal78 = ReadPort(BC.Word);
|
||||
AF.High = portVal78;
|
||||
|
||||
// --- Update Flags ---
|
||||
// S (Bit 7), Z (Bit 6), P/V (Bit 2) are set based on the input.
|
||||
// H (Bit 4) and N (Bit 1) are RESET.
|
||||
// C (Bit 0) is PRESERVED.
|
||||
|
||||
byte newFlags = (byte)(AF.Low & 0x01); // Preserve Carry
|
||||
|
||||
if ((portVal78 & 0x80) != 0) newFlags |= 0x80; // Sign Flag
|
||||
if (portVal78 == 0) newFlags |= 0x40; // Zero Flag
|
||||
if (CalculateParity(portVal78)) newFlags |= 0x04; // Parity/Overflow Flag
|
||||
|
||||
AF.Low = newFlags;
|
||||
|
||||
return 12; // Takes 12 T-States
|
||||
case 0xB0: // LDIR
|
||||
// 1. Read byte from (HL)
|
||||
val = _memory.Read(HL.Word);
|
||||
@@ -1271,7 +1356,37 @@ namespace Core.Cpu
|
||||
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!");
|
||||
// The specific shift type is in the same bits we previously used for 'bitIndex'
|
||||
int shiftType = (cbOpcode >> 3) & 0x07;
|
||||
bool carryOut = false;
|
||||
|
||||
switch (shiftType)
|
||||
{
|
||||
case 0: // RLC
|
||||
// Grab Bit 7 to see if it's going to fall off
|
||||
carryOut = (val & 0x80) != 0;
|
||||
// Shift left, and loop the falling bit back into Bit 0
|
||||
val = (byte)((val << 1) | (carryOut ? 1 : 0));
|
||||
break;
|
||||
|
||||
// (We will add RRC, RL, RR, SLA, SRA, SRL here as the ROM asks for them!)
|
||||
default:
|
||||
throw new NotImplementedException($"CB Shift instruction type {shiftType} not implemented!");
|
||||
}
|
||||
|
||||
// --- Update Flags ---
|
||||
// All CB Shift instructions calculate flags the exact same way!
|
||||
// They set S, Z, P/V, and C. They forcefully clear H and N.
|
||||
byte newFlags = 0;
|
||||
|
||||
if (carryOut) newFlags |= 0x01; // C Flag
|
||||
if ((val & 0x80) != 0) newFlags |= 0x80; // S Flag
|
||||
if (val == 0) newFlags |= 0x40; // Z Flag
|
||||
if (CalculateParity(val)) newFlags |= 0x04; // P/V Flag
|
||||
|
||||
AF.Low = newFlags; // Apply the new flags
|
||||
|
||||
break; // Proceed to the write-back phase
|
||||
|
||||
default:
|
||||
throw new Exception("Invalid CB operation.");
|
||||
|
||||
Reference in New Issue
Block a user