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.
Control de Interrupciones, XOR y Cargas de 16 bits
Resumen
Implementación de instrucciones críticas de control de sistema y operaciones lógicas necesarias para que Tetris DX continúe ejecutándose. Se añadió el atributo IME (Interrupt Master Enable) a la CPU para controlar las interrupciones. Se implementaron los opcodes DI (0xF3) y EI (0xFB) para desactivar/activar interrupciones, XOR A (0xAF) como optimización para poner el registro A a cero, y las instrucciones de carga inmediata de 16 bits LD SP, d16 (0x31) y LD HL, d16 (0x21). Estas instrucciones son esenciales para la inicialización del sistema. Suite completa de tests TDD (13 tests) validando todas las funcionalidades implementadas.
Concepto de Hardware
IME (Interrupt Master Enable): No es un registro accesible directamente, sino un "interruptor" interno de la CPU que controla si las interrupciones están habilitadas o no. Cuando IME está activado (True), la CPU puede procesar interrupciones (VBlank, Timer, Serial, Joypad, etc.). Cuando está desactivado (False), las interrupciones se ignoran. Los juegos suelen desactivar las interrupciones al inicio con DI para configurar el hardware sin interrupciones, y luego las reactivan con EI cuando están listos.
DI (Disable Interrupts - 0xF3): Desactiva las interrupciones poniendo IME a False. Esta instrucción es crítica para la inicialización del sistema, ya que permite configurar el hardware sin que las interrupciones interfieran.
EI (Enable Interrupts - 0xFB): Activa las interrupciones poniendo IME a True. Nota importante: En hardware real, EI tiene un retraso de 1 instrucción. Esto significa que las interrupciones no se activan inmediatamente, sino después de ejecutar la siguiente instrucción. Por ahora, implementamos la activación inmediata para simplificar. Más adelante, cuando implementemos el manejo completo de interrupciones, añadiremos este retraso.
XOR A (0xAF) - Optimización histórica: Realiza la operación XOR entre el registro A y él mismo: A = A ^ A. Como cualquier valor XOR consigo mismo siempre es 0, esta instrucción pone el registro A a cero de forma eficiente. Los desarrolladores usaban `XOR A` en lugar de `LD A, 0` porque:
- Ocupa menos bytes: 1 byte vs 2 bytes (opcode + operando)
- Consume menos ciclos: 1 ciclo vs 2 ciclos
- Es más rápido: En hardware antiguo, las operaciones lógicas eran más rápidas que las cargas
Flags en operaciones lógicas (XOR): XOR siempre pone los flags N (Subtract), H (Half-Carry) y C (Carry) a 0. El flag Z (Zero) depende del resultado: si el resultado es 0, Z se activa; si no, se desactiva. En el caso de XOR A, el resultado siempre es 0, por lo que Z siempre se activa.
LD SP, d16 (0x31) y LD HL, d16 (0x21): Estas instrucciones cargan un valor inmediato de 16 bits en un registro de 16 bits. Lee los siguientes 2 bytes de memoria en formato Little-Endian y los carga en el registro especificado. Estas instrucciones son críticas para la inicialización del sistema, ya que los juegos suelen configurar SP (Stack Pointer) y HL (puntero de memoria) al inicio del programa.
Fuente: Pan Docs - CPU Instruction Set (DI, EI, XOR, LD)
Implementación
Se añadió el atributo ime (bool) a la clase CPU para controlar el estado de las interrupciones.
Se implementaron 5 nuevos opcodes en src/cpu/core.py:
Componentes creados/modificados
- src/cpu/core.py: Añadido atributo
imey 5 nuevos opcodes (DI, EI, XOR A, LD SP d16, LD HL d16) - tests/test_cpu_control.py: Suite completa de tests TDD (13 tests)
Decisiones de diseño
1. IME como atributo booleano: Se decidió usar un simple booleano (self.ime: bool)
en lugar de un registro completo, ya que IME no es accesible directamente y solo tiene dos estados (activado/desactivado).
2. Inicialización de IME en False: Por defecto, IME se inicializa en False por seguridad. Aunque después de la boot ROM real, IME suele estar activado, los juegos suelen desactivarlo explícitamente al inicio con DI, así que inicializar en False es más seguro.
3. EI sin retraso (por ahora): En hardware real, EI tiene un retraso de 1 instrucción. Por ahora, implementamos la activación inmediata para simplificar. Más adelante, cuando implementemos el manejo completo de interrupciones, añadiremos este retraso.
4. XOR A como optimización documentada: Se documentó explícitamente por qué XOR A es una optimización histórica (menos bytes, menos ciclos, más rápido). Esto ayuda a entender el código de los juegos antiguos que usan esta técnica.
5. Flags en XOR: Se implementó correctamente la actualización de flags en XOR A: Z siempre se activa (resultado siempre es 0), N, H y C siempre se desactivan (XOR no es resta, no tiene carry).
Archivos Afectados
src/cpu/core.py- Añadido atributo IME y 5 nuevos opcodestests/test_cpu_control.py- Suite completa de tests TDD (13 tests)
Tests y Verificación
Se creó una suite completa de tests de integración en tests/test_cpu_control.py:
- test_di_disables_interrupts: Verifica que DI desactiva IME
- test_ei_enables_interrupts: Verifica que EI activa IME
- test_di_ei_sequence: Verifica secuencia DI seguida de EI
- test_xor_a_zeros_accumulator: Verifica que XOR A pone A a cero
- test_xor_a_sets_zero_flag: Verifica que XOR A siempre activa Z
- test_xor_a_clears_other_flags: Verifica que XOR A desactiva N, H y C
- test_xor_a_with_different_values: Verifica que XOR A siempre da 0 con cualquier valor
- test_ld_sp_d16_loads_immediate_value: Verifica que LD SP, d16 carga valor correctamente
- test_ld_sp_d16_with_different_values: Verifica LD SP, d16 con diferentes valores
- test_ld_hl_d16_loads_immediate_value: Verifica que LD HL, d16 carga valor correctamente
- test_ld_hl_d16_with_different_values: Verifica LD HL, d16 con diferentes valores
- test_ld_sp_d16_advances_pc: Verifica que LD SP, d16 avanza PC correctamente
- test_ld_hl_d16_advances_pc: Verifica que LD HL, d16 avanza PC correctamente
- 13 tests en total, todos pasando ✅
Validación manual: Se ejecutó el emulador con python3 main.py tetris_dx.gbc --debug
y se observó que el sistema ahora puede ejecutar más instrucciones antes de detenerse.
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)
- Primera instrucción (0x0100): ✅ NOP (0x00) ejecutada correctamente, PC avanzó a 0x0101 (1 ciclo)
- Segunda instrucción (0x0101): ✅ JP nn (0xC3) ejecutada correctamente, saltó a 0x0150 (4 ciclos)
- Tercera instrucción (0x0150): ✅ DI (0xF3) ejecutada correctamente, IME desactivado, PC avanzó a 0x0151 (1 ciclo)
- Modo debug: ✅ Las trazas muestran correctamente PC, opcode, registros y ciclos consumidos
- Detención por opcode no implementado: ✅ El sistema se detiene correctamente en 0x0151 con opcode 0xE0 (LDH (n), A - Load A into I/O) no implementado
- Total de ciclos ejecutados: 6 ciclos (1 + 4 + 1)
- Progreso: ✅ El sistema ahora ejecuta 3 instrucciones (antes solo 2) antes de detenerse
Observaciones importantes:
- La instrucción DI (0xF3) se ejecutó correctamente, confirmando que el control de interrupciones funciona.
- El siguiente opcode no implementado es 0xE0 (LDH (n), A), que es una instrucción de escritura en memoria I/O. Esta instrucción escribe el valor del registro A en la dirección (0xFF00 + n), donde n es el siguiente byte. Es una instrucción crítica para la comunicación con los puertos I/O de la Game Boy.
- El emulador está progresando correctamente: ahora ejecuta 3 instrucciones antes de detenerse (antes solo 2), lo que confirma que las nuevas implementaciones funcionan correctamente.
Fuentes Consultadas
- Pan Docs: CPU Instruction Set (DI, EI)
- Pan Docs: CPU Instruction Set (XOR)
- Pan Docs: CPU Instruction Set (LD)
Nota: Implementación basada en conocimiento general de arquitectura LR35902 y documentación técnica de Pan Docs.
Integridad Educativa
Lo que Entiendo Ahora
- IME (Interrupt Master Enable): No es un registro accesible, sino un "interruptor" interno de la CPU que controla si las interrupciones están habilitadas. DI lo apaga, EI lo enciende.
- Optimización XOR A: Los desarrolladores usaban XOR A en lugar de LD A, 0 porque ocupa menos bytes (1 vs 2), consume menos ciclos (1 vs 2), y es más rápido en hardware antiguo.
- Flags en operaciones lógicas: XOR siempre pone N, H y C a 0. El flag Z depende del resultado. En XOR A, el resultado siempre es 0, por lo que Z siempre se activa.
- Carga inmediata de 16 bits: LD SP, d16 y LD HL, d16 leen 2 bytes en formato Little-Endian y los cargan en el registro especificado. Son críticas para la inicialización del sistema.
Lo que Falta Confirmar
- Retraso de EI: En hardware real, EI tiene un retraso de 1 instrucción. Por ahora, implementamos la activación inmediata. Más adelante, cuando implementemos el manejo completo de interrupciones, añadiremos este retraso.
- Manejo completo de interrupciones: Por ahora solo controlamos IME, pero falta implementar el registro IF (Interrupt Flag) y IE (Interrupt Enable), y el manejo real de las interrupciones en el bucle principal.
- ✅ Validación con ROMs reales: COMPLETADO - Se validó exitosamente con tetris_dx.gbc (ROM real de Game Boy Color). El sistema ahora ejecuta 3 instrucciones (NOP, JP nn, DI) antes de detenerse en 0x0151 con opcode 0xE0 (LDH (n), A) no implementado. La instrucción DI se ejecutó correctamente, confirmando que el control de interrupciones funciona. El siguiente paso es implementar LDH (n), A (0xE0) para continuar con la inicialización del sistema.
Hipótesis y Suposiciones
Suposición 1: Por ahora, asumimos que inicializar IME en False es seguro, ya que los juegos suelen desactivarlo explícitamente al inicio con DI. Si en el futuro hay problemas, podemos cambiar la inicialización.
Suposición 2: Implementamos EI sin retraso por ahora para simplificar. Más adelante, cuando implementemos el manejo completo de interrupciones, añadiremos el retraso de 1 instrucción que tiene en hardware real.
Próximos Pasos
- [ ] PRIORITARIO: Implementar opcode 0xE0 (LDH (n), A - Load A into I/O) - La siguiente instrucción que necesita Tetris DX
- [ ] Implementar más opcodes según los que encuentre el emulador (probablemente LD (HL), A, DEC, LDH A, (n), etc.)
- [ ] Implementar el retraso de 1 instrucción en EI cuando se implemente el manejo completo de interrupciones
- [ ] Implementar registros IF (Interrupt Flag) y IE (Interrupt Enable)
- [ ] Implementar manejo completo de interrupciones en el bucle principal