Support for synchronous events (PR #841)

Switched Mockingboard/6522 Timer interrupts & Mousecard's VBlank interrupt to use synchronous events.
This is a linked-list of ordered timer-based event, where only the head of the list needs updating after every opcode.
This commit is contained in:
TomCh 2020-10-11 16:08:05 +01:00 committed by GitHub
parent 7d7aa17a47
commit 6125c2b12d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 597 additions and 165 deletions

View File

@ -757,6 +757,14 @@
RelativePath=".\source\Speech.h"
>
</File>
<File
RelativePath=".\source\SynchronousEventManager.cpp"
>
</File>
<File
RelativePath=".\source\SynchronousEventManager.h"
>
</File>
<File
RelativePath=".\source\Tape.cpp"
>

View File

@ -96,6 +96,7 @@
<ClInclude Include="source\Speech.h" />
<ClInclude Include="source\SSI263Phonemes.h" />
<ClInclude Include="source\StdAfx.h" />
<ClInclude Include="source\SynchronousEventManager.h" />
<ClInclude Include="source\Tape.h" />
<ClInclude Include="source\Tfe\Bittypes.h" />
<ClInclude Include="source\Tfe\Bpf.h" />
@ -184,6 +185,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release NoDX|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="source\SynchronousEventManager.cpp" />
<ClCompile Include="source\Tape.cpp" />
<ClCompile Include="source\Tfe\Tfe.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>

View File

@ -199,6 +199,9 @@
<ClCompile Include="source\Disk2CardManager.cpp">
<Filter>Source Files\Disk</Filter>
</ClCompile>
<ClCompile Include="source\SynchronousEventManager.cpp">
<Filter>Source Files\Emulator</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="source\Applewin.h">
@ -483,6 +486,9 @@
<ClInclude Include="source\Card.h">
<Filter>Source Files\Emulator</Filter>
</ClInclude>
<ClInclude Include="source\SynchronousEventManager.h">
<Filter>Source Files\Emulator</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="resource\Applewin.bmp">

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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<<Phasor_SY6522A_CS)
#define Phasor_SY6522B_Offset (1<<Phasor_SY6522B_CS)
//#define Phasor_SY6522A_CS 4
//#define Phasor_SY6522B_CS 7
//#define Phasor_SY6522A_Offset (1<<Phasor_SY6522A_CS)
//#define Phasor_SY6522B_Offset (1<<Phasor_SY6522B_CS)
enum MockingboardUnitState_e {AY_NOP0, AY_NOP1, AY_INACTIVE, AY_READ, AY_NOP4, AY_NOP5, AY_WRITE, AY_LATCH};
@ -132,10 +133,6 @@ struct SY6522_AY8910
SSI263A SpeechChip;
MockingboardUnitState_e state; // Where a unit is a 6522+AY8910 pair
MockingboardUnitState_e stateB; // Phasor: 6522 & 2nd AY8910
// NB. No need to save to save-state, as it will be done immediately after opcode completes in MB_UpdateCycles()
bool bLoadT1C; // Load T1C with T1L after opcode completes
bool bLoadT2C; // Load T2C with T2L after opcode completes
};
@ -162,6 +159,11 @@ struct SY6522_AY8910
// Support 2 MB's, each with 2x SY6522/AY8910 pairs.
static SY6522_AY8910 g_MB[NUM_AY8910];
const UINT kExtraTimerCycles = 2; // Rockwell, Fig.16: period = N+2 cycles
const UINT kNumTimersPer6522 = 2;
const UINT kNumSyncEvents = NUM_MB * NUM_SY6522 * kNumTimersPer6522;
static SyncEvent* g_syncEvent[kNumSyncEvents];
// Timer vars
static const UINT kTIMERDEVICE_INVALID = -1;
static UINT g_nMBTimerDevice = kTIMERDEVICE_INVALID; // SY6522 device# which is generating timer IRQ
@ -222,6 +224,7 @@ static UINT g_cyclesThisAudioFrame = 0;
// Forward refs:
static DWORD WINAPI SSI263Thread(LPVOID);
static void Votrax_Write(BYTE nDevice, BYTE nValue);
static int MB_SyncEventCallback(int id, int cycles, ULONG uExecutedCycles);
//---------------------------------------------------------------------------
@ -340,6 +343,99 @@ static void AY8910_Write(BYTE nDevice, BYTE /*nReg*/, BYTE nValue, BYTE nAYDevic
}
}
static UINT GetOpcodeCycles(BYTE reg)
{
UINT opcodeCycles = 0;
BYTE opcode = 0;
bool abs16 = false;
const BYTE opcodeMinus3 = mem[(regs.pc-3)&0xffff];
const BYTE opcodeMinus2 = mem[(regs.pc-2)&0xffff];
if ( (opcodeMinus3 == 0x8C) || // sty abs16
(opcodeMinus3 == 0x8D) || // sta abs16
(opcodeMinus3 == 0x8E) ) // stx abs16
{ // Eg. FT demos: CHIP, MADEF, MAD2
opcodeCycles = 4;
opcode = opcodeMinus3;
abs16 = true;
}
else if ( (opcodeMinus3 == 0x99) || // sta abs16,y
(opcodeMinus3 == 0x9D) ) // sta abs16,x
{ // Eg. Paleotronic microTracker demo
opcodeCycles = 5;
opcode = opcodeMinus3;
abs16 = true;
}
else if (opcodeMinus2 == 0x81) // sta (zp,x)
{
opcodeCycles = 6;
opcode = opcodeMinus2;
}
else if (opcodeMinus2 == 0x91) // sta (zp),y
{ // Eg. FT demos: OMT, PLS
opcodeCycles = 6;
opcode = opcodeMinus2;
}
else if (opcodeMinus2 == 0x92 && GetMainCpu() == CPU_65C02) // sta (zp) : 65C02-only
{
opcodeCycles = 5;
opcode = opcodeMinus2;
}
else
{
_ASSERT(0);
opcodeCycles = 0;
return 0;
}
//
WORD addr16 = 0;
if (!abs16)
{
BYTE zp = mem[(regs.pc-1)&0xffff];
if (opcode == 0x81) zp += regs.x;
addr16 = (mem[zp] | (mem[(zp+1)&0xff]<<8));
if (opcode == 0x91) addr16 += regs.y;
}
else
{
addr16 = mem[(regs.pc-2)&0xffff] | (mem[(regs.pc-1)&0xffff]<<8);
if (opcode == 0x99) addr16 += regs.y;
if (opcode == 0x9D) addr16 += regs.x;
}
// Check we've reverse looked-up the 6502 opcode correctly
if ((addr16 & 0xF80F) != (0xC000+reg))
{
_ASSERT(0);
return 0;
}
return opcodeCycles;
}
// Insert a new synchronous event whenever the 6522 timer's counter is written.
// . NB. it doesn't matter if the timer's interrupt enable (IER) is set or not
// - the state of IER is only important when the counter underflows - see: MB_SyncEventCallback()
static USHORT SetTimerSyncEvent(UINT id, BYTE reg, USHORT timerLatch)
{
// NB. This TIMER adjustment value gets subtracted when this current opcode completes, so no need to persist to save-state
const UINT opcodeCycleAdjust = GetOpcodeCycles(reg);
SyncEvent* pSyncEvent = g_syncEvent[id];
if (pSyncEvent->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; id<kNumSyncEvents; id++)
{
g_syncEvent[id] = new SyncEvent(id, 0, MB_SyncEventCallback);
}
}
static void MB_SetSoundcardType(SS_CARDTYPE NewSoundcardType);
@ -1550,6 +1646,12 @@ void MB_Destroy()
DeleteCriticalSection(&g_CriticalSection);
g_bCritSectionValid = false;
}
for (int id=0; id<kNumSyncEvents; id++)
{
delete g_syncEvent[id];
g_syncEvent[id] = NULL;
}
}
//-----------------------------------------------------------------------------
@ -1574,6 +1676,12 @@ static void ResetState()
g_uLastMBUpdateCycle = 0;
g_cyclesThisAudioFrame = 0;
for (int id = 0; id < kNumSyncEvents; id++)
{
if (g_syncEvent[id] && g_syncEvent[id]->m_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; i<NUM_SY6522; i++)
for (int i = 0; i < NUM_SY6522; i++)
{
SY6522_AY8910* pMB = &g_MB[i];
bool bTimer1Irq = false;
bool bTimer1IrqOnLastCycle = false;
const bool bTimer1Underflow = CheckTimerUnderflow(pMB->sy6522.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)
{

View File

@ -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();

View File

@ -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()

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -479,7 +479,6 @@ static void Snapshot_LoadState_v2(void)
MemUpdatePaging(TRUE);
SetMouseCardInstalled( g_CardMgr.IsMouseCardInstalled() );
DebugReset();
if (g_nAppMode == MODE_DEBUG)
DebugDisplay(TRUE);

View File

@ -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
}
}

View File

@ -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;
};

View File

@ -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

View File

@ -194,6 +194,10 @@
RelativePath=".\stdafx.h"
>
</File>
<File
RelativePath="..\..\source\SynchronousEventManager.cpp"
>
</File>
<File
RelativePath=".\TestCPU6502.cpp"
>

View File

@ -11,6 +11,7 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\source\SynchronousEventManager.cpp" />
<ClCompile Include="stdafx.cpp" />
<ClCompile Include="TestCPU6502.cpp" />
</ItemGroup>

View File

@ -13,6 +13,9 @@
<ClCompile Include="TestCPU6502.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\source\SynchronousEventManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">

View File

@ -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;
}