You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
3.3 KiB
100 lines
3.3 KiB
/* GameBoyInstance.timer.cpp |
|
* |
|
* Simulated timer implementation in the GameBoy core. |
|
*/ |
|
|
|
#include "GameBoyInstance.h" |
|
|
|
#include "../../PlipEmulationException.h" |
|
#include "../../PlipUtility.h" |
|
|
|
namespace Plip::Core::GameBoy { |
|
void GameBoyInstance::TimerExecute() { |
|
m_timerLast = m_timer; |
|
if(m_lastWrite.address == m_addrRegisters + m_regDivider) { |
|
// Reset the timer variable if DIV is written to. |
|
m_timer = 0; |
|
} |
|
|
|
// The timer increases by 4 every cycle. |
|
m_timer += 4; |
|
|
|
if(m_timerTimaOverflow) { |
|
// Reload TIMA and raise an interrupt (if applicable). |
|
auto tma = m_ioRegisters->GetByte(m_regTma); |
|
m_ioRegisters->SetByte(m_regTima, tma); |
|
|
|
// Quirk: If IF is written during the interrupt cycle, the |
|
// written value will override this. |
|
if(m_lastWrite.address != Plip::Cpu::SharpLr35902::MemInterruptFlag) |
|
m_cpu->Interrupt(INTERRUPT_TIMER); |
|
} |
|
|
|
// Handle TIMA. |
|
TimerTima(); |
|
|
|
// Update the divider register. |
|
m_ioRegisters->SetByte(m_regDivider, m_timer >> 8); |
|
} |
|
|
|
inline uint8_t GameBoyInstance::TimerFallingEdgeBit(uint8_t tac) { |
|
switch(tac & 0b11) { |
|
case 0b00: |
|
return 9; |
|
case 0b01: |
|
return 3; |
|
case 0b10: |
|
return 5; |
|
case 0b11: |
|
return 7; |
|
} |
|
|
|
return 0; // Compiler appeasement. :) |
|
} |
|
|
|
inline bool GameBoyInstance::TimerFallingEdgeDetection(uint8_t bit) const { |
|
return BIT_TEST(m_timerLast, bit) && !BIT_TEST(m_timer, bit); |
|
} |
|
|
|
inline void GameBoyInstance::TimerIncreaseTima() { |
|
uint8_t tima = m_ioRegisters->GetByte(m_regTima) + 1; |
|
|
|
if(tima == 0) { |
|
if(m_lastWrite.address == m_addrRegisters + m_regTima) { |
|
// Quirk: If TIMA is written to during the cycle that causes it to |
|
// overflow, the pending reset and interrupt are cancelled. |
|
tima = m_lastWrite.value; |
|
} else { |
|
// TIMA overflow. |
|
m_timerTimaOverflow = true; |
|
} |
|
} |
|
|
|
m_ioRegisters->SetByte(m_regTima, tima); |
|
} |
|
|
|
void GameBoyInstance::TimerTima() { |
|
auto tac = m_ioRegisters->GetByte(m_regTac) & 0b111; |
|
auto freqBit = TimerFallingEdgeBit(tac); |
|
m_timerTimaOverflow = false; |
|
|
|
if(TimerFallingEdgeDetection(freqBit) && BIT_TEST(tac, 2)) { |
|
TimerIncreaseTima(); |
|
} else if(BIT_TEST(m_timer, freqBit) && BIT_TEST(m_timerTacLast, 2) && !BIT_TEST(tac, 2)) { |
|
// Quirk: Increase TIMA if the corresponding bit is set when disabling |
|
// the timer. |
|
TimerIncreaseTima(); |
|
} else if(m_lastWrite.address == m_addrRegisters + m_regTac |
|
&& !BIT_TEST(m_timerTacLast, 2) && BIT_TEST(tac, 2) |
|
&& (m_timerTacLast & 0b11) == 0 && (tac & 0b11) == 1) { |
|
// Quirk: Increase TIMA if the timer goes from disabled to enabled, and |
|
// the multiplexer goes from 0 to 1 (agh). |
|
TimerIncreaseTima(); |
|
} |
|
|
|
m_timerTacLast = tac; |
|
|
|
// Write TAC back into memory to ensure that only the low 3 bits are set. |
|
m_ioRegisters->SetByte(m_regTac, tac); |
|
} |
|
}
|
|
|