1. 认识nes文件
我们既然是模拟,就不可能使用实体的卡带硬件。那我们如何获取游戏文件呢?好在已经有人为我们准备好了(心怀感恩)。
.nes文件是NES(FC)的rom文件,关于它的来龙去脉这里就不做详细介绍了(我也不知道)。.nes有1.0和2.0之分,简单起见 这里只考虑1.0。
NES文件由 Header、Trainer、PRG ROM、CHR ROM 4个部分组成。
- Header 大小:16字节,用于ROM文件的校验,记录PRG和CHR大小等作用。
- Trainer,这个区域很多时候并不存在,也基本不影响运行。因此我们忽略此部分。
- PRG ROM 是程序的可执行部分,也就是程序的“代码部分”。将来CPU就是读取这个区域的内容,再执行。PRG ROM由若干个16KB的“块”组成,块的数量在Header中记录。通常至少有1个PRG ROM。
- CHR ROM 是ROM的图像数据,将来PPU会读取这部分的内容,并拼合成最终可显示的图像数据。这部分同样是由若干块组成,只是块的大小为8KB。块的数量在Header中记录。数量可能为0。
rom文件具体分布:
2. Mapper与内存映射
2.1 内存映射
在弄清楚什么是mapper之前,我们先来看看什么是内存映射。FC的CPU是8BIT处理器,但是地址总线却是16BIT的,这就意味着CPU的寻址范围是0x0000~0xFFFF,也就是最大能寻址64KB。尽管64KB已经非常小了,但是在那个年代,即便是64KB的内存也十分昂贵。FC内实际可自由使用的内存仅为2KB,而多余的地址空间就分别映射到了不同的外设上面。(有单片机经验或者操作系统底层开发经验的朋友应该很熟悉这种做法)。
FC内部的两个重要部件CPU和PPU都有自己独立的地址总线,因此CPU和PPU分别有不同的内存映射方案。
地址范围 | 大小 | 所映射的设备 |
---|---|---|
0x0000-0x07FF | 2 KB | 内置的2KB内存 |
0x0800-0x1FFF | 6 KB | 0x0000-0x07FF的镜像 |
0x2000-0x2007 | 8 byte | PPU寄存器 |
0x2008-0x3FFF | 0x2000-0x2007的镜像 | |
0x4000-0x4017 | APU和I/O寄存器 | |
0x4018-0x401F | 暂时忽略 | |
0x4020-0x5FFF | 暂时忽略 | |
0x6000-0x7FFF | 8 KB | PRG RAM |
0x8000-0xBFFF | 16KB | 带空间前16KB PRG ROM |
0xC000-0xFFFF | 16KB | 带空间后16KB PRG ROM |
从上图可以看到0x8000~0xFFFF的地址空间被映射到了PRG ROM,因此CPU读取0x8000~0xFFFF的时候实际上访问的就是rom中的PRG ROM内容。这块空间总计32KB,而当ROM中只有1个PRG ROM时,大小仅为16KB。这个时候0xC000~0xFFFF的地址空间就是0x8000-0xBFFF的镜像。
Pattern Tables | 0x0000-0x0FFF | Pattern Table 0 |
0x1000-0x1FFF | Pattern Table 1 | |
Name Tables | 0x2000-0x23BF | Name Table 0 |
0x23C0-0x23FF | Attribute Table 0 | |
0x2400-0x27BF | Name Table 1 | |
0x27C0-0x27FF | Attribute Table 1 | |
0x2800-0x2BBF | Name Table 2 | |
0x2BC0-0x2BFF | Attribute Table 2 | |
0x2C00-0x2FBF | Name Table 3 | |
0x2FC0-0x2FFF | Attribute Table 3 | |
Palettes | 0x3F00-0x3F0F | Image Palette |
0x3F10-0x3F1F | Sprite Palette | |
0x0000 - 0x3FFF的镜像 | 0x0000 - 0x3FFF的镜像 |
PPU的内存映射主要分为图样表(Pattern Tables)、命名表(Name Tables)和调色板(Palettes)。其中 图样表分为两个小区块,每个4KB,总共8K。实际上就是ROM中CHR ROM的内容。
总结下来,CPU的地址空间0x8000~0xFFFF因映射到了PRG ROM ,而PPU的地址空间0x0000-0x1FFF则映射到了CHR ROM。因此我们在稍后的实现代码中,需要预留出相关接口。
您也许会疑惑为什么内存映射表还有那么多部分没有讲到?这个不用担心,我们再讲到CPU和PPU章节的时候,会更为详细的介绍。而我们现在不需要关心这些。
2.2 那什么是Mapper?
我们通过前面的小节已经了解到了什么是地址映射,但是我们也知道,映射到PRG ROM的地址空间大小只有32KB,映射到CHR ROM的则更小,仅为8KB。很多游戏想要实现比较复杂多样的显示效果这远远不够。那怎么办呢?厂商在开发卡带的会把PRG ROM和CHR ROM分成不同大小的空间区块(BANK)。
例如:
某块游戏以16KB区块为大小划分PRG ROM,一共10个。那就是160KB的程序空间! 但是地址空间只有32KB啊?Mapper的作用就出现了。他可以控制地址空间到底映射到那个BANK,然后程序可以通过读写特定的地址切换。比如我默认将第0和1 BANK映射到0x8000-0xFFFF。运行过程中再切换为第5、6 BANK映射到0x8000-0xFFFF。这样就能在有限的地址空间内实现了存储空间的扩容。而且这部分由游戏卡带实现,因此成本不必计算在游戏机本体内。
不过官方(nes1.0)支持256种不同的Mapper,不同的Mapper映射方法和区块划分都不同,有的还支持中断甚至额外的”声卡“。我们一开始就实现这些太复杂了。因此我们只实现基本的Mapper功能用来测试。后面Mapper进阶我们再实现更高级的功能。
(未完待续)
小贴士:
因为本章较长,因此分了“原理”和“实现”两部分。实现部分在下次更新哦~