From ce46e7ed5293804ff1e1d5c95d4cb920c241158d Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Tue, 26 May 2026 22:02:47 +0100 Subject: [PATCH] Updated Z80 CPU to fix interrupts etc... --- Core/Cpu/Z80.cs | 86 +++++++++++++++++++++----------- Core/Memory/SmsMemoryBus.cs | 4 +- Core/SmsMachine.cs | 19 ++++--- Core/Video/SmsVdp.cs | 11 ++++ Desktop/DebuggerForm.Designer.cs | 40 ++++++++++++--- Desktop/DebuggerForm.cs | 8 ++- 6 files changed, 121 insertions(+), 47 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 16cb436..673fd46 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -30,7 +30,8 @@ namespace Core.Cpu public bool IFF1 { get; private set; } = false; public bool IFF2 { 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 public RegisterPair AF; @@ -50,7 +51,7 @@ namespace Core.Cpu // Special Purpose Registers public ushort PC; // Program Counter - public ushort SP; // Stack Pointer + public ushort SP; public byte I; // Interrupt Vector public byte R; // Memory Refresh @@ -98,6 +99,10 @@ namespace Core.Cpu IFF2 = false; InterruptMode = 0; TotalTStates = 0; + + _eiDelay = 0; + IsHalted = false; + InterruptRequested = false; } public void SaveState(BinaryWriter bw) @@ -137,6 +142,7 @@ namespace Core.Cpu public int RequestInterrupt() { + IsHalted = false; InterruptRequested = true; // 1. If the ROM has disabled interrupts (DI), ignore the request if (!IFF1) return 0; @@ -145,6 +151,8 @@ namespace Core.Cpu IFF1 = false; IFF2 = false; + _eiDelay = 0; + // 3. Push the current Program Counter to the stack so we can return later Push(PC); @@ -216,26 +224,40 @@ namespace Core.Cpu public int Step() { - bool triggerEi = _eiPending; + int tStates; - // Fetch the next opcode and increment the Program Counter - byte opcode = ReadMemory(PC++); - R = (byte)((R + 1) & 0x7F); - int tStates = ExecuteOpcode(opcode); - TotalTStates += tStates; + R = (byte)((R & 0x80) | ((R + 1) & 0x7F)); - if (triggerEi) + if (IsHalted) { - IFF1 = true; - IFF2 = true; - _eiPending = false; + // The CPU is asleep! Do not fetch instructions, just pass the time. + tStates = 4; + 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; } - + public string GetFlagsString() { @@ -931,17 +953,21 @@ namespace Core.Cpu case 0x73: WriteMemory(HL.Word, DE.Low); return 7; case 0x74: WriteMemory(HL.Word, HL.High); return 7; case 0x75: WriteMemory(HL.Word, HL.Low); return 7; - case 0x76: //HALT - if (!InterruptRequested) - { - PC--; - return 4; - } - else - { - InterruptRequested = false; - return 4; - } + case 0x76: // HALT + IsHalted = true; + return 4; + + //case 0x76: //HALT + // if (!InterruptRequested) + // { + // PC--; + // return 4; + // } + // else + // { + // InterruptRequested = false; + // return 4; + // } case 0x77: WriteMemory(HL.Word, AF.High); return 7; // --- LD A, r --- @@ -1279,7 +1305,11 @@ namespace Core.Cpu case 0xF3: // DI IFF1 = 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; case 0xf5: //push af Push(AF.Word); @@ -1290,9 +1320,7 @@ namespace Core.Cpu case 0xF9: // LD SP, HL SP = HL.Word; return 6; - case 0xFB: // EI - _eiPending = true; - return 4; + case 0xFD: return ExecuteFDPrefix(); case 0xFE: // CP n diff --git a/Core/Memory/SmsMemoryBus.cs b/Core/Memory/SmsMemoryBus.cs index 6cb5bfa..9ab9cb4 100644 --- a/Core/Memory/SmsMemoryBus.cs +++ b/Core/Memory/SmsMemoryBus.cs @@ -64,7 +64,7 @@ namespace Core.Memory if (address < 0xC000) { // Slot 2 (Or SRAM) - if (SramUsed && (_mapperControl & 0x08) != 0) + if ((_mapperControl & 0x08) != 0) return _cartridgeRam[address - 0x8000]; else return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)]; @@ -80,7 +80,7 @@ namespace Core.Memory if (address < 0xC000) { - if (SramUsed && (_mapperControl & 0x08) != 0) + if ((_mapperControl & 0x08) != 0) _cartridgeRam[address - 0x8000] = value; return; } diff --git a/Core/SmsMachine.cs b/Core/SmsMachine.cs index 31f66b0..1de7e2b 100644 --- a/Core/SmsMachine.cs +++ b/Core/SmsMachine.cs @@ -17,6 +17,7 @@ namespace Core public SmsVdp VideoProcessor { get; private set; } public SmsApu AudioProcessor { get; private set; } public ushort? Breakpoint { get; set; } = null; + private int _tStateCarryover = 0; // NTSC SMS T-States per frame public const int TStatesPerFrame = 59736; //NTSC @@ -40,13 +41,15 @@ namespace Core public void Reset() { MemoryBus.CleanRAMData(); + VideoProcessor.Reset(); Cpu.Reset(); + _tStateCarryover = 0; } - + public void RunFrame() { - int tStatesThisFrame = 0; + int tStatesThisFrame = _tStateCarryover; while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time { // 1. Run one CPU instruction @@ -58,12 +61,16 @@ namespace Core AudioProcessor.Update(cycles); // 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(); - tStatesThisFrame += intCycles; - VideoProcessor.Update(intCycles); - AudioProcessor.Update(intCycles); + if (intCycles > 0) + { + tStatesThisFrame += intCycles; + VideoProcessor.Update(intCycles); + AudioProcessor.Update(intCycles); + } } // 4. THE RESTORED BREAKPOINT TRAP diff --git a/Core/Video/SmsVdp.cs b/Core/Video/SmsVdp.cs index 5c6cc6f..27d1dc2 100644 --- a/Core/Video/SmsVdp.cs +++ b/Core/Video/SmsVdp.cs @@ -376,6 +376,17 @@ namespace Core.Video 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) { bw.Write(VRAM); diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs index 22d6b24..14ff1f5 100644 --- a/Desktop/DebuggerForm.Designer.cs +++ b/Desktop/DebuggerForm.Designer.cs @@ -63,6 +63,8 @@ lblTone2 = new Label(); lblNoise = new Label(); lblTone0 = new Label(); + lblHalt = new Label(); + lblR = new Label(); groupBox1.SuspendLayout(); SuspendLayout(); // @@ -243,7 +245,7 @@ // lblIff1 // lblIff1.AutoSize = true; - lblIff1.Location = new Point(9, 426); + lblIff1.Location = new Point(10, 472); lblIff1.Name = "lblIff1"; lblIff1.Size = new Size(35, 20); lblIff1.TabIndex = 24; @@ -252,7 +254,7 @@ // lblIff2 // lblIff2.AutoSize = true; - lblIff2.Location = new Point(9, 469); + lblIff2.Location = new Point(10, 515); lblIff2.Name = "lblIff2"; lblIff2.Size = new Size(35, 20); lblIff2.TabIndex = 25; @@ -261,7 +263,7 @@ // lblIE // lblIE.AutoSize = true; - lblIE.Location = new Point(9, 389); + lblIE.Location = new Point(10, 435); lblIE.Name = "lblIE"; lblIE.Size = new Size(26, 20); lblIE.TabIndex = 26; @@ -287,7 +289,7 @@ // lblFrames // lblFrames.AutoSize = true; - lblFrames.Location = new Point(11, 528); + lblFrames.Location = new Point(10, 605); lblFrames.Margin = new Padding(2, 0, 2, 0); lblFrames.Name = "lblFrames"; lblFrames.Size = new Size(124, 20); @@ -297,7 +299,7 @@ // lblFPS // lblFPS.AutoSize = true; - lblFPS.Location = new Point(103, 606); + lblFPS.Location = new Point(102, 683); lblFPS.Margin = new Padding(2, 0, 2, 0); lblFPS.Name = "lblFPS"; lblFPS.Size = new Size(32, 20); @@ -307,7 +309,7 @@ // lblFrameTime // lblFrameTime.AutoSize = true; - lblFrameTime.Location = new Point(48, 565); + lblFrameTime.Location = new Point(47, 642); lblFrameTime.Margin = new Padding(2, 0, 2, 0); lblFrameTime.Name = "lblFrameTime"; lblFrameTime.Size = new Size(87, 20); @@ -351,9 +353,9 @@ groupBox1.Controls.Add(lblNoise); groupBox1.Controls.Add(lblTone0); groupBox1.Location = new Point(213, 526); - groupBox1.Margin = new Padding(2, 2, 2, 2); + groupBox1.Margin = new Padding(2); groupBox1.Name = "groupBox1"; - groupBox1.Padding = new Padding(2, 2, 2, 2); + groupBox1.Padding = new Padding(2); groupBox1.Size = new Size(318, 178); groupBox1.TabIndex = 35; groupBox1.TabStop = false; @@ -399,11 +401,31 @@ lblTone0.TabIndex = 0; 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 // AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(965, 714); + Controls.Add(lblR); + Controls.Add(lblHalt); Controls.Add(groupBox1); Controls.Add(CpuRun); Controls.Add(btnCpuStep); @@ -478,6 +500,8 @@ private Label lblTone2; private Label lblNoise; private Label lblTone0; + private Label lblHalt; + private Label lblR; //private TextBox textBox4; } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index cea06dc..8d76c1c 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -110,11 +110,12 @@ namespace Desktop lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}"; lblFrameTime.Text = $"Frame Time: {((float)_mainForm.FrameTime):F1}ms"; lblFPS.Text = $"FPS: {_mainForm.FramesPerSecond:F2}"; + lblHalt.Text = $"CPU Halted: {_cpu.IsHalted}"; + lblR.Text = $"R: {_cpu.R:X2}"; UpdateMemoryView(); UpdateStackView(); UpdateDisassemblyView(); - // --- AUDIO REGISTERS --- - // Use _mainForm to access the Machine, and then grab the AudioProcessor! + // --- AUDIO REGISTERS ---// if (_mainForm._machine != null && _mainForm._machine.AudioProcessor != null) { ushort[] apuRegs = _mainForm._machine.AudioProcessor.Registers; @@ -668,6 +669,9 @@ namespace Desktop case 0xC9: mnemonic = "RET"; break; + case 0x76: + mnemonic = "HALT!"; + break; case 0xCB: cbOp = _memoryBus.Read((ushort)(currentPc + 1));