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

Migración de Registros a C++ (CoreRegisters)

Fecha: 2025-12-19 Step ID: 0103 Estado: Completo

Resumen

Se ha completado la migración de los registros de la CPU de Python a C++, creando la clase CoreRegisters que proporciona acceso ultrarrápido a los registros de 8 y 16 bits. Esta implementación es crítica para el rendimiento, ya que los registros se acceden miles de veces por segundo durante la emulación. Con acceso directo a memoria en lugar de llamadas a métodos Python, el bucle principal de la CPU será significativamente más rápido.

Concepto de Hardware

La CPU LR35902 de la Game Boy utiliza una arquitectura de registros híbrida basada en el Z80/8080. Los registros están organizados en:

  • Registros de 8 bits: A, B, C, D, E, H, L, F
  • Registros de 16 bits: PC (Program Counter), SP (Stack Pointer)
  • Pares virtuales de 16 bits: AF, BC, DE, HL (combinaciones de registros de 8 bits)

El registro F (Flags) tiene una peculiaridad hardware importante: los 4 bits bajos siempre son 0 en el hardware real. Solo los bits 7, 6, 5, 4 son válidos y representan las flags Z (Zero), N (Subtract), H (Half Carry) y C (Carry) respectivamente.

Para emular esto eficientemente, implementamos getters/setters para los pares de 16 bits que manipulan los bits correctamente usando operaciones bitwise. Por ejemplo, el par AF tiene A en el byte alto y F en el byte bajo, pero al leer/escribir F, siempre aplicamos la máscara 0xF0 para simular el comportamiento del hardware.

Fuente: Pan Docs - Game Boy CPU Manual, sección de registros.

Implementación

Se implementó la clase CoreRegisters en C++ con los siguientes principios de diseño para máximo rendimiento:

  • Estructura de datos simple: Los registros son miembros públicos para acceso directo sin overhead de métodos. Esto permite que el compilador optimice el acceso a memoria.
  • Métodos inline: Los métodos para pares virtuales (get_af, set_af, etc.) y helpers de flags están marcados como inline para que el compilador los expanda en el lugar de llamada, eliminando el overhead de llamadas a función.
  • Cache-friendly: Todos los registros están contiguos en memoria, aprovechando la localidad espacial de la caché del procesador.

Componentes creados

  • src/core/cpp/Registers.hpp - Declaración de la clase CoreRegisters con todos los registros, métodos inline para pares virtuales y helpers de flags.
  • src/core/cpp/Registers.cpp - Implementación del constructor que inicializa todos los registros a cero.
  • src/core/cython/registers.pxd - Definición Cython de la clase C++ para el sistema de enlace.
  • src/core/cython/registers.pyx - Wrapper Cython PyRegisters que expone propiedades Python para acceso intuitivo a los registros.
  • tests/test_core_registers.py - Suite completa de tests (14 tests) que validan todos los aspectos de los registros.

Decisiones de diseño

1. Miembros públicos vs privados: Decidimos usar miembros públicos para los registros individuales (a, b, c, etc.) porque el acceso directo es más rápido que los getters/setters, y estos datos no necesitan validación adicional. Los métodos inline solo se usan para pares virtuales y flags, donde hay lógica adicional.

2. Wrap-around en Cython: El wrapper Cython acepta valores int de Python y aplica el wrap-around antes de convertir a tipos C (uint8_t, uint16_t). Esto permite que los tests escriban valores como 256 o 0x10000 y el sistema los maneje correctamente.

3. Propiedades Python: En lugar de métodos get/set explícitos, usamos propiedades de Python (@property) para que el código Python pueda acceder a los registros como atributos (ej: reg.a = 0x12 en lugar de reg.set_a(0x12)).

Archivos Afectados

  • src/core/cpp/Registers.hpp - Clase CoreRegisters (nuevo)
  • src/core/cpp/Registers.cpp - Implementación del constructor (nuevo)
  • src/core/cython/registers.pxd - Definición Cython (nuevo)
  • src/core/cython/registers.pyx - Wrapper Cython PyRegisters (nuevo)
  • src/core/cython/native_core.pyx - Actualizado para incluir registers.pyx
  • setup.py - Añadido Registers.cpp a las fuentes de compilación
  • tests/test_core_registers.py - Suite completa de tests (nuevo)

Tests y Verificación

Se creó una suite completa de tests que valida todos los aspectos de los registros:

  • Tests unitarios: 14 tests que cubren:
    • Registros de 8 bits y wrap-around
    • Pares virtuales de 16 bits (AF, BC, DE, HL)
    • Máscara de bits bajos en registro F
    • Flags individuales (Z, N, H, C)
    • Program Counter y Stack Pointer
    • Inicialización por defecto
  • Compilación: ✅ Exitosa sin errores (warnings menores de Cython esperados)
  • Validación de módulo compilado C++: ✅ Todos los tests pasan (14/14)
  • Tiempo de ejecución: ~0.05s (extremadamente rápido)

Comando de ejecución: python -m pytest tests/test_core_registers.py -v

Fuentes Consultadas

  • Pan Docs: Game Boy CPU Manual - Sección de registros y flags
  • Implementación basada en especificaciones del hardware LR35902

Nota: La implementación sigue el patrón establecido en la versión Python, pero optimizada para C++ con acceso directo a memoria y métodos inline.

Integridad Educativa

Lo que Entiendo Ahora

  • Endianness y pares virtuales: En una arquitectura Little-Endian como la Game Boy, el par AF tiene A en el byte alto y F en el byte bajo. Esto significa que al hacer (a << 8) | f obtenemos el valor correcto del par de 16 bits.
  • Optimización de acceso a memoria: Tener todos los registros contiguos en memoria (en un struct) permite que el procesador los cargue en la caché juntos, reduciendo los misses de caché y mejorando el rendimiento.
  • Inline functions: Los métodos marcados como inline son expandidos por el compilador en el lugar de llamada, eliminando el overhead de llamadas a función. Esto es crítico para funciones pequeñas que se llaman miles de veces por segundo.
  • Gestión de tipos en Cython: Para manejar wrap-around correctamente, necesitamos aceptar valores int de Python, aplicar la máscara, y luego hacer cast explícito a tipos C (uint8_t, uint16_t).

Lo que Falta Confirmar

  • Rendimiento real en bucle de CPU: Aunque teóricamente el acceso directo a memoria debería ser más rápido, necesitaremos medir el rendimiento real cuando integremos esto en el bucle principal de la CPU.
  • Comportamiento de flags en operaciones complejas: Cuando implementemos las instrucciones de la CPU, verificaremos que los flags se establezcan correctamente según las especificaciones del hardware.

Hipótesis y Suposiciones

Asumimos que el compilador C++ optimizará adecuadamente los métodos inline y el acceso a miembros públicos. Si hay problemas de rendimiento, podríamos considerar usar union para los pares virtuales, pero esto complicaría el código y probablemente no sea necesario.

Próximos Pasos

  • [ ] Migrar CPU a C++ usando CoreRegisters y CoreMMU
  • [ ] Implementar ciclo de instrucción (Fetch-Decode-Execute) en C++
  • [ ] Integrar CoreRegisters con el bucle principal de emulación
  • [ ] Medir rendimiento comparado con la versión Python
  • [ ] Implementar instrucciones críticas de la CPU en C++