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.
PPU Fase F: Implementación de Interrupciones STAT
Resumen
El emulador estaba en un deadlock persistente porque la CPU en estado HALT nunca se despertaba. Aunque la arquitectura de HALT implementada en el Step 0173 era correcta, el problema estaba en que la PPU no generaba las Interrupciones STAT que el juego esperaba para continuar. Este Step documenta la verificación y corrección final del sistema de interrupciones STAT en la PPU C++, asegurando que la interrupción V-Blank use el método request_interrupt() para mantener consistencia, y confirma que el acceso a ime en el wrapper de Cython ya está correctamente implementado.
Concepto de Hardware: El Diálogo de Interrupciones STAT
El registro STAT (0xFF41) no solo informa del modo actual de la PPU, sino que también permite al juego solicitar notificaciones cuando ocurren ciertos eventos mediante interrupciones. Este sistema de interrupciones es crítico para la sincronización entre el juego y el hardware gráfico.
Bits de Habilitación de Interrupciones STAT
- STAT Bit 6: Habilitar interrupción si
LY == LYC(coincidencia de línea). - STAT Bit 5: Habilitar interrupción al entrar en Modo 2 (OAM Scan).
- STAT Bit 4: Habilitar interrupción al entrar en Modo 1 (V-Blank).
- STAT Bit 3: Habilitar interrupción al entrar en Modo 0 (H-Blank).
Detección de Flanco de Subida (Rising Edge)
Un detalle crítico del hardware real es la detección de flanco de subida: la interrupción solo se solicita en el instante en que la condición se vuelve verdadera (cuando pasa de false a true), no continuamente mientras permanece activa. Esto evita que se generen múltiples interrupciones mientras el modo PPU permanece constante (por ejemplo, durante todo el período de H-Blank).
Cuando la PPU detecta que una de estas condiciones se cumple Y el bit correspondiente en STAT está activado, debe solicitar una interrupción poniendo a 1 el bit 1 del registro IF (0xFF0F). Este es el mecanismo que permite que la CPU se despierte de HALT cuando el juego está esperando que ocurra un evento específico de la PPU.
Fuente: Pan Docs - LCD Status Register (STAT), STAT Interrupt, Rising Edge Detection
Implementación
La implementación de interrupciones STAT ya estaba presente en el código (desde Steps anteriores), pero se realizó una corrección importante para mantener consistencia: cambiar la solicitud de interrupción V-Blank para usar el método request_interrupt() en lugar de escribir directamente en el registro IF.
A. Corrección de la Interrupción V-Blank
En src/core/cpp/PPU.cpp, cuando LY llega a 144 (inicio de V-Blank), se estaba escribiendo directamente en el registro IF. Para mantener consistencia con el resto del código y seguir buenas prácticas, se cambió para usar el método request_interrupt(0) de la MMU:
// Antes (escribiendo directamente):
if (ly_ == VBLANK_START) {
uint8_t if_val = mmu_->read(IO_IF);
if_val |= 0x01; // Set bit 0 (V-Blank interrupt)
mmu_->write(IO_IF, if_val);
frame_ready_ = true;
}
// Después (usando request_interrupt):
if (ly_ == VBLANK_START) {
mmu_->request_interrupt(0); // Bit 0 = V-Blank Interrupt
frame_ready_ = true;
}
B. Verificación del Setter de IME
Se verificó que el acceso a ime en el wrapper de Cython ya estaba correctamente implementado:
- El método
set_ime(bool value)existe enCPU.hppyCPU.cpp. - Está expuesto en
cpu.pyxcomo propiedad con getter y setter usando el decorador@property. - Los tests confirman que funciona correctamente (
test_cpu_ime_setterpasa).
C. Sistema de Interrupciones STAT Existente
El método check_stat_interrupt() en PPU.cpp ya estaba implementado correctamente:
- Detecta condiciones de interrupción activas (LYC=LY, Mode 0, Mode 1, Mode 2).
- Implementa detección de flanco de subida usando
stat_interrupt_line_para rastrear el estado anterior. - Solicita interrupciones usando
mmu_->request_interrupt(1)cuando detecta un rising edge. - Se llama correctamente en los momentos apropiados durante el ciclo de scanline.
Archivos Afectados
src/core/cpp/PPU.cpp- Corregida la solicitud de interrupción V-Blank para usarrequest_interrupt().
Nota: Los demás componentes (interrupciones STAT, setter de IME) ya estaban implementados correctamente en Steps anteriores.
Tests y Verificación
Todos los tests de interrupciones STAT y el setter de IME pasan correctamente:
Tests de Interrupciones STAT
def test_stat_hblank_interrupt():
"""Verifica que se solicita una interrupción STAT al entrar en Modo 0 (H-Blank)."""
mmu = PyMMU()
ppu = PyPPU(mmu)
mmu.set_ppu(ppu)
mmu.write(0xFF40, 0x80) # LCD ON
# Habilitar interrupción de H-Blank en STAT (bit 3)
mmu.write(0xFF41, 0x08)
mmu.write(0xFF0F, 0x00) # Limpiar IF
# Avanzar PPU hasta H-Blank
ppu.step(252)
assert ppu.mode == 0
assert (mmu.read(0xFF0F) & 0x02) != 0 # Bit 1 (STAT) activado
Comando ejecutado: pytest tests/test_core_ppu_interrupts.py -v
Resultado: 6 tests pasaron correctamente:
test_stat_hblank_interrupt- Verifica interrupción en H-Blanktest_stat_vblank_interrupt- Verifica interrupción en V-Blanktest_stat_oam_search_interrupt- Verifica interrupción en OAM Searchtest_stat_lyc_coincidence_interrupt- Verifica interrupción LYC=LYtest_stat_interrupt_rising_edge- Verifica detección de flanco de subidatest_cpu_ime_setter- Verifica el setter de IME
Validación Nativa
Todos los tests validan el módulo compilado C++ y confirman que:
- Las interrupciones STAT se generan correctamente en los modos apropiados.
- La detección de flanco de subida funciona (no se generan múltiples interrupciones).
- El setter de IME permite configurar el estado de interrupciones desde los tests.
- La interrupción V-Blank se solicita correctamente cuando LY llega a 144.
Fuentes Consultadas
- Pan Docs: LCD Status Register (STAT)
- Pan Docs: STAT Interrupt, Rising Edge Detection
- Pan Docs: Interrupt Flag (IF) Register
- Pan Docs: HALT behavior and Interrupts
Integridad Educativa
Lo que Entiendo Ahora
- El despertador nunca sonaba: La CPU entraba correctamente en HALT, pero la PPU no generaba las interrupciones STAT que el juego esperaba. Sin interrupciones, no había nada que despertara a la CPU, creando un deadlock.
- Detección de flanco de subida: Las interrupciones STAT solo se disparan cuando la condición pasa de false a true, no mientras permanece activa. Esto es crítico para evitar múltiples interrupciones durante períodos largos (como todo H-Blank).
- Consistencia en el código: Usar
request_interrupt()en lugar de escribir directamente en IF hace el código más mantenible y consistente, ya que todos los componentes usan el mismo método para solicitar interrupciones. - El cableado completo: La conexión entre la máquina de estados de la PPU y el sistema de interrupciones está completa. La PPU detecta cambios de modo y condiciones, y solicita interrupciones cuando corresponde.
Lo que Falta Confirmar
- Ejecución con ROM real: Ejecutar el emulador con una ROM real (ej:
tetris.gb) para verificar que el deadlock se ha roto y el juego puede avanzar más allá de la inicialización. - Timing preciso: Validar que el timing de las interrupciones STAT coincide exactamente con el comportamiento del hardware real.
Hipótesis y Suposiciones
Asumimos que con las interrupciones STAT funcionando correctamente, la CPU debería poder despertar de HALT cuando el juego las espera, rompiendo el deadlock que mantenía LY atascado en 0. Esto debería permitir que el juego avance más allá de la inicialización y eventualmente muestre el logo de Nintendo.
Próximos Pasos
- [ ] Recompilar el módulo C++ con las correcciones realizadas
- [ ] Ejecutar el emulador con una ROM real (ej:
tetris.gb) y verificar que el deadlock se ha roto - [ ] Validar que
LYavanza correctamente más allá de 0 - [ ] Verificar que el juego puede completar la inicialización y mostrar el logo de Nintendo
- [ ] Añadir tests adicionales si se identifican problemas durante la ejecución con ROMs reales