From fbba7c25aed54efe1476278bf51ed805f483db70 Mon Sep 17 00:00:00 2001
From: Asnivor <coding@asnitech.co.uk>
Date: Mon, 11 Jun 2018 14:35:12 +0100
Subject: [PATCH] ZXHawk: New interrupt implementation

---
 .../SinclairSpectrum/Machine/CPUMonitor.cs    |   3 +
 .../SinclairSpectrum/Machine/SpectrumBase.cs  |  22 ++--
 .../Computers/SinclairSpectrum/Machine/ULA.cs | 117 +++++++++---------
 .../Machine/ZXSpectrum128K/ZX128.Screen.cs    |  26 ++--
 .../ZX128Plus2a.Screen.cs                     |  35 ++----
 .../Machine/ZXSpectrum48K/ZX48.Screen.cs      |  28 ++---
 6 files changed, 104 insertions(+), 127 deletions(-)

diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/CPUMonitor.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/CPUMonitor.cs
index 9b8a822c15..8838abe75e 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/CPUMonitor.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/CPUMonitor.cs
@@ -61,6 +61,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// </summary>
         public void ExecuteCycle()
         {
+            // simulate the ULA clock cycle before the CPU cycle
+            _machine.ULADevice.CycleClock(TotalExecutedCycles);
+
             // is the next CPU cycle causing a BUSRQ or IORQ?
             if (BUSRQ > 0)
             {
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
index 3b5eaa6da9..58753c45e5 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
@@ -147,6 +147,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// </summary>
         public virtual void ExecuteFrame(bool render, bool renderSound)
         {
+            ULADevice.FrameEnd = false;
+            ULADevice.ULACycleCounter = CurrentFrameCycle;
+
             InputRead = false;
             _render = render;
             _renderSound = renderSound;
@@ -164,15 +167,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
 
             PollInput();
 
-            while (CurrentFrameCycle < ULADevice.FrameLength)
+            for (;;)
             {
-                ULADevice.CheckForInterrupt(CurrentFrameCycle);
-
+                // run the CPU Monitor cycle
                 CPUMon.ExecuteCycle();
 
                 // cycle the tape device
                 if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
                     TapeDevice.TapeCycle();
+
+                // has frame end been reached?
+                if (ULADevice.FrameEnd)
+                    break;
             }
 
             OverFlow = (int)CurrentFrameCycle - ULADevice.FrameLength;
@@ -190,9 +196,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
                 AYDevice.EndFrame();
 
             FrameCount++;
-
-            // setup for next frame
-            ULADevice.ResetInterrupt();
                         
             if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
                 TapeDevice.EndFrame();
@@ -202,8 +205,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
             // is this a lag frame?
             Spectrum.IsLagFrame = !InputRead;
 
-            // FDC debug
-            
+            // FDC debug            
             if (UPDDiskDevice != null && UPDDiskDevice.writeDebug)
             {
                 // only write UPD log every second
@@ -222,7 +224,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// </summary>
         public virtual void HardReset()
         {
-            ULADevice.ResetInterrupt();
+            //ULADevice.ResetInterrupt();
             ROMPaged = 0;
             SpecialPagingMode = false;
             RAMPaged = 0;
@@ -274,7 +276,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// </summary>
         public virtual void SoftReset()
         {
-             ULADevice.ResetInterrupt();
+             //ULADevice.ResetInterrupt();
             ROMPaged = 0;
             SpecialPagingMode = false;
             RAMPaged = 0;
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULA.cs
index e875a09565..2e9ff98b8c 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULA.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULA.cs
@@ -89,14 +89,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         public int InterruptLength;
 
         /// <summary>
-        /// Arbitrary offset into the contention array (for memory ops)
-        /// </summary>
-        public int MemoryContentionOffset;
-
-        /// <summary>
-        /// Arbitrary offset into the contention array (for port ops)
-        /// </summary>
-        public int PortContentionOffset;
+        /// Contention offset
+        /// </summary> 
+        public int ContentionOffset;
 
         /// <summary>
         /// Arbitrary offset for render table generation
@@ -137,60 +132,58 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// </summary>
         protected bool InterruptRaised;
 
-        /// <summary>
-        /// Signs that the interrupt signal has been revoked
-        /// </summary>
-        protected bool InterruptRevoked;
+        public long ULACycleCounter;
+        public long LastULATick;
+        public bool FrameEnd;
 
         /// <summary>
-        /// Resets the interrupt - this should happen every frame in order to raise
-        /// the VBLANK interrupt in the proceding frame
-        /// </summary>
-        public virtual void ResetInterrupt()
-        {
-            InterruptRaised = false;
-            InterruptRevoked = false;
-        }
-
-        /// <summary>
-        /// Generates an interrupt in the current phase if needed
+        /// Cycles the ULA clock
+        /// Handles interrupt generation
         /// </summary>
         /// <param name="currentCycle"></param>
-        public virtual void CheckForInterrupt(long currentCycle)
+        public virtual void CycleClock(long totalCycles)
         {
-            if (InterruptRevoked)
+            // has more than one cycle past since this last ran
+            // (this can be true if contention has taken place)
+            var ticksToProcess = totalCycles - LastULATick;
+
+            // store the current cycle
+            LastULATick = totalCycles;
+            
+            // process the cycles past as well as the upcoming one
+            for (int i = 0; i < ticksToProcess; i++)
             {
-                // interrupt has already been handled
-                return;
-            }
+                ULACycleCounter++;
 
-            if (currentCycle <= InterruptStartTime)
-            {
-                // interrupt does not need to be raised yet
-                return;
-            }
-
-            if (currentCycle > InterruptStartTime + InterruptLength)
-            {
-                // interrupt should have already been raised and the cpu may or
-                // may not have caught it. The time has passed so revoke the signal
-                InterruptRevoked = true;
-                _machine.CPU.FlagI = false;
-                return;
-            }
-
-            if (InterruptRaised)
-            {
-                // INT is raised but not yet revoked
-                // CPU has NOT handled it yet
-                return;
-            }
-
-            // Raise the interrupt
-            InterruptRaised = true;
-            _machine.CPU.FlagI = true;
-
-            CalcFlashCounter();
+                if (InterruptRaised)
+                {
+                    // /INT pin is currently being held low
+                    if (ULACycleCounter < InterruptLength + InterruptStartTime)
+                    {
+                        // ULA should still hold the /INT pin low
+                        _machine.CPU.FlagI = true;
+                    }
+                    else
+                    {
+                        // its time (or past time) to stop holding the /INT pin low
+                        _machine.CPU.FlagI = false;
+                        InterruptRaised = false;
+                    }
+                }
+                else
+                {
+                    // interrupt is currently not raised
+                    if (ULACycleCounter == FrameLength + InterruptStartTime)
+                    {
+                        // time to raise the interrupt
+                        InterruptRaised = true;
+                        _machine.CPU.FlagI = true;
+                        FrameEnd = true;
+                        ULACycleCounter = InterruptStartTime;
+                        CalcFlashCounter();
+                    }
+                }
+            }            
         }
 
         /// <summary>
@@ -329,7 +322,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
             {
                 for (var t = 0; t < _ula.FrameCycleLength; t++)
                 {
-                    var tStateScreen = t + _ula.RenderTableOffset + _ula.InterruptStartTime;
+                    var tStateScreen = t + _ula.RenderTableOffset;// + _ula.InterruptStartTime;
 
                     if (tStateScreen < 0)
                         tStateScreen += _ula.FrameCycleLength;
@@ -460,7 +453,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
                 // calculate contention values
                 for (int t = 0; t < _ula.FrameCycleLength; t++)
                 {
-                    int shifted = t + _ula.RenderTableOffset + _ula.InterruptStartTime;
+                    int shifted = t + _ula.RenderTableOffset + _ula.ContentionOffset; // _ula.InterruptStartTime;
                     if (shifted < 0)
                         shifted += _ula.FrameCycleLength;
                     shifted %= _ula.FrameCycleLength;
@@ -747,7 +740,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// <returns></returns>
         public int GetContentionValue(int tstate)
         {
-            tstate += MemoryContentionOffset;
+            //tstate += MemoryContentionOffset;
             if (tstate >= FrameCycleLength)
                 tstate -= FrameCycleLength;
 
@@ -763,7 +756,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         /// <returns></returns>
         public int GetPortContentionValue(int tstate)
         {
-            tstate +=  PortContentionOffset;
+            //tstate +=  PortContentionOffset;
             if (tstate >= FrameCycleLength)
                 tstate -= FrameCycleLength;
 
@@ -994,8 +987,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
             ser.BeginSection("ULA");
             if (ScreenBuffer != null)
                 ser.Sync("ScreenBuffer", ref ScreenBuffer, false);
-            ser.Sync("FrameLength", ref FrameCycleLength);
-            ser.Sync("ClockSpeed", ref ClockSpeed);
             ser.Sync("BorderColor", ref BorderColor);
             ser.Sync("LastTState", ref LastTState);
             ser.Sync("flashOn", ref flashOn);
@@ -1010,6 +1001,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
             ser.Sync("flash", ref flash);
             ser.Sync("palPaper", ref palPaper);
             ser.Sync("palInk", ref palInk);
+
+            ser.Sync("LastULATick", ref LastULATick);
+            ser.Sync("ULACycleCounter", ref ULACycleCounter);
+            ser.Sync("FrameEnd", ref FrameEnd);
             ser.EndSection();
         }
 
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs
index 54a0fe058f..06b1d7d9ba 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs
@@ -13,32 +13,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         public Screen128(SpectrumBase machine)
 			: base(machine)
         {
+            // interrupt
+            InterruptStartTime = 3;
+            InterruptLength = 36;
+            // offsets
+            RenderTableOffset = 58;
+            ContentionOffset = 6;
+            FloatingBusOffset = 1;
             // timing
             ClockSpeed = 3546900;
             FrameCycleLength = 70908;
-            InterruptStartTime = 34;
-            InterruptLength = 36;
             ScanlineTime = 228;
-
-            MemoryContentionOffset = 6;
-            PortContentionOffset = 6;
-            RenderTableOffset = -4;
-            FloatingBusOffset = 1;
-
             BorderLeftTime = 24;
             BorderRightTime = 24;
-
             FirstPaperLine = 63;
             FirstPaperTState = 64;
-
-            Border4T = true;
-            Border4TStage = 2;
-
             // screen layout
+            Border4T = true;
+            Border4TStage = 2;            
             ScreenWidth = 256;
             ScreenHeight = 192;
-            BorderTopHeight = 55; // 48;
-            BorderBottomHeight = 56;
+            BorderTopHeight = 48; // 55; // 48;
+            BorderBottomHeight = 48; // 56;
             BorderLeftWidth = 48;
             BorderRightWidth = 48;
             ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Screen.cs
index 2611a7fd43..a95d06d8ce 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Screen.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Screen.cs
@@ -13,43 +13,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
         public Screen128Plus2a(SpectrumBase machine)
 			: base(machine)
         {
+            // interrupt
+            InterruptStartTime = 0;
+            InterruptLength = 32;            
+            // offsets
+            RenderTableOffset = 58;
+            ContentionOffset = 9;
+            FloatingBusOffset = 1;
             // timing
             ClockSpeed = 3546900;
             FrameCycleLength = 70908;
-            /*
-            InterruptStartTime = 31;
-            InterruptLength = 32;
             ScanlineTime = 228;
-
-            MemoryContentionOffset = 7;
-            PortContentionOffset = 7;
-            RenderTableOffset = 1;
-            FloatingBusOffset = 1;
-            */
-
-            InterruptStartTime = 33;
-            InterruptLength = 32;
-            ScanlineTime = 228;
-
-            MemoryContentionOffset = 6;
-            PortContentionOffset = 6;
-            RenderTableOffset = -2;
-            FloatingBusOffset = 1;
-
             BorderLeftTime = 24;
             BorderRightTime = 24;
-
             FirstPaperLine = 63;
             FirstPaperTState = 64;
-
+            // screen layout
             Border4T = true;
             Border4TStage = 2;
-
-            // screen layout
             ScreenWidth = 256;
             ScreenHeight = 192;
-            BorderTopHeight = 55;
-            BorderBottomHeight = 56;
+            BorderTopHeight = 48;// 55;
+            BorderBottomHeight = 48; // 56;
             BorderLeftWidth = 48;
             BorderRightWidth = 48;
             ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Screen.cs
index 0a4c236d86..d572f44e37 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Screen.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Screen.cs
@@ -13,32 +13,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
 		public Screen48(SpectrumBase machine)
 			: base(machine)
         {
-			// timing
-			ClockSpeed = 3500000;
-            FrameCycleLength = 69888;
-            InterruptStartTime = 33;
+            // interrupt
+            InterruptStartTime = 3;
             InterruptLength = 32;
+            // offsets
+            RenderTableOffset = 56;
+            ContentionOffset = 6;
+            FloatingBusOffset = 1;   
+            // timing
+            ClockSpeed = 3500000;
+            FrameCycleLength = 69888;
             ScanlineTime = 224;
-
-            MemoryContentionOffset = 6;
-            PortContentionOffset = 6;
-            RenderTableOffset = -9; // 2;
-            FloatingBusOffset = 1;
-
             BorderLeftTime = 24;
             BorderRightTime = 24;
-
             FirstPaperLine = 64;
             FirstPaperTState = 64;
-
+            // screen layout
             Border4T = true;
             Border4TStage = 0;
-
-            // screen layout
             ScreenWidth = 256;
             ScreenHeight = 192;
-            BorderTopHeight = 55;// 48;
-            BorderBottomHeight = 56;
+            BorderTopHeight = 48;// 55;// 48;
+            BorderBottomHeight = 48;// 56;
             BorderLeftWidth = 48;
             BorderRightWidth = 48;
             ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;