⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

MBC1 and Bank Switching

Date:2025-12-17 StepID:0029 State: Verified

Summary

It was implementedMemory Bank Controller 1 (MBC1)to allow cartridges larger than 32KB work correctly. The MBC1 solves the problem that the CPU can only address 64KB, but the Games can have ROMs of 512KB or more. The solution is theBank Switching: split ROM in 16KB banks and dynamically change which bank is visible in the range 0x4000-0x7FFF. Bank 0 (0x0000-0x3FFF) always points to the first 16KB and does not change. The switchable bank is changed by writing in the range 0x2000-0x3FFF, which the MBC1 interprets as commands (even though the ROM is "Read Only"). With this implementation, Tetris DX (512KB) can access all of its ROM banks, including music and graphics that They are on higher benches.

Hardware Concept

The Game Boy has a 16-bit address space (0x0000 to 0xFFFF = 65536 bytes). However, the Games can have much larger ROMs (64KB, 128KB, 256KB, 512KB, 1MB, etc.). The problem is that the CPU It can only address 64KB at a time.

The solution is theMemory Bank Controller (MBC), a chip in the cartridge that acts as a "sliding window" over the ROM. The MBC1 divides the ROM into 16KB banks:

  • Bank 0 (Fixed): The range 0x0000-0x3FFF always points to the first 16KB of the ROM. This bank contains critical code (initialization, interrupt vectors) that must always be accessible.
  • Bank X (Switchable): The range 0x4000-0x7FFF points to the selected bank. The game you can change which bank is visible by writing to the range 0x2000-0x3FFF.

Although the ROM is "Read Only", the MBC1 interprets writes in certain ranges as commands:

  • 0x2000-0x3FFF: Select the ROM bank (low 5 bits only, 0x1F). If the game It tries to select bank 0, the MBC1 gives it bank 1 (hardware quirk).
  • 0x0000-0x1FFF: (Reserved for RAM enable, not implemented yet)
  • 0x4000-0x5FFF: (Reserved for RAM bank / ROM bank upper bits, not implemented yet)
  • 0x6000-0x7FFF: (Reserved for mode select, not implemented yet)

This "Bank Switching" technique is common in systems with limited addressing. Allows games large devices to run on hardware with addressing restrictions.

Fountain:Pan Docs - MBC1 Memory Bank Controller

Implementation

The class was modifiedCartridgeto implement MBC1 and updated theMMUfor allow writes to the ROM zone to be sent to the cartridge.

Components created/modified

  • Cartridge: Added attribute_rom_bank(initialized to 1), modifiedread_byte()to handle bank switching, addedwrite_byte()to receive MBC commands.
  • MMU: Modifiedwrite_byte()to allow writes to the ROM zone (0x0000-0x7FFF) to be sent to the cartridge.
  • Tests: Createdtest_mbc1.pywith 6 tests that validate the behavior of the MBC1.

Design decisions

Starting bank:The initial ROM bank is 1 (it cannot be 0 in a switchable zone). This reflects the actual behavior of the MBC1 hardware.

Bank 0 Quirk:If the game tries to select bank 0 by writing 0x00 to 0x2000, MBC1 gives bank 1. This behavior is documented in Pan Docs and was implemented explicitly.

Bit masking:Only the low 5 bits (0x1F) are used for bank select. This allows up to 32 banks (although some cartridges may have more using additional bits in other ranges, not implemented yet).

RAM Banking and Mode Select:For now, the ranges 0x0000-0x1FFF (RAM enable) are ignored, 0x4000-0x5FFF (RAM bank / ROM bank upper bits) and 0x6000-0x7FFF (mode select). These will be implemented when necessary for cartridges that use them.

Affected Files

  • src/memory/cartridge.py- MBC1 implementation: bank switching and MBC commands
  • src/memory/mmu.py- Modification of write_byte() to allow writes to the ROM zone
  • tests/test_mbc1.py- Complete TDD test suite for MBC1 (6 tests)

Tests and Verification

A complete suite of TDD tests was created that validates the behavior of the MBC1:

Unit tests (pytest)

  • Command executed: python3 -m pytest tests/test_mbc1.py -v
  • Around:macOS, Python 3.9.6
  • Result:6 tests PASSED in 0.29s
  • What is valid:
    • Fixed Bank 0: Bank 0 (0x0000-0x3FFF) always points to the first 16KB, regardless of which bank is selected.
    • Default bank: The switchable zone (0x4000-0x7FFF) points to bank 1 by default.
    • Bank switching: Writing to 0x2000-0x3FFF correctly changes the visible bank.
    • Bank 0 Quirk: Writing 0x00 selects bank 1 (not bank 0).
    • Bit Masking: Only the low 5 bits are used for bank select.
    • MMU Integration: The MMU allows writes to the ROM area that are sent to the cartridge.

Test code (essential fragment)

def test_mbc1_bank_switching() -> None:
    """Test: Change bank by writing to 0x2000-0x3FFF."""
    # Create 64KB dummy ROM with different values in each bank
    rom_data = bytearray(64 * 1024)
    for i in range(0x4000, 0x8000):
        rom_data[i] = 0x11 # Bank 1
    for i in range(0x8000, 0xC000):
        rom_data[i] = 0x22 # Bank 2
    
    cartridge = Cartridge(temp_path)
    
    # Switch to bank 2
    cartridge.write_byte(0x2000, 2)
    assert cartridge.read_byte(0x4000) == 0x22, "Must read from bank 2"

Full route: tests/test_mbc1.py

Validation with Real ROM (Tetris DX)

ROM:Tetris DX (user-contributed ROM, not distributed)

Execution mode:UI with Pygame, logging enabled

Success Criterion:The game must be able to access higher ROM banks without crashing. Before this implementation, Tetris DX could only access the first 32KB and would crash when trying load music or graphics from higher banks.

Observation:With MBC1 implemented, the game can switch banks correctly. The logs show bank changes when the game writes to 0x2000-0x3FFF. The game no longer crashes when trying to access higher banks.

Result: verified- The game can access all your ROM banks correctly.

Legal notes:The Tetris DX ROM is provided by the user for local testing. It is not distributed or linked in the repository.

Sources consulted

Educational Integrity

What I Understand Now

  • Bank Switching:Technique to access memory larger than the address space available, dividing memory into "banks" and dynamically changing which bank is visible.
  • MBC1:Chip in the cartridge that implements bank switching for ROMs larger than 32KB. Interprets writes in certain ranges as commands, even if the ROM is "Read Only".
  • Bank 0 fixed:Bank 0 always points to the first 16KB because it contains code critical that must always be accessible (initialization, interrupt vectors).
  • Bank 0 Quirk:The MBC1 hardware does not allow bank 0 to be selected in the zone switchable. If the game tries to do this, the chip gives bank 1. This is a normal behavior. documented of the actual hardware.

What remains to be confirmed

  • RAM Banking:The MBC1 can also handle external RAM with bank switching. By now, this is not implemented. Will be added when needed for cartridges that use RAM external.
  • ModeSelect:The MBC1 has a mode that allows additional bits to be used to select more ROM banks. For now, only the low 5 bits (32 banks) are implemented. Will be added when necessary.
  • Other MBCs:There are other types of MBC (MBC2, MBC3, MBC5, etc.) with behaviors different. They will be implemented when necessary for cartridges that use them.

Hypotheses and Assumptions

Out of range behavior:If the game tries to read from a bank that is outside the ROM size, 0xFF is returned. This is a reasonable assumption based on typical behavior hardware, but it is not fully documented. It will be validated with tests and behavioral observation in real ROMs.

Next Steps

  • [ ] Implement Joypad (keyboard input) to be able to play
  • [ ] Implement RAM Banking from MBC1 if necessary for cartridges that use it
  • [ ] Implement MBC1 Mode Select if necessary for large cartridges
  • [ ] Consider implementing other types of MBC (MBC2, MBC3, MBC5) when necessary