Files
dolphin/Source/UnitTests/Core/IOS/Starlet/SRAMWriteTest.cpp
T
Yaya48 c230971f07 IOS LLE: checkpoint NAND conformance and diagnostic tooling
Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
2026-09-23 13:30:17 +02:00

129 lines
4.3 KiB
C++

// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <iomanip>
#include <memory>
#include <optional>
#include <gtest/gtest.h>
#include "Core/Core.h"
#include "Core/HW/WII_IPC.h"
#include "Core/IOS/Starlet/StarletMemory.h"
#include "Core/System.h"
namespace IOS::LLE
{
namespace
{
// Deliberately branch-based reference, independent of the optimized aperture-bit swap.
std::optional<u32> MapSRAMByte(u32 address, bool split, bool boot0)
{
if (!(address >= 0x0d400000 && address < 0x0d420000) && address < 0xfff00000)
return std::nullopt;
const u32 window = address & 0x1ffff;
if (boot0 && address >= 0xfffe0000 && (split ? window < 0x10000 : window >= 0x10000))
return std::nullopt;
if (!split)
return window < 0x18000 ? std::optional(window) : std::nullopt;
if (window < 0x8000)
return 0x10000 + window;
if (window >= 0x10000)
return window - 0x10000;
return std::nullopt;
}
class StarletSRAMWrite : public testing::Test
{
protected:
void SetUp() override
{
Core::DeclareAsCPUThread();
Core::System::GetInstance().GetWiiIPC().Reset();
}
static void SetMapping(StarletMemory& memory, bool split, bool boot0)
{
memory.Write32(0x0d800060, split ? 0x20 : 0);
memory.Write32(0x0d80018c, boot0 ? 0 : 0x1000);
}
};
TEST_F(StarletSRAMWrite, EveryApertureByteMatchesIndependentMapping)
{
auto memory = std::make_unique<StarletMemory>(Core::System::GetInstance());
memory->Reset();
u8* const sram = memory->GetFastmemSRAMBase();
for (u32 i = 0; i < StarletMemory::SRAM_SIZE; ++i)
sram[i] = static_cast<u8>((i * 13) ^ (i >> 8) ^ 0xa5);
for (const bool split : {false, true})
{
for (const bool boot0 : {false, true})
{
SetMapping(*memory, split, boot0);
for (const u32 base : {0x0d400000u, 0xfff00000u, 0xfffe0000u})
{
for (u32 i = 0; i < 0x20000; ++i)
{
const u32 address = base + i;
const auto offset = MapSRAMByte(address, split, boot0);
// Reset leaves the synthetic boot ROM zero-filled. Holes also read zero.
ASSERT_EQ(memory->Read8(address), offset ? sram[*offset] : 0)
<< "address=" << std::hex << address << " split=" << split << " boot0=" << boot0;
}
}
}
}
}
TEST_F(StarletSRAMWrite, WideStoresMatchByteOracleAcrossLiveMappingsAndAllMirrors)
{
auto memory = std::make_unique<StarletMemory>(Core::System::GetInstance());
memory->Reset();
std::array<u8, StarletMemory::SRAM_SIZE> expected;
for (u32 i = 0; i < expected.size(); ++i)
expected[i] = static_cast<u8>((i * 13) ^ (i >> 8) ^ 0xa5);
std::copy(expected.begin(), expected.end(), memory->GetFastmemSRAMBase());
// Toggle back, too: no cached host pointer may survive changes to SM/boot0.
for (const bool split : {false, true, false})
{
for (const bool boot0 : {false, true, false})
{
SetMapping(*memory, split, boot0);
for (const u32 base : {0x0d400000u, 0xfff00000u, 0xfff20000u, 0xfff40000u, 0xfff60000u,
0xfff80000u, 0xfffa0000u, 0xfffc0000u, 0xfffe0000u})
{
for (u32 page = 0; page < 32; ++page)
{
for (const u32 in_page : {0u, 1u, 2u, 3u, 0xffcu, 0xffdu, 0xffeu, 0xfffu})
{
const u32 address = base + page * 0x1000 + in_page;
for (const u32 width : {2u, 4u})
{
if (address > 0xffffffffu - (width - 1))
continue; // Do not wrap into uninitialized MEM1.
const u32 value = (address * 0x12345u) ^ (width == 2 ? 0x89abcdefu : 0x76543210u);
for (u32 byte = 0; byte < width; ++byte)
{
if (const auto offset = MapSRAMByte(address + byte, split, boot0))
expected[*offset] = static_cast<u8>(value >> ((width - 1 - byte) * 8));
}
if (width == 2)
memory->Write16(address, static_cast<u16>(value));
else
memory->Write32(address, value);
ASSERT_TRUE(
std::equal(expected.begin(), expected.end(), memory->GetFastmemSRAMBase()))
<< "address=" << std::hex << address << " width=" << width << " split=" << split
<< " boot0=" << boot0;
}
}
}
}
}
}
}
} // namespace
} // namespace IOS::LLE