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.
Debug: Instrumentar default para Capturar Opcodes Desconocidos
Resumen
Se instrumentó el caso default del switch de opcodes en la CPU de C++ para detectar y reportar explícitamente qué opcode no implementado está causando el deadlock lógico. El diagnóstico previo confirmó que LY está atascado en 0 porque la CPU devuelve 0 ciclos repetidamente, indicando que está ejecutando un opcode desconocido en un bucle infinito. La solución implementada añade un printf y exit(1) en el caso default para que el emulador termine inmediatamente y muestre el opcode y PC exactos donde ocurre el problema.
Concepto de Hardware
En un emulador de Game Boy, cuando la CPU encuentra un opcode que no está implementado, debe manejarlo de alguna forma. En nuestro caso, el caso default del switch retornaba 0 ciclos, lo que causaba un deadlock lógico:
- La CPU ejecuta un opcode desconocido y devuelve 0 ciclos.
- El motor de timing en Python acumula 0 ciclos, por lo que nunca alcanza el umbral de
CYCLES_PER_SCANLINE. - Como resultado,
LY(línea de escaneo) nunca avanza, quedándose atascado en 0. - El bucle principal sigue corriendo (por eso vemos el Heartbeat), pero el tiempo de emulación no avanza.
Esta técnica de instrumentación es conocida como "fail-fast" o "fail-loud": en lugar de fallar silenciosamente, el programa termina inmediatamente con un mensaje claro que identifica exactamente qué opcode falta. Esto es especialmente útil durante el desarrollo, cuando aún no se han implementado todos los 256 opcodes base (más los 256 del prefijo CB).
Referencia: Técnica de depuración estándar en emulación - "Opcode Trap" o "Unimplemented Instruction Handler".
Implementación
Se modificó el archivo src/core/cpp/CPU.cpp para añadir instrumentación de depuración en el caso default del switch de opcodes.
Modificaciones Realizadas
- Inclusión de headers: Se añadió
#include <cstdlib>para usarexit(). - Instrumentación del default: Se reemplazó el retorno silencioso de 0 ciclos por un
printfque muestra el opcode y PC, seguido deexit(1)para terminar la ejecución inmediatamente.
Código Implementado
El caso default ahora tiene esta estructura:
default:
// Opcode no implementado - INSTRUMENTACIÓN DE DEPURACIÓN
// Esta es la "bala de plata" para detectar opcodes faltantes
// Imprimimos el opcode y PC, luego terminamos la ejecución inmediatamente
printf("[CPU FATAL] Opcode no implementado: 0x%02X en PC: 0x%04X\n", opcode, current_pc);
// Detenemos la emulación bruscamente para que el mensaje sea lo último que veamos
exit(1);
return 0; // No se ejecutará, pero es buena práctica
Decisiones de Diseño
Se eligió exit(1) en lugar de lanzar una excepción o usar logging porque:
- Inmediatez: Termina el programa de forma inmediata, evitando que se inunde la consola con mensajes repetidos.
- Claridad: El mensaje de error es lo último que se ve, facilitando su identificación.
- Simplicidad: No requiere manejo de excepciones en el código Python, simplificando el flujo de depuración.
- Debugging: Es una técnica estándar en desarrollo de emuladores para identificar rápidamente opcodes faltantes.
Nota: Esta instrumentación es temporal y se removerá o se convertirá en un modo de debug opcional una vez que todos los opcodes estén implementados.
Archivos Afectados
src/core/cpp/CPU.cpp- Añadido#include <cstdlib>y modificado el casodefaultdel switch de opcodes.
Tests y Verificación
Esta modificación no requiere tests unitarios tradicionales, ya que es una herramienta de depuración. La validación se realizará ejecutando el emulador:
- Comando de recompilación:
.\rebuild_cpp.ps1 - Comando de ejecución:
python main.py roms/tetris.gb - Resultado esperado: El emulador debería terminar inmediatamente con un mensaje como:
[CPU FATAL] Opcode no implementado: 0xXX en PC: 0xYYYY
Este mensaje identificará exactamente qué opcode falta implementar, permitiendo continuar con la depuración de forma dirigida.
Fuentes Consultadas
- Técnica de depuración estándar en emulación: "Opcode Trap" o "Unimplemented Instruction Handler"
- Principio de "Fail-Fast" en desarrollo de software
Integridad Educativa
Lo que Entiendo Ahora
- Deadlock lógico: Cuando la CPU devuelve 0 ciclos repetidamente, el tiempo de emulación no avanza, causando que
LYse quede atascado. - Instrumentación de depuración: Añadir código específico para detectar y reportar problemas durante el desarrollo es una práctica estándar.
- Fail-fast: Terminar inmediatamente con un mensaje claro es más útil que fallar silenciosamente o generar logs masivos.
Lo que Falta Confirmar
- Opcode faltante: Una vez ejecutado el emulador con esta instrumentación, sabremos exactamente qué opcode no está implementado y está causando el deadlock.
- Contexto de ejecución: El PC donde ocurre el problema nos dará contexto sobre en qué parte del código de la ROM se está ejecutando este opcode.
Hipótesis y Suposiciones
Hipótesis principal: La CPU está ejecutando un opcode no implementado en un bucle infinito, causando que devuelva 0 ciclos repetidamente. Esta instrumentación confirmará o refutará esta hipótesis de forma definitiva.
Próximos Pasos
- [ ] Recompilar el módulo C++ con
.\rebuild_cpp.ps1 - [ ] Ejecutar el emulador con
python main.py roms/tetris.gb - [ ] Identificar el opcode faltante del mensaje de error
- [ ] Implementar el opcode faltante en la CPU de C++
- [ ] Verificar que el emulador avanza más allá del punto de bloqueo
- [ ] Actualizar el estado de esta entrada a VERIFIED una vez confirmado