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

17

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

在16位指令中,这里声称如果从第7位到第8位发生进位,则设置半进位标志,而这里则说半进位标志表示从第11位到第12位的进位。在这个Reddit主题中,似乎对此问题有些困惑,而(我听说)Game Boy CPU手册也没有任何有用的信息。

我的问题如下:

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

2
虽然我不知道答案,但也许Blargg的CPU测试ROM对你有用。它们非常全面。 - Michael
2
7到8的说法没有参考依据。11到12的说法有一些非常好的参考资料支持,但不幸的是,这些都适用于原始的Zilog Z-80实现。Gameboy的CPU采用了(部分)相同的指令集,但这并不能保证它具有相同的4位ALU。尤其是在涉及半进位被认为不相关的指令时,Sharp可能会完全以不同的方式连接半进位标志。只有真正的Gameboy才能说明问题。 - Ruud Helderman
1
@ihonen 是的,我的意思是它是由[I无法记住芯片供应商]添加的。 - old_timer
1
通常在移动堆栈指针后不会检查标志。因此,我甚至怀疑在E8操作之后C和H标志的行为是否被很好地规定 - 这意味着不同版本的CPU甚至可能以不同的方式设置这两个标志。我怀疑任何现有软件都不会在E8操作后使用这两个标志中的任何一个。因此,如果您的仿真器不模拟E8操作的标志,则不会有问题。 - Martin Rosenau
2
仅供参考,这个网站可能更适合这类问题:https://retrocomputing.stackexchange.com/。 - Lundin
显示剩余11条评论
2个回答

12
TL;DR:对于ADD SP,n,当从第3位到第4位进位时,将设置H标志。
我决定在真正的硬件上进行测试,因此我用GB-Z80汇编语言编写了一个简单的测试ROM,测试以下场景:
[SP = $000F] ADD SP,$01 [SP = $00F0] ADD SP,$10 [SP = $0FF0] ADD SP,$10 对于每种情况,我将ADD后寄存器F的值存储在内存中,稍后在屏幕上显示这些字节的第5位(即H标志)。
我在三种不同型号(Gameboy Pocket、Gameboy Color和Gameboy Advance SP)上运行了此程序,并在所有三台设备上得到了以下结果:1 0 0。因此,从第3位到第4位的进位会导致设置H标志,而从第7位到第8位或从第11位到第12位的进位则不会。
对于ADD HL,rr(其中rrBC/DE/HL/SP),情况似乎不同。基于我的测试,当从第11位到第12位进位时,H会被设置。

4
作为一名曾经的专业GBC/DMG开发者,我对你能够回答这个问题感到印象深刻和震惊 :) 但是是的,据我所记得,答案是3到4位,虽然我不能回忆起曾经使用过它。 - Michael Dorgan

9
几乎可以肯定 Game Boy 中使用的 SM83 CPU 核心具有 8 位 ALU,这意味着 16 位 ALU 操作实际上由两个 8 位操作组成。与普通的 Z80 CPU 一样,它还有一个专门的 16 位递增/递减/加载单元,可以快速处理某些 16 位操作,但无法更新标志位。基本上:
- 如果更新了标志位,则 16 位操作一定涉及 ALU,因此在底层实际上使用了两个 8 位 ALU 操作 - 如果未更新标志位,并且 16 位操作仅为 +1/-1/load,则使用 16 位递增器单元
因此,每当你涉及标志位时,如果想考虑操作,请尝试以 8 位操作(低字节优先,然后是高字节)为基础进行推理。
问答:
1. 在操作码 0xE8 中,半进位标志如何行为?
正如其他答案所指出的那样,当从位 3 中存在进位时,H 被设置。(当从位 7 中存在进位时,C 被设置)
这是一个有趣的思维练习:如果 SP=$FFFF 并执行 ADD SP, -1,则会得到 SP=$FFFE同时设置 H 和 C. 你能理解为什么会这样吗?
提示 ↑
2. 操作码 0xE8 在物理硬件中如何实现?
我们尚未完全理解它在最低可能级别上的工作原理,但我知道有两个 8 位操作。通过我的 Game Boy 测试系统,我确认首先进行了更新标志(H,C)但不更新 SP 的 ALU 操作,然后进行另一些操作,最后 SP 一次性更新。这表明 ADD SP, e 可能实际上会在两个分离的 8 位操作中将结果计算到某个临时寄存器中(例如,真正的 Z80 对于某些 ALU 操作具有不可见的 WZ 临时寄存器),然后从中加载 SP。
我认为 ADD HL, BC 是一个更有趣的例子…通过我的测试系统,我确认它首先更新 L,然后是 H,并且标志位被更新两次。这意味着它实际上执行了类似以下内容的操作:
ADD L, C
ADC H, B

后面的8位操作更新标志寄存器,因此我们看不到“ADD L, C”的结果标志。但是如果从L的第3位进位,那么半进位标志可能暂时被设置!
3. 半进位是从第7位到第8位还是从第11位到第12位(在16位指令的情况下)?
答案取决于指令,但是如果你按照8位值来计算,标志总是基于相同的位位置进行更新...只是根据16位值的高字节或低字节而变化。第11位就是高字节的第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位递增/递减单元执行) - DEC rr:没有标志更新(由16位递增/递减单元执行)

Game Boy几乎肯定有一个8位ALU,它有一个4位ALU。 - GabrielOshiro
@GabrielOshiro请提供任何证明此说法的来源;我只能找到一些与Zilog Z80有关的内容,没有与Sharp LR35902有关的。二进制兼容性并不一定意味着完全相同的架构;请记住,在1976年至1989年之间,晶体管和微码之间的权衡逐渐发生了变化。 - Ruud Helderman
2
哦,兄弟,很久没碰了......不过我曾经研究过z80和dmg CPU的逆向工程。我会去找实际参考资料的,但现在请把我的评论视为废话(或个人启示)吧。 - GabrielOshiro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接