Interrupts added at 50fps. Dummy keyboard. Ready for graphics!

This commit is contained in:
2026-04-15 15:44:24 +01:00
parent d9966099f8
commit 960f2b85cc
2 changed files with 205 additions and 23 deletions

View File

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