在模拟CPU时,关于循环计数精度的问题

6
我计划在未来几个月内创建一个塞加大师系统模拟器,作为Java的兴趣项目(我知道这不是最好的语言,但我觉得它非常舒适易用,并且作为Windows和Linux的频繁用户,我认为跨平台应用程序非常棒)。我的问题与周期计数有关;
我查看了另一个Z80模拟器以及其他模拟器的源代码,特别是执行循环引起了我的兴趣 - 当调用它时,会将int作为参数传递(例如1000)。现在我知道每个操作码执行需要不同数量的周期,并且随着这些操作码的执行,剩余周期的数量会递减。一旦剩余周期的数量<= 0,执行循环就会结束。
我的问题是,许多这些模拟器没有考虑到最后一条指令可能会将周期数推到负值 - 这意味着在执行循环之间,可能会执行1002个周期而不是1000个周期。这个问题是否重要?一些模拟器通过在下一个执行循环中进行补偿来解决此问题,而一些则没有 - 哪种方法最好?请允许我用例子说明我的问题,因为我不太善于表达:
public void execute(int numOfCycles) 
{ //this is an execution loop method, called with 1000.
   while (numOfCycles > 0)
   {
      instruction = readInstruction();
      switch (instruction)
      {
         case 0x40: dowhatever, then decrement numOfCycles by 5;
         break; 
         //lets say for arguments sake this case is executed when numOfCycles is 3.
      }
}

在这个循环示例结束后,numOfCycles将为-2。这只会是一种小的不精确度,但在人们的整体经验中是否重要呢?我很感激任何人对这个问题的见解。我计划在每一帧之后中断CPU,因为这似乎是合适的,所以1000个周期很低,我知道,但这只是一个示例。
非常感谢, 菲尔
3个回答

5
  1. 大多数仿真器/模拟器只涉及CPU时钟滴答声

    这对于游戏等来说很好……所以你有一些计时器或其他东西,运行CPU的模拟,直到CPU模拟计时器的持续时间。然后它会睡眠,直到下一个计时器间隔发生。这非常容易模拟。您可以通过您正在询问的方法减少定时误差。但是,如此为游戏而言通常是不必要的。

    这种方法有一个显著的缺点,那就是您的代码只在实际时间的一小部分工作。如果计时器间隔(定时粒度)足够大,则即使在游戏中也可能会注意到这一点。例如,您在模拟程序睡眠时按下键盘键,则无法检测到它。(键有时不起作用)。您可以通过使用更小的定时粒度来解决此问题,但在某些平台上这很难。在这种情况下,定时误差在软件生成的声音中可能更加“明显”(至少对于那些能听到它并且不对这类事情像我这样的聋子)。

  2. 如果您需要更复杂的东西

    例如,如果您想将真实硬件连接到您的仿真/模拟中,则需要仿真/模拟总线。此外,像浮动总线系统争用这样的事情很难添加到#1方法中(虽然可行,但非常麻烦)。

    如果您将定时和仿真移植到机器周期中,事情就会变得容易得多,突然之间,诸如争用或硬件中断、漂浮总线等问题几乎可以自行解决。我将我的ZXSpectrum Z80仿真器移植到了这种定时方式,并看到了光明。许多事情变得显而易见(例如Z80操作码文档中的错误、定时等)。此外,从那里开始争用变得非常简单(只需几行代码,而不是每个指令类型条目的可怕解码表)。HW仿真也变得非常容易,我以这种方式向Z80添加了FDC控制器AY芯片仿真(没有黑客,它确实运行在它们的原始代码上……甚至软盘格式:)),所以不再需要TAPE加载黑客和不适用于自定义加载程序(例如TURBO)。

    为了使这个工作,我以一种类似于每个指令的微码的方式创建了我的Z80仿真/模拟。由于我经常纠正Z80指令集中的错误(因为我知道即使其中一些声称它们是无错误和完整的文档,也没有单个100%正确的文档),所以我想出了一种处理它的方法,而不必痛苦地重新编程仿真器。

    每个指令都由表中的一个条目表示,包含有关定时、操作数、功能等的信息……所有指令的整个指令集都是所有这些指令的表的表。然后,我为我的指令集形成了一个MySQL数据库,并为我找到

    几年前,我也发表了一篇关于这个问题的论文(可悲的是,举办该会议的机构已不存在,因此那些旧论文的服务器永久关闭了,幸运的是我还有一份副本)。这里有一张图片描述了这个问题:

    CPU Scheduling

    • a) 全油门没有同步,只有原始速度。
    • b) #1 有大间隙,导致硬件同步问题。
    • c) #2 需要睡眠很多,粒度非常小(可能会导致问题和减慢速度),但指令执行非常接近实时...
    • 红线是主机CPU的处理速度(显然以上内容需要更多时间,因此应在下一条指令之前剪切并插入,但这将很难正确绘制)。
    • 品红线是模拟CPU的处理速度。
    • 交替使用绿色/蓝色 表示下一条指令。
    • 两个轴都是时间。

    [编辑1] 更精确的图像:

    上面那个是手画的...这个由VCL/C++程序生成:

    timing

    由这些参数生成:
    const int iset[]={4,6,7,8,10,15,21,23}; // possible timings [T]
    const int n=128,m=sizeof(iset)/sizeof(iset[0]); // number of instructions to emulate, size of iset[]
    const int Tps_host=25;  // max possible simulation speed [T/s]
    const int Tps_want=10;  // wanted simulation speed [T/s]
    const int T_timer=500;  // simulation timer period [T]
    

    所以主机可以模拟所需速度的250%,模拟粒度为500T。指令是伪随机生成的...

3

最近Arstechnica上有一篇关于游戏机模拟的文章,内容相当有趣。此外,还提供了很多模拟器,可以作为研究的好素材:

Accuracy takes power: one man's 3GHz quest to build a perfect SNES emulator

作者提到了一个相关的问题,我也赞同,即使时间偏差在+/-20%以内,大多数游戏看起来都会正常运行。你提到的问题可能永远不会引入超过百分之一的时间误差,在玩最终游戏时可能根本无法察觉。作者可能认为这并不值得处理。


谢谢 :-) 几天前我看到了这篇文章,它是一篇非常有趣的阅读。看起来我可以忽略这个问题 - 我的工作已经安排好了,但应该会很有趣! :-p - PhilPotter1987

0

我想这取决于你希望你的模拟器有多准确。我认为它不必非常准确。考虑到x86平台的仿真,有如此多的处理器变体,每个处理器都有不同的执行延迟和问题率。


我认为只要将它绑定到帧速率上 - 如果我模拟PAL控制台,则为50fps - 那么在这些帧之间,如果我执行多少个周期,然后暂停,同时更新屏幕,那应该就足够了。我猜我会找出来的 :-p 感谢您的回答。 - PhilPotter1987

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