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

Aritmética de 16 bits y Retornos Condicionales

Fecha: 2025-12-16 Step ID: 0014 Estado: Verified

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), y TestConditionalReturn (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 flags
    • test_add_hl_bc: Verifica Half-Carry en bit 11 para ADD HL, BC
    • test_add_hl_no_z_flag: Verifica que ADD HL no toca Z incluso cuando resultado es 0
    • test_ret_nz_taken y test_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

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