Updated Z80 CPU to fix interrupts etc...

This commit is contained in:
2026-05-26 22:02:47 +01:00
parent b5695b5c2f
commit ce46e7ed52
6 changed files with 121 additions and 47 deletions

View File

@@ -30,7 +30,8 @@ namespace Core.Cpu
public bool IFF1 { get; private set; } = false; public bool IFF1 { get; private set; } = false;
public bool IFF2 { get; private set; } = false; public bool IFF2 { get; private set; } = false;
public bool InterruptRequested { get; private set; } = false; public bool InterruptRequested { get; private set; } = false;
private bool _eiPending = false; private int _eiDelay = 0;
public bool IsHalted { get; private set; } = false;
// Main Register Set // Main Register Set
public RegisterPair AF; public RegisterPair AF;
@@ -50,7 +51,7 @@ namespace Core.Cpu
// Special Purpose Registers // Special Purpose Registers
public ushort PC; // Program Counter public ushort PC; // Program Counter
public ushort SP; // Stack Pointer public ushort SP;
public byte I; // Interrupt Vector public byte I; // Interrupt Vector
public byte R; // Memory Refresh public byte R; // Memory Refresh
@@ -98,6 +99,10 @@ namespace Core.Cpu
IFF2 = false; IFF2 = false;
InterruptMode = 0; InterruptMode = 0;
TotalTStates = 0; TotalTStates = 0;
_eiDelay = 0;
IsHalted = false;
InterruptRequested = false;
} }
public void SaveState(BinaryWriter bw) public void SaveState(BinaryWriter bw)
@@ -137,6 +142,7 @@ namespace Core.Cpu
public int RequestInterrupt() public int RequestInterrupt()
{ {
IsHalted = false;
InterruptRequested = true; InterruptRequested = true;
// 1. If the ROM has disabled interrupts (DI), ignore the request // 1. If the ROM has disabled interrupts (DI), ignore the request
if (!IFF1) return 0; if (!IFF1) return 0;
@@ -145,6 +151,8 @@ namespace Core.Cpu
IFF1 = false; IFF1 = false;
IFF2 = false; IFF2 = false;
_eiDelay = 0;
// 3. Push the current Program Counter to the stack so we can return later // 3. Push the current Program Counter to the stack so we can return later
Push(PC); Push(PC);
@@ -216,26 +224,40 @@ namespace Core.Cpu
public int Step() public int Step()
{ {
bool triggerEi = _eiPending; int tStates;
// Fetch the next opcode and increment the Program Counter R = (byte)((R & 0x80) | ((R + 1) & 0x7F));
byte opcode = ReadMemory(PC++);
R = (byte)((R + 1) & 0x7F);
int tStates = ExecuteOpcode(opcode);
TotalTStates += tStates;
if (triggerEi) if (IsHalted)
{ {
IFF1 = true; // The CPU is asleep! Do not fetch instructions, just pass the time.
IFF2 = true; tStates = 4;
_eiPending = false; TotalTStates += tStates;
}
else
{
// Normal execution
byte opcode = ReadMemory(PC++);
tStates = ExecuteOpcode(opcode);
TotalTStates += tStates;
}
// The interrupt enablement perfectly mimics the physical hardware delay.
// This MUST tick down even if the CPU is halted!
if (_eiDelay > 0)
{
_eiDelay--;
if (_eiDelay == 0)
{
IFF1 = true;
IFF2 = true;
}
} }
// Decode and execute
return tStates; return tStates;
} }
public string GetFlagsString() public string GetFlagsString()
{ {
@@ -931,17 +953,21 @@ namespace Core.Cpu
case 0x73: WriteMemory(HL.Word, DE.Low); return 7; case 0x73: WriteMemory(HL.Word, DE.Low); return 7;
case 0x74: WriteMemory(HL.Word, HL.High); return 7; case 0x74: WriteMemory(HL.Word, HL.High); return 7;
case 0x75: WriteMemory(HL.Word, HL.Low); return 7; case 0x75: WriteMemory(HL.Word, HL.Low); return 7;
case 0x76: //HALT case 0x76: // HALT
if (!InterruptRequested) IsHalted = true;
{ return 4;
PC--;
return 4; //case 0x76: //HALT
} // if (!InterruptRequested)
else // {
{ // PC--;
InterruptRequested = false; // return 4;
return 4; // }
} // else
// {
// InterruptRequested = false;
// return 4;
// }
case 0x77: WriteMemory(HL.Word, AF.High); return 7; case 0x77: WriteMemory(HL.Word, AF.High); return 7;
// --- LD A, r --- // --- LD A, r ---
@@ -1279,7 +1305,11 @@ namespace Core.Cpu
case 0xF3: // DI case 0xF3: // DI
IFF1 = false; IFF1 = false;
IFF2 = false; IFF2 = false;
_eiPending = false; _eiDelay = 0; // Hard cancel any pending EI delays
return 4;
case 0xFB: // EI
_eiDelay = 2; // Ticks down across the current and subsequent instruction
return 4; return 4;
case 0xf5: //push af case 0xf5: //push af
Push(AF.Word); Push(AF.Word);
@@ -1290,9 +1320,7 @@ namespace Core.Cpu
case 0xF9: // LD SP, HL case 0xF9: // LD SP, HL
SP = HL.Word; SP = HL.Word;
return 6; return 6;
case 0xFB: // EI
_eiPending = true;
return 4;
case 0xFD: case 0xFD:
return ExecuteFDPrefix(); return ExecuteFDPrefix();
case 0xFE: // CP n case 0xFE: // CP n

View File

@@ -64,7 +64,7 @@ namespace Core.Memory
if (address < 0xC000) if (address < 0xC000)
{ {
// Slot 2 (Or SRAM) // Slot 2 (Or SRAM)
if (SramUsed && (_mapperControl & 0x08) != 0) if ((_mapperControl & 0x08) != 0)
return _cartridgeRam[address - 0x8000]; return _cartridgeRam[address - 0x8000];
else else
return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)]; return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)];
@@ -80,7 +80,7 @@ namespace Core.Memory
if (address < 0xC000) if (address < 0xC000)
{ {
if (SramUsed && (_mapperControl & 0x08) != 0) if ((_mapperControl & 0x08) != 0)
_cartridgeRam[address - 0x8000] = value; _cartridgeRam[address - 0x8000] = value;
return; return;
} }

View File

@@ -17,6 +17,7 @@ namespace Core
public SmsVdp VideoProcessor { get; private set; } public SmsVdp VideoProcessor { get; private set; }
public SmsApu AudioProcessor { get; private set; } public SmsApu AudioProcessor { get; private set; }
public ushort? Breakpoint { get; set; } = null; public ushort? Breakpoint { get; set; } = null;
private int _tStateCarryover = 0;
// NTSC SMS T-States per frame // NTSC SMS T-States per frame
public const int TStatesPerFrame = 59736; //NTSC public const int TStatesPerFrame = 59736; //NTSC
@@ -40,13 +41,15 @@ namespace Core
public void Reset() public void Reset()
{ {
MemoryBus.CleanRAMData(); MemoryBus.CleanRAMData();
VideoProcessor.Reset();
Cpu.Reset(); Cpu.Reset();
_tStateCarryover = 0;
} }
public void RunFrame() public void RunFrame()
{ {
int tStatesThisFrame = 0; int tStatesThisFrame = _tStateCarryover;
while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time
{ {
// 1. Run one CPU instruction // 1. Run one CPU instruction
@@ -58,12 +61,16 @@ namespace Core
AudioProcessor.Update(cycles); AudioProcessor.Update(cycles);
// 3. Check if the VDP is begging for attention! // 3. Check if the VDP is begging for attention!
if (VideoProcessor.InterruptPending && Cpu.IFF1) //if (VideoProcessor.InterruptPending && Cpu.IFF1)
if (VideoProcessor.InterruptPending)
{ {
int intCycles = Cpu.RequestInterrupt(); int intCycles = Cpu.RequestInterrupt();
tStatesThisFrame += intCycles; if (intCycles > 0)
VideoProcessor.Update(intCycles); {
AudioProcessor.Update(intCycles); tStatesThisFrame += intCycles;
VideoProcessor.Update(intCycles);
AudioProcessor.Update(intCycles);
}
} }
// 4. THE RESTORED BREAKPOINT TRAP // 4. THE RESTORED BREAKPOINT TRAP

View File

@@ -376,6 +376,17 @@ namespace Core.Video
return (255 << 24) | (r << 16) | (g << 8) | b; return (255 << 24) | (r << 16) | (g << 8) | b;
} }
public void Reset()
{
_tStateCounter = 0;
_currentScanline = 0;
_lineCounter = 0;
_statusRegister = 0x00;
_controlWord = 0;
_isSecondControlByte = false;
_readBuffer = 0;
}
public void SaveState(BinaryWriter bw) public void SaveState(BinaryWriter bw)
{ {
bw.Write(VRAM); bw.Write(VRAM);

View File

@@ -63,6 +63,8 @@
lblTone2 = new Label(); lblTone2 = new Label();
lblNoise = new Label(); lblNoise = new Label();
lblTone0 = new Label(); lblTone0 = new Label();
lblHalt = new Label();
lblR = new Label();
groupBox1.SuspendLayout(); groupBox1.SuspendLayout();
SuspendLayout(); SuspendLayout();
// //
@@ -243,7 +245,7 @@
// lblIff1 // lblIff1
// //
lblIff1.AutoSize = true; lblIff1.AutoSize = true;
lblIff1.Location = new Point(9, 426); lblIff1.Location = new Point(10, 472);
lblIff1.Name = "lblIff1"; lblIff1.Name = "lblIff1";
lblIff1.Size = new Size(35, 20); lblIff1.Size = new Size(35, 20);
lblIff1.TabIndex = 24; lblIff1.TabIndex = 24;
@@ -252,7 +254,7 @@
// lblIff2 // lblIff2
// //
lblIff2.AutoSize = true; lblIff2.AutoSize = true;
lblIff2.Location = new Point(9, 469); lblIff2.Location = new Point(10, 515);
lblIff2.Name = "lblIff2"; lblIff2.Name = "lblIff2";
lblIff2.Size = new Size(35, 20); lblIff2.Size = new Size(35, 20);
lblIff2.TabIndex = 25; lblIff2.TabIndex = 25;
@@ -261,7 +263,7 @@
// lblIE // lblIE
// //
lblIE.AutoSize = true; lblIE.AutoSize = true;
lblIE.Location = new Point(9, 389); lblIE.Location = new Point(10, 435);
lblIE.Name = "lblIE"; lblIE.Name = "lblIE";
lblIE.Size = new Size(26, 20); lblIE.Size = new Size(26, 20);
lblIE.TabIndex = 26; lblIE.TabIndex = 26;
@@ -287,7 +289,7 @@
// lblFrames // lblFrames
// //
lblFrames.AutoSize = true; lblFrames.AutoSize = true;
lblFrames.Location = new Point(11, 528); lblFrames.Location = new Point(10, 605);
lblFrames.Margin = new Padding(2, 0, 2, 0); lblFrames.Margin = new Padding(2, 0, 2, 0);
lblFrames.Name = "lblFrames"; lblFrames.Name = "lblFrames";
lblFrames.Size = new Size(124, 20); lblFrames.Size = new Size(124, 20);
@@ -297,7 +299,7 @@
// lblFPS // lblFPS
// //
lblFPS.AutoSize = true; lblFPS.AutoSize = true;
lblFPS.Location = new Point(103, 606); lblFPS.Location = new Point(102, 683);
lblFPS.Margin = new Padding(2, 0, 2, 0); lblFPS.Margin = new Padding(2, 0, 2, 0);
lblFPS.Name = "lblFPS"; lblFPS.Name = "lblFPS";
lblFPS.Size = new Size(32, 20); lblFPS.Size = new Size(32, 20);
@@ -307,7 +309,7 @@
// lblFrameTime // lblFrameTime
// //
lblFrameTime.AutoSize = true; lblFrameTime.AutoSize = true;
lblFrameTime.Location = new Point(48, 565); lblFrameTime.Location = new Point(47, 642);
lblFrameTime.Margin = new Padding(2, 0, 2, 0); lblFrameTime.Margin = new Padding(2, 0, 2, 0);
lblFrameTime.Name = "lblFrameTime"; lblFrameTime.Name = "lblFrameTime";
lblFrameTime.Size = new Size(87, 20); lblFrameTime.Size = new Size(87, 20);
@@ -351,9 +353,9 @@
groupBox1.Controls.Add(lblNoise); groupBox1.Controls.Add(lblNoise);
groupBox1.Controls.Add(lblTone0); groupBox1.Controls.Add(lblTone0);
groupBox1.Location = new Point(213, 526); groupBox1.Location = new Point(213, 526);
groupBox1.Margin = new Padding(2, 2, 2, 2); groupBox1.Margin = new Padding(2);
groupBox1.Name = "groupBox1"; groupBox1.Name = "groupBox1";
groupBox1.Padding = new Padding(2, 2, 2, 2); groupBox1.Padding = new Padding(2);
groupBox1.Size = new Size(318, 178); groupBox1.Size = new Size(318, 178);
groupBox1.TabIndex = 35; groupBox1.TabIndex = 35;
groupBox1.TabStop = false; groupBox1.TabStop = false;
@@ -399,11 +401,31 @@
lblTone0.TabIndex = 0; lblTone0.TabIndex = 0;
lblTone0.Text = "Tone0"; lblTone0.Text = "Tone0";
// //
// lblHalt
//
lblHalt.AutoSize = true;
lblHalt.Location = new Point(9, 564);
lblHalt.Name = "lblHalt";
lblHalt.Size = new Size(37, 20);
lblHalt.TabIndex = 36;
lblHalt.Text = "Halt";
//
// lblR
//
lblR.AutoSize = true;
lblR.Location = new Point(9, 392);
lblR.Name = "lblR";
lblR.Size = new Size(18, 20);
lblR.TabIndex = 37;
lblR.Text = "R";
//
// DebuggerForm // DebuggerForm
// //
AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(965, 714); ClientSize = new Size(965, 714);
Controls.Add(lblR);
Controls.Add(lblHalt);
Controls.Add(groupBox1); Controls.Add(groupBox1);
Controls.Add(CpuRun); Controls.Add(CpuRun);
Controls.Add(btnCpuStep); Controls.Add(btnCpuStep);
@@ -478,6 +500,8 @@
private Label lblTone2; private Label lblTone2;
private Label lblNoise; private Label lblNoise;
private Label lblTone0; private Label lblTone0;
private Label lblHalt;
private Label lblR;
//private TextBox textBox4; //private TextBox textBox4;
} }
} }

View File

@@ -110,11 +110,12 @@ namespace Desktop
lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}"; lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}";
lblFrameTime.Text = $"Frame Time: {((float)_mainForm.FrameTime):F1}ms"; lblFrameTime.Text = $"Frame Time: {((float)_mainForm.FrameTime):F1}ms";
lblFPS.Text = $"FPS: {_mainForm.FramesPerSecond:F2}"; lblFPS.Text = $"FPS: {_mainForm.FramesPerSecond:F2}";
lblHalt.Text = $"CPU Halted: {_cpu.IsHalted}";
lblR.Text = $"R: {_cpu.R:X2}";
UpdateMemoryView(); UpdateMemoryView();
UpdateStackView(); UpdateStackView();
UpdateDisassemblyView(); UpdateDisassemblyView();
// --- AUDIO REGISTERS --- // --- AUDIO REGISTERS ---//
// Use _mainForm to access the Machine, and then grab the AudioProcessor!
if (_mainForm._machine != null && _mainForm._machine.AudioProcessor != null) if (_mainForm._machine != null && _mainForm._machine.AudioProcessor != null)
{ {
ushort[] apuRegs = _mainForm._machine.AudioProcessor.Registers; ushort[] apuRegs = _mainForm._machine.AudioProcessor.Registers;
@@ -668,6 +669,9 @@ namespace Desktop
case 0xC9: case 0xC9:
mnemonic = "RET"; mnemonic = "RET";
break; break;
case 0x76:
mnemonic = "HALT!";
break;
case 0xCB: case 0xCB:
cbOp = _memoryBus.Read((ushort)(currentPc + 1)); cbOp = _memoryBus.Read((ushort)(currentPc + 1));