Game boy模拟器:内存池

Posted 妇男主人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Game boy模拟器:内存池相关的知识,希望对你有一定的参考价值。

到目前为止,在本系列中,我们一直在处理GameBoy的简单内存映射的加载和仿真,整个游戏 ROM 都适合内存的下半部分。完全适合内存的游戏并不多(俄罗斯方块是少数游戏之一);大多数游戏都比这大,并且必须采用独立机制将游戏 ROM 的“银行”交换到 GameBoy CPU 的视图中。

GameBoy 库中的一些第一批游戏是在卡带内使用内存库控制器构建的,它完成了将 ROM 库交换到视图中的工作;一直以来,各种版本的卡带 MBC 都是为越来越大的游戏而设计的。在与此部分相关的演示的特定示例中,MBC 的第一个版本用于处理 64kB ROM 的加载。

内存池与延展

多年来,许多计算机系统不得不处理程序过多而无法装入内存的问题。传统上,有两种方法可以解决这个问题。

  • 增加地址空间:构建具有更多地址线的新 CPU,使其能够查看和理解更大的内存量。这是首选的解决方案,但需要大量时间来重新开发有问题的计算机系统,并且可能需要对 CPU 的支持芯片组进行更多更改。
  • 虚拟内存:这可以指在磁盘上保存大块 RAM,并在需要时进行交换;或者在需要时交换预先写入的 ROM 块。在这两种情况下,系统硬件几乎不需要扩展,但系统的任何软件都必须知道寻呼/银行系统,以便使用它。

由于GameBoy是一个固定的硬件平台,分布广泛,在制作较大的游戏时没有办法增加地址空间;取而代之的是,内置在卡带中的内存库控制器提供了一种将 16kB ROM 库切换到视图中的方法。除此之外,MBC1 支持高达 32kB 的“外部 RAM”,这是盒式存储器中的可写存储器;[A000-BFFF]如果可用,这可以存入内存映射中的空间。

为了方便软件使用MBC1,ROM的第一个16kB bank(bank 0)被固定在地址处0000;ROM 空间的后半部分可以在 1 到 127 之间的任何 ROM 组上制作一个窗口,最大 ROM 大小为 2048kB。MBC1 的一个奇怪之处在于它在 32 位内部进行交易:银行 #32、#64 和 #96 无法访问,因为它们在银行系统内被视为银行 #0。这意味着除了固定 bank #0 之外还有 125 个 bank 可用。

MBC1 芯片中有四个寄存器,可以切换 ROM 和 RAM 的存储体;这些可以通过写入特定范围内的(通常是只读的)ROM 空间来更改。详细信息如下表所示。

MBC1 寄存器组

LocationsRegisterDetails
0000-1FFFEnable external RAM4 bits wide; value of
0x0A enables RAM,
any other value disables
2000-3FFFROM bank (low 5 bits)Switch between banks 1-31 (value 0 is seen as 1)
4000-5FFFROM bank (high 2 bits)
RAM bank
ROM mode: switch ROM bank "set" {1-31}-{97-127}
RAM mode: switch RAM bank 0-3
6000-7FFFMode0: ROM mode (no RAM banks, up to 2MB ROM)
1: RAM mode (4 RAM banks, up to 512kB ROM)

MBCs and the cartridge header

由于有多种用于内存池业务的控制器,任何给定的游戏都必须在卡带头数据中说明使用的是哪个 MBC。这是盒式 ROM 中的第一个数据块,并遵循特定格式。

Cartridge header format

Location(s)ValueSize
(bytes)
Details
0100-0103hEntry point4Where the game starts
Usually "NOP; JP 0150h"
0104-0133hNintendo logo48Used by the Bios to verify checksum
0134-0143hTitle16Uppercase, padded with 0
0144-0145hPublisher2Used by newer GameBoy games
0146hSuper GameBoy flag1Value of 3 indicates SGB support
0147hCartridge type1MBC type/extras
0148hROM size1Usually between 0 and 7
Size = 32kB << [0148h]
0149hRAM size1Size of external RAM
014AhDestination10 for Japan market, 1 otherwise
014BhPublisher1Used by older GameBoy games
014ChROM version1Version of the game, usually 0
014DhHeader checksum1Checked by BIOS before loading
014E-014FhGlobal checksum2Simple summation, not checked
0150hStart of game

在这种特殊情况下,我们0147h对cartridge 类型的值感兴趣。如果cartridge 安装了 MBC1,cartridge 类型可以是以下值之一:

与 MBC1 相关的 Cartridge 类型值

ValueDefinition
00hNo MBC
01hMBC1
02hMBC1 with external RAM
03hMBC1 with battery-backed external RAM

就本文而言,不会为外部 RAM 实施电池后备系统;这个特性经常被游戏用来保存它们的状态以备后用,我们将在后面的部分更详细地介绍。

MBC1的实现

存储库控制器显然是对内存的一种操作,因此非常适合 MMU。由于第一个 ROM 组(组 #0)是固定的,因此只需要为 MBC 维护一个偏移量,以指示第二个组的读取位置。为了允许稍后添加更多 MBC 处理,可以使用一组数据来保存给定控制器的状态:

MMU.js:MBC 状态和重置

MMU = {
    // MBC states
    _mbc: [],

    // Offset for second ROM bank
    _romoffs: 0x4000,

    // Offset for RAM bank
    _ramoffs: 0x0000,

    // Copy of the ROM's cartridge-type value
    _carttype: 0,

    reset: function()
    {
        ...

		// In addition to previous reset code,
		// initialise MBC internal data
		MMU._mbc[0] = {};
		MMU._mbc[1] = {
		    rombank: 0,		// Selected ROM bank
		    rambank: 0,		// Selected RAM bank
		    ramon: 0,		// RAM enable switch
		    mode: 0		// ROM/RAM expansion mode
		};
	
		MMU._romoffs = 0x4000;
		MMU._ramoffs = 0x0000;
    },

    load: function(file)
    {
        ...
	MMU._carttype = MMU._rom.charCodeAt(0x0147);
    }
}

从上面的代码中可以看出,MBC1 的四个寄存器的内部状态由 MMU 内的一个对象表示,与 MBC 类型 1 相关联。当这些改变时,可以修改 ROM 和 RAM 偏移量以指向适当的记忆库;一旦设置了指针,对内存的访问几乎可以照常进行。

MMU.js:基于 MBC1 的访问

MMU = {
    rb: function(addr)
    {
    	switch(addr & 0xF000)
		{
		    ...
	
		    // ROM (switched bank)
		    case 0x4000:
		    case 0x5000:
		    case 0x6000:
		    case 0x7000:
		        return MMU._rom.charCodeAt(MMU._romoffs +
			                           (addr & 0x3FFF));
	
		    // External RAM
		    case 0xA000:
		    case 0xB000:
		        return MMU._eram[MMU._ramoffs +
			                 (addr & 0x1FFF)];
		}
    }
};

这些指针偏移量的计算是在写入 MBC 寄存器时进行的,如下所示。

MMU.js:MBC1控制器

    wb: function(addr, val)
    {
        switch(addr & 0xF000)
		{
		    // MBC1: External RAM switch
		    case 0x0000:
		    case 0x1000:
		        switch(MMU._carttype)
				{
				    case 2:
				    case 3:
					MMU._mbc[1].ramon =
					    ((val & 0x0F) == 0x0A) ? 1 : 0;
					break;
				}
			break;
	
		    // MBC1: ROM bank
		    case 0x2000:
		    case 0x3000:
		        switch(MMU._carttype)
				{
				    case 1:
				    case 2:
				    case 3:
				        // Set lower 5 bits of ROM bank (skipping #0)
					val &= 0x1F;
					if(!val) val = 1;
					MMU._mbc[1].rombank =
					    (MMU._mbc[1].rombank & 0x60) + val;
		
					// Calculate ROM offset from bank
					MMU._romoffs = MMU._mbc[1].rombank * 0x4000;
					break;
				}
				break;
	
		    // MBC1: RAM bank
		    case 0x4000:
		    case 0x5000:
		        switch(MMU._carttype)
				{
				    case 1:
				    case 2:
				    case 3:
				    	if(MMU._mbc[1].mode)
					{
					    // RAM mode: Set bank
					    MMU._mbc[1].rambank = val & 3;
					    MMU._ramoffs = MMU._mbc[1].rambank * 0x2000;
					}
					else
					{
					    // ROM mode: Set high bits of bank
					    MMU._mbc[1].rombank =
					    	(MMU._mbc[1].rombank & 0x1F) +
						((val & 3) << 5);
					
					    MMU._romoffs = MMU._mbc[1].rombank * 0x4000;
					}
					break;
				}
				break;
	
		    // MBC1: Mode switch
		    case 0x6000:
		    case 0x7000:
		        switch(MMU._carttype)
				{
				    case 2:
				    case 3:
				    	MMU._mbc[1].mode = val & 1;
					break;
				}
				break;
	
		    ...
	
		    // External RAM
		    case 0xA000:
		    case 0xB000:
		        MMU._eram[MMU._ramoffs + (addr & 0x1FFF)] = val;
			break;
		}
    }

在上面的控制代码中,声明为附加了外部 RAM 的 MBC1 实例是具有 RAM 存储区的实例。有了这段代码,图 1 所示的演示就可以正常加载并运行了;如果没有 MBC1 处理程序,代码将在尝试访问显示的精灵和背景数据时崩溃。

接下来

除了能够将更大的游戏放入内存之外,游戏更重要的方面之一是保持时间的能力:例如,基于时钟的游戏如果没有某种计时机制作为其时钟的基础就毫无用处. 如前所述,很多游戏都使用垂直消隐中断来做这个计时,但有些游戏需要更细粒度的时间结构;这是由 GameBoy 中的硬件计时器提供的,与 CPU 时钟相关联。

计时器还提供了一种检查 CPU 时钟的方法,这使它可用作随机数生成器的种子;例如,俄罗斯方块使用硬件定时器的这个功能来挑选它的块。在下一部分中,我将详细了解计时器的工作原理以及它的实现方式。

以上是关于Game boy模拟器:内存池的主要内容,如果未能解决你的问题,请参考以下文章

Game boy模拟器:CPU

Game boy模拟器:精灵

Game boy模拟器:GPU的时序

Game boy模拟器:图形

Game boy模拟器:完整的 Z80 内核CPU源码

Game boy模拟器:输入