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

Implementación del Prefijo CB (Instrucciones Extendidas) en C++

Fecha: 2025-12-19 Step ID: 0110 Estado: Completado

Resumen

Se implementó el prefijo CB completo (256 instrucciones extendidas) en C++, incluyendo rotaciones, shifts, BIT, RES y SET. Esta es la "joya de la corona" de la CPU de la Game Boy, permitiendo manipulación de bits nativa y extremadamente rápida. Se añadió el método handle_cb() que decodifica el opcode CB usando lógica bitwise eficiente, y se implementaron todas las operaciones según Pan Docs. Todos los tests pasan (11/11), validando el comportamiento correcto de flags, timing y acceso a memoria indirecta.

Concepto de Hardware

La Game Boy tiene más instrucciones de las que caben en 1 byte (256 opcodes). Cuando la CPU lee el opcode 0xCB, sabe que el siguiente byte debe interpretarse con una tabla diferente de instrucciones. El prefijo CB permite acceder a 256 instrucciones adicionales, organizadas de forma muy ordenada:

  • 0x00-0x3F: Rotaciones y Shifts (RLC, RRC, RL, RR, SLA, SRA, SWAP, SRL)
    • RLC/RRC: Rotación circular (bit 7/0 sale y entra por el otro extremo)
    • RL/RR: Rotación a través de Carry (bit 7/0 va a C, antiguo C entra)
    • SLA: Shift Left Arithmetic (multiplica por 2, bit 7 → C, bit 0 ← 0)
    • SRA: Shift Right Arithmetic (divide por 2 con signo, preserva bit 7)
    • SRL: Shift Right Logical (divide por 2 sin signo, bit 7 ← 0)
    • SWAP: Intercambia nibbles altos y bajos (0xF0 → 0x0F)
  • 0x40-0x7F: BIT b, r (Test bit) - Prueba si un bit está encendido
    • Z = !bit (si bit está apagado, Z=1)
    • H = 1 (siempre, quirk del hardware)
    • N = 0 (siempre)
    • C = Preservado (no se modifica)
  • 0x80-0xBF: RES b, r (Reset bit) - Apaga un bit específico
    • No afecta flags (preserva todos)
  • 0xC0-0xFF: SET b, r (Set bit) - Enciende un bit específico
    • No afecta flags (preserva todos)

Estructura Matemática del Opcode CB: El opcode CB está perfectamente estructurado para decodificación eficiente:

  • Bits 0-2: Registro (0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A)
  • Bits 3-5: Índice de Bit (0-7) para BIT/SET/RES, o tipo de operación para rotaciones
  • Bits 6-7: Grupo de operación (00=Rotaciones/Shifts, 01=BIT, 10=RES, 11=SET)

Diferencia Crítica con Rotaciones Rápidas: Las rotaciones del prefijo CB (ej: RLC) calculan el flag Z según el resultado, mientras que las rotaciones rápidas (ej: RLCA) siempre ponen Z=0. Esta diferencia es crítica para la compatibilidad con juegos.

Optimización C++: Las operaciones bitwise nativas de C++ (&, |, ~, <<, >>) se compilan directamente a instrucciones de máquina de un solo ciclo, ofreciendo rendimiento máximo. El acceso a memoria indirecta (HL) requiere 4 M-Cycles (leer, modificar, escribir), mientras que los registros solo requieren 2 M-Cycles.

Implementación

Se implementó el método handle_cb() en CPU.cpp que decodifica el opcode CB usando lógica bitwise eficiente. El método extrae los componentes del opcode (registro, bit índice, grupo de operación) y ejecuta la operación correspondiente usando un switch anidado para máximo rendimiento.

Componentes creados/modificados

  • CPU.hpp: Añadida declaración de handle_cb() con documentación completa.
  • CPU.cpp:
    • Implementación de handle_cb() con decodificación bitwise
    • Switch anidado para rotaciones/shifts (8 operaciones: RLC, RRC, RL, RR, SLA, SRA, SWAP, SRL)
    • Lógica para BIT (testear bits con flags correctos)
    • Lógica para RES y SET (resetear y establecer bits sin afectar flags)
    • Manejo de acceso a memoria indirecta (HL) con timing correcto
  • CPU.cpp (step()): Añadido case 0xCB que llama a handle_cb().
  • tests/test_core_cpu_cb.py: Suite completa de 11 tests validando:
    • BIT con bits encendidos/apagados y preservación de C
    • RL con y sin carry previo
    • SET/RES en memoria indirecta (HL)
    • SWAP con diferentes valores
    • RLC con diferencia crítica de flags Z vs RLCA

Decisiones de diseño

  • Decodificación Bitwise: Se usa extracción de bits (&, >>) para decodificar el opcode CB en lugar de un switch gigante de 256 casos. Esto reduce el tamaño del código y mejora la predicción de ramas del procesador host.
  • Switch Anidado: Se usa un switch externo para el grupo de operación (bits 6-7) y un switch interno para el tipo de rotación/shift (bits 3-5). Esto permite al compilador optimizar mejor que un switch plano de 256 casos.
  • Preservación de Flags: RES y SET no modifican flags, mientras que BIT siempre pone H=1 y N=0, pero preserva C. Esta lógica se implementa explícitamente para garantizar compatibilidad con el hardware real.
  • Timing Preciso: Se retorna 2 M-Cycles para registros y 4 M-Cycles para (HL), reflejando el costo real de acceso a memoria (leer, modificar, escribir).
  • Early Return en BIT: BIT no modifica el registro/memoria, por lo que se retorna inmediatamente después de actualizar flags, evitando escritura innecesaria.

Código Clave

int CPU::handle_cb() {
    uint8_t cb_opcode = fetch_byte();
    uint8_t reg_code = cb_opcode & 0x07;        // Bits 0-2
    uint8_t bit_index = (cb_opcode >> 3) & 0x07; // Bits 3-5
    uint8_t op_group = (cb_opcode >> 6) & 0x03;  // Bits 6-7
    
    bool is_memory = (reg_code == 6);
    uint8_t value = read_register_or_mem(reg_code);
    
    // Switch según grupo de operación
    switch (op_group) {
        case 0x00: // Rotaciones/Shifts
            // Switch interno para tipo de operación
            break;
        case 0x01: // BIT
            // Testear bit, actualizar flags, retornar
            break;
        case 0x02: // RES
            result = value & ~(1 << bit_index);
            break;
        case 0x03: // SET
            result = value | (1 << bit_index);
            break;
    }
    
    write_register_or_mem(reg_code, result);
    return is_memory ? 4 : 2;
}

Archivos Afectados

  • src/core/cpp/CPU.hpp - Añadida declaración de handle_cb()
  • src/core/cpp/CPU.cpp - Implementación completa del prefijo CB (200+ líneas)
  • tests/test_core_cpu_cb.py - Suite de 11 tests para validar operaciones CB

Tests y Verificación

Se creó una suite completa de tests en test_core_cpu_cb.py que valida:

  • BIT: Test con bits encendidos/apagados, verificación de flags (Z inverso, H=1 siempre, C preservado)
  • RL: Rotación a través de carry con y sin carry previo, verificación de flags
  • SET/RES: Operaciones en memoria indirecta (HL), verificación de timing (4 M-Cycles)
  • SWAP: Intercambio de nibbles con diferentes valores, verificación de flags Z
  • RLC: Diferencia crítica de flags Z vs rotaciones rápidas (RLCA)

Resultado: Todos los tests pasan (11/11) ✅

$ python -m pytest tests/test_core_cpu_cb.py -v
============================= test session starts =============================
tests/test_core_cpu_cb.py::TestCBBit::test_cb_bit_7_h_set PASSED
tests/test_core_cpu_cb.py::TestCBBit::test_cb_bit_7_h_clear PASSED
tests/test_core_cpu_cb.py::TestCBBit::test_cb_bit_preserves_carry PASSED
tests/test_core_cpu_cb.py::TestCBRot::test_cb_rl_c PASSED
tests/test_core_cpu_cb.py::TestCBRot::test_cb_rl_with_carry PASSED
tests/test_core_cpu_cb.py::TestCBHL::test_cb_set_3_hl PASSED
tests/test_core_cpu_cb.py::TestCBHL::test_cb_res_0_hl PASSED
tests/test_core_cpu_cb.py::TestCBSwap::test_cb_swap_a PASSED
tests/test_core_cpu_cb.py::TestCBSwap::test_cb_swap_zero_result PASSED
tests/test_core_cpu_cb.py::TestCBRLC::test_cb_rlc_z_flag PASSED
tests/test_core_cpu_cb.py::TestCBRLC::test_cb_rlc_nonzero_result PASSED
============================= 11 passed in 0.07s =============================

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Estructura Matemática del Opcode CB: El opcode CB está perfectamente organizado para decodificación eficiente usando operaciones bitwise. Los bits 6-7 determinan el grupo de operación, los bits 3-5 determinan el tipo de operación o el índice de bit, y los bits 0-2 determinan el registro destino.
  • Diferencia Crítica de Flags: Las rotaciones del prefijo CB (RLC, RRC, RL, RR) calculan el flag Z según el resultado, mientras que las rotaciones rápidas (RLCA, RRCA, RLA, RRA) siempre ponen Z=0. Esta diferencia es crítica para la compatibilidad con juegos.
  • Quirks del Hardware: BIT siempre pone H=1, independientemente del valor del bit. RES y SET no afectan flags, preservando todos los flags anteriores. Estas peculiaridades se implementan explícitamente para garantizar compatibilidad.
  • Timing Preciso: El acceso a memoria indirecta (HL) requiere 4 M-Cycles porque implica leer, modificar y escribir, mientras que los registros solo requieren 2 M-Cycles.
  • Optimización C++: Las operaciones bitwise nativas de C++ se compilan directamente a instrucciones de máquina de un solo ciclo, ofreciendo rendimiento máximo. El switch anidado permite al compilador optimizar mejor que un switch plano de 256 casos.

Lo que Falta Confirmar

  • Comportamiento con Juegos Reales: Aunque los tests pasan, sería valioso probar con ROMs de test permitidas para verificar que las operaciones CB funcionan correctamente en contextos reales de juego.
  • Edge Cases: Verificar comportamiento con valores límite (0x00, 0xFF) y con todos los bits encendidos/apagados en diferentes combinaciones.

Hipótesis y Suposiciones

La implementación se basa estrictamente en Pan Docs. No se han hecho suposiciones adicionales, y todas las decisiones de diseño están respaldadas por documentación técnica oficial.

Próximos Pasos

  • [ ] Implementar instrucciones restantes de la CPU (STOP, DAA, etc.) si es necesario
  • [ ] Validar con ROMs de test permitidas para verificar compatibilidad completa
  • [ ] Optimizar el bucle principal de emulación para máximo rendimiento
  • [ ] Comenzar implementación de APU (Audio Processing Unit) - Fase 2 objetivo principal