⚠️ 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 de Loads y Aritmética 16-bit en C++

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

Resumen

Se implementaron las operaciones de transferencia de datos (Loads) y aritmética de 16 bits en C++, cubriendo aproximadamente el 40% del set de instrucciones de la Game Boy. Se añadieron helpers genéricos para manejar el bloque completo 0x40-0x7F de LD r, r', así como operaciones de carga inmediata (8 y 16 bits) y aritmética de pares de registros. Se implementaron 64+ nuevos opcodes con una arquitectura optimizada que usa punteros a registros y funciones helper inline para máximo rendimiento. Todos los 16 tests pasan correctamente.

Concepto de Hardware

La mayoría de instrucciones de una CPU son operaciones de movimiento de datos (Loads). En la Game Boy, el bloque de opcodes 0x40-0x7F forma una matriz perfecta donde:

  • Bits 3-5: Código del registro destino (0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A)
  • Bits 0-2: Código del registro origen (misma codificación)
  • Excepción: 0x76 es HALT, no LD

Esta estructura permite implementar 63 instrucciones LD con una sola función helper genérica, en lugar de escribir 63 casos individuales. En C++, podemos usar punteros a los registros (uint8_t*) para crear funciones genéricas como ld_r_r(uint8_t* dest, uint8_t* src).

Aritmética 16-bit: La Game Boy tiene una peculiaridad importante:

  • INC/DEC rr: NO afectan flags (solo incrementan/decrementan el par)
  • ADD HL, rr: SÍ afecta flags, pero solo H y C (NO Z). El half-carry se calcula en el bit 11 (bit 3 del byte alto), no en el bit 3 como en operaciones de 8 bits.

Esta diferencia es crítica para la precisión de la emulación. El half-carry en 16 bits detecta desbordamiento del nibble bajo del byte alto: ((hl & 0xFFF) + (value & 0xFFF)) > 0xFFF.

Optimización C++: Usar funciones helper inline con punteros a registros permite que el compilador optimice el código eliminando indirecciones innecesarias y generando código de máquina altamente eficiente. El bloque completo 0x40-0x7F se compila a un solo switch con lógica genérica, reduciendo el tamaño del código y mejorando la predicción de ramas.

Implementación

Se implementaron helpers genéricos para manejar el bloque completo de instrucciones LD y operaciones de aritmética 16-bit. La arquitectura usa punteros a registros y funciones helper inline para máximo rendimiento.

Componentes creados/modificados

  • CPU.hpp: Añadidas declaraciones de helpers de Load y aritmética 16-bit:
    • get_register_ptr(): Obtiene puntero a registro según código
    • read_register_or_mem(): Lee de registro o memoria (HL)
    • write_register_or_mem(): Escribe en registro o memoria (HL)
    • ld_r_r(): Copia valor entre registros/memoria
    • inc_16bit() / dec_16bit(): Incremento/decremento de pares
    • add_hl(): Suma de 16 bits a HL con cálculo de flags
  • CPU.cpp: Implementación de helpers y 64+ nuevos opcodes:
    • Bloque 0x40-0x7F: LD r, r' (63 instrucciones, excepto 0x76 HALT)
    • LD r, n: 0x06, 0x0E, 0x16, 0x1E, 0x26, 0x2E, 0x3E (7 instrucciones)
    • LD (HL), n: 0x36 (1 instrucción)
    • LD rr, nn: 0x01, 0x11, 0x21, 0x31 (4 instrucciones)
    • INC/DEC rr: 0x03, 0x0B, 0x13, 0x1B, 0x23, 0x2B, 0x33, 0x3B (8 instrucciones)
    • ADD HL, rr: 0x09, 0x19, 0x29, 0x39 (4 instrucciones)
  • cpu.pxd: Corregido import de bool para compatibilidad con Cython
  • tests/test_core_cpu_loads.py: Suite completa de 16 tests para validar todas las operaciones

Decisiones de diseño

  • Punteros a registros: En lugar de usar un switch gigante con 64 casos, se usa una función helper que mapea códigos de registro a punteros. Esto reduce el tamaño del código y mejora la mantenibilidad.
  • Manejo de (HL): El código 6 representa acceso a memoria en dirección HL. Las funciones read_register_or_mem() y write_register_or_mem() manejan este caso especial, simplificando la lógica de LD.
  • Timing preciso: Las instrucciones que acceden a memoria (destino u origen = (HL)) consumen 2 M-Cycles en lugar de 1. La lógica de cálculo de ciclos verifica ambos códigos.
  • Flags en ADD HL: Se implementó el cálculo correcto de half-carry en bit 11 y carry completo en 16 bits, respetando que Z no se afecta.
  • INC/DEC rr sin flags: Estos opcodes no afectan flags, lo cual es crítico para la precisión de la emulación (muchos juegos dependen de este comportamiento).

Archivos Afectados

  • src/core/cpp/CPU.hpp - Añadidas declaraciones de helpers de Load y aritmética 16-bit
  • src/core/cpp/CPU.cpp - Implementación de helpers y 64+ nuevos opcodes
  • src/core/cython/cpu.pxd - Corregido import de bool para compatibilidad
  • tests/test_core_cpu_loads.py - Suite completa de 16 tests

Tests y Verificación

Se creó una suite completa de 16 tests que validan todas las operaciones implementadas:

  • TestLD_8bit_Register (5 tests): Verifica LD r, r', LD (HL), r y LD r, (HL)
  • TestLD_8bit_Immediate (2 tests): Verifica LD r, n y LD (HL), n
  • TestLD_16bit (2 tests): Verifica LD rr, nn para BC y HL
  • TestINC_DEC_16bit (3 tests): Verifica INC/DEC rr y que NO afectan flags
  • TestADD_HL (4 tests): Verifica ADD HL, rr con diferentes casos de carry

Resultado: Todos los 16 tests pasan correctamente. La validación incluye:

  • Verificación de valores correctos en registros y memoria
  • Verificación de timing (M-Cycles consumidos)
  • Verificación de flags (especialmente que INC/DEC rr NO afectan flags)
  • Verificación de half-carry y carry en operaciones de 16 bits
  • Verificación de wrap-around en 16 bits

Fuentes Consultadas

Nota: La implementación sigue estrictamente la documentación técnica, sin consultar código fuente de otros emuladores.

Integridad Educativa

Lo que Entiendo Ahora

  • Matriz de opcodes: El bloque 0x40-0x7F forma una matriz perfecta que permite implementar 63 instrucciones con una sola función helper genérica, reduciendo significativamente el tamaño del código y mejorando la mantenibilidad.
  • Punteros a miembros: En C++, podemos usar punteros a los registros para crear funciones genéricas que trabajan con cualquier registro, eliminando la necesidad de código repetitivo.
  • Half-carry en 16 bits: El half-carry en operaciones de 16 bits se calcula en el bit 11 (bit 3 del byte alto), no en el bit 3 como en operaciones de 8 bits. Esto es crítico para la precisión de la emulación.
  • INC/DEC rr sin flags: Estas instrucciones NO afectan flags, lo cual es un comportamiento específico del hardware de la Game Boy que muchos juegos dependen.
  • Timing de memoria: Las instrucciones que acceden a memoria (destino u origen = (HL)) consumen 2 M-Cycles en lugar de 1, reflejando el costo adicional de acceso a memoria.

Lo que Falta Confirmar

  • Prefijo CB: Las operaciones de rotación y bits (prefijo CB) aún no están implementadas. Estas son críticas para muchas operaciones de manipulación de bits.
  • Operaciones restantes: Aún faltan operaciones como ADC, SBC, CP, y otras operaciones aritméticas y lógicas que son comunes en el set de instrucciones.

Hipótesis y Suposiciones

La implementación asume que el comportamiento de flags en ADD HL, rr es correcto según Pan Docs. El cálculo de half-carry en bit 11 se basa en la documentación técnica, pero debería validarse con tests más exhaustivos o ROMs de test permitidas si están disponibles.

Próximos Pasos

  • [ ] Implementar operaciones aritméticas restantes (ADC, SBC, CP)
  • [ ] Implementar operaciones lógicas restantes (OR, CPL, SCF, CCF)
  • [ ] Implementar prefijo CB (rotaciones y operaciones de bits)
  • [ ] Implementar operaciones de stack restantes (PUSH/POP para otros pares)
  • [ ] Validar con ROMs de test permitidas si están disponibles