mmx6 framework: CD/DMA/MDEC + codegen fixes (Tomba builds+plays, MMX6 boots to intro)

Framework work on codex/mmx6-support that makes Tomba build and play
(title screen, 0 dispatch misses) and MMX6 boot through BIOS to its
intro. Touches recompiler codegen (code_generator, function_analysis)
and runtime CD-ROM/DMA/MDEC/GPU/SIO timing (cdrom, dma, mdec, gpu,
psx_cycles, traps, fntrace, debug_server) + headers + runtime.cmake.

Milestone snapshot for collapsing onto master. Build dir excluded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-06-04 12:06:02 -07:00
parent 08fa3765ba
commit ec4306fbb3
20 changed files with 1289 additions and 200 deletions
+64 -76
View File
@@ -1481,82 +1481,70 @@ std::string CodeGenerator::generate_file(
// in CPUState, but these standalone functions provide correct reference behavior.
ss << "/* --- Unaligned memory access helper implementations --- */\n";
ss << "\n";
ss << "/* LWL: Load Word Left - merges high bytes from aligned word into rt.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the current value of the destination register.\n";
ss << " * MIPS big-endian convention: byte 0 is the most-significant byte.\n";
ss << " * The PSX (little-endian) maps addr&3==0 -> load entire word,\n";
ss << " * addr&3==1 -> load 3 bytes into high 24 bits, etc.\n";
ss << " * Formula:\n";
ss << " * aligned_addr = addr & ~3;\n";
ss << " * shift = (addr & 3) * 8;\n";
ss << " * mask = 0xFFFFFFFF << shift;\n";
ss << " * result = (rt_value & ~mask) | (word << shift);\n";
ss << " * where word = read_word(aligned_addr).\n";
ss << " */\n";
ss << "static uint32_t psx_lwl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t aligned_addr = addr & ~3u;\n";
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
ss << " uint32_t shift = (addr & 3u) * 8u;\n";
ss << " uint32_t mask = 0xFFFFFFFFu << shift;\n";
ss << " return (rt_value & ~mask) | (word << shift);\n";
ss << "}\n";
ss << "\n";
ss << "/* LWR: Load Word Right - merges low bytes from aligned word into rt.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the current value of the destination register.\n";
ss << " * Formula:\n";
ss << " * aligned_addr = addr & ~3;\n";
ss << " * shift = (3 - (addr & 3)) * 8;\n";
ss << " * mask = 0xFFFFFFFF >> shift;\n";
ss << " * result = (rt_value & ~mask) | (word >> shift);\n";
ss << " * where word = read_word(aligned_addr).\n";
ss << " */\n";
ss << "static uint32_t psx_lwr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t aligned_addr = addr & ~3u;\n";
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
ss << " uint32_t shift = (3u - (addr & 3u)) * 8u;\n";
ss << " uint32_t mask = 0xFFFFFFFFu >> shift;\n";
ss << " return (rt_value & ~mask) | (word >> shift);\n";
ss << "}\n";
ss << "\n";
ss << "/* SWL: Store Word Left - stores the high bytes of rt_value into memory.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the value of the source register.\n";
ss << " * Formula:\n";
ss << " * aligned_addr = addr & ~3;\n";
ss << " * shift = (addr & 3) * 8;\n";
ss << " * mem_mask = 0xFFFFFFFF >> (32 - shift) [i.e. low 'shift' bits preserved]\n";
ss << " * Actually:\n";
ss << " * mask = 0xFFFFFFFF >> shift; [bits to take from rt]\n";
ss << " * write_word(aligned_addr,\n";
ss << " * (read_word(aligned_addr) & ~mask) | (rt_value >> shift));\n";
ss << " */\n";
ss << "static void psx_swl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t aligned_addr = addr & ~3u;\n";
ss << " uint32_t existing = cpu->read_word(aligned_addr);\n";
ss << " uint32_t shift = (addr & 3u) * 8u;\n";
ss << " uint32_t mask = 0xFFFFFFFFu >> shift;\n";
ss << " cpu->write_word(aligned_addr, (existing & ~mask) | (rt_value >> shift));\n";
ss << "}\n";
ss << "\n";
ss << "/* SWR: Store Word Right - stores the low bytes of rt_value into memory.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the value of the source register.\n";
ss << " * Formula:\n";
ss << " * aligned_addr = addr & ~3;\n";
ss << " * shift = (3 - (addr & 3)) * 8;\n";
ss << " * mask = 0xFFFFFFFF << shift;\n";
ss << " * write_word(aligned_addr,\n";
ss << " * (read_word(aligned_addr) & ~mask) | (rt_value << shift));\n";
ss << " */\n";
ss << "static void psx_swr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t aligned_addr = addr & ~3u;\n";
ss << " uint32_t existing = cpu->read_word(aligned_addr);\n";
ss << " uint32_t shift = (3u - (addr & 3u)) * 8u;\n";
ss << " uint32_t mask = 0xFFFFFFFFu << shift;\n";
ss << " cpu->write_word(aligned_addr, (existing & ~mask) | (rt_value << shift));\n";
ss << "}\n";
ss << "/* LWL/LWR/SWL/SWR implement little-endian R3000A unaligned word\n";
ss << " * semantics. Keep these switch tables in sync with psx_interpreter.c\n";
ss << " * and dirty_ram_interp.c.\n";
ss << " */\n";
ss << "\n";
ss << "/* LWL: Load Word Left - merges high bytes from aligned word into rt.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the current value of the destination register.\n";
ss << " */\n";
ss << "static uint32_t psx_lwl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t word = cpu->read_word(addr & ~3u);\n";
ss << " switch (addr & 3u) {\n";
ss << " case 0: return (rt_value & 0x00FFFFFFu) | (word << 24);\n";
ss << " case 1: return (rt_value & 0x0000FFFFu) | (word << 16);\n";
ss << " case 2: return (rt_value & 0x000000FFu) | (word << 8);\n";
ss << " default: return word;\n";
ss << " }\n";
ss << "}\n";
ss << "\n";
ss << "/* LWR: Load Word Right - merges low bytes from aligned word into rt.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the current value of the destination register.\n";
ss << " */\n";
ss << "static uint32_t psx_lwr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t word = cpu->read_word(addr & ~3u);\n";
ss << " switch (addr & 3u) {\n";
ss << " case 0: return word;\n";
ss << " case 1: return (rt_value & 0xFF000000u) | (word >> 8);\n";
ss << " case 2: return (rt_value & 0xFFFF0000u) | (word >> 16);\n";
ss << " default: return (rt_value & 0xFFFFFF00u) | (word >> 24);\n";
ss << " }\n";
ss << "}\n";
ss << "\n";
ss << "/* SWL: Store Word Left - stores the high bytes of rt_value into memory.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the value of the source register.\n";
ss << " */\n";
ss << "static void psx_swl(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t aligned_addr = addr & ~3u;\n";
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
ss << " switch (addr & 3u) {\n";
ss << " case 0: word = (word & 0xFFFFFF00u) | (rt_value >> 24); break;\n";
ss << " case 1: word = (word & 0xFFFF0000u) | (rt_value >> 16); break;\n";
ss << " case 2: word = (word & 0xFF000000u) | (rt_value >> 8); break;\n";
ss << " default: word = rt_value; break;\n";
ss << " }\n";
ss << " cpu->write_word(aligned_addr, word);\n";
ss << "}\n";
ss << "\n";
ss << "/* SWR: Store Word Right - stores the low bytes of rt_value into memory.\n";
ss << " * addr is the effective (possibly unaligned) address.\n";
ss << " * rt_value is the value of the source register.\n";
ss << " */\n";
ss << "static void psx_swr(CPUState* cpu, uint32_t addr, uint32_t rt_value) {\n";
ss << " uint32_t aligned_addr = addr & ~3u;\n";
ss << " uint32_t word = cpu->read_word(aligned_addr);\n";
ss << " switch (addr & 3u) {\n";
ss << " case 0: word = rt_value; break;\n";
ss << " case 1: word = (word & 0x000000FFu) | (rt_value << 8); break;\n";
ss << " case 2: word = (word & 0x0000FFFFu) | (rt_value << 16); break;\n";
ss << " default: word = (word & 0x00FFFFFFu) | (rt_value << 24); break;\n";
ss << " }\n";
ss << " cpu->write_word(aligned_addr, word);\n";
ss << "}\n";
ss << "\n";
// Make mutable copies for potential function splitting