6

我计划在接下来的几个月里创建一个 Sega Master System 模拟器,作为 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 个周期很低,但这只是一个例子。

非常感谢,菲尔

4

3 回答 3

5
  1. 大多数模拟器/模拟器只处理 CPU 时钟抽动

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

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

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

    例如,如果您想将真实硬件连接到您的仿真/仿真,那么您需要仿真/仿真 BUS。此外,诸如浮动总线或系统争用之类的事情很难添加到方法#1(这是可行的,但有很大的痛苦)。

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

    为了完成这项工作,我创建了Z80的仿真/模拟,它为每条指令使用微码之类的东西。因为我经常纠正Z80指令集中的错误(因为我知道没有一个 100% 正确的文档,即使他们中的一些人声称它们没有错误且完整)我想出了一种方法来处理它而无需痛苦地重新编程模拟器。

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

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

CPU调度

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

于 2015-10-24T10:36:08.860 回答
3

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

准确性取得力量:一个人的 3GHz 探索构建完美的 SNES 仿真器

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

于 2011-08-19T14:50:43.653 回答
0

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

于 2011-08-19T14:25:30.420 回答