Rework RIOT timer to match real hardware behavior.

This commit is contained in:
Christian Speckner 2017-03-14 01:26:14 +01:00
parent 7cbec18f5a
commit 05b10bec6b
2 changed files with 99 additions and 83 deletions

View File

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

View File

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