模拟器是如何工作的,它们是如何编写的?

模拟器是如何工作的?当我看到NES/SNES或C64模拟器时,它让我大吃一惊。

http://www.tommowalker.co.uk/snemzelda.png

您是否必须通过解释其特定的组装指令来模拟这些机器的处理器?它还包含什么?它们通常是如何设计的?

你能给那些对编写模拟器(尤其是游戏系统)感兴趣的人一些建议吗?

328559 次浏览

仿真是一个多方面的领域。以下是基本思想和功能组件。我将把它分成几个部分,然后通过编辑填充细节。我要描述的许多事情都需要处理器内部工作的知识——组装知识是必要的。如果我对某些事情有点模糊,请提出问题,这样我就可以继续改进这个答案。

基本思路:

仿真通过处理处理器和各个组件的行为来工作。您构建系统的每个单独部分,然后将这些部分连接起来,就像硬件中的电线一样。

处理器仿真:

有三种处理处理器仿真的方法:

  • 口译
  • 动态重新编译
  • 静态重编译

使用所有这些路径,你有相同的总体目标:执行一段代码来修改处理器状态并与“硬件”交互。处理器状态是给定处理器目标的处理器寄存器、中断处理程序等的集合。对于6502,你会有许多代表寄存器的8位整数:AXYPS;你还会有一个16位PC寄存器。

使用解释,您从IP(指令指针-也称为PC,程序计数器)开始并从内存中读取指令。您的代码解析此指令并使用此信息来更改处理器指定的处理器状态。解释的核心问题是它的非常慢;每次处理给定指令时,您都必须解码它并执行必要的操作。

使用动态重新编译,你可以像解释一样迭代代码,但不仅仅是执行操作码,而是建立一个操作列表。一旦你到达一个分支指令,你就把这个操作列表编译成主机平台的机器代码,然后缓存这个编译后的代码并执行它。然后当你再次命中一个给定的指令组时,你只需要执行缓存中的代码。(顺便说一句,大多数人实际上并没有列出一个指令列表,而是动态地将它们编译成机器代码——这使得优化变得更加困难,但这超出了这个答案的范围,除非有足够多的人感兴趣)

使用静态重新编译,你做的与动态重新编译相同,但你遵循分支。你最终构建了一块代表程序中所有代码的代码块,然后可以在没有进一步干扰的情况下执行。如果没有以下问题,这将是一个很棒的机制:

  • 一开始不在程序中的代码(例如压缩、加密、在运行时生成/修改等)不会重新编译,因此不会运行
  • 已经证明,找到给定二进制文件中的所有代码相当于停机问题

这些结合在一起使得静态重新编译在99%的情况下完全不可行。有关更多信息,Michael Steil对静态重新编译做了一些很好的研究-这是我见过的最好的。

处理器仿真的另一方面是您与硬件交互的方式。这实际上有两个方面:

  • 处理器计时
  • 中断处理

处理器计时:

某些平台-特别是较旧的控制台,如NES,SNES等-要求您的模拟器具有严格的时序才能完全兼容。使用NES,您有PPU(像素处理单元),它要求CPU在精确的时刻将像素放入其内存中。如果您使用解释,您可以轻松计算周期并模拟适当的时序;使用动态/静态重新编译,事情 /lot/更复杂。

中断处理:

中断是CPU与硬件通信的主要机制。通常,您的硬件组件会告诉CPU它关心什么中断。这很简单-当您的代码抛出给定的中断时,您会查看中断处理程序表并调用适当的回调。

硬件仿真:

模拟给定的硬件设备有两个方面:

  • 模拟设备的功能
  • 模拟实际设备接口

以硬盘驱动器为例。通过创建后备存储、读/写/格式化例程等来模拟功能。这部分通常非常简单。

设备的实际接口有点复杂。这通常是内存映射寄存器(例如设备监视更改以执行信令的内存部分)和中断的某种组合。对于硬盘驱动器,您可能有一个内存映射区域,您可以在其中放置读取命令、写入等,然后读取此数据。

我会更详细地说明,但是你有一百万种方法可以使用它。如果你有任何具体的问题,随时问,我会添加信息。

资源说明:

我想我在这里已经给了一个很好的介绍,但是还有的其他领域。我非常乐意帮助任何问题;由于巨大的复杂性,我在大多数方面都非常模糊。

维基百科链接:

常规仿真资源:

  • 琐法--这是我从仿真开始的地方,首先下载模拟器,最终掠夺他们巨大的留档档案。这是你可能拥有的绝对最好的资源。
  • ngemu--直接资源不多,但他们的论坛是无与伦比的。
  • RomHacking.net--文档部分包含有关流行控制台的机器架构的资源

模拟器项目参考:

  • IronBabel--这是一个. NET的仿真平台,用NEmerle编写,并动态将代码重新编译为C#。免责声明:这是我的项目,所以请原谅无耻的插件。
  • BSnes-一个很棒的SNES模拟器,目标是循环完美的准确性。
  • MAME--街机模拟器。很好的参考。
  • 6502asm.com--这是一个带有一个很酷的小论坛的JavaScript 6502模拟器。
  • dynarec'd 6502asm这是我花了一两天时间做的一个小黑客。我从6502asm.com中获取了现有的模拟器,并将其更改为动态地将代码重新编译为JavaScript以大幅提高速度。

处理器重新编译参考:

  • Michael Steil对静态重新编译的研究(如上所述)最终导致了本文,您可以找到源代码和这里

增编:

自从这个答案提交以来已经有一年多了,随着它得到的所有关注,我想是时候更新一些东西了。

也许现在仿真中最令人兴奋的事情是libcpu,由前面提到的Michael Steil开始。这是一个旨在支持大量CPU内核的库,它们使用LLVM进行重新编译(静态和动态!)。它有巨大的潜力,我认为它会为仿真做很多事情。

emu-docs也引起了我的注意,它包含了一个很棒的系统留档库,对于仿真目的非常有用。我没有花太多时间在那里,但看起来他们有很多很棒的资源。

我很高兴这篇文章有所帮助,我希望我能在年底/明年初完成我关于这个主题的书。

是的,您必须“手动”解释整个二进制机器代码混乱。不仅如此,大多数时候您还必须模拟一些在目标机器上没有等效的奇异硬件。

简单的方法是逐条解释指令。这很有效,但速度很慢。更快的方法是重新编译——将源机器代码翻译为目标机器代码。这更复杂,因为大多数指令不会一对一映射。相反,你必须做出复杂的变通方法,涉及额外的代码。但最终它要快得多。大多数现代模拟器都这样做。

当您开发模拟器时,您正在解释系统正在处理的处理器组件(Z80、8080、PS CPU等)。

您还需要模拟系统具有的所有外围设备(视频输出、控制器)。

您应该开始为simpe系统编写仿真器,例如旧的Game Boy(使用Z80处理器,我没有弄错)或C64。

仿真可能看起来令人生畏,但实际上比模拟更容易。

任何处理器通常都有一个编写良好的规范来描述状态、交互等。

如果你根本不在乎性能,那么你可以使用非常优雅的面向对象程序轻松模拟大多数旧处理器。例如,X86处理器需要一些东西来维护寄存器的状态(简单),一些东西来维护内存的状态(简单),以及一些将每个传入命令应用于机器当前状态的东西。如果你真的想要准确性,你还可以模拟内存转换、缓存等,但这是可行的。

事实上,许多微芯片和CPU制造商会针对芯片的模拟器测试程序,然后针对芯片本身进行测试,这有助于他们找出芯片规格中是否存在问题,或者芯片在硬件中的实际实现中是否存在问题。例如,有可能编写会导致死锁的芯片规范,当硬件中出现截止日期时,重要的是看看是否可以在规范中复制它,因为这表明比芯片实现中的问题更大。

当然,视频游戏的模拟器通常关心性能,因此它们不会使用天真的实现,并且它们还包括与主机系统的操作系统接口的代码,例如使用绘图和声音。

考虑到旧视频游戏(NES/SNES等)的性能非常缓慢,在现代系统上模拟相当容易。事实上,更令人惊讶的是,你可以下载一套每款SNES游戏或任何Atari 2600游戏,考虑到当这些系统流行时,免费访问每一个墨盒将是梦想成真。

一个叫Victor Moya del Barrio的人写了他关于这个主题的论文。152页上有很多好的信息。您可以下载PDF这里

如果您不想注册scribd,您可以在Google上搜索PDF标题《仿真编程技术研究》。PDF有几个不同的来源。

关于模拟真实系统或自己的东西的建议?我可以说模拟器通过模拟整个硬件来工作。也许不是到电路(就像硬件一样移动位。移动字节是最终结果,所以复制字节很好)。模拟器很难创建,因为你需要模拟许多黑客(如不寻常的效果)、时间问题等。如果一个(输入)部分错误,整个系统可以下降,或者充其量有bug /glitch.

共享源设备仿真器包含PocketPC/智能手机模拟器的可构建源代码(需要Visual Studio,在Windows上运行)。我在二进制版本的V1和V2上工作。

它解决了许多仿真问题:-从访客虚拟到访客物理到主机虚拟的高效地址转换-来宾代码的JIT编译-模拟外围设备,如网络适配器,触摸屏和音频-UI集成,主机键盘和鼠标-保存/恢复状态,用于模拟从低功耗模式恢复

还可以查看Darek Mihocka的Emulators.com,了解有关JIT指令级优化的重要建议,以及有关构建高效模拟器的许多其他好东西。

模拟器很难创建,因为有很多黑客(如不寻常的效果),时间问题等,你需要模拟。

关于这个例子,请参见http://queue.acm.org/detail.cfm?id=1755886

这也将向您展示为什么您“需要”一个多GHz CPU来模拟1MHz CPU。

我知道这个问题有点老,但我想在讨论中增加一些东西。这里的大多数答案都围绕着模拟器解释它们模拟的系统的机器指令。

然而,有一个非常著名的例外,叫做“UltraHLE”(维基百科文章)。UltraHLE是有史以来最著名的模拟器之一,它模拟了商业任天堂64游戏(在家用电脑上表现不错),当时人们普遍认为这是不可能的。事实上,当UltraHLE创建时,任天堂仍在为任天堂64制作新游戏!

我第一次在印刷杂志上看到关于模拟器的文章,以前我只在网上看到过它们。

UltraHLE的概念是通过模拟C库调用而不是机器级调用来实现不可能。

在创建了我自己的80年代BBC微型计算机模拟器(在Google中输入VBeeb)之后,有很多事情需要了解。

  • 你不是在模拟真实的东西,那将是一个复制品。相反,你正在模拟国家。一个很好的例子是计算器,真实的东西有按钮,屏幕,外壳等。但是要模拟计算器,你只需要模拟按钮是向上还是向下,LCD的哪些部分是打开的,等等。基本上,一组数字代表计算器中可以更改的所有可能的组合。
  • 你只需要模拟器的界面像真实的一样出现和表现。这越令人信服,仿真就越接近。幕后发生的事情可以是你喜欢的任何事情。但是,为了便于编写模拟器,在真实系统之间发生了心理映射,即芯片、显示器、键盘、电路板和抽象的计算机代码。
  • 要模拟计算机系统,最简单的方法是将其分解成更小的块并单独模拟这些块。然后将整个块串在一起以获得成品。就像一组带有输入和输出的黑盒,非常适合面向对象程序设计。您可以进一步细分这些块以使生活更轻松。

实际上,您通常希望编写模拟的速度和保真度。这是因为目标系统上的软件将(可能)比源系统上的原始硬件运行得更慢。这可能会限制编程语言、编译器、目标系统等的选择。
除此之外,您还必须限定您准备模拟的内容,例如,没有必要模拟微处理器中晶体管的电压状态,但可能有必要模拟微处理器寄存器集的状态。
一般来说,仿真的详细级别越小,您对原始系统的保真度就越高。
最后,旧系统的信息可能不完整或不存在。因此,掌握原始设备至关重要,或者至少破坏别人编写的另一个好的模拟器!

值得一看的是Imran Nazar尝试用JavaScript编写Gameboy模拟器。

我从来没有做过像模拟游戏机这样花哨的事情,但我确实参加过一次课程,任务是为Andrew Tanenbaum结构化计算机组织中描述的机器编写一个模拟器。那很有趣,给了我很多顿悟的时刻。在潜入编写真正的模拟器之前,你可能想拿起那本书。

添加@Cody Brocious提供的答案
在虚拟化的上下文中,您正在将新系统(CPU、I/O等)模拟到虚拟机,我们可以看到以下类别的模拟器。

解释:bochs是解释器的一个例子,它是一个x86 PC仿真器,它接受来自客户系统的每条指令,将其翻译成另一组指令(主机ISA)以产生预期的效果。是的,它非常慢,它不缓存任何东西,所以每条指令都经过相同的循环。

动态仿真器:Qemu是一个动态仿真器。它实时翻译来宾指令并缓存结果。最好的部分是直接在主机系统上执行尽可能多的指令,这样仿真速度更快。正如Cody提到的,它将代码分为块(1个执行流程)。

静态模拟器:据我所知,没有静态模拟器可以帮助虚拟化。

我将如何开始模仿。

1.获取基于低级编程的书籍,您将需要它用于任天堂的“假装”操作系统…游戏男孩…

2.专门阅读有关仿真的书籍,也许还有操作系统开发。(你不会做一个操作系统,但最接近它。

3.look一些开源模拟器,尤其是那些你想为其制作模拟器的系统。

4.copy更复杂的代码片段到您的IDE/编译器中。这将节省您编写长代码。这就是我为os开发所做的,使用linux的一个区域

我写了一篇关于模拟JavaScript中的Chip-8系统的文章。

这是一个很好的起点,因为系统不是很复杂,但您仍然可以了解操作码,堆栈,寄存器等如何工作。

我将很快为NES写一个更长的指南。