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

V-Blank Polling: IF Independiente de IME

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

Resumen

Se verificó y documentó que el registro IF (Interrupt Flag, 0xFF0F) se actualiza siempre cuando ocurre V-Blank, independientemente del estado de IME (Interrupt Master Enable). Esto permite que los juegos hagan "polling" manual de IF para detectar V-Blank sin usar interrupciones automáticas. Se crearon tests específicos para validar este comportamiento crítico y se mejoró la documentación en el código para dejar claro este aspecto del hardware.

Concepto de Hardware

En la Game Boy, existen dos mecanismos para detectar eventos del hardware (V-Blank, Timer, etc.):

  1. Interrupciones Automáticas: Si IME=True, IE tiene el bit activo, y IF tiene el bit activo, la CPU automáticamente salta al vector de interrupción.
  2. Polling Manual: El juego lee manualmente el registro IF y verifica si algún bit está activo. Si detecta V-Blank (bit 0), ejecuta su propia rutina de actualización.

CRÍTICO: El registro IF es hardware puro. Cuando ocurre un evento (V-Blank, Timer, etc.), el hardware siempre activa el bit correspondiente en IF, independientemente de:

  • El estado de IME (Interrupt Master Enable)
  • El estado de IE (Interrupt Enable, 0xFFFF)

Esto significa que incluso si un juego ejecuta DI (Disable Interrupts, IME=False) y nunca ejecuta EI (Enable Interrupts), el hardware sigue actualizando IF cuando ocurre V-Blank. El juego puede leer IF manualmente y detectar V-Blank para actualizar gráficos.

Fuente: Pan Docs - Interrupts, V-Blank Interrupt Flag

Implementación

Se verificó que la implementación actual en src/gpu/ppu.py ya actualiza IF correctamente cuando ocurre V-Blank, sin depender de IME. Sin embargo, se mejoró la documentación para dejar explícito este comportamiento crítico.

Componentes creados/modificados

  • src/gpu/ppu.py: Se añadió documentación explícita en el método step() explicando que IF se actualiza siempre, independientemente de IME.
  • tests/test_ppu_vblank_polling.py: Nuevo archivo con 3 tests que validan:
    • test_vblank_sets_if_with_ime_false: Verifica que IF se activa con IME=False
    • test_vblank_if_persists_until_cleared: Verifica que IF persiste hasta limpieza manual
    • test_vblank_if_independent_of_ie: Verifica que IF se actualiza independientemente de IE

Decisiones de diseño

La implementación actual ya era correcta: la PPU actualiza IF directamente llamando a mmu.write_byte(0xFF0F, if_val | 0x01) sin verificar el estado de IME. Esto es correcto porque IF es un registro de hardware que se actualiza automáticamente cuando ocurre el evento, no cuando la CPU procesa la interrupción.

La MMU permite escribir libremente en 0xFF0F (no hay restricciones especiales), lo cual es correcto porque el hardware puede actualizar IF en cualquier momento.

Archivos Afectados

  • src/gpu/ppu.py - Mejora de documentación en método step() para explicar que IF se actualiza independientemente de IME
  • tests/test_ppu_vblank_polling.py - Nuevo archivo con tests para validar V-Blank polling

Tests y Verificación

Se ejecutaron los tests nuevos para validar el comportamiento de V-Blank polling:

Comando ejecutado

pytest tests/test_ppu_vblank_polling.py -v

Entorno

  • OS: Windows 10
  • Python: 3.13.5

Resultado

============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2, pluggy-1.6.0
collecting ... collected 3 items

tests/test_ppu_vblank_polling.py::TestPPUVBlankPolling::test_vblank_sets_if_with_ime_false PASSED [ 33%]
tests/test_ppu_vblank_polling.py::TestPPUVBlankPolling::test_vblank_if_persists_until_cleared PASSED [ 66%]
tests/test_ppu_vblank_polling.py::TestPPUVBlankPolling::test_vblank_if_independent_of_ie PASSED [100%]

======================== 3 passed, 2 warnings in 0.31s ========================

Qué valida

  • test_vblank_sets_if_with_ime_false: Valida que el registro IF se actualiza cuando ocurre V-Blank (LY=144), incluso cuando IME=False. Esto demuestra que el hardware actualiza IF independientemente del estado de IME, permitiendo polling manual.
  • test_vblank_if_persists_until_cleared: Valida que IF permanece activo hasta que el juego lo limpia manualmente, y que se reactiva en el siguiente V-Blank. Esto demuestra el comportamiento de polling: el juego puede leer IF, detectar V-Blank, hacer su trabajo, y limpiar el bit manualmente.
  • test_vblank_if_independent_of_ie: Valida que IF se actualiza incluso cuando IE (Interrupt Enable) tiene el bit 0 deshabilitado. Esto demuestra que IF es hardware puro y no depende de la configuración de IE.

Código del test

Fragmento esencial del test principal:

def test_vblank_sets_if_with_ime_false(self) -> None:
    """Test: IF se actualiza cuando ocurre V-Blank, incluso con IME=False."""
    mmu = MMU(None)
    cpu = CPU(mmu)
    ppu = PPU(mmu)
    mmu.set_ppu(ppu)
    
    # Configurar IME=False (interrupciones automáticas deshabilitadas)
    cpu.ime = False
    
    # Limpiar IF inicialmente
    mmu.write_byte(IO_IF, 0x00)
    
    # Avanzar PPU hasta V-Blank (línea 144)
    total_cycles = 144 * 456
    ppu.step(total_cycles)
    
    # CRÍTICO: IF debe tener el bit 0 activado, incluso con IME=False
    if_val = mmu.read_byte(IO_IF)
    assert (if_val & 0x01) == 0x01

Por qué este test demuestra algo del hardware: Este test verifica que el registro IF se comporta como hardware puro: se actualiza automáticamente cuando ocurre el evento (V-Blank), sin depender de la configuración de software (IME). Esto es exactamente cómo funciona el hardware real de la Game Boy, permitiendo que los juegos hagan polling manual incluso cuando las interrupciones automáticas están deshabilitadas.

Fuentes Consultadas

  • Pan Docs: Interrupts - Explicación de IF, IE, IME y polling
  • Pan Docs: LCD Timing - V-Blank y registro LY

Integridad Educativa

Lo que Entiendo Ahora

  • IF es hardware puro: El registro IF (0xFF0F) se actualiza automáticamente por el hardware cuando ocurre un evento (V-Blank, Timer, etc.), independientemente del estado de IME o IE. Esto es diferente de otros sistemas donde los flags de interrupción pueden depender de la configuración de software.
  • Polling vs Interrupciones Automáticas: Los juegos pueden usar dos estrategias para detectar V-Blank: (1) Interrupciones automáticas (requiere IME=True, IE con bit activo, y IF con bit activo), o (2) Polling manual (leer IF periódicamente y verificar bits). La segunda estrategia funciona incluso con IME=False.
  • Separación de responsabilidades: IF indica "qué eventos han ocurrido", IE indica "qué eventos quiero procesar automáticamente", y IME indica "si quiero procesar interrupciones automáticamente". El hardware siempre actualiza IF, pero solo procesa automáticamente si IME=True y el bit correspondiente está activo en IE.

Lo que Falta Confirmar

  • Comportamiento en hardware real: Aunque la documentación y los tests confirman que IF se actualiza independientemente de IME, sería útil verificar esto con un test ROM específico que haga polling de IF con IME=False y verifique que detecta V-Blank correctamente.
  • Timing exacto: El test actual verifica que IF se activa cuando LY=144, pero no verifica el timing exacto dentro del ciclo de la línea. En hardware real, IF se activa al inicio de la línea 144, pero no está claro si ocurre al principio o al final del ciclo de la línea.

Hipótesis y Suposiciones

Suposición verificada: La implementación actual asume que IF se actualiza cuando LY llega a 144, independientemente de IME. Los tests confirman que esta suposición es correcta y coincide con el comportamiento documentado del hardware.

Suposición pendiente: Asumimos que el timing de activación de IF (al inicio de la línea 144) es correcto, pero esto no está completamente verificado con test ROMs. Si un juego hace polling muy preciso del timing, podría haber discrepancias menores.

Próximos Pasos

  • [ ] Verificar que los juegos que hacen polling de IF con IME=False pueden detectar V-Blank correctamente
  • [ ] Si el juego sigue sin avanzar, investigar si hay otros aspectos del polling que no están implementados correctamente
  • [ ] Considerar añadir logging opcional para rastrear cuando el juego lee/escribe en IF para depurar polling
  • [ ] Verificar si el juego espera algún estado específico de LCDC o otros registros antes de hacer polling