diff --git a/AppleWinExpress2008.vcproj b/AppleWinExpress2008.vcproj index 369aeecf..5fd2985e 100644 --- a/AppleWinExpress2008.vcproj +++ b/AppleWinExpress2008.vcproj @@ -757,6 +757,14 @@ RelativePath=".\source\Speech.h" > + + + + diff --git a/AppleWinExpress2019.vcxproj b/AppleWinExpress2019.vcxproj index 004429d6..d078c268 100644 --- a/AppleWinExpress2019.vcxproj +++ b/AppleWinExpress2019.vcxproj @@ -96,6 +96,7 @@ + @@ -184,6 +185,7 @@ Create Create + NotUsing diff --git a/AppleWinExpress2019.vcxproj.filters b/AppleWinExpress2019.vcxproj.filters index f6503852..8dc47688 100644 --- a/AppleWinExpress2019.vcxproj.filters +++ b/AppleWinExpress2019.vcxproj.filters @@ -199,6 +199,9 @@ Source Files\Disk + + Source Files\Emulator + @@ -483,6 +486,9 @@ Source Files\Emulator + + Source Files\Emulator + diff --git a/source/Applewin.cpp b/source/Applewin.cpp index 7449496b..96141144 100644 --- a/source/Applewin.cpp +++ b/source/Applewin.cpp @@ -53,6 +53,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifdef USE_SPEECH_API #include "Speech.h" #endif +#include "SynchronousEventManager.h" #include "Video.h" #include "RGBMonitor.h" #include "NTSC.h" @@ -112,6 +113,8 @@ int g_nMemoryClearType = MIP_FF_FF_00_00; // Note: -1 = random MIP in Memory.c CardManager g_CardMgr; IPropertySheet& sg_PropertySheet = * new CPropertySheet; +SynchronousEventManager g_SynchronousEventMgr; + HANDLE g_hCustomRomF8 = INVALID_HANDLE_VALUE; // Cmd-line specified custom F8 ROM at $F800..$FFFF static bool g_bCustomRomF8Failed = false; // Set if custom F8 ROM file failed HANDLE g_hCustomRom = INVALID_HANDLE_VALUE; // Cmd-line specified custom ROM at $C000..$FFFF(16KiB) or $D000..$FFFF(12KiB) @@ -1424,6 +1427,9 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int) DSUninit(); LogFileOutput("Main: DSUninit()\n"); + if (g_bRestart) + g_SynchronousEventMgr.Reset(); + if (g_bHookSystemKey) { UninitHookThread(); @@ -2214,8 +2220,6 @@ static void RepeatInitialization(void) g_cmdLine.bBoot = false; } } - - SetMouseCardInstalled( g_CardMgr.IsMouseCardInstalled() ); } static void Shutdown(void) diff --git a/source/Applewin.h b/source/Applewin.h index 49c446f1..34c8f1d3 100644 --- a/source/Applewin.h +++ b/source/Applewin.h @@ -53,6 +53,7 @@ extern bool g_bDisableDirectSoundMockingboard; // Cmd line switch: don't i extern int g_nMemoryClearType; // Cmd line switch: use specific MIP (Memory Initialization Pattern) extern class CardManager g_CardMgr; +extern class SynchronousEventManager g_SynchronousEventMgr; extern HANDLE g_hCustomRomF8; // INVALID_HANDLE_VALUE if no custom F8 rom extern HANDLE g_hCustomRom; // INVALID_HANDLE_VALUE if no custom rom diff --git a/source/CPU.cpp b/source/CPU.cpp index a7361f7d..957b4fea 100644 --- a/source/CPU.cpp +++ b/source/CPU.cpp @@ -96,6 +96,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifdef USE_SPEECH_API #include "Speech.h" #endif +#include "SynchronousEventManager.h" #include "Video.h" #include "NTSC.h" #include "Log.h" @@ -149,8 +150,6 @@ static volatile BOOL g_bNmiFlank = FALSE; // Positive going flank on NMI line static bool g_irqDefer1Opcode = false; -static bool g_isMouseCardInstalled = false; - // static eCpuType g_MainCPU = CPU_65C02; @@ -206,11 +205,6 @@ void ResetCyclesExecutedForDebugger(void) g_nCyclesExecuted = 0; } -void SetMouseCardInstalled(bool installed) -{ - g_isMouseCardInstalled = installed; -} - // #include "CPU/cpu_general.inl" @@ -403,8 +397,13 @@ static __forceinline void NMI(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, #endif } -// NB. No need to save to save-state, as IRQ() follows CheckInterruptSources(), and IRQ() always sets it to false. -static bool g_irqOnLastOpcodeCycle = false; +static __forceinline void CheckSynchronousInterruptSources(UINT cycles, ULONG uExecutedCycles) +{ + g_SynchronousEventMgr.Update(cycles, uExecutedCycles); +} + +// NB. No need to save to save-state, as IRQ() follows CheckSynchronousInterruptSources(), and IRQ() always sets it to false. +bool g_irqOnLastOpcodeCycle = false; static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, BOOL& flagv, BOOL& flagz) { @@ -435,39 +434,12 @@ static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, #if defined(_DEBUG) && LOG_IRQ_TAKEN_AND_RTI LogOutput("IRQ\n"); #endif + CheckSynchronousInterruptSources(7, uExecutedCycles); } g_irqOnLastOpcodeCycle = false; } -const int IRQ_CHECK_OPCODE_FULL_SPEED = 40; // ~128 cycles (assume 3 cycles per opcode) -static int g_fullSpeedOpcodeCount = IRQ_CHECK_OPCODE_FULL_SPEED; - -static __forceinline void CheckInterruptSources(ULONG uExecutedCycles, const bool bVideoUpdate) -{ - if (!bVideoUpdate) - { - g_fullSpeedOpcodeCount--; - if (g_fullSpeedOpcodeCount >= 0) - return; - g_fullSpeedOpcodeCount = IRQ_CHECK_OPCODE_FULL_SPEED; - } - - if (MB_UpdateCycles(uExecutedCycles)) - g_irqOnLastOpcodeCycle = true; - - if (g_isMouseCardInstalled) - g_CardMgr.GetMouseCard()->SetVBlank( !VideoGetVblBar(uExecutedCycles) ); -} - -// GH#608: IRQ needs to occur within 17 cycles (6 opcodes) of configuring the timer interrupt -void CpuAdjustIrqCheck(UINT uCyclesUntilInterrupt) -{ - const UINT opcodesUntilInterrupt = uCyclesUntilInterrupt/3; // assume 3 cycles per opcode - if (g_bFullSpeed && opcodesUntilInterrupt < IRQ_CHECK_OPCODE_FULL_SPEED) - g_fullSpeedOpcodeCount = opcodesUntilInterrupt; -} - //=========================================================================== #define READ _READ @@ -506,7 +478,7 @@ void CpuAdjustIrqCheck(UINT uCyclesUntilInterrupt) static DWORD InternalCpuExecute(const DWORD uTotalCycles, const bool bVideoUpdate) { - if (g_nAppMode == MODE_RUNNING) + if (g_nAppMode == MODE_RUNNING || g_nAppMode == MODE_BENCHMARK) { if (GetMainCpu() == CPU_6502) return Cpu6502(uTotalCycles, bVideoUpdate); // Apple ][, ][+, //e, Clones diff --git a/source/CPU.h b/source/CPU.h index 70056c6c..c8bab5c5 100644 --- a/source/CPU.h +++ b/source/CPU.h @@ -14,7 +14,6 @@ struct regsrec extern regsrec regs; extern unsigned __int64 g_nCumulativeCycles; -void CpuAdjustIrqCheck(UINT uCyclesUntilInterrupt); void CpuDestroy (); void CpuCalcCycles(ULONG nExecutedCycles); DWORD CpuExecute(const DWORD uCycles, const bool bVideoUpdate); @@ -45,4 +44,3 @@ void SetActiveCpu(eCpuType cpu); bool Is6502InterruptEnabled(void); void ResetCyclesExecutedForDebugger(void); -void SetMouseCardInstalled(bool installed); diff --git a/source/CPU/cpu6502.h b/source/CPU/cpu6502.h index 4f9d381e..d7d036ff 100644 --- a/source/CPU/cpu6502.h +++ b/source/CPU/cpu6502.h @@ -317,7 +317,7 @@ static DWORD Cpu6502(DWORD uTotalCycles, const bool bVideoUpdate) } } - CheckInterruptSources(uExecutedCycles, bVideoUpdate); + CheckSynchronousInterruptSources(uExecutedCycles - uPreviousCycles, uExecutedCycles); NMI(uExecutedCycles, flagc, flagn, flagv, flagz); IRQ(uExecutedCycles, flagc, flagn, flagv, flagz); diff --git a/source/CPU/cpu65C02.h b/source/CPU/cpu65C02.h index 5d21d58b..425a0b6c 100644 --- a/source/CPU/cpu65C02.h +++ b/source/CPU/cpu65C02.h @@ -317,7 +317,7 @@ static DWORD Cpu65C02(DWORD uTotalCycles, const bool bVideoUpdate) } } - CheckInterruptSources(uExecutedCycles, bVideoUpdate); + CheckSynchronousInterruptSources(uExecutedCycles - uPreviousCycles, uExecutedCycles); NMI(uExecutedCycles, flagc, flagn, flagv, flagz); IRQ(uExecutedCycles, flagc, flagn, flagv, flagz); diff --git a/source/Common.h b/source/Common.h index 4f1e0ea5..89daf20f 100644 --- a/source/Common.h +++ b/source/Common.h @@ -24,6 +24,7 @@ enum AppMode_e , MODE_RUNNING // 6502 is running at normal/full speed (Debugger breakpoints may or may not be active) , MODE_DEBUG // 6502 is paused , MODE_STEPPING // 6502 is running at normal/full speed (Debugger breakpoints always active) + , MODE_BENCHMARK }; #define SPEED_MIN 0 diff --git a/source/Frame.cpp b/source/Frame.cpp index 59b8c76e..95abc0e7 100644 --- a/source/Frame.cpp +++ b/source/Frame.cpp @@ -1911,10 +1911,11 @@ LRESULT CALLBACK FrameWndProc ( case WM_USER_BENCHMARK: { UpdateWindow(window); ResetMachineState(); - g_nAppMode = MODE_LOGO; DrawStatusArea((HDC)0,DRAW_TITLE); HCURSOR oldcursor = SetCursor(LoadCursor(0,IDC_WAIT)); + g_nAppMode = MODE_BENCHMARK; VideoBenchmark(); + g_nAppMode = MODE_LOGO; ResetMachineState(); SetCursor(oldcursor); break; diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index 38d7780b..e405a2d5 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -86,6 +86,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "Memory.h" #include "Mockingboard.h" #include "SoundCore.h" +#include "SynchronousEventManager.h" #include "YamlHelper.h" #include "Riff.h" @@ -115,10 +116,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #define SY6522B_Offset 0x80 #define SSI263_Offset 0x40 -#define Phasor_SY6522A_CS 4 -#define Phasor_SY6522B_CS 7 -#define Phasor_SY6522A_Offset (1<m_active) + g_SynchronousEventMgr.Remove(id); + + pSyncEvent->SetCycles(timerLatch + kExtraTimerCycles + opcodeCycleAdjust); + g_SynchronousEventMgr.Insert(pSyncEvent); + + // It doesn't matter if this overflows (ie. >0xFFFF), since on completion of current opcode it'll be corrected + return (USHORT) (timerLatch + opcodeCycleAdjust); +} + static void UpdateIFR(SY6522_AY8910* pMB, BYTE clr_ifr, BYTE set_ifr=0) { // Need critical section to avoid data-race: main thread & SSI263Thread can both access IFR @@ -425,34 +521,29 @@ static void SY6522_Write(BYTE nDevice, BYTE nReg, BYTE nValue) pMB->sy6522.TIMER1_LATCH.l = nValue; break; case 0x05: // TIMER1H_COUNTER - /* Initiates timer1 & clears time-out of timer1 */ - - // Clear Timer Interrupt Flag. - UpdateIFR(pMB, IxR_TIMER1); - - pMB->sy6522.TIMER1_LATCH.h = nValue; - pMB->bLoadT1C = true; - - StartTimer1(pMB); - CpuAdjustIrqCheck(pMB->sy6522.TIMER1_LATCH.w); // Sync IRQ check timeout with 6522 counter underflow - GH#608 + { + UpdateIFR(pMB, IxR_TIMER1); // Clear Timer1 Interrupt Flag + pMB->sy6522.TIMER1_LATCH.h = nValue; + const UINT id = nDevice*kNumTimersPer6522+0; // TIMER1 + pMB->sy6522.TIMER1_COUNTER.w = SetTimerSyncEvent(id, nReg, pMB->sy6522.TIMER1_LATCH.w); + StartTimer1(pMB); + } break; case 0x07: // TIMER1H_LATCH - // Clear Timer1 Interrupt Flag. - UpdateIFR(pMB, IxR_TIMER1); + UpdateIFR(pMB, IxR_TIMER1); // Clear Timer1 Interrupt Flag pMB->sy6522.TIMER1_LATCH.h = nValue; break; case 0x08: // TIMER2L pMB->sy6522.TIMER2_LATCH.l = nValue; break; case 0x09: // TIMER2H - // Clear Timer2 Interrupt Flag. - UpdateIFR(pMB, IxR_TIMER2); - - pMB->sy6522.TIMER2_LATCH.h = nValue; // NB. Real 6522 doesn't have TIMER2_LATCH.h - pMB->sy6522.TIMER2_COUNTER.w = pMB->sy6522.TIMER2_LATCH.w; - - StartTimer2(pMB); - CpuAdjustIrqCheck(pMB->sy6522.TIMER2_LATCH.w); // Sync IRQ check timeout with 6522 counter underflow - GH#608 + { + UpdateIFR(pMB, IxR_TIMER2); // Clear Timer2 Interrupt Flag + pMB->sy6522.TIMER2_LATCH.h = nValue; // NB. Real 6522 doesn't have TIMER2_LATCH.h + const UINT id = nDevice*kNumTimersPer6522+1; // TIMER2 + pMB->sy6522.TIMER2_COUNTER.w = SetTimerSyncEvent(id, nReg, pMB->sy6522.TIMER2_LATCH.w); + StartTimer2(pMB); + } break; case 0x0a: // SERIAL_SHIFT break; @@ -1509,6 +1600,11 @@ void MB_Initialize() InitializeCriticalSection(&g_CriticalSection); g_bCritSectionValid = true; + + for (int id=0; idm_active) + g_SynchronousEventMgr.Remove(id); + } + // Not these, as they don't change on a CTRL+RESET or power-cycle: // g_bMBAvailable = false; // g_SoundcardType = CT_Empty; // Don't uncomment, else _ASSERT will fire in MB_Read() after an F2->MB_Reset() @@ -1601,8 +1709,7 @@ void MB_Reset() // CTRL+RESET or power-cycle static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { - if (g_bFullSpeed) - MB_UpdateCycles(nExecutedCycles); + MB_UpdateCycles(nExecutedCycles); #ifdef _DEBUG if(!IS_APPLE2 && MemCheckINTCXROM()) @@ -1665,8 +1772,7 @@ static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULO static BYTE __stdcall MB_Write(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { - if (g_bFullSpeed) - MB_UpdateCycles(nExecutedCycles); + MB_UpdateCycles(nExecutedCycles); #ifdef _DEBUG if(!IS_APPLE2 && MemCheckINTCXROM()) @@ -1928,7 +2034,7 @@ void MB_PeriodicUpdate(UINT executedCycles) //----------------------------------------------------------------------------- -static bool CheckTimerUnderflowAndIrq(USHORT& timerCounter, int& timerIrqDelay, const USHORT nClocks) +static bool CheckTimerUnderflow(USHORT& timerCounter, int& timerIrqDelay, const USHORT nClocks) { if (nClocks == 0) return false; @@ -1960,96 +2066,83 @@ static bool CheckTimerUnderflowAndIrq(USHORT& timerCounter, int& timerIrqDelay, } // Called by: -// . CpuExecute() every ~1000 @ 1MHz -// . CheckInterruptSources() every opcode (or every 40 opcodes at full-speed) -// . MB_Read() / MB_Write() (only for full-speed) -bool MB_UpdateCycles(ULONG uExecutedCycles) +// . CpuExecute() every ~1000 cycles @ 1MHz +// . MB_SyncEventCallback() on a TIMER1/2 underflow +// . MB_Read() / MB_Write() (for both normal & full-speed) +void MB_UpdateCycles(ULONG uExecutedCycles) { if (g_SoundcardType == CT_Empty) - return false; + return; CpuCalcCycles(uExecutedCycles); UINT64 uCycles = g_nCumulativeCycles - g_uLastCumulativeCycles; + _ASSERT(uCycles >= 0); if (uCycles == 0) - return false; // Likely when called from CpuExecute() - - const bool isOpcode = (uCycles <= 7); // todo: better to pass in a flag? + return; g_uLastCumulativeCycles = g_nCumulativeCycles; - _ASSERT(uCycles < 0x10000); - USHORT nClocks = (USHORT) uCycles; + _ASSERT(uCycles < 0x10000 || g_nAppMode == MODE_BENCHMARK); + USHORT nClocks = (USHORT)uCycles; - bool bIrqOnLastOpcodeCycle = false; - - for (int i=0; isy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks); + const bool bTimer2Underflow = CheckTimerUnderflow(pMB->sy6522.TIMER2_COUNTER.w, pMB->sy6522.timer2IrqDelay, nClocks); - if (isOpcode) + if (pMB->bTimer1Active && bTimer1Underflow) { - bTimer1Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks-1); - bTimer1IrqOnLastCycle = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, 1); - bTimer1Irq = bTimer1Irq || bTimer1IrqOnLastCycle; - } - else - { - bTimer1Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks); - } - - const bool bTimer2Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER2_COUNTER.w, pMB->sy6522.timer2IrqDelay, nClocks); - - if (pMB->bTimer1Active && bTimer1Irq) - { - UpdateIFR(pMB, 0, IxR_TIMER1); - bIrqOnLastOpcodeCycle = bTimer1IrqOnLastCycle; - - MB_Update(); - - if ((pMB->sy6522.ACR & RUNMODE) == RM_ONESHOT) + pMB->sy6522.TIMER1_COUNTER.w += pMB->sy6522.TIMER1_LATCH.w; // GH#651: account for underflowed cycles too + pMB->sy6522.TIMER1_COUNTER.w += kExtraTimerCycles; // GH#652: account for extra 2 cycles + // EG. T1C=0xFFFE, T1L=0x0001 + // . T1C += T1L = 0xFFFF + // . T1C += 2 = 0x0001 + if (pMB->sy6522.TIMER1_COUNTER.w > pMB->sy6522.TIMER1_LATCH.w) { - // One-shot mode - // - Phasor's playback code uses one-shot mode - StopTimer1(pMB); + if (pMB->sy6522.TIMER1_LATCH.w) + pMB->sy6522.TIMER1_COUNTER.w %= pMB->sy6522.TIMER1_LATCH.w; // Only occurs if LATCH.w<0x0007 (# cycles for longest opcode) + else + pMB->sy6522.TIMER1_COUNTER.w = 0; } - else - { - // Free-running mode - // - Ultima4/5 change ACCESS_TIMER1 after a couple of IRQs into tune - pMB->sy6522.TIMER1_COUNTER.w += pMB->sy6522.TIMER1_LATCH.w; // GH#651: account for underflowed cycles too - pMB->sy6522.TIMER1_COUNTER.w += 2; // GH#652: account for extra 2 cycles (Rockwell, Fig.16: period=N+2cycles) - // EG. T1C=0xFFFE, T1L=0x0001 - // . T1C += T1L = 0xFFFF - // . T1C += 2 = 0x0001 - if (pMB->sy6522.TIMER1_COUNTER.w > pMB->sy6522.TIMER1_LATCH.w) - { - if (pMB->sy6522.TIMER1_LATCH.w) - pMB->sy6522.TIMER1_COUNTER.w %= pMB->sy6522.TIMER1_LATCH.w; // Only occurs if LATCH.w<0x0007 (# cycles for longest opcode) - else - pMB->sy6522.TIMER1_COUNTER.w = 0; - } - StartTimer1(pMB); - } - } - - if (pMB->bLoadT1C) - { - pMB->bLoadT1C = false; - pMB->sy6522.TIMER1_COUNTER.w = pMB->sy6522.TIMER1_LATCH.w; - } - - if (pMB->bTimer2Active && bTimer2Irq) - { - UpdateIFR(pMB, 0, IxR_TIMER2); - - // TIMER2 only runs in one-shot mode - StopTimer2(pMB); } } +} - return bIrqOnLastOpcodeCycle; +//----------------------------------------------------------------------------- + +static int MB_SyncEventCallback(int id, int /*cycles*/, ULONG uExecutedCycles) +{ + SY6522_AY8910* pMB = &g_MB[id / kNumTimersPer6522]; + + if ((id & 1) == 0) + { + _ASSERT(pMB->bTimer1Active); + MB_Update(); + + UpdateIFR(pMB, 0, IxR_TIMER1); + + if ((pMB->sy6522.ACR & RUNMODE) == RM_ONESHOT) + { + // One-shot mode + // - Phasor's playback code uses one-shot mode + StopTimer1(pMB); + return 0; // Don't repeat event + } + + MB_UpdateCycles(uExecutedCycles); + + StartTimer1(pMB); + return pMB->sy6522.TIMER1_COUNTER.w + kExtraTimerCycles; + } + else + { + _ASSERT(pMB->bTimer2Active); + UpdateIFR(pMB, 0, IxR_TIMER2); + + StopTimer2(pMB); // TIMER2 only runs in one-shot mode + return 0; // Don't repeat event + } } //----------------------------------------------------------------------------- @@ -2350,6 +2443,21 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) StartTimer1(pMB); // Attempt to start timer } + if (pMB->bTimer1Active) + { + const UINT id = nDeviceNum*kNumTimersPer6522+0; // TIMER1 + SyncEvent* pSyncEvent = g_syncEvent[id]; + pSyncEvent->SetCycles(pMB->sy6522.TIMER1_COUNTER.w + kExtraTimerCycles); // NB. use COUNTER, not LATCH + g_SynchronousEventMgr.Insert(pSyncEvent); + } + if (pMB->bTimer2Active) + { + const UINT id = nDeviceNum*kNumTimersPer6522+1; // TIMER2 + SyncEvent* pSyncEvent = g_syncEvent[id]; + pSyncEvent->SetCycles(pMB->sy6522.TIMER2_COUNTER.w + kExtraTimerCycles); // NB. use COUNTER, not LATCH + g_SynchronousEventMgr.Insert(pSyncEvent); + } + // FIXME: currently only support a single speech chip // NB. g_bVotraxPhoneme is never true, as the phoneme playback completes in SSI263Thread() before this point in the save-state. // NB. SpeechChip.DurationPhoneme will mostly be non-zero during speech playback, as this is the SSI263 register, not whether the phonene is active. @@ -2487,6 +2595,21 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version StartTimer1(pMB); // Attempt to start timer } + if (pMB->bTimer1Active) + { + const UINT id = nDeviceNum*kNumTimersPer6522+0; // TIMER1 + SyncEvent* pSyncEvent = g_syncEvent[id]; + pSyncEvent->SetCycles(pMB->sy6522.TIMER1_COUNTER.w + kExtraTimerCycles); // NB. use COUNTER, not LATCH + g_SynchronousEventMgr.Insert(pSyncEvent); + } + if (pMB->bTimer2Active) + { + const UINT id = nDeviceNum*kNumTimersPer6522+1; // TIMER2 + SyncEvent* pSyncEvent = g_syncEvent[id]; + pSyncEvent->SetCycles(pMB->sy6522.TIMER2_COUNTER.w + kExtraTimerCycles); // NB. use COUNTER, not LATCH + g_SynchronousEventMgr.Insert(pSyncEvent); + } + // FIXME: currently only support a single speech chip if (pMB->SpeechChip.DurationPhoneme || g_bVotraxPhoneme) { diff --git a/source/Mockingboard.h b/source/Mockingboard.h index 49859f58..66d2ebba 100644 --- a/source/Mockingboard.h +++ b/source/Mockingboard.h @@ -16,7 +16,7 @@ void MB_CheckCumulativeCycles(); // DEBUG void MB_SetCumulativeCycles(); void MB_PeriodicUpdate(UINT executedCycles); void MB_CheckIRQ(); -bool MB_UpdateCycles(ULONG uExecutedCycles); +void MB_UpdateCycles(ULONG uExecutedCycles); SS_CARDTYPE MB_GetSoundcardType(); bool MB_IsActive(); DWORD MB_GetVolume(); diff --git a/source/MouseInterface.cpp b/source/MouseInterface.cpp index 3f15dda2..ab7faf16 100644 --- a/source/MouseInterface.cpp +++ b/source/MouseInterface.cpp @@ -44,11 +44,14 @@ Etc. #include "SaveState_Structs_common.h" #include "Common.h" +#include "AppleWin.h" // g_SynchronousEventMgr +#include "CardManager.h" #include "CPU.h" #include "Frame.h" // FrameSetCursorPosByMousePos() #include "Log.h" #include "Memory.h" #include "MouseInterface.h" +#include "NTSC.h" // NTSC_GetCyclesUntilVBlank() #include "YamlHelper.h" #include "../resource/resource.h" @@ -132,7 +135,8 @@ void M6821_Listener_A( void* objTo, BYTE byData ) CMouseInterface::CMouseInterface(UINT slot) : Card(CT_MouseInterface), m_uSlot(slot), - m_pSlotRom(NULL) + m_pSlotRom(NULL), + m_syncEvent(slot, 0, SyncEventCallback) // use slot# as "unique" id for MouseInterfaces { m_6821.SetListenerB( this, M6821_Listener_B ); m_6821.SetListenerA( this, M6821_Listener_A ); @@ -145,6 +149,9 @@ CMouseInterface::CMouseInterface(UINT slot) : CMouseInterface::~CMouseInterface() { delete [] m_pSlotRom; + + if (m_syncEvent.m_active) + g_SynchronousEventMgr.Remove(m_syncEvent.m_id); } //=========================================================================== @@ -184,6 +191,10 @@ void CMouseInterface::Initialize(LPBYTE pCxRomPeripheral, UINT uSlot) _ASSERT(m_uSlot == uSlot); SetSlotRom(); // Pre: m_bActive == true RegisterIoHandler(uSlot, &CMouseInterface::IORead, &CMouseInterface::IOWrite, NULL, NULL, this, NULL); + + if (m_syncEvent.m_active) g_SynchronousEventMgr.Remove(m_syncEvent.m_id); + m_syncEvent.m_cyclesRemaining = NTSC_GetCyclesUntilVBlank(0); + g_SynchronousEventMgr.Insert(&m_syncEvent); } #if 0 @@ -221,6 +232,8 @@ void CMouseInterface::Reset() Clear(); memset( m_byBuff, 0, sizeof( m_byBuff ) ); SetSlotRom(); + + // NB. Leave the syncEvent in the list - otherwise nothing else will re-add it! } void CMouseInterface::SetSlotRom() @@ -475,16 +488,10 @@ void CMouseInterface::OnMouseEvent(bool bEventVBL) } } -void CMouseInterface::SetVBlank(bool bVBL) +int CMouseInterface::SyncEventCallback(int id, int cycles, ULONG /*uExecutedCycles*/) { -// _ASSERT(m_bActive); // Only called from CheckInterruptSources() - - if ( m_bVBL != bVBL ) - { - m_bVBL = bVBL; - if ( m_bVBL ) // Rising edge - OnMouseEvent(true); - } + g_CardMgr.GetMouseCard()->OnMouseEvent(true); + return NTSC_GetCyclesUntilVBlank(cycles); } void CMouseInterface::Clear() diff --git a/source/MouseInterface.h b/source/MouseInterface.h index ee16975e..1b97aaf3 100644 --- a/source/MouseInterface.h +++ b/source/MouseInterface.h @@ -1,6 +1,7 @@ #include "6821.h" #include "Common.h" #include "Card.h" +#include "SynchronousEventManager.h" class CMouseInterface : public Card { @@ -23,7 +24,6 @@ public: bool IsEnabled() { return m_bEnabled; } // NB. m_bEnabled == true implies that m_bActive == true bool IsActiveAndEnabled() { return /*IsActive() &&*/ IsEnabled(); } // todo: just use IsEnabled() void SetEnabled(bool bEnabled) { m_bEnabled = bEnabled; } - void SetVBlank(bool bVBL); void GetXY(int& iX, int& iMinX, int& iMaxX, int& iY, int& iMinY, int& iMaxY) { iX = m_iX; @@ -62,6 +62,8 @@ protected: void SetClampX(int iMinX, int iMaxX); void SetClampY(int iMinY, int iMaxY); + static int SyncEventCallback(int id, int cycles, ULONG uExecutedCycles); + void SaveSnapshotMC6821(class YamlSaveHelper& yamlSaveHelper, std::string key); void LoadSnapshotMC6821(class YamlLoadHelper& yamlLoadHelper, std::string key); @@ -102,6 +104,8 @@ protected: bool m_bEnabled; // Windows' mouse events get passed to Apple]['s mouse h/w (m_bEnabled == true implies that m_bActive == true) LPBYTE m_pSlotRom; UINT m_uSlot; + + SyncEvent m_syncEvent; }; namespace DIMouse diff --git a/source/NTSC.cpp b/source/NTSC.cpp index c12d3d96..d066bcb6 100644 --- a/source/NTSC.cpp +++ b/source/NTSC.cpp @@ -2609,6 +2609,24 @@ UINT NTSC_GetVideoLines(void) return (GetVideoRefreshRate() == VR_50HZ) ? VIDEO_SCANNER_MAX_VERT_PAL : VIDEO_SCANNER_MAX_VERT; } +// Get # cycles until rising Vbl edge: !VBl -> VBl at (0,192) +// . NB. Called from CMouseInterface::SyncEventCallback(), which occurs *before* NTSC_VideoUpdateCycles() +// therefore g_nVideoClockVert/Horz will be behind, so correct 'cycleCurrentPos' by adding 'cycles'. +UINT NTSC_GetCyclesUntilVBlank(int cycles) +{ + const UINT cyclesPerFrames = NTSC_GetCyclesPerFrame(); + + if (g_bFullSpeed) + return cyclesPerFrames; // g_nVideoClockVert/Horz not correct & accuracy isn't important: so just wait a frame's worth of cycles + + const UINT cycleVBl = VIDEO_SCANNER_Y_DISPLAY * VIDEO_SCANNER_MAX_HORZ; + const UINT cycleCurrentPos = (g_nVideoClockVert * VIDEO_SCANNER_MAX_HORZ + g_nVideoClockHorz + cycles) % cyclesPerFrames; + + return (cycleCurrentPos < cycleVBl) ? + (cycleVBl - cycleCurrentPos) : + (cyclesPerFrames - cycleCurrentPos + cycleVBl); +} + bool NTSC_IsVisible(void) { return (g_nVideoClockVert < VIDEO_SCANNER_Y_DISPLAY) && (g_nVideoClockHorz >= VIDEO_SCANNER_HORZ_START); diff --git a/source/NTSC.h b/source/NTSC.h index e3822541..a5a61440 100644 --- a/source/NTSC.h +++ b/source/NTSC.h @@ -23,4 +23,5 @@ UINT NTSC_GetCyclesPerFrame(void); UINT NTSC_GetCyclesPerLine(void); UINT NTSC_GetVideoLines(void); + UINT NTSC_GetCyclesUntilVBlank(int cycles); bool NTSC_IsVisible(void); diff --git a/source/SaveState.cpp b/source/SaveState.cpp index 87aa3dbc..e2d88170 100644 --- a/source/SaveState.cpp +++ b/source/SaveState.cpp @@ -479,7 +479,6 @@ static void Snapshot_LoadState_v2(void) MemUpdatePaging(TRUE); - SetMouseCardInstalled( g_CardMgr.IsMouseCardInstalled() ); DebugReset(); if (g_nAppMode == MODE_DEBUG) DebugDisplay(TRUE); diff --git a/source/SynchronousEventManager.cpp b/source/SynchronousEventManager.cpp new file mode 100644 index 00000000..02984738 --- /dev/null +++ b/source/SynchronousEventManager.cpp @@ -0,0 +1,168 @@ +/* +AppleWin : An Apple //e emulator for Windows + +Copyright (C) 1994-1996, Michael O'Brien +Copyright (C) 1999-2001, Oliver Schmidt +Copyright (C) 2002-2005, Tom Charlesworth +Copyright (C) 2006-2020, Tom Charlesworth, Michael Pohoreski, Nick Westgate + +AppleWin is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +AppleWin is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AppleWin; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* Description: Synchronous Event Manager + * + * This manager class maintains a linked-list of ordered timer-based event, + * where only the head of the list needs updating after every opcode. + * + * The Nth event in the list will expire in: event[1] + ... + event[N] cycles time. + * (So each event has a cycle delta expiry time relative to the previous event.) + * + * A synchronous event is used for a deterministic event that will occur in N cycles' time, + * eg. 6522 timer & Mousecard VBlank. (As opposed to async events, like SSC Rx/Tx interrupts.) + * + * Events that are active in the list can be removed before they expire, + * eg. 6522 timer when the interval changes. + * + * Author: Various + * + */ + +#include "StdAfx.h" + +#include "AppleWin.h" +#include "SynchronousEventManager.h" + +void SynchronousEventManager::Insert(SyncEvent* pNewEvent) +{ + pNewEvent->m_active = true; // add always succeeds + + if (!m_syncEventHead) + { + m_syncEventHead = pNewEvent; + return; + } + + // walk list to find where to insert new event + + SyncEvent* pPrevEvent = NULL; + SyncEvent* pCurrEvent = m_syncEventHead; + int newEventExtraCycles = pNewEvent->m_cyclesRemaining; + + while (pCurrEvent) + { + if (newEventExtraCycles >= pCurrEvent->m_cyclesRemaining) + { + newEventExtraCycles -= pCurrEvent->m_cyclesRemaining; + + pPrevEvent = pCurrEvent; + pCurrEvent = pCurrEvent->m_next; + + if (!pCurrEvent) // end of list + { + pPrevEvent->m_next = pNewEvent; + pNewEvent->m_cyclesRemaining = newEventExtraCycles; + } + + continue; + } + + // insert new event + if (!pPrevEvent) + m_syncEventHead = pNewEvent; + else + pPrevEvent->m_next = pNewEvent; + pNewEvent->m_next = pCurrEvent; + + pNewEvent->m_cyclesRemaining = newEventExtraCycles; + + // update cycles for next event + if (pCurrEvent) + { + pCurrEvent->m_cyclesRemaining -= newEventExtraCycles; + _ASSERT(pCurrEvent->m_cyclesRemaining >= 0); + } + + return; + } +} + +bool SynchronousEventManager::Remove(int id) +{ + SyncEvent* pPrevEvent = NULL; + SyncEvent* pCurrEvent = m_syncEventHead; + + while (pCurrEvent) + { + if (pCurrEvent->m_id != id) + { + pPrevEvent = pCurrEvent; + pCurrEvent = pCurrEvent->m_next; + continue; + } + + // remove event + if (!pPrevEvent) + m_syncEventHead = pCurrEvent->m_next; + else + pPrevEvent->m_next = pCurrEvent->m_next; + + int oldEventExtraCycles = pCurrEvent->m_cyclesRemaining; + pPrevEvent = pCurrEvent; + pCurrEvent = pCurrEvent->m_next; + + pPrevEvent->m_active = false; + pPrevEvent->m_next = NULL; + + // update cycles for next event + if (pCurrEvent) + pCurrEvent->m_cyclesRemaining += oldEventExtraCycles; + + return true; + } + + _ASSERT(0); + return false; +} + +extern bool g_irqOnLastOpcodeCycle; + +void SynchronousEventManager::Update(int cycles, ULONG uExecutedCycles) +{ + SyncEvent* pCurrEvent = m_syncEventHead; + + if (!pCurrEvent) + return; + + pCurrEvent->m_cyclesRemaining -= cycles; + if (pCurrEvent->m_cyclesRemaining <= 0) + { + if (pCurrEvent->m_cyclesRemaining == 0) + g_irqOnLastOpcodeCycle = true; // IRQ occurs on last cycle of opcode + + int cyclesUnderflowed = -pCurrEvent->m_cyclesRemaining; + + pCurrEvent->m_cyclesRemaining = pCurrEvent->m_callback(pCurrEvent->m_id, cycles, uExecutedCycles); + m_syncEventHead = pCurrEvent->m_next; // unlink this event + + pCurrEvent->m_active = false; + pCurrEvent->m_next = NULL; + + // Always Update even if cyclesUnderflowed=0, as next event may have cycleRemaining=0 (ie. the 2 events fire at the same time) + Update(cyclesUnderflowed, uExecutedCycles); // update (potential) next event with underflow cycles + + if (pCurrEvent->m_cyclesRemaining) + Insert(pCurrEvent); // re-add event + } +} diff --git a/source/SynchronousEventManager.h b/source/SynchronousEventManager.h new file mode 100644 index 00000000..d0b9f621 --- /dev/null +++ b/source/SynchronousEventManager.h @@ -0,0 +1,50 @@ +#pragma once + +class SyncEvent; + +class SynchronousEventManager +{ +public: + SynchronousEventManager() : m_syncEventHead(NULL) + {} + ~SynchronousEventManager(){} + + SyncEvent* GetHead(void) { return m_syncEventHead; } + void SetHead(SyncEvent* head) { m_syncEventHead = head; } + + void Insert(SyncEvent* pNewEvent); + bool Remove(int id); + void Update(int cycles, ULONG uExecutedCycles); + void Reset(void) { m_syncEventHead = NULL; } + +private: + SyncEvent* m_syncEventHead; +}; + +// + +typedef int (*syncEventCB)(int id, int cycles, ULONG uExecutedCycles); + +class SyncEvent +{ +public: + SyncEvent(int id, int initCycles, syncEventCB callback) + : m_id(id), + m_cyclesRemaining(initCycles), + m_active(false), + m_callback(callback), + m_next(NULL) + {} + ~SyncEvent(){} + + void SetCycles(int cycles) + { + m_cyclesRemaining = cycles; + } + + int m_id; + int m_cyclesRemaining; + bool m_active; + syncEventCB m_callback; + SyncEvent* m_next; +}; diff --git a/source/Video.cpp b/source/Video.cpp index 33fe6a73..13036808 100644 --- a/source/Video.cpp +++ b/source/Video.cpp @@ -172,6 +172,7 @@ void VideoInitialize () //=========================================================================== void VideoBenchmark () { + _ASSERT(g_nAppMode == MODE_BENCHMARK); Sleep(500); // PREPARE TWO DIFFERENT FRAME BUFFERS, EACH OF WHICH HAVE HALF OF THE diff --git a/test/TestCPU6502/TestCPU6502-vs2008.vcproj b/test/TestCPU6502/TestCPU6502-vs2008.vcproj index f5582875..774b1580 100644 --- a/test/TestCPU6502/TestCPU6502-vs2008.vcproj +++ b/test/TestCPU6502/TestCPU6502-vs2008.vcproj @@ -194,6 +194,10 @@ RelativePath=".\stdafx.h" > + + diff --git a/test/TestCPU6502/TestCPU6502-vs2019.vcxproj b/test/TestCPU6502/TestCPU6502-vs2019.vcxproj index 4166d508..0454719d 100644 --- a/test/TestCPU6502/TestCPU6502-vs2019.vcxproj +++ b/test/TestCPU6502/TestCPU6502-vs2019.vcxproj @@ -11,6 +11,7 @@ + diff --git a/test/TestCPU6502/TestCPU6502-vs2019.vcxproj.filters b/test/TestCPU6502/TestCPU6502-vs2019.vcxproj.filters index 81896f42..cf1e4e54 100644 --- a/test/TestCPU6502/TestCPU6502-vs2019.vcxproj.filters +++ b/test/TestCPU6502/TestCPU6502-vs2019.vcxproj.filters @@ -13,6 +13,9 @@ Source Files + + Source Files + diff --git a/test/TestCPU6502/TestCPU6502.cpp b/test/TestCPU6502/TestCPU6502.cpp index 62634e16..3a248699 100644 --- a/test/TestCPU6502/TestCPU6502.cpp +++ b/test/TestCPU6502/TestCPU6502.cpp @@ -3,10 +3,12 @@ #include "../../source/Applewin.h" #include "../../source/CPU.h" #include "../../source/Memory.h" +#include "../../source/SynchronousEventManager.h" // From Applewin.cpp bool g_bFullSpeed = false; enum AppMode_e g_nAppMode = MODE_RUNNING; +SynchronousEventManager g_SynchronousEventMgr; // From Memory.cpp LPBYTE memwrite[0x100]; // TODO: Init @@ -27,8 +29,7 @@ iofunction IOWrite[256] = {0}; // TODO: Init regsrec regs; -static const int IRQ_CHECK_TIMEOUT = 128; -static signed int g_nIrqCheckTimeout = IRQ_CHECK_TIMEOUT; +bool g_irqOnLastOpcodeCycle = false; static eCpuType g_ActiveCPU = CPU_65C02; @@ -54,7 +55,7 @@ static __forceinline void DoIrqProfiling(DWORD uCycles) { } -static __forceinline void CheckInterruptSources(ULONG uExecutedCycles, const bool bVideoUpdate) +static __forceinline void CheckSynchronousInterruptSources(UINT cycles, ULONG uExecutedCycles) { } @@ -1253,6 +1254,62 @@ int GH321_test() //------------------------------------- +int testCB(int id, int cycles, ULONG uExecutedCycles) +{ + return 0; +} + +int SyncEvents_test(void) +{ + SyncEvent syncEvent0(0, 0x10, testCB); + SyncEvent syncEvent1(1, 0x20, testCB); + SyncEvent syncEvent2(2, 0x30, testCB); + SyncEvent syncEvent3(3, 0x40, testCB); + + g_SynchronousEventMgr.Insert(&syncEvent0); + g_SynchronousEventMgr.Insert(&syncEvent1); + g_SynchronousEventMgr.Insert(&syncEvent2); + g_SynchronousEventMgr.Insert(&syncEvent3); + // id0 -> id1 -> id2 -> id3 + if (syncEvent0.m_cyclesRemaining != 0x10) return 1; + if (syncEvent1.m_cyclesRemaining != 0x10) return 1; + if (syncEvent2.m_cyclesRemaining != 0x10) return 1; + if (syncEvent3.m_cyclesRemaining != 0x10) return 1; + + g_SynchronousEventMgr.Remove(1); + g_SynchronousEventMgr.Remove(3); + g_SynchronousEventMgr.Remove(0); + if (syncEvent2.m_cyclesRemaining != 0x30) return 1; + g_SynchronousEventMgr.Remove(2); + + // + + syncEvent0.m_cyclesRemaining = 0x40; + syncEvent1.m_cyclesRemaining = 0x30; + syncEvent2.m_cyclesRemaining = 0x20; + syncEvent3.m_cyclesRemaining = 0x10; + + g_SynchronousEventMgr.Insert(&syncEvent0); + g_SynchronousEventMgr.Insert(&syncEvent1); + g_SynchronousEventMgr.Insert(&syncEvent2); + g_SynchronousEventMgr.Insert(&syncEvent3); + // id3 -> id2 -> id1 -> id0 + if (syncEvent0.m_cyclesRemaining != 0x10) return 1; + if (syncEvent1.m_cyclesRemaining != 0x10) return 1; + if (syncEvent2.m_cyclesRemaining != 0x10) return 1; + if (syncEvent3.m_cyclesRemaining != 0x10) return 1; + + g_SynchronousEventMgr.Remove(3); + g_SynchronousEventMgr.Remove(0); + g_SynchronousEventMgr.Remove(1); + if (syncEvent2.m_cyclesRemaining != 0x20) return 1; + g_SynchronousEventMgr.Remove(2); + + return 0; +} + +//------------------------------------- + int _tmain(int argc, _TCHAR* argv[]) { int res = 1; @@ -1277,5 +1334,8 @@ int _tmain(int argc, _TCHAR* argv[]) res = GH292_test(); if (res) return res; + res = SyncEvents_test(); + if (res) return res; + return 0; }