模拟器是如何工作的?当我看到NES/SNES或C64模拟器时,我很震惊。
您是否必须通过解释特定的组装指令来模拟这些机器的处理器?还有什么?它们通常是如何设计的?
你能给那些对编写模拟器(特别是游戏系统)感兴趣的人一些建议吗?
模拟器是如何工作的?当我看到NES/SNES或C64模拟器时,我很震惊。
您是否必须通过解释特定的组装指令来模拟这些机器的处理器?还有什么?它们通常是如何设计的?
你能给那些对编写模拟器(特别是游戏系统)感兴趣的人一些建议吗?
仿真是一个多方面的领域。下面是基本思想和功能组件。我将把它分成几部分,然后通过编辑填充细节。我将要描述的许多东西都需要了解处理器的内部工作原理——组装知识是必要的。如果我在某些事情上有点太模糊,请提问,这样我可以继续改进这个答案。
基本思想:
仿真通过处理处理器和各个组件的行为来工作。您构建系统的每个单独部分,然后将这些部分连接起来,就像硬件中的电线一样。
处理器仿真:
有三种处理处理器仿真的方法:
解释 动态重新编译 静态重新编译
使用所有这些路径,您有相同的总体目标:执行一段代码来修改处理器状态并与“硬件”交互。处理器状态是给定处理器目标的处理器寄存器、中断处理程序等的集合体。对于6502,你有一些8位整数表示寄存器:a, X, Y, P和S;你还会有一个16位的PC寄存器。
通过解释,从IP(指令指针——也称为PC,程序计数器)开始,从内存中读取指令。您的代码解析此指令并使用此信息更改处理器指定的处理器状态。解释的核心问题是它非常慢;每次处理给定指令时,都必须对其进行解码并执行必要的操作。
With dynamic recompilation, you iterate over the code much like interpretation, but instead of just executing opcodes, you build up a list of operations. Once you reach a branch instruction, you compile this list of operations to machine code for your host platform, then you cache this compiled code and execute it. Then when you hit a given instruction group again, you only have to execute the code from the cache. (BTW, most people don't actually make a list of instructions but compile them to machine code on the fly -- this makes it more difficult to optimize, but that's out of the scope of this answer, unless enough people are interested)
对于静态重新编译,您所做的与动态重新编译相同,但是您遵循分支。最终构建的代码块代表程序中的所有代码,然后可以在没有进一步干扰的情况下执行。如果没有以下问题,这将是一个很棒的机制:
不在程序中开始的代码(例如压缩、加密、运行时生成/修改等)不会被重新编译,因此不会运行 已经证明,在给定的二进制文件中找到所有的代码等同于停止问题
这些结合在一起使得静态重新编译在99%的情况下完全不可行的。要了解更多信息,Michael Steil对静态重新编译做了一些很好的研究——这是我所见过的最好的研究。
处理器模拟的另一方面是与硬件交互的方式。这确实有两面性:
处理器时间 中断处理
处理器时间:
某些平台——尤其是NES、SNES等较老的主机——要求你的模拟器具有严格的兼容时间。对于NES,您有PPU(像素处理单元),它要求CPU在精确的时刻将像素放入内存。如果你使用解释,你可以很容易地计算周期并模拟适当的计时;对于动态/静态重新编译,事情要复杂得多。
中断处理:
中断是CPU与硬件通信的主要机制。通常,硬件组件会告诉CPU它所关心的中断。这非常简单——当您的代码抛出一个给定的中断时,您查看中断处理程序表并调用适当的回调。
硬件仿真:
模拟一个给定的硬件设备有两个方面:
模拟设备的功能 模拟实际的设备接口
以硬盘为例。通过创建备份存储、读/写/格式化例程等来模拟该功能。这部分通常非常简单。
设备的实际界面要复杂一些。这通常是内存映射寄存器(例如,设备观察变化以发出信号的内存部分)和中断的某种组合。对于硬盘驱动器,您可能有一个内存映射区域,您可以在其中放置读取命令,写入等,然后读取此数据。
我想说得更详细一些,但你有很多方法可以用它。如果你有任何具体的问题,请提出来,我会补充信息。
资源:
我认为我在这里做了一个很好的介绍,但是还有很多其他的领域。我非常乐意帮助解答任何问题;我在这方面的大部分内容都很模糊,因为它非常复杂。
强制性的维基百科链接:
模拟器 动态重新编译
一般仿真资源:
Zophar—这是我开始模拟的地方,首先下载模拟器,最终掠夺它们巨大的文档档案。这绝对是你能拥有的最好的资源。 NGEmu——没有太多直接资源,但他们的论坛是无敌的。 RomHacking.net—文档部分包含有关流行控制台的机器架构的资源
模拟器项目参考:
IronBabel -- This is an emulation platform for .NET, written in Nemerle and recompiles code to C# on the fly. Disclaimer: This is my project, so pardon the shameless plug. BSnes -- An awesome SNES emulator with the goal of cycle-perfect accuracy. MAME -- The arcade emulator. Great reference. 6502asm.com -- This is a JavaScript 6502 emulator with a cool little forum. dynarec'd 6502asm -- This is a little hack I did over a day or two. I took the existing emulator from 6502asm.com and changed it to dynamically recompile the code to JavaScript for massive speed increases.
处理器重新编译引用:
Michael Steil对静态重新编译的研究(如上所述)在本文中达到了顶峰,您可以在这里找到源代码等。
附录:
自从这个答案提交以来已经有一年多了,它得到了所有的关注,我认为是时候更新一些东西了。
也许现在仿真中最令人兴奋的东西是libcpu,由前面提到的Michael Steil发起。它是一个旨在支持大量CPU内核的库,这些CPU内核使用LLVM进行重新编译(静态和动态!)它有巨大的潜力,我认为它会在模拟方面做得很好。
Emu-docs也引起了我的注意,它有一个很棒的系统文档存储库,对于模拟目的非常有用。我在那里待的时间不长,但看起来他们有很多很棒的资源。
我很高兴这篇文章对你有所帮助,我希望我能在年底/明年初完成我关于这个主题的书。
是的,你必须“手工”解释整个二进制机器码的混乱。不仅如此,大多数情况下,您还必须模拟一些在目标机器上没有等效硬件的外来硬件。
简单的方法是一个接一个地解释指令。这工作得很好,但是很慢。一种更快的方法是重新编译——将源机器码转换为目标机器码。这比较复杂,因为大多数指令都不是一对一映射的。相反,您将不得不制定涉及额外代码的详细变通方案。但最终还是要快得多。大多数现代模拟器都是这样做的。
当你开发一个模拟器时,你是在解释系统正在运行的处理器组件(Z80、8080、PS CPU等)。
您还需要模拟系统拥有的所有外设(视频输出、控制器)。
你应该开始为简单的系统编写模拟器,比如旧的Game Boy(使用Z80处理器,我是不是没有弄错)或C64。
模拟可能看起来令人生畏,但实际上比模拟容易得多。
任何处理器通常都有一个编写良好的描述状态、交互等的规范。
如果您根本不关心性能,那么您可以使用非常优雅的面向对象程序轻松地模拟大多数老式处理器。例如,X86处理器需要一些东西来维护寄存器的状态(简单),需要一些东西来维护内存的状态(简单),还需要一些东西来接收每个传入的命令并将其应用到机器的当前状态。如果您真的需要准确性,您还可以模拟内存转换、缓存等,但这是可行的。
In fact, many microchip and CPU manufacturers test programs against an emulator of the chip and then against the chip itself, which helps them find out if there are issues in the specifications of the chip, or in the actual implementation of the chip in hardware. For example, it is possible to write a chip specification that would result in deadlocks, and when a deadline occurs in the hardware it's important to see if it could be reproduced in the specification since that indicates a greater problem than something in the chip implementation.
当然,电子游戏的模拟器通常关心性能,所以它们不会使用简单的实现,它们还包括与主机系统的操作系统接口的代码,例如使用绘图和声音。
考虑到老式电子游戏(NES/SNES等)非常缓慢的性能,在现代系统上进行仿真相当容易。事实上,更令人惊讶的是,你可以下载一套SNES游戏或Atari 2600游戏,考虑到当这些系统流行时,免费访问每个卡带都是一个梦想。
一个叫维克多·莫亚·德尔·巴里奥的人写了一篇关于这个主题的论文。152页里有很多有用的信息。你可以在这里下载PDF文件。
如果您不想注册scribd,您可以谷歌获取PDF标题,“模拟编程技术的研究”。PDF文档有几个不同的来源。
关于模拟真实系统或你自己的东西的建议? 我可以说模拟器通过模拟整个硬件来工作。也许不是电路(像HW那样四处移动比特)。移动字节是最终结果,所以复制字节是好的)。模拟器是很难创建的,因为有很多黑客(如在不寻常的效果),时间问题,等等,你需要模拟。如果一个(输入)部分是错误的,那么整个系统就会崩溃,或者最多出现一个bug/小故障。
共享源设备模拟器包含PocketPC/智能手机模拟器的可构建源代码(需要Visual Studio,在Windows上运行)。我参与了二进制版本的V1和V2。
它解决了许多仿真问题: -从虚拟客户端到物理客户端再到虚拟主机的高效地址转换 - JIT编译客户代码 -模拟周边设备,如网络适配器、触摸屏和音频 - UI集成,主机键盘和鼠标 -保存/恢复状态,用于模拟从低功耗模式恢复
模拟器很难创建,因为有很多黑客(在不寻常的 效果),时间问题,等等,你需要模拟。
有关示例,请参见http://queue.acm.org/detail.cfm?id=1755886。
这也会告诉你为什么你需要一个多ghz的CPU来模拟一个1MHz的CPU。
我知道这个问题有点老了,但我想在讨论中补充一些东西。这里的大多数答案都围绕模拟器解释它们所模拟的系统的机器指令。
然而,有一个非常著名的例外,称为“UltraHLE”(维基百科文章)。UltraHLE是有史以来最著名的模拟器之一,它模拟了商用任天堂64游戏(在家用电脑上表现不错),当时人们普遍认为这是不可能的。事实上,当UltraHLE诞生时,任天堂还在为任天堂64制作新游戏!
这是我第一次在印刷杂志上看到关于模拟器的文章,而以前我只在网上看到过它们的讨论。
UltraHLE的概念是通过模拟C库调用而不是机器级调用来实现不可能。
在创建了我自己的80年代BBC微型计算机模拟器(类型VBeeb到谷歌)后,有许多事情要知道。
You're not emulating the real thing as such, that would be a replica. Instead, you're emulating State. A good example is a calculator, the real thing has buttons, screen, case etc. But to emulate a calculator you only need to emulate whether buttons are up or down, which segments of LCD are on, etc. Basically, a set of numbers representing all the possible combinations of things that can change in a calculator. You only need the interface of the emulator to appear and behave like the real thing. The more convincing this is the closer the emulation is. What goes on behind the scenes can be anything you like. But, for ease of writing an emulator, there is a mental mapping that happens between the real system, i.e. chips, displays, keyboards, circuit boards, and the abstract computer code. To emulate a computer system, it's easiest to break it up into smaller chunks and emulate those chunks individually. Then string the whole lot together for the finished product. Much like a set of black boxes with inputs and outputs, which lends itself beautifully to object oriented programming. You can further subdivide these chunks to make life easier.
Practically speaking, you're generally looking to write for speed and fidelity of emulation. This is because software on the target system will (may) run more slowly than the original hardware on the source system. That may constrain the choice of programming language, compilers, target system etc. Further to that you have to circumscribe what you're prepared to emulate, for example its not necessary to emulate the voltage state of transistors in a microprocessor, but its probably necessary to emulate the state of the register set of the microprocessor. Generally speaking the smaller the level of detail of emulation, the more fidelity you'll get to the original system. Finally, information for older systems may be incomplete or non-existent. So getting hold of original equipment is essential, or at least prising apart another good emulator that someone else has written!
我从来没有做过像模拟游戏机这样奇特的事情,但我曾经上过一门课程,作业是为Andrew Tanenbaums结构化计算机组织中描述的机器编写模拟器。这很有趣,给了我很多顿悟的时刻。在开始编写一个真正的模拟器之前,您可能需要先阅读一下这本书。
添加@Cody Brocious提供的答案 在虚拟化环境中,您正在向虚拟机模拟一个新系统(CPU、I/O等),我们可以看到以下类别的模拟器。
解释:bochs是解释器的一个例子,它是一个x86 PC模拟器,它把来自客户系统的每条指令翻译成另一组指令(主机ISA的指令)来产生预期的效果。是的,它非常慢,它不缓存任何东西,所以每条指令都经过相同的周期。
动态仿真器:Qemu是一个动态仿真器。它可以实时翻译客户指令,也可以缓存结果。最好的部分是直接在主机系统上执行尽可能多的指令,这样模拟就更快了。正如Cody所提到的,它将代码划分为块(一个单独的执行流)。
静态模拟器:据我所知,没有静态模拟器可以帮助虚拟化。
我将如何开始模仿。
1.买一些关于低级编程的书籍,你将需要它来“模拟”任天堂的操作系统……游戏的男孩……
2.专门找一些模拟方面的书,也许还有操作系统开发方面的。(你不会做一个操作系统,但最接近它。
3.看看一些开源仿真器,特别是那些您想为其创建仿真器的系统。
4.将更复杂的代码片段复制到IDE/编译器中。这将节省编写长代码的时间。这就是我为操作系统开发所做的,使用linux的一个区域
我写过一篇关于用JavaScript模拟Chip-8系统的文章。
这是一个很好的开始,因为系统不是很复杂,但你仍然了解操作码、堆栈、寄存器等是如何工作的。
我将很快为NES写一篇更长的指南。