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好像也没啥好说的
我的问题如下:
- 半进位标志在操作码 0xE8 中的表现如何?
- 操作码0xE8在物理硬件中是如何实现的?
- 哪个是正确的,半进位发生在第 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
(其中 rr
是 BC/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 位操作的角度考虑(低字节在前,然后是高字节)。
- 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
.
以上提示↑
- 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 有进位,可能会临时设置半进位标志!
- 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单元执行)
像其他许多人一样,我正在编写一个 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好像也没啥好说的
我的问题如下:
- 半进位标志在操作码 0xE8 中的表现如何?
- 操作码0xE8在物理硬件中是如何实现的?
- 哪个是正确的,半进位发生在第 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
(其中 rr
是 BC/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 位操作的角度考虑(低字节在前,然后是高字节)。
- 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
.
以上提示↑
- 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 有进位,可能会临时设置半进位标志!
- 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单元执行)