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 del Esqueleto de CPU a C++ (CoreCPU)
Resumen
Se ha completado la migración del esqueleto básico de la CPU a C++, estableciendo el patrón de inyección de dependencias en código nativo. La CPU ahora ejecuta el ciclo Fetch-Decode-Execute en C++ puro, accediendo a MMU y Registros mediante punteros directos. Se implementaron dos opcodes de prueba (NOP y LD A, d8) para validar el patrón arquitectónico antes de migrar el resto de instrucciones.
Concepto de Hardware
La CPU LR35902 de la Game Boy ejecuta instrucciones en un ciclo continuo llamado Fetch-Decode-Execute:
- Fetch: Lee el byte apuntado por PC (Program Counter) de memoria
- Increment: Avanza PC al siguiente byte
- Decode/Execute: Identifica el opcode y ejecuta la operación correspondiente
Cada instrucción consume un número específico de M-Cycles (Machine Cycles). Un M-Cycle corresponde típicamente a una operación de memoria. Por ejemplo:
NOP(0x00): 1 M-Cycle (no hace nada, solo consume tiempo)LD A, d8(0x3E): 2 M-Cycles (lee opcode + lee valor inmediato)
La CPU no posee la MMU ni los Registros; solo mantiene referencias (punteros) a ellos. Esto permite que múltiples componentes compartan el mismo estado, siguiendo el patrón de inyección de dependencias.
Fuente: Pan Docs - CPU Instruction Set
Implementación
Se creó la clase C++ CPU que implementa el ciclo de instrucción básico.
La arquitectura utiliza punteros a MMU y CoreRegisters (no los posee), siguiendo
el principio de inyección de dependencias. Esto permite que Python sea el dueño
de la memoria, mientras que C++ opera a máxima velocidad con punteros directos.
Componentes creados/modificados
- CPU.hpp / CPU.cpp: Clase C++ que implementa el ciclo Fetch-Decode-Execute
- Miembros: punteros a MMU y CoreRegisters, contador de ciclos
- Método
step(): Ejecuta un ciclo de instrucción - Helper
fetch_byte(): Lee byte de memoria e incrementa PC - Switch optimizado por compilador para decodificación de opcodes
- cpu.pxd / cpu.pyx: Wrapper Cython que expone CPU a Python
- Clase
PyCPU: Wrapper Python para CPU - Constructor recibe
PyMMUyPyRegisters - Extrae punteros C++ subyacentes para inyección de dependencias
- Clase
- setup.py: Añadido CPU.cpp a las fuentes de compilación
- native_core.pyx: Incluido cpu.pyx en el módulo principal
- test_core_cpu.py: Suite completa de tests de integración (6 tests)
Decisiones de diseño
1. Inyección de Dependencias en C++: La CPU recibe punteros a MMU y Registros en lugar de poseerlos. Esto permite que Python gestione el ciclo de vida de los objetos, mientras que C++ opera con punteros directos (máximo rendimiento).
2. Switch Statement para Decodificación: Se usa un switch
en lugar de una tabla de funciones. El compilador puede optimizar esto en una tabla
de saltos (jump table), proporcionando O(1) decodificación de opcodes.
3. Opcodes Mínimos para Validación: Solo se implementaron 2 opcodes (NOP y LD A, d8) para validar el patrón arquitectónico. El resto de opcodes se migrarán en pasos posteriores.
4. Manejo de Errores: Los opcodes desconocidos retornan 0 (error) en lugar de lanzar excepciones. Esto evita overhead en el bucle crítico de emulación.
5. Acceso a Miembros Privados en Cython: Como mmu.pyx y
registers.pyx están incluidos en native_core.pyx, podemos
acceder directamente a los miembros privados _mmu y _regs desde
cpu.pyx (mismo módulo compilado).
Archivos Afectados
src/core/cpp/CPU.hpp- Declaración de la clase CPU en C++src/core/cpp/CPU.cpp- Implementación del ciclo de instrucciónsrc/core/cython/cpu.pxd- Definición Cython de la clase C++src/core/cython/cpu.pyx- Wrapper Python para CPUsrc/core/cython/native_core.pyx- Incluido cpu.pyxsrc/core/cython/mmu.pyx- Comentario sobre acceso a miembros privadossrc/core/cython/registers.pyx- Comentario sobre acceso a miembros privadossetup.py- Añadido CPU.cpp a fuentestests/test_core_cpu.py- Suite de tests de integración (6 tests)
Tests y Verificación
Se creó una suite completa de tests de integración que valida:
- Inicialización: La CPU se crea correctamente con MMU y Registros
- NOP (0x00): Consume 1 M-Cycle, incrementa PC correctamente
- LD A, d8 (0x3E): Lee valor inmediato, lo guarda en A, consume 2 M-Cycles
- Múltiples ejecuciones: Secuencia de instrucciones funciona correctamente
- Opcodes desconocidos: Retornan 0 (error) sin crashear
- Inyección de dependencias: Múltiples CPUs pueden compartir MMU y Registros
Resultados de ejecución:
============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2
collected 6 items
tests/test_core_cpu.py::TestCoreCPU::test_cpu_initialization PASSED [ 16%]
tests/test_core_cpu.py::TestCoreCPU::test_nop_instruction PASSED [ 33%]
tests/test_core_cpu.py::TestCoreCPU::test_ld_a_d8_instruction PASSED [ 50%]
tests/test_core_cpu.py::TestCoreCPU::test_ld_a_d8_multiple_executions PASSED [ 66%]
tests/test_core_cpu.py::TestCoreCPU::test_unknown_opcode_returns_zero PASSED [ 83%]
tests/test_core_cpu.py::TestCoreCPU::test_cpu_with_shared_mmu_and_registers PASSED [100%]
============================== 6 passed in 0.06s =============================
✅ Todos los tests pasan (6/6)
Compilación: Exitosa con Visual Studio 2022 (MSVC 14.44.35207). Warnings menores de Cython esperados (no afectan funcionalidad).
Fuentes Consultadas
- Pan Docs: CPU Instruction Set - Especificación de opcodes y ciclos de máquina
- Arquitectura LR35902: Conocimiento general del ciclo Fetch-Decode-Execute
- Cython Documentation: Acceso a miembros privados en módulos incluidos
Integridad Educativa
Lo que Entiendo Ahora
- Inyección de Dependencias en C++: Python crea los objetos (dueño de memoria), C++ recibe punteros para operar a máxima velocidad. Esto evita el overhead de pasar objetos Python en cada ciclo de instrucción.
- Ciclo Fetch-Decode-Execute: El ciclo básico de la CPU es Fetch (leer opcode), Decode (identificar instrucción), Execute (ejecutar operación). Cada paso consume ciclos de máquina específicos.
- Switch Statement Optimizado: El compilador C++ puede convertir un switch con casos consecutivos en una tabla de saltos (jump table), proporcionando O(1) decodificación.
- Acceso a Miembros Privados en Cython: Cuando módulos Cython están incluidos en el mismo archivo principal (native_core.pyx), pueden acceder a miembros privados entre sí porque se compilan en el mismo módulo.
Lo que Falta Confirmar
- Rendimiento Real: Aún no se ha medido el impacto de rendimiento comparado con la implementación Python. Se necesitará profiling para validar la mejora.
- Migración Completa de Opcodes: Solo 2 opcodes están implementados. El resto (256 opcodes + 256 opcodes CB) necesitan migración gradual.
- Manejo de Interrupciones: El esqueleto actual no maneja interrupciones. Se necesitará añadir lógica de IME (Interrupt Master Enable) y manejo de interrupciones.
Hipótesis y Suposiciones
Suposición sobre rendimiento: Asumimos que el switch statement será optimizado por el compilador en una jump table. Esto debería validarse con profiling y análisis de código generado.
Suposición sobre acceso a miembros privados: Asumimos que el acceso directo a
_mmu y _regs desde cpu.pyx es seguro porque están en el
mismo módulo compilado. Esto se validó con la compilación exitosa y tests pasando.
Próximos Pasos
- [ ] Migrar más opcodes básicos (LD, ADD, SUB, etc.)
- [ ] Implementar manejo de interrupciones (IME, HALT)
- [ ] Añadir profiling para medir rendimiento real vs Python
- [ ] Migrar opcodes CB (prefijo 0xCB)
- [ ] Integrar CPU nativa con el bucle principal de emulación