flycast/core/hw/sh4/sh4_cache.h

591 lines
13 KiB
C++

/*
Copyright 2020 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <array>
#include "types.h"
#include "sh4_mem.h"
#include "modules/mmu.h"
#include "hw/sh4/sh4_core.h"
static bool cachedArea(u32 area)
{
static const bool cached_areas[8] = {
true, true, true, true, // P0/U0
true, // P1
false, // P2
true, // P3
false // P4
};
return cached_areas[area];
}
static bool translatedArea(u32 area)
{
static const bool translated_areas[8] = {
true, true, true, true, // P0/U0
false, // P1
false, // P2
true, // P3
false // P4
};
return translated_areas[area];
}
//
// SH4 instruction cache
//
class sh4_icache
{
public:
u16 ReadMem(u32 address)
{
bool cacheOn = false;
u32 physAddr;
u32 err = translateAddress(address, physAddr, cacheOn);
if (err != MMU_ERROR_NONE)
mmu_raise_exception(err, address, MMU_TT_IREAD);
if (!cacheOn)
return _vmem_readt<u16, u16>(physAddr);
const u32 index = CCN_CCR.IIX ?
((address >> 5) & 0x7f) | ((address >> (25 - 7)) & 0x80)
: (address >> 5) & 0xff;
cache_line& line = lines[index];
const u32 tag = (physAddr >> 10) & 0x7ffff;
if (!line.valid || tag != line.address)
{
// miss
line.valid = true;
line.address = tag;
const u32 line_addr = physAddr & ~0x1f;
u8* const memPtr = GetMemPtr(line_addr, sizeof(line.data));
if (memPtr != nullptr)
memcpy(line.data, memPtr, sizeof(line.data));
else
{
u32 *p = (u32 *)line.data;
for (int i = 0; i < 32; i += 4)
*p++ = _vmem_ReadMem32(line_addr + i);
}
}
return *(u16*)&line.data[physAddr & 0x1f];
}
void Invalidate()
{
for (auto& line : lines)
line.valid = false;
}
void Reset(bool hard)
{
if (hard)
memset(&lines[0], 0, sizeof(lines));
}
bool Serialize(void **data, unsigned int *total_size)
{
REICAST_S(lines);
return true;
}
bool Unserialize(void **data, unsigned int *total_size)
{
REICAST_US(lines);
return true;
}
u32 ReadAddressArray(u32 addr)
{
u32 index = (addr >> 5) & 0xFF;
return lines[index].valid | (lines[index].address << 10);
}
void WriteAddressArray(u32 addr, u32 data)
{
u32 index = (addr >> 5) & 0xFF;
cache_line& line = lines[index];
bool associative = (addr & 8) != 0;
if (!associative)
{
line.valid = data & 1;
line.address = (data >> 10) & 0x7ffff;
}
else
{
const u32 vaddr = data & ~0x3ff;
bool cached;
u32 physAddr;
u32 err = translateAddress(vaddr, physAddr, cached);
if (err == MMU_ERROR_TLB_MISS)
// Ignore the write
return;
if (err != MMU_ERROR_NONE)
mmu_raise_exception(err, vaddr, MMU_TT_IREAD);
u32 tag = (physAddr >> 10) & 0x7ffff;
if (!line.valid || tag != line.address)
// Ignore the write
return;
line.valid = data & 1;
}
}
u32 ReadDataArray(u32 addr)
{
u32 index = (addr >> 5) & 0xFF;
cache_line& line = lines[index];
return *(u32 *)&line.data[addr & 0x1C];
}
void WriteDataArray(u32 addr, u32 data)
{
u32 index = (addr >> 5) & 0xFF;
cache_line& line = lines[index];
*(u32 *)&line.data[addr & 0x1C] = data;
}
private:
struct cache_line {
bool valid;
u32 address;
u8 data[32];
};
u32 translateAddress(u32 address, u32& physAddr, bool& cached)
{
// Alignment errors
if (address & 1)
return MMU_ERROR_BADADDR;
const u32 area = address >> 29;
const bool userMode = sr.MD == 0;
if (userMode)
{
// kernel mem protected in user mode
// FIXME this makes WinCE fail
//if (address & 0x80000000)
// return MMU_ERROR_BADADDR;
}
else
{
// P4 not executable
if (area == 7)
return MMU_ERROR_BADADDR;
}
cached = CCN_CCR.ICE == 1 && cachedArea(area);
if (CCN_MMUCR.AT == 0 || !translatedArea(area)
// 7C000000 to 7FFFFFFF in P0 not translated in supervisor mode
|| (!userMode && (address & 0xFC000000) == 0x7C000000))
{
physAddr = address;
}
else
{
const TLB_Entry *entry;
u32 err = mmu_instruction_lookup(address, &entry, physAddr);
if (err != MMU_ERROR_NONE)
return err;
//0X & User mode-> protection violation
//Priv mode protection
if (userMode)
{
u32 md = entry->Data.PR >> 1;
if (md == 0)
return MMU_ERROR_PROTECTED;
}
cached = cached && entry->Data.C == 1;
}
return MMU_ERROR_NONE;
}
std::array<cache_line, 256> lines;
};
extern sh4_icache icache;
//
// SH4 operand cache
//
class sh4_ocache
{
public:
template<class T>
T ReadMem(u32 address)
{
u32 physAddr;
bool cacheOn = false;
bool copyBack;
u32 err = translateAddress<T, MMU_TT_DREAD>(address, physAddr, cacheOn, copyBack);
if (err != MMU_ERROR_NONE)
mmu_raise_exception(err, address, MMU_TT_DREAD);
if (!cacheOn)
return _vmem_readt<T, T>(physAddr);
const u32 index = lineIndex(address);
cache_line& line = lines[index];
const u32 tag = (physAddr >> 10) & 0x7ffff;
if (!line.valid || tag != line.address)
{
// miss
if (line.dirty && line.valid)
// write-back needed
doWriteBack(index, line);
line.address = tag;
readCacheLine(physAddr, line);
}
return *(T*)&line.data[physAddr & 0x1f];
}
template<class T>
void WriteMem(u32 address, T data)
{
u32 physAddr = 0;
bool cacheOn = false;
bool copyBack = false;
u32 err = translateAddress<T, MMU_TT_DWRITE>(address, physAddr, cacheOn, copyBack);
if (err != MMU_ERROR_NONE)
mmu_raise_exception(err, address, MMU_TT_DWRITE);
if (!cacheOn)
{
_vmem_writet<T>(physAddr, data);
return;
}
const u32 index = lineIndex(address);
cache_line& line = lines[index];
const u32 tag = (physAddr >> 10) & 0x7ffff;
if (!line.valid || tag != line.address)
{
// miss and copy-back => read cache line
if (copyBack)
{
if (line.dirty && line.valid)
// write-back needed
doWriteBack(index, line);
line.address = tag;
readCacheLine(physAddr, line);
}
}
else if (!copyBack)
{
// hit and write-through => update cache
*(T*)&line.data[physAddr & 0x1f] = data;
}
if (copyBack)
{
// copy-back => update cache and mark line as dirty
line.dirty = true;
*(T*)&line.data[physAddr & 0x1f] = data;
}
else
{
// write-through => update main ram
_vmem_writet<T>(physAddr, data);
}
}
void WriteBack(u32 address, bool write_back, bool invalidate)
{
u32 physAddr;
bool cached = false;
bool copyBack;
u32 err = translateAddress<u8, MMU_TT_DWRITE>(address, physAddr, cached, copyBack);
if (err != MMU_ERROR_NONE)
mmu_raise_exception(err, address, MMU_TT_DWRITE);
if (!cached)
return;
const u32 index = lineIndex(address);
cache_line& line = lines[index];
const u32 tag = (physAddr >> 10) & 0x7ffff;
if (!line.valid || tag != line.address)
return;
if (write_back && line.dirty)
doWriteBack(index, line);
line.valid = !invalidate;
line.dirty = false;
}
void Prefetch(u32 address)
{
address &= ~0x1F;
u32 physAddr;
bool cached;
bool copyBack;
u32 err = translateAddress<u8, MMU_TT_DREAD>(address, physAddr, cached, copyBack);
if (err != MMU_ERROR_NONE || !cached)
// ignore address translation errors
return;
const u32 index = lineIndex(address);
cache_line& line = lines[index];
const u32 tag = (physAddr >> 10) & 0x7ffff;
if (line.valid && tag == line.address)
return;
if (line.valid && line.dirty)
doWriteBack(index, line);
line.address = tag;
readCacheLine(physAddr, line);
}
void Invalidate()
{
for (auto& line : lines)
{
line.dirty = false;
line.valid = false;
}
}
void Reset(bool hard)
{
if (hard)
memset(&lines[0], 0, sizeof(lines));
}
bool Serialize(void **data, unsigned int *total_size)
{
REICAST_S(lines);
return true;
}
bool Unserialize(void **data, unsigned int *total_size)
{
REICAST_US(lines);
return true;
}
u32 ReadAddressArray(u32 addr)
{
u32 index = (addr >> 5) & 0x1FF;
return lines[index].valid | (lines[index].dirty << 1) | (lines[index].address << 10);
}
void WriteAddressArray(u32 addr, u32 data)
{
u32 index = (addr >> 5) & 0x1FF;
cache_line& line = lines[index];
bool associative = (addr & 8) != 0;
if (!associative)
{
if (line.valid && line.dirty)
doWriteBack(index, line);
line.address = (data >> 10) & 0x7ffff;
}
else
{
u32 physAddr;
bool cached = false;
bool copyBack;
u32 err = translateAddress<u8, MMU_TT_DREAD>(data & ~0x3ff, physAddr, cached, copyBack);
if (err == MMU_ERROR_TLB_MISS)
// Ignore the write
return;
if (err != MMU_ERROR_NONE)
mmu_raise_exception(err, data & ~0x3ff, MMU_TT_DREAD);
u32 tag = (physAddr >> 10) & 0x7ffff;
if (!line.valid || tag != line.address)
// Ignore the write
return;
if ((data & 3) != 0 && line.dirty)
doWriteBack(index, line);
}
line.valid = data & 1;
line.dirty = (data >> 1) & 1;
}
u32 ReadDataArray(u32 addr)
{
u32 index = (addr >> 5) & 0x1FF;
cache_line& line = lines[index];
return *(u32 *)&line.data[addr & 0x1C];
}
void WriteDataArray(u32 addr, u32 data)
{
u32 index = (addr >> 5) & 0x1FF;
cache_line& line = lines[index];
*(u32 *)&line.data[addr & 0x1C] = data;
}
void WriteBackAll()
{
for (cache_line& line : lines)
{
if (line.valid && line.dirty)
doWriteBack((u32)(&line - &lines[0]), line);
line.valid = false;
line.dirty = false;
}
}
private:
struct cache_line {
bool valid;
bool dirty;
u32 address;
u8 data[32];
};
u32 lineIndex(u32 address)
{
u32 index = CCN_CCR.OIX ?
((address >> (25 - 8)) & 0x100) | ((address >> 5) & (CCN_CCR.ORA ? 0x7f : 0xff))
: (address >> 5) & (CCN_CCR.ORA ? 0x17f : 0x1ff);
if (CCN_CCR.ORA && (address >> 29) == 3)
index |= 0x80;
return index;
}
void readCacheLine(u32 address, cache_line& line)
{
line.valid = true;
line.dirty = false;
const u32 line_addr = address & ~0x1f;
u8* memPtr = GetMemPtr(line_addr, sizeof(line.data));
if (memPtr != nullptr)
memcpy(line.data, memPtr, sizeof(line.data));
else
{
u32 *p = (u32 *)line.data;
for (int i = 0; i < 32; i += 4)
*p++ = _vmem_ReadMem32(line_addr + i);
}
}
void doWriteBack(u32 index, cache_line& line)
{
if (CCN_CCR.ORA && (index & 0x80))
return;
u32 line_addr = (line.address << 10) | ((index & 0x1F) << 5);
u8* memPtr = GetMemPtr(line_addr, sizeof(line.data));
if (memPtr != nullptr)
memcpy(memPtr, line.data, sizeof(line.data));
else
{
u32 *p = (u32 *)line.data;
for (int i = 0; i < 32; i += 4)
_vmem_WriteMem32(line_addr + i, *p++);
}
}
template<class T, u32 ACCESS>
u32 translateAddress(u32 address, u32& physAddr, bool& cached, bool& copyBack)
{
// Alignment errors
if (address & (sizeof(T) - 1))
return MMU_ERROR_BADADDR;
if (ACCESS == MMU_TT_DWRITE && (address & 0xFC000000) == 0xE0000000)
{
// Store queues
u32 rv;
u32 lookup = mmu_full_SQ<MMU_TT_DWRITE>(address, rv);
physAddr = address;
return lookup;
}
const u32 area = address >> 29;
const bool userMode = sr.MD == 0;
// kernel mem protected in user mode
if (userMode && (address & 0x80000000))
return MMU_ERROR_BADADDR;
cached = CCN_CCR.OCE == 1 && cachedArea(area);
if (ACCESS == MMU_TT_DWRITE)
// Use CCR.CB if P1 otherwise use !CCR.WT
copyBack = area == 4 ? CCN_CCR.CB : !CCN_CCR.WT;
if (CCN_MMUCR.AT == 0 || !translatedArea(area)
// 7C000000 to 7FFFFFFF in P0 not translated in supervisor mode
|| (!userMode && (address & 0xFC000000) == 0x7C000000))
{
physAddr = address;
}
else
{
const TLB_Entry *entry;
u32 lookup = mmu_full_lookup(address, &entry, physAddr);
if (lookup != MMU_ERROR_NONE)
return lookup;
//0X & User mode-> protection violation
//Priv mode protection
if (userMode)
{
u32 md = entry->Data.PR >> 1;
if (md == 0)
return MMU_ERROR_PROTECTED;
}
//X0 -> read only
//X1 -> read/write , can be FW
if (ACCESS == MMU_TT_DWRITE)
{
if ((entry->Data.PR & 1) == 0)
return MMU_ERROR_PROTECTED;
if (entry->Data.D == 0)
return MMU_ERROR_FIRSTWRITE;
copyBack = copyBack && entry->Data.WT == 0;
}
cached = cached && entry->Data.C == 1;
}
return MMU_ERROR_NONE;
}
std::array<cache_line, 512> lines;
};
extern sh4_ocache ocache;
template<class T>
T ReadCachedMem(u32 address)
{
return ocache.ReadMem<T>(address);
}
template<class T>
void WriteCachedMem(u32 address, T data)
{
ocache.WriteMem<T>(address, data);
}
static inline u16 IReadCachedMem(u32 address)
{
return icache.ReadMem(address);
}