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++
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
- Implementación de
- CPU.cpp (step()): Añadido case
0xCBque llama ahandle_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 dehandle_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
- Pan Docs: CB Prefix Instructions
- Pan Docs: BIT b, r Instruction
- Pan Docs: RES b, r Instruction
- Pan Docs: SET b, r Instruction
- Pan Docs: Rotations and Shifts
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