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

Step 0472: Boot State + STOP/KEY1 (CGB Speed Switch)

Fecha: 2026-01-04 Step ID: 0472 Estado: VERIFIED

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, getters
  • src/core/cpp/MMU.hpp: Declaraciones de getters
  • src/core/cpp/CPU.cpp: Lógica STOP/KEY1 speed switch, contador STOP
  • src/core/cpp/CPU.hpp: Declaraciones de getters STOP
  • src/core/cython/mmu.pxd: Definiciones Cython KEY1/JOYP
  • src/core/cython/mmu.pyx: Wrappers Python KEY1/JOYP
  • src/core/cython/cpu.pxd: Definiciones Cython STOP
  • src/core/cython/cpu.pyx: Wrappers Python STOP
  • tools/rom_smoke_0442.py: Nuevas métricas en snapshot
  • tests/test_post_boot_io_defaults_0472.py: Test clean-room power-up defaults
  • tests/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=1 para 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, getters
  • src/core/cpp/MMU.hpp - Declaraciones getters KEY1/JOYP
  • src/core/cpp/CPU.cpp - Lógica STOP/KEY1 speed switch, contador STOP
  • src/core/cpp/CPU.hpp - Declaraciones getters STOP
  • src/core/cython/mmu.pxd - Definiciones Cython KEY1/JOYP
  • src/core/cython/mmu.pyx - Wrappers Python KEY1/JOYP
  • src/core/cython/cpu.pxd - Definiciones Cython STOP
  • src/core/cython/cpu.pyx - Wrappers Python STOP
  • tools/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

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.)