Mesen2/Core/GBA/GbaRomPrefetch.h
2024-11-06 21:21:55 +09:00

238 lines
No EOL
6.1 KiB
C++

#pragma once
#include "pch.h"
#include "GBA/GbaMemoryManager.h"
#include "GBA/GbaCpu.h"
#include "Utilities/ISerializable.h"
#include "Utilities/Serializer.h"
class GbaRomPrefetch final : ISerializable
{
private:
GbaRomPrefetchState _state = {};
GbaMemoryManager* _memoryManager = nullptr;
GbaCpu* _cpu = nullptr;
__forceinline bool IsEmpty()
{
return _state.ReadAddr == _state.PrefetchAddr;
}
__forceinline bool IsFull()
{
return _state.PrefetchAddr - _state.ReadAddr >= 16;
}
__forceinline bool IsRomBoundary()
{
//When the prefetcher hits an address that's a multiple of 128kb,
//it behaves as if it was filled (blocking any more fetches until
//it gets emptied)
bool result = (_state.PrefetchAddr & 0x1FFFE) == 0;
_state.BoundaryCyclePenalty = (uint8_t)result;
return result;
}
__forceinline uint8_t WaitForPendingRead()
{
if(_state.ClockCounter == 0) {
_state.ClockCounter = GetAccessClockCount();
}
uint8_t counter = _state.ClockCounter;
ProcessRead();
return counter;
}
__forceinline uint8_t ReadHalfWord()
{
uint8_t counter = GetAccessClockCount();
ProcessRead();
return counter;
}
__forceinline void ProcessRead()
{
_state.ReadAddr += 2;
_state.PrefetchAddr += 2;
if(IsRomBoundary()) {
_state.WasFilled = true;
}
_state.ClockCounter = 0;
_state.Sequential = true;
}
__forceinline uint8_t GetAccessClockCount()
{
uint8_t count = _memoryManager->GetWaitStates(GbaAccessMode::HalfWord | (_state.Sequential ? GbaAccessMode::Sequential : 0), _state.PrefetchAddr) + _state.BoundaryCyclePenalty;
_state.BoundaryCyclePenalty = 0;
return count;
}
public:
void Init(GbaMemoryManager* memoryManager, GbaCpu* cpu)
{
_memoryManager = memoryManager;
_cpu = cpu;
}
GbaRomPrefetchState& GetState()
{
return _state;
}
bool Reset()
{
bool delay = _state.ClockCounter == 1;
_state.Started = false;
_state.Sequential = false;
_state.WasFilled = false;
_state.ReadAddr = 0;
_state.PrefetchAddr = 0;
_state.ClockCounter = 0;
return delay;
}
void ForceNonSequential(uint32_t addr)
{
if(addr == _state.ReadAddr && _state.ClockCounter == 0 && !_state.Started) {
//When the CPU jumps to the address the prefetch was about to read/fetch
//and the prefetcher has been empty since its last reset (always ReadAddr==PrefetchAddr),
//then the fetch counts as a non-sequential read
_state.Sequential = false;
}
}
__forceinline void SetSuspendState(bool suspended)
{
//Prefetch is suspended by DMA once DMA accesses ROM
_state.Suspended = suspended;
}
void Exec(uint8_t clocks, bool prefetchEnabled)
{
if(_state.Suspended || _state.ReadAddr == 0 || IsFull() || (!prefetchEnabled && _state.ClockCounter == 0)) {
return;
}
_state.Started = true;
if(_state.WasFilled) {
//Once the prefetch buffer is filled, it needs to be emptied completely
//before any further prefetching is allowed
return;
}
do {
if(_state.ClockCounter == 0) {
_state.ClockCounter = GetAccessClockCount();
_state.Sequential = true;
}
if(--_state.ClockCounter == 0) {
_state.PrefetchAddr += 2;
//Any cpu access after the prefetch stops should be non-sequential
_cpu->ClearSequentialFlag();
if(IsFull() || IsRomBoundary()) {
_state.WasFilled = true;
break;
}
if(!prefetchEnabled) {
break;
}
}
} while(--clocks);
}
template<bool prefetchEnabled>
__forceinline uint8_t Read(GbaAccessModeVal mode, uint32_t addr)
{
if(_state.WasFilled && IsEmpty()) {
//Prefetch was paused because it was filled,
//and it can resume because it's empty
_state.WasFilled = false;
_state.Sequential = false;
_state.Started = false;
}
if constexpr(!prefetchEnabled) {
if(IsEmpty()) {
//Prefetcher is disabled & empty (but a prefetch read might be in-progress)
if(_state.ClockCounter == 0) {
//No prefetch is in progress - read normally
return _memoryManager->GetWaitStates(mode, addr);
} else {
//Finish the current prefetch read
uint8_t totalTime = WaitForPendingRead();
_cpu->ClearSequentialFlag();
if(mode & GbaAccessMode::Word) {
//If fetching a 32-bit value, add the time it'll take to read the next half-word (non sequential)
totalTime += _memoryManager->GetWaitStates(GbaAccessMode::HalfWord, addr);
//The previous read counts as the first non-sequential read, next one should be sequential
_cpu->SetSequentialFlag();
}
return totalTime;
}
}
} else {
if(addr != _state.ReadAddr) {
//Restart prefetch, need to read an entire opcode
uint8_t totalTime = Reset();
_state.PrefetchAddr = addr;
_state.ReadAddr = addr;
totalTime += ReadHalfWord();
if(mode & GbaAccessMode::Word) {
totalTime += ReadHalfWord();
}
return totalTime;
} else if(IsEmpty()) {
//Prefetch in progress, wait until it ends
uint8_t totalTime = WaitForPendingRead();
if(mode & GbaAccessMode::Word) {
//Need to fetch the next half-word, too
totalTime += ReadHalfWord();
}
return totalTime;
}
}
_state.ReadAddr += 2;
if(mode & GbaAccessMode::Word) {
//Need to read another half-word
if(!IsEmpty()) {
//Data is already available, return it without any additional delay
_state.ReadAddr += 2;
Exec(1, prefetchEnabled);
return 1;
} else {
//Prefetch in progress, wait until it ends
if constexpr(!prefetchEnabled) {
if(_state.ClockCounter == 0) {
//Prefetch is disabled and no pending read is in progress, but the CPU needs to read an extra half-word (ARM mode)
uint8_t totalTime = _memoryManager->GetWaitStates(GbaAccessMode::HalfWord, addr);
//The previous read counts as the first non-sequential read, next one should be sequential
_cpu->SetSequentialFlag();
return totalTime;
}
}
return WaitForPendingRead();
}
} else {
Exec(1, prefetchEnabled);
return 1;
}
}
void Serialize(Serializer& s) override
{
SV(_state.ClockCounter);
SV(_state.ReadAddr);
SV(_state.PrefetchAddr);
SV(_state.BoundaryCyclePenalty);
SV(_state.Suspended);
SV(_state.WasFilled);
SV(_state.Sequential);
SV(_state.Started);
}
};