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.
Placa Base y Bucle Principal (Game Loop)
Resumen
Implementación de la clase Viboy que actúa como la "placa base" del emulador, integrando
todos los componentes (CPU, MMU, Cartridge) en un sistema unificado. Creación del bucle principal (Game Loop)
que ejecuta instrucciones continuamente, simulando el funcionamiento de la Game Boy real. El sistema ahora
puede ejecutar código real de ROMs, aunque se detendrá cuando encuentre opcodes no implementados. Modo debug
implementado para mostrar trazas detalladas de cada instrucción ejecutada. Sin este bucle, la CPU no puede
"vivir" y procesar código de juegos.
Concepto de Hardware
La Game Boy funciona a una frecuencia de reloj de 4.194304 MHz (4.194.304 ciclos por segundo). Esto significa que el procesador ejecuta aproximadamente 4.2 millones de instrucciones por segundo (aunque cada instrucción consume múltiples ciclos de reloj).
El System Clock (Reloj del Sistema) es el "latido" que sincroniza todos los componentes: CPU, PPU (Pixel Processing Unit), APU (Audio Processing Unit), timers, etc. Sin un reloj, el sistema no puede funcionar de manera coordinada.
Un fotograma (frame) en la Game Boy dura aproximadamente 70.224 ciclos de reloj para mantener una tasa de refresco de 59.7 FPS. Esto significa que cada segundo, el sistema procesa aproximadamente 59.7 frames, cada uno consumiendo ~70.224 ciclos.
El Game Loop (Bucle Principal) es el corazón del emulador. Es un bucle infinito que:
- Ejecuta una instrucción de la CPU
- Actualiza otros componentes (PPU, APU, timers) según los ciclos consumidos
- Repite hasta que se interrumpe o se produce un error
Importante: Sin control de timing, un ordenador moderno ejecutaría millones de instrucciones en un segundo y el juego iría a velocidad de la luz. Por ahora, no implementamos sincronización de tiempo real (sleep), solo ejecutamos instrucciones en un bucle continuo. La sincronización se añadirá más adelante cuando implementemos la PPU y el renderizado.
Fuente: Pan Docs - System Clock, Timing
Implementación
Se creó la clase Viboy en src/viboy.py que actúa como la "placa base" del emulador.
Esta clase integra todos los componentes y proporciona una interfaz unificada para el bucle principal.
Componentes creados/modificados
- src/viboy.py: Nueva clase
Viboycon métodostick()yrun() - main.py: Refactorizado para usar la clase
Viboyen lugar de inicializar componentes manualmente - tests/test_viboy_integration.py: Suite completa de tests de integración (8 tests)
Decisiones de diseño
1. Clase Viboy como "Placa Base": Se decidió crear una clase central que integre todos los componentes
en lugar de tener lógica dispersa en main.py. Esto mejora la modularidad y facilita los tests.
2. Método tick() para ejecución paso a paso: El método tick() ejecuta una sola instrucción
y devuelve los ciclos consumidos. Esto permite control granular sobre la ejecución y facilita el debugging.
3. Método run() para bucle principal: El método run() contiene el bucle infinito principal.
Maneja excepciones (KeyboardInterrupt, NotImplementedError) para salir limpiamente.
4. Modo Debug: Se implementó un modo debug que imprime información detallada de cada instrucción: PC, opcode, estado de registros, ciclos consumidos. Esto es crítico para entender qué está haciendo el emulador.
5. Post-Boot State: Se implementó el método _initialize_post_boot_state() que simula
el estado después de que la Boot ROM se ejecuta. Por ahora, solo inicializa PC=0x0100 y SP=0xFFFE. Más adelante,
cuando implementemos la Boot ROM, estos valores se establecerán automáticamente.
6. Sin sincronización de tiempo (por ahora): No se implementó sincronización de tiempo real (sleep) porque aún no tenemos PPU ni renderizado. El bucle ejecuta instrucciones tan rápido como puede. Esto es aceptable para esta fase, ya que el objetivo es ver el "trace" de instrucciones ejecutándose.
Archivos Afectados
src/viboy.py- Nueva clase Viboy (placa base) con bucle principalmain.py- Refactorizado para usar la clase Viboytests/test_viboy_integration.py- Suite completa de tests de integración (8 tests)
Tests y Verificación
Se creó una suite completa de tests de integración en tests/test_viboy_integration.py:
- test_viboy_initialization_without_rom: Verifica que Viboy se inicializa correctamente sin ROM
- test_viboy_tick_executes_instruction: Verifica que tick() ejecuta una instrucción y avanza el PC
- test_viboy_total_cycles_counter: Verifica que el contador de ciclos totales se incrementa correctamente
- test_viboy_load_cartridge: Verifica que load_cartridge() carga un cartucho correctamente
- test_viboy_initialization_with_rom: Verifica que Viboy se inicializa correctamente con ROM
- test_viboy_executes_nop_sequence: Verifica que Viboy ejecuta una secuencia de NOPs correctamente
- test_viboy_post_boot_state: Verifica que el estado post-arranque se inicializa correctamente
Validación manual: Se ejecutó el emulador con python3 main.py tetris_dx.gbc --debug y se observó
que el sistema inicia correctamente, carga la ROM, y comienza a ejecutar instrucciones. El sistema se detiene cuando
encuentra un opcode no implementado (comportamiento esperado).
Resultados del test con ROM real (Tetris DX):
- Carga de ROM: ✅ El archivo se cargó correctamente (524,288 bytes, 512 KB)
- Parsing del Header: ✅ Título "TETRIS DX", Tipo 0x03 (MBC1), ROM 512 KB, RAM 8 KB
- Inicialización del sistema: ✅ Viboy se inicializó correctamente con la ROM
- Post-Boot State: ✅ PC y SP se inicializaron correctamente (PC=0x0100, SP=0xFFFE)
- Ejecución de instrucciones: ✅ El sistema comenzó a ejecutar instrucciones desde 0x0100
- Primera instrucción (0x0100): ✅ NOP (0x00) ejecutada correctamente, PC avanzó a 0x0101
- Segunda instrucción (0x0101): ✅ JP nn (0xC3) ejecutada correctamente, saltó a 0x0150
- Modo debug: ✅ Las trazas muestran correctamente PC, opcode, registros y ciclos consumidos
- Detención por opcode no implementado: ✅ El sistema se detiene correctamente en 0x0150 con opcode 0xF3 (DI - Disable Interrupts) no implementado
- Total de ciclos ejecutados: 5 ciclos (1 ciclo para NOP + 4 ciclos para JP nn)
Observaciones importantes:
- El código de arranque del juego comienza con un NOP seguido de un salto incondicional (JP) a 0x0150, que es típico del código de inicialización de juegos de Game Boy.
- La siguiente instrucción en 0x0150 es 0xF3 (DI - Disable Interrupts), que es una instrucción crítica para la inicialización del sistema. Esta instrucción debe implementarse próximamente.
- El modo debug funciona perfectamente, mostrando información detallada de cada instrucción ejecutada, lo cual es esencial para el debugging y desarrollo del emulador.
Logs: El modo debug muestra trazas detalladas de cada instrucción ejecutada, permitiendo verificar que el PC avanza correctamente y que los registros se actualizan según las instrucciones.
Fuentes Consultadas
- Pan Docs: System Clock, Timing
- Pan Docs: Boot ROM, Post-Boot State
Nota: Implementación basada en conocimiento general de arquitectura LR35902 y documentación técnica de Pan Docs.
Integridad Educativa
Lo que Entiendo Ahora
- System Clock: La Game Boy funciona a 4.194304 MHz. Sin un reloj, el sistema no puede funcionar de manera coordinada. El reloj sincroniza todos los componentes.
- Game Loop: El bucle principal es el corazón del emulador. Ejecuta instrucciones continuamente hasta que se interrumpe o se produce un error. Sin este bucle, la CPU no puede "vivir".
- Post-Boot State: Después de que la Boot ROM se ejecuta, el sistema queda en un estado específico: PC=0x0100 (inicio del código del cartucho), SP=0xFFFE (top de la pila), registros con valores específicos.
- Timing: Sin control de timing, un ordenador moderno ejecutaría millones de instrucciones por segundo. Por ahora, no implementamos sincronización de tiempo real, solo ejecutamos instrucciones en un bucle continuo.
Lo que Falta Confirmar
- Sincronización de tiempo: Cómo implementar sincronización de tiempo real (sleep) para mantener 59.7 FPS. Esto se implementará más adelante cuando tengamos PPU y renderizado.
- Boot ROM: Los valores exactos de los registros después de que la Boot ROM se ejecuta. Por ahora, solo inicializamos PC y SP con valores básicos.
- Interrupciones: Cómo manejar interrupciones (VBlank, Timer, etc.) en el bucle principal. Esto se implementará más adelante. Nota: El test con Tetris DX muestra que la primera instrucción después del salto es DI (0xF3 - Disable Interrupts), lo cual confirma que las interrupciones son críticas para la inicialización del sistema.
- ✅ Validación con ROMs reales: COMPLETADO - Se validó exitosamente con tetris_dx.gbc (ROM real de Game Boy Color). El sistema inicia correctamente, carga la ROM, y comienza a ejecutar instrucciones. Se ejecutaron 2 instrucciones (NOP en 0x0100 y JP nn en 0x0101 que saltó a 0x0150) antes de detenerse en 0x0150 con opcode 0xF3 (DI - Disable Interrupts) no implementado. El comportamiento es el esperado: el sistema ejecuta código real del juego hasta encontrar un opcode no implementado. La siguiente instrucción a implementar es DI (0xF3), que es crítica para la inicialización del sistema.
Hipótesis y Suposiciones
Suposición 1: Por ahora, asumimos que no necesitamos sincronización de tiempo real porque aún no tenemos PPU ni renderizado. El bucle ejecuta instrucciones tan rápido como puede, lo cual es aceptable para esta fase.
Suposición 2: Asumimos que el estado post-arranque solo requiere inicializar PC=0x0100 y SP=0xFFFE. Más adelante, cuando implementemos la Boot ROM, estos valores se establecerán automáticamente con mayor precisión.
Próximos Pasos
- [ ] PRIORITARIO: Implementar opcode 0xF3 (DI - Disable Interrupts) - La siguiente instrucción que necesita Tetris DX
- [ ] Implementar más opcodes de la CPU (XOR A, LD HL, imm, etc.) para que el emulador pueda ejecutar más código
- [ ] Implementar MBC1 (Memory Bank Controller) para ROMs mayores de 32KB (como Tetris DX de 512KB)
- [ ] Implementar interrupciones (VBlank, Timer, etc.) y su manejo en el bucle principal
- [ ] Implementar sincronización de tiempo real (sleep) para mantener 59.7 FPS
- [ ] Implementar PPU (Pixel Processing Unit) para renderizar gráficos