#include #include #include #include #include #include "core/math.h" #include "core/memory.h" #include "hw/sh4/sh4.h" #include "hw/dreamcast.h" #include "hw/memory.h" #include "hw/scheduler.h" using namespace re; using namespace re::hw; using namespace re::hw::sh4; using namespace re::jit; using namespace re::jit::frontend::sh4; DECLARE_bool(interpreter); void re::hw::sh4::RunSH4Test(const SH4Test &test); enum { UNINITIALIZED_REG = 0xbaadf00d, }; struct SH4Test { const char *name; const uint8_t *buffer; uint32_t buffer_size; uint32_t buffer_offset; SH4Context in; SH4Context out; }; struct SH4TestRegister { const char *name; size_t offset; int size; }; // as per the notes in sh4_context.h, the fr / xf register pairs are swapped static SH4TestRegister sh4_test_regs[] = { {"fpscr", offsetof(SH4Context, fpscr), 4}, {"r0", offsetof(SH4Context, r[0]), 4}, {"r1", offsetof(SH4Context, r[1]), 4}, {"r2", offsetof(SH4Context, r[2]), 4}, {"r3", offsetof(SH4Context, r[3]), 4}, {"r4", offsetof(SH4Context, r[4]), 4}, {"r5", offsetof(SH4Context, r[5]), 4}, {"r6", offsetof(SH4Context, r[6]), 4}, {"r7", offsetof(SH4Context, r[7]), 4}, {"r8", offsetof(SH4Context, r[8]), 4}, {"r9", offsetof(SH4Context, r[9]), 4}, {"r10", offsetof(SH4Context, r[10]), 4}, {"r11", offsetof(SH4Context, r[11]), 4}, {"r12", offsetof(SH4Context, r[12]), 4}, {"r13", offsetof(SH4Context, r[13]), 4}, {"r14", offsetof(SH4Context, r[14]), 4}, {"r15", offsetof(SH4Context, r[15]), 4}, {"fr0", offsetof(SH4Context, fr[1]), 4}, {"fr1", offsetof(SH4Context, fr[0]), 4}, {"fr2", offsetof(SH4Context, fr[3]), 4}, {"fr3", offsetof(SH4Context, fr[2]), 4}, {"fr4", offsetof(SH4Context, fr[5]), 4}, {"fr5", offsetof(SH4Context, fr[4]), 4}, {"fr6", offsetof(SH4Context, fr[7]), 4}, {"fr7", offsetof(SH4Context, fr[6]), 4}, {"fr8", offsetof(SH4Context, fr[9]), 4}, {"fr9", offsetof(SH4Context, fr[8]), 4}, {"fr10", offsetof(SH4Context, fr[11]), 4}, {"fr11", offsetof(SH4Context, fr[10]), 4}, {"fr12", offsetof(SH4Context, fr[13]), 4}, {"fr13", offsetof(SH4Context, fr[12]), 4}, {"fr14", offsetof(SH4Context, fr[15]), 4}, {"fr15", offsetof(SH4Context, fr[14]), 4}, {"xf0", offsetof(SH4Context, xf[1]), 4}, {"xf1", offsetof(SH4Context, xf[0]), 4}, {"xf2", offsetof(SH4Context, xf[3]), 4}, {"xf3", offsetof(SH4Context, xf[2]), 4}, {"xf4", offsetof(SH4Context, xf[5]), 4}, {"xf5", offsetof(SH4Context, xf[4]), 4}, {"xf6", offsetof(SH4Context, xf[7]), 4}, {"xf7", offsetof(SH4Context, xf[6]), 4}, {"xf8", offsetof(SH4Context, xf[9]), 4}, {"xf9", offsetof(SH4Context, xf[8]), 4}, {"xf10", offsetof(SH4Context, xf[11]), 4}, {"xf11", offsetof(SH4Context, xf[10]), 4}, {"xf12", offsetof(SH4Context, xf[13]), 4}, {"xf13", offsetof(SH4Context, xf[12]), 4}, {"xf14", offsetof(SH4Context, xf[15]), 4}, {"xf15", offsetof(SH4Context, xf[14]), 4}, }; int sh4_num_test_regs = static_cast(sizeof(sh4_test_regs) / sizeof(sh4_test_regs[0])); // clang-format off #define INIT_CONTEXT(fpscr, r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, \ r12, r13, r14, r15, fr0, fr1, fr2, fr3, fr4, fr5, fr6, \ fr7, fr8, fr9, fr10, fr11, fr12, fr13, fr14, fr15, xf0, \ xf1, xf2, xf3, xf4, xf5, xf6, xf7, xf8, xf9, xf10, xf11, \ xf12, xf13, xf14, xf15) \ SH4Context { \ nullptr, nullptr, nullptr, nullptr, nullptr, \ 0, \ 0, 0, 0, 0, fpscr, \ 0, 0, 0, \ 0, 0, 0, \ 0, 0, 0, \ { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0 } }, \ {r0, r1, r2, r3, r4, r5, r6, r7, \ r8, r9, r10, r11, r12, r13, r14, r15}, \ {0, 0, 0, 0, 0, 0, 0, 0}, \ {fr1, fr0, fr3, fr2, fr5, fr4, fr7, fr6, \ fr9, fr8, fr11, fr10, fr13, fr12, fr15, fr14}, \ {xf1, xf0, xf3, xf2, xf5, xf4, xf7, xf6, \ xf9, xf8, xf11, xf10, xf13, xf12, xf15, xf14}, \ } #define TEST_SH4(name, buffer, buffer_size, buffer_offset, \ fpscr_in, \ r0_in, r1_in, r2_in, r3_in, r4_in, r5_in, r6_in, r7_in, r8_in, r9_in, r10_in, r11_in, r12_in, r13_in, r14_in, r15_in, \ fr0_in, fr1_in, fr2_in, fr3_in, fr4_in, fr5_in, fr6_in, fr7_in, fr8_in, fr9_in, fr10_in, fr11_in, fr12_in, fr13_in, fr14_in, fr15_in, \ xf0_in, xf1_in, xf2_in, xf3_in, xf4_in, xf5_in, xf6_in, xf7_in, xf8_in, xf9_in, xf10_in, xf11_in, xf12_in, xf13_in, xf14_in, xf15_in, \ fpscr_out, \ r0_out, r1_out, r2_out, r3_out, r4_out, r5_out, r6_out, r7_out, r8_out, r9_out, r10_out, r11_out, r12_out, r13_out, r14_out, r15_out, \ fr0_out, fr1_out, fr2_out, fr3_out, fr4_out, fr5_out, fr6_out, fr7_out, fr8_out, fr9_out, fr10_out, fr11_out, fr12_out, fr13_out, fr14_out, fr15_out, \ xf0_out, xf1_out, xf2_out, xf3_out, xf4_out, xf5_out, xf6_out, xf7_out, xf8_out, xf9_out, xf10_out, xf11_out, xf12_out, xf13_out, xf14_out, xf15_out) \ static SH4Test test_##name = { \ #name, buffer, buffer_size, buffer_offset, \ INIT_CONTEXT(fpscr_in, \ r0_in, r1_in, r2_in, r3_in, r4_in, r5_in, r6_in, r7_in, r8_in, r9_in, r10_in, r11_in, r12_in, r13_in, r14_in, r15_in, \ fr0_in, fr1_in, fr2_in, fr3_in, fr4_in, fr5_in, fr6_in, fr7_in, fr8_in, fr9_in, fr10_in, fr11_in, fr12_in, fr13_in, fr14_in, fr15_in, \ xf0_in, xf1_in, xf2_in, xf3_in, xf4_in, xf5_in, xf6_in, xf7_in, xf8_in, xf9_in, xf10_in, xf11_in, xf12_in, xf13_in, xf14_in, xf15_in), \ INIT_CONTEXT(fpscr_out, \ r0_out, r1_out, r2_out, r3_out, r4_out, r5_out, r6_out, r7_out, r8_out, r9_out, r10_out, r11_out, r12_out, r13_out, r14_out, r15_out, \ fr0_out, fr1_out, fr2_out, fr3_out, fr4_out, fr5_out, fr6_out, fr7_out, fr8_out, fr9_out, fr10_out, fr11_out, fr12_out, fr13_out, fr14_out, fr15_out, \ xf0_out, xf1_out, xf2_out, xf3_out, xf4_out, xf5_out, xf6_out, xf7_out, xf8_out, xf9_out, xf10_out, xf11_out, xf12_out, xf13_out, xf14_out, xf15_out) \ }; \ TEST(sh4_interpreter, name) { \ FLAGS_interpreter = true; \ RunSH4Test(test_##name); \ } \ TEST(sh4_x64, name) { \ FLAGS_interpreter = false; \ RunSH4Test(test_##name); \ } #include "test_sh4.inc" #undef TEST_SH4 // clang-format on namespace re { namespace hw { namespace sh4 { void RunSH4Test(const SH4Test &test) { // initialize fake dreamcast device // TODO avoid initializing an entire 32-bit addres space for each test? // perhaps initialize the machine once, resetting the SH4 context between // runs? std::unique_ptr dc(new Dreamcast()); std::unique_ptr sh4(new SH4(dc.get())); CHECK(dc->Init()); // setup in registers for (int i = 0; i < sh4_num_test_regs; i++) { SH4TestRegister ® = sh4_test_regs[i]; uint32_t input = re::load( reinterpret_cast(&test.in) + reg.offset); if (input == UNINITIALIZED_REG) { continue; } re::store(reinterpret_cast(&sh4->ctx_) + reg.offset, input); } // setup initial stack pointer sh4->ctx_.r[15] = 0x8d000000; // load binary dc->memory->Memcpy(0x8c010000, test.buffer, test.buffer_size); // skip to the test's offset sh4->SetPC(0x8c010000 + test.buffer_offset); // run until the function returns while (sh4->ctx_.pc) { sh4->Run(std::chrono::nanoseconds(1)); } // validate out registers for (int i = 0; i < sh4_num_test_regs; i++) { SH4TestRegister ® = sh4_test_regs[i]; uint32_t expected = re::load( reinterpret_cast(&test.out) + reg.offset); if (expected == UNINITIALIZED_REG) { continue; } uint32_t actual = re::load( reinterpret_cast(&sh4->ctx_) + reg.offset); ASSERT_EQ(expected, actual) << reg.name << " expected: 0x" << std::hex << expected << ", actual 0x" << actual; } } } } }