仿真器如何工作,它们是如何编写的?

仿真器如何工作? 当我看到NES / SNES或C64仿真器时,令我吃惊。

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

您是否必须通过解释其特定的汇编指令来模拟这些机器的处理器? 还有什么呢? 他们通常如何devise?

你可以给有兴趣编写仿真器(特别是游戏系统)的人提供任何build议吗?

仿真是一个多面的区域。 这里是基本的想法和function组件。 我将把它分解成几部分,然后通过编辑来填写细节。 我将要描述的许多事情都需要知道处理器的内部工作 – 组装知识是必要的。 如果我对某些事情有点太模糊,请提出问题,以便我可以继续改进这个答案。

基本理念:

仿真通过处理处理器和各个组件的行为来工作。 你build立系统的每一个单独的部分,然后连接件很像电线在硬件。

处理器仿真:

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

  • 解释
  • dynamic重新编译
  • 静态重新编译

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

通过解释,您可以从IP (指令指针 – 也称为PC ,程序计数器)开始,并从内存中读取指令。 您的代码会分析此指令,并使用此信息来改变处理器指定的处理器状态。 解释的核心问题是速度慢, 每次处理给定的指令时,都必须解码并执行必要的操作。

通过dynamic重新编译,您可以像解释一样遍历代码,但不是只执行操作码,而是build立一个操作列表。 一旦你到达一个分支指令,你将这个操作列表编译成你的主机平台的机器代码,然后你caching这个编译后的代码并执行它。 然后,当您再次访问给定的指令组时,您只需执行caching中的代码即可。 (顺便说一句,大多数人并没有真正列出指令列表,而是将它们编译成机器代码 – 这使得优化变得更加困难,但是这不在这个答案的范围内,除非有足够多的人感兴趣)

通过静态重新编译,您可以在dynamic重新编译中执行相同的操作,但是您需要遵循分支。 您最终将创build一段代码来代表程序中的所有代码,然后可以执行这些代码而不会再有任何干扰。 如果不是以下问题,这将是一个很好的机制:

  • 不在程序中开始的代码(例如压缩,encryption,运行时生成/修改等)不会被重新编译,所以它不会运行
  • 已经certificate,find给定二进制文件中的所有代码都等同于停机问题

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

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

  • 处理器时序
  • 中断处理

处理器时间:

某些平台 – 特别是像NES,SNES等老的控制台 – 要求你的模拟器有严格的时间来完全兼容。 使用NES,您可以使用PPU(像素处理单元),它要求CPU在精确时刻将像素放入内存中。 如果你使用解释,你可以很容易地计算周期和模拟正确的时间; dynamic/静态重新编译,事情是/很多/更复杂。

中断处理:

中断是CPU与硬件通信的主要机制。 一般来说,你的硬件组件会告诉CPU它关心什么中断。 这很简单 – 当你的代码抛出一个给定的中断时,你看中断处理程序表并调用正确的callback函数。

硬件仿真:

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

  • 模拟设备的function
  • 仿真实际的设备接口

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

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

我会更详细地介绍一下,但有一百万种方法可以与之配合。 如果您有任何具体的问题,请随时询问,我将添加信息。

资源:

我想我已经在这里介绍了一个很好的介绍,但是还有很多额外的领域。 我很乐意提出任何问题。 大部分情况下,我都非常模糊,只是由于巨大的复杂性。

强制维基百科链接:

  • 模拟器
  • dynamic重新编译

一般仿真资源:

  • Zophar – 这是我从模拟开始,首先下载模拟器,并最终掠夺他们巨大的文档档案。 这是你可能拥有绝对最好的资源。
  • NGEmu – 没有太多的直接资源,但他们的论坛是无与伦比的。
  • RomHacking.net – 文档部分包含有关stream行控制台机器体系结构的资源

模拟器项目引用:

  • IronBabel – 这是一个用Nemerle编写的用于.NET的仿真平台,可以将代码重新编译到C#上。 免责声明:这是我的项目,所以请原谅无耻的插件。
  • BSnes – 一个非常棒的SNES仿真器,其目标是完美的周期。
  • MAME – 街机模拟器。 伟大的参考。
  • 6502asm.com – 这是一个很酷的小论坛的JavaScript 6502模拟器。
  • dynarec'd 6502asm – 这是我做了一两天的一点小事。 我从6502asm.com取出了现有的模拟器,并将其改为dynamic地将代码重新编译为JavaScript以加快速度。

处理器重新编译参考:

  • 由Michael Steil(上面引用)进行的静态重新编译的研究在本文中达到了高潮,您可以在这里find源代码等。

附录:

自从这个答案提交以来,已经过去了一年多了,并且所有的注意力都已经得到了,我想现在是更新一些东西的时候了。

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

emu-docs也引起了我的注意,这里面有一个很好的系统文档库,对于仿真目的非常有用。 我没有花太多时间,但看起来他们有很多很好的资源。

我很高兴这篇文章有帮助,我希望能够在年底/明年初完成我的书。

一个名叫维克托·莫亚·德尔·巴里奥(Victor Moya del Barrio)的人写了他的论文。 152页上的很多很好的信息。 你可以在这里下载PDF。

如果你不想用scribd注册,你可以谷歌的PDF标题, “仿真编程技术研究” 。 PDF有几个不同的来源。

仿真似乎令人生畏,但实际上比仿真更容易。

任何处理器通常都有精心编写的规范来描述状态,交互等。

如果你根本不关心性能,那么你可以使用非常优雅的面向对象的程序轻松地模拟大多数较旧的处理器。 例如,X86处理器需要维护寄存器的状态(简单),维持内存状态(简单),以及将每个传入命令应用到机器当前状态的内容。 如果你真的想要精度,你也可以模拟内存翻译,caching等,但这是可行的。

实际上,很多微芯片和CPU制造商都是针对芯片的仿真器对芯片进行testing,然后对芯片本身进行testing,从而帮助他们发现芯片的规格或硬件在芯片的实际应用中是否存在问题。 例如,写入芯片规格可能导致死锁,并且当硬件出现最后期限时,看看是否可以在规范中复制是很重要的,因为这意味着比芯片实现中的问题更大的问题。

当然,video游戏的仿真器通常关心的是性能,所以他们不使用天真的实现,而且还包含与主机系统操作系统接口的代码,例如使用绘图和声音。

考虑到旧video游戏(NES / SNES等)的性能非常低,在现代系统上仿真是相当容易的。 事实上,更令人惊讶的是,你可以下载一系列每一个SNES游戏或任何Atari 2600游戏,因为当这些系统受欢迎时,可以自由访问每个盒式磁带将梦想成真。

我知道这个问题有点老,但我想补充一些。 这里的大多数答案都围绕着模拟器来解释他们模拟的系统的机器指令。

然而,这个被称为“UltraHLE”( WIKIpedia文章 )是一个非常着名的例外。 作为有史以来最着名的模拟器之一的UltraHLE仿效了商业的任天堂64游戏(在家庭计算机上的performance不错),而这一切都被广泛认为是不可能的。 事实上,当UltraHLE诞生时,任天堂仍然在为任天堂64制作新的游戏!

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

UltraHLE的概念是通过仿真C库调用而不是机器级调用来实现不可能的事情。

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

创build了我自己的80年代的BBC微型计算机模拟器(在VB中inputVBeeb),有很多东西需要知道。

  • 你不是模仿真实的东西,这将是一个副本。 相反,你在模拟状态 。 一个很好的例子是一个计算器,真实的东西有button,屏幕,shell等等。但是要模拟一个计算器,你只需要模拟button是开启还是closures,LCD的哪些部分是开着的等等。基本上,一组数字代表可以在计算器中改变的所有可能的组合。
  • 您只需要仿真器的界面显示和行为就像真实的东西。 更有说服力的是,仿真越接近。 幕后发生的事情可以是任何你喜欢的事情。 但是,为了便于编写仿真器,在真实系统(即芯片,显示器,键盘,电路板和抽象计算机代码)之间存在心理映射。
  • 要模拟一个计算机系统,最简单的方法是将其分解成更小的块并分别模拟这些块。 然后把整个产品串起来用于成品。 就像一组带有input和输出的黑盒子一样,它本身就非常适合面向对象的编程。 您可以进一步细分这些块,使生活更轻松。

从实际的angular度来说,你一般都在寻求速度和保真度的模拟。 这是因为目标系统上的软件将(可能)比源系统上的原始硬件运行得更慢。 这可能会限制编程语言,编译器,目标系统等的select。
除此之外,你必须限定你准备仿效的东西,例如它不需要仿真微处理器中晶体pipe的电压状态,但是它可能需要仿真微处理器寄存器组的状态。
一般而言,仿真细节的级别越低,您将获得的原始系统的保真度越高。
最后,旧系统的信息可能不完整或不存在。 所以抓住原始设备是必不可less的,或至less撬开别人写的另一个好的模拟器!

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

简单的方法是逐一解释说明。 这很好,但速度很慢。 更快的方法是重新编译 – 将源机器代码翻译成目标机器代码。 这是更复杂的,因为大多数指令不会一一映射。 相反,你将不得不作出详细的解决方法,涉及到额外的代码。 但最终它要快得多。 大多数现代模拟器都这样做。

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

您还需要模拟系统所有外设(video输出,控制器)。

你应该开始编写仿真系统,像老式的Game Boy (使用Z80处理器,我不会误解)或C64。

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

有关这方面的示例,请参阅http://queue.acm.org/detail.cfm?id=1755886

这也将告诉你为什么你需要一个多GHz的CPU来模拟一个1MHz的CPU。

另外请查阅Darek Mihocka的Emulators.com ,以获取关于JIT指令级优化的大量build议,以及构build高效仿真器的许多其他好处。

我从来没有做过像模拟游戏控制台那样的任何事情,但是我曾经在一个任务中为Andrew Tanenbaums 结构计算机组织中描述的机器写一个模拟器。 这很有趣,给了我很多的时间。 在潜心撰写真正的模拟器之前,您可能需要select那本书。

build议模仿一个真正的系统或你自己的东西? 我可以说模拟器通过模拟整个硬件来工作。 也许不是电路的问题(像硬件一样移动位移动字节是最终的结果,所以复制字节是可以的)。 模拟器非常难以创build,因为有许多黑客(如在不寻常的效果),时间问题等,你需要模拟。 如果一个(input)片断是错误的,整个系统可能停下来,或者至多有一个错误/毛刺。

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

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

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

解释:bochs是一个翻译的例子,它是一个x86 PC模拟器,它将来自客户系统的每条指令翻译成另一组指令(主机ISA)来产生预期的效果。它是非常慢的,它不会不会caching任何东西,因此每条指令都经历相同的周期。

dynamicemalator:Qemu是一个dynamic的模拟器。 它在客户指令的翻译上也caching了结果。最好的部分是在主机系统上直接执行尽可能多的指令,以便模拟更快。 正如Cody所提到的,它将代码分成块(单个执行stream)。

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

我将如何开始模拟。

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

2.专门针对仿真的书籍,也许是os的发展。 (你不会做一个操作系统,但最接近它。

3.查看一些开源的模拟器,特别​​是你想制作模拟器的系统。

4.将更复杂的代码片段复制到IDE / compliler中。 这将节省您写出长的代码。 这是我为OS开发所做的,使用Linux的一个区域

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

这是一个很好的开始,因为系统不是很复杂,但你仍然学习操作码,堆栈,寄存器等是如何工作的。

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