【问题标题】:Question about cycle counting accuracy when emulating a CPU模拟CPU时关于循环计数准确性的问题
【发布时间】:2011-10-30 16:20:01
【问题描述】:

我计划在接下来的几个月里创建一个 Sega Master System 模拟器,作为 Java 的一个业余项目(我知道它不是最好的语言,但我觉得使用它很舒服,并且作为一个Windows 和 Linux 的频繁用户,我认为跨平台应用程序会很棒)。我的问题是关于周期盘点的;

我查看了另一个 Z80 模拟器和其他模拟器的源代码,特别是执行循环让我很感兴趣 - 当它被调用时,一个 int 作为参数传递(假设 1000 作为例子)。现在我知道每个操作码需要不同数量的周期来执行,并且在执行这些操作时,周期数会从整体数字中减少。一旦剩余的循环数

我的问题是,这些仿真器中的许多都没有考虑到要执行的最后一条指令可以将周期数推为负值这一事实 - 这意味着在执行循环之间,最终可能会说,执行了 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 个周期很低,但这只是一个示例。

非常感谢, 菲尔

【问题讨论】:

    标签: emulation z80 cpu-cycles


    【解决方案1】:
    1. 大多数模拟器/模拟器只处理 CPU 时钟抖动

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

      这种方法有一个显着的缺点,那就是您的代码只运行实时的一小部分。如果计时器间隔(计时粒度)足够大,即使在游戏中也会很明显。例如,您在仿真休眠时及时按下了键盘键,则无法检测到它。 (键有时不起作用)。您可以通过使用较小的时序粒度来解决此问题,但这在某些平台上非常困难。在这种情况下,计时错误在软件生成的声音中可能更加“可见”(至少对于那些能听到它并且对像我这样的东西不耳聋的人来说)。

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

      例如,如果您想将真实硬件连接到您的仿真/仿真,那么您需要仿真/仿真 BUS'es。此外,像 floating buscontention 这样的系统很难添加到方法 #1 中(这是可行的,但很痛苦)。

      如果您将时序和仿真移植到 机器周期,事情会变得容易得多,突然之间出现争用或硬件中断等问题,浮动 BUS 几乎可以自行解决他们自己的。我将我的 ZXSpectrum Z80 仿真器移植到这种时序并看到了曙光。许多事情变得显而易见(例如 Z80 操作码文档中的错误、计时等)。此外,争用从那里变得非常简单(几乎每个指令类型条目只有几行代码,而不是可怕的解码表)。硬件仿真也很容易我以这种方式向 Z80 添加了诸如 FDC 控制器 AY 芯片仿真之类的东西(没有真正在其原始代码上运行的 hack ......甚至是软盘格式化 :))所以不再需要 TAPE 加载 hack 并且无法正常工作对于像 TURBO 这样的自定义加载器

      为了完成这项工作,我创建了 Z80 的仿​​真/模拟,它为每条指令使用了类似微代码的东西。因为我经常纠正 Z80 指令集中的错误(因为我知道没有一个 100% 正确的文档,即使其中一些声称它们没有错误且完整)我来了如何在不痛苦地重新编程模拟器的情况下处理它。

      每条指令都由表中的一个条目表示,其中包含有关时序、操作数、功能的信息...整个指令集是所有指令的所有这些条目的表。然后我为我的指令集构建了一个 MySQL 数据库。并为我找到的每个指令集形成类似的表格。然后痛苦地比较了他们所有人选择/修复什么是错的和什么是正确的。结果被导出到在仿真启动时加载的单个文本文件。这听起来很可怕,但实际上它简化了很多事情,甚至加速了仿真,因为指令解码现在只是访问指针。指令集数据文件示例可以在这里找到What's the proper implementation for hardware emulation

    几年前,我也发表了关于此的论文(遗憾的是,举办该会议的机构已不存在,因此服务器在这些旧论文上永久关闭,幸运的是我仍然有一份副本)所以这里的图片描述了问题:

    • a) 全油门没有同步,只有原始速度
    • b) #1 有很大的差距导致硬件同步问题
    • c) #2 需要以非常小的粒度进行大量睡眠(可能会出现问题并减慢速度)但是指令的执行时间非常接近它们的实时...
    • 红线是主机CPU处理速度(显然上面的需要多一点时间,所以应该在下一条指令之前剪切和插入,但很难正确绘制)
    • 洋红色线是 Emulated/Simulated CPU 处理速度
    • 交替的green/blue颜色代表下一条指令
    • 两个轴都是时间

    [edit1] 更精确的图像

    上图是手绘的……这个是VCL/C++程序生成的:

    由这些参数生成:

    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。伪随机生成的指令...

    【讨论】:

      【解决方案2】:

      最近在 Arstechnica 上发表了一篇非常有趣的文章,讨论了控制台模拟,还链接到了许多可能有助于进行很好研究的模拟器:

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

      相关的一点是,作者提到,我倾向于同意,即使时间偏差为 +/-20%,大多数游戏似乎也能正常运行。你提到的问题看起来可能永远不会真正引入超过百分之几的计时错误,这在玩最后一场比赛时可能是难以察觉的。作者可能认为它不值得处理。

      【讨论】:

      • 谢谢 :-) 几天前我看到了这篇文章,读起来很有趣。看起来我可以忽略这个问题 - 完成了我的工作,但应该很有趣! :-p
      【解决方案3】:

      我想这取决于你希望你的模拟器有多准确。我不认为它必须那么准确。想想 x86 平台的仿真,处理器的变体非常多,并且每个都有不同的执行延迟和发出率。

      【讨论】:

      • 好吧,我想只要我把它与帧速率联系起来——如果我正在模拟 PAL 控制台,比如说 50fps——然后在这些帧之间,如果我执行多少个周期然后暂停我更新了应该足够的屏幕。我想我会找出答案的:-p 感谢您的回答。
      猜你喜欢
      • 1970-01-01
      • 2021-10-04
      • 2011-08-17
      • 1970-01-01
      • 2017-10-30
      • 2021-02-06
      • 1970-01-01
      • 1970-01-01
      • 2014-05-21
      相关资源
      最近更新 更多