⚠️ 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.

Doctor Viboy: Diagnóstico Autónomo y Fix de HALT

Fecha: 2025-12-18 Step ID: 0045 Estado: Verified

Resumen

Se creó la herramienta Doctor Viboy (tools/doctor_viboy.py), un analizador autónomo de bloqueos que detecta bucles infinitos, desensambla el código del bucle, muestra el estado completo del sistema y aplica heurísticas de diagnóstico. El Doctor identificó que el emulador se quedaba congelado porque la CPU entraba en estado HALT esperando interrupciones que nunca llegaban, ya que durante HALT solo se avanzaban 4 T-Cycles por tick, insuficientes para que la PPU generara interrupciones V-Blank. Se implementó un fix en src/viboy.py que hace avanzar múltiples ciclos durante HALT (hasta 114 M-Cycles = 456 T-Cycles = 1 línea de PPU) para que los subsistemas sigan funcionando normalmente.

Concepto de Hardware

HALT en la Game Boy: La instrucción HALT (opcode 0x76) pone la CPU en modo de bajo consumo. La CPU deja de ejecutar instrucciones (el PC no avanza) hasta que ocurre una interrupción. Sin embargo, el reloj del sistema sigue funcionando durante HALT. Esto significa que:

  • La PPU (Pixel Processing Unit) sigue avanzando líneas y generando interrupciones V-Blank
  • El Timer sigue contando y puede generar interrupciones
  • Otros subsistemas siguen funcionando normalmente

Cuando la CPU está en HALT y hay interrupciones pendientes (en IE y IF), la CPU se despierta automáticamente, incluso si IME (Interrupt Master Enable) está desactivado. Esto permite que los juegos hagan "polling" manual de IF después de HALT.

Problema identificado: En la implementación anterior, cuando la CPU estaba en HALT, el método step() devolvía solo 1 M-Cycle (4 T-Cycles) por tick. La PPU necesita 456 T-Cycles para completar una línea de escaneo, por lo que necesitaría 114 ticks en HALT para avanzar una sola línea. Esto hacía que la PPU avanzara extremadamente lento y nunca generara interrupciones V-Blank, causando que el juego se quedara congelado esperando interrupciones que nunca llegaban.

Fuente: Pan Docs - HALT behavior, System Clock, Interrupts

Implementación

1. Herramienta Doctor Viboy

Se creó tools/doctor_viboy.py, una herramienta de diagnóstico autónoma que:

  • Ejecuta el emulador sin interfaz gráfica: Carga una ROM y ejecuta instrucciones paso a paso
  • Detecta bucles infinitos: Mantiene un historial de las últimas 100 direcciones PC visitadas y detecta cuando el PC está atrapado en un rango pequeño de memoria durante muchas iteraciones (umbral: 5000 iteraciones)
  • Desensambla el bucle: Implementa un mini-desensamblador básico que muestra las instrucciones del bucle con sus mnemónicos (NOP, HALT, LD A, (nn), CP d8, JR, etc.)
  • Muestra el estado completo del sistema: Registros CPU (AF, BC, DE, HL, PC, SP, Flags), IME, estado HALT, y registros de hardware (LCDC, STAT, LY, IF, IE, DIV, TIMA, TAC)
  • Aplica heurísticas de diagnóstico: Analiza las instrucciones del bucle y el estado del hardware para identificar la causa:
    • Si lee 0xFF44 (LY) y compara con 144: "Esperando V-Blank"
    • Si lee 0xFF0F (IF): "Esperando Interrupción (Polling)"
    • Si lee 0xFF04 o 0xFF05: "Esperando Timer"
    • Si lee 0xFF41 (STAT): "Esperando Modo LCD"
    • Si CPU está en HALT sin interrupciones activas: "CPU en HALT esperando interrupción, pero ninguna está activa"
  • Proporciona recomendaciones específicas: Basadas en el diagnóstico, sugiere qué verificar o qué componente puede estar fallando

2. Fix de HALT en Viboy

Se modificó el método tick() en src/viboy.py para manejar correctamente el estado HALT:

  • Detección de HALT: Si la CPU está en HALT, se entra en un bucle especial
  • Avance múltiple de ciclos: Durante HALT, se ejecutan múltiples ticks (hasta 114 M-Cycles = 456 T-Cycles = 1 línea completa de PPU) en una sola llamada a tick()
  • Despertar automático: Si la CPU se despierta (ya no está en HALT) o hay interrupciones pendientes, se sale del bucle
  • Límite de seguridad: Se usa un límite máximo (114 M-Cycles) para evitar bucles infinitos si algo falla

Esto simula correctamente el comportamiento del hardware: durante HALT, el reloj del sistema sigue funcionando y los subsistemas (PPU, Timer) siguen avanzando normalmente, permitiendo que se generen interrupciones que despierten a la CPU.

Componentes creados/modificados

  • tools/doctor_viboy.py (nuevo): Herramienta de diagnóstico autónoma con detector de bucles, desensamblador básico, análisis de estado y heurísticas de diagnóstico
  • src/viboy.py (modificado): Mejora del método tick() para avanzar múltiples ciclos durante HALT, permitiendo que la PPU y el Timer sigan funcionando normalmente

Decisiones de diseño

Por qué 114 M-Cycles durante HALT: 114 M-Cycles = 456 T-Cycles = exactamente 1 línea completa de PPU. Esto permite que la PPU avance al menos una línea por cada llamada a tick() durante HALT, asegurando que el progreso sea visible y que eventualmente se generen interrupciones V-Blank.

Límite de seguridad: El límite de 114 M-Cycles evita bucles infinitos si algo falla. En hardware real, el reloj sigue funcionando indefinidamente, pero en el emulador necesitamos un límite para evitar que el programa se quede atascado si hay un bug.

Archivos Afectados

  • tools/doctor_viboy.py - Nueva herramienta de diagnóstico autónoma (creado)
  • src/viboy.py - Mejora del método tick() para manejar HALT correctamente (modificado)

Tests y Verificación

La verificación se realizó ejecutando el Doctor Viboy con ROMs reales:

A) Ejecución del Doctor Viboy con tetris_dx.gbc

  • Comando ejecutado: python tools/doctor_viboy.py tetris_dx.gbc --max-instructions 1000000
  • Entorno: Windows 10, Python 3.13.5
  • Resultado: PASSED - 1,000,000 instrucciones ejecutadas sin detectar bucles infinitos
  • Qué valida:
    • El emulador no se queda congelado en bucles infinitos
    • El PC cambia normalmente durante la ejecución
    • La CPU puede despertarse de HALT cuando ocurren interrupciones
  • Observación: El PC cambió normalmente (0x12E1 → 0x01D2 → 0x0616 → 0x5055 → 0x0283 → 0x4528 → 0x41A7 → 0x41B3 → 0x02DF → 0x0283), confirmando que el emulador avanza correctamente

B) Ejecución del Doctor Viboy con mario.gbc

  • Comando ejecutado: python tools/doctor_viboy.py mario.gbc --max-instructions 500000
  • Entorno: Windows 10, Python 3.13.5
  • Resultado: PASSED - 500,000 instrucciones ejecutadas sin detectar bucles infinitos
  • Qué valida:
    • El fix de HALT funciona correctamente con diferentes ROMs
    • El emulador no se queda congelado con mario.gbc
  • Observación: El PC cambió normalmente (0x12DF → 0x145F → 0x025B → 0x025A → 0x51F9), confirmando que el emulador funciona correctamente

C) Diagnóstico del problema original (antes del fix)

  • Comando ejecutado: python tools/doctor_viboy.py tetris_dx.gbc
  • Resultado: BUCLE INFINITO DETECTADO en 0x0283 (NOP)
  • Diagnóstico generado:
    • CPU en HALT esperando interrupción, pero ninguna está activa
    • V-Blank habilitada pero no activa (IF bit 0 = 0)
    • LY = 119 (no había llegado a 144 para activar V-Blank)
  • Conclusión: El Doctor Viboy identificó correctamente que el problema era que la CPU estaba en HALT esperando interrupciones que nunca llegaban porque la PPU no avanzaba lo suficientemente rápido durante HALT

Notas legales: Las ROMs tetris_dx.gbc y mario.gbc son aportadas por el usuario para pruebas locales. No se distribuyen, no se suben al repositorio, y no se enlazan descargas.

Fuentes Consultadas

  • Pan Docs: https://gbdev.io/pandocs/ - HALT behavior, System Clock, Interrupts
  • Pan Docs: LCD Timing, V-Blank Interrupt
  • Pan Docs: CPU Instruction Set (HALT opcode 0x76)

Integridad Educativa

Lo que Entiendo Ahora

  • HALT y el reloj del sistema: Cuando la CPU está en HALT, el reloj del sistema sigue funcionando. Los subsistemas (PPU, Timer) deben seguir avanzando normalmente. Esto es crítico para que se generen interrupciones que despierten a la CPU.
  • Timing durante HALT: En hardware real, durante HALT la CPU consume 1 ciclo por tick pero el reloj del sistema sigue funcionando a 4.19 MHz. La PPU y el Timer siguen avanzando a su velocidad normal. En el emulador, necesitamos simular esto avanzando múltiples ciclos durante HALT.
  • Herramientas de diagnóstico: Crear herramientas de diagnóstico autónomas es esencial para identificar problemas complejos como bucles infinitos. El Doctor Viboy puede detectar problemas que serían difíciles de encontrar manualmente.

Lo que Falta Confirmar

  • Precisión del timing durante HALT: El límite de 114 M-Cycles es una aproximación. En hardware real, el reloj sigue funcionando indefinidamente. ¿Es suficiente este límite para todos los casos? Se validará con más ROMs y tests.
  • Comportamiento exacto de HALT con IME desactivado: Según la documentación, HALT con IME=False puede tener comportamientos especiales. Se validará con más tests específicos.

Hipótesis y Suposiciones

Límite de 114 M-Cycles durante HALT: Asumimos que avanzar hasta 114 M-Cycles (1 línea de PPU) por cada llamada a tick() durante HALT es suficiente para que la PPU y el Timer avancen normalmente. Esto se basa en el hecho de que 456 T-Cycles es el tiempo de una línea completa de PPU, pero no está completamente verificado con documentación específica. Se validará empíricamente con ROMs reales.

Próximos Pasos

  • [ ] Probar el emulador con más ROMs para validar que el fix de HALT funciona en todos los casos
  • [ ] Mejorar el Doctor Viboy para detectar más patrones de bucles (ej: bucles que esperan cambios en registros específicos)
  • [ ] Añadir más heurísticas de diagnóstico al Doctor Viboy (ej: detectar esperas de DMA, esperas de cambios en STAT)
  • [ ] Crear tests unitarios específicos para validar el comportamiento de HALT con diferentes configuraciones de IME e interrupciones
  • [ ] Documentar el comportamiento exacto de HALT según la documentación técnica