Support expanding RAM to 8MB (dev console)

This commit is contained in:
Connor McLaughlin 2021-05-02 20:46:48 +10:00
parent d021850394
commit e382df0d41
21 changed files with 146 additions and 78 deletions

View File

@ -71,8 +71,11 @@ union MEMCTRL
}; };
}; };
std::bitset<RAM_CODE_PAGE_COUNT> m_ram_code_bits{}; std::bitset<RAM_8MB_CODE_PAGE_COUNT> m_ram_code_bits{};
u8* g_ram = nullptr; // 2MB RAM u32 m_ram_code_page_count = 0;
u8* g_ram = nullptr; // 2MB RAM
u32 g_ram_size = 0;
u32 g_ram_mask = 0;
u8 g_bios[BIOS_SIZE]{}; // 512K BIOS ROM u8 g_bios[BIOS_SIZE]{}; // 512K BIOS ROM
static std::array<TickCount, 3> m_exp1_access_time = {}; static std::array<TickCount, 3> m_exp1_access_time = {};
@ -106,7 +109,7 @@ static constexpr auto m_fastmem_ram_mirrors =
static std::tuple<TickCount, TickCount, TickCount> CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay); static std::tuple<TickCount, TickCount, TickCount> CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay);
static void RecalculateMemoryTimings(); static void RecalculateMemoryTimings();
static bool AllocateMemory(); static bool AllocateMemory(bool enable_8mb_ram);
static void ReleaseMemory(); static void ReleaseMemory();
static void SetCodePageFastmemProtection(u32 page_index, bool writable); static void SetCodePageFastmemProtection(u32 page_index, bool writable);
@ -125,7 +128,7 @@ static void SetCodePageFastmemProtection(u32 page_index, bool writable);
bool Initialize() bool Initialize()
{ {
if (!AllocateMemory()) if (!AllocateMemory(g_settings.enable_8mb_ram))
{ {
g_host_interface->ReportError("Failed to allocate memory"); g_host_interface->ReportError("Failed to allocate memory");
return false; return false;
@ -153,7 +156,7 @@ void Shutdown()
void Reset() void Reset()
{ {
std::memset(g_ram, 0, RAM_SIZE); std::memset(g_ram, 0, g_ram_size);
m_MEMCTRL.exp1_base = 0x1F000000; m_MEMCTRL.exp1_base = 0x1F000000;
m_MEMCTRL.exp2_base = 0x1F802000; m_MEMCTRL.exp2_base = 0x1F802000;
m_MEMCTRL.exp1_delay_size.bits = 0x0013243F; m_MEMCTRL.exp1_delay_size.bits = 0x0013243F;
@ -170,12 +173,25 @@ void Reset()
bool DoState(StateWrapper& sw) bool DoState(StateWrapper& sw)
{ {
u32 ram_size = g_ram_size;
sw.DoEx(&ram_size, 52, static_cast<u32>(RAM_2MB_SIZE));
if (ram_size != g_ram_size)
{
const bool using_8mb_ram = (ram_size == RAM_8MB_SIZE);
ReleaseMemory();
if (!AllocateMemory(using_8mb_ram))
return false;
UpdateFastmemViews(m_fastmem_mode);
CPU::UpdateFastmemBase();
}
sw.Do(&m_exp1_access_time); sw.Do(&m_exp1_access_time);
sw.Do(&m_exp2_access_time); sw.Do(&m_exp2_access_time);
sw.Do(&m_bios_access_time); sw.Do(&m_bios_access_time);
sw.Do(&m_cdrom_access_time); sw.Do(&m_cdrom_access_time);
sw.Do(&m_spu_access_time); sw.Do(&m_spu_access_time);
sw.DoBytes(g_ram, RAM_SIZE); sw.DoBytes(g_ram, g_ram_size);
sw.DoBytes(g_bios, BIOS_SIZE); sw.DoBytes(g_bios, BIOS_SIZE);
sw.DoArray(m_MEMCTRL.regs, countof(m_MEMCTRL.regs)); sw.DoArray(m_MEMCTRL.regs, countof(m_MEMCTRL.regs));
sw.Do(&m_ram_size_reg); sw.Do(&m_ram_size_reg);
@ -255,7 +271,7 @@ void RecalculateMemoryTimings()
m_spu_access_time[2] + 1); m_spu_access_time[2] + 1);
} }
bool AllocateMemory() bool AllocateMemory(bool enable_8mb_ram)
{ {
if (!m_memory_arena.Create(MEMORY_ARENA_SIZE, true, false)) if (!m_memory_arena.Create(MEMORY_ARENA_SIZE, true, false))
{ {
@ -264,14 +280,20 @@ bool AllocateMemory()
} }
// Create the base views. // Create the base views.
g_ram = static_cast<u8*>(m_memory_arena.CreateViewPtr(MEMORY_ARENA_RAM_OFFSET, RAM_SIZE, true, false)); const u32 ram_size = enable_8mb_ram ? RAM_8MB_SIZE : RAM_2MB_SIZE;
const u32 ram_mask = enable_8mb_ram ? RAM_8MB_MASK : RAM_2MB_MASK;
g_ram = static_cast<u8*>(m_memory_arena.CreateViewPtr(MEMORY_ARENA_RAM_OFFSET, ram_size, true, false));
if (!g_ram) if (!g_ram)
{ {
Log_ErrorPrint("Failed to create base views of memory"); Log_ErrorPrintf("Failed to create base views of memory (%u bytes RAM)", ram_size);
return false; return false;
} }
Log_InfoPrintf("RAM is %u bytes at %p", RAM_SIZE, g_ram); g_ram_mask = ram_mask;
g_ram_size = ram_size;
m_ram_code_page_count = enable_8mb_ram ? RAM_8MB_CODE_PAGE_COUNT : RAM_2MB_CODE_PAGE_COUNT;
Log_InfoPrintf("RAM is %u bytes at %p", g_ram_size, g_ram);
return true; return true;
} }
@ -279,8 +301,10 @@ void ReleaseMemory()
{ {
if (g_ram) if (g_ram)
{ {
m_memory_arena.ReleaseViewPtr(g_ram, RAM_SIZE); m_memory_arena.ReleaseViewPtr(g_ram, g_ram_size);
g_ram = nullptr; g_ram = nullptr;
g_ram_mask = 0;
g_ram_size = 0;
} }
m_memory_arena.Destroy(); m_memory_arena.Destroy();
@ -354,15 +378,15 @@ void UpdateFastmemViews(CPUFastmemMode mode)
auto MapRAM = [](u32 base_address) { auto MapRAM = [](u32 base_address) {
u8* map_address = m_fastmem_base + base_address; u8* map_address = m_fastmem_base + base_address;
auto view = m_memory_arena.CreateView(MEMORY_ARENA_RAM_OFFSET, RAM_SIZE, true, false, map_address); auto view = m_memory_arena.CreateView(MEMORY_ARENA_RAM_OFFSET, g_ram_size, true, false, map_address);
if (!view) if (!view)
{ {
Log_ErrorPrintf("Failed to map RAM at fastmem area %p (offset 0x%08X)", map_address, RAM_SIZE); Log_ErrorPrintf("Failed to map RAM at fastmem area %p (offset 0x%08X)", map_address, g_ram_size);
return; return;
} }
// mark all pages with code as non-writable // mark all pages with code as non-writable
for (u32 i = 0; i < RAM_CODE_PAGE_COUNT; i++) for (u32 i = 0; i < m_ram_code_page_count; i++)
{ {
if (m_ram_code_bits[i]) if (m_ram_code_bits[i])
{ {
@ -386,7 +410,7 @@ void UpdateFastmemViews(CPUFastmemMode mode)
auto view = m_memory_arena.CreateReservedView(end_address_inclusive - start_address + 1, map_address); auto view = m_memory_arena.CreateReservedView(end_address_inclusive - start_address + 1, map_address);
if (!view) if (!view)
{ {
Log_ErrorPrintf("Failed to map RAM at fastmem area %p (offset 0x%08X)", map_address, RAM_SIZE); Log_ErrorPrintf("Failed to map reserved region %p (size 0x%08X)", map_address, end_address_inclusive - start_address + 1);
return; return;
} }
@ -432,7 +456,7 @@ void UpdateFastmemViews(CPUFastmemMode mode)
} }
auto MapRAM = [](u32 base_address) { auto MapRAM = [](u32 base_address) {
for (u32 address = 0; address < RAM_SIZE; address += HOST_PAGE_SIZE) for (u32 address = 0; address < g_ram_size; address += HOST_PAGE_SIZE)
{ {
SetLUTFastmemPage(base_address + address, &g_ram[address], SetLUTFastmemPage(base_address + address, &g_ram[address],
!m_ram_code_bits[FastmemAddressToLUTPageIndex(address)]); !m_ram_code_bits[FastmemAddressToLUTPageIndex(address)]);
@ -474,7 +498,7 @@ bool CanUseFastmemForAddress(VirtualMemoryAddress address)
#endif #endif
case CPUFastmemMode::LUT: case CPUFastmemMode::LUT:
return (paddr < RAM_SIZE); return (paddr < g_ram_size);
case CPUFastmemMode::Disabled: case CPUFastmemMode::Disabled:
default: default:
@ -556,7 +580,7 @@ void ClearRAMCodePageFlags()
if (m_fastmem_mode == CPUFastmemMode::LUT) if (m_fastmem_mode == CPUFastmemMode::LUT)
{ {
for (u32 i = 0; i < RAM_CODE_PAGE_COUNT; i++) for (u32 i = 0; i < m_ram_code_page_count; i++)
{ {
const u32 addr = (i * HOST_PAGE_SIZE); const u32 addr = (i * HOST_PAGE_SIZE);
for (u32 mirror_start : m_fastmem_ram_mirrors) for (u32 mirror_start : m_fastmem_ram_mirrors)
@ -567,7 +591,7 @@ void ClearRAMCodePageFlags()
bool IsCodePageAddress(PhysicalMemoryAddress address) bool IsCodePageAddress(PhysicalMemoryAddress address)
{ {
return IsRAMAddress(address) ? m_ram_code_bits[(address & RAM_MASK) / HOST_PAGE_SIZE] : false; return IsRAMAddress(address) ? m_ram_code_bits[(address & g_ram_mask) / HOST_PAGE_SIZE] : false;
} }
bool HasCodePagesInRange(PhysicalMemoryAddress start_address, u32 size) bool HasCodePagesInRange(PhysicalMemoryAddress start_address, u32 size)
@ -575,7 +599,7 @@ bool HasCodePagesInRange(PhysicalMemoryAddress start_address, u32 size)
if (!IsRAMAddress(start_address)) if (!IsRAMAddress(start_address))
return false; return false;
start_address = (start_address & RAM_MASK); start_address = (start_address & g_ram_mask);
const u32 end_address = start_address + size; const u32 end_address = start_address + size;
while (start_address < end_address) while (start_address < end_address)
@ -592,10 +616,10 @@ bool HasCodePagesInRange(PhysicalMemoryAddress start_address, u32 size)
std::optional<MemoryRegion> GetMemoryRegionForAddress(PhysicalMemoryAddress address) std::optional<MemoryRegion> GetMemoryRegionForAddress(PhysicalMemoryAddress address)
{ {
if (address < RAM_SIZE) if (address < RAM_2MB_SIZE)
return MemoryRegion::RAM; return MemoryRegion::RAM;
else if (address < RAM_MIRROR_END) else if (address < RAM_MIRROR_END)
return static_cast<MemoryRegion>(static_cast<u32>(MemoryRegion::RAM) + (address / RAM_SIZE)); return static_cast<MemoryRegion>(static_cast<u32>(MemoryRegion::RAM) + (address / RAM_2MB_SIZE));
else if (address >= EXP1_BASE && address < (EXP1_BASE + EXP1_SIZE)) else if (address >= EXP1_BASE && address < (EXP1_BASE + EXP1_SIZE))
return MemoryRegion::EXP1; return MemoryRegion::EXP1;
else if (address >= CPU::DCACHE_LOCATION && address < (CPU::DCACHE_LOCATION + CPU::DCACHE_SIZE)) else if (address >= CPU::DCACHE_LOCATION && address < (CPU::DCACHE_LOCATION + CPU::DCACHE_SIZE))
@ -609,10 +633,10 @@ std::optional<MemoryRegion> GetMemoryRegionForAddress(PhysicalMemoryAddress addr
static constexpr std::array<std::pair<PhysicalMemoryAddress, PhysicalMemoryAddress>, static constexpr std::array<std::pair<PhysicalMemoryAddress, PhysicalMemoryAddress>,
static_cast<u32>(MemoryRegion::Count)> static_cast<u32>(MemoryRegion::Count)>
s_code_region_ranges = {{ s_code_region_ranges = {{
{0, RAM_SIZE}, {0, RAM_2MB_SIZE},
{RAM_SIZE, RAM_SIZE * 2}, {RAM_2MB_SIZE, RAM_2MB_SIZE * 2},
{RAM_SIZE * 2, RAM_SIZE * 3}, {RAM_2MB_SIZE * 2, RAM_2MB_SIZE * 3},
{RAM_SIZE * 3, RAM_MIRROR_END}, {RAM_2MB_SIZE * 3, RAM_MIRROR_END},
{EXP1_BASE, EXP1_BASE + EXP1_SIZE}, {EXP1_BASE, EXP1_BASE + EXP1_SIZE},
{CPU::DCACHE_LOCATION, CPU::DCACHE_LOCATION + CPU::DCACHE_SIZE}, {CPU::DCACHE_LOCATION, CPU::DCACHE_LOCATION + CPU::DCACHE_SIZE},
{BIOS_BASE, BIOS_BASE + BIOS_SIZE}, {BIOS_BASE, BIOS_BASE + BIOS_SIZE},
@ -633,11 +657,17 @@ u8* GetMemoryRegionPointer(MemoryRegion region)
switch (region) switch (region)
{ {
case MemoryRegion::RAM: case MemoryRegion::RAM:
case MemoryRegion::RAMMirror1:
case MemoryRegion::RAMMirror2:
case MemoryRegion::RAMMirror3:
return g_ram; return g_ram;
case MemoryRegion::RAMMirror1:
return (g_ram + (RAM_2MB_SIZE & g_ram_mask));
case MemoryRegion::RAMMirror2:
return (g_ram + ((RAM_2MB_SIZE * 2) & g_ram_mask));
case MemoryRegion::RAMMirror3:
return (g_ram + ((RAM_8MB_SIZE * 3) & g_ram_mask));
case MemoryRegion::EXP1: case MemoryRegion::EXP1:
return nullptr; return nullptr;
@ -734,14 +764,13 @@ static TickCount DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, P
if (type == MemoryAccessType::Read) if (type == MemoryAccessType::Read)
value = UINT32_C(0xFFFFFFFF); value = UINT32_C(0xFFFFFFFF);
return 1; return (type == MemoryAccessType::Read) ? 1 : 0;
} }
template<MemoryAccessType type, MemoryAccessSize size> template<MemoryAccessType type, MemoryAccessSize size>
ALWAYS_INLINE static TickCount DoRAMAccess(u32 offset, u32& value) ALWAYS_INLINE static TickCount DoRAMAccess(u32 offset, u32& value)
{ {
// TODO: Configurable mirroring. offset &= g_ram_mask;
offset &= UINT32_C(0x1FFFFF);
if constexpr (type == MemoryAccessType::Read) if constexpr (type == MemoryAccessType::Read)
{ {
if constexpr (size == MemoryAccessSize::Byte) if constexpr (size == MemoryAccessSize::Byte)
@ -1270,7 +1299,7 @@ ALWAYS_INLINE_RELEASE bool DoInstructionRead(PhysicalMemoryAddress address, void
if (address < RAM_MIRROR_END) if (address < RAM_MIRROR_END)
{ {
std::memcpy(data, &g_ram[address & RAM_MASK], sizeof(u32) * word_count); std::memcpy(data, &g_ram[address & g_ram_mask], sizeof(u32) * word_count);
if constexpr (add_ticks) if constexpr (add_ticks)
g_state.pending_ticks += (icache_read ? 1 : RAM_READ_TICKS) * word_count; g_state.pending_ticks += (icache_read ? 1 : RAM_READ_TICKS) * word_count;
@ -1523,7 +1552,7 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
} }
} }
if (address < 0x800000) if (address < RAM_MIRROR_END)
{ {
return DoRAMAccess<type, size>(address, value); return DoRAMAccess<type, size>(address, value);
} }
@ -1860,7 +1889,7 @@ void* GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize
if (read_ticks) if (read_ticks)
*read_ticks = RAM_READ_TICKS; *read_ticks = RAM_READ_TICKS;
return &g_ram[paddr & RAM_MASK]; return &g_ram[paddr & g_ram_mask];
} }
if ((paddr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION) if ((paddr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)

View File

@ -15,8 +15,10 @@ namespace Bus {
enum : u32 enum : u32
{ {
RAM_BASE = 0x00000000, RAM_BASE = 0x00000000,
RAM_SIZE = 0x200000, RAM_2MB_SIZE = 0x200000,
RAM_MASK = RAM_SIZE - 1, RAM_2MB_MASK = RAM_2MB_SIZE - 1,
RAM_8MB_SIZE = 0x800000,
RAM_8MB_MASK = RAM_8MB_SIZE - 1,
RAM_MIRROR_END = 0x800000, RAM_MIRROR_END = 0x800000,
EXP1_BASE = 0x1F000000, EXP1_BASE = 0x1F000000,
EXP1_SIZE = 0x800000, EXP1_SIZE = 0x800000,
@ -78,7 +80,7 @@ enum : TickCount
enum : size_t enum : size_t
{ {
// Our memory arena contains storage for RAM. // Our memory arena contains storage for RAM.
MEMORY_ARENA_SIZE = RAM_SIZE, MEMORY_ARENA_SIZE = RAM_8MB_SIZE,
// Offsets within the memory arena. // Offsets within the memory arena.
MEMORY_ARENA_RAM_OFFSET = 0, MEMORY_ARENA_RAM_OFFSET = 0,
@ -87,8 +89,12 @@ enum : size_t
// Fastmem region size is 4GB to cover the entire 32-bit address space. // Fastmem region size is 4GB to cover the entire 32-bit address space.
FASTMEM_REGION_SIZE = UINT64_C(0x100000000), FASTMEM_REGION_SIZE = UINT64_C(0x100000000),
#endif #endif
};
RAM_CODE_PAGE_COUNT = (RAM_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE, enum : u32
{
RAM_2MB_CODE_PAGE_COUNT = (RAM_2MB_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE,
RAM_8MB_CODE_PAGE_COUNT = (RAM_8MB_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE,
FASTMEM_LUT_NUM_PAGES = 0x100000, // 0x100000000 >> 12 FASTMEM_LUT_NUM_PAGES = 0x100000, // 0x100000000 >> 12
FASTMEM_LUT_NUM_SLOTS = FASTMEM_LUT_NUM_PAGES * 2, FASTMEM_LUT_NUM_SLOTS = FASTMEM_LUT_NUM_PAGES * 2,
@ -107,8 +113,10 @@ bool CanUseFastmemForAddress(VirtualMemoryAddress address);
void SetExpansionROM(std::vector<u8> data); void SetExpansionROM(std::vector<u8> data);
void SetBIOS(const std::vector<u8>& image); void SetBIOS(const std::vector<u8>& image);
extern std::bitset<RAM_CODE_PAGE_COUNT> m_ram_code_bits; extern std::bitset<RAM_8MB_CODE_PAGE_COUNT> m_ram_code_bits;
extern u8* g_ram; // 2MB RAM extern u8* g_ram; // 2MB-8MB RAM
extern u32 g_ram_size; // Active size of RAM.
extern u32 g_ram_mask; // Active address bits for RAM.
extern u8 g_bios[BIOS_SIZE]; // 512K BIOS ROM extern u8 g_bios[BIOS_SIZE]; // 512K BIOS ROM
/// Returns true if the address specified is writable (RAM). /// Returns true if the address specified is writable (RAM).
@ -120,7 +128,7 @@ ALWAYS_INLINE static bool IsRAMAddress(PhysicalMemoryAddress address)
/// Returns the code page index for a RAM address. /// Returns the code page index for a RAM address.
ALWAYS_INLINE static u32 GetRAMCodePageIndex(PhysicalMemoryAddress address) ALWAYS_INLINE static u32 GetRAMCodePageIndex(PhysicalMemoryAddress address)
{ {
return (address & RAM_MASK) / HOST_PAGE_SIZE; return (address & g_ram_mask) / HOST_PAGE_SIZE;
} }
/// Returns true if the specified page contains code. /// Returns true if the specified page contains code.

View File

@ -54,7 +54,7 @@ static T DoMemoryRead(PhysicalMemoryAddress address)
if (address < Bus::RAM_MIRROR_END) if (address < Bus::RAM_MIRROR_END)
{ {
std::memcpy(&result, &Bus::g_ram[address & Bus::RAM_MASK], sizeof(result)); std::memcpy(&result, &Bus::g_ram[address & Bus::g_ram_mask], sizeof(result));
return result; return result;
} }
@ -84,12 +84,12 @@ static void DoMemoryWrite(PhysicalMemoryAddress address, T value)
{ {
// Only invalidate code when it changes. // Only invalidate code when it changes.
T old_value; T old_value;
std::memcpy(&old_value, &Bus::g_ram[address & Bus::RAM_MASK], sizeof(old_value)); std::memcpy(&old_value, &Bus::g_ram[address & Bus::g_ram_mask], sizeof(old_value));
if (old_value != value) if (old_value != value)
{ {
std::memcpy(&Bus::g_ram[address & Bus::RAM_MASK], &value, sizeof(value)); std::memcpy(&Bus::g_ram[address & Bus::g_ram_mask], &value, sizeof(value));
const u32 code_page_index = Bus::GetRAMCodePageIndex(address & Bus::RAM_MASK); const u32 code_page_index = Bus::GetRAMCodePageIndex(address & Bus::g_ram_mask);
if (Bus::IsRAMCodePage(code_page_index)) if (Bus::IsRAMCodePage(code_page_index))
CPU::CodeCache::InvalidateBlocksWithPageIndex(code_page_index); CPU::CodeCache::InvalidateBlocksWithPageIndex(code_page_index);
} }

View File

@ -55,7 +55,7 @@ ALWAYS_INLINE static u32 GetFastMapIndex(u32 pc)
{ {
return ((pc & PHYSICAL_MEMORY_ADDRESS_MASK) >= Bus::BIOS_BASE) ? return ((pc & PHYSICAL_MEMORY_ADDRESS_MASK) >= Bus::BIOS_BASE) ?
(FAST_MAP_RAM_SLOT_COUNT + ((pc & Bus::BIOS_MASK) >> 2)) : (FAST_MAP_RAM_SLOT_COUNT + ((pc & Bus::BIOS_MASK) >> 2)) :
((pc & Bus::RAM_MASK) >> 2); ((pc & Bus::g_ram_mask) >> 2);
} }
static void CompileDispatcher(); static void CompileDispatcher();
@ -102,7 +102,7 @@ static void UnlinkBlock(CodeBlock* block);
static void ClearState(); static void ClearState();
static BlockMap s_blocks; static BlockMap s_blocks;
static std::array<std::vector<CodeBlock*>, Bus::RAM_CODE_PAGE_COUNT> m_ram_block_map; static std::array<std::vector<CodeBlock*>, Bus::RAM_8MB_CODE_PAGE_COUNT> m_ram_block_map;
#ifdef WITH_RECOMPILER #ifdef WITH_RECOMPILER
static HostCodeMap s_host_code_map; static HostCodeMap s_host_code_map;
@ -694,7 +694,7 @@ void FastCompileBlockFunction()
void InvalidateBlocksWithPageIndex(u32 page_index) void InvalidateBlocksWithPageIndex(u32 page_index)
{ {
DebugAssert(page_index < Bus::RAM_CODE_PAGE_COUNT); DebugAssert(page_index < Bus::RAM_8MB_CODE_PAGE_COUNT);
auto& blocks = m_ram_block_map[page_index]; auto& blocks = m_ram_block_map[page_index];
for (CodeBlock* block : blocks) for (CodeBlock* block : blocks)
{ {

View File

@ -18,7 +18,7 @@ namespace CPU {
enum : u32 enum : u32
{ {
FAST_MAP_RAM_SLOT_COUNT = Bus::RAM_SIZE / 4, FAST_MAP_RAM_SLOT_COUNT = Bus::RAM_8MB_SIZE / 4,
FAST_MAP_BIOS_SLOT_COUNT = Bus::BIOS_SIZE / 4, FAST_MAP_BIOS_SLOT_COUNT = Bus::BIOS_SIZE / 4,
FAST_MAP_TOTAL_SLOT_COUNT = FAST_MAP_RAM_SLOT_COUNT + FAST_MAP_BIOS_SLOT_COUNT, FAST_MAP_TOTAL_SLOT_COUNT = FAST_MAP_RAM_SLOT_COUNT + FAST_MAP_BIOS_SLOT_COUNT,
}; };

View File

@ -180,6 +180,9 @@ bool DoState(StateWrapper& sw)
sw.Do(&g_state.icache_data); sw.Do(&g_state.icache_data);
} }
if (sw.IsReading())
UpdateFastmemBase();
return !sw.HasError(); return !sw.HasError();
} }

View File

@ -2868,7 +2868,7 @@ CodeGenerator::SpeculativeValue CodeGenerator::SpeculativeReadMemory(VirtualMemo
if (Bus::IsRAMAddress(phys_addr)) if (Bus::IsRAMAddress(phys_addr))
{ {
u32 ram_offset = phys_addr & Bus::RAM_MASK; u32 ram_offset = phys_addr & Bus::g_ram_mask;
std::memcpy(&value, &Bus::g_ram[ram_offset], sizeof(value)); std::memcpy(&value, &Bus::g_ram[ram_offset], sizeof(value));
return value; return value;
} }

View File

@ -2004,7 +2004,7 @@ CodeCache::DispatcherFunction CodeGenerator::CompileDispatcher()
m_emit->str(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, current_instruction_pc))); m_emit->str(a32::r0, a32::MemOperand(GetHostReg32(RCPUPTR), offsetof(State, current_instruction_pc)));
// r1 <- (pc & RAM_MASK) >> 2 // r1 <- (pc & RAM_MASK) >> 2
m_emit->and_(a32::r1, a32::r0, Bus::RAM_MASK); m_emit->and_(a32::r1, a32::r0, Bus::g_ram_mask);
m_emit->lsr(a32::r1, a32::r1, 2); m_emit->lsr(a32::r1, a32::r1, 2);
// r2 <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT // r2 <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT

View File

@ -2219,7 +2219,7 @@ CodeCache::DispatcherFunction CodeGenerator::CompileDispatcher()
m_emit->str(a64::w8, a64::MemOperand(GetHostReg64(RCPUPTR), offsetof(State, current_instruction_pc))); m_emit->str(a64::w8, a64::MemOperand(GetHostReg64(RCPUPTR), offsetof(State, current_instruction_pc)));
// w9 <- (pc & RAM_MASK) >> 2 // w9 <- (pc & RAM_MASK) >> 2
m_emit->and_(a64::w9, a64::w8, Bus::RAM_MASK); m_emit->and_(a64::w9, a64::w8, Bus::g_ram_mask);
m_emit->lsr(a64::w9, a64::w9, 2); m_emit->lsr(a64::w9, a64::w9, 2);
// w10 <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT // w10 <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT

View File

@ -44,7 +44,7 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const
if (g_settings.IsUsingFastmem() && Bus::IsRAMAddress(static_cast<u32>(address.constant_value))) if (g_settings.IsUsingFastmem() && Bus::IsRAMAddress(static_cast<u32>(address.constant_value)))
{ {
// have to mask away the high bits for mirrors, since we don't map them in fastmem // have to mask away the high bits for mirrors, since we don't map them in fastmem
EmitLoadGuestRAMFastmem(Value::FromConstantU32(static_cast<u32>(address.constant_value) & Bus::RAM_MASK), size, EmitLoadGuestRAMFastmem(Value::FromConstantU32(static_cast<u32>(address.constant_value) & Bus::g_ram_mask), size,
result); result);
} }
else else

View File

@ -2968,7 +2968,7 @@ CodeCache::DispatcherFunction CodeGenerator::CompileDispatcher()
// ebx <- (pc & RAM_MASK) >> 2 // ebx <- (pc & RAM_MASK) >> 2
m_emit->mov(m_emit->ebx, m_emit->eax); m_emit->mov(m_emit->ebx, m_emit->eax);
m_emit->and_(m_emit->ebx, Bus::RAM_MASK); m_emit->and_(m_emit->ebx, Bus::g_ram_mask);
m_emit->shr(m_emit->ebx, 2); m_emit->shr(m_emit->ebx, 2);
// ecx <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT // ecx <- ((pc & BIOS_MASK) >> 2) + FAST_MAP_RAM_SLOT_COUNT

View File

@ -17,6 +17,11 @@
#endif #endif
Log_SetChannel(DMA); Log_SetChannel(DMA);
static u32 GetAddressMask()
{
return Bus::g_ram_mask & 0xFFFFFFFCu;
}
DMA g_dma; DMA g_dma;
DMA::DMA() = default; DMA::DMA() = default;
@ -293,6 +298,7 @@ TickCount DMA::GetTransferHaltTicks() const
bool DMA::TransferChannel(Channel channel) bool DMA::TransferChannel(Channel channel)
{ {
ChannelState& cs = m_state[static_cast<u32>(channel)]; ChannelState& cs = m_state[static_cast<u32>(channel)];
const u32 mask = GetAddressMask();
const bool copy_to_device = cs.channel_control.copy_to_device; const bool copy_to_device = cs.channel_control.copy_to_device;
@ -307,13 +313,13 @@ bool DMA::TransferChannel(Channel channel)
{ {
const u32 word_count = cs.block_control.manual.GetWordCount(); const u32 word_count = cs.block_control.manual.GetWordCount();
Log_DebugPrintf("DMA%u: Copying %u words %s 0x%08X", static_cast<u32>(channel), word_count, Log_DebugPrintf("DMA%u: Copying %u words %s 0x%08X", static_cast<u32>(channel), word_count,
copy_to_device ? "from" : "to", current_address & ADDRESS_MASK); copy_to_device ? "from" : "to", current_address & mask);
TickCount used_ticks; TickCount used_ticks;
if (copy_to_device) if (copy_to_device)
used_ticks = TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, word_count); used_ticks = TransferMemoryToDevice(channel, current_address & mask, increment, word_count);
else else
used_ticks = TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, word_count); used_ticks = TransferDeviceToMemory(channel, current_address & mask, increment, word_count);
CPU::AddPendingTicks(used_ticks); CPU::AddPendingTicks(used_ticks);
} }
@ -328,20 +334,20 @@ bool DMA::TransferChannel(Channel channel)
} }
Log_DebugPrintf("DMA%u: Copying linked list starting at 0x%08X to device", static_cast<u32>(channel), Log_DebugPrintf("DMA%u: Copying linked list starting at 0x%08X to device", static_cast<u32>(channel),
current_address & ADDRESS_MASK); current_address & mask);
u8* ram_pointer = Bus::g_ram; u8* ram_pointer = Bus::g_ram;
TickCount remaining_ticks = GetTransferSliceTicks(); TickCount remaining_ticks = GetTransferSliceTicks();
while (cs.request && remaining_ticks > 0) while (cs.request && remaining_ticks > 0)
{ {
u32 header; u32 header;
std::memcpy(&header, &ram_pointer[current_address & ADDRESS_MASK], sizeof(header)); std::memcpy(&header, &ram_pointer[current_address & mask], sizeof(header));
CPU::AddPendingTicks(10); CPU::AddPendingTicks(10);
remaining_ticks -= 10; remaining_ticks -= 10;
const u32 word_count = header >> 24; const u32 word_count = header >> 24;
const u32 next_address = header & UINT32_C(0x00FFFFFF); const u32 next_address = header & UINT32_C(0x00FFFFFF);
Log_TracePrintf(" .. linked list entry at 0x%08X size=%u(%u words) next=0x%08X", current_address & ADDRESS_MASK, Log_TracePrintf(" .. linked list entry at 0x%08X size=%u(%u words) next=0x%08X", current_address & mask,
word_count * UINT32_C(4), word_count, next_address); word_count * UINT32_C(4), word_count, next_address);
if (word_count > 0) if (word_count > 0)
{ {
@ -349,7 +355,7 @@ bool DMA::TransferChannel(Channel channel)
remaining_ticks -= 5; remaining_ticks -= 5;
const TickCount block_ticks = const TickCount block_ticks =
TransferMemoryToDevice(channel, (current_address + sizeof(header)) & ADDRESS_MASK, 4, word_count); TransferMemoryToDevice(channel, (current_address + sizeof(header)) & mask, 4, word_count);
CPU::AddPendingTicks(block_ticks); CPU::AddPendingTicks(block_ticks);
remaining_ticks -= block_ticks; remaining_ticks -= block_ticks;
} }
@ -383,7 +389,7 @@ bool DMA::TransferChannel(Channel channel)
Log_DebugPrintf("DMA%u: Copying %u blocks of size %u (%u total words) %s 0x%08X", static_cast<u32>(channel), Log_DebugPrintf("DMA%u: Copying %u blocks of size %u (%u total words) %s 0x%08X", static_cast<u32>(channel),
cs.block_control.request.GetBlockCount(), cs.block_control.request.GetBlockSize(), cs.block_control.request.GetBlockCount(), cs.block_control.request.GetBlockSize(),
cs.block_control.request.GetBlockCount() * cs.block_control.request.GetBlockSize(), cs.block_control.request.GetBlockCount() * cs.block_control.request.GetBlockSize(),
copy_to_device ? "from" : "to", current_address & ADDRESS_MASK); copy_to_device ? "from" : "to", current_address & mask);
const u32 block_size = cs.block_control.request.GetBlockSize(); const u32 block_size = cs.block_control.request.GetBlockSize();
u32 blocks_remaining = cs.block_control.request.GetBlockCount(); u32 blocks_remaining = cs.block_control.request.GetBlockCount();
@ -395,8 +401,7 @@ bool DMA::TransferChannel(Channel channel)
{ {
blocks_remaining--; blocks_remaining--;
const TickCount ticks = const TickCount ticks = TransferMemoryToDevice(channel, current_address & mask, increment, block_size);
TransferMemoryToDevice(channel, current_address & ADDRESS_MASK, increment, block_size);
CPU::AddPendingTicks(ticks); CPU::AddPendingTicks(ticks);
ticks_remaining -= ticks; ticks_remaining -= ticks;
@ -409,8 +414,7 @@ bool DMA::TransferChannel(Channel channel)
{ {
blocks_remaining--; blocks_remaining--;
const TickCount ticks = const TickCount ticks = TransferDeviceToMemory(channel, current_address & mask, increment, block_size);
TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, block_size);
CPU::AddPendingTicks(ticks); CPU::AddPendingTicks(ticks);
ticks_remaining -= ticks; ticks_remaining -= ticks;
@ -490,8 +494,9 @@ void DMA::UnhaltTransfer(TickCount ticks)
TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count) TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count)
{ {
const u32* src_pointer = reinterpret_cast<u32*>(Bus::g_ram + address); const u32* src_pointer = reinterpret_cast<u32*>(Bus::g_ram + address);
const u32 mask = GetAddressMask();
if (channel != Channel::GPU && if (channel != Channel::GPU &&
(static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address)) (static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & mask) <= address))
{ {
// Use temp buffer if it's wrapping around // Use temp buffer if it's wrapping around
if (m_transfer_buffer.size() < word_count) if (m_transfer_buffer.size() < word_count)
@ -502,7 +507,7 @@ TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 incremen
for (u32 i = 0; i < word_count; i++) for (u32 i = 0; i < word_count; i++)
{ {
std::memcpy(&m_transfer_buffer[i], &ram_pointer[address], sizeof(u32)); std::memcpy(&m_transfer_buffer[i], &ram_pointer[address], sizeof(u32));
address = (address + increment) & ADDRESS_MASK; address = (address + increment) & mask;
} }
} }
@ -518,7 +523,7 @@ TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 incremen
u32 value; u32 value;
std::memcpy(&value, &ram_pointer[address], sizeof(u32)); std::memcpy(&value, &ram_pointer[address], sizeof(u32));
g_gpu->DMAWrite(address, value); g_gpu->DMAWrite(address, value);
address = (address + increment) & ADDRESS_MASK; address = (address + increment) & mask;
} }
g_gpu->EndDMAWrite(); g_gpu->EndDMAWrite();
} }
@ -546,6 +551,8 @@ TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 incremen
TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count) TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count)
{ {
const u32 mask = GetAddressMask();
if (channel == Channel::OTC) if (channel == Channel::OTC)
{ {
// clear ordering table // clear ordering table
@ -553,9 +560,9 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen
const u32 word_count_less_1 = word_count - 1; const u32 word_count_less_1 = word_count - 1;
for (u32 i = 0; i < word_count_less_1; i++) for (u32 i = 0; i < word_count_less_1; i++)
{ {
u32 value = ((address - 4) & ADDRESS_MASK); u32 value = ((address - 4) & mask);
std::memcpy(&ram_pointer[address], &value, sizeof(value)); std::memcpy(&ram_pointer[address], &value, sizeof(value));
address = (address - 4) & ADDRESS_MASK; address = (address - 4) & mask;
} }
const u32 terminator = UINT32_C(0xFFFFFF); const u32 terminator = UINT32_C(0xFFFFFF);
@ -565,7 +572,7 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen
} }
u32* dest_pointer = reinterpret_cast<u32*>(&Bus::g_ram[address]); u32* dest_pointer = reinterpret_cast<u32*>(&Bus::g_ram[address]);
if (static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address) if (static_cast<s32>(increment) < 0 || ((address + (increment * word_count)) & mask) <= address)
{ {
// Use temp buffer if it's wrapping around // Use temp buffer if it's wrapping around
if (m_transfer_buffer.size() < word_count) if (m_transfer_buffer.size() < word_count)
@ -604,7 +611,7 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen
for (u32 i = 0; i < word_count; i++) for (u32 i = 0; i < word_count; i++)
{ {
std::memcpy(&ram_pointer[address], &m_transfer_buffer[i], sizeof(u32)); std::memcpy(&ram_pointer[address], &m_transfer_buffer[i], sizeof(u32));
address = (address + increment) & ADDRESS_MASK; address = (address + increment) & mask;
} }
} }

View File

@ -475,6 +475,7 @@ std::string HostInterface::GetShaderCacheBasePath() const
void HostInterface::SetDefaultSettings(SettingsInterface& si) void HostInterface::SetDefaultSettings(SettingsInterface& si)
{ {
si.SetStringValue("Console", "Region", Settings::GetConsoleRegionName(Settings::DEFAULT_CONSOLE_REGION)); si.SetStringValue("Console", "Region", Settings::GetConsoleRegionName(Settings::DEFAULT_CONSOLE_REGION));
si.SetBoolValue("Console", "Enable8MBRAM", false);
si.SetFloatValue("Main", "EmulationSpeed", 1.0f); si.SetFloatValue("Main", "EmulationSpeed", 1.0f);
si.SetFloatValue("Main", "FastForwardSpeed", 0.0f); si.SetFloatValue("Main", "FastForwardSpeed", 0.0f);

View File

@ -2,7 +2,7 @@
#include "types.h" #include "types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
static constexpr u32 SAVE_STATE_VERSION = 51; static constexpr u32 SAVE_STATE_VERSION = 52;
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42; static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION); static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);

View File

@ -149,6 +149,7 @@ void Settings::Load(SettingsInterface& si)
region = region =
ParseConsoleRegionName(si.GetStringValue("Console", "Region", "NTSC-U").c_str()).value_or(DEFAULT_CONSOLE_REGION); ParseConsoleRegionName(si.GetStringValue("Console", "Region", "NTSC-U").c_str()).value_or(DEFAULT_CONSOLE_REGION);
enable_8mb_ram = si.GetBoolValue("Console", "Enable8MBRAM", false);
emulation_speed = si.GetFloatValue("Main", "EmulationSpeed", 1.0f); emulation_speed = si.GetFloatValue("Main", "EmulationSpeed", 1.0f);
fast_forward_speed = si.GetFloatValue("Main", "FastForwardSpeed", 0.0f); fast_forward_speed = si.GetFloatValue("Main", "FastForwardSpeed", 0.0f);
@ -340,6 +341,7 @@ void Settings::Load(SettingsInterface& si)
void Settings::Save(SettingsInterface& si) const void Settings::Save(SettingsInterface& si) const
{ {
si.SetStringValue("Console", "Region", GetConsoleRegionName(region)); si.SetStringValue("Console", "Region", GetConsoleRegionName(region));
si.SetBoolValue("Console", "Enable8MBRAM", enable_8mb_ram);
si.SetFloatValue("Main", "EmulationSpeed", emulation_speed); si.SetFloatValue("Main", "EmulationSpeed", emulation_speed);
si.SetFloatValue("Main", "FastForwardSpeed", fast_forward_speed); si.SetFloatValue("Main", "FastForwardSpeed", fast_forward_speed);

View File

@ -212,6 +212,7 @@ struct Settings
bool bios_patch_tty_enable = false; bool bios_patch_tty_enable = false;
bool bios_patch_fast_boot = false; bool bios_patch_fast_boot = false;
bool enable_8mb_ram = false;
std::array<ControllerType, NUM_CONTROLLER_AND_CARD_PORTS> controller_types{}; std::array<ControllerType, NUM_CONTROLLER_AND_CARD_PORTS> controller_types{};
bool controller_disable_analog_mode_forcing = false; bool controller_disable_analog_mode_forcing = false;

View File

@ -1874,7 +1874,7 @@ bool DumpRAM(const char* filename)
if (!IsValid()) if (!IsValid())
return false; return false;
return FileSystem::WriteBinaryFile(filename, Bus::g_ram, Bus::RAM_SIZE); return FileSystem::WriteBinaryFile(filename, Bus::g_ram, Bus::g_ram_size);
} }
bool DumpVRAM(const char* filename) bool DumpVRAM(const char* filename)

View File

@ -125,7 +125,7 @@ void CheatManagerDialog::connectUi()
if (index == 0) if (index == 0)
{ {
m_ui.scanStartAddress->setText(formatHexValue(0, 8)); m_ui.scanStartAddress->setText(formatHexValue(0, 8));
m_ui.scanEndAddress->setText(formatHexValue(Bus::RAM_SIZE, 8)); m_ui.scanEndAddress->setText(formatHexValue(Bus::g_ram_size, 8));
} }
else if (index == 1) else if (index == 1)
{ {

View File

@ -31,6 +31,7 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region",
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName, &Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,
Settings::DEFAULT_CONSOLE_REGION); Settings::DEFAULT_CONSOLE_REGION);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enable8MBRAM, "Console", "Enable8MBRAM", false);
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.cpuExecutionMode, "CPU", "ExecutionMode", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.cpuExecutionMode, "CPU", "ExecutionMode",
&Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName, &Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName,
Settings::DEFAULT_CPU_EXECUTION_MODE); Settings::DEFAULT_CPU_EXECUTION_MODE);
@ -53,6 +54,15 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
tr("When this option is chosen, the clock speed set below will be used.")); tr("When this option is chosen, the clock speed set below will be used."));
dialog->registerWidgetHelp(m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"), dialog->registerWidgetHelp(m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"),
tr("Selects the percentage of the normal clock speed the emulated hardware will run at.")); tr("Selects the percentage of the normal clock speed the emulated hardware will run at."));
dialog->registerWidgetHelp(
m_ui.enable8MBRAM, tr("Enable 8MB RAM (Dev Console)"), tr("Unchecked"),
tr("Enables an additional 6MB of RAM, usually present on dev consoles. Games have to use a larger heap size for "
"this additional RAM to be usable, and may break games which rely on memory mirrors, so it should only be used "
"with compatible mods."));
dialog->registerWidgetHelp(
m_ui.cdromLoadImageToRAM, tr("Preload Image to RAM"), tr("Unchecked"),
tr("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some "
"cases also eliminates stutter when games initiate audio track playback."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.cdromReadSpeedup, tr("CDROM Read Speedup"), tr("None (Double Speed)"), m_ui.cdromReadSpeedup, tr("CDROM Read Speedup"), tr("None (Double Speed)"),
tr("Speeds up CD-ROM reads by the specified factor. Only applies to double-speed reads, and is ignored when audio " tr("Speeds up CD-ROM reads by the specified factor. Only applies to double-speed reads, and is ignored when audio "

View File

@ -42,6 +42,13 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="region"/> <widget class="QComboBox" name="region"/>
</item> </item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="enable8MBRAM">
<property name="text">
<string>Enable 8MB RAM (Dev Console)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -1140,7 +1140,7 @@ static T DoMemoryRead(PhysicalMemoryAddress address)
if (address < Bus::RAM_MIRROR_END) if (address < Bus::RAM_MIRROR_END)
{ {
std::memcpy(&result, &Bus::g_ram[address & Bus::RAM_MASK], sizeof(result)); std::memcpy(&result, &Bus::g_ram[address & Bus::g_ram_mask], sizeof(result));
return result; return result;
} }