diff --git a/src/emucore/M6532.cxx b/src/emucore/M6532.cxx index 4159b4a37..055b9005d 100644 --- a/src/emucore/M6532.cxx +++ b/src/emucore/M6532.cxx @@ -29,10 +29,10 @@ M6532::M6532(const Console& console, const Settings& settings) : myConsole(console), mySettings(settings), - myTimer(0), myIntervalShift(0), myCyclesWhenTimerSet(0), + myTimer(0), mySubTimer(0), myDivider(1), + myTimerWrapped(false), mySetTimerCycle(0), myLastCycle(0), myDDRA(0), myDDRB(0), myOutA(0), myOutB(0), myInterruptFlag(false), - myTimerFlagValid(false), myEdgeDetectPositive(false) { } @@ -47,11 +47,12 @@ void M6532::reset() else memset(myRAM, 0, 128); - // The timer absolutely cannot be initialized to zero; some games will - // loop or hang (notably Solaris and H.E.R.O.) - myTimer = (0xff - (mySystem->randGenerator().next() % 0xfe)) << 10; - myIntervalShift = 10; - myCyclesWhenTimerSet = 0; + myTimer = mySystem->randGenerator().next() & 0xff; + myDivider = 1024; + mySubTimer = 0; + myTimerWrapped = false; + mySetTimerCycle = 0; + myLastCycle = mySystem->cycles(); // Zero the I/O registers myDDRA = myDDRB = myOutA = myOutB = 0x00; @@ -61,7 +62,6 @@ void M6532::reset() // Zero the interrupt flag register and mark D7 as invalid myInterruptFlag = 0x00; - myTimerFlagValid = false; // Edge-detect set to negative (high to low) myEdgeDetectPositive = false; @@ -72,7 +72,8 @@ void M6532::systemCyclesReset() { // System cycles are being reset to zero so we need to adjust // the cycle count we remembered when the timer was last set - myCyclesWhenTimerSet -= mySystem->cycles(); + myLastCycle -= mySystem->cycles(); + mySetTimerCycle -= mySystem->cycles(); // We should also inform any 'smart' controllers as well myConsole.leftController().systemCyclesReset(); @@ -102,6 +103,35 @@ void M6532::update() myInterruptFlag |= PA7Bit; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void M6532::updateEmulation() +{ + uInt32 cycles = mySystem->cycles() - myLastCycle; + uInt32 subTimer = mySubTimer; + + mySubTimer = (cycles + mySubTimer) % myDivider; + + if (!myTimerWrapped) { + uInt32 timerTicks = (cycles + subTimer) / myDivider; + + if (timerTicks > myTimer) { + cycles -= ((myTimer + 1) * myDivider - subTimer); + myTimer = 0xFF; + myTimerWrapped = true; + myInterruptFlag |= TimerBit; + } else { + myTimer -= timerTicks; + cycles = 0; + } + } + + if (myTimerWrapped) { + myTimer = (myTimer - cycles) & 0xFF; + } + + myLastCycle = mySystem->cycles(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6532::install(System& system) { @@ -126,6 +156,8 @@ void M6532::installDelegate(System& system, Device& device) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 M6532::peek(uInt16 addr) { + updateEmulation(); + // Access RAM directly. Originally, accesses to RAM could bypass // this method and its pages could be installed directly into the // system. However, certain cartridges (notably 4A50) can mirror @@ -170,36 +202,14 @@ uInt8 M6532::peek(uInt16 addr) // Timer Flag is always cleared when accessing INTIM myInterruptFlag &= ~TimerBit; - // Get number of clocks since timer was set - Int32 timer = timerClocks(); + myTimerWrapped = false; - if(timer >= 0) - { - // Return at 'divide by TIMxT' interval rate - return (timer >> myIntervalShift) & 0xff; - } - else - { - // Return at 'divide by 1' rate - uInt8 divByOne = timer & 0xff; - - // Timer flag has been updated; don't update it again on TIMINT read - if(divByOne != 0 && divByOne != 255) - myTimerFlagValid = true; - - return divByOne; - } + return myTimer; } case 0x05: // TIMINT/INSTAT - Interrupt Flag case 0x07: { - // Update timer flag if it is invalid and timer has expired - if(!myTimerFlagValid && timerClocks() < 0) - { - myInterruptFlag |= TimerBit; - myTimerFlagValid = true; - } // PA7 Flag is always cleared after accessing TIMINT uInt8 result = myInterruptFlag; myInterruptFlag &= ~PA7Bit; @@ -219,6 +229,8 @@ uInt8 M6532::peek(uInt16 addr) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6532::poke(uInt16 addr, uInt8 value) { + updateEmulation(); + // Access RAM directly. Originally, accesses to RAM could bypass // this method and its pages could be installed directly into the // system. However, certain cartridges (notably 4A50) can mirror @@ -278,16 +290,19 @@ bool M6532::poke(uInt16 addr, uInt8 value) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6532::setTimerRegister(uInt8 value, uInt8 interval) { - static constexpr uInt8 shift[] = { 0, 3, 6, 10 }; + static constexpr uInt32 divider[] = { 1, 8, 64, 1024 }; - myIntervalShift = shift[interval]; + myDivider = divider[interval]; myOutTimer[interval] = value; - myTimer = value << myIntervalShift; - myCyclesWhenTimerSet = mySystem->cycles(); + + myTimer = value; + mySubTimer = myDivider - 1; + myTimerWrapped = false; // Interrupt timer flag is cleared (and invalid) when writing to the timer myInterruptFlag &= ~TimerBit; - myTimerFlagValid = false; + + mySetTimerCycle = mySystem->cycles(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -334,8 +349,11 @@ bool M6532::save(Serializer& out) const out.putByteArray(myRAM, 128); out.putInt(myTimer); - out.putInt(myIntervalShift); - out.putInt(myCyclesWhenTimerSet); + out.putInt(mySubTimer); + out.putInt(myDivider); + out.putBool(myTimerWrapped); + out.putInt(myLastCycle); + out.putInt(mySetTimerCycle); out.putByte(myDDRA); out.putByte(myDDRB); @@ -343,7 +361,6 @@ bool M6532::save(Serializer& out) const out.putByte(myOutB); out.putByte(myInterruptFlag); - out.putBool(myTimerFlagValid); out.putBool(myEdgeDetectPositive); out.putByteArray(myOutTimer, 4); } @@ -367,8 +384,11 @@ bool M6532::load(Serializer& in) in.getByteArray(myRAM, 128); myTimer = in.getInt(); - myIntervalShift = in.getInt(); - myCyclesWhenTimerSet = in.getInt(); + mySubTimer = in.getInt(); + myDivider = in.getInt(); + myTimerWrapped = in.getBool(); + myLastCycle = in.getInt(); + mySetTimerCycle = in.getInt(); myDDRA = in.getByte(); myDDRB = in.getByte(); @@ -376,7 +396,6 @@ bool M6532::load(Serializer& in) myOutB = in.getByte(); myInterruptFlag = in.getByte(); - myTimerFlagValid = in.getBool(); myEdgeDetectPositive = in.getBool(); in.getByteArray(myOutTimer, 4); } @@ -390,44 +409,35 @@ bool M6532::load(Serializer& in) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 M6532::intim() const +uInt8 M6532::intim() { - // This method is documented in ::peek(0x284), and exists so that the - // debugger can read INTIM without changing the state of the system + updateEmulation(); - // Get number of clocks since timer was set - Int32 timer = timerClocks(); - if(timer >= 0) - return (timer >> myIntervalShift) & 0xff; - else - return timer & 0xff; + return myTimer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 M6532::timint() const +uInt8 M6532::timint() { - // This method is documented in ::peek(0x285), and exists so that the - // debugger can read TIMINT without changing the state of the system + updateEmulation(); - // Update timer flag if it is invalid and timer has expired - uInt8 interrupt = myInterruptFlag; - if(timerClocks() < 0) - interrupt |= TimerBit; - - return interrupt; + return myInterruptFlag; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int32 M6532::intimClocks() const +Int32 M6532::intimClocks() { + updateEmulation(); + // This method is similar to intim(), except instead of giving the actual // INTIM value, it will give the current number of clocks between one // INTIM value and the next - // Get number of clocks since timer was set - Int32 timer = timerClocks(); - if(timer >= 0) - return timerClocks() & ((1 << myIntervalShift) - 1); - else - return timer & 0xff; + return myTimerWrapped ? 1 : (myDivider - mySubTimer); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 M6532::timerClocks() const +{ + return mySystem->cycles() - mySetTimerCycle; } diff --git a/src/emucore/M6532.hxx b/src/emucore/M6532.hxx index 923be1719..f64afe003 100644 --- a/src/emucore/M6532.hxx +++ b/src/emucore/M6532.hxx @@ -133,18 +133,19 @@ class M6532 : public Device bool poke(uInt16 address, uInt8 value) override; private: - Int32 timerClocks() const - { return myTimer - (mySystem->cycles() - myCyclesWhenTimerSet); } void setTimerRegister(uInt8 data, uInt8 interval); void setPinState(bool shcha); + void updateEmulation(); + // The following are used by the debugger to read INTIM/TIMINT // We need separate methods to do this, so the state of the system // isn't changed - uInt8 intim() const; - uInt8 timint() const; - Int32 intimClocks() const; + uInt8 intim(); + uInt8 timint(); + Int32 intimClocks(); + uInt32 timerClocks() const; private: // Accessible bits in the interrupt flag register @@ -164,13 +165,22 @@ class M6532 : public Device uInt8 myRAM[128]; // Current value of the timer - uInt32 myTimer; + uInt8 myTimer; - // Log base 2 of the number of cycles in a timer interval - uInt32 myIntervalShift; + // Current number of clocks "queued" for the divider + uInt32 mySubTimer; - // Indicates the number of cycles when the timer was last set - Int32 myCyclesWhenTimerSet; + // The divider + uInt32 myDivider; + + // Has the timer wrapped? + bool myTimerWrapped; + + // Cycle when the timer set. Debugging only. + Int32 mySetTimerCycle; + + // Last cycle considered in emu updates + Int32 myLastCycle; // Data Direction Register for Port A uInt8 myDDRA; @@ -187,10 +197,6 @@ class M6532 : public Device // Interrupt Flag Register uInt8 myInterruptFlag; - // Whether the timer flag (as currently set) can be used - // If it isn't valid, it will be updated as required - bool myTimerFlagValid; - // Used to determine whether an active transition on PA7 has occurred // True is positive edge-detect, false is negative edge-detect bool myEdgeDetectPositive;