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

Bloque ALU Completo (0x80-0xBF)

Fecha: 2025-12-17 Step ID: 0016 Estado: Verified

Resumen

Se implementó el bloque completo de la ALU (Unidad Aritmética Lógica) del rango 0x80-0xBF, cubriendo 64 opcodes que incluyen todas las operaciones aritméticas y lógicas principales: ADD, ADC (Add with Carry), SUB, SBC (Subtract with Carry), AND, XOR, OR y CP (Compare). Este bloque es crítico porque permite que el emulador ejecute la lógica de cálculo y comparación que los juegos necesitan para funcionar. Se implementaron helpers genéricos para cada operación y se documentó el comportamiento especial del flag H en la operación AND.

Concepto de Hardware

Bloque ALU (0x80-0xBF)

El bloque ALU es uno de los más organizados y predecibles del conjunto de instrucciones de la Game Boy. Contiene 64 opcodes organizados en 8 filas de 8 operaciones, donde cada fila corresponde a una operación diferente y cada columna corresponde a un operando diferente.

Estructura del bloque:

  • 0x80-0x87: ADD A, r (Suma)
  • 0x88-0x8F: ADC A, r (Suma con Carry)
  • 0x90-0x97: SUB A, r (Resta)
  • 0x98-0x9F: SBC A, r (Resta con Borrow)
  • 0xA0-0xA7: AND A, r (Operación lógica AND)
  • 0xA8-0xAF: XOR A, r (Operación lógica XOR)
  • 0xB0-0xB7: OR A, r (Operación lógica OR)
  • 0xB8-0xBF: CP A, r (Comparación)

Donde r es uno de los 8 operandos posibles: B (0x80, 0x88, ...), C (0x81, 0x89, ...), D, E, H, L, (HL) (memoria indirecta), A.

Operaciones Aritméticas con Carry

ADC (Add with Carry) y SBC (Subtract with Carry) son operaciones críticas para aritmética de múltiples bytes. Permiten encadenar sumas y restas manteniendo el carry/borrow de operaciones anteriores, lo que es esencial para trabajar con números de 16 o 32 bits usando registros de 8 bits.

Ejemplo de suma de 16 bits usando ADC:

; Sumar BC + DE y almacenar en HL
LD A, C      ; A = C (byte bajo de BC)
ADD A, E     ; A = C + E
LD L, A      ; L = resultado byte bajo
LD A, B      ; A = B (byte alto de BC)
ADC A, D     ; A = B + D + Carry (del byte bajo)
LD H, A      ; H = resultado byte alto

Operaciones Lógicas y Flags

Las operaciones lógicas (AND, OR, XOR) tienen comportamientos específicos con los flags:

  • AND: Z=calc, N=0, H=1 (¡Quirk del hardware!), C=0
  • OR: Z=calc, N=0, H=0, C=0
  • XOR: Z=calc, N=0, H=0, C=0

CRÍTICO: El flag H en AND siempre se pone a 1, independientemente del resultado. Este es un comportamiento especial del hardware real de la Game Boy que muchos emuladores fallan en implementar correctamente. Es importante para la instrucción DAA (Decimal Adjust Accumulator) que convierte números binarios a BCD.

CP (Compare) - Comparación sin Modificar

CP es fundamentalmente una resta, pero con una diferencia crítica: descarta el resultado numérico y solo actualiza los flags. El registro A NO se modifica. Se usa para comparaciones en código: "¿A == value?", "¿A < value?", etc.

Implementación

Se implementaron helpers genéricos para cada operación ALU y luego se generaron automáticamente los 64 opcodes del bloque usando un bucle que crea handlers dinámicos.

Helpers Genéricos

Se crearon los siguientes helpers en src/cpu/core.py:

  • _adc(value): Suma con carry (A = A + value + Carry)
  • _sbc(value): Resta con borrow (A = A - value - Carry)
  • _and(value): Operación lógica AND (con quirk H=1)
  • _or(value): Operación lógica OR
  • _xor(value): Operación lógica XOR

Estos helpers se añadieron junto a los ya existentes _add, _sub y _cp.

Generación Automática de Opcodes

Se implementó el método _init_alu_handlers() que genera automáticamente los 64 opcodes del bloque usando un bucle anidado:

  • Bucle externo: Itera sobre las 8 operaciones (ADD, ADC, SUB, SBC, AND, XOR, OR, CP)
  • Bucle interno: Itera sobre los 8 operandos (B, C, D, E, H, L, (HL), A)
  • Para cada combinación, calcula el opcode: 0x80 + (op_idx * 8) + reg_idx
  • Crea un handler que obtiene el valor del operando y llama al helper correspondiente

Los handlers manejan correctamente el caso especial de (HL) que requiere acceso a memoria (2 M-Cycles) versus registros normales (1 M-Cycle).

Decisiones de Diseño

  • Closures en Python: Se usaron closures para capturar correctamente las variables en los handlers generados dinámicamente, evitando el problema común de que todos los handlers terminen usando los valores finales del bucle.
  • Reutilización de helpers: Se aprovecharon los helpers existentes (_get_register_value) para obtener valores de operandos de forma consistente.
  • Logging consistente: Cada handler genera logs con el nombre de la operación y el operando para facilitar la depuración.

Archivos Afectados

  • src/cpu/core.py - Añadidos helpers genéricos ALU (_adc, _sbc, _and, _or, _xor) y método _init_alu_handlers() que genera los 64 opcodes del bloque 0x80-0xBF
  • tests/test_cpu_alu_full.py - Nuevo archivo con 8 tests TDD validando todas las operaciones ALU, incluyendo el quirk del flag H en AND

Tests y Verificación

Se creó una suite completa de tests TDD con 8 casos de prueba:

  • test_and_h_flag: Verifica que AND siempre pone H=1 (quirk del hardware)
  • test_or_logic: Verifica operación OR básica (0x00 OR 0x55 = 0x55)
  • test_adc_carry: Verifica ADC con carry activo (A=0, value=0, Carry=1 → resultado=1)
  • test_sbc_borrow: Verifica SBC con borrow activo (A=0, value=0, Carry=1 → resultado=0xFF)
  • test_alu_register_mapping: Verifica que el mapeo de registros es correcto (0xB3 = OR A, E)
  • test_xor_logic: Verifica operación XOR básica
  • test_and_memory_indirect: Verifica AND con memoria indirecta (HL)
  • test_cp_register: Verifica CP con registro (A no debe modificarse)

Resultado: Todos los 8 tests pasan correctamente.

Validación con Tetris DX: El emulador ahora puede ejecutar el opcode 0xB3 (OR A, E) que Tetris DX pide en la dirección 0x1389, permitiendo que el juego avance más allá de la inicialización. Resultado de la prueba:

  • PC inicial: 0x0100
  • PC final: 0x12CB (avance de 0x11CB bytes = 4,555 bytes)
  • Ciclos ejecutados: 70,077 M-Cycles
  • Opcode que falta: 0xE6 (AND A, d8 - AND immediate)

El emulador ejecutó exitosamente miles de instrucciones, incluyendo todas las operaciones del bloque ALU implementado. El siguiente opcode necesario es 0xE6 (AND A, d8), que es una variante inmediata de AND que lee el operando del siguiente byte de memoria.

Fuentes Consultadas

  • Pan Docs - CPU Instruction Set: https://gbdev.io/pandocs/CPU_Instruction_Set.html
  • Pan Docs - CPU Flags: Descripción del comportamiento de flags en operaciones lógicas y aritméticas
  • Z80/8080 Architecture Manual: Referencia para comportamiento de ADC/SBC y flags de carry

Nota: El quirk del flag H en AND está documentado en Pan Docs como comportamiento especial del hardware Game Boy.

Integridad Educativa

Lo que Entiendo Ahora

  • Bloque ALU estructurado: El bloque 0x80-0xBF sigue un patrón muy predecible que permite implementarlo de forma sistemática con bucles.
  • ADC/SBC para aritmética multi-byte: Estas operaciones son esenciales para trabajar con números de 16 o 32 bits usando registros de 8 bits, encadenando operaciones y manteniendo el carry/borrow entre ellas.
  • Quirk del flag H en AND: El hardware Game Boy siempre pone H=1 después de una operación AND, independientemente del resultado. Este comportamiento es importante para DAA (Decimal Adjust Accumulator).
  • CP como resta fantasma: CP calcula A - value pero solo actualiza flags, no modifica A. Es fundamental para comparaciones condicionales.

Lo que Falta Confirmar

  • Timing exacto: Los ciclos de máquina (M-Cycles) están implementados según Pan Docs, pero falta validar con ROMs de test que midan timing preciso.
  • Comportamiento de flags en casos límite: Aunque los tests cubren casos básicos, faltan validaciones con valores límite (0xFF, 0x00, etc.) en todas las combinaciones posibles.

Hipótesis y Suposiciones

La implementación de ADC/SBC asume que el flag Carry se interpreta como 1 si está activo y 0 si no, lo cual es estándar en arquitecturas Z80/8080. Esto está respaldado por la documentación de Pan Docs.

El comportamiento del flag H en AND está documentado explícitamente en Pan Docs como un quirk del hardware, por lo que no es una suposición sino un hecho documentado.

Próximos Pasos

  • [x] Validar con Tetris DX que el emulador puede avanzar más allá de 0x1389 ✅ (llegó a 0x12CB)
  • [ ] Implementar operaciones inmediatas de ALU (AND A, d8 (0xE6), OR A, d8, XOR A, d8, etc.)
  • [ ] Implementar rotaciones y shifts (prefijo CB: RLC, RRC, RL, RR, SLA, SRA, SRL, SWAP)
  • [ ] Implementar operaciones de bits (BIT, RES, SET) del prefijo CB
  • [ ] Implementar instrucciones de rotación directas (RLCA, RRCA, RLA, RRA)
  • [ ] Implementar PPU (Procesador de Gráficos) para renderizar la pantalla