Game Boy:半进位标志和 16 位指令(尤其是操作码 0xE8)

Game Boy: Half-carry flag and 16-bit instructions (especially opcode 0xE8)

像其他许多人一样,我正在编写一个 Game Boy 模拟器,我有几个关于指令 0xE8(ADD SP, n 和 8 位立即数)的问题。

据说here that in 16-bit instructions the half-carry flag is set if a carry occurs from bit 7 to bit 8, whereas here it is said that the half-carry flag indicates carry from bit 11 to bit 12. In this Reddit thread there seems to be a bit of confusion regarding the issue, and the (notoriously flawed, I hear) Game Boy CPU manual好像也没啥好说的

我的问题如下:

  1. 半进位标志在操作码 0xE8 中的表现如何?
  2. 操作码0xE8在物理硬件中是如何实现的?
  3. 哪个是正确的,半进位发生在第 7 位到第 8 位,还是半进位发生在第 11 位到第 12 位(在 16 位指令的情况下)?

TL;DR:对于ADD SP,n,当第3位到第4位发生进位时设置H标志。


我决定在真实硬件上进行测试,所以我在 GB-Z80 汇编中编写了一个简单的测试 ROM,用于测试以下场景:

[SP = $000F]
ADD SP,

[SP = $00F0]
ADD SP,

[SP = $0FF0]
ADD SP,

对于每种情况,我将寄存器 F 的值存储在内存中的 ADD 之后,然后我在屏幕上显示每个字节的第 5 位(H 标志)。

我 运行 在 3 种不同的型号(Gameboy Pocket、Gameboy Color 和 Gameboy Advance SP)上执行此操作,并在所有 3 台设备上获得以下输出:1 0 0。因此,位 3->4 的进位导致 H 被置位,而位 7->8 或 11->12 的进位则没有。


对于 ADD HL,rr(其中 rrBC/DE/HL/SP)它似乎是一个不同的故事。根据我的测试,如果从第 11 位到第 12 位发生进位,则设置 H。

Game Boy使用的SM83CPU核心几乎肯定有一个8位的ALU,也就是说16位的ALU运算实际上是由两个8位的运算组合而成的。与普通的 Z80 CPU 一样,它也有一个专用的 16 位 increment/decrement/load 单元,可以快速处理某些 16 位操作,但不能更新标志。基本上:

  • 如果flags更新了,一个16位的运算肯定涉及到ALU,所以它实际上使用了两个8位的ALU运算
  • 如果不更新flags,16位运算只是+1/-1/load,用16位增量单元完成

因此,无论何时处理标志,如果您想对操作进行推理,请尝试从 8 位操作的角度考虑(低字节在前,然后是高字节)。

  1. How does the half-carry flag behave in opcode 0xE8?

正如另一个答案中所指出的,当第 3 位有进位时设置 H。(当第 7 位有进位时设置 C)。

这是一个有趣的思考练习:如果 SP=$FFFF 并且您执行 ADD SP, -1,您将得到 SP=$FFFE 并且 H 和 C 都已设置 .你能明白为什么吗?

由于有符号数的工作原理,低字节操作在这种情况下基本上只是一个普通的加法。 -1 = $FF, 所以它正在计算 $FF+ $FF.

以上提示↑

  1. How is the opcode 0xE8 implemented in the physical hardware?

我们还没有在尽可能低的层次上完全了解它,但我知道有两个 8 位操作。通过我的 Game Boy 测试台系统,我已经确认首先有一个 ALU 操作更新标志(H,C)但不更新 SP,然后是其他一些操作,最后 SP 一次自动更新。这表明 ADD SP, e 可能实际上在两个单独的 8 位操作中将结果计算到某个临时寄存器(例如,真正的 Z80 有一个不可见的 WZ 临时寄存器用于某些 ALU 操作),然后从中加载 SP。

我认为 ADD HL, BC 是一个更有趣的例子...在我的测试台上我已经确认它先更新 L 然后更新 H,并且 标志被更新两次 .这意味着它实际上执行的是

ADD L, C
ADC H, B

后面的 8 位操作更新标志,因此我们永远看不到 ADD L, C 的结果标志。但是如果 L 位 3 有进位,可能会临时设置半进位标志!

  1. Which is right, that half-carry occurs from bit 7 to bit 8 or that half-carry occurs from bit 11 to bit 12 (in the case of 16-bit instructions)?

这取决于指令,但是如果您从 8 位值的角度考虑,标志总是基于相同的位位置更新...只是我们谈论的是高字节还是低字节而已的 16 位值。 Bit 11 就是高字节的 bit 3。

  • ADD SP, e:H 来自第 3 位,C 来自第 7 位(来自低字节操作的标志)
  • LD HL, SP+e:H 来自第 3 位,C 来自第 7 位(来自低字节操作的标志)
  • ADD HL, rr:H 来自第 11 位,C 来自第 15 位(来自高字节操作的标志)
  • INC rr:没有标志更新(由16位inc/dec单元执行)
  • DEC rr:没有标志更新(由16位inc/dec单元执行)