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 la ALU y Gestión de Flags
Resumen
Implementación de la ALU (Unidad Aritmética Lógica) de la CPU con gestión correcta de flags, especialmente el Half-Carry (H) que es crítico para la instrucción DAA y el manejo de números decimales. Refactorización de la CPU para usar una tabla de despacho (dispatch table) en lugar de if/elif, mejorando la escalabilidad del código. Implementación de los opcodes ADD A, d8 (0xC6) y SUB d8 (0xD6). Suite completa de tests TDD (5 tests) validando operaciones aritméticas y flags.
Concepto de Hardware
La ALU (Unidad Aritmética Lógica) es el componente de la CPU responsable de realizar operaciones aritméticas (suma, resta) y lógicas (AND, OR, XOR). En la Game Boy, la ALU opera sobre valores de 8 bits y actualiza un conjunto de flags que indican el estado del resultado.
Los Flags de la CPU LR35902
La CPU mantiene 4 flags principales en el registro F (solo bits altos válidos):
- Z (Zero, bit 7): Se activa cuando el resultado de una operación es cero.
- N (Subtract, bit 6): Indica si la última operación fue una resta (1) o suma (0).
- H (Half-Carry, bit 5): Indica si hubo carry/borrow del bit 3 al 4 (nibble bajo).
- C (Carry, bit 4): Indica si hubo carry/borrow del bit 7 (overflow/underflow de 8 bits).
El Half-Carry: La "Bestia Negra" de los Emuladores
El flag Half-Carry (H) es especialmente crítico y a menudo malentendido. Indica si hubo un "carry" (en suma) o "borrow" (en resta) entre el nibble bajo (bits 0-3) y el nibble alto (bits 4-7).
¿Por qué es importante? La instrucción DAA (Decimal Adjust Accumulator)
utiliza el flag H para convertir números binarios a BCD (Binary Coded Decimal). Sin H correcto, los
números decimales en juegos (puntuaciones, vidas, contadores) se mostrarán corruptos.
Fórmulas:
- Suma: H = 1 si
(A & 0xF) + (value & 0xF) > 0xF - Resta: H = 1 si
(A & 0xF) < (value & 0xF)
Ejemplo: Sumar 15 (0x0F) + 1 (0x01) = 16 (0x10). El nibble bajo pasa de 0xF a 0x0
con carry al nibble alto. H se activa porque 0xF + 0x1 = 0x10 (excede 0xF).
Diferencia entre Carry (C) y Half-Carry (H)
- Carry (C): Detecta overflow/underflow de 8 bits completos (bit 7).
- Half-Carry (H): Detecta overflow/underflow del nibble bajo (bit 3 a 4).
Ambos son independientes: una suma puede activar H sin activar C (ej: 0x0F + 0x01), o ambos (ej: 0xFF + 0x01).
Implementación
Se refactorizó la CPU para usar una tabla de despacho (dispatch table) en lugar de una cadena de if/elif. Esto mejora la escalabilidad y el rendimiento, y es compatible con Python 3.9+.
Componentes creados/modificados
- Tabla de despacho: Diccionario
_opcode_tableque mapea opcodes a funciones manejadoras. - Helper
_add(): Suma un valor al registro A y actualiza flags Z, N, H, C correctamente. - Helper
_sub(): Resta un valor del registro A y actualiza flags Z, N, H, C correctamente. - Handlers de opcodes: Funciones individuales para cada opcode (
_op_nop(),_op_add_a_d8(), etc.). - Opcodes implementados: 0xC6 (ADD A, d8) y 0xD6 (SUB d8).
Decisiones de diseño
1. Tabla de despacho vs if/elif: Se eligió un diccionario para mejor escalabilidad. Cada opcode se mapea a una función, facilitando la adición de nuevos opcodes sin modificar lógica condicional. Compatible con Python 3.9+ (no requiere match/case de Python 3.10+).
2. Helpers privados para ALU: Los métodos _add() y _sub() son
privados y reutilizables. Futuros opcodes que sumen/resten (ADD A, B; SUB A, C; etc.) pueden reutilizar
estos helpers, asegurando consistencia en la gestión de flags.
3. Fórmulas de Half-Carry: Se implementaron según documentación técnica (Pan Docs).
Para suma: (A & 0xF) + (value & 0xF) > 0xF. Para resta: (A & 0xF) < (value & 0xF).
4. Wrap-around explícito: Todas las operaciones aplican máscaras & 0xFF
para asegurar wrap-around de 8 bits, simulando el comportamiento del hardware real.
Archivos Afectados
src/cpu/core.py- Refactorizado para usar tabla de despacho, implementados helpers ALU y opcodes 0xC6/0xD6tests/test_alu.py- Nuevo archivo con 5 tests TDD para validar ALU y flagsINFORME_COMPLETO.md- Actualizado con entrada de bitácoradocs/bitacora/entries/2025-12-16__0004__alu-flags.html- Nueva entrada de bitácora webdocs/bitacora/index.html- Actualizado con nueva entrada
Tests y Verificación
Se creó una suite completa de tests TDD en tests/test_alu.py con 5 tests:
- test_add_basic: Suma básica 10 + 5 = 15, verifica flags Z=0, N=0, H=0, C=0
- test_add_half_carry: Suma 15 + 1 = 16, verifica que H se activa (CRÍTICO para DAA)
- test_add_full_carry: Suma 255 + 1 = 0 (wrap-around), verifica Z=1, H=1, C=1
- test_sub_basic: Resta básica 10 - 5 = 5, verifica flags Z=0, N=1, H=0, C=0
- test_sub_half_carry: Resta 16 - 1 = 15, verifica que H se activa (half-borrow)
Validación: Todos los tests pasan correctamente. La sintaxis del código fue verificada
con py_compile. Los tests validan especialmente el Half-Carry, que es crítico para la
futura implementación de DAA.
Nota: Los tests se ejecutan usando el ciclo completo de la CPU (fetch-decode-execute), no solo los helpers ALU, asegurando que la integración funciona correctamente.
Fuentes Consultadas
- Pan Docs: CPU Flags, Instruction Set (ADD, SUB)
- Z80/8080 Architecture Manual: Explicación de Half-Carry y su uso en DAA
- Game Boy CPU Manual: Comportamiento de flags en operaciones aritméticas
Nota: Las fórmulas de Half-Carry están basadas en documentación técnica estándar de arquitecturas Z80/8080, de las cuales la LR35902 es derivada.
Integridad Educativa
Lo que Entiendo Ahora
- Half-Carry: Es un flag que detecta overflow/underflow del nibble bajo (bits 0-3). Es crítico para DAA y el manejo de números decimales en juegos. Sin H correcto, las puntuaciones y contadores se mostrarán corruptos.
- Tabla de despacho: Un diccionario que mapea opcodes a funciones es más escalable que if/elif, especialmente cuando hay 256 opcodes posibles. Compatible con Python 3.9+.
- Helpers reutilizables: Los métodos
_add()y_sub()pueden ser reutilizados por múltiples opcodes (ADD A, B; ADD A, C; SUB A, B; etc.), asegurando consistencia. - Fórmulas de flags: H en suma:
(A & 0xF) + (value & 0xF) > 0xF. H en resta:(A & 0xF) < (value & 0xF). C en suma:(A + value) > 0xFF. C en resta:A < value.
Lo que Falta Confirmar
- Comportamiento de flags en operaciones con carry previo: Cuando se implementen instrucciones ADC (Add with Carry) y SBC (Subtract with Carry), habrá que verificar cómo se combinan los flags con el carry previo.
- Timing exacto de flags: Los flags se actualizan inmediatamente después de la operación, pero falta verificar si hay casos edge donde el timing sea crítico (probablemente no, pero es algo a tener en cuenta).
- Validación con ROMs de test: Aunque los tests unitarios pasan, sería ideal validar con ROMs de test redistribuibles que prueben operaciones aritméticas y DAA.
Hipótesis y Suposiciones
Suposición principal: Las fórmulas de Half-Carry implementadas son correctas según la documentación técnica consultada. Sin embargo, no he podido verificar directamente con hardware real o ROMs de test comerciales (que no podemos distribuir). La implementación se basa en:
- Documentación técnica estándar (Pan Docs, manuales Z80/8080)
- Tests unitarios que validan casos conocidos (15+1, 255+1, etc.)
- Lógica matemática del comportamiento esperado
Plan de validación futura: Cuando se implemente DAA, si los números decimales se muestran correctamente en juegos, confirmará que H está bien implementado. Si hay corrupción, habrá que revisar las fórmulas.
Próximos Pasos
- [ ] Implementar más opcodes aritméticos (ADD A, B; ADD A, C; SUB A, B; etc.) reutilizando helpers ALU
- [ ] Implementar instrucciones ADC (Add with Carry) y SBC (Subtract with Carry)
- [ ] Implementar operaciones lógicas (AND, OR, XOR) con gestión de flags
- [ ] Implementar DAA (Decimal Adjust Accumulator) que utiliza el flag H
- [ ] Validar con ROMs de test redistribuibles que prueben operaciones aritméticas
- [ ] Expandir la tabla de despacho con más opcodes del set de instrucciones