6502和小端转换

6502 and little-endian conversion

为了好玩,我正在实现一个 NES 模拟器。我目前正在阅读 6502 CPU 的文档,但我有点困惑。

我看到文档说因为 6502 是小端,所以当使用绝对寻址模式时你需要交换字节。我在一台也是小端的 x86 机器上写这个,所以我不明白为什么我不能简单地转换为 uint16_t*,取消引用它,然后让编译器计算出细节。

我在 google 测试中写了一些简单的测试,他们似乎同意我的看法。

// implementation of READ16
#define READ16(addr) (*(uint16_t*)addr)

TEST(MemMacro, READ16) {
  uint8_t arr[] = {0xFF,0xCC};
  uint8_t *mem = (&arr[0]);

  EXPECT_EQ(0xCCFF, READ16(mem));
}

这通过了,看来我的假设是正确的,但我想我应该问问比我更有经验的人。

这样在6502绝对寻址方式下取出操作数是否正确?我可能遗漏了什么吗?

它适用于 little-endian 系统上的简单情况,但是当相应的可移植实现很简单时,将您的实现绑定到那些感觉上是不必要的。坚持宏,你可以这样做:

#define READ16(addr) (addr[0] + (addr[1] << 8))

(为了迂腐,你还应该确保 addr[1] 不能越界,如果 addr 可能是一个复杂的,则需要添加更多括号表达式。)

但是,随着您不断开发模拟器,您会发现最自然的做法是使用一对对单个字节进行操作的通用 read_mem()write_mem() 函数。请记住,地址 space 被分成多个区域(RAM、ROM 以及来自 PPU 和 APU 的内存映射寄存器),例如您索引到的单个数组将无法正常工作。内存区域可以被映射器重新映射的事实也使事情变得复杂。 (不过,对于简单的游戏,您不必担心这一点——我建议从大金刚开始。)

你需要做的是在你的read_mem()write_mem()函数中找出地址属于哪个区域或内存映射寄存器(这称为地址解码), 并为地址做正确的事情。

回到最初的问题,事实上你最终会使用 read_mem() 来读取地址的各个字节,这意味着 uint16_t 转换技巧更不可能是有用。这是最简单和最可靠的方法 w.r.t。处理极端情况,以及我见过的每个模拟器在实践中的作用(Nestopia、Nintendulator 和 FCEUX)。

万一您错过了,EFNet 上的 #nesdev 频道非常活跃,顺便说一句,这是一个很好的资源。我假设您已经熟悉 NESDev wiki。 :)

我也一直在研究可以找到的模拟器 here