Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Implementación de Echo RAM (El Espejo)
Resumen
La autopsia del Step 0237 y el análisis forense del Step 0238 revelaron la causa raíz del bucle infinito
en Tetris: la dirección 0xE645 pertenece a la región de Echo RAM (0xE000-0xFDFF),
que en el hardware real es un espejo exacto de la WRAM (0xC000-0xDDFF). El juego escribió
0xFD en 0xC645 (memoria real) y luego lee 0xE645 (espejo) para
verificar la integridad de la memoria. Como nuestra MMU no implementaba Echo RAM, devolvía 0x00,
causando que la comparación CP 0xFD fallara y el bucle nunca terminara.
Este Step implementa la lógica de Echo RAM en MMU.cpp, redirigiendo automáticamente cualquier
acceso a 0xE000-0xFDFF hacia 0xC000-0xDDFF tanto en lectura como en escritura.
Concepto de Hardware
Echo RAM (Mirror RAM) es una peculiaridad del hardware de Game Boy causada por el cableado
del bus de direcciones. Debido a limitaciones en el diseño del chip, acceder a direcciones en el rango
0xE000-0xFDFF accede físicamente a la misma memoria que 0xC000-0xDDFF.
- WRAM (Work RAM):
0xC000-0xDDFF(8KB de memoria real). - Echo RAM:
0xE000-0xFDFF(espejo de WRAM, misma memoria física). - Relación:
Echo_RAM_Address = WRAM_Address + 0x2000
¿Por qué existe Echo RAM? En el hardware real, esto es un efecto secundario del diseño del bus de direcciones. Los bits de dirección no están completamente decodificados, causando que ciertos rangos se "reflejen" en otros. Los juegos a veces usan esta característica para verificar la integridad de la memoria: escriben un valor en WRAM y luego leen su espejo en Echo RAM para confirmar que la memoria funciona correctamente.
Comportamiento:
- Leer
0xE645debe devolver el valor almacenado en0xC645. - Escribir
0xFDen0xE645debe modificar0xC645. - Ambas direcciones apuntan a la misma celda de memoria física.
Fuente: Pan Docs - Memory Map, sección "Echo RAM".
Implementación
La implementación de Echo RAM se realiza en los métodos read() y write() de
MMU.cpp, redirigiendo automáticamente cualquier acceso al rango 0xE000-0xFDFF
hacia 0xC000-0xDDFF.
Modificación en MMU::read()
uint8_t MMU::read(uint16_t addr) const {
addr &= 0xFFFF;
// --- Step 0239: IMPLEMENTACIÓN DE ECHO RAM ---
// Echo RAM (0xE000-0xFDFF) es un espejo de WRAM (0xC000-0xDDFF)
if (addr >= 0xE000 && addr <= 0xFDFF) {
addr = addr - 0x2000; // Redirigir a WRAM: 0xE645 -> 0xC645
}
// -----------------------------------------
// ... resto del código de lectura ...
}
Modificación en MMU::write()
void MMU::write(uint16_t addr, uint8_t value) {
addr &= 0xFFFF;
// --- Step 0239: IMPLEMENTACIÓN DE ECHO RAM ---
// Echo RAM (0xE000-0xFDFF) es un espejo de WRAM (0xC000-0xDDFF)
if (addr >= 0xE000 && addr <= 0xFDFF) {
addr = addr - 0x2000; // Redirigir a WRAM: 0xE645 -> 0xC645
}
// -----------------------------------------
// ... resto del código de escritura ...
}
Limpieza de Logs de Debug
También eliminamos los logs del "Francotirador" (Step 0237) en CPU.cpp que cumplieron su
misión de identificar el problema. Estos logs ralentizaban la ejecución y ya no son necesarios.
Decisiones de Diseño
- Redirección temprana: La redirección se hace al inicio de
read()ywrite(), antes de cualquier otro procesamiento, para garantizar que todos los accesos a Echo RAM se manejen correctamente. - Modificación de la dirección: En lugar de crear una lógica separada, simplemente modificamos la dirección
addrpara que apunte a WRAM. Esto garantiza que toda la lógica existente (registros I/O, STAT, etc.) funcione correctamente sin cambios adicionales. - Rango exacto: Usamos
0xE000-0xFDFF(no0xE000-0xFE00) porque el rango0xFE00-0xFEFFes OAM (Object Attribute Memory), no Echo RAM.
Archivos Afectados
src/core/cpp/MMU.cpp- Implementación de Echo RAM enread()ywrite()src/core/cpp/CPU.cpp- Eliminación de logs del Francotirador (Step 0237)docs/bitacora/entries/2025-12-22__0239__implementacion-echo-ram.html- Esta entrada
Tests y Verificación
La implementación se validó mediante:
- Compilación:
python setup.py build_ext --inplace- Sin errores de compilación. - Ejecución de Tetris:
python main.py roms/tetris.gb- El juego debería salir del bucle infinito en0x2B2Ay avanzar a la pantalla de Copyright. - Validación de memoria: Verificar que escribir en
0xE645modifica0xC645y viceversa.
Resultado esperado: Cuando Tetris lea 0xE645 después de escribir 0xFD en
0xC645, debería obtener 0xFD, la comparación CP 0xFD pasará (Z=1), y el salto
condicional JR NZ no se tomará, permitiendo que el juego continúe.
Fuentes Consultadas
- Pan Docs: Memory Map - Sección "Echo RAM"
- Análisis forense del Step 0238: Identificación de la dirección
0xE645como causa del bucle
Integridad Educativa
Lo que Entiendo Ahora
- Echo RAM: Es un espejo de WRAM causado por el diseño del bus de direcciones, no una región de memoria separada.
- Uso en juegos: Los juegos a veces usan Echo RAM para verificar la integridad de la memoria, escribiendo en WRAM y leyendo desde Echo RAM.
- Implementación: La redirección se hace simplemente restando
0x2000a la dirección antes de acceder a la memoria.
Lo que Falta Confirmar
- Comportamiento en hardware real: Verificar si hay alguna diferencia de timing entre acceder a WRAM directamente vs. a través de Echo RAM (probablemente no, pero es importante confirmarlo).
- Rango exacto: Confirmar que
0xFDFFes el límite superior correcto (no0xFE00).
Hipótesis y Suposiciones
Asumimos que la redirección de Echo RAM no tiene efectos secundarios en el timing o en otros componentes del hardware. Si el juego sigue fallando después de esta implementación, podría haber otros problemas (inicialización de WRAM, rutinas de copia, etc.).
Próximos Pasos
- [ ] Ejecutar Tetris y verificar que sale del bucle infinito
- [ ] Confirmar que el juego avanza a la pantalla de Copyright
- [ ] Si el juego sigue fallando, investigar otras posibles causas (inicialización de WRAM, rutinas de copia, etc.)
- [ ] Verificar que otros juegos que usen Echo RAM funcionen correctamente