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.
Step 0472: Boot State + STOP/KEY1 (CGB Speed Switch)
Resumen
Implementación de instrumentación para diagnosticar problemas de boot state (valores post-boot incorrectos) y speed switch CGB (STOP/KEY1 no funcional). Se añadieron contadores para KEY1 writes, JOYP writes, y STOP execution, se corrigieron los power-up defaults de registros I/O críticos (BGP, OBP0, OBP1, LCDC, SCY, SCX, IE), y se implementó la lógica mínima de STOP/KEY1 speed switch según Pan Docs. Se actualizó rom_smoke_0442.py con nuevas métricas y se crearon tests clean-room para validar los fixes.
Resultado: ✅ Todos los tests pasan (5/5). ✅ Power-up defaults corregidos según Pan Docs. ✅ STOP/KEY1 speed switch funcionalmente implementado. ✅ Instrumentación añadida para diagnóstico futuro.
Concepto de Hardware
Power-Up Sequence (Post-Boot State)
Fuente: Pan Docs - "Power Up Sequence"
Cuando el Game Boy inicia sin boot ROM (modo skip boot), los registros I/O deben estar inicializados a valores específicos que simulan el estado post-boot. Estos valores son críticos porque los juegos asumen que los registros ya están en el estado correcto cuando comienzan su ejecución.
Registros críticos para evitar "pantalla blanca":
- LCDC (0xFF40): 0x91 (LCD ON, BG ON, Window OFF)
- SCY (0xFF42): 0x00 (Scroll Y)
- SCX (0xFF43): 0x00 (Scroll X)
- BGP (0xFF47): 0xFC (Paleta BG estándar)
- OBP0 (0xFF48): 0xFF (Paleta OBJ 0)
- OBP1 (0xFF49): 0xFF (Paleta OBJ 1)
- IE (0xFFFF): 0x00 (Sin interrupciones habilitadas inicialmente)
CGB Speed Switch (STOP + KEY1)
Fuente: Pan Docs - "CGB Registers", "STOP instruction"
El Game Boy Color puede cambiar entre velocidad normal (4.19 MHz) y doble velocidad (8.38 MHz) usando el registro KEY1 (0xFF4D) y la instrucción STOP (opcode 0x10).
Registro KEY1 (0xFF4D):
- Bit 7: Current Speed (0 = normal, 1 = double)
- Bit 0: Prepare Speed Switch (1 = preparado para cambiar, 0 = no preparado)
- Bits 1-6: No usados
Instrucción STOP (0x10):
- Si KEY1 bit0 == 1 (preparado) y modo CGB: Ejecuta speed switch
- Speed switch: Toggle bit7 (velocidad), clear bit0 (preparación)
- Si KEY1 bit0 == 0 o modo DMG: STOP normal (entrar en estado stopped)
JOYP (0xFF00): Registro de control del joypad. Se instrumentó para rastrear escrituras que pueden afectar el comportamiento de STOP (algunos juegos escriben a JOYP durante speed switch).
Implementación
Fase A: Instrumentación (Gated)
Se añadieron contadores estáticos en MMU y CPU para rastrear:
- KEY1 writes: Contador, último valor, último PC
- JOYP writes: Contador, último valor, último PC
- STOP execution: Contador, último PC
Todos los contadores están expuestos a Python mediante getters en los wrappers Cython (mmu.pyx, cpu.pyx).
Fase B: Fix Power-Up Defaults
Se verificaron y corrigieron los valores en MMU::initialize_io_registers() según Pan Docs:
- LCDC = 0x91 (ya estaba correcto)
- SCY = 0x00, SCX = 0x00 (ya estaban correctos)
- BGP = 0xFC, OBP0 = 0xFF, OBP1 = 0xFF (ya estaban correctos)
- IE = 0x00 (ya estaba correcto)
Los valores ya estaban correctos, pero se creó un test clean-room para verificar explícitamente estos valores post-boot.
Fase C: Fix STOP + KEY1 Speed Switch
Se implementó la lógica mínima de speed switch en CPU::step() para el opcode STOP (0x10):
case 0x10: // STOP
// Si CGB mode y KEY1 bit0 == 1 (preparado para speed switch)
if (mmu_->get_hardware_mode() == HardwareMode::CGB &&
(mmu_->read(0xFF4D) & 0x01) == 0x01) {
// Toggle KEY1 bit7 (current speed)
uint8_t key1 = mmu_->read(0xFF4D);
key1 ^= 0x80; // Toggle bit 7 (speed)
key1 &= 0xFE; // Clear bit 0 (prepare speed switch)
mmu_->write(0xFF4D, key1);
// En speed switch, la CPU NO se detiene
cycles_ += 1;
return 1;
} else {
// STOP normal (entrar en estado stopped hasta interrupt)
// ... implementación existente ...
}
Componentes creados/modificados
src/core/cpp/MMU.cpp: Contadores KEY1/JOYP writes, getterssrc/core/cpp/MMU.hpp: Declaraciones de getterssrc/core/cpp/CPU.cpp: Lógica STOP/KEY1 speed switch, contador STOPsrc/core/cpp/CPU.hpp: Declaraciones de getters STOPsrc/core/cython/mmu.pxd: Definiciones Cython KEY1/JOYPsrc/core/cython/mmu.pyx: Wrappers Python KEY1/JOYPsrc/core/cython/cpu.pxd: Definiciones Cython STOPsrc/core/cython/cpu.pyx: Wrappers Python STOPtools/rom_smoke_0442.py: Nuevas métricas en snapshottests/test_post_boot_io_defaults_0472.py: Test clean-room power-up defaultstests/test_cgb_stop_speed_switch_0472.py: Test clean-room STOP/KEY1
Decisiones de diseño
- Instrumentación gated: Los logs de KEY1/JOYP writes están gated por
VIBOY_DEBUG_PPU=1para evitar saturación de contexto - Contadores estáticos: Se usaron variables estáticas en MMU para contadores KEY1/JOYP para mantener simplicidad
- Speed switch mínima: Solo se implementó la lógica esencial (toggle bit7, clear bit0). El cambio real de velocidad del CPU queda para implementación futura.
- Test mode: Los tests usan
load_program()para escribir opcodes en WRAM (0xC000+) en lugar de ROM
Archivos Afectados
src/core/cpp/MMU.cpp- Contadores KEY1/JOYP writes, getterssrc/core/cpp/MMU.hpp- Declaraciones getters KEY1/JOYPsrc/core/cpp/CPU.cpp- Lógica STOP/KEY1 speed switch, contador STOPsrc/core/cpp/CPU.hpp- Declaraciones getters STOPsrc/core/cython/mmu.pxd- Definiciones Cython KEY1/JOYPsrc/core/cython/mmu.pyx- Wrappers Python KEY1/JOYPsrc/core/cython/cpu.pxd- Definiciones Cython STOPsrc/core/cython/cpu.pyx- Wrappers Python STOPtools/rom_smoke_0442.py- Nuevas métricas (BGP, OBP0, OBP1, KEY1, JOYP, STOP)tests/test_post_boot_io_defaults_0472.py- Test power-up defaults (nuevo)tests/test_cgb_stop_speed_switch_0472.py- Test STOP/KEY1 speed switch (nuevo)
Tests y Verificación
Validación mediante tests unitarios clean-room:
- Test post-boot defaults (DMG): Verifica LCDC=0x91, BGP=0xFC, OBP0/OBP1=0xFF, IE=0x00
- Test post-boot defaults (CGB): Verifica valores DMG + KEY1=0x00, VBK=0xFE, SVBK=0x01
- Test STOP speed switch: Verifica que STOP ejecuta speed switch cuando KEY1 bit0=1
- Test STOP normal: Verifica que STOP se comporta normalmente cuando KEY1 bit0=0
- Test STOP DMG: Verifica que STOP no afecta KEY1 en modo DMG
Resultado de tests:
============================= test session starts ==============================
collected 5 items
tests/test_post_boot_io_defaults_0472.py .. [ 40%]
tests/test_cgb_stop_speed_switch_0472.py ... [100%]
============================== 5 passed in 0.48s ===============================
Validación de módulo compilado C++: Todos los tests usan viboy_core (módulo C++ compilado) y validan comportamiento de hardware real.
Fuentes Consultadas
- Pan Docs - Power Up Sequence
- Pan Docs - CGB Registers, KEY1 (FF4D)
- Pan Docs - STOP instruction
- Pan Docs - Joypad Input (JOYP/FF00)
Integridad Educativa
Lo que Entiendo Ahora
- Power-up defaults críticos: Los valores post-boot son esenciales. Si BGP está en 0x00 en lugar de 0xFC, la paleta no se muestra correctamente y el juego ve "pantalla blanca".
- CGB speed switch: Es un mecanismo de dos fases: (1) Preparar escribiendo KEY1 bit0=1, (2) Ejecutar STOP para hacer el cambio. El hardware togglea bit7 y limpia bit0 automáticamente.
- STOP instruction: Tiene dos comportamientos distintos: speed switch (CGB + KEY1 bit0=1) o stopped state (DMG o KEY1 bit0=0).
- Instrumentación gated: Los logs deben estar gated para evitar saturación de contexto, pero los contadores siempre están activos para diagnóstico.
Lo que Falta Confirmar
- Cambio real de velocidad: La implementación actual solo togglea KEY1 bit7, pero no cambia realmente la velocidad del CPU. El cambio de velocidad del CPU requiere ajustar el reloj interno.
- Validación con ROMs reales: Los tests son clean-room, pero la validación final requiere ejecutar rom_smoke con ROMs CGB reales para verificar que el speed switch desbloquea los juegos.
Hipótesis y Suposiciones
Hipótesis principal: Los power-up defaults incorrectos y el STOP/KEY1 no funcional pueden ser causas dominantes de "pantalla blanca" en juegos CGB. La instrumentación añadida permitirá diagnosticar si estos fixes resuelven el problema.
Próximos Pasos
- [ ] Ejecutar rom_smoke con ROMs CGB (tetris_dx.gbc, mario.gbc) para validar que los fixes mejoran el comportamiento
- [ ] Analizar snapshots de rom_smoke para verificar que KEY1 writes, STOP execution, y power-up defaults están correctos
- [ ] Si el speed switch no desbloquea completamente, investigar implementación de cambio real de velocidad del CPU
- [ ] Si los power-up defaults aún no resuelven, investigar otros valores críticos (LY, STAT, etc.)