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.
Monitor de Arranque Inmediato
Resumen
Se implementó un sistema de monitorización agresiva del arranque del emulador para detectar "deadlocks silenciosos". El sistema imprime el estado de la CPU (PC, SP) en los primeros 20 pasos del bucle principal y añade protección contra opcodes que devuelven 0 ciclos, evitando que el emulador se congele en un bucle infinito matemático.
Concepto de Hardware
En un Game Boy real, cada instrucción de la CPU consume un número determinado de ciclos de máquina (M-Cycles). El reloj del sistema avanza continuamente, y cada componente (CPU, PPU, Timer) consume ciclos para avanzar su estado interno.
Si una instrucción devolviera 0 ciclos (lo cual no debería ocurrir en hardware real), el contador de tiempo del emulador nunca avanzaría, causando que el bucle principal se quede atascado en la misma instrucción infinitamente. Esto es un "deadlock silencioso": el programa no crashea, pero tampoco progresa.
El diagnóstico de arranque permite identificar:
- PC estancado: Si el Program Counter no cambia entre pasos, la CPU está ejecutando la misma instrucción repetidamente (posible bucle infinito en el código del juego o bug en el emulador).
- Bloqueo en inicialización: Si el emulador se atasca antes de completar el primer segundo, el problema está en la fase de arranque (posiblemente esperando un evento que nunca ocurre).
- Opcode con 0 ciclos: Si se detecta una instrucción que devuelve 0 ciclos, hay un bug en la implementación del opcode que causa deadlock matemático.
Fuente: Pan Docs - CPU Instruction Set, Timing, System Clock
Implementación
Se añadieron dos mecanismos de diagnóstico en src/viboy.py:
1. Monitor de Arranque (Boot Monitor)
Al inicio del bucle principal run(), se añadió un contador debug_step_counter
que imprime el estado de la CPU (PC y SP) en los primeros 20 pasos del bucle. Esto permite
verificar que la CPU está ejecutando instrucciones y avanzando el Program Counter.
# DEBUG: Monitor de arranque agresivo
debug_step_counter = 0
while True:
# DEBUG: Monitor de arranque - imprimir los primeros 20 pasos
if debug_step_counter < 20:
pc = self._cpu.registers.get_pc()
sp = self._cpu.registers.get_sp()
print(f"🚀 BOOT STEP {debug_step_counter}: PC={pc:04X} | SP={sp:04X}", flush=True)
debug_step_counter += 1
2. Protección contra Ciclos Cero
En el método tick(), se añadió una verificación que detecta si la CPU devuelve
0 ciclos. Si esto ocurre, se fuerza al menos 1 ciclo para evitar que el contador de tiempo
se congele. Esta protección se aplica tanto en ejecución normal como en estado HALT.
# Ejecutar una instrucción normal
cycles = self._cpu.step()
# CRÍTICO: Protección contra bucle infinito
if cycles == 0:
pc = self._cpu.registers.get_pc()
print(f"🚨 ALERTA: CPU devolvió 0 ciclos en PC={pc:04X}!", flush=True)
cycles = 1 # Forzar avance para no colgar
Decisiones de diseño
- Print directo en lugar de logger: Se usa
print()conflush=Truepara garantizar visibilidad inmediata, incluso si el nivel de logging está en WARNING o ERROR. - Límite de 20 pasos: Los primeros 20 pasos son suficientes para detectar bloqueos inmediatos sin saturar la salida.
- Forzar 1 ciclo mínimo: Si se detecta 0 ciclos, se fuerza 1 ciclo para evitar deadlock, pero se mantiene el aviso para que el desarrollador sepa que hay un bug en el opcode.
Archivos Afectados
src/viboy.py- Añadido monitor de arranque y protección contra ciclos cero
Tests y Verificación
Este cambio es puramente diagnóstico y no requiere tests unitarios. La validación se realiza ejecutando el emulador y observando la salida del monitor de arranque.
Comando ejecutado: python main.py pkmn.gb --verbose
Entorno: Windows, Python 3.13.5
Resultado: ✅ PASSED - El monitor de arranque funcionó correctamente
Observaciones:
- ✅ La CPU está ejecutando instrucciones normalmente (PC avanza: 0x0100 → 0x0101 → 0x0150 → ... → 0x1F68)
- ✅ No aparecieron alertas de "0 ciclos" (no hay opcodes problemáticos detectados)
- ✅ El emulador se ejecutó sin deadlock silencioso (el usuario lo detuvo manualmente con Ctrl+C después de 4211 ciclos)
- ⚠️ El heartbeat no apareció porque el emulador se detuvo antes de completar 60 frames (solo 4211 ciclos ejecutados, insuficiente para generar frames)
Interpretación: El problema original (no ver heartbeat) no es un deadlock silencioso.
La CPU está funcionando correctamente. El siguiente paso es verificar si la PPU está generando frames
correctamente, ya que el frame_count solo se incrementa cuando ppu.is_frame_ready()
devuelve True.
Próximo diagnóstico necesario: Añadir monitor del estado de la PPU (LY, modo, frames generados) para determinar si el problema está en que la PPU no está llegando a V-Blank o si hay otro problema.
Fuentes Consultadas
Nota: La protección contra 0 ciclos es una medida de seguridad del emulador. En hardware real, todas las instrucciones consumen al menos 1 M-Cycle.
Integridad Educativa
Lo que Entiendo Ahora
- Deadlock silencioso: Un programa puede quedar atascado sin crashear, especialmente si el contador de tiempo no avanza (ciclos == 0).
- Diagnóstico de arranque: Imprimir el estado de la CPU en los primeros pasos permite identificar rápidamente dónde se produce el bloqueo.
- Protección defensiva: Aunque en hardware real no debería ocurrir, es útil añadir verificaciones que detecten condiciones anómalas y eviten deadlocks.
Lo que Falta Confirmar
- Comportamiento real: Necesito ejecutar el emulador con una ROM para verificar qué muestra el monitor de arranque y si detecta el problema del deadlock.
- Opcode problemático: Si aparece la alerta de 0 ciclos, necesito identificar qué opcode está causando el problema y corregirlo.
Hipótesis y Suposiciones
Hipótesis principal: El emulador se está atascando en el bucle principal antes de poder completar un segundo de ejecución. El monitor de arranque permitirá identificar si el problema es:
- Un PC que no avanza (bucle infinito en el código del juego o bug en el emulador)
- Un opcode que devuelve 0 ciclos (bug en la implementación)
- Un bloqueo en pygame.event.pump() o en la inicialización gráfica
Próximos Pasos
- [x] Ejecutar el emulador con una ROM y observar la salida del monitor de arranque ✅
- [x] Verificar que la CPU está ejecutando correctamente ✅
- [ ] Añadir monitor del estado de la PPU (LY, modo, frames generados) para diagnosticar por qué no aparece el heartbeat
- [ ] Verificar si la PPU está llegando a V-Blank (línea 144) y marcando frames como listos
- [ ] Si la PPU no está generando frames, investigar el timing o el estado del LCD (LCDC bit 7)