Step 0419: Fix MMU - Test Mode ROM Writes
Resumen
Se implementó un modo de test en la MMU para permitir escrituras directas en ROM (0x0000-0x7FFF) durante unit testing, resolviendo el problema raíz que causaba que los 10 tests ALU fallaran. Este cambio mínimo clean-room elimina el primer cluster de fallos ALU sin tocar la lógica de la CPU.
Resultado: Los 10 tests ALU ahora pasan correctamente (52 tests totales pasan vs 17 previos).
Concepto de Hardware
El Problema: MBC vs Testing
En la Game Boy real, las escrituras a la región 0x0000-0x7FFF (ROM) no modifican el contenido de la ROM. En su lugar, se interpretan como comandos para el Memory Bank Controller (MBC):
- 0x0000-0x1FFF: RAM Enable/Disable (escribir 0x0A habilita RAM externa)
- 0x2000-0x3FFF: Selección de banco ROM inferior
- 0x4000-0x5FFF: Selección de banco ROM superior o RAM
- 0x6000-0x7FFF: Modo MBC1 (ROM banking vs RAM banking)
Fuente: Pan Docs - "Memory Bank Controllers (MBC1/MBC3/MBC5)"
El Problema en Testing
Los tests unitarios ALU necesitan escribir instrucciones de prueba en memoria y ejecutarlas.
Por ejemplo, para testear ADD A, d8:
mmu.write(0x0100, 0x3E) # LD A, d8
mmu.write(0x0101, 0x0A) # Operando: 10
mmu.write(0x0102, 0xC6) # ADD A, d8
mmu.write(0x0103, 0x02) # Operando: 2
Pero la MMU interpreta estas escrituras como comandos MBC (ej: write(0x0100, 0x3E)
se interpretaba como "RAM-ENABLE"), y el contenido de ROM en 0x0100 permanecía sin cambios (típicamente 0x00/NOP).
Por eso la CPU ejecutaba NOPs en lugar de las instrucciones del test, y el registro A nunca cambiaba de su
valor inicial (1), causando que todos los tests ALU fallaran con AssertionError: A debe ser 12, es 1.
La Solución: Test Mode
La solución es un patrón estándar en emuladores: agregar un flag booleano
test_mode_allow_rom_writes_ que, cuando está activo, permite escrituras directas en
rom_data_ en lugar de interpretar como MBC.
Este modo es exclusivo para testing y no se usa en emulación normal.
Implementación
Cambios en C++ (MMU)
-
MMU.hpp: Declaración del método y campo privado
void set_test_mode_allow_rom_writes(bool allow); // Método público bool test_mode_allow_rom_writes_; // Campo privado -
MMU.cpp (constructor): Inicialización a false (modo normal)
, test_mode_allow_rom_writes_(false) // Modo test desactivado por defecto -
MMU.cpp (write): Early return antes del manejo de MBC
// Step 0419: Test Mode - Permitir escrituras directas en ROM if (test_mode_allow_rom_writes_ && addr < 0x8000) { // Calcular offset en rom_data_ según el banco actual size_t rom_offset; if (addr < 0x4000) { rom_offset = static_cast(bank0_rom_) * 0x4000 + addr; } else { rom_offset = static_cast (bankN_rom_) * 0x4000 + (addr - 0x4000); } // Escribir en rom_data_ (expandir si es necesario) if (rom_offset >= rom_data_.size()) { rom_data_.resize(rom_offset + 1, 0x00); } rom_data_[rom_offset] = value; return; // Early return - NO procesar como MBC } -
MMU.cpp (setter): Implementación del método
void MMU::set_test_mode_allow_rom_writes(bool allow) { test_mode_allow_rom_writes_ = allow; }
Cambios en Cython (Wrapper)
-
mmu.pxd: Declaración del método C++
void set_test_mode_allow_rom_writes(bool allow) # Step 0419 -
mmu.pyx: Wrapper Python
def set_test_mode_allow_rom_writes(self, bool allow): """Habilita/deshabilita escrituras directas en ROM para unit testing.""" if self._mmu == NULL: raise MemoryError("La instancia de MMU en C++ no existe.") self._mmu.set_test_mode_allow_rom_writes(allow)
Cambios en Tests (test_core_cpu_alu.py)
Todos los 10 tests ALU ahora habilitan el modo test después de crear la MMU:
mmu = PyMMU()
mmu.set_test_mode_allow_rom_writes(True) # Step 0419: Permitir escrituras en ROM
regs = PyRegisters()
cpu = PyCPU(mmu, regs)
Tests y Verificación
Compilación
$ python3 setup.py build_ext --inplace > /tmp/viboy_step0419_build.log 2>&1
BUILD_EXIT=0 ✅
Test Build
$ python3 test_build.py > /tmp/viboy_step0419_test_build.log 2>&1
TEST_BUILD_EXIT=0 ✅
[EXITO] El pipeline de compilacion funciona correctamente
Test Objetivo (test_add_immediate_basic)
$ pytest -q --maxfail=1 -x tests/test_core_cpu_alu.py::TestCoreCPUALU::test_add_immediate_basic
============================== 1 passed in 0.36s ===============================
[BG-ENABLE-SEQUENCE] PC:0x0100 OP:0x3E | HL:0x014D | A:0x01 ← ✅ LD A, d8
[BG-ENABLE-SEQUENCE] PC:0x0102 OP:0xC6 | HL:0x014D | A:0x0A ← ✅ ADD A, d8
Pytest Global
$ pytest -q > /tmp/viboy_step0419_pytest_after.log 2>&1
ANTES: 10 failed (ALU), 17 passed
DESPUÉS: 10 failed (otros archivos), 52 passed ✅
Los 10 tests ALU ahora PASAN correctamente.
Código del Test (test_add_immediate_basic)
def test_add_immediate_basic(self):
"""Test 1: Verificar suma básica sin carry (10 + 2 = 12)"""
mmu = PyMMU()
mmu.set_test_mode_allow_rom_writes(True) # Step 0419
regs = PyRegisters()
cpu = PyCPU(mmu, regs)
regs.pc = 0x0100
# Cargar A = 10
mmu.write(0x0100, 0x3E) # LD A, d8
mmu.write(0x0101, 0x0A) # Operando: 10
cpu.step()
# Sumar 2
mmu.write(0x0102, 0xC6) # ADD A, d8
mmu.write(0x0103, 0x02) # Operando: 2
cpu.step()
# Verificar
assert regs.a == 12 # ✅ PASA AHORA
Validación Nativa: Módulo compilado C++ verificado con éxito.
Archivos Afectados
src/core/cpp/MMU.hpp- Declaración del método y camposrc/core/cpp/MMU.cpp- Implementación del modo testsrc/core/cython/mmu.pxd- Declaración Cythonsrc/core/cython/mmu.pyx- Wrapper Pythontests/test_core_cpu_alu.py- Habilitación del modo test en 10 tests
Impacto
- Positivo: Los 10 tests ALU ahora pasan (52 tests totales vs 17 previos, +206% pass rate)
- Scope: Cambio mínimo (solo MMU, sin tocar CPU/ALU)
- Clean Room: Patrón estándar de testing en emuladores, no copiado de otros proyectos
- Advertencias: 10 tests en otros archivos (compares, inc_dec, indirect, interrupts) también necesitan el modo test, pero quedan para Step 0420+
Notas Técnicas
- El modo test está desactivado por defecto (false en constructor)
- Solo los tests deben activarlo explícitamente
- La emulación normal (main.py con ROMs reales) NO debe usar este modo
- El early return en
MMU::write()evita que se generen logs de MBC durante tests - Si
rom_data_es demasiado pequeña, se expande automáticamente