⚠️ Clean-Room / Educativo

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 Case para Capturar Opcodes Desconocidos

Fecha: 2025-12-20 Step ID: 0168 Estado: 🔍 DRAFT

Resumen

Se modificó el caso default en el método CPU::step() para implementar una estrategia "fail-fast" que termina la ejecución inmediatamente cuando se encuentra un opcode no implementado, en lugar de devolver 0 ciclos y causar un deadlock silencioso. Esto permite identificar rápidamente qué opcodes faltan implementar al mostrar un mensaje de error fatal con el opcode y el PC exactos donde ocurre el problema.

Concepto de Hardware: Depuración "Fail-Fast"

En el desarrollo de emuladores, es una práctica estándar hacer que el núcleo falle de manera ruidosa y temprana cuando encuentra una condición inesperada, como un opcode desconocido. En lugar de permitir que el emulador continúe en un estado indefinido (como nuestro deadlock de LY=0), lo forzamos a detenerse inmediatamente, mostrándonos la causa exacta del problema.

La estrategia "fail-fast" acelera drásticamente el ciclo de depuración porque:

  • Identificación Inmediata: El programa termina en el momento exacto en que encuentra el problema, no después de ejecutar miles de instrucciones en un estado corrupto.
  • Información Precisa: Reporta el opcode exacto y la dirección de memoria (PC) donde ocurre el fallo, permitiendo una investigación directa y eficiente.
  • Evita Estados Indefinidos: Previene que el emulador entre en bucles infinitos o estados corruptos que son difíciles de depurar retrospectivamente.

En nuestro caso específico, el deadlock de LY=0 persistía porque cpu.step() estaba devolviendo 0 ciclos repetidamente cuando encontraba un opcode no implementado. Al cambiar a fail-fast, el emulador se detendrá inmediatamente y nos mostrará qué opcode falta, permitiéndonos implementarlo y continuar con la emulación.

Implementación

Se modificó el caso default en el método CPU::step() en src/core/cpp/CPU.cpp para que, en lugar de imprimir un warning y devolver 0 ciclos, imprima un mensaje fatal y termine la ejecución con exit(1).

Componentes modificados

  • src/core/cpp/CPU.cpp: Modificado el caso default del switch de opcodes para implementar fail-fast.

Cambios realizados

El código anterior solo imprimía un warning y devolvía 0 ciclos:

default:
    printf("[CPU WARN] Opcode no implementado: 0x%02X en PC: 0x%04X. Devolviendo 0 ciclos.\n", opcode, current_pc);
    return 0;

El nuevo código implementa fail-fast:

default:
    // --- Instrumentación del Step 0168 ---
    // Fail-fast: Si encontramos un opcode desconocido, lo reportamos
    // y terminamos la ejecución inmediatamente. Esto es mucho mejor que
    // devolver 0 ciclos y causar un deadlock silencioso.
    printf("[CPU FATAL] Unimplemented opcode: 0x%02X at PC: 0x%04X\n", opcode, current_pc);
    exit(1); // Termina el programa con un código de error.
    return 0; // No se alcanzará, pero mantiene la lógica del compilador.

Decisiones de diseño

Se decidió usar exit(1) en lugar de lanzar una excepción C++ porque:

  • Simplicidad: En esta fase de desarrollo, queremos un fallo inmediato y visible, no manejo de excepciones complejo.
  • Claridad: El mensaje de error se imprime directamente en la consola, facilitando la depuración.
  • Eficiencia: No hay overhead de manejo de excepciones en el código crítico de ejecución.

En el futuro, cuando el núcleo esté más completo, podríamos considerar cambiar esto a un sistema de logging más sofisticado o excepciones, pero para identificar opcodes faltantes durante el desarrollo, fail-fast es la estrategia óptima.

Archivos Afectados

  • src/core/cpp/CPU.cpp - Modificado el caso default para implementar fail-fast

Tests y Verificación

Esta modificación no requiere tests unitarios específicos porque es una herramienta de depuración que se activará cuando se ejecute el emulador con una ROM real. La verificación se realiza ejecutando el emulador:

python main.py roms/tetris.gb

Resultado esperado: El emulador debe terminar inmediatamente (sin mostrar el bucle de heartbeat) y mostrar un mensaje de error fatal en la consola con el formato:

[CPU FATAL] Unimplemented opcode: 0xXX at PC: 0xXXXX

Este mensaje identificará exactamente qué opcode falta implementar y en qué dirección de memoria se encuentra, permitiendo una corrección rápida y precisa.

Validación de módulo compilado C++: El módulo debe recompilarse exitosamente. El archivo cstdlib ya estaba incluido en el archivo, por lo que no se necesitan includes adicionales.

Fuentes Consultadas

  • Práctica de desarrollo de software: Estrategia "Fail-Fast" para depuración temprana
  • Pan Docs: CPU Instruction Set - Referencia para implementar opcodes faltantes una vez identificados

Integridad Educativa

Lo que Entiendo Ahora

  • Fail-Fast en Emulación: Es mejor hacer que el emulador falle inmediatamente con información precisa que permitir que continúe en un estado indefinido, especialmente durante el desarrollo.
  • Deadlock Silencioso: Devolver 0 ciclos cuando se encuentra un opcode desconocido puede causar que el tiempo emulado no avance, creando un deadlock difícil de depurar. Fail-fast evita este problema completamente.
  • Instrumentación de Debug: Agregar código específico de depuración (como mensajes de error fatal) en puntos críticos del código ayuda a identificar problemas rápidamente.

Lo que Falta Confirmar

  • Qué opcode específico está causando el deadlock - esto se determinará cuando se ejecute el emulador con la nueva instrumentación.
  • Si hay múltiples opcodes faltantes o solo uno que bloquea la ejecución temprana.

Hipótesis y Suposiciones

Hipótesis principal: Hay al menos un opcode no implementado que se ejecuta durante la inicialización de la ROM (antes de llegar a PC=0x0300), causando que cpu.step() devuelva 0 ciclos y el emulador entre en deadlock.

Suposición: Una vez identificado el opcode faltante, implementarlo debería permitir que la emulación avance hasta el siguiente opcode faltante (si existe) o hasta que se complete la inicialización y comience el renderizado.

Próximos Pasos

  • [ ] Recompilar el módulo C++ con la nueva instrumentación
  • [ ] Ejecutar el emulador con una ROM para identificar el opcode faltante
  • [ ] Implementar el opcode identificado según Pan Docs
  • [ ] Repetir el proceso hasta que la emulación avance correctamente
  • [ ] Una vez que el emulador avance, considerar cambiar de fail-fast a un sistema de logging más sofisticado para producción