diff --git a/source/CPU.cpp b/source/CPU.cpp index d4e7e7d0..61bf5c68 100644 --- a/source/CPU.cpp +++ b/source/CPU.cpp @@ -403,10 +403,23 @@ static __forceinline void NMI(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, #endif } +static bool g_irqOnLastOpcodeCycle = false; +static bool g_irqDefer1Opcode = false; + static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, BOOL& flagv, BOOL& flagz) { if(g_bmIRQ && !(regs.ps & AF_INTERRUPT)) { + // if 6522 interrupt occurs on opcode's last cycle, then defer IRQ by 1 opcode + if (g_irqOnLastOpcodeCycle && !g_irqDefer1Opcode) + { + g_irqOnLastOpcodeCycle = false; + g_irqDefer1Opcode = true; // if INT occurs again on next opcode, then do NOT defer + return; + } + + g_irqDefer1Opcode = false; + // IRQ signals are deasserted when a specific r/w operation is done on device #ifdef _DEBUG g_nCycleIrqStart = g_nCumulativeCycles + uExecutedCycles; @@ -420,6 +433,8 @@ static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn, UINT uExtraCycles = 0; // Needed for CYC(a) macro CYC(7) } + + g_irqOnLastOpcodeCycle = false; } const int IRQ_CHECK_OPCODE_FULL_SPEED = 40; // ~128 cycles (assume 3 cycles per opcode) @@ -435,7 +450,9 @@ static __forceinline void CheckInterruptSources(ULONG uExecutedCycles, const boo g_fullSpeedOpcodeCount = IRQ_CHECK_OPCODE_FULL_SPEED; } - MB_UpdateCycles(uExecutedCycles); + if (MB_UpdateCycles(uExecutedCycles)) + g_irqOnLastOpcodeCycle = true; + if (sg_Mouse.IsActive()) sg_Mouse.SetVBlank( !VideoGetVblBar(uExecutedCycles) ); } @@ -536,11 +553,10 @@ DWORD CpuExecute(const DWORD uCycles, const bool bVideoUpdate) // >0 : Do multi-opcode emulation const DWORD uExecutedCycles = InternalCpuExecute(uCycles, bVideoUpdate); + // NB. Required for normal-speed (even though 6522 is updated after every opcode), as may've finished on IRQ() MB_UpdateCycles(uExecutedCycles); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below) // NB. Ensures that 6522 regs are up-to-date for any potential save-state - // - const UINT nRemainingCycles = uExecutedCycles - g_nCyclesExecuted; g_nCumulativeCycles += nRemainingCycles; diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index 8c4c6a8c..a8cc13b2 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -129,6 +129,8 @@ struct SY6522_AY8910 SSI263A SpeechChip; MockingboardUnitState_e state; // Where a unit is a 6522+AY8910 pair MockingboardUnitState_e stateB; // Phasor: 6522 & 2nd AY8910 + bool bLoadT1C; + bool bLoadT2C; }; @@ -376,6 +378,8 @@ static void UpdateIFR(SY6522_AY8910* pMB, BYTE clr_ifr, BYTE set_ifr=0) CpuIrqDeassert(IS_6522); } +#define DEFER_T1C_LOAD + static void SY6522_Write(BYTE nDevice, BYTE nReg, BYTE nValue) { g_bMB_Active = true; @@ -433,7 +437,7 @@ static void SY6522_Write(BYTE nDevice, BYTE nReg, BYTE nValue) UpdateIFR(pMB, IxR_TIMER1); pMB->sy6522.TIMER1_LATCH.h = nValue; - pMB->sy6522.TIMER1_COUNTER.w = pMB->sy6522.TIMER1_LATCH.w; + pMB->bLoadT1C = true; StartTimer1(pMB); CpuAdjustIrqCheck(pMB->sy6522.TIMER1_LATCH.w); // Sync IRQ check timeout with 6522 counter underflow - GH#608 @@ -529,7 +533,8 @@ static BYTE SY6522_Read(BYTE nDevice, BYTE nReg) nValue = pMB->sy6522.DDRA; break; case 0x04: // TIMER1L_COUNTER - nValue = pMB->sy6522.TIMER1_COUNTER.l; + // NB. GH#701 (T1C:=0xFFFF, LDA T1C_L, A==0xFC) + nValue = (pMB->sy6522.TIMER1_COUNTER.w - 3) & 0xff; // -3 to compensate for the (assumed) 4-cycle STA 6522.T1C_H UpdateIFR(pMB, IxR_TIMER1); break; case 0x05: // TIMER1H_COUNTER @@ -1541,7 +1546,8 @@ void MB_Reset() // CTRL+RESET or power-cycle static BYTE __stdcall MB_Read(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { - MB_UpdateCycles(nExecutedCycles); + if (g_bFullSpeed) + MB_UpdateCycles(nExecutedCycles); #ifdef _DEBUG if(!IS_APPLE2 && MemCheckINTCXROM()) @@ -1604,7 +1610,8 @@ 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) { - MB_UpdateCycles(nExecutedCycles); + if (g_bFullSpeed) + MB_UpdateCycles(nExecutedCycles); #ifdef _DEBUG if(!IS_APPLE2 && MemCheckINTCXROM()) @@ -1828,24 +1835,45 @@ 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() -void MB_UpdateCycles(ULONG uExecutedCycles) +// . MB_Read() / MB_Write() (only for full-speed) +bool MB_UpdateCycles(ULONG uExecutedCycles) { if (g_SoundcardType == CT_Empty) - return; + return false; CpuCalcCycles(uExecutedCycles); UINT64 uCycles = g_nCumulativeCycles - g_uLastCumulativeCycles; + if (uCycles == 0) + return false; // Likely when called from CpuExecute() + _ASSERT(uCycles > 1); + + const bool isOpcode = (uCycles > 1 && uCycles <= 7); // todo: better to pass in a flag? + g_uLastCumulativeCycles = g_nCumulativeCycles; _ASSERT(uCycles < 0x10000); USHORT nClocks = (USHORT) uCycles; + bool bIrqOnLastOpcodeCycle = false; + for (int i=0; isy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks, &bTimer1Underflow); + bool bTimer1Irq = false; + bool bTimer1IrqOnLastCycle = false; + + if (isOpcode) + { + bTimer1Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks-1, &bTimer1Underflow); + bTimer1IrqOnLastCycle = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, 1, &bTimer1Underflow); + bTimer1Irq = bTimer1Irq || bTimer1IrqOnLastCycle; + } + else + { + bTimer1Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks, &bTimer1Underflow); + } + const bool bTimer2Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER2_COUNTER.w, pMB->sy6522.timer2IrqDelay, nClocks); if (!pMB->bTimer1Active && bTimer1Underflow) @@ -1864,6 +1892,7 @@ void MB_UpdateCycles(ULONG uExecutedCycles) if (pMB->bTimer1Active && bTimer1Irq) { UpdateIFR(pMB, 0, IxR_TIMER1); + bIrqOnLastOpcodeCycle = true; MB_Update(); @@ -1880,6 +1909,9 @@ void MB_UpdateCycles(ULONG uExecutedCycles) // - 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) @@ -1891,6 +1923,12 @@ void MB_UpdateCycles(ULONG uExecutedCycles) } } + 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); @@ -1913,6 +1951,8 @@ void MB_UpdateCycles(ULONG uExecutedCycles) } } } + + return bIrqOnLastOpcodeCycle; } //----------------------------------------------------------------------------- diff --git a/source/Mockingboard.h b/source/Mockingboard.h index 40da37ef..89526938 100644 --- a/source/Mockingboard.h +++ b/source/Mockingboard.h @@ -11,7 +11,7 @@ void MB_Demute(); void MB_StartOfCpuExecute(); void MB_PeriodicUpdate(UINT executedCycles); void MB_CheckIRQ(); -void MB_UpdateCycles(ULONG uExecutedCycles); +bool MB_UpdateCycles(ULONG uExecutedCycles); SS_CARDTYPE MB_GetSoundcardType(); bool MB_IsActive(); DWORD MB_GetVolume(); diff --git a/source/Video.cpp b/source/Video.cpp index 431b7351..806d0558 100644 --- a/source/Video.cpp +++ b/source/Video.cpp @@ -660,9 +660,10 @@ BYTE VideoSetMode(WORD, WORD address, BYTE write, BYTE, ULONG uExecutedCycles) if (!IS_APPLE2) RGB_SetVideoMode(address); - bool delay = true; - if ((oldVideoMode ^ g_uVideoMode) & VF_PAGE2) - delay = false; // PAGE2 flag changed state, so no 1 cycle delay (GH#656) + // Only 1-cycle delay for VF_TEXT & VF_MIXED mode changes (GH#656) + bool delay = false; + if ((oldVideoMode ^ g_uVideoMode) & (VF_TEXT|VF_MIXED)) + delay = true; NTSC_SetVideoMode( g_uVideoMode, delay );