ULA Implemented. Scanline renderer so cycle accurate
This commit is contained in:
294
Core/Cpu/Z80.cs
294
Core/Cpu/Z80.cs
@@ -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
128
Core/Io/ULA.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Desktop/DebuggerForm.Designer.cs
generated
55
Desktop/DebuggerForm.Designer.cs
generated
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
2
Desktop/Form1.Designer.cs
generated
2
Desktop/Form1.Designer.cs
generated
@@ -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;
|
||||||
//
|
//
|
||||||
|
|||||||
227
Desktop/Form1.cs
227
Desktop/Form1.cs
@@ -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++;
|
||||||
|
|
||||||
// Throttle to real-time (50 FPS = 20ms)
|
// Hit the bottom of the screen (Line 312)?
|
||||||
long targetTimeMs = frameCount * 20;
|
if (scanlineCount % 312 == 0)
|
||||||
long elapsedMs = stopwatch.ElapsedMilliseconds;
|
|
||||||
|
|
||||||
if (elapsedMs < targetTimeMs)
|
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep((int)(targetTimeMs - elapsedMs));
|
_cpu.RequestInterrupt(); // 50Hz interrupt
|
||||||
|
|
||||||
|
this.Invoke((MethodInvoker)delegate
|
||||||
|
{
|
||||||
|
UpdateScreenBitmap();
|
||||||
|
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}";
|
||||||
|
});
|
||||||
|
TotalFrameCount++;
|
||||||
|
|
||||||
|
// Throttle to real-time (50 FPS = 20ms)
|
||||||
|
long targetTimeMs = (scanlineCount / 312) * 20;
|
||||||
|
long elapsedMs = stopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
if (elapsedMs < targetTimeMs)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user