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

Implementación de ALU y Flags en C++

Fecha: 2025-12-19 Step ID: 0105 Estado: Completado

Resumen

Se implementó la ALU (Arithmetic Logic Unit) y la gestión de Flags en C++, añadiendo operaciones aritméticas básicas (ADD, SUB) y lógicas (AND, XOR) al núcleo nativo. Se implementaron 5 nuevos opcodes: INC A, DEC A, ADD A d8, SUB d8 y XOR A. Todos los tests pasan correctamente, validando la gestión precisa de flags (Z, N, H, C) y el cálculo eficiente de half-carry en C++.

Concepto de Hardware

La ALU (Arithmetic Logic Unit) es el componente de la CPU que realiza operaciones matemáticas y lógicas. En la Game Boy (LR35902), la ALU opera principalmente sobre el registro A (Acumulador) y actualiza 4 flags críticos:

  • Z (Zero): Se activa cuando el resultado de una operación es 0.
  • N (Subtract): Se activa en operaciones de resta, se desactiva en suma.
  • H (Half-Carry): Se activa cuando hay desbordamiento del nibble bajo (bit 3 → 4) en suma, o borrow en resta.
  • C (Carry): Se activa cuando hay desbordamiento completo de 8 bits (overflow/underflow).

El cálculo de Half-Carry es crítico para la instrucción DAA (Decimal Adjust Accumulator), que convierte resultados binarios a BCD (Binary Coded Decimal). En C++, estas operaciones bitwise se compilan a muy pocas instrucciones de máquina, ofreciendo rendimiento máximo.

Optimización C++: La fórmula ((a & 0xF) + (b & 0xF)) > 0xF para half-carry se compila directamente a operaciones de registro, eliminando el overhead de objetos Python y llamadas a función.

Implementación

Se añadieron 4 métodos privados inline en la clase CPU para operaciones ALU: alu_add(), alu_sub(), alu_and() y alu_xor(). Estos métodos actualizan el registro A y los flags de forma atómica, usando los métodos inline de CoreRegisters para máximo rendimiento.

Componentes creados/modificados

  • CPU.hpp: Añadidas declaraciones de métodos ALU inline.
  • CPU.cpp: Implementación de métodos ALU y 5 nuevos opcodes (0x3C, 0x3D, 0xC6, 0xD6, 0xAF).
  • tests/test_core_cpu_alu.py: Suite completa de 7 tests para validar ALU nativa.

Decisiones de diseño

  • Métodos inline: Los helpers ALU son métodos privados inline para que el compilador los incruste directamente en el switch de opcodes, eliminando el coste de llamada a función.
  • Reutilización de CoreRegisters: Se usan los métodos set_flag_*() ya existentes en CoreRegisters, que son inline y aplican automáticamente la máscara del registro F.
  • Cálculo de Half-Carry: Se guarda el valor original de A antes de la operación para calcular correctamente el half-carry/half-borrow.
  • Opcodes implementados:
    • 0x3C: INC A (Increment A) - 1 M-Cycle
    • 0x3D: DEC A (Decrement A) - 1 M-Cycle
    • 0xC6: ADD A, d8 (Add immediate) - 2 M-Cycles
    • 0xD6: SUB d8 (Subtract immediate) - 2 M-Cycles
    • 0xAF: XOR A (XOR A with A, optimización para A=0) - 1 M-Cycle

Código clave

// Ejemplo: alu_add() en CPU.cpp
void CPU::alu_add(uint8_t value) {
    uint8_t a_old = regs_->a;  // Guardar para calcular flags
    uint16_t result = static_cast<uint16_t>(a_old) + static_cast<uint16_t>(value);
    regs_->a = static_cast<uint8_t>(result);
    
    // Flags
    regs_->set_flag_z(regs_->a == 0);
    regs_->set_flag_n(false);
    
    // Half-carry: ((a_old & 0xF) + (value & 0xF)) > 0xF
    uint8_t a_low = a_old & 0x0F;
    uint8_t value_low = value & 0x0F;
    regs_->set_flag_h((a_low + value_low) > 0x0F);
    
    regs_->set_flag_c(result > 0xFF);
}

Archivos Afectados

  • src/core/cpp/CPU.hpp - Añadidas declaraciones de métodos ALU inline
  • src/core/cpp/CPU.cpp - Implementación de ALU y 5 nuevos opcodes
  • tests/test_core_cpu_alu.py - Suite de 7 tests para validar ALU nativa

Tests y Verificación

Se creó una suite completa de tests en Python que valida la ALU nativa:

  • test_add_immediate_basic: Suma básica (10 + 2 = 12), verifica flags Z, N, H, C.
  • test_sub_immediate_zero_flag: Resta que activa Flag Z (10 - 10 = 0).
  • test_add_half_carry: Detección de half-carry (0x0F + 0x01 = 0x10).
  • test_xor_a_optimization: XOR A limpia A a 0 y activa Z.
  • test_inc_a: Incremento de A con actualización de flags.
  • test_dec_a: Decremento de A con half-borrow.
  • test_add_full_carry: Detección de carry completo (0xFF + 0x01 = 0x00).

Resultado: ✅ 7/7 tests pasando (100% éxito).

Validación de módulo compilado C++: La extensión Cython se compiló correctamente sin errores. Los tests ejecutan código nativo C++ a través del wrapper Python, validando la interoperabilidad completa.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Half-Carry en C++: La fórmula ((a & 0xF) + (b & 0xF)) > 0xF se compila a muy pocas instrucciones de máquina (AND, ADD, CMP), ofreciendo rendimiento máximo comparado con Python donde cada operación crea objetos int.
  • Flags y DAA: El flag H (Half-Carry) es crítico para DAA, que ajusta resultados binarios a BCD. Sin H correcto, DAA falla y los juegos que usan BCD crashean.
  • Optimización XOR A: XOR A (0xAF) es una optimización común en código Game Boy para limpiar A a 0 en un solo ciclo, más eficiente que LD A, 0.
  • Inline en C++: Los métodos inline se incrustan directamente en el código de llamada, eliminando el overhead de llamada a función. En el bucle crítico de emulación, esto es esencial para rendimiento.

Lo que Falta Confirmar

  • ADC/SBC: Operaciones con carry/borrow previo (ADC A, d8 y SBC A, d8) aún no implementadas. Requieren leer el flag C antes de la operación.
  • Operaciones con registros: ADD A, r (donde r es B, C, D, E, H, L) aún no implementadas. Requieren mapeo de opcodes a registros.
  • OR y CP: Operaciones lógicas OR y comparación CP aún no implementadas.

Hipótesis y Suposiciones

Half-Borrow en DEC: La implementación actual calcula half-borrow como (old_a & 0x0F) == 0x00, lo que detecta cuando el nibble bajo es 0 antes de decrementar. Esto es correcto según Pan Docs, pero se validó con tests para asegurar precisión.

Próximos Pasos

  • [ ] Implementar ADC A, d8 (0xCE) y SBC A, d8 (0xDE) - operaciones con carry/borrow
  • [ ] Implementar operaciones ALU con registros (ADD A, r donde r = B, C, D, E, H, L)
  • [ ] Implementar operaciones lógicas restantes (OR, CP)
  • [ ] Implementar operaciones de 16 bits (ADD HL, rr, INC rr, DEC rr)
  • [ ] Optimizar el switch de opcodes con lookup tables o jump tables