写在前面:最近公司在做GBA的模拟器,顺便找了点资料研究了一下。
提起模拟器,爱好者们一定会想到NeoRAGEx、MAME, N64等著名游戏机的模拟器。在这些模拟器上可以模拟一些功能强大的街机板,可以让我们在自己的PC上免费玩街机的游戏。想起当初数着钱买游戏币的日子, 真是一个天堂一个人间啊。玩着让人热血沸腾的游戏的时候,你是否想过这些模拟器是怎样开发出来的?你是否了想解这些模拟器的运行原理?本文将给你一个回答。
模拟器的功能就是用软件模拟硬件环境,让原本只能在硬件环境下执行的程序可以在软件模拟的环境下执行。而现今硬件的结构都是大同小异的,所以这些模拟器的 技术不但可以模拟游戏机的机板,也可以模拟其它的硬件环境,例如单片机、PDA, Mac等等,只是细节上有一点不同罢了。不过,模拟器的开发用一句话来说,就是“底层、底层、再底层”,从最低层的硬件角度入手来模拟硬件,这才是正确的 模拟道路。
要了解模拟器的工作原理,首先 我们要了解一些计算机硬件的知识.我们现在使用的计算机的模型是图灵机,主要是由存储器、控制器和运算 器组成的。所 谓存储器就是内存、硬盘等可以存储数据的设备;控制器就是计算机内部用来控制程序执行的设备,计算机的指令都是由它来调度执行的:而运算器顾名思义就是运 算用的啦。由存储器、控制器和运算器组 成了一个以控制器为中心的工作系统,大概的工作流程是这样的:
1、准备运行,将指令调入存储器;
2、开始运行程序;
3、在一个时钟周期内,控制器从存储器里面取回一条指令;
4、解释指令,指出要做什么,并产生对应的控制命令和操作数地址 (如果有的话);
5、由CPU执行指令;
6、重复到第3步,一直到有外界干预或者程序执行完毕为止。
通过这个工作流程解释,我们可以知道,CPU不停地在取指令、解释指令、执行指令,一直循环直到中止。其中当然还有一些其他的控制,下面我们细细谈来。
首先,CPU什么时候取指令?答案是CPU在一个时钟周期内取指令,指令执行要耗费一定的时钟周期,指令执行完毕以后,CPU会再来取指令,然后重复。这是一般的工作模式,什么流水线之类的东西就不提了。
其次,什么时候执行中断?CPU在每一个取指令周期之前都会检查是否有中断产生,如果有的话,就去执行中断,直到中断返回。
第三,定时器怎样工作?定时器是一个硬件设备,设置了一些相关的参数以后,它就会在特定的时候产生一个定时中断。CPU在检查中断的时候就会检测到这个中断。
好了,了解了这么多复杂的硬件工作原理以后,我们就可以了解一下模拟器是怎样来模拟这些硬件的工作原理了。模拟器的主要工作原理是,模拟存储器,将指令放到存储器里面,然后不断地从存储器里面取回指令、通过模拟硬件的指令集来解释指令,再将原来硬件环境中的指令转换成为PC机上的指令,在模拟的环境下重执行指令,以达到模拟指令执行的目的。
首先要模拟的是存储器,可以说就是模拟一个硬件的内存。我们运行的PC机上也有内存,所以直接在PC开一个内存空间,用来给要模拟的硬件环境存储指令。因为要模拟的硬件访问这段内存时通过指令来达成目的,所以在CPU执行指令的时候直接来访问这段内存就可以了。
其次要模拟的是CPU,根据上面说到的工作流程,
这段程序的主要结构是这样的:
while(没有外界的结束命令)
{
检查是否有中断
if(有中断)
执行中断;
取指令;
解释指令;
执行指令;
if(程序没有结束)//这里应该是if(程序结束)吧
跳出循环
}
接下来要模拟就是定时器,我曾经写过二个单片机的模拟器,但是定时器的问题困惑我很久,因为用PC的定时器来模拟对应硬件的模拟器,一般达不到要求。PC 机的定时器最小的时钟周期是1ms (1毫秒,1/ 1000秒),但是用来模拟Mhz级数的时钟需要纳秒级的定时,这是PC所不能达到的。所以,干脆模拟到底,将CPU的时钟周期也来个计数,模拟硬件的定时设备工作原理。这样就可以正常地控制定时,当然速度取决于个人机器的快慢了,这就是大多数模拟器对机器的要求比较高的原因,毕竟软件模拟时钟周期的运作要慢很多。
那么上面的核心程序流程就可以转化为这样:
int Cycle = 0;
while(没有外界的结束命令)
{
检查是否有中段
if(有中断)
执行对应中断处理程序;
取指令;
Cycle += 对应硬件取指令箫要的时钟周期;
解释指令;
Cycle += 对应硬件解释指令需要的时钟周期;
执行指令;
Cysle += 对应硬件执行这条指令需要的时神周期;
if(到达定时需要的时钟周期数)
{
cycle= 0;
触发定时中断;
}
if(程序没有结束)//这里应该是if(程序结束)吧
跳出循环;
}
下面是模拟取指令,一般的CPU有一个程序计数器PC,是一个内存地址,指向下面一条指令的执行地址。那么,在程序开始的时候,PC是指向程序第一条指令的内存地址,取回一条指令后,PC自动加1,指向下面一条要执行的指令。
取指令的结构是这样的:
int PC;
Byte Code;//指令码
Byte Mem[65535]; //存储器设定为65535个字节
初始化PC = 0;//起始地址
取指令
Code = Mem[PC];
PC++;
解释指令就比较简单了,取回指令以后,就可以转移到特定的处理去。一般来说有两种方法。一种是使用switch语句,根据指令码来转移到特定的处理过程中;另外一种是构建一个指令表,表中保存的是指令的相关信息和处理函数的函数指针。这两种方法都是可取的,只是前者比较直观、简单,后者灵活、适合于通用结构。这两种方法都是根据CPU的指令集来构造的。
中断模拟也比较简单,不同的CPU都有不同的中断控制寄存器,根据中断寄存器来判断是否有中断。而如果检测到中断就转移到对应的处理过程中去。一般的来说,中断号是固定的,处理过程一般是由要模拟程序定义的,而地址也是由要模拟程序定义的。所以,我们本身的模拟器不需要关心太多。
总的来说,一个模拟器的结构大抵如此。如果你真的想开发一个游戏模拟器的话,还需要了解更多的硬件细节资料。另外,游戏模拟器大多数都是需要图形相关的指令的,普通的运算指令比较好模拟,但是图形指令的模拟需要与你熟悉Windows下面的图形相关指令,这样才能够很好地模拟出应有的图形效果。与声音相关的指令同样如此。不过,如果你只是想模拟一个CPU的话,那还是比较简单的。如果你想成为一个模拟器的开发者,还是从最简单的CPU模拟开始吧。
永远记住,模拟器主要是来模拟硬件环境的。