浅谈 Gameboy 的 MBC1 卡带的模拟实现

Posted on Jul 20, 2024

背景

MBC(Multiple Bank Controller), 是 gamboy 游戏卡带上的一个控制芯片,旨在于实现在运行过程中对同一片内存地址的不同空间映射。

pandoc 的内存分布我们可以得出, $0000-$7FFF$A000-$BFFF 为卡带的内存地址范围。这些地址范围总共只有 32+8=40k 的空间,而 $0000-$7FFF 通常为 rom 的地址范围,也就是游戏本体的大小,而$A000-$BFFF 为游戏数据的存储范围(通常用于游戏进度的保存)。

对于简单的游戏,32k 的大小已经足够了,对于更大玩法更丰富的游戏来说,32k是远远不够的,为了解决这个问题,有几种办法:

  1. 扩大地址空间,Gameboy 只有 16 位地址空间。但是这样会增加硬件成本。
  2. 将更多的地址空间交由游戏卡带使用。
  3. 使用“虚拟内存”

所以,上古先辈选择了第三种方案,把不同的空间通过一些奇怪的方式映射到相应的地址上,达到 1+1>2 的效果,不得不感慨那个年代的设计和制作是如此的优秀,都是一个bit一个bit的抠,不能有丝毫的浪费。

MBC1 的寄存器

MBC1 一共有 4 个寄存器,分别为:

地址空间 名称 备注
$0000-$1FFF RAM Enable 1位,控制是否启用 ram
$2000-$3FFF ROM Bank 5位,控制使用第几个 rom bank
$4000-$5FFF RAM Bank 2位,控制使用第几个 ram bank
$6000-$7FFF Bank Mode 1位,bank 模式,分为 simple(默认)和 advance 两种,有些文章也称为 0/1 模式

写入上述地址范围内任意一个地址将会写入指定的寄存器。

$0000-$1FFF RAM Enable

对于写入该范围的值,低 4 位为 0xA 都会启动 RAM.

如果 RAM 关闭,任何对 RAM 的读取将会返回 0xFF

$2000-$3FFF ROM Bank

对于该范围写入的值,取低 5 位,且根据 header 中 ROM 的大小,还需要忽略一定的高位,规则如下

header[0x0148] ROM size ROM bank 个数 掩码
$00 32 KiB 2 (no banking) 0x0
$01 64 KiB 4 0x3
$02 128 KiB 8 0x7
$03 256 KiB 16 0xF
$04 512 KiB 32 0x1F
$05 1 MiB 64 0x1F
$06 2 MiB 128 0x1F
$07 4 MiB 256 0x1F
$08 8 MiB 512 0x1F
$52 1.1 MiB 72 0x1F
$53 1.2 MiB 80 0x1F
$54 1.5 MiB 96 0x1F

由于该寄存器只有 5 位,所以最多只能 5 位为 1(0x1F)。而高于 5 位的,将会根据 Bank Mode 的模式,将 RAM bank 的两位纳入计算。

而在实际的模拟实现中,由于我们需要取有效地址,所以 5 位的掩码是不够的,我们可以通过仔细观察上面的表格,得出这个公式计算出有效位的掩码 (1 << (header[0x0148] + 1)) - 1 ,或者通过 打表/if else/swich 的方式得出。

$4000-$5FFF RAM Bank

对于该范围写入的值,取低 2 位

同样的,和 ROM Bank 一样,也需要根据 header 中的大小忽略高位,规则如下

header[0x0149] SRAM size Comment 掩码
$00 0 No RAM 0
$01 2 KiB 0
$02 8 KiB 1 bank 0
$03 32 KiB 4 banks of 8 KiB each 0x3
$04 128 KiB 16 banks of 8 KiB each 0xF
$05 64 KiB 8 banks of 8 KiB each 0x7

$6000-$7FFF Bank Mode

对于该范围写入的值,将控制卡带的工作模式 0 为 simple, 1 为 advance

RAM 的写入

$A000-$BFFF External RAM

对于该地址范围的写入将会写到相应的 RAM bank 中,如果 RAM enable 未启用,写入将被忽略。

否则:

  1. Simple Mode:

    则无需计算地址,直接写入即可

  2. Advance Mode:

    需要用以下公式计算地址 (RAM_BANK & RAM_BANK_MASK) * 0x2000 + (ADDRESS - 0xA000)

    其中,RAM_BANK$4000-$5FFF 寄存器的值,RAM_BANK_MASK 为上述表格中提到的掩码。

MBC1 中的内存

对于 ROM 的数据,为只读

$0000-$3FFF ROM Bank 0

对于该区间内存的读取,受到 Bank Mode 的控制。

Simple Mode

直接返回读取地址的内容

Advance Mode

使用 RAM Bank 作为 ROM bank 根据公式计算地址:

((RAM_BANK << 5) & ROM_BANK_MASK) * 0x4000 + ADDRESS)

$4000-$7FFF

这个地址范围不受 Bank Mode 控制

使用 RAM Bank 和 ROM Bank 共同计算出地址

(((RAM_BANK << 5) | ROM_BANK) & ROM_BANK_MASK)) * 0x4000 + (ADDRESS - 0x4000)

由于 ROM_BANk 不能为 0,所以在该位置读取的地址永远不可能为 (0x20, 0x40, 0x60 和 0x00)。如果需要读取 0x20/0x40/0x60/0x00,可以设置 Advance 模式,从 $0000-$3FFF 处读取。

$A000-$BFFF

如果没有开启 RAM 读写,那么返回 0xFF。

否则将受到 Bank Mode 的控制

Simple

按地址直接返回即可

Advance

需要根据公式计算地址

(RAM_BANK & RAM_BANK_MASK) * 0x2000 + (ADDRESS - 0xA000)

参考资料

https://gbdev.io/pandocs/MBC1.html

https://hacktix.github.io/GBEDG/mbcs/mbc1/