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.
Aritmética de 16 bits y Retornos Condicionales
Resumen
Se implementaron las operaciones de aritmética de 16 bits (INC/DEC de registros pares y ADD HL, rr) y los retornos condicionales (RET NZ, RET Z, RET NC, RET C). La peculiaridad crítica es que INC/DEC de 16 bits NO afectan a ningún flag (a diferencia de los de 8 bits), lo cual es esencial para bucles que decrementan contadores sin corromper flags de comparaciones anteriores. ADD HL, rr actualiza flags H y C pero NO toca Z, otro comportamiento especial del hardware. Los retornos condicionales permiten implementar subrutinas con lógica condicional. Suite completa de tests TDD (24 tests) validando todas las funcionalidades. El emulador ahora puede ejecutar bucles complejos como los de inicialización de Tetris DX.
Concepto de Hardware
INC/DEC de 16 bits - NO afectan Flags: A diferencia de los incrementos/decrementos
de 8 bits que actualizan flags Z, N, H (pero no C), las versiones de 16 bits (INC BC, DEC DE, etc.)
NO modifican ningún flag. Esta es una trampa clásica en emulación. Se usan para recorrer
memoria o decrementar contadores en bucles sin corromper el estado de flags de una comparación anterior
(como CP). Por ejemplo, en un bucle que hace DEC BC y luego verifica si BC es 0 usando
LD A, B; OR C; JR NZ, loop, el DEC BC no debe tocar los flags para que
la comparación funcione correctamente.
ADD HL, rr - Flags Especiales: La suma de 16 bits ADD HL, BC/DE/HL/SP
tiene un comportamiento peculiar con los flags:
- Z (Zero): NO SE TOCA (se mantiene como estaba antes de la operación).
- N (Subtract): Siempre 0 (es una suma).
- H (Half-Carry): Se activa si hay carry del bit 11 al 12 (desbordamiento de 12 bits).
- C (Carry): Se activa si hay carry del bit 15 (desbordamiento de 16 bits).
El Half-Carry en ADD HL se calcula sobre 12 bits (no 8 como en ADD de 8 bits), porque el hardware verifica el desbordamiento del nibble de 12 bits (bits 0-11). Esto es diferente al Half-Carry de operaciones de 8 bits que se calcula sobre 4 bits.
Retornos Condicionales: Similar a los saltos condicionales (JR NZ, etc.), existen retornos condicionales (RET NZ, RET Z, RET NC, RET C) que solo ejecutan el retorno si se cumple una condición. Si la condición es verdadera, consumen 5 M-Cycles (20 T-Cycles). Si es falsa, consumen 2 M-Cycles (8 T-Cycles). Se usan para implementar subrutinas que toman decisiones antes de retornar.
Implementación
Se añadieron 16 nuevos opcodes a la tabla de despacho:
- INC 16-bit: 0x03 (BC), 0x13 (DE), 0x23 (HL), 0x33 (SP)
- DEC 16-bit: 0x0B (BC), 0x1B (DE), 0x2B (HL), 0x3B (SP)
- ADD HL, rr: 0x09 (BC), 0x19 (DE), 0x29 (HL), 0x39 (SP)
- RET condicional: 0xC0 (NZ), 0xC8 (Z), 0xD0 (NC), 0xD8 (C)
Componentes creados/modificados
src/cpu/core.py: Añadidos handlers para INC/DEC de 16 bits (8 handlers), helper_add_hl_16bit()para ADD HL, rr (4 handlers), y handlers para retornos condicionales (4 handlers). Todos con documentación exhaustiva sobre el comportamiento de flags.tests/test_cpu_math16.py: Nuevo archivo con 24 tests unitarios organizados en 4 clases:TestInc16Bit(5 tests),TestDec16Bit(5 tests),TestAddHL16Bit(6 tests), yTestConditionalReturn(8 tests).
Decisiones de diseño
Helper _add_hl_16bit(): Se creó un helper genérico para evitar duplicación
de código entre los 4 handlers de ADD HL, rr. El helper maneja la lógica de flags (especialmente el
Half-Carry de 12 bits) de forma centralizada, facilitando el mantenimiento y la corrección de bugs.
Verificación de flags en tests: Los tests enfatizan verificar que INC/DEC de 16 bits NO tocan flags (especialmente Z y C), y que ADD HL no toca Z. Estos son puntos críticos que pueden causar bugs sutiles si se implementan incorrectamente.
Wrap-around explícito: Todas las operaciones de 16 bits usan enmascarado explícito
con & 0xFFFF para asegurar wrap-around correcto, siguiendo el patrón establecido en el código.
Archivos Afectados
src/cpu/core.py- Añadidos 16 nuevos handlers de opcodes y helper_add_hl_16bit()tests/test_cpu_math16.py- Nuevo archivo con 24 tests unitarios
Tests y Verificación
Validación exhaustiva mediante tests unitarios:
- Tests unitarios: 24 tests en
test_cpu_math16.py, todos pasando (100% de éxito). - Tests específicos críticos:
test_inc_bc_no_flags: Verifica que INC BC no modifica flags (especialmente Z)test_dec_bc_no_flags: Verifica que DEC BC no modifica flagstest_add_hl_bc: Verifica Half-Carry en bit 11 para ADD HL, BCtest_add_hl_no_z_flag: Verifica que ADD HL no toca Z incluso cuando resultado es 0test_ret_nz_takenytest_ret_nz_not_taken: Verifican ciclos condicionales (5 vs 2 M-Cycles)
- Verificación con ROM: Tetris DX ahora ejecuta correctamente hasta
DEC DE(0x1B) y avanza más en el código antes de encontrar un opcode no implementado (0x7A = LD A, D). - Suite completa: Todos los 136 tests del proyecto pasan correctamente.
Fuentes Consultadas
- Pan Docs: Game Boy CPU Instruction Set - Sección sobre INC/DEC de 16 bits, ADD HL, rr, y RET condicionales.
- Pan Docs: CPU Flags Behavior - Comportamiento de flags en operaciones de 16 bits, especialmente el flag Z en ADD HL.
Nota: La implementación sigue las especificaciones de Pan Docs para el comportamiento exacto de flags en operaciones de 16 bits, especialmente las peculiaridades de que INC/DEC 16-bit no afectan flags y que ADD HL no toca Z.
Integridad Educativa
Lo que Entiendo Ahora
- INC/DEC de 16 bits no tocan flags: Esta diferencia clave con los de 8 bits es crítica para bucles que usan contadores de 16 bits. Si los flags cambiaran, se corrompería el estado de comparaciones anteriores.
- ADD HL, rr y el flag Z: Es muy curioso que ADD HL no toque Z incluso cuando el resultado es 0. Esto significa que Z debe ser preservado de la operación anterior, lo cual es útil para bucles que combinan aritmética de 16 bits con comparaciones.
- Half-Carry en 12 bits: En ADD HL, el Half-Carry se calcula sobre 12 bits (bits 0-11), no sobre 4 bits como en operaciones de 8 bits. Esto refleja la arquitectura interna del hardware.
- Retornos condicionales: Son esenciales para implementar subrutinas que toman decisiones. El timing condicional (5 vs 2 M-Cycles) es importante para emulación precisa.
Lo que Falta Confirmar
- Comportamiento de flags en ADD HL, HL: Aunque está implementado según documentación, no he podido verificar directamente con hardware real. Los tests unitarios validan el comportamiento esperado según Pan Docs.
- Timing exacto de RET condicional: Los 5 M-Cycles cuando se toma el retorno y 2 M-Cycles cuando no se toma están documentados, pero no he verificado con hardware real. Los tests validan el comportamiento esperado.
Hipótesis y Suposiciones
Ninguna suposición crítica: La implementación sigue fielmente las especificaciones de Pan Docs para el comportamiento de flags y timing. Todas las decisiones están respaldadas por documentación técnica.
Validación mediante tests: Aunque no tengo acceso a hardware real, la suite completa de tests unitarios (24 tests específicos + 136 tests totales) valida el comportamiento esperado según documentación. El hecho de que Tetris DX avance correctamente hasta encontrar un opcode no implementado sugiere que la implementación es correcta.
Próximos Pasos
- [ ] Implementar más instrucciones de carga entre registros (LD r, r') que faltan, como LD A, D (0x7A)
- [ ] Implementar operaciones lógicas adicionales (OR, AND) que son comunes en bucles
- [ ] Continuar avanzando con Tetris DX para identificar las siguientes instrucciones críticas
- [ ] Considerar implementar más variantes de ADD/SUB para operaciones aritméticas más complejas