【问题标题】:Emulation: Unconditional jumps and PC increment through CPU cycles仿真:无条件跳转和 PC 通过 CPU 周期递增
【发布时间】:2012-05-15 22:40:20
【问题描述】:

我正在编写一个简单的 GB 模拟器(哇,现在这是新的东西,不是吗),因为我真的在 emu 中迈出了第一步。

我似乎不明白的是如何正确实现CPU周期和无条件跳转。

如果我有一个基本循环,请考虑命令 JP nn(指出的无条件跳转到内存地址),例如 JP 1000h:

increment PC
read opcode
execute command

然后在读取 JP 操作码并执行命令后(从内存中读取 1000h 并设置 PC = 1000h),PC 会递增并变为 1001h,从而导致仿真不良。

tl;dr 当 CPU 循环增加 PC 时,您如何模拟模拟器中的跳转,以使 PC 值保持正确?

【问题讨论】:

  • 您是否查看过其他模拟器以了解使用的不同方法?

标签: emulation gameboy


【解决方案1】:

每次用于返回字节时,PC 都应作为“原子”操作递增。这意味着立即操作数和操作码。

在您的示例中,PC 将被使用 3 次,一次用于操作码,两次用于两个操作数字节。当 CPU 获取三个字节并可以加载 PC 时,PC 已经指向第二个操作数之后的下一条指令操作码,但是,由于实际执行该指令会重新加载 PC,所以没关系.

【讨论】:

  • 是的,这解释了整个“将增量 PC 置于循环中间”背后的逻辑。谢谢你,先生。非常感谢。
  • @joncys 事实上,这就是微处理器过去在内部工作的方式。 (如今,现代微处理器可以做各种各样的事情,但仍然必须将原始行为作为抽象来公开。)
  • @Neil 是在第一个规范中专门为这些类型的情况设计的,还是作为布线(硬件)的“副作用”(这里使用非常松散的术语),证明有用吗?
  • @joncys 我不知道,但*p++ 是 C 语言中的一种常见模式,所以如果它不是设计出来的,我会感到惊讶。
  • 在我看来,如果不是硬件强加给你的话,这听起来像是一种可怕的编码方式。当您实现类似相对跳转(需要在操作码执行期间读取 PC)时会发生什么?一个操作应该要么有副作用返回一个值,而不是两者都有的经验法则发生了什么?
【解决方案2】:

increment PC 移动到循环的末尾,并根据操作码有条件地执行吗?

【讨论】:

  • 哇,这太尴尬了。我不能按照你建议的方式去做,因为我正在编写一个更复杂的解释器,而不仅仅是 switch - 所有操作码的大小写,即便如此 - 两个 switch - case 语句将是多余的(必须检查这是否是一个 JP 命令第二次)。不过,我可以做的是,在循环中间增加 PC,这样操作码在 INC PC 之前被读取——这解决了跳转问题,而不会破坏当前循环实现中的任何内容。请更新您的答案以反映这一点,以便我接受。感谢您的帮助!
【解决方案3】:

我对仿真几乎一无所知,但我想到了两种明显的方法。

  1. 不要将PC += 1 硬编码到主循环中,而是让评估是否每个操作码返回下一个 PC 值(或偏移量,或表示是否增加它的标志等)。然后跳转和其他操作码(它们对程序计数器的影响)之间的区别以及关于它们的所有其他内容都是可以定义的。

  2. 知道主循环总是将 PC 加 1,只需执行跳转将 PC 设置为 target - 1 而不是 target

【讨论】:

  • 我想到了这两种方法,我只是在直觉中知道必须有一种经典的方法来解决所有情况。我想到,在阅读 Oli 的答案后,您可以将 inc PC 置于循环的中间。感谢您的洞察力,不胜感激。
  • 对我来说,1. 是解决问题的“经典”方式,2 是稍微不那么经典但实用且节省打字的方法。我希望将由执行哪个操作码确定的所有内容封装在每个操作码的单个定义中; “PC += 1”确实特定于每个操作码的东西,您遇到这个问题的事实证明了这一点。根据操作码有条件地执行它意味着您的跳转代码的实现分布在多个地方,如果您添加另一个跳转并忘记更新主循环,就会出错。
  • 方法 2 通常(大多数处理器)不需要任何调整,相对分支假定 pc 已递增。一个无限循环,一个分支到它自身的指令是一条 pc = pc - 1 指令,如果 pc 已知在执行时领先一个,或者 pc = pc -2 如果已知领先两个。 1)要求每条指令都有一个 pc 修饰符,更多的打字。 2)只有奇怪的情况,如果有的话,在实现中会有一个 pc 修饰符。而不是 *pc。而不是单个 *pc++ 替换为 *pc 和几百个 pc++;线? 1) 不少打字。
  • 大多数处理器的 1) 方法实际上是您的答案 2) 同时通过删除使用时的增量,您必须在所有地方添加 pc 修饰符。目标 = pc+1+offset 而不是 target = pc+offset。对于 gb 中修改后的 z80,200+ 条指令中的大多数都不使用 pc,因此您不需要所有这些 pc++;代码行把它放在首位。 some/many 是可变字长,只有少数使用 pc 作为计算地址的操作数,对于那些你必须知道 pc 修饰符是什么的人。
  • 1) 方法虽然从程序员的角度来看,更多的输入可能更理智,但代码可能更容易让人们阅读和理解,因为事情只会在指令的实现中发生变化,而不是在上面和下方和各处。更具可读性可能意味着更容易调试和维护。这个论点可以是双向的,更多的行意味着更多的人为错误意味着更多的错误。
猜你喜欢
  • 1970-01-01
  • 2012-12-17
  • 2013-04-21
  • 1970-01-01
  • 1970-01-01
  • 2013-06-19
  • 1970-01-01
  • 1970-01-01
  • 2016-08-22
相关资源
最近更新 更多