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.
Step 0251: Implementación de DMA (OAM Transfer)
Resumen
Este Step implementa la transferencia DMA (Direct Memory Access) para copiar datos a la OAM (Object Attribute Memory).
Cuando un juego escribe un valor en el registro 0xFF46, el hardware copia automáticamente 160 bytes desde
la dirección XX00 (donde XX es el valor escrito) hasta la OAM (0xFE00-0xFE9F).
Esta funcionalidad es crítica para que los juegos puedan actualizar los sprites, y muchos juegos (como Tetris)
dependen de ella para su secuencia de arranque.
Concepto de Hardware
La Game Boy incluye un mecanismo de DMA (Direct Memory Access) que permite copiar datos a la OAM sin intervención directa de la CPU. Este mecanismo es esencial porque la OAM es accesible solo durante ciertos períodos del ciclo de renderizado de la PPU.
Funcionamiento del DMA:
- Registro DMA (
0xFF46): Escribir un valorXXen este registro inicia una transferencia DMA. - Dirección Origen: El valor escrito forma la parte alta de la dirección origen:
XX00(ej: escribir0xC0→ origen en0xC000). - Destino: La OAM siempre está en
0xFE00-0xFE9F(160 bytes, 40 sprites × 4 bytes). - Duración: En hardware real, la transferencia tarda aproximadamente 160 microsegundos (640 ciclos de CPU).
- Restricción durante DMA: Durante la transferencia, la CPU solo puede acceder a HRAM (
0xFF80-0xFFFE). Intentar acceder a otras regiones de memoria durante la DMA puede causar comportamientos impredecibles.
¿Por qué es importante?
Muchos juegos usan DMA para copiar datos de sprites a la OAM porque es más rápido y eficiente que copiar byte a byte con la CPU. Además, algunos juegos (como Tetris) usan la DMA como parte de su secuencia de inicialización o como mecanismo de sincronización. Si la DMA no está implementada, estos juegos pueden quedarse en bucles infinitos esperando que la transferencia se complete.
Fuente: Pan Docs - "DMA Transfer"
Implementación
Se implementó la transferencia DMA en el método write() de MMU.cpp. Cuando se detecta una
escritura en 0xFF46, se calcula la dirección origen y se copian 160 bytes a la OAM.
Componentes modificados
src/core/cpp/MMU.cpp: Añadida lógica de transferencia DMA en el métodowrite().
Decisiones de diseño
DMA Instantánea: Por simplicidad, implementamos una copia instantánea de los 160 bytes. En hardware real, la transferencia tarda ~640 ciclos, pero para esta primera implementación asumimos que la copia es inmediata. Una implementación más precisa requeriría:
- Contar ciclos durante la transferencia (640 ciclos).
- Bloquear el acceso a memoria (excepto HRAM) durante la transferencia.
- Sincronizar con el ciclo de renderizado de la PPU.
Validación de Direcciones: Se valida que la dirección de destino (0xFE00 + i) esté dentro
de los límites de memoria antes de escribir. Esto previene accesos fuera de rango.
Uso de read(): Se usa el método read() de la MMU para leer desde la dirección
origen, lo que garantiza que se respeten todas las reglas de mapeo de memoria (ej: Echo RAM, registros especiales).
Esto es importante porque la DMA puede copiar desde cualquier región de memoria (ROM, RAM, VRAM, etc.).
Código implementado
// --- Step 0251: IMPLEMENTACIÓN DMA (OAM TRANSFER) ---
if (addr == 0xFF46) {
// 1. Calcular dirección origen: value * 0x100
uint16_t source_base = static_cast<uint16_t>(value) << 8;
// 2. Copiar 160 bytes (0xA0) a OAM (0xFE00-0xFE9F)
for (int i = 0; i < 160; i++) {
uint16_t source_addr = source_base + i;
uint8_t data = read(source_addr);
if ((0xFE00 + i) < MEMORY_SIZE) {
memory_[0xFE00 + i] = data;
}
}
printf("[DMA] Transferencia completada: %04X -> FE00 (160 bytes)\n", source_base);
}
Archivos Afectados
src/core/cpp/MMU.cpp- Añadida lógica de transferencia DMA en el métodowrite()(líneas 302-323).
Tests y Verificación
La implementación se validará ejecutando juegos que dependen de DMA:
- Tetris: El log del Step 0250 mostró un intento de DMA (
Write DMA [FF46] = 00). Con esta implementación, Tetris debería poder completar su secuencia de arranque. - Mario Deluxe y Pokémon Red: Estos juegos ya muestran actividad gráfica, pero la DMA permitirá que los sprites se rendericen correctamente.
- Log de DMA: El código incluye un mensaje de log (
[DMA] Transferencia completada...) que confirmará cuando se ejecute una transferencia DMA.
Comando de prueba:
python main.py roms/tetris.gb
Validación esperada:
- Ver el mensaje
[DMA] Transferencia completada...en la consola. - Tetris sale del bucle infinito (el PC cambia de
0x2Bxx). - Los sprites (piezas) aparecen en pantalla o el logo se muestra correctamente.
Fuentes Consultadas
- Pan Docs: DMA Transfer
- Pan Docs: Memory Map - OAM
Integridad Educativa
Lo que Entiendo Ahora
- DMA como mecanismo de copia rápida: La DMA permite copiar bloques de memoria sin intervención directa de la CPU, lo que es más eficiente que copiar byte a byte.
- Restricciones durante DMA: En hardware real, durante la transferencia DMA, la CPU solo puede acceder a HRAM. Esta restricción no está implementada en esta versión inicial, pero es importante para una emulación precisa.
- Uso de DMA en juegos: Los juegos usan DMA no solo para copiar sprites, sino también como mecanismo de sincronización o como parte de su secuencia de inicialización.
Lo que Falta Confirmar
- Timing preciso: La implementación actual es instantánea, pero en hardware real tarda 640 ciclos. Necesitamos verificar si los juegos dependen de este timing para funcionar correctamente.
- Bloqueo de memoria: No implementamos el bloqueo de acceso a memoria (excepto HRAM) durante la DMA. Algunos juegos pueden depender de este comportamiento.
- Interacción con PPU: La OAM solo es accesible durante ciertos períodos del ciclo de renderizado. Necesitamos verificar si la DMA respeta estos períodos o si puede escribir en cualquier momento.
Hipótesis y Suposiciones
DMA Instantánea: Asumimos que una copia instantánea es suficiente para que los juegos funcionen. Si algunos juegos fallan, puede ser necesario implementar el timing preciso de 640 ciclos.
Acceso a Memoria durante DMA: Por ahora, permitimos que la CPU acceda a cualquier región de memoria durante la DMA. Si encontramos problemas, necesitaremos implementar el bloqueo de acceso (excepto HRAM).
Próximos Pasos
- [ ] Probar Tetris y verificar si sale del bucle infinito.
- [ ] Verificar que los sprites aparecen correctamente en Mario y Pokémon.
- [ ] Si es necesario, implementar timing preciso de DMA (640 ciclos).
- [ ] Si es necesario, implementar bloqueo de acceso a memoria durante DMA (excepto HRAM).
- [ ] Investigar si hay otros juegos que dependen críticamente de DMA para su arranque.