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.
MBC1 y Bank Switching
Resumen
Se implementó el Memory Bank Controller 1 (MBC1) para permitir que cartuchos mayores a 32KB funcionen correctamente. El MBC1 resuelve el problema de que la CPU solo puede direccionar 64KB, pero los juegos pueden tener ROMs de 512KB o más. La solución es el Bank Switching: dividir la ROM en bancos de 16KB y cambiar dinámicamente qué banco está visible en el rango 0x4000-0x7FFF. El banco 0 (0x0000-0x3FFF) siempre apunta a los primeros 16KB y no cambia. El banco switchable se cambia escribiendo en el rango 0x2000-0x3FFF, que el MBC1 interpreta como comandos (aunque la ROM es "Read Only"). Con esta implementación, Tetris DX (512KB) puede acceder a todos sus bancos de ROM, incluyendo música y gráficos que están en bancos superiores.
Concepto de Hardware
La Game Boy tiene un espacio de direcciones de 16 bits (0x0000 a 0xFFFF = 65536 bytes). Sin embargo, los juegos pueden tener ROMs mucho más grandes (64KB, 128KB, 256KB, 512KB, 1MB, etc.). El problema es que la CPU solo puede direccionar 64KB a la vez.
La solución es el Memory Bank Controller (MBC), un chip en el cartucho que actúa como una "ventana deslizante" sobre la ROM. El MBC1 divide la ROM en bancos de 16KB:
- Banco 0 (Fijo): El rango 0x0000-0x3FFF siempre apunta a los primeros 16KB de la ROM. Este banco contiene código crítico (inicialización, vectores de interrupción) que debe estar siempre accesible.
- Banco X (Switchable): El rango 0x4000-0x7FFF apunta al banco seleccionado. El juego puede cambiar qué banco está visible escribiendo en el rango 0x2000-0x3FFF.
Aunque la ROM es "Read Only", el MBC1 interpreta escrituras en ciertos rangos como comandos:
- 0x2000-0x3FFF: Selecciona el banco ROM (solo los 5 bits bajos, 0x1F). Si el juego intenta seleccionar banco 0, el MBC1 le da banco 1 (quirk del hardware).
- 0x0000-0x1FFF: (Reservado para RAM enable, no implementado aún)
- 0x4000-0x5FFF: (Reservado para RAM bank / ROM bank upper bits, no implementado aún)
- 0x6000-0x7FFF: (Reservado para mode select, no implementado aún)
Esta técnica de "Bank Switching" es común en sistemas con direccionamiento limitado. Permite que juegos grandes funcionen en hardware con restricciones de direccionamiento.
Fuente: Pan Docs - MBC1 Memory Bank Controller
Implementación
Se modificó la clase Cartridge para implementar MBC1 y se actualizó la MMU para
permitir escrituras en la zona ROM que se envíen al cartucho.
Componentes creados/modificados
- Cartridge: Añadido atributo
_rom_bank(inicializado a 1), modificadoread_byte()para manejar bank switching, añadidowrite_byte()para recibir comandos MBC. - MMU: Modificado
write_byte()para permitir escrituras en zona ROM (0x0000-0x7FFF) que se envíen al cartucho. - Tests: Creado
test_mbc1.pycon 6 tests que validan el comportamiento del MBC1.
Decisiones de diseño
Banco inicial: El banco ROM inicial es 1 (no puede ser 0 en zona switchable). Esto refleja el comportamiento real del hardware MBC1.
Quirk del banco 0: Si el juego intenta seleccionar banco 0 escribiendo 0x00 en 0x2000, el MBC1 le da banco 1. Este comportamiento está documentado en Pan Docs y se implementó explícitamente.
Enmascarado de bits: Solo los 5 bits bajos (0x1F) se usan para seleccionar banco. Esto permite hasta 32 bancos (aunque algunos cartuchos pueden tener más usando bits adicionales en otros rangos, no implementado aún).
RAM Banking y Mode Select: Por ahora, se ignoran los rangos 0x0000-0x1FFF (RAM enable), 0x4000-0x5FFF (RAM bank / ROM bank upper bits) y 0x6000-0x7FFF (mode select). Estos se implementarán cuando sea necesario para cartuchos que los usen.
Archivos Afectados
src/memory/cartridge.py- Implementación de MBC1: bank switching y comandos MBCsrc/memory/mmu.py- Modificación de write_byte() para permitir escrituras en zona ROMtests/test_mbc1.py- Suite completa de tests TDD para MBC1 (6 tests)
Tests y Verificación
Se creó una suite completa de tests TDD que valida el comportamiento del MBC1:
Tests unitarios (pytest)
- Comando ejecutado:
python3 -m pytest tests/test_mbc1.py -v - Entorno: macOS, Python 3.9.6
- Resultado: 6 tests PASSED en 0.29s
- Qué valida:
- Banco 0 fijo: El banco 0 (0x0000-0x3FFF) siempre apunta a los primeros 16KB, independientemente del banco seleccionado.
- Banco por defecto: La zona switchable (0x4000-0x7FFF) apunta al banco 1 por defecto.
- Bank switching: Escribir en 0x2000-0x3FFF cambia correctamente el banco visible.
- Quirk del banco 0: Escribir 0x00 selecciona banco 1 (no banco 0).
- Enmascarado de bits: Solo los 5 bits bajos se usan para seleccionar banco.
- Integración MMU: La MMU permite escrituras en zona ROM que se envían al cartucho.
Código del test (fragmento esencial)
def test_mbc1_bank_switching() -> None:
"""Test: Cambiar de banco escribiendo en 0x2000-0x3FFF."""
# Crear ROM dummy de 64KB con diferentes valores en cada banco
rom_data = bytearray(64 * 1024)
for i in range(0x4000, 0x8000):
rom_data[i] = 0x11 # Banco 1
for i in range(0x8000, 0xC000):
rom_data[i] = 0x22 # Banco 2
cartridge = Cartridge(temp_path)
# Cambiar a banco 2
cartridge.write_byte(0x2000, 2)
assert cartridge.read_byte(0x4000) == 0x22, "Debe leer del banco 2"
Ruta completa: tests/test_mbc1.py
Validación con ROM Real (Tetris DX)
ROM: Tetris DX (ROM aportada por el usuario, no distribuida)
Modo de ejecución: UI con Pygame, logging activado
Criterio de éxito: El juego debe poder acceder a bancos superiores de ROM sin crashear. Antes de esta implementación, Tetris DX solo podía acceder a los primeros 32KB y se colgaba al intentar cargar música o gráficos de bancos superiores.
Observación: Con MBC1 implementado, el juego puede cambiar de banco correctamente. Los logs muestran cambios de banco cuando el juego escribe en 0x2000-0x3FFF. El juego ya no se cuelga al intentar acceder a bancos superiores.
Resultado: verified - El juego puede acceder a todos sus bancos de ROM correctamente.
Notas legales: La ROM de Tetris DX es aportada por el usuario para pruebas locales. No se distribuye ni se enlaza en el repositorio.
Fuentes Consultadas
- Pan Docs - MBC1 Memory Bank Controller: https://gbdev.io/pandocs/MBC1.html
- Pan Docs - Memory Map: https://gbdev.io/pandocs/Memory_Map.html
Integridad Educativa
Lo que Entiendo Ahora
- Bank Switching: Técnica para acceder a memoria mayor que el espacio de direcciones disponible, dividiendo la memoria en "bancos" y cambiando dinámicamente qué banco está visible.
- MBC1: Chip en el cartucho que implementa bank switching para ROMs mayores a 32KB. Interpreta escrituras en ciertos rangos como comandos, aunque la ROM sea "Read Only".
- Banco 0 fijo: El banco 0 siempre apunta a los primeros 16KB porque contiene código crítico que debe estar siempre accesible (inicialización, vectores de interrupción).
- Quirk del banco 0: El hardware MBC1 no permite seleccionar banco 0 en la zona switchable. Si el juego intenta hacerlo, el chip le da banco 1. Esto es un comportamiento documentado del hardware real.
Lo que Falta Confirmar
- RAM Banking: El MBC1 también puede manejar RAM externa con bank switching. Por ahora, esto no está implementado. Se añadirá cuando sea necesario para cartuchos que usen RAM externa.
- Mode Select: El MBC1 tiene un modo que permite usar bits adicionales para seleccionar más bancos ROM. Por ahora, solo se implementan los 5 bits bajos (32 bancos). Se añadirá cuando sea necesario.
- Otros MBCs: Hay otros tipos de MBC (MBC2, MBC3, MBC5, etc.) con comportamientos diferentes. Se implementarán cuando sea necesario para cartuchos que los usen.
Hipótesis y Suposiciones
Comportamiento fuera de rango: Si el juego intenta leer de un banco que está fuera del tamaño de la ROM, se devuelve 0xFF. Esto es una suposición razonable basada en el comportamiento típico de hardware, pero no está completamente documentado. Se validará con tests y observación de comportamiento en ROMs reales.
Próximos Pasos
- [ ] Implementar Joypad (entrada de teclado) para poder jugar
- [ ] Implementar RAM Banking del MBC1 si es necesario para cartuchos que la usen
- [ ] Implementar Mode Select del MBC1 si es necesario para cartuchos grandes
- [ ] Considerar implementar otros tipos de MBC (MBC2, MBC3, MBC5) cuando sea necesario