ULA Implemented. Scanline renderer so cycle accurate

This commit is contained in:
2026-04-21 15:34:10 +01:00
parent ad3a0b5040
commit dcbb505145
6 changed files with 424 additions and 310 deletions

View File

@@ -43,6 +43,9 @@ namespace Core.Cpu
private readonly IO_Bus _simpleIoBus; private readonly IO_Bus _simpleIoBus;
public TapManager _tapManager; public TapManager _tapManager;
//External Timing interface
public Func<ushort, long, int>? WaitStateCallback { get; set; }
//Misc Variables //Misc Variables
byte newFlags = 0; byte newFlags = 0;
int result = 0; int result = 0;
@@ -88,6 +91,15 @@ namespace Core.Cpu
//_memory.CleanRAMData(); //_memory.CleanRAMData();
} }
private void ApplyWaitStates(ushort address)
{
// If a system (like a ULA) is attached and listening, ask it for the delay
if (WaitStateCallback != null)
{
TotalTStates += WaitStateCallback(address, TotalTStates);
}
}
public int RequestInterrupt() public int RequestInterrupt()
{ {
InterruptRequested = true; InterruptRequested = true;
@@ -116,8 +128,8 @@ namespace Core.Cpu
ushort vectorAddress = (ushort)((I << 8) | 0xFF); ushort vectorAddress = (ushort)((I << 8) | 0xFF);
// B. Read the actual 16-bit ISR address from that location in memory (Little-Endian) // B. Read the actual 16-bit ISR address from that location in memory (Little-Endian)
byte pcLow = _memory.Read(vectorAddress); byte pcLow = ReadMemory(vectorAddress);
byte pcHigh = _memory.Read((ushort)(vectorAddress + 1)); byte pcHigh = ReadMemory((ushort)(vectorAddress + 1));
// C. Jump to the custom game routine! // C. Jump to the custom game routine!
PC = (ushort)((pcHigh << 8) | pcLow); PC = (ushort)((pcHigh << 8) | pcLow);
@@ -131,6 +143,38 @@ namespace Core.Cpu
} }
} }
// 1. For fetching opcodes and immediate values (Advances PC)
public byte FetchByte()
{
ApplyWaitStates(PC);
byte data = _memory.Read(PC);
PC++;
return data;
}
// 2. For fetching 16-bit immediate values
private ushort FetchWord()
{
// By using FetchByte twice, we perfectly apply wait states to BOTH memory reads!
byte low = FetchByte();
byte high = FetchByte();
return (ushort)((high << 8) | low);
}
// 3. For standard memory reads (e.g., LD A, (HL))
public byte ReadMemory(ushort address)
{
ApplyWaitStates(address);
return _memory.Read(address);
}
// 4. For standard memory writes (e.g., LD (HL), A)
public void WriteMemory(ushort address, byte data)
{
ApplyWaitStates(address);
_memory.Write(address, data);
}
// Helper method to calculate if a byte has an Even Parity of 1s // Helper method to calculate if a byte has an Even Parity of 1s
private bool CalculateParity(byte b) private bool CalculateParity(byte b)
{ {
@@ -157,7 +201,7 @@ namespace Core.Cpu
} }
// Fetch the next opcode and increment the Program Counter // Fetch the next opcode and increment the Program Counter
byte opcode = _memory.Read(PC++); byte opcode = ReadMemory(PC++);
int tStates = ExecuteOpcode(opcode); int tStates = ExecuteOpcode(opcode);
TotalTStates += tStates; TotalTStates += tStates;
@@ -194,7 +238,7 @@ namespace Core.Cpu
// The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF // The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF
for (int i = 0; i < 49152; i++) for (int i = 0; i < 49152; i++)
{ {
_memory.Write((ushort)(0x4000 + i), snaData[27 + i]); WriteMemory((ushort)(0x4000 + i), snaData[27 + i]);
} }
// --- 3. The Magic Bullet --- // --- 3. The Magic Bullet ---
@@ -226,7 +270,7 @@ namespace Core.Cpu
int bytesToCopy = DE.Word; int bytesToCopy = DE.Word;
for (int i = 0; i < bytesToCopy; i++) for (int i = 0; i < bytesToCopy; i++)
{ {
_memory.Write((ushort)(IX.Word + i), block[i + 1]); WriteMemory((ushort)(IX.Word + i), block[i + 1]);
} }
// 4. Update the registers exactly how the ROM would after a successful load // 4. Update the registers exactly how the ROM would after a successful load
@@ -246,25 +290,15 @@ namespace Core.Cpu
// A quick helper to simulate a RET instruction manually // A quick helper to simulate a RET instruction manually
private void ExecuteRet() private void ExecuteRet()
{ {
byte pcLow = _memory.Read(SP); byte pcLow = ReadMemory(SP);
SP++; SP++;
byte pcHigh = _memory.Read(SP); byte pcHigh = ReadMemory(SP);
SP++; SP++;
PC = (ushort)((pcHigh << 8) | pcLow); PC = (ushort)((pcHigh << 8) | pcLow);
} }
// Reads a 16-bit word from the current PC (Little-Endian) and advances PC by 2 // 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() public string GetFlagsString()
{ {
@@ -674,20 +708,20 @@ namespace Core.Cpu
{ {
// High byte goes first // High byte goes first
SP--; SP--;
_memory.Write(SP, (byte)(value >> 8)); WriteMemory(SP, (byte)(value >> 8));
// Low byte goes second // Low byte goes second
SP--; SP--;
_memory.Write(SP, (byte)(value & 0xFF)); WriteMemory(SP, (byte)(value & 0xFF));
} }
private ushort Pop() private ushort Pop()
{ {
// The Z80 is Little-Endian. Low byte comes off the stack first. // The Z80 is Little-Endian. Low byte comes off the stack first.
byte low = _memory.Read(SP++); byte low = ReadMemory(SP++);
// High byte comes off second. // High byte comes off second.
byte high = _memory.Read(SP++); byte high = ReadMemory(SP++);
return (ushort)((high << 8) | low); return (ushort)((high << 8) | low);
} }
@@ -704,7 +738,7 @@ namespace Core.Cpu
BC.Word = FetchWord(); BC.Word = FetchWord();
return 10; return 10;
case 0x02: // LD (BC), A case 0x02: // LD (BC), A
_memory.Write(BC.Word, AF.High); WriteMemory(BC.Word, AF.High);
return 7; return 7;
case 0x03: // INC BC case 0x03: // INC BC
BC.Word++; BC.Word++;
@@ -736,11 +770,11 @@ namespace Core.Cpu
AF_Prime.Word = tempAF; AF_Prime.Word = tempAF;
return 4; return 4;
case 0x0A: //LD A (BC) case 0x0A: //LD A (BC)
AF.High = _memory.Read(BC.Word); AF.High = ReadMemory(BC.Word);
return 7; return 7;
case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C
case 0x12: // LD (DE), A case 0x12: // LD (DE), A
_memory.Write(DE.Word, AF.High); WriteMemory(DE.Word, AF.High);
return 7; return 7;
case 0x14: DE.High = Inc8(DE.High); return 4; // INC D case 0x14: DE.High = Inc8(DE.High); return 4; // INC D
case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E case 0x1C: DE.Low = Inc8(DE.Low); return 4; // INC E
@@ -752,7 +786,7 @@ namespace Core.Cpu
HL.Low = FetchByte(); HL.Low = FetchByte();
return 7; return 7;
case 0x34: case 0x34:
_memory.Write(HL.Word, Inc8(_memory.Read(HL.Word))); WriteMemory(HL.Word, Inc8(ReadMemory(HL.Word)));
return 11; // INC (HL) takes 11 T-States return 11; // INC (HL) takes 11 T-States
case 0x3C: AF.High = Inc8(AF.High); return 4; // INC A case 0x3C: AF.High = Inc8(AF.High); return 4; // INC A
@@ -773,7 +807,7 @@ namespace Core.Cpu
return 4; return 4;
case 0x35: case 0x35:
_memory.Write(HL.Word, Dec8(_memory.Read(HL.Word))); WriteMemory(HL.Word, Dec8(ReadMemory(HL.Word)));
return 11; // DEC (HL) takes 11 T-States return 11; // DEC (HL) takes 11 T-States
case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A case 0x3D: AF.High = Dec8(AF.High); return 4; // DEC A
case 0x06: // LD B, n case 0x06: // LD B, n
@@ -864,7 +898,7 @@ namespace Core.Cpu
return 12; return 12;
case 0x1A: // LD A, (DE) case 0x1A: // LD A, (DE)
AF.High = _memory.Read(DE.Word); AF.High = ReadMemory(DE.Word);
return 7; return 7;
case 0x1B: // DEC DE case 0x1B: // DEC DE
DE.Word--; DE.Word--;
@@ -903,8 +937,8 @@ namespace Core.Cpu
return 10; return 10;
case 0x22: // LD (nn), HL case 0x22: // LD (nn), HL
ushort dest22 = FetchWord(); ushort dest22 = FetchWord();
_memory.Write(dest22, HL.Low); WriteMemory(dest22, HL.Low);
_memory.Write((ushort)(dest22 + 1), HL.High); WriteMemory((ushort)(dest22 + 1), HL.High);
return 16; return 16;
case 0x23: // INC HL case 0x23: // INC HL
HL.Word++; HL.Word++;
@@ -971,8 +1005,8 @@ namespace Core.Cpu
case 0x2A: // LD HL, (nn) case 0x2A: // LD HL, (nn)
{ {
ushort srcAddress = FetchWord(); ushort srcAddress = FetchWord();
HL.Low = _memory.Read(srcAddress); HL.Low = ReadMemory(srcAddress);
HL.High = _memory.Read((ushort)(srcAddress + 1)); HL.High = ReadMemory((ushort)(srcAddress + 1));
return 16; return 16;
} }
case 0x2B: // DEC HL case 0x2B: // DEC HL
@@ -991,7 +1025,7 @@ namespace Core.Cpu
case 0x32: // LD (nn), A case 0x32: // LD (nn), A
{ {
ushort destAddress = FetchWord(); ushort destAddress = FetchWord();
_memory.Write(destAddress, AF.High); WriteMemory(destAddress, AF.High);
return 13; return 13;
} }
case 0x33: // INC SP case 0x33: // INC SP
@@ -999,7 +1033,7 @@ namespace Core.Cpu
return 6; return 6;
case 0x36: // LD (HL), n case 0x36: // LD (HL), n
byte nValue = FetchByte(); byte nValue = FetchByte();
_memory.Write(HL.Word, nValue); WriteMemory(HL.Word, nValue);
return 10; return 10;
case 0x37: // SCF case 0x37: // SCF
AF.Low |= 0x01; // Force Carry Flag (Bit 0) to 1 AF.Low |= 0x01; // Force Carry Flag (Bit 0) to 1
@@ -1016,7 +1050,7 @@ namespace Core.Cpu
return 7; return 7;
case 0x3A: // LD A, (nn) case 0x3A: // LD A, (nn)
ushort address3A = FetchWord(); ushort address3A = FetchWord();
AF.High = _memory.Read(address3A); AF.High = ReadMemory(address3A);
return 13; return 13;
case 0x3B: // DEC SP case 0x3B: // DEC SP
SP--; SP--;
@@ -1041,7 +1075,7 @@ namespace Core.Cpu
case 0x43: BC.High = DE.Low; return 4; case 0x43: BC.High = DE.Low; return 4;
case 0x44: BC.High = HL.High; return 4; case 0x44: BC.High = HL.High; return 4;
case 0x45: BC.High = HL.Low; return 4; case 0x45: BC.High = HL.Low; return 4;
case 0x46: BC.High = _memory.Read(HL.Word); return 7; case 0x46: BC.High = ReadMemory(HL.Word); return 7;
case 0x47: BC.High = AF.High; return 4; case 0x47: BC.High = AF.High; return 4;
// --- LD C, r --- // --- LD C, r ---
@@ -1052,7 +1086,7 @@ namespace Core.Cpu
case 0x4B: BC.Low = DE.Low; return 4; case 0x4B: BC.Low = DE.Low; return 4;
case 0x4C: BC.Low = HL.High; return 4; case 0x4C: BC.Low = HL.High; return 4;
case 0x4D: BC.Low = HL.Low; return 4; case 0x4D: BC.Low = HL.Low; return 4;
case 0x4E: BC.Low = _memory.Read(HL.Word); return 7; case 0x4E: BC.Low = ReadMemory(HL.Word); return 7;
case 0x4F: BC.Low = AF.High; return 4; case 0x4F: BC.Low = AF.High; return 4;
// --- LD D, r --- // --- LD D, r ---
@@ -1063,7 +1097,7 @@ namespace Core.Cpu
case 0x53: DE.High = DE.Low; return 4; case 0x53: DE.High = DE.Low; return 4;
case 0x54: DE.High = HL.High; return 4; case 0x54: DE.High = HL.High; return 4;
case 0x55: DE.High = HL.Low; return 4; case 0x55: DE.High = HL.Low; return 4;
case 0x56: DE.High = _memory.Read(HL.Word); return 7; case 0x56: DE.High = ReadMemory(HL.Word); return 7;
case 0x57: DE.High = AF.High; return 4; case 0x57: DE.High = AF.High; return 4;
// --- LD E, r --- // --- LD E, r ---
@@ -1074,7 +1108,7 @@ namespace Core.Cpu
return 4; return 4;
case 0x5C: DE.Low = HL.High; return 4; case 0x5C: DE.Low = HL.High; return 4;
case 0x5D: DE.Low = HL.Low; return 4; case 0x5D: DE.Low = HL.Low; return 4;
case 0x5E: DE.Low = _memory.Read(HL.Word); return 7; case 0x5E: DE.Low = ReadMemory(HL.Word); return 7;
case 0x5F: DE.Low = AF.High; return 4; case 0x5F: DE.Low = AF.High; return 4;
// --- LD H, r --- // --- LD H, r ---
@@ -1085,7 +1119,7 @@ namespace Core.Cpu
case 0x64: //HL.High = HL.High; case 0x64: //HL.High = HL.High;
return 4; return 4;
case 0x65: HL.High = HL.Low; return 4; case 0x65: HL.High = HL.Low; return 4;
case 0x66: HL.High = _memory.Read(HL.Word); return 7; case 0x66: HL.High = ReadMemory(HL.Word); return 7;
case 0x67: HL.High = AF.High; return 4; case 0x67: HL.High = AF.High; return 4;
// --- LD L, r --- // --- LD L, r ---
@@ -1096,16 +1130,16 @@ namespace Core.Cpu
case 0x6C: HL.Low = HL.High; return 4; case 0x6C: HL.Low = HL.High; return 4;
case 0x6D: //HL.Low = HL.Low; case 0x6D: //HL.Low = HL.Low;
return 4; return 4;
case 0x6E: HL.Low = _memory.Read(HL.Word); return 7; case 0x6E: HL.Low = ReadMemory(HL.Word); return 7;
case 0x6F: HL.Low = AF.High; return 4; case 0x6F: HL.Low = AF.High; return 4;
// --- LD (HL), r --- (Note: 0x76 is HALT, so it is skipped here) // --- LD (HL), r --- (Note: 0x76 is HALT, so it is skipped here)
case 0x70: _memory.Write(HL.Word, BC.High); return 7; case 0x70: WriteMemory(HL.Word, BC.High); return 7;
case 0x71: _memory.Write(HL.Word, BC.Low); return 7; case 0x71: WriteMemory(HL.Word, BC.Low); return 7;
case 0x72: _memory.Write(HL.Word, DE.High); return 7; case 0x72: WriteMemory(HL.Word, DE.High); return 7;
case 0x73: _memory.Write(HL.Word, DE.Low); return 7; case 0x73: WriteMemory(HL.Word, DE.Low); return 7;
case 0x74: _memory.Write(HL.Word, HL.High); return 7; case 0x74: WriteMemory(HL.Word, HL.High); return 7;
case 0x75: _memory.Write(HL.Word, HL.Low); return 7; case 0x75: WriteMemory(HL.Word, HL.Low); return 7;
case 0x76: //HALT case 0x76: //HALT
if (!InterruptRequested) if (!InterruptRequested)
{ {
@@ -1117,7 +1151,7 @@ namespace Core.Cpu
InterruptRequested = false; InterruptRequested = false;
return 4; return 4;
} }
case 0x77: _memory.Write(HL.Word, AF.High); return 7; case 0x77: WriteMemory(HL.Word, AF.High); return 7;
// --- LD A, r --- // --- LD A, r ---
case 0x78: AF.High = BC.High; return 4; case 0x78: AF.High = BC.High; return 4;
@@ -1126,7 +1160,7 @@ namespace Core.Cpu
case 0x7B: AF.High = DE.Low; return 4; case 0x7B: AF.High = DE.Low; return 4;
case 0x7C: AF.High = HL.High; return 4; case 0x7C: AF.High = HL.High; return 4;
case 0x7D: AF.High = HL.Low; return 4; case 0x7D: AF.High = HL.Low; return 4;
case 0x7E: AF.High = _memory.Read(HL.Word); return 7; case 0x7E: AF.High = ReadMemory(HL.Word); return 7;
case 0x7F: //AF.High = AF.High; case 0x7F: //AF.High = AF.High;
return 4; return 4;
case 0x80: Add(BC.High); return 4; // ADD A, B case 0x80: Add(BC.High); return 4; // ADD A, B
@@ -1135,7 +1169,7 @@ namespace Core.Cpu
case 0x83: Add(DE.Low); return 4; // ADD A, E case 0x83: Add(DE.Low); return 4; // ADD A, E
case 0x84: Add(HL.High); return 4; // ADD A, H case 0x84: Add(HL.High); return 4; // ADD A, H
case 0x85: Add(HL.Low); return 4; // ADD A, L case 0x85: Add(HL.Low); return 4; // ADD A, L
case 0x86: Add(_memory.Read(HL.Word)); return 7; // ADD A, (HL) case 0x86: Add(ReadMemory(HL.Word)); return 7; // ADD A, (HL)
case 0x87: Add(AF.High); return 4; // ADD A, A case 0x87: Add(AF.High); return 4; // ADD A, A
// --- ADC A, Register Family --- // --- ADC A, Register Family ---
case 0x88: AdcA(BC.High); return 4; // ADC A, B case 0x88: AdcA(BC.High); return 4; // ADC A, B
@@ -1148,7 +1182,7 @@ namespace Core.Cpu
// --- ADC A, Memory --- // --- ADC A, Memory ---
case 0x8E: // ADC A, (HL) case 0x8E: // ADC A, (HL)
AdcA(_memory.Read(HL.Word)); AdcA(ReadMemory(HL.Word));
return 7; return 7;
// --- ADC A, Immediate --- // --- ADC A, Immediate ---
@@ -1161,7 +1195,7 @@ namespace Core.Cpu
case 0x93: Sub(DE.Low); return 4; // SUB E case 0x93: Sub(DE.Low); return 4; // SUB E
case 0x94: Sub(HL.High); return 4; // SUB H case 0x94: Sub(HL.High); return 4; // SUB H
case 0x95: Sub(HL.Low); return 4; // SUB L case 0x95: Sub(HL.Low); return 4; // SUB L
case 0x96: Sub(_memory.Read(HL.Word)); return 7; // SUB (HL) case 0x96: Sub(ReadMemory(HL.Word)); return 7; // SUB (HL)
case 0x97: Sub(AF.High); return 4; // SUB A case 0x97: Sub(AF.High); return 4; // SUB A
// --- SBC A, r --- // --- SBC A, r ---
case 0x98: Sbc(BC.High); return 4; // SBC A, B case 0x98: Sbc(BC.High); return 4; // SBC A, B
@@ -1170,7 +1204,7 @@ namespace Core.Cpu
case 0x9B: Sbc(DE.Low); return 4; // SBC A, E case 0x9B: Sbc(DE.Low); return 4; // SBC A, E
case 0x9C: Sbc(HL.High); return 4; // SBC A, H case 0x9C: Sbc(HL.High); return 4; // SBC A, H
case 0x9D: Sbc(HL.Low); return 4; // SBC A, L case 0x9D: Sbc(HL.Low); return 4; // SBC A, L
case 0x9E: Sbc(_memory.Read(HL.Word)); return 7; // SBC A, (HL) case 0x9E: Sbc(ReadMemory(HL.Word)); return 7; // SBC A, (HL)
case 0x9F: Sbc(AF.High); return 4; // SBC A, A case 0x9F: Sbc(AF.High); return 4; // SBC A, A
case 0xA0: And(BC.High); return 4; // AND B case 0xA0: And(BC.High); return 4; // AND B
case 0xA1: And(BC.Low); return 4; // AND C case 0xA1: And(BC.Low); return 4; // AND C
@@ -1178,7 +1212,7 @@ namespace Core.Cpu
case 0xA3: And(DE.Low); return 4; // AND E case 0xA3: And(DE.Low); return 4; // AND E
case 0xA4: And(HL.High); return 4; // AND H case 0xA4: And(HL.High); return 4; // AND H
case 0xA5: And(HL.Low); return 4; // AND L case 0xA5: And(HL.Low); return 4; // AND L
case 0xA6: And(_memory.Read(HL.Word)); return 7; // AND (HL) case 0xA6: And(ReadMemory(HL.Word)); return 7; // AND (HL)
case 0xA7: And(AF.High); return 4; // AND A case 0xA7: And(AF.High); return 4; // AND A
case 0xA8: Xor(BC.High); return 4; // XOR B case 0xA8: Xor(BC.High); return 4; // XOR B
case 0xA9: Xor(BC.Low); return 4; // XOR C case 0xA9: Xor(BC.Low); return 4; // XOR C
@@ -1186,7 +1220,7 @@ namespace Core.Cpu
case 0xAB: Xor(DE.Low); return 4; // XOR E case 0xAB: Xor(DE.Low); return 4; // XOR E
case 0xAC: Xor(HL.High); return 4; // XOR H case 0xAC: Xor(HL.High); return 4; // XOR H
case 0xAD: Xor(HL.Low); return 4; // XOR L case 0xAD: Xor(HL.Low); return 4; // XOR L
case 0xAE: Xor(_memory.Read(HL.Word)); return 7; // XOR (HL) case 0xAE: Xor(ReadMemory(HL.Word)); return 7; // XOR (HL)
case 0xAF: Xor(AF.High); return 4; // XOR A case 0xAF: Xor(AF.High); return 4; // XOR A
// --- OR r --- // --- OR r ---
@@ -1196,7 +1230,7 @@ namespace Core.Cpu
case 0xB3: Or(DE.Low); return 4; // OR E case 0xB3: Or(DE.Low); return 4; // OR E
case 0xB4: Or(HL.High); return 4; // OR H case 0xB4: Or(HL.High); return 4; // OR H
case 0xB5: Or(HL.Low); return 4; // OR L case 0xB5: Or(HL.Low); return 4; // OR L
case 0xB6: Or(_memory.Read(HL.Word)); return 7; // OR (HL) case 0xB6: Or(ReadMemory(HL.Word)); return 7; // OR (HL)
case 0xB7: Or(AF.High); return 4; // OR A case 0xB7: Or(AF.High); return 4; // OR A
// --- CP r --- // --- CP r ---
@@ -1206,7 +1240,7 @@ namespace Core.Cpu
case 0xBB: Cp(DE.Low); return 4; // CP E case 0xBB: Cp(DE.Low); return 4; // CP E
case 0xBC: Cp(HL.High); return 4; // CP H case 0xBC: Cp(HL.High); return 4; // CP H
case 0xBD: Cp(HL.Low); return 4; // CP L case 0xBD: Cp(HL.Low); return 4; // CP L
case 0xBE: Cp(_memory.Read(HL.Word)); return 7; // CP (HL) case 0xBE: Cp(ReadMemory(HL.Word)); return 7; // CP (HL)
case 0xBF: Cp(AF.High); return 4; // CP A case 0xBF: Cp(AF.High); return 4; // CP A
// --- Conditional Returns (11 T-States if taken, 5 if not) --- // --- Conditional Returns (11 T-States if taken, 5 if not) ---
case 0xC0: // RET NZ case 0xC0: // RET NZ
@@ -1431,12 +1465,12 @@ namespace Core.Cpu
return 10; return 10;
case 0xE3: // EX (SP), HL case 0xE3: // EX (SP), HL
// 1. Read the 16-bit value currently on top of the stack // 1. Read the 16-bit value currently on top of the stack
byte spLow = _memory.Read(SP); byte spLow = ReadMemory(SP);
byte spHigh = _memory.Read((ushort)(SP + 1)); byte spHigh = ReadMemory((ushort)(SP + 1));
// 2. Write the current HL registers onto the stack in its place // 2. Write the current HL registers onto the stack in its place
_memory.Write(SP, HL.Low); WriteMemory(SP, HL.Low);
_memory.Write((ushort)(SP + 1), HL.High); WriteMemory((ushort)(SP + 1), HL.High);
// 3. Update HL with the data we pulled off the stack // 3. Update HL with the data we pulled off the stack
HL.Low = spLow; HL.Low = spLow;
@@ -1508,7 +1542,7 @@ namespace Core.Cpu
private int ExecuteExtendedPrefix() //ED private int ExecuteExtendedPrefix() //ED
{ {
// Fetch the actual extended instruction // Fetch the actual extended instruction
byte extendedOpcode = _memory.Read(PC++); byte extendedOpcode = FetchByte();
byte val = 0; byte val = 0;
switch (extendedOpcode) switch (extendedOpcode)
@@ -1518,8 +1552,8 @@ namespace Core.Cpu
return 15; return 15;
case 0x43: // LD (nn), BC case 0x43: // LD (nn), BC
ushort dest43 = FetchWord(); ushort dest43 = FetchWord();
_memory.Write(dest43, BC.Low); WriteMemory(dest43, BC.Low);
_memory.Write((ushort)(dest43 + 1), BC.High); WriteMemory((ushort)(dest43 + 1), BC.High);
return 20; return 20;
case 0x44: // NEG case 0x44: // NEG
int aBefore = AF.High; int aBefore = AF.High;
@@ -1557,8 +1591,8 @@ namespace Core.Cpu
return 15; return 15;
case 0x4B: // LD BC, (nn) case 0x4B: // LD BC, (nn)
ushort src4B = FetchWord(); ushort src4B = FetchWord();
BC.Low = _memory.Read(src4B); BC.Low = ReadMemory(src4B);
BC.High = _memory.Read((ushort)(src4B + 1)); BC.High = ReadMemory((ushort)(src4B + 1));
return 20; return 20;
case 0x4D: // RETI Does not affect IFF1 or IFF2 case 0x4D: // RETI Does not affect IFF1 or IFF2
PC = Pop(); PC = Pop();
@@ -1568,8 +1602,8 @@ namespace Core.Cpu
return 15; return 15;
case 0x53: // LD (nn), DE case 0x53: // LD (nn), DE
ushort dest53 = FetchWord(); ushort dest53 = FetchWord();
_memory.Write(dest53, DE.Low); WriteMemory(dest53, DE.Low);
_memory.Write((ushort)(dest53 + 1), DE.High); WriteMemory((ushort)(dest53 + 1), DE.High);
return 20; return 20;
case 0x56: // IM 1 case 0x56: // IM 1
InterruptMode = 1; InterruptMode = 1;
@@ -1579,8 +1613,8 @@ namespace Core.Cpu
return 15; return 15;
case 0x5B: // LD DE, (nn) case 0x5B: // LD DE, (nn)
ushort src5B = FetchWord(); ushort src5B = FetchWord();
DE.Low = _memory.Read(src5B); DE.Low = ReadMemory(src5B);
DE.High = _memory.Read((ushort)(src5B + 1)); DE.High = ReadMemory((ushort)(src5B + 1));
return 20; return 20;
case 0x5E: // IM 2 case 0x5E: // IM 2
// Set the CPU's internal interrupt mode state // Set the CPU's internal interrupt mode state
@@ -1649,8 +1683,8 @@ namespace Core.Cpu
return 15; // 15 T-States return 15; // 15 T-States
case 0x73: // LD (nn), SP case 0x73: // LD (nn), SP
ushort dest73 = FetchWord(); ushort dest73 = FetchWord();
_memory.Write(dest73, (byte)SP); WriteMemory(dest73, (byte)SP);
_memory.Write((ushort)(dest73 + 1), (byte)(SP >> 8)); WriteMemory((ushort)(dest73 + 1), (byte)(SP >> 8));
return 20; return 20;
case 0x78: // IN A, (C) case 0x78: // IN A, (C)
// Read from the hardware port using the full BC register as the address // Read from the hardware port using the full BC register as the address
@@ -1684,8 +1718,8 @@ namespace Core.Cpu
ushort address7B = (ushort)((addrHigh << 8) | addrLow); ushort address7B = (ushort)((addrHigh << 8) | addrLow);
// 2. Read the 16-bit value stored at that exact memory location (Little-Endian!) // 2. Read the 16-bit value stored at that exact memory location (Little-Endian!)
byte spLow = _memory.Read(address7B); byte spLow = ReadMemory(address7B);
byte spHigh = _memory.Read((ushort)(address7B + 1)); byte spHigh = ReadMemory((ushort)(address7B + 1));
// 3. Load the resulting 16-bit value directly into the Stack Pointer // 3. Load the resulting 16-bit value directly into the Stack Pointer
SP = (ushort)((spHigh << 8) | spLow); SP = (ushort)((spHigh << 8) | spLow);
@@ -1693,10 +1727,10 @@ namespace Core.Cpu
return 20; return 20;
case 0xA0: // LDI case 0xA0: // LDI
// 1. Read byte from (HL) // 1. Read byte from (HL)
val = _memory.Read(HL.Word); val = ReadMemory(HL.Word);
// 2. Write byte to (DE) // 2. Write byte to (DE)
_memory.Write(DE.Word, val); WriteMemory(DE.Word, val);
// 3. Increment memory pointers, Decrement byte counter // 3. Increment memory pointers, Decrement byte counter
HL.Word++; HL.Word++;
@@ -1717,10 +1751,10 @@ namespace Core.Cpu
return 16; return 16;
case 0xB0: // LDIR case 0xB0: // LDIR
// 1. Read byte from (HL) // 1. Read byte from (HL)
val = _memory.Read(HL.Word); val = ReadMemory(HL.Word);
// 2. Write byte to (DE) // 2. Write byte to (DE)
_memory.Write(DE.Word, val); WriteMemory(DE.Word, val);
// 3. Increment memory pointers, Decrement byte counter // 3. Increment memory pointers, Decrement byte counter
HL.Word++; HL.Word++;
@@ -1744,10 +1778,10 @@ namespace Core.Cpu
return 16; return 16;
case 0xB8: // LDDR case 0xB8: // LDDR
// 1. Read byte from (HL) // 1. Read byte from (HL)
val = _memory.Read(HL.Word); val = ReadMemory(HL.Word);
// 2. Write byte to (DE) // 2. Write byte to (DE)
_memory.Write(DE.Word, val); WriteMemory(DE.Word, val);
// 3. Decrement all three pointers // 3. Decrement all three pointers
HL.Word--; HL.Word--;
@@ -1797,7 +1831,7 @@ namespace Core.Cpu
case 3: val = DE.Low; break; case 3: val = DE.Low; break;
case 4: val = HL.High; break; case 4: val = HL.High; break;
case 5: val = HL.Low; break; case 5: val = HL.Low; break;
case 6: val = _memory.Read(HL.Word); break; // The 0x110 (HL) exception case 6: val = ReadMemory(HL.Word); break; // The 0x110 (HL) exception
case 7: val = AF.High; break; case 7: val = AF.High; break;
} }
@@ -1930,7 +1964,7 @@ namespace Core.Cpu
case 3: DE.Low = val; break; case 3: DE.Low = val; break;
case 4: HL.High = val; break; case 4: HL.High = val; break;
case 5: HL.Low = val; break; case 5: HL.Low = val; break;
case 6: _memory.Write(HL.Word, val); break; case 6: WriteMemory(HL.Word, val); break;
case 7: AF.High = val; break; case 7: AF.High = val; break;
} }
@@ -1969,10 +2003,10 @@ namespace Core.Cpu
ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22); ushort address22 = (ushort)((addrHigh22 << 8) | addrLow22);
// 2. Write the LOW byte of IX to the exact address // 2. Write the LOW byte of IX to the exact address
_memory.Write(address22, IX.Low); WriteMemory(address22, IX.Low);
// 3. Write the HIGH byte of IX to the address + 1 // 3. Write the HIGH byte of IX to the address + 1
_memory.Write((ushort)(address22 + 1), IX.High); WriteMemory((ushort)(address22 + 1), IX.High);
return 20; return 20;
case 0x23: // INC IX case 0x23: // INC IX
@@ -1998,10 +2032,10 @@ namespace Core.Cpu
ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A); ushort address2A = (ushort)((addrHigh2A << 8) | addrLow2A);
// 2. Read the LOW byte from that specific memory location // 2. Read the LOW byte from that specific memory location
byte ixLow = _memory.Read(address2A); byte ixLow = ReadMemory(address2A);
// 3. Read the HIGH byte from the next consecutive memory location // 3. Read the HIGH byte from the next consecutive memory location
byte ixHigh = _memory.Read((ushort)(address2A + 1)); byte ixHigh = ReadMemory((ushort)(address2A + 1));
// 4. Combine them and drop them into the IX register pair // 4. Combine them and drop them into the IX register pair
IX.Word = (ushort)((ixHigh << 8) | ixLow); IX.Word = (ushort)((ixHigh << 8) | ixLow);
@@ -2026,13 +2060,13 @@ namespace Core.Cpu
ushort address34 = (ushort)(IX.Word + offset34); ushort address34 = (ushort)(IX.Word + offset34);
// 3. Read the value from memory // 3. Read the value from memory
byte val34 = _memory.Read(address34); byte val34 = ReadMemory(address34);
// 4. Pass it through your helper to increment and set the flags perfectly // 4. Pass it through your helper to increment and set the flags perfectly
byte result34 = Inc8(val34); byte result34 = Inc8(val34);
// 5. Write the incremented value back to memory // 5. Write the incremented value back to memory
_memory.Write(address34, result34); WriteMemory(address34, result34);
return 23; return 23;
case 0x35: // DEC (IX+d) case 0x35: // DEC (IX+d)
@@ -2043,13 +2077,13 @@ namespace Core.Cpu
ushort address35 = (ushort)(IX.Word + offset35); ushort address35 = (ushort)(IX.Word + offset35);
// 3. Read the value from memory // 3. Read the value from memory
byte val35 = _memory.Read(address35); byte val35 = ReadMemory(address35);
// 4. Pass it through your helper to decrement and set the flags // 4. Pass it through your helper to decrement and set the flags
byte result35 = Dec8(val35); byte result35 = Dec8(val35);
// 5. Write the decremented value back to memory // 5. Write the decremented value back to memory
_memory.Write(address35, result35); WriteMemory(address35, result35);
return 23; return 23;
case 0x36: // LD (IX+d), n case 0x36: // LD (IX+d), n
@@ -2063,7 +2097,7 @@ namespace Core.Cpu
ushort address36 = (ushort)(IX.Word + offset36); ushort address36 = (ushort)(IX.Word + offset36);
// 4. Write the immediate value directly into memory // 4. Write the immediate value directly into memory
_memory.Write(address36, n36); WriteMemory(address36, n36);
return 19; return 19;
case 0x46: // LD B, (IX+d) case 0x46: // LD B, (IX+d)
@@ -2074,7 +2108,7 @@ namespace Core.Cpu
ushort address46 = (ushort)(IX.Word + offset46); ushort address46 = (ushort)(IX.Word + offset46);
// 3. Read the byte from memory and drop it into the C register (Low byte of BC) // 3. Read the byte from memory and drop it into the C register (Low byte of BC)
BC.High = _memory.Read(address46); BC.High = ReadMemory(address46);
return 19; return 19;
case 0x4E: // LD C, (IX+d) case 0x4E: // LD C, (IX+d)
@@ -2085,7 +2119,7 @@ namespace Core.Cpu
ushort address4E = (ushort)(IX.Word + offset4E); ushort address4E = (ushort)(IX.Word + offset4E);
// 3. Read the byte from memory and drop it into the C register (Low byte of BC) // 3. Read the byte from memory and drop it into the C register (Low byte of BC)
BC.Low = _memory.Read(address4E); BC.Low = ReadMemory(address4E);
return 19; return 19;
case 0x56: // LD D, (IX+d) case 0x56: // LD D, (IX+d)
@@ -2096,7 +2130,7 @@ namespace Core.Cpu
ushort address56 = (ushort)(IX.Word + offset56); ushort address56 = (ushort)(IX.Word + offset56);
// 3. Read the byte from memory and drop it into the D register (High byte of DE) // 3. Read the byte from memory and drop it into the D register (High byte of DE)
DE.High = _memory.Read(address56); DE.High = ReadMemory(address56);
return 19; return 19;
case 0x5E: // LD E, (IX+d) case 0x5E: // LD E, (IX+d)
@@ -2107,7 +2141,7 @@ namespace Core.Cpu
ushort address5E = (ushort)(IX.Word + offset5E); ushort address5E = (ushort)(IX.Word + offset5E);
// 3. Read the byte from memory and drop it into the E register // 3. Read the byte from memory and drop it into the E register
DE.Low = _memory.Read(address5E); DE.Low = ReadMemory(address5E);
return 19; return 19;
case 0x66: // LD H, (IX+d) case 0x66: // LD H, (IX+d)
@@ -2118,7 +2152,7 @@ namespace Core.Cpu
ushort address66 = (ushort)(IX.Word + offset66); ushort address66 = (ushort)(IX.Word + offset66);
// 3. Read the byte from memory and drop it into the H register (High byte of HL) // 3. Read the byte from memory and drop it into the H register (High byte of HL)
HL.High = _memory.Read(address66); HL.High = ReadMemory(address66);
return 19; return 19;
case 0x67: // LD IXH, A case 0x67: // LD IXH, A
@@ -2145,7 +2179,7 @@ namespace Core.Cpu
ushort address6E = (ushort)(IX.Word + offset6E); ushort address6E = (ushort)(IX.Word + offset6E);
// 3. Read the byte from memory and drop it into the L register (Low byte of HL) // 3. Read the byte from memory and drop it into the L register (Low byte of HL)
HL.Low = _memory.Read(address6E); HL.Low = ReadMemory(address6E);
return 19; return 19;
case 0x72: // LD (IX+d), D case 0x72: // LD (IX+d), D
@@ -2156,7 +2190,7 @@ namespace Core.Cpu
ushort address72 = (ushort)(IX.Word + offset72); ushort address72 = (ushort)(IX.Word + offset72);
// 3. Write the D register (DE.High) to memory // 3. Write the D register (DE.High) to memory
_memory.Write(address72, DE.High); WriteMemory(address72, DE.High);
return 19; // 19 T-States return 19; // 19 T-States
case 0x73: // LD (IX+d), E case 0x73: // LD (IX+d), E
@@ -2167,7 +2201,7 @@ namespace Core.Cpu
ushort address73 = (ushort)(IX.Word + offset73); ushort address73 = (ushort)(IX.Word + offset73);
// 3. Write the E register (DE.Low) to memory // 3. Write the E register (DE.Low) to memory
_memory.Write(address73, DE.Low); WriteMemory(address73, DE.Low);
return 19; return 19;
@@ -2179,7 +2213,7 @@ namespace Core.Cpu
ushort address74 = (ushort)(IX.Word + offset74); ushort address74 = (ushort)(IX.Word + offset74);
// 3. Write the contents of the L register (Low byte of HL) into memory // 3. Write the contents of the L register (Low byte of HL) into memory
_memory.Write(address74, HL.High); WriteMemory(address74, HL.High);
return 19; return 19;
case 0x75: // LD (IX+d), L case 0x75: // LD (IX+d), L
@@ -2190,7 +2224,7 @@ namespace Core.Cpu
ushort address75 = (ushort)(IX.Word + offset75); ushort address75 = (ushort)(IX.Word + offset75);
// 3. Write the contents of the L register (Low byte of HL) into memory // 3. Write the contents of the L register (Low byte of HL) into memory
_memory.Write(address75, HL.Low); WriteMemory(address75, HL.Low);
return 19; return 19;
case 0x77: // LD (IX+d), A case 0x77: // LD (IX+d), A
@@ -2201,7 +2235,7 @@ namespace Core.Cpu
ushort address77 = (ushort)(IX.Word + offset77); ushort address77 = (ushort)(IX.Word + offset77);
// 3. Write the Accumulator (AF.High) into memory at that address // 3. Write the Accumulator (AF.High) into memory at that address
_memory.Write(address77, AF.High); WriteMemory(address77, AF.High);
return 19; return 19;
case 0x7C: // LD A, IXH case 0x7C: // LD A, IXH
@@ -2216,7 +2250,7 @@ namespace Core.Cpu
ushort address7E = (ushort)(IX.Word + offset7E); ushort address7E = (ushort)(IX.Word + offset7E);
// 3. Read the byte from memory and drop it straight into the Accumulator (A) // 3. Read the byte from memory and drop it straight into the Accumulator (A)
AF.High = _memory.Read(address7E); AF.High = ReadMemory(address7E);
return 19; return 19;
case 0x86: // ADD A, (IX+d) case 0x86: // ADD A, (IX+d)
@@ -2224,7 +2258,7 @@ namespace Core.Cpu
ushort address86 = (ushort)(IX.Word + offset86); ushort address86 = (ushort)(IX.Word + offset86);
// Read the memory and pass it straight into your flawless helper! // Read the memory and pass it straight into your flawless helper!
Add(_memory.Read(address86)); Add(ReadMemory(address86));
return 19; return 19;
case 0x96: // SUB (IX+d) case 0x96: // SUB (IX+d)
@@ -2232,7 +2266,7 @@ namespace Core.Cpu
ushort address96 = (ushort)(IX.Word + offset96); ushort address96 = (ushort)(IX.Word + offset96);
// Read the memory and pass it straight into your flawless helper! // Read the memory and pass it straight into your flawless helper!
Sub(_memory.Read(address96)); Sub(ReadMemory(address96));
return 19; return 19;
case 0xBE: // CP (IX+d) case 0xBE: // CP (IX+d)
// 1. Fetch the displacement byte and calculate the address // 1. Fetch the displacement byte and calculate the address
@@ -2240,7 +2274,7 @@ namespace Core.Cpu
ushort addressBE = (ushort)(IX.Word + offsetBE); ushort addressBE = (ushort)(IX.Word + offsetBE);
// 2. Read the value from memory // 2. Read the value from memory
byte cpVal = _memory.Read(addressBE); byte cpVal = ReadMemory(addressBE);
// 3. Perform the phantom subtraction // 3. Perform the phantom subtraction
int aVal = AF.High; int aVal = AF.High;
@@ -2281,7 +2315,7 @@ namespace Core.Cpu
byte cbOpcode = FetchByte(); byte cbOpcode = FetchByte();
ushort targetAddress = (ushort)(IX.Word + displacement); ushort targetAddress = (ushort)(IX.Word + displacement);
byte memVal = _memory.Read(targetAddress); byte memVal = ReadMemory(targetAddress);
// Extract the mathematical properties of the opcode // Extract the mathematical properties of the opcode
int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET
@@ -2312,11 +2346,11 @@ namespace Core.Cpu
} }
case 0xE1: // POP IX case 0xE1: // POP IX
// 1. Read the low byte from the top of the stack // 1. Read the low byte from the top of the stack
byte popLow = _memory.Read(SP); byte popLow = ReadMemory(SP);
SP++; // Move stack pointer up SP++; // Move stack pointer up
// 2. Read the high byte // 2. Read the high byte
byte popHigh = _memory.Read(SP); byte popHigh = ReadMemory(SP);
SP++; // Move stack pointer up again SP++; // Move stack pointer up again
// 3. Combine them and store in IX // 3. Combine them and store in IX
@@ -2326,11 +2360,11 @@ namespace Core.Cpu
case 0xE5: // PUSH IX case 0xE5: // PUSH IX
// 1. Decrement the stack pointer and write the HIGH byte // 1. Decrement the stack pointer and write the HIGH byte
SP--; SP--;
_memory.Write(SP, IX.High); WriteMemory(SP, IX.High);
// 2. Decrement the stack pointer again and write the LOW byte // 2. Decrement the stack pointer again and write the LOW byte
SP--; SP--;
_memory.Write(SP, IX.Low); WriteMemory(SP, IX.Low);
return 15; return 15;
case 0xE9: // JP (IX) case 0xE9: // JP (IX)
@@ -2359,7 +2393,7 @@ namespace Core.Cpu
ushort address34 = (ushort)(IY.Word + offset34); ushort address34 = (ushort)(IY.Word + offset34);
// 2. Read the value from memory // 2. Read the value from memory
byte valBefore = _memory.Read(address34); byte valBefore = ReadMemory(address34);
// 3. Increment the value // 3. Increment the value
int result = valBefore + 1; int result = valBefore + 1;
@@ -2387,7 +2421,7 @@ namespace Core.Cpu
AF.Low = newFlags; AF.Low = newFlags;
// 4. Write the modified value back to memory // 4. Write the modified value back to memory
_memory.Write(address34, (byte)result); WriteMemory(address34, (byte)result);
return 23; // 23 T-States return 23; // 23 T-States
case 0x35: // DEC (IY+d) case 0x35: // DEC (IY+d)
@@ -2395,9 +2429,9 @@ namespace Core.Cpu
targetAddress = (ushort)(IY.Word + offset); targetAddress = (ushort)(IY.Word + offset);
// Read, decrement using your existing helper, and write back // Read, decrement using your existing helper, and write back
memVal = _memory.Read(targetAddress); memVal = ReadMemory(targetAddress);
byte decVal = Dec8(memVal); byte decVal = Dec8(memVal);
_memory.Write(targetAddress, decVal); WriteMemory(targetAddress, decVal);
return 23; return 23;
case 0x36: // LD (IY+d), n case 0x36: // LD (IY+d), n
{ {
@@ -2405,7 +2439,7 @@ namespace Core.Cpu
byte nValue = FetchByte(); byte nValue = FetchByte();
targetAddress = (ushort)(IY.Word + offset36); targetAddress = (ushort)(IY.Word + offset36);
_memory.Write(targetAddress, nValue); WriteMemory(targetAddress, nValue);
return 19; // Takes 19 T-States return 19; // Takes 19 T-States
} }
case 0x46: // LD B, (IY+d) case 0x46: // LD B, (IY+d)
@@ -2413,7 +2447,7 @@ namespace Core.Cpu
sbyte displacement = (sbyte)FetchByte(); sbyte displacement = (sbyte)FetchByte();
targetAddress = (ushort)(IY.Word + displacement); targetAddress = (ushort)(IY.Word + displacement);
BC.High = _memory.Read(targetAddress); BC.High = ReadMemory(targetAddress);
return 19; // Takes 19 T-States return 19; // Takes 19 T-States
} }
case 0x4E: // LD C, (IY+d) case 0x4E: // LD C, (IY+d)
@@ -2424,7 +2458,7 @@ namespace Core.Cpu
ushort address4E = (ushort)(IY.Word + offset4E); ushort address4E = (ushort)(IY.Word + offset4E);
// 3. Read the memory and store it in C // 3. Read the memory and store it in C
BC.Low = _memory.Read(address4E); BC.Low = ReadMemory(address4E);
return 19; return 19;
case 0x56: // LD D, (IY+d) case 0x56: // LD D, (IY+d)
@@ -2435,7 +2469,7 @@ namespace Core.Cpu
ushort address56 = (ushort)(IY.Word + offset56); ushort address56 = (ushort)(IY.Word + offset56);
// 3. Read the memory and store it in D (the high byte of DE) // 3. Read the memory and store it in D (the high byte of DE)
DE.High = _memory.Read(address56); DE.High = ReadMemory(address56);
return 19; return 19;
case 0x5E: // LD E, (IY+d) case 0x5E: // LD E, (IY+d)
@@ -2446,14 +2480,14 @@ namespace Core.Cpu
ushort address5E = (ushort)(IY.Word + offset5E); ushort address5E = (ushort)(IY.Word + offset5E);
// 3. Read the memory and store it in E (the low byte of DE) // 3. Read the memory and store it in E (the low byte of DE)
DE.Low = _memory.Read(address5E); DE.Low = ReadMemory(address5E);
return 19; return 19;
case 0x6E: // LD L, (IY+d) case 0x6E: // LD L, (IY+d)
sbyte displacementVal = (sbyte)FetchByte(); sbyte displacementVal = (sbyte)FetchByte();
ushort targetAddr = (ushort)(IY.Word + displacementVal); ushort targetAddr = (ushort)(IY.Word + displacementVal);
HL.Low = _memory.Read(targetAddr); HL.Low = ReadMemory(targetAddr);
return 19; return 19;
case 0x71: // LD (IY+d), C case 0x71: // LD (IY+d), C
{ {
@@ -2461,7 +2495,7 @@ namespace Core.Cpu
targetAddress = (ushort)(IY.Word + offset71); targetAddress = (ushort)(IY.Word + offset71);
// Write the C register (low byte of BC) to memory // Write the C register (low byte of BC) to memory
_memory.Write(targetAddress, BC.Low); WriteMemory(targetAddress, BC.Low);
return 19; // Takes 19 T-States return 19; // Takes 19 T-States
} }
case 0x72: // LD (IY+d), D case 0x72: // LD (IY+d), D
@@ -2472,7 +2506,7 @@ namespace Core.Cpu
ushort address72 = (ushort)(IY.Word + offset72); ushort address72 = (ushort)(IY.Word + offset72);
// 3. Write the contents of the D register (High byte of DE) into memory // 3. Write the contents of the D register (High byte of DE) into memory
_memory.Write(address72, DE.High); WriteMemory(address72, DE.High);
return 19; // 19 T-States return 19; // 19 T-States
case 0x74: // LD (IY+d), H case 0x74: // LD (IY+d), H
@@ -2483,20 +2517,20 @@ namespace Core.Cpu
ushort address74 = (ushort)(IY.Word + offset74); ushort address74 = (ushort)(IY.Word + offset74);
// 3. Write the contents of the H register into memory at that address // 3. Write the contents of the H register into memory at that address
_memory.Write(address74, HL.High); WriteMemory(address74, HL.High);
return 19; return 19;
case 0x75: // LD (IY+d), L case 0x75: // LD (IY+d), L
sbyte offset75 = (sbyte)FetchByte(); sbyte offset75 = (sbyte)FetchByte();
targetAddress = (ushort)(IY.Word + offset75); targetAddress = (ushort)(IY.Word + offset75);
// Write the low byte of HL to memory // Write the low byte of HL to memory
_memory.Write(targetAddress, HL.Low); WriteMemory(targetAddress, HL.Low);
return 19; return 19;
case 0x86: // ADD A, (IY+d) case 0x86: // ADD A, (IY+d)
{ {
sbyte displacementAdd = (sbyte)FetchByte(); sbyte displacementAdd = (sbyte)FetchByte();
ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd); ushort targetAddressAdd = (ushort)(IY.Word + displacementAdd);
byte valueToAdd = _memory.Read(targetAddressAdd); byte valueToAdd = ReadMemory(targetAddressAdd);
AddA(valueToAdd); AddA(valueToAdd);
return 19; return 19;
@@ -2507,7 +2541,7 @@ namespace Core.Cpu
ushort address96 = (ushort)(IY.Word + offset96); ushort address96 = (ushort)(IY.Word + offset96);
// 2. Read the value from memory // 2. Read the value from memory
byte subVal = _memory.Read(address96); byte subVal = ReadMemory(address96);
// 3. Perform the subtraction from the Accumulator // 3. Perform the subtraction from the Accumulator
int aVal = AF.High; int aVal = AF.High;
@@ -2545,7 +2579,7 @@ namespace Core.Cpu
ushort addressBE = (ushort)(IY.Word + offsetBE); ushort addressBE = (ushort)(IY.Word + offsetBE);
// 2. Read the value from memory // 2. Read the value from memory
byte cpVal = _memory.Read(addressBE); byte cpVal = ReadMemory(addressBE);
// 3. Perform the phantom subtraction // 3. Perform the phantom subtraction
aVal = AF.High; aVal = AF.High;
@@ -2580,7 +2614,7 @@ namespace Core.Cpu
sbyte displacement = (sbyte)FetchByte(); sbyte displacement = (sbyte)FetchByte();
byte cbOpcode = FetchByte(); byte cbOpcode = FetchByte();
targetAddress = (ushort)(IY.Word + displacement); targetAddress = (ushort)(IY.Word + displacement);
memVal = _memory.Read(targetAddress); memVal = ReadMemory(targetAddress);
// Extract the mathematical properties of the opcode // Extract the mathematical properties of the opcode
int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET int operation = cbOpcode >> 6; // 01 = BIT, 10 = RES, 11 = SET
@@ -2605,12 +2639,12 @@ namespace Core.Cpu
case 2: // ALL RES Instructions case 2: // ALL RES Instructions
memVal &= (byte)(~bitMask); // Invert mask and AND it to clear the bit memVal &= (byte)(~bitMask); // Invert mask and AND it to clear the bit
_memory.Write(targetAddress, memVal); WriteMemory(targetAddress, memVal);
return 23; return 23;
case 3: // ALL SET Instructions case 3: // ALL SET Instructions
memVal |= bitMask; // OR the mask to force the bit to 1 memVal |= bitMask; // OR the mask to force the bit to 1
_memory.Write(targetAddress, memVal); WriteMemory(targetAddress, memVal);
return 23; return 23;
case 0: case 0:

128
Core/Io/ULA.cs Normal file
View File

@@ -0,0 +1,128 @@
using Core.Memory;
using System;
namespace Core.Io
{
public class ULA
{
private readonly MemoryBus _memoryBus;
private readonly IO_Bus _simpleIoBus;
// The ULA owns the frame buffer now!
public const int ScreenWidth = 320;
public const int ScreenHeight = 256; // Perfectly cropped size
public int[] FrameBuffer { get; private set; }
private int _ulaFrameCount = 0;
// The hardware color palette belongs to the ULA, not the Windows Form!
private readonly int[] SpectrumColors = new int[]
{
unchecked((int)0xFF000000), unchecked((int)0xFF0000D7),
unchecked((int)0xFFD70000), unchecked((int)0xFFD700D7),
unchecked((int)0xFF00D700), unchecked((int)0xFF00D7D7),
unchecked((int)0xFFD7D700), unchecked((int)0xFFD7D7D7),
unchecked((int)0xFF000000), unchecked((int)0xFF0000FF),
unchecked((int)0xFFFF0000), unchecked((int)0xFFFF00FF),
unchecked((int)0xFF00FF00), unchecked((int)0xFF00FFFF),
unchecked((int)0xFFFFFF00), unchecked((int)0xFFFFFFFF)
};
public ULA(MemoryBus memoryBus, IO_Bus ioBus)
{
_memoryBus = memoryBus;
_simpleIoBus = ioBus;
FrameBuffer = new int[ScreenWidth * ScreenHeight];
}
// We will wire this up to the Z80 in Phase 3 so the CPU stays system-agnostic!
public int GetContentionDelay(ushort address, long currentTStates)
{
if (address < 0x4000 || address > 0x7FFF) return 0;
long frameT = currentTStates % 69888;
if (frameT < 14336 || frameT >= 57344) return 0;
int lineT = (int)(frameT % 224);
if (lineT < 14 || lineT > 141) return 0;
int delayOffset = (lineT - 14) % 8;
int[] delayPattern = { 6, 5, 4, 3, 2, 1, 0, 0 };
return delayPattern[delayOffset];
}
// The perfectly cropped, 320x256 Scanline Renderer
public void RenderScanline(int scanline)
{
// 1. Drop the invisible lines instantly (VBlank/Overscan)
if (scanline < 32 || scanline > 287) return;
// 2. Calculate our visual Y coordinate (0 to 255) for the bitmap array
int renderY = scanline - 32;
int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex];
// --- Are we in the Top or Bottom Border? ---
if (scanline < 64 || scanline > 255)
{
int yOffset = renderY * ScreenWidth;
for (int x = 0; x < ScreenWidth; x++)
{
FrameBuffer[yOffset + x] = currentBorderColor;
}
return;
}
// --- We are in the Visible Display Area (Lines 64 to 255) ---
int y = scanline - 64;
// Handle Flash Phase (only increment once per frame when y == 0)
if (y == 0) _ulaFrameCount++;
bool invertFlashPhase = (_ulaFrameCount % 32) >= 16;
int rowStartIndex = renderY * ScreenWidth;
// Draw the 32 pixels of left border
for (int b = 0; b < 32; b++) FrameBuffer[rowStartIndex + b] = currentBorderColor;
int third = y / 64;
int characterRow = (y % 64) / 8;
int pixelRow = y % 8;
// Draw the 32 horizontal character blocks of the visible screen
for (int col = 0; col < 32; col++)
{
ushort pixelAddress = (ushort)(0x4000 | (third << 11) | (pixelRow << 8) | (characterRow << 5) | col);
ushort attrAddress = (ushort)(0x5800 + (y / 8) * 32 + col);
byte pixels = _memoryBus.Read(pixelAddress);
byte attr = _memoryBus.Read(attrAddress);
int ink = attr & 0x07;
int paper = (attr >> 3) & 0x07;
int brightOffset = (attr & 0x40) != 0 ? 8 : 0;
bool isFlashSet = (attr & 0x80) != 0;
int inkColor = SpectrumColors[ink + brightOffset];
int paperColor = SpectrumColors[paper + brightOffset];
if (isFlashSet && invertFlashPhase)
{
int temp = inkColor; inkColor = paperColor; paperColor = temp;
}
// Draw the 8 pixels in this block
for (int bit = 0; bit < 8; bit++)
{
bool isPixelSet = (pixels & (1 << (7 - bit))) != 0;
int renderX = 32 + (col * 8) + bit;
FrameBuffer[rowStartIndex + renderX] = isPixelSet ? inkColor : paperColor;
}
}
// Draw the 32 pixels of right border
for (int b = ScreenWidth - 32; b < ScreenWidth; b++)
{
FrameBuffer[rowStartIndex + b] = currentBorderColor;
}
}
}
}

View File

@@ -39,7 +39,6 @@
lblTStates = new Label(); lblTStates = new Label();
txtMemoryStart = new TextBox(); txtMemoryStart = new TextBox();
btnStep = new Button(); btnStep = new Button();
btnRun = new Button();
btnRefreshMemory = new Button(); btnRefreshMemory = new Button();
txtMemoryView = new RichTextBox(); txtMemoryView = new RichTextBox();
lstDisassembly = new ListBox(); lstDisassembly = new ListBox();
@@ -56,6 +55,9 @@
lblIE = new Label(); lblIE = new Label();
btnReset = new Button(); btnReset = new Button();
uiUpdateTimer = new System.Windows.Forms.Timer(components); uiUpdateTimer = new System.Windows.Forms.Timer(components);
lblFrames = new Label();
lblFPS = new Label();
lblFrameTime = new Label();
SuspendLayout(); SuspendLayout();
// //
// lblAF // lblAF
@@ -160,16 +162,6 @@
btnStep.UseVisualStyleBackColor = true; btnStep.UseVisualStyleBackColor = true;
btnStep.Click += btnStep_Click; btnStep.Click += btnStep_Click;
// //
// btnRun
//
btnRun.Location = new Point(119, 418);
btnRun.Margin = new Padding(2);
btnRun.Name = "btnRun";
btnRun.Size = new Size(90, 27);
btnRun.TabIndex = 13;
btnRun.Text = "Run";
btnRun.UseVisualStyleBackColor = true;
//
// btnRefreshMemory // btnRefreshMemory
// //
btnRefreshMemory.Location = new Point(425, 21); btnRefreshMemory.Location = new Point(425, 21);
@@ -215,7 +207,7 @@
btnExit.Name = "btnExit"; btnExit.Name = "btnExit";
btnExit.Size = new Size(90, 27); btnExit.Size = new Size(90, 27);
btnExit.TabIndex = 18; btnExit.TabIndex = 18;
btnExit.Text = "Full Exit"; btnExit.Text = "Exit";
btnExit.UseVisualStyleBackColor = true; btnExit.UseVisualStyleBackColor = true;
// //
// label1 // label1
@@ -304,13 +296,47 @@
// uiUpdateTimer // uiUpdateTimer
// //
uiUpdateTimer.Enabled = true; uiUpdateTimer.Enabled = true;
uiUpdateTimer.Interval = 1;
uiUpdateTimer.Tick += uiUpdateTimer_Tick; uiUpdateTimer.Tick += uiUpdateTimer_Tick;
// //
// lblFrames
//
lblFrames.AutoSize = true;
lblFrames.Location = new Point(538, 320);
lblFrames.Margin = new Padding(2, 0, 2, 0);
lblFrames.Name = "lblFrames";
lblFrames.Size = new Size(124, 20);
lblFrames.TabIndex = 28;
lblFrames.Text = "Frames Rendered";
//
// lblFPS
//
lblFPS.AutoSize = true;
lblFPS.Location = new Point(630, 397);
lblFPS.Margin = new Padding(2, 0, 2, 0);
lblFPS.Name = "lblFPS";
lblFPS.Size = new Size(32, 20);
lblFPS.TabIndex = 29;
lblFPS.Text = "FPS";
//
// lblFrameTime
//
lblFrameTime.AutoSize = true;
lblFrameTime.Location = new Point(575, 356);
lblFrameTime.Margin = new Padding(2, 0, 2, 0);
lblFrameTime.Name = "lblFrameTime";
lblFrameTime.Size = new Size(87, 20);
lblFrameTime.TabIndex = 30;
lblFrameTime.Text = "Frame Time";
//
// DebuggerForm // DebuggerForm
// //
AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(928, 454); ClientSize = new Size(928, 454);
Controls.Add(lblFrameTime);
Controls.Add(lblFPS);
Controls.Add(lblFrames);
Controls.Add(btnReset); Controls.Add(btnReset);
Controls.Add(lblIE); Controls.Add(lblIE);
Controls.Add(lblIff2); Controls.Add(lblIff2);
@@ -325,7 +351,6 @@
Controls.Add(lstDisassembly); Controls.Add(lstDisassembly);
Controls.Add(txtMemoryView); Controls.Add(txtMemoryView);
Controls.Add(btnRefreshMemory); Controls.Add(btnRefreshMemory);
Controls.Add(btnRun);
Controls.Add(btnStep); Controls.Add(btnStep);
Controls.Add(txtMemoryStart); Controls.Add(txtMemoryStart);
Controls.Add(lblTStates); Controls.Add(lblTStates);
@@ -355,7 +380,6 @@
private Label lblTStates; private Label lblTStates;
private TextBox txtMemoryStart; private TextBox txtMemoryStart;
private Button btnStep; private Button btnStep;
private Button btnRun;
private Button btnRefreshMemory; private Button btnRefreshMemory;
private RichTextBox txtMemoryView; private RichTextBox txtMemoryView;
private ListBox lstStack; private ListBox lstStack;
@@ -372,6 +396,9 @@
private Label lblIE; private Label lblIE;
private Button btnReset; private Button btnReset;
private System.Windows.Forms.Timer uiUpdateTimer; private System.Windows.Forms.Timer uiUpdateTimer;
private Label lblFrames;
private Label lblFPS;
private Label lblFrameTime;
//private TextBox textBox4; //private TextBox textBox4;
} }
} }

View File

@@ -11,8 +11,6 @@ namespace Desktop
private readonly Z80 _cpu; private readonly Z80 _cpu;
private readonly MemoryBus _memoryBus; private readonly MemoryBus _memoryBus;
private readonly Form1 _mainForm; private readonly Form1 _mainForm;
private bool _isRunning = false;
private ushort? _breakpoint = null;
public DebuggerForm(Z80 cpu, MemoryBus memoryBus, Form1 mainForm) public DebuggerForm(Z80 cpu, MemoryBus memoryBus, Form1 mainForm)
{ {
@@ -61,15 +59,12 @@ namespace Desktop
private void uiUpdateTimer_Tick(object sender, EventArgs e) private void uiUpdateTimer_Tick(object sender, EventArgs e)
{ {
// Every 100ms, take a snapshot of the CPU state and draw it to the screen.
// This makes the registers look like they are spinning matrix-style while running!
UpdateDisplay(); UpdateDisplay();
} }
// This is the master function that pulls state from the CPU // Current Emulator State
private void UpdateDisplay() private void UpdateDisplay()
{ {
// 1. Update Registers (Formatting as 4-character Hex strings)
lblAF.Text = $"AF: {_cpu.AF.Word:X4}"; lblAF.Text = $"AF: {_cpu.AF.Word:X4}";
lblBC.Text = $"BC: {_cpu.BC.Word:X4}"; lblBC.Text = $"BC: {_cpu.BC.Word:X4}";
lblDE.Text = $"DE: {_cpu.DE.Word:X4}"; lblDE.Text = $"DE: {_cpu.DE.Word:X4}";
@@ -81,12 +76,11 @@ namespace Desktop
lblIff1.Text = $"IFF1: {_cpu.IFF1}"; lblIff1.Text = $"IFF1: {_cpu.IFF1}";
lblIff2.Text = $"IFF2: {_cpu.IFF2}"; lblIff2.Text = $"IFF2: {_cpu.IFF2}";
lblIE.Text = $"Interrupt Mode: {_cpu.InterruptMode}"; lblIE.Text = $"Interrupt Mode: {_cpu.InterruptMode}";
// 2. Update Flags & T-States
lblFlags.Text = $"Flags: {_cpu.GetFlagsString()}"; lblFlags.Text = $"Flags: {_cpu.GetFlagsString()}";
lblTStates.Text = $"T-States: {_cpu.TotalTStates}"; lblTStates.Text = $"T-States: {_cpu.TotalTStates}";
lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}";
// 3. Update Memory Viewer lblFrameTime.Text = $"Frame Time: {((float)_mainForm.FrameTime):F1}ms";
lblFPS.Text = $"FPS: {_mainForm.FramesPerSecond:F2}";
UpdateMemoryView(); UpdateMemoryView();
UpdateStackView(); UpdateStackView();
UpdateDisassemblyView(); UpdateDisassemblyView();

View File

@@ -112,7 +112,7 @@
// debuggerToolStripMenuItem // debuggerToolStripMenuItem
// //
debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem"; debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem";
debuggerToolStripMenuItem.Size = new Size(224, 26); debuggerToolStripMenuItem.Size = new Size(159, 26);
debuggerToolStripMenuItem.Text = "Debugger"; debuggerToolStripMenuItem.Text = "Debugger";
debuggerToolStripMenuItem.Click += openDebuggerToolStripMenuItem_Click; debuggerToolStripMenuItem.Click += openDebuggerToolStripMenuItem_Click;
// //

View File

@@ -1,8 +1,8 @@
using System;
using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Forms; using System.Diagnostics;
using System.Threading;
using System.IO;
using Core.Cpu; using Core.Cpu;
using Core.Io; using Core.Io;
using Core.Memory; using Core.Memory;
@@ -14,37 +14,18 @@ namespace Desktop
private Z80 _cpu = null!; private Z80 _cpu = null!;
private MemoryBus _memoryBus = null!; private MemoryBus _memoryBus = null!;
private IO_Bus _simpleIoBus = null!; private IO_Bus _simpleIoBus = null!;
private ULA _ula = null!;
private TapManager _tapManager = null!; private TapManager _tapManager = null!;
private int _ulaFrameCount = 0; private DebuggerForm? _debugger = null;
private string _baseTitle = "";
private bool _isRunning = false; private bool _isRunning = false;
private bool _isPaused = false; private bool _isPaused = false;
private bool _resetFlag = false; public ushort? Breakpoint = null;
public ushort? Breakpoint = null; // Public so the debugger can set it! public long TotalFrameCount = 0;
private DebuggerForm _debugger = null; public double FramesPerSecond = 0;
public double TotalFrameTime = 0;
public double FrameTime = 0;
// The 16 physical colors of the ZX Spectrum (ARGB format)
private readonly int[] SpectrumColors = new int[]
{
// Normal Colors (Bright = 0)
unchecked((int)0xFF000000), // 0: Black
unchecked((int)0xFF0000D7), // 1: Blue
unchecked((int)0xFFD70000), // 2: Red
unchecked((int)0xFFD700D7), // 3: Magenta
unchecked((int)0xFF00D700), // 4: Green
unchecked((int)0xFF00D7D7), // 5: Cyan
unchecked((int)0xFFD7D700), // 6: Yellow
unchecked((int)0xFFD7D7D7), // 7: White
// Bright Colors (Bright = 1)
unchecked((int)0xFF000000), // 8: Bright Black
unchecked((int)0xFF0000FF), // 9: Bright Blue
unchecked((int)0xFFFF0000), // 10: Bright Red
unchecked((int)0xFFFF00FF), // 11: Bright Magenta
unchecked((int)0xFF00FF00), // 12: Bright Green
unchecked((int)0xFF00FFFF), // 13: Bright Cyan
unchecked((int)0xFFFFFF00), // 14: Bright Yellow
unchecked((int)0xFFFFFFFF) // 15: Bright White
};
public Form1() public Form1()
{ {
@@ -56,18 +37,17 @@ namespace Desktop
{ {
try try
{ {
_baseTitle = this.Text;
_memoryBus = new MemoryBus(); _memoryBus = new MemoryBus();
_simpleIoBus = new IO_Bus(); _simpleIoBus = new IO_Bus();
_ula = new ULA(_memoryBus, _simpleIoBus);
_tapManager = new TapManager(); _tapManager = new TapManager();
_memoryBus.CrapRAMData(); _memoryBus.CrapRAMData();
byte[] romData = RomLoader.Load("48.rom"); byte[] romData = RomLoader.Load("48.rom");
_memoryBus.LoadRom(romData); _memoryBus.LoadRom(romData);
_cpu = new Z80(_memoryBus, _simpleIoBus, _tapManager); _cpu = new Z80(_memoryBus, _simpleIoBus, _tapManager);
_cpu.WaitStateCallback = _ula.GetContentionDelay;
// Pass 'this' so the DebuggerForm can talk back to this main window
//DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus, this);
//debugger.Show();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -81,28 +61,22 @@ namespace Desktop
if (_isRunning) return; if (_isRunning) return;
_isRunning = true; _isRunning = true;
_isPaused = false; _isPaused = false;
Task.Run(() => Task.Run(() =>
{ {
try try
{ {
const int TStatesPerFrame = 69888; const int TStatesPerFrame = 69888;
long nextFrameTargetTStates = _cpu.TotalTStates + TStatesPerFrame; long nextScanlineTarget = _cpu.TotalTStates + TStatesPerFrame;
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
long frameCount = 0; var fpsStopwatch = Stopwatch.StartNew();
long scanlineCount = 0;
while (_isRunning) while (_isRunning)
{ {
//if(_resetFlag)
//{
// _resetFlag = false;
// stopwatch.Reset();
// nextFrameTargetTStates = _cpu.TotalTStates + TStatesPerFrame;
// frameCount = 0;
//}
if (_isPaused) if (_isPaused)
{ {
System.Threading.Thread.Sleep(10); // Don't melt the host CPU while paused Thread.Sleep(10);
continue; continue;
} }
@@ -110,7 +84,6 @@ namespace Desktop
if (Breakpoint.HasValue && _cpu.PC == Breakpoint.Value) if (Breakpoint.HasValue && _cpu.PC == Breakpoint.Value)
{ {
_isPaused = true; _isPaused = true;
// Optional: You could force the debugger to open here!
continue; continue;
} }
@@ -118,22 +91,43 @@ namespace Desktop
_cpu.Step(); _cpu.Step();
// --- Check for End of Frame --- // --- Check for End of Frame ---
if (_cpu.TotalTStates >= nextFrameTargetTStates) if (_cpu.TotalTStates >= nextScanlineTarget)
{ {
_cpu.RequestInterrupt(); // Tell the ULA to draw one line of pixels
nextFrameTargetTStates += TStatesPerFrame; _ula.RenderScanline((int)scanlineCount % 312);
frameCount++;
// Render the screen nextScanlineTarget += 224; // Advance target by ONE line (224 T-States)
this.Invoke((MethodInvoker)delegate { RenderScreen(); }); scanlineCount++;
// Hit the bottom of the screen (Line 312)?
if (scanlineCount % 312 == 0)
{
_cpu.RequestInterrupt(); // 50Hz interrupt
this.Invoke((MethodInvoker)delegate
{
UpdateScreenBitmap();
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}";
});
TotalFrameCount++;
// Throttle to real-time (50 FPS = 20ms) // Throttle to real-time (50 FPS = 20ms)
long targetTimeMs = frameCount * 20; long targetTimeMs = (scanlineCount / 312) * 20;
long elapsedMs = stopwatch.ElapsedMilliseconds; long elapsedMs = stopwatch.ElapsedMilliseconds;
if (elapsedMs < targetTimeMs) if (elapsedMs < targetTimeMs)
{ {
System.Threading.Thread.Sleep((int)(targetTimeMs - elapsedMs)); Thread.Sleep((int)(targetTimeMs - elapsedMs));
}
TotalFrameTime += fpsStopwatch.Elapsed.TotalMilliseconds;
if (TotalFrameCount % 50 == 0)
{
FramesPerSecond = 1000.0 / (TotalFrameTime / 50.0);
FrameTime = TotalFrameTime / 50.0;
TotalFrameTime = 0;
}
fpsStopwatch.Restart();
} }
} }
} }
@@ -141,13 +135,33 @@ namespace Desktop
catch (Exception ex) catch (Exception ex)
{ {
_isPaused = true; _isPaused = true;
this.Invoke((MethodInvoker)delegate { this.Invoke((MethodInvoker)delegate
{
MessageBox.Show(ex.Message, "CPU Crash", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show(ex.Message, "CPU Crash", MessageBoxButtons.OK, MessageBoxIcon.Error);
}); });
} }
}); });
} }
private void UpdateScreenBitmap()
{
// Build the bitmap
Bitmap bmp = new Bitmap(ULA.ScreenWidth, ULA.ScreenHeight, PixelFormat.Format32bppArgb);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, ULA.ScreenWidth, ULA.ScreenHeight),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
// Pull the raw pixel data
Marshal.Copy(_ula.FrameBuffer, 0, bmpData.Scan0, _ula.FrameBuffer.Length);
bmp.UnlockBits(bmpData);
if (picScreen.Image != null) picScreen.Image.Dispose();
picScreen.Image = bmp;
}
private void loadTAPToolStripMenuItem_Click(object sender, EventArgs e) private void loadTAPToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using (OpenFileDialog ofd = new OpenFileDialog()) using (OpenFileDialog ofd = new OpenFileDialog())
@@ -155,13 +169,8 @@ namespace Desktop
ofd.Filter = "Spectrum TAP Files|*.tap"; ofd.Filter = "Spectrum TAP Files|*.tap";
if (ofd.ShowDialog() == DialogResult.OK) if (ofd.ShowDialog() == DialogResult.OK)
{ {
// The Desktop UI reads the file from the hard drive byte[] tapBytes = File.ReadAllBytes(ofd.FileName);
byte[] tapBytes = System.IO.File.ReadAllBytes(ofd.FileName);
// The pure Core logic processes the bytes
_cpu._tapManager.LoadTapData(tapBytes); _cpu._tapManager.LoadTapData(tapBytes);
//MessageBox.Show("Tape inserted! Type LOAD \"\" and press Enter.", "Tape Deck");
} }
} }
} }
@@ -169,10 +178,10 @@ namespace Desktop
{ {
using (OpenFileDialog ofd = new OpenFileDialog()) using (OpenFileDialog ofd = new OpenFileDialog())
{ {
ofd.Filter = "Spectrum Snapshot Files (*.sna)|*.sna"; ofd.Filter = "Snapshot Files (sna,z80)|*.sna";
if (ofd.ShowDialog() == DialogResult.OK) if (ofd.ShowDialog() == DialogResult.OK)
{ {
byte[] snaBytes = System.IO.File.ReadAllBytes(ofd.FileName); byte[] snaBytes = File.ReadAllBytes(ofd.FileName);
_cpu.LoadSNA(snaBytes); _cpu.LoadSNA(snaBytes);
} }
} }
@@ -189,7 +198,6 @@ namespace Desktop
private void btnReset_Click(object sender, EventArgs e) private void btnReset_Click(object sender, EventArgs e)
{ {
//_resetFlag = true;
_isPaused = true; _isPaused = true;
_cpu.Reset(); _cpu.Reset();
_memoryBus.CleanRAMData(); _memoryBus.CleanRAMData();
@@ -214,83 +222,6 @@ namespace Desktop
} }
} }
// Public so the Debugger's background thread can call it 50 times a second
public void RenderScreen()
{
_ulaFrameCount++;
bool invertFlashPhase = (_ulaFrameCount % 32) >= 16;
// --- NEW: Expanded screen size (32px border on all sides) ---
const int screenWidth = 320;
const int screenHeight = 256;
const int borderSize = 32;
int[] pixelData = new int[screenWidth * screenHeight];
// --- NEW: Fill the background with the Border Color ---
// (Note: The hardware border always uses standard brightness, never bright)
int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex];
Array.Fill(pixelData, currentBorderColor);
// Loop through the 6144 bytes of Pixel RAM
for (int offset = 0; offset < 6144; offset++)
{
ushort address = (ushort)(0x4000 + offset);
byte pixels = _memoryBus.Read(address);
int y = ((offset & 0x0700) >> 8) |
((offset & 0x00E0) >> 2) |
((offset & 0x1800) >> 5);
int x = (offset & 0x001F) * 8;
int attrRow = y / 8;
int attrCol = x / 8;
ushort attrAddress = (ushort)(0x5800 + (attrRow * 32) + attrCol);
byte attr = _memoryBus.Read(attrAddress);
int ink = attr & 0x07;
int paper = (attr >> 3) & 0x07;
int brightOffset = (attr & 0x40) != 0 ? 8 : 0;
bool isFlashSet = (attr & 0x80) != 0;
int inkColor = SpectrumColors[ink + brightOffset];
int paperColor = SpectrumColors[paper + brightOffset];
if (isFlashSet && invertFlashPhase)
{
int temp = inkColor;
inkColor = paperColor;
paperColor = temp;
}
// Draw the 8 pixels
for (int bit = 0; bit < 8; bit++)
{
bool isPixelSet = (pixels & (1 << (7 - bit))) != 0;
// --- NEW: Add the 32px border offset to our X and Y calculations! ---
int renderY = y + borderSize;
int renderX = x + borderSize + bit;
// Map it to our new, wider pixel array
pixelData[(renderY * screenWidth) + renderX] = isPixelSet ? inkColor : paperColor;
}
}
// --- NEW: Update Bitmap dimensions to match the new 320x256 array ---
Bitmap bmp = new Bitmap(screenWidth, screenHeight, PixelFormat.Format32bppArgb);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, screenWidth, screenHeight),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
Marshal.Copy(pixelData, 0, bmpData.Scan0, pixelData.Length);
bmp.UnlockBits(bmpData);
if (picScreen.Image != null) picScreen.Image.Dispose();
picScreen.Image = bmp;
}
private void UpdateMatrix(int row, int col, bool isPressed) private void UpdateMatrix(int row, int col, bool isPressed)
{ {
if (isPressed) if (isPressed)
@@ -305,14 +236,14 @@ namespace Desktop
} }
} }
// Hook this to Form1's KeyDown event
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
HandleKey(e.KeyCode, true); HandleKey(e.KeyCode, true);
base.OnKeyDown(e); base.OnKeyDown(e);
} }
// Hook this to Form1's KeyUp event
protected override void OnKeyUp(KeyEventArgs e) protected override void OnKeyUp(KeyEventArgs e)
{ {
HandleKey(e.KeyCode, false); HandleKey(e.KeyCode, false);