Page 1 of 1

模拟器制作流程

Posted: Thu Jun 01, 2006 2:21 pm
by zgh4000
模拟器制作流程
[ 第一步之前的那一步! ]
[ 第一步 - 收集资料 ]
1.0 你有什么?
1.1 别人有什么?
[ 第二步 - 整理所获得的资料 ]
[ 第三步 TVGAME 硬体的运作方式 ]
3.1 硬体周边分类
3.2 CPU及MEMORY MAP
3.2.0 CPU的功能与地位
3.2.1 MULTI-PROCESSOR的系统:
3.2.2 看起来不一样,实际又是大同小异的CPU
3.2.3 MEMORY MAP的功能
3.2.4 ROM IMAGE
3.2.5 CPU与其他周边沟通方式
3.2.6 DSP的功能
3.3 PPU(图形处理单元运作)方式
3.3.0 PPU(图形处理单元)的功能及地位
3.3.1 PPU的组成
3.3.2 VBLANK & HBLANK
3.4 SPU(声音处理单元)运作方式:
3.4.0 SPU(图形处理单元)的功能及地位:
3.4.1 SPU的组成:
3.4.2 与PPU的不同处:
3.4.3 声音处理器专用DSP的功能:
3.4.4 声音处理器的指令:
3.5 ROM & RAM AREA
3.5.0 SYSTEM ROM:
3.5.1 没有SYSTEM ROM的基版:
3.5.2 GAME ROM
3.5.3 以光碟做媒介的ROM -- CD ROM:
3.5.4 GAME ROM的MAPPER 问题:
3.5.5 如何开发游戏:
3.5.6 ROM中的程式如何控制硬体:
3.6 摇杆控制装置及钱币计数器等NMI的处理
3.6.0 摇杆通知CPU有按键按下的原理:
3.6.1 MEMORY MAP中分配给摇杆控制装置的方式:
3.6.2 钱币计数器,START键等NMI的处理:
3.6.3 非数位式摇杆的输入:
3.6.4 钱币计数器,START键等NMI的处理:
3.7 RESET处理
3.7.0 加入RESET的原因:
3.7.1 RESET的流程:
3.8 DIP SWITCH
3.8.0 加入DIP SWITCH的原因:
3.8.1 DIP SWITCH的作用方式:
3.9 BACKUP DEVICE
3.9.0 需要BACKUP DEVICE的原因:
3.9.1 存取BACKUP DEVICE的方式:
3.A DMA CONTROLLER
3.A.0 什么是DMA(DIRECT MEMORY ACCESS) CONTROLLER?
3.A.1 DMA的运作方式:
3.B 光碟机装置
3.B.0 使用CD-ROM DRIVER的原因:
3.B.1 CD-ROM的存取方式:
3.C 其他周边

[ 第四步 模拟器的运作方式 ]
4.0 模拟器与真实的硬体还是有差距的
4.1 CPU的处理
4.1.0 CPU CORE
4.1.1 套用现成的CPU CORE :
4.1.2 自己做一个CPU CORE:
4.1.3 MULTI-PROCCESSOR的场合:
4.1.4 CPU与其他周边沟通方式的处理
4.1.5 DSP的处理
4.2 MEMORY MAP的处理
4.3 PPU的处理
4.3.0 PPU的处理
4.3.1 实际绘图的动作:
4.3.2 SCREEN REFRESH
4.4 SPU运作方式:
4.5 摇杆控制装置及钱币计数器等NMI的处理
4.5.0 MEMORY MAP中处理摇杆控制装置的方式:
4.5.1 特殊指向装置的处理:
4.5.2 钱币计数器,START键等NMI的处理:
4.6 RESET处理
4.7 DIP SWITCH作用处理方式:
4.8 DMA CONTROLLER运作处理方式
4.9 光碟机装置存取方式:
4.A 档案处理:
4.A.0 档案载入
4.A.1 LOAD HISCORE FILE
4.C.2 LOAD/SAVE 随时记忆档
4.B 特殊功能:
4.B.0 抓图
4.A.1 抓音乐档
4.B.2 GUI
4.B.3 CHEAT CODE
4.B.4 CHEAT TOOL
4.B.5 DISASM
4.C 可携性版本,WINDOWS版本,更先进的模拟器架构
4.C.0 可携性的研究
4.C.1 WINDOWS版本
4.C.2 更先进的模拟器架构

[ 第五步 如何偷别人的程式及与别人交换意见 ]
5.0 如何偷别人的程式
5.0.0 模仿是进步最快的方式:
5.0.1 哪里找得到模拟器原始码?
5.0.2 看得懂别人的程式也不是件容易的事:
5.1 和别人交换意见:

[ 第六步 实际撰写一个模拟器 ]

[ 第七步 测试,除错及版本更新 ]
7.0 测试与除错是一条漫长的道路:
7.1 自己测试的盲点:
7.2 版本更新:
7.3 最少保留一份每一个版本的原始档及执行档:

[ 第八步 撰写说明文件 ]

[ 第九步 公开你的版本 ]
9.0 建立个人网页:
9.1 投稿至模拟器新闻性网页!

[ 后记 ]

[ 常用名词解释 ]

------------------------------------------------------------------------
[ 第一步之前的那一步! ]
为什么你想做一个模拟器?练习程式语言?还是你想玩很难玩到的游戏?还是你只
是单纯的想做一个模拟器?动机会影响你的热衷度,同时你也必须选定一个明确
的目标,模拟一个ARCADE的游戏,还是游戏主机?一旦选定了明确的目标,你一定
要下定决心,否则便没有完成这个模拟器的机会!在撰写模拟器时,你会遇到很多
困难,像找不到资料;为了除错,几天都很难睡觉!就算是写好了,放出来也还有一
堆莫名其妙的人抱怨一些有的没的!你唯一的报酬可能就只有成就感而已,或者
加上能玩到已经玩不到的游戏.如果你还愿意去写一个模拟器,我真的十分佩服
你!

------------------------------------------------------------------------
[ 第一步 - 收集资料 ]
1.0 你有什么?
在你想写模拟器之前,你必须要真实的检视你自己的资源,你会什么程式语言?
你对所模拟的硬体认识多少?你了解计算机内部运作的方式吗?你看不看得懂硬
体线路图?这些会影响你写不写得出一个完整的模拟器!其实程式语言的影响
也不是那么严重,还是有人可以用QUICK BASIC写出模拟器,只是效率实在不好
而已,但是其他的因素却是影响这个模拟器写不写的出来!

1.1 别人有什么?
你需要去找你想要模拟的硬体的资料,你有哪些要找的呢?
A.使用何种CPU?基版上面有多少特殊的晶片?时序速度多少?有多少相关资料?
B.有没有硬体概图(Schematics)?有没有DIP SWITCH的资料?
C.有没有MEMORY MAP?有没有软体的其他相关资料?
D.有没有别人已经写好的模拟器原始档?
你可到哪里找?
A.新闻讨论区!
B.模拟器网页的留言版!
C.模拟器新闻网页-可以知道是否有模拟器提供其原始档!
D.模拟器程式发展资源网页-可以收集到许多前人写好的文件!
E.别人的模拟器网页-也许有额外的连结可以让你找到多一点资讯!
F.如果是游戏主机,到官方网页也许有意想不到的资料!
还缺了什么?
A.CPU 手册,反组译工具!
B.也许你会需要电子元件手册,供你分别或查询元件的用途及接脚定义等!
C.适当的程式语言,适当的函式库(节省开发时间)!

------------------------------------------------------------------------
[ 第二步 - 整理所获得的资料 ]
在搜集到所有能收集的资料之后,依照我们要的部份加以分类:
硬体:
A.CPU种类&速度!次系统处理器种类&速度!
B.Schematics,DIP SWITCH,硬体线路,电路图等!
C.MEMORY MAP,摇杆接脚定义!
D.基版上的SYSTEM ROM!
E.特殊晶片的设计资料!例如图形处理晶片,声音处理晶片等!
F.画面处理的方式,图形及声音编码方式等!
软体:
A.CPU手册,反组译工具,CPU模拟程式等!
B.游戏ROM!SYSTEM ROM!
C.特殊晶片的模拟程式!
D.别人的模拟器程式,可以"偷"或是看看别人的写法!
E.函式库手册等!

------------------------------------------------------------------------
[ 第三步 TVGAME 硬体的运作方式 ]
3.1 硬体周边分类
一般说来,你可以把整个TVGAME硬体分为:
A.CPU:
一般是廉价的泛用处理器,例如68000,6502,Z80等!用来指挥整个硬体的
运作!复杂的系统甚至以多处理机协同处理!例如TAITO的双68000系统,SS
的双SH-2系统!
B.PPU(图形处理单元,VDP,GPU):
专职于图形的处理!包含PICTURE PROCCESSOR,专用RAM,专用ROM,数位类
比转换器等!常见名称的有PPU(PICTURE PROCCESS UNIT),GPU(GRAGHIX
PROCESS UNIT),VDP(VIDEO DATA PROCCESSOR),以下都使用PPU代替!
C.特殊DSP晶片
一种能快速计算数值的晶片,用来协助处理资料!
D.SPU(声音处理单元,APU):
专职于声音的处理!包含SOUND PROCCESSOR,专用RAM,专用ROM,专用DSP,
声音晶片,数位类比转换器等!名称也很多,SPU(SOUND PROCCESS UNIT),
APU(AUDIO PROCEESS UNIT ),以下用SPU代替!
E.ROM AREA:储放ROM的区域!
1.SYSTEM ROM:
类似于PC 中BIOS+OS的地位,提供开机检查,常用函式等!
2.GAME ROM:
真正游戏的程式区域!家用主机把图形,声音,及其他资料储放在一起!
但是ARCADE游戏通常会分开储放!如果细分的话,可以分为:
a.GRAPHIC ROM:储放游戏图形资料!
b.SOUND ROM:储放声音资料!
3.储放其他资料的ROM!
F.MAIN MEMORY,WORK RAM
就是主记忆体!
G.光碟机及控制周边!
只有以光碟作为储存媒介的主机才有!
H.摇杆等控制装置:
摇杆输入,RESET,钱币计数器等!
I.BACKUP DEVICE:
电池记忆,记忆卡之类的装置!
J.基版控制周边,DMA装置,外部扩充界面及装置等!
K.硬体其他周边,萤幕,电源供应器等!

当然也不是每一个TVGAME系统都这么完整,越早期的家用主机或ARCADE基版就
简单一点!现在的家用主机或是ARCADE 系统基版就十分复杂,这都是你在搜集
资料时一定要找到的,有些是模拟器一定会用到的,甚至影响模拟器的完成度,
有些则是模拟器可省略的,例如萤幕,电源供应器等!以下对于会使用在模拟器
上的装置作一下说明!

------------------------------------------------------------------------
3.2 CPU及MEMORY MAP:
3.2.0 CPU的功能与地位:
对整个TVGAME或是ARCADE来说,CPU应该算是"导演"的工作,而ROM IMAGE则是
导演手上的剧本,用来指挥周边的动作.指挥别人,当然可以不用太高级,只要
足够就行了!画面表现的好坏,声音处理的好不好,与CPU的关系较少!但是如果
周边的处理比CPU快太多,让周边老是等待也不好,同时,有一部份工作也必须
由CPU处理,所以CPU也不可能太慢!把价格与运算能力做考量依据,才选出适用
的CPU!常见的有68000,6502,Z80等!

3.2.1 MULTI-PROCESSOR的系统:
MULTI-PROCESSOR的系统就是指这个系统中含有两个以上的CPU,例如SS就是由
两个SH-2所组成的,依照CPU分工的不同,可以分为:
A.MASTER/SLAVE(非对称结构):
只有一个CPU(MASTER)负责IO的工作,计算的工作则每个CPU都能执行!同
时只有MASTER CPU能执型O.S.,而SLAVE CPU则是以中断要求MASTER的服
务!
B.SEPERATE EXECUTIVES:
每个CPU有自己的OS,INTERRUPTS,CPU种类及OS种类均允许不同,不能共同
处理同一工作!
C.SYMMETRIC ORGANIZATION:
由一个OS整合管理所有的处理机,称为PROCESSOR POOL,每个CPU皆能存取
任一I/O DEVICE及储存单元!
如果你想做的是MULTI-PROCESSOR的系统,必须要先搜集CPU间的关系,沟通方
式,例如SS就是一个MASTER/SLAVE的系统!

3.2.2 MEMORY MAP的功能:
正如家家都必须要有不同的地址,才能把邮件送到正确的地方,CPU也需要相同
的机制,用来存取到正确的资料,处理机所使用的方式是加上位址线,连接至位
址汇流排,再连接至RAM,ROM,其他装置等!位址汇流排宽度决定了CPU所能定址
的最大空间,这空间,你可称为"定址空间"!实际上不同的定址模式会影响定址
空间,不过这里我们就假装不知道吧!

为了存取方便,需要将定址空间做一番规划,规划之后的定址空间,称之为MEMORY
MAP!把定址空间分成一段一段的,把每一段都赋予不同的用途!例如下面是SFC的
MEMORY MAP!

SNES Documentation v2.30: Written by Yoshi
----------------------------------------------------------------------------
|Here's a really basic memory map of the SNES's memory. Thanks to Geggin of |
|Censor for supplying this. Reminder: this is a memory map in MODE 20. |
|----------------------------------------------------------------------------|
|Bank |Address |Description |
|-------|--------------|-----------------------------------------------------|
|$00-$3F|$0000-$1FFF |Scratchpad RAM. Set D-reg here if you'd like (I do) |
| |$2000-$5FFF |Reserved (PPU, DMA) |
| |$6000-$7FFF |Expand (???) |
| |$8000-$FFFF |ROM (for code, graphics, etc.) |
|$70 |$0000-$7FFF |SRAM (BRAM) - Battery RAM |
|$7E |$0000-$1FFF |Scratchpad RAM (same as bank $00 to $3F) |
| |$2000-$FFFF |RAM (for music, or whatever) |
|$7F |$0000-$FFFF |RAM (for whatever) |
----------------------------------------------------------------------------

不只是TVGAME及ARCADE,IBM PC也有同样的机制,例如DOS的0KB-640KB-1024KB
规划!

3.2.3 看起来不一样,实际又是大同小异的CPU:
这里先暂且搁下MEMORY MAP不谈!回到CPU,如果你有机会看到SNK NEOGEO/MVS
系统的基版的话,从文件上知道,它应该是MOTOROLA 68000的CPU,不过你就是找
不到长方形的MOTOROLA 68000 CPU,因为SNK特别订制这颗CPU,把68000及两个
PPU作在一起,成为一颗VLSI!实际上,有很多机器都是以这样的方式制作,订制
成VLSI!在写模拟器时,只需要知道它的解码方式等同于MOTOROLA 68000 CPU
就可以了!

还有一种形式是DECODE及执行结果等同于其他CPU,最明显的例子就是FC用的"
65C02",事实上它与真正的6502不太一样,只是编码及执行结果等同于6502!在
写模拟器时,仍然需要知道它的解码方式等同于6502就行了!

3.2.4 ROM IMAGE:
一般TVGAME及ARCADE游戏,都是把程式或资料烧录在ROM中-不论是ROM IC颗粒
,还是CD-ROM!很明显的,除了CD-ROM之外,没有办法直接读取其中的指令或资
料(即便是CD-ROM也可能自己搞奇怪格式,所以也可能无法读取),所以需要使
用ROM DUMPER,把ROM中的指令或资料DUMP成二进位制档案,称为"ROM IMAGE"!

因为所使用的机器语言不同(或者粗略的称为CPU不同),ROM IMAGE的内容目前
对PC并不具任何意义,所能做的也只局限在COPY,MOVE,EDIT HEX,或是ZIP起来
,类似于图形档的处理,即便它是一个受欢迎的游戏!直到有适用的模拟器出现
,ROM IMAGE才有价值--拿来玩游戏!把ROM做成ROM IMAGE,在大部分情况下都
算是比较简单,所以ROM IMAGE多,而对应的模拟器少,作ROM IMAGE只要有机器
就行,但是没人写模拟器就是白搭!

回到ROM IC上,ROM上烧录的当然是程式或资料,问题是给谁的资料!如果不考
虑有些基版有SYSTEM ROM的情况(留待SYSTEM ROM区再讨论)!ROM一般都是给
CPU的资料,所以指令都是CPU的机器语言,存取到资料区时,即便可能是给另外
PPU或SPU所使用的指令,对于CPU而言,这些仍然视作"资料"而不是"指令"!等
到这些"资料"传递给对应PPU或SPU时,才会变成"指令"!

3.2.5 CPU与其他周边沟通方式:
这里必须要先解释"MEMORY MAPPED I/O"!所谓MEMORY MAPPED I/O"是指I/O界
面位址和记忆体位址使用相同的位址空间,此时界面暂存器是记忆体系统的一
部份!界面单元和记忆体使用相同指令存取,资料位址指到记忆体,即是和记忆
体沟通;如果指到界面暂存器位址范围即是要进行I/O动作!

大部份的TVGAME及ARCADE都是设计成MEMORY MAPPED I/O,以这种方式对其周
边作存取动作,所以知道你所要模拟的系统的MEMORY MAP是很重要的事!尤其
是对PPU,SPU的存取(设定执行某一特定功能,或传递图形或声音资料),知道
细部的MEMORY MAP结构,及每个BIT的用途,才能正确的模拟出PPU,SPU的功能!

在MEMORY MAP中,属于PPU,SPU功能的段落的位址上的字组中,每一个BYTE上的
每一个BIT都有特定的用途,这些就是CPU与周边沟通的窗口!CPU把设定好的资
料,写入到正确的位址,就等于是叫PPU,SPU执行某一个特定的功能!详细的执
行这些功能的时机,我没有找到资料!比较可能执行时机可能为两种,一是CPU
直接中断PPU,SPU,然后PPU,SPU检查这些窗口,执行对应的功能!一是PPU,SPU
在定期中断CPU时,同时检查这些窗口,再执行对应的功能!不过写成模拟器时,
都是在CPU暂停的期间,PPU,SPU再去检查这些窗口,执行对应的功能!

3.2.6 DSP的功能
在大部分的情况下,CPU不需要处理大量的数字资料,所以在选择CPU时,一般并
不刻意挑选数值处理速度极快而高价的CPU,而是在CPU之外,另外搭配高速的
DSP,协助CPU处理数值资料,类似于PC上的FPU,当然PC现在都是内建的,在386
及486SX的时代,FPU是选购项目!在特殊的情况下,DSP是属于可加入式的,例如
SFC便是可在卡匣上加装DSP协助CPU处理大量多边型的运算,而不是固定在基
版上,因为它并不是必要的!不过因为需要处理的资料越来越多,新一代的TV
GAME及ARCADE系统都内建了DSP,以免数值资料处理过慢,拖累整个执行速度!
在你找资料时,也必须找到CPU与DSP沟通的方式,DSP如何处理资料,以模拟出
DSP的功能!

------------------------------------------------------------------------
3.3 PPU(图形处理单元运作)方式:
3.3.0 PPU(图形处理单元)的功能及地位:
PPU是整个TV GAME/ARCADE系统的重心,游戏画面能做到多炫,特效多少,完全
都是由PPU去表现!而CPU只是负责指挥的工作,所以使用同一个CPU的系统,如
果所使用的PPU能力不同的话,所呈现的效果也将有很大的不同!这是模拟器所
要模拟的重心,整个模拟器完成度几乎全取决于PPU及SPU是否能模拟的完美!

3.3.1 PPU的组成:
包含PICTURE PROCCESSOR,专用RAM,专用ROM,数位类比转换器等!简图如下:

PPU
┌───┐ +---------------------------+
├───┤ | ┌───┐ |
│DATA ├─┬──────┬─┬┤RAM │ |
├───┤┌┴─┐| ┌─┴┐││(VRAM)│ |
│ ││CPU ├──┤PP ││├───┤ |
│ ││ │| │ │├┤ROM │ |
│ │└──┘| └──┘││ │ |
│ │ | │├───┤ |
└───┘ | ││DAC │ |
ROM&RAM | └┤ ├──── VIDEO OUT
| └───┘ |
+---------------------------+

CPU透过MEMORY MAP中的各个窗口,与PP(图形处理器,PICTURE PROCCESSOR)沟
通并设定欲执行的功能!PP便到从ROM&RAM中搬移所要处理的资料到自己的RAM
中!接下来是功能解码,再从ROM中找出应该执行的指令,依序执行之后,写回RAM
,待萤幕更新周期时,由DAC(数位-类比转换器)转换成MONITOR能接受的讯号,
显示到萤幕!
3.3.2 VBLANK & HBLANK:
由于萤幕(MONITOR)的更新是由电子束打在对应的点,让点上的萤光质发光,藉
以显示出要显示的图形!电子束打的方式是一个接着一个,由左至右,由上至下
,因为视觉暂留的现象,人眼看到的就是整个萤幕的图形!电子束从每一行的最
右边回到下一行的最左边,这一段时间称为H-BLANK,水平空白期!同样的,电子
束会从右下角再回到左上角,准备做下一次整个萤幕的更新,这一段时间称为
V-BLANK,垂直空白期!HBLANK与VBLANK的时间长短与移动的距离有关,行移动
的HBLANK时间较短,而VBLANK的时间较长,所以PPU使用VBALNK时间做一些工作
是很重要的事!这也是写模拟器及搜集资料时非常重要的一部份!

------------------------------------------------------------------------
3.4 SPU(声音处理单元)运作方式:
3.4.0 SPU(图形处理单元)的功能及地位:
与PPU一样,SPU用来专职处理声音.在比较古早的系统中,一般并不另外搭配专
用的处理器,而是由CPU直接控制.不过因为声音资料日益增多,于是另外搭配
专用的处理器,以减轻CPU的负担!随着CPU速度的加快,所搭配的SOUND CPU也
日益高级,从Z80,变成MOTOROLA 68000,逐渐变快!

3.4.1 SPU的组成:
包含SUUND PROCCESSOR,专用RAM,专用ROM,专用DSP,数位类比转换器等!简图
如下:

SPU
┌───┐ +---------------------------+
├───┤ | ┌───┐ |
│DATA ├─┬──────┬─┬┤RAM & │ |
├───┤┌┴─┐| ┌─┴┐││ROM │ |
│ ││CPU ├──┤SP ││├───┤ |
│ ││ │| │ ├┴┤SOUND │ |
│ │└──┘| └──┘┌┤CHIP │ |
│ │ | │├───┤ |
└───┘ | ││DAC │ |
ROM&RAM | └┤ ├──── SOUND OUT
| └───┘ |
+---------------------------+

大致上与PPU的组成是一样的!
CPU透过MEMORY MAP中的各个窗口,与SP(声音处理器,SOUND PROCCESSOR)沟通
并设定欲执行的功能!SP便到从ROM&RAM中搬移所要处理的资料到自己的RAM中
!接下来是功能解码,再从ROM中找出应该执行的指令,依序执行并指挥声音晶
片(SOUND CHIP)产生实际的声音资料,传递给DAC转换成声音讯号!

3.4.2 与PPU的不同处:
最大的不同处在于MONITOR还有VBLANK&HBLANK可以做很多事!SPU则必须不间
断的产生声音输出,同时声音晶片的模拟也是一大问题,这是模拟器的一大障
碍,所以模拟器一般都是发展到一定阶段了,机器的等级也到一定速度了,才逐
渐加入声音的支援!

3.4.3 声音处理器专用DSP的功能:
因为所选用的声音处理器不一定适于处理声音资料,尤其是FM与PCM有极大的
不同,PCM需要更强大的数值运算能力,因此有些系统会加入声音处理器专用
DSP,协助声音处理器处理PCM的运算!这也是搜集资料时的重要部份!

3.4.4 声音处理器的指令:
一般来说,CPU传给声音处理器的资料,将会是这个声音处理器的指令.所以必
须加入CPU CORE处理指令解码,例如是Z80的,就必须加入一个Z80的CPU CORE,
用来处理声音资料,同样有频率及定址空间的设计,整个SPU基本上就是一个隐
含的完整电脑系统!

------------------------------------------------------------------------
3.5 ROM & RAM AREA
3.5.0 SYSTEM ROM:
在某些TVGAME或ARCADE基版上会加入所谓SYSTEM ROM,这个SYSTEM ROM有几种
功能:
A.执行开机检查程式及O.S.!
B.储放重要或常用函式!
C.储放PPU,SPU常用函式!
D.储放中断向量及中断处理常式
这样的作法,明显的可以减轻GAME ROM的成本,不用每一个GAME ROM都需要挪
出空间放置这些程式,而使得成本降低!简单的说,SYSTEM ROM就等于是PC上的
作业系统!通常设置的目的是方便游戏开发人员能方便的使用一些特殊或常用
函式,基版设计厂商则必须尽量加入需要的功能函式!所以,如果你想写的主机
上有SYSTEM ROM的话,你必须想办法DUMP下来,因为不管是CPU,或者可能有PPU
,SPU都会存取到这里,执行这里的程式!

3.5.1 没有SYSTEM ROM的基版:
大部分为单一游戏或非系统基版的情况!必须在GAME ROM中拨出空间放置等同
于SYSTEM ROM功能的程式,因为是单一游戏,可以依个别情况撰写,空间的损失
并不大!通常会在基版开机时,就会从ROM中载入并执行开机检查程式!

3.5.2 GAME ROM
除去了SYSTEM ROM的部份,剩下来的就是GAME ROM的部份,按照功能可以分成:
A.PROGRAM AREA 程式区:
提供CPU指令的区域!
B.GRAGHIX DATA AREA 图形资料区 :
提供PPU图形资料的部份!
C.SOUND DATA AREA 声音资料区:
提供SPU声音资料的部份!
D.其他资料区

不同的基版设计,处理这些ROM的方式也不同!以家用主机大部分的设计来说,
所有的ROM DATA是按照其位址分开的,从外观上不易看出,DUMP时也都是直接
DUMP成一整个ROM IMAGE FILE(目前只有FC有分开DUMP的ROM IMAGE FILE出现
)!其原因应该是卡匣的体积,面积有限,不适于分开放置!

ps: 这也可能是DUMP工具的问题,家用主机的ROM DUMPER(或称为ROM COPIER)比
较常见的都并不是一个ROM IC一个ROM IC的DUMP!

相对于家用主机的情形,ARCADE则是能够从ROM IC放置的区域分别出这颗ROM
IC的内容及用途!一般来说,在DUMP这些基版的ROM IC时,都会按照其用途或
SCHEMATICS 上的编号命名,方便识别及载入至正确位址!

3.5.3 以光碟做媒介的ROM -- CD ROM:
CD ROM的处理方式与一般的卡匣或是基版上的ROM IC载入的方式并不相同!因
为必须从CD ROM中分段载入所需要的程式段,图形资料段,声音段!所以分区的
情况不明显,而是以当时游戏进行的状况处理!

3.5.4 GAME ROM的MAPPER 问题:
如果你是准备写家用主机的模拟器的话,所要面临的便是可能出现一个ROM
MAPPER 问题,ROM MAPPER问题是指MEMORY MAP中提供给ROM的区域比实际ROM
的容量还小,于是必需存在一个切换ROM BANK的暂存器(以MEMORY MAPPING I/O
方式读写)或是其他的机制,利用这些机制与MEMORY MAP中的ROM 区域而使得
能读取到更大的GAME ROM!这些机制就被称为MAPPER,模拟出MAPPER的机制就
是MAPPER问题!目前这个问题仍然困扰着FC模拟器的作者们,因为已知的MAPPER
格式就超过40种,不管是不支援某一种MAPPER或是MAPPER程式写的不好,这都
不能使你的模拟器完美!是你在搜集资料时必须注意到的!

3.5.5 如何开发游戏:
一般来说,主机公司会提供开发工具,提供游戏厂商开发游戏!游戏厂商利用开
发工具,撰写高阶的原始程式,绘制图形,编写乐谱及音效等,经过DEBUG后,把
全部的档案经过排列之后做成转烧成ROM IC,或是直接压制成CD!

3.5.6 ROM中的程式如何控制硬体:
应该有三种方式:
A.直接控制:
在开发时撰写低阶的硬体直接控制程式,理论上效能较快且可能创造出不
同的效果!游戏厂商需对硬体有相当认识,同时开发较不易!
B.呼叫SYSTEM ROM中的函式:
算是一种间接控制的方式.写高阶程式但是以呼叫SYSTEM ROM中的函式来
控制硬体!SYSTEM ROM中的函式因为容量因素,肯定不会大到哪去也不会
太多,游戏厂商还是大部分需要自己写!效能亦相当不错!
C.利用开发工具中内附的函式:
利用开发工具是最方便的方式,开发工具内附的函式到最后仍然会展成前
面两种方式控制硬体!不过开发工具的良窳会影响到游戏的开发,这点也
是很重要!
实际上通常都是这三种方式交叉使用,端看游戏厂商的能力!

------------------------------------------------------------------------
3.6 摇杆控制装置及钱币计数器等NMI的处理
3.6.0 摇杆通知CPU有按键按下的原理:
当有按键按下时,连接在基版的控制晶片会发出对CPU发出一个中断讯号,并且
把按键资讯写入到分配在MEMORY MAP中的位址!CPU接收到这个中断讯号之后,
中断现在的工作,先处理按键输入!处理完之后,在回到中断前的程式,并根据
接收到的按键讯息做反应!看起来会像这样:

┌───┐
│ │
┌──┐ ┌────┐ ├───┤
│摇杆├─┤控制晶片├─┤ ├┐
└──┘ └────┘ ├───┤│┌──┐
│ │└┤CPU ├─ 执行
│ │ └──┘
└───┘
记忆体中存放按键资讯的位址(MEMORY MAPPED IO)

3.6.1 MEMORY MAP中分配给摇杆控制装置的方式:
因为TVGAME,ARCADE一般都是使用数位式的摇杆,也就是只有[按下]及[没按下]
两种反应,分别以1BIT代替一个按键,看起来会像这样:
上 下 左 右 B1 B2 B3 B4
┌─┬─┬─┬─┬─┬─┬─┬─┐
│7 │6 │5 │4 │3 │2 │1 │0 │
└─┴─┴─┴─┴─┴─┴─┴─┘
按键多一点的用2 byte:
上 下 左 右 B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 B12
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│7 │6 │5 │4 │3 │2 │1 │0 │7 │6 │5 │4 │3 │2 │1 │0 │
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
更多的按键的,像麻将,当然就继续加下去!几P就加几组了!

3.6.3 非数位式摇杆的输入:
就流程上基本上是与数位式摇杆相同!问题在于如何输入资料于给定的位址中
,随着不同的摇杆会有不同的设计,这是搜集资料时一定找到的!但是找到之后
,更严重的问题是如何对应到PC的指向装置!要是不幸找不到对应的装置,你基
本上就直接放弃好了!

3.6.4 钱币计数器,START键等NMI的处理:
NMI是指"NON-MASKABLE INTERRUPT(NON-MASKED INTERRUPT)",也就是不可遮
罩的中断!这与一般中断有何不同呢?相对于NMI,其他的中断是属于可以被暂
时忽略(称为MASK掉),CPU可稍后再处理!而NMI是CPU无论如何都必须放下手边
的工作,必须优先处理这个中断!在TVGAME系统中,PPU的VBLANK就是属于一个
NMI,因为它必须萤幕更新等动作,此时PPU必须抢到周边的使用权,它会强迫
CPU暂停手边的工作!同样的,在ARCADE系统中,像投钱,1P select,1P start,
2P select,2P start,这种动作当然是一定要优先处理!不过.有些产生NMI的
装置,如PPU,还是可以利用一些暂存器的设定,使它暂时不要产生NMI,这些暂
存器的位址,设定的方式,也是在搜集资料时要找到的!

------------------------------------------------------------------------
3.7 RESET处理:
3.7.0 加入RESET的原因:
不管是TVGAME还是ARCADE,游戏通常都是不会停止的!问题是,还是有某些情况
必须要重新开机一次,例如:进到硬体测试画面,设定DIP SWITCH,使用某些密
技,打不赢人家耍贱,就是想重新开机等等!更重要的是,不断开关电源会可能
导致硬体损坏!所以必须要有一种机制可以暂时切断电源,使系统重新开机,这
种机制便是RESET!
3.7.1 RESET的流程:
RESET的第一步当然是暂时切断电源!重新初始化CPU,周边等!载入SYSTEM ROM
进行开机测试,检查是否有需要处理特殊程序,如:进到硬体测试画面,设定DIP
SWITCH!如果没有,再载入GAME ROM,开始游戏!

------------------------------------------------------------------------
3.8 DIP SWITCH:
3.8.0 加入DIP SWITCH的原因:
因为ARCADE系统是一个赚钱的工具,一成不变的游戏在被人摸熟时,就没有人
玩了,同时还有不少游戏的参数要设定,所以需要一种方便的储存游戏参数的
工具,于是在基版上加上DIP SWITCH,只要开关拨一拨,就能设定参数!不需要
外加电源!但是新一代的基版有些就不是使用DIP SWICH,而是利用EEPROM储存
,这样就不须要用手拨DIP SWITCH了!

3.8.1 DIP SWITCH的作用方式:
说穿了,也只不过是一些逻辑电路!但是利用程式检查电路短路与否,赋予实际
的意义,例如:投多少COIN才算一次CREDIT;设定出红血看起来暴力一点;或是
无敌之类的!不过少了设定DIP SWITCH的功能,并不会有什么问题,只是没那么
完美罢了!但是DIP SWITCH是用"手"设定的,所以如果要做DIP SWITCH就要做
设定函式代替用手拨开关!当然你还是需要找到该游戏DIP SWITCH图,才能知
道各开关的定义!

------------------------------------------------------------------------
3.9 BACKUP DEVICE

3.9.0 需要BACKUP DEVICE的原因:
不是每一种游戏都能一次玩完的,例如RPG,SLG等,于是需要一种能保存资料的
装置!古早之前,是利用特殊编码的CODE来保存资料!不过这不是办法,越复杂
的游戏或是需要保存的资料越多,CODE会变得越来越长!于是出现划时代的装
置---利用电池提供SRAM的电源,把资料储存在SRAM中!一般称为"电池记忆",
但是用电池还是有问题--电池是有寿命的!新一代的便是使用FLASH ROM,不须
外加电源,可重复写入!

3.9.1 存取BACKUP DEVICE的方式:
和其他装置一样,在MEMORY MAP有保留给BACKUP DEVICE的空间,只要把资料搬
过去就行了!在此之前,当然是需要先知道MEMORY MAP的空间在哪里!

------------------------------------------------------------------------
3.A DMA CONTROLLER
3.A.0 什么是DMA(DIRECT MEMORY ACCESS) CONTROLLER?
详细的原理及机制请找计算机组织之类的书!总之就是不想让资料传输通道闲
着及减轻CPU的负担,使CPU不需要自己处理资料的搬移!于是设计了一个专门
处理资料搬移的处理器,便称为DMA CONTROLLER,处理的范围是RAM与周边I/O!
因为它也是一个处理器,所以CPU与DMA CONTROLLER的沟通是有一定程序的!如
果想写的系统也具有DMA控制器的话,就必须搜集有关DMA控制器的资料!

3.A.1 DMA的运作方式(从书上抄来的):
COMPUTER ORGANIZATION & DESIGN --- DAVID A. PETERSON/JOHN L. HENNESSY
DMA传送中有三步骤:
A.由装置本身提供DMA,而操作命令是根据记忆体位址,位元数目并在装置上
执行资料的传送.
B.DMA在装置上开始操作命令并重才汇流排.当资料可用时(从装置或记忆体
),它传送此资料.而DMA装置对读取或写入提供记忆体位址.若在汇流排上
需求资料超过一次以上,DMA 单元将产生下一个记忆体位址并启动下一次
传送.DMA使用这种机制,在不用干扰处理机情况下,它能传送数千位元组
资料.在很多DMA控制器包含一些缓冲区允许它们在传送资料或等待成为
汇流排主控器时来处理延滞时间的可塑性.
C.一旦DMA传送完成时,控制器将中断处理器,然后处理器查询DMA装置或记
忆体,是否整个操作命令成功地完成.

------------------------------------------------------------------------
3.B 光碟机装置
3.B.0 使用CD-ROM DRIVER的原因:
随着游戏的声光效果越来越好,GAME ROM的容量也随之不断上升!原来所使用
的ROM IC在应用上已经有所困难!这个困难包括两方面,卡匣的尺寸会随着ROM
IC的增加而变大,另一方面,容量越大的游戏,不是ROM IC用的更多,就是必须
改用大容量的ROM IC.而不管哪一种,显而易见地都会带来成本上升的影响,造
成卡匣价钱上升!就目前而言,解决的方式有两种:一是对资料进行压缩,仍然
使用ROM IC制成卡匣的型式,优点是保有卡匣不须等待,速度较快的优势,缺点
是就算是压缩过后,还是不一定能解决容量的问题,语音及动画都是属于不易
再压缩的资料,只好忍痛删除,例如N64!另一种解决的方式是改用CD-ROM作储
存媒体,优点是再没有容量上的限制,只要增加CD-ROM片数即可,同时成本低廉
,价钱便宜!缺点是CD-ROM的存取时间远大于卡匣!例如:一堆次世代机!

3.B.1 CD-ROM的存取方式:
CD-ROM的容量有650Mbyte,大部份使用CD-ROM DRIVER的系统,其主记忆体通常
并不足以容?#123;所有资料存放于主记忆体中,于是必须以分段读取的方式存取!

┌─────────┐
│CPU 读取某一段资料│
└────┬────┘

┌────┴─────┐
│是否存在于主记忆体中│
YES └────┬─────┘ NO
┌────────┴─────────┐
│ │
┌───┴────┐ ┌────┴─────┐
│ 从主记忆体读取 │ │ 是否存在于缓冲区中 │
└───┬────┘ YES └────┬─────┘ NO
│ ┌───────┴───────┐
│ ┌───┴─────┐ ┌───────┴──────┐
│ │ 从缓冲区搬移资料 │ │ 对CD-ROM控制器提出读取要求│
│ │ 到主记忆体 │ └──────┬───────┘
│ └───┬─────┘ │
│ │ ┌──────┴────┐
│ │ │现在的碟片存在此资料 │
│ │ YES └────┬──────┘ NO
│ │ ┌────────┴───────┐
│ │┌──┴──┐ ┌───┴┐
│ ││ 从目前的 │ │换片读取│
│ ││ 碟片读取 │ └─┬──┘
│ │└──┬──┘ │
│ │ │ │
│ │ └──────┬───────┘
│ │ ┌────┴───────┐
│ │ │ 资料从碟片读入至缓冲区 │
│ │ └────┬───────┘
│ │ ┌─────┴────────┐
│ │ │资料从缓冲区COPY至主记忆体 │
│ │ └─────┬────────┘
│ │ ┌─────┴───────┐
│ │ │CD-ROM控制器对CPU发出中断,│
│ └────┤通知CPU已完成读取动作! │
│ └─────┬───────┘
│ │
└─────────┬───────────┘
┌─────┴────┐
│CPU完成资料读取动作 │
└──────────┘

这一个流程图实际上还忽略了CD-ROM DRIVER硬体的动作,这才是真正最耗费
时间的地方!

另外这其实不太像TV GAME主机会干的事,这样的检查算起来还是太多,应该是
跳过第一个判断,因为需常驻的资料区及指令区不动,剩下的MAIN MEMORY全部
使用画面处理及声音上,旧资料全部放弃,从CD ROM中读取新资料,自然就不用
检查资料在不在!优点是分配处理画面及声音的MEMORY够大,可以作出超炫CGA
及全程语音,缺点是CD ROM DRIVER的SEEK TIME过慢及BUFFER过小时,等待时
间会很长,又需要利用画面处理的技巧解决及妥善安排档案位置解决!

无论如何你需要知道系统对CD-ROM存取的详细流程,所发出的中断要求,所分
配的MEMORY MAP位置,容量等!

------------------------------------------------------------------------
3.C 其他周边
对于ARCADE系统中,当然还有其他的周边,我建议可以到亚站及巴站的ARCADE
版中,找寻相关的资料!
对于TV GAME系统,所剩下的就是接到TV上了!
[ 第四步 模拟器的运作方式 ]
4.0 模拟器与真实的硬体还是有差距的
姑且不论模拟的像不像,好不好,从执行时的情况看来,真实的硬体是允许平行
处理的,而模拟器却是以循序的方式,轮流使用PC CPU的资源!有很多硬体的机
制是模拟器做不来的,全盘以硬体的角度看模拟器,会发现有很多东东是无法
实作的,单从模拟器的角度看硬体,也会发现文件实在漏掉太多东西!以下的大
部分内容是以解读SNES9X 1.10,MAME 35b5的原始档内容所撰写的,就SFC的硬
体而言,算是满符合上面所分类的项目,除了缺少CD-ROM DRIVER的部份之外,
此外有很多都是我个人的想法,不一定是正确的,这点一定要知道!

------------------------------------------------------------------------
4.1 CPU的处理
当你选定了CPU之后,自然便是需要模拟那个CPU的运行!模拟器的CPU与真实的
CPU有很大的不同,可以分为:
A.CPU CORE:负责解译指令!
B.EXEC_CPU:负责流程控制!
CPU CORE与EXEC_CPU组成了模拟器的核心,CPU与外界的接触及排程由EXEC_CPU
控制,在NMI,SPU,其他装置何时执行,在其余时间再把ROM中的指令交由CPU CORE
解译及执行!执行的流程是由EXEC_CPU检查需不需要执行NMI,SPU,及其他装置
,若需要执行,则执行NMI等相关程式,若不需要执行,则从ROM中抓取指令交由
CPU CORE解译及执行,CPU CORE处理完再交回EXEC_CPU检查需不需要执行NMI,
SPU,及其他装置,直到程式结束为止!
以流程图表示:
┌───┐
│ 开始 │
└─┬─┘
┌──────────→│
│ ↓
│ ┌───────┐ EXEC_CPU
│ │ 取下一个指令 │
│ └───┬───┘ ↓
│ │-----------------
│ ↓ ↑
│ ┌───────┐
│ │ 执行指令 │ CPU CORE
│ └───┬───┘ ↓
│ │-----------------
│ ↓ ↑
│ ┌──┐ ┌────────┐
│ │停止│←┤SHOTDOWN CPU │ EXEC_CPU
│ └──┘ │检查中断 │
│ │行程中断 │
│ │执行或遮掉中断 │<---- 此时模拟器CPU等于
│ └────┬───┘ 处于暂停状况
│ │
└───────────┘
此时模拟器CPU等于又开始执行下一个指令


4.1.0 CPU CORE
CPU CORE的名称应该是指这个程式承继了真正CPU的核心,实际上也是,整个CPU
CORE只保留了暂存器,定址模式,OPCODE的解译及执行(含ALU,CU,IU,OU),PC(
PROGRAM COUNTER,程式计数器)改为整体变数(或EXEC_CPU的区域变数)等,去
除掉所有的中间暂存器(IR,MBR,MAR)!但是因为缺少了时序周期,必须增加另
外一个变数CLOCK_COUNT,当成时序周期来用,并对所有的OP-CODE加上一个会
用掉多少CLOCK的数值,当执行某一个OP-CODE 之后,CLOCK_COUNT必须减掉这
个数值,表示经过了多少个CLOCK!

有许多CPU CORE的作者,同样的也会有不少不同种类的CPU CORE!不同的CPU
CORE效果也大不相同!其中的差别在于:
A.所使用的语言不同:
以组合语言及C语言两种为主,一般以组合语言所写的速度较快!原因是可
以避免掉编译时所产生多余的码,不过实际上还必须评估每个OP-CODE花
多少码模拟,执行将花费多少时间等!
B.对OP-CODE解译的方式不同:
怎么做到有效率的模拟及正确的模拟OP-CODE,一直是CPU CORE作者的目
标,问题是每个人对于OP-CODE的解释不同,实作出来的CPU CORE便会发生
解译不同的情形!这种解译不同的情况,严重时,会导致模拟的效果不正确!

4.1.1 套用现成的CPU CORE :
有现成的CPU CORE 时,当然就是想办法直接套用!要注意套用的程序,例如,必
须先初始化CPU CORE,设定一个定址空间交给CPU CORE等!所以在套用之前,一
定需要先看过说明档,以求了解套用的程序!

4.1.2 自己做一个CPU CORE:
只好自己做一个!做一个CPU CORE模拟器,说难不难,说简单也是骗人!当然一
个简单的方式便是修改别人的CPU CORE的OP-CODE部份,修改成为你心目中认
为的OP-CODE模拟方式!我没有实际做一个CPU CORE模拟器的经验,只能有观
察别人的模拟器中提供一些经验!
需要提供几个重要的函式,这是CPU CORE的外观:
A.RESET_CPU(INITIAL_CPU):
把CPU CORE的状态恢复至初始时的状态!各暂存器,旗标,程式计数器!
B.CPU_SHUTDOWN:
释放CPU CORE所占用的记忆体!
C.OP-CODE DECODER&EXECUTOR:
解译并执行OPCODE的程式!写法很多,CASE-SWITCH,或是函式阵列等!不管
是CASE-SWITCH或是函式阵列都是利用转译前OP-CODE的十六进位数值当
成判断的依据!

就结构上来说,需要实作出这是CPU CORE的内在:
A.CPU的暂存器,旗标等!你可以直接定义一个CPU的结构,这样应用上较为便
利!
B.CPU的定址模式!直接定址指令为何,间接定址指令如何处理等!
C.CPU所用的每个OP-CODE,实作的方式满多的,需要ALU的OP-CODE直接用四
则运算代替等!你必须要先知道这个CPU提供多少种OP-CODE,每个OP-CODE
的运作方式,OP-CODE如何解码等,再一一实作出每个OP-CODE来!
我目前的程度,只能提供意见到这里!

4.1.3 MULTI-PROCCESSOR的场合:
面对多处理机的系统时,不可避免的问题就是:
A.必须实作出好几个CPU!
B.必须实作CPU的沟通方式!
对于问题A而言,可以以定义出CPU结构再宣告多个CPU结构当成有多个CPU,而
CPU CORE共用同一份!对于问题B而言,如果是MASTER/SLAVE系统,就是在
MASTER CPU的EXEC_CPU中多实作出处理SLAVE CPU中断及其他沟通的方式等!

4.1.4 CPU与其他周边沟通方式的处理
简单的说,就是处理中断的方式!真实的硬体是可以平行运作的,但是模拟器可
不行,至少在没办法写成几乎可平行运作又可相互沟通的执行绪前不行!由前
面的流程图可以发现,必须在EXEC_CPU中排入中断检查,中断执行,和一切周边
状态检查及更新的程式片段!整理一下,包括:
A.SPU状态的检查:
SPU状态的检查在整个模拟器占了很重要的地位,因为声音不能有任何中
断现象,所以几乎在每个函式都必须执行一次,在较长的函式中甚至必须
执行许多次,以确保声音不会中断!
B.摇杆状态检查:
检查摇杆按键是否有按下的讯息传来!
C.NMI中断检查:
检查是否有NMI中断发生,依其优先顺序执行!
D.一般中断检查:
检查是否有中断发生,依其优先顺序执行!
E.PPU定期资料更新:
简单的说就是画面的更新!前面CPU CORE段落中说到必须将每个OP-CODE
加上一个用掉多少CLOCK TIME的常数,在把PPU定期资料更新周期定义成
多少个CLOCK COUNT,每执行一个OP-CODE就扣掉一些,等到变成负数或等
于零时,就表示定期资料更新周期到了,就去执行资料更新的函式!这就
是没有时序产生器以产生时序的变通方法!
F.SPU定期资料更新:
若SPU中含有TIMER,则必须于一定时间时更新状态!这可保持整个模拟器
以一定的时序同步执行,因为有些装置是需要同步执行的!这个TIMER的
计算方式是以相对于多少CPU CLOCK TIME或是以相对于多少CLOCK COUNT
来计算!

4.1.5 DSP的处理
实际上有两种类型的DSP,这两种在处理有很大的分别:
A.模拟这个DSP的外在行为:
模拟时需要做一个DSP的结构与CPU沟通,但是实际上的内部执行只是呼叫
函式而已!例如给DSP算一个COS值等!这是模拟这个DSP的外在行为就可以
了!
B.模拟这个DSP的内在行为:
最明显的例子就是SFC的SuperFX晶片,这其实是一个RISC的处理器,必须
模拟出这个处理器的内在行为,OP-CODE等,就像模拟一个CPU一样!

------------------------------------------------------------------------
4.2 MEMORY MAP的处理
简单的说,就是配置所需的记忆体,包含ROM,RAM,SRAM,VRAM(PPU用)等!并载入
至对应的区域内!同时在模拟器结束时必须释放出所配置的记忆体!需要实作
出:
A.INIT MEMORY MAP:
也就是初始化MEMORY MAP,对ROM,RAM,SRAM,VRAM(PPU用)都配置足够大的
记忆体!不过倒是不需要只配置一大块记忆体当成整个MEMORY MAP,分成
RAM区,SRAM区,ROM区也可以,但是存取时必须视为一整块MEMORY MAP!另
外还有一些需要配置的记忆体区块,也能在此一同处理!
B.FREE MEMORY MAP:
在模拟器结束时,必须释放出原来配置的记忆体,在这个函式中处理!
C.LOAD ROM IMAGE FILE:
档案处理的部份不在这边做,这里直接用档案指标参数操作!把档案内容
搬移到MEMORY MAP中分配给ROM的区域!一般而言,除了CD-ROM之外,都是
配置出足够的大小,很明显的,如果ROM IMAGE有50MBYTE,就需要配置出
50MBYTE!这里需要注意一下,有些系统的ROM区域是分段的,LOW ROM&HIGH
ROM,也需特别处理!
D.LOAD/SAVE SRAM FILE:
与LOAD ROM IMAGE FILE处理的方式一样!比较不一样的是,SRAM FILE要
随时准备存取,ROM IMAGE FILE一般只有在更换游戏时才会更动!
E.LOAD SYSTEM ROM FILE:
与LOAD ROM IMAGE FILE处理的方式一样!
F.FIX ROM SPEED:
有时候,会需要模拟ROM存取的速度!
G.特殊晶片的ROM IMAGE处理:
有些晶片或是PPU,SPU会有用到专用的ROM,原则上仍然等同LOAD ROM
IMAGE FILE的方式处理!
这里提供的还是原则性的写法,详细的内容,还是得多看看别人的处理方法!
4.3 PPU的处理
4.3.0 PPU的处理
在大部分的情况而言,通常都不会知道PPU是什么晶片,所用的OP-CODE等,也找
不到相关的资料,通常找到的是CPU透过MEMORY MAP的窗口与PPU沟通的资料,
所以模拟的方式一般都是模拟外在的行为,而不是模拟内在的操作情形!无论
如何,需要提供:
A.RESET PPU(INIT PPU):
重新把每个MEMORY MAP所对应的功能的参数,恢复成初始的状态!
B.CPU设定PPU的函式:
也就是处理那些MEMORY MAP的功能的函式!与CPU的OP-CODE不太一样,这
里较常用CASE-SWITCH的结构!原因是速度较快吧!以MEMORY MAP所分配的
"位址"当成SWITCH的判断依据!
C.CPU读取PPU处理后状态的函式:
也就是处理完那些MEMORY MAP的功能之后,预备由CPU读取的函式!也是使
用CASE-SWITCH的结构!这里不一样的地方是,并不是每个功能都有传回资
料,某些CASE的处理就可直接跳过!也是以MEMORY MAP所分配的"位址"当
成SWITCH的判断依据!
不过这都只是外观而已,真正的还是模拟每个功能的程式,一般找得到的资料
会像这个样子,以SNES9X为例:

SNES Documentation v2.30: Written by Yoshi
----------------------------------------------------------------------------
|rwd2?|Address|Title & Explanation |
||||||-----------------------------------------------------------------------|
|||||| |
||||||__ ?: Don't know what the statistics on this register are |
|||||____ 2: 2 byte (1 word) length register |
||||_____ d: Double-byte write required when writing to this register |
|||______ w: Writable register |
||_______ r: Readable register |
| |
|Words in brackets ( [] ) are the official "names" of the registers |
|Words in braces ( {} ) are different from the "real" SNES manual |
|Bits define 1 as "ON/ENABLE" and 0 as "OFF/DISABLE," unless otherwise stated|
|Registers without any bits/defined-data can be assumed to be 8 bits in size |
|and should only be read once. |
----------------------------------------------------------------------------
|rwd2?|Address|Title & Explanation |
|----------------------------------------------------------------------------|
| w |$2100 |Screen display register [INIDISP] |
| | |x000bbbb x: 0 = Screen on. |
| | | 1 = Screen off. |
| | | bbbb: Brightness ($0-$F). |
| | | |
| | | |
----------------------------------------------------------------------------
按照说明写成对应的程式:
========================================================================
case 0x2100:
// Brightness and screen blank bit
if (Byte != Memory.FillRAM [0x2100])
{
FLUSH_REDRAW ();
if (PPU.Brightness != (Byte & 0xF))
{
IPPU.ColorsChanged = TRUE;
PPU.Brightness = Byte & 0xF;
S9xFixColourBrightness ();
if (PPU.Brightness > IPPU.MaxBrightness)
IPPU.MaxBrightness = PPU.Brightness;
}
if ((Memory.FillRAM[0x2100] & 0x80) != (Byte & 0x80))
{
IPPU.ColorsChanged = TRUE;
PPU.ForcedBlanking = (Byte >> 7) & 1;
}
}
break;
========================================================================
关于如何写才算完美,对不起,我没有办法说明!而实际上,这就是一个模拟器
好坏的差别!

需要补充的是,通常在CPU设定PPU的函式中会把像SPU,DMA等同样使用CASE-
SWITCH结构的函式放在同一个CASE-SWITCH结构中!只是执行的函式本体另外
写而已!

4.3.1 实际绘图的动作:
上面拉拉杂杂一堆,就整个PPU而言,仍然只是外观而已!只是设定PPU去做某一
件事,设定它的参数,你还是得实作出真正绘图的动作!这里还是受限于我的知
识不足,等到以后再补充!另外,需要做一个初始动作的函式,这是处理PPU内部
的初始化!

4.3.2 SCREEN REFRESH
SCREEN REFRESH有两种:
A.模拟硬体处理SCREEN REFRESH的动作:
这里还是受限于我的知识不足,等到以后再补充!
B.实际显示出画面于MONITOR上:
这里还是受限于我的知识不足,等到以后再补充!

------------------------------------------------------------------------
4.4 SPU运作方式:
4.4.0 SPU(图形处理单元)的功能及地位:
4.4.1 SPU的组成:
4.4.2 与PPU的不同处:
4.4.3 声音处理器专用DSP的功能:
4.4.4 声音处理器的指令:
------------------------------------------------------------------------
4.5 摇杆控制装置及钱币计数器等NMI的处理
4.5.0 MEMORY MAP中处理摇杆控制装置的方式:
除了一些特殊控制器之外,摇杆上的每个按键或是方向键都是用1BIT表示其状
况--按下或是没按下!直接由MEMORY MAP所分配的位址下手,但是因为PC上可
拿来控制的装置太多了,需要一个中间程式转换,大致如下:

┌───┐┌───┐
│键盘 ├┤DRIVER├┐ MEMORY MAP所分配的位址
├───┤├───┤│ ┌───┐
│滑鼠 ├┤DRIVER├┤ │ │
├───┤├───┤│┌────┐├───┤ ┌────┐
│摇杆1 ├┤DRIVER├┼┤转换程式├┤ PAD ├─┤EXEC_CPU│
├───┤├───┤│└────┘├───┤ └────┘
│摇杆2 ├┤DRIVER├┤ │ │
├───┤├───┤│ │ │
│摇杆3 ├┤DRIVER├┘ └───┘
└───┘└───┘
摇杆?表示
另一种摇杆,不是另一只

这样画是稍微不正确,不过讲解很方便!要加入一种新的装置,就需要有专用的
DRIVER才能支援到!所谓专用的DRIVER是指如何读取讯号,怎样解读所得到的
讯号是什么意义,负责这种工作的程式,一些特殊的指向装置都会提供专用的
开发工具,这些开发工具便会提供如何解读所得到的讯号,其实这就是我所指
的DRIVER!DRIVER是对应WINDOWS的说法!摇杆的动作不正常,乱跳啦,不支援某
种摇杆啦,就是DRIVER层级的问题!重新对应所按的按键,或是选择不同的装置
输入讯号,就是转换程式的问题!控制装置所找到的DRIVER不同的话,支援的情
况就有很大的差别!就WINDOWS模拟器而言,比较新的通常都使用DIRECT X的
DIRECT INPUT支援不同的指向装置,这样非常方便,只要符合DIRECT INPUT的
函式呼叫,就通用所有指向装置!但是DOS模拟器并没有这么简单,必须一种一
种的支援!

另外我把指向装置切换及按键重定义视作转换程式!转换程式的目的在于适当
的把所选择的指向装置上所按的按键转换成所模拟TV GAME主机的按键资讯字
组!很明显的,具有三种功能:
1.选择所支援的指向装置!
2.可自由定义所选择的指向装置上的按键对应所模拟主机上的按键!
3.转换所选择的指向装置上的按键定义成为所模拟主机使用的资讯!
不过其实还有第四种功能:
4.热键(HOT KEY)处理界面!

转换程式一般都是利用CASE-SWITCH结构设计,不论是处理何种指向装置的DRIVER
提供的讯息!而K/B的所使用的CASE-SWITCH结构,还可以加入热键处理,例如呼
叫GUI,RESET,FRAMESKIP,抓图,当然还有EXIT--退出模拟器,处理方式就是增
加CASE,当KB有按键按下时,属于模拟器摇杆的部份就组合成正确的字组填入
MEMORY MAP的位址,其他的则检查是否属于热键,是则执行对应的热键处理函
式,再不属于热键的就直接忽略掉!

不管是什么指向装置,到了转换程式最后都会变成所模拟主机的按键资讯字组
!然后设定一个中断FLAG,等到模拟器控制权又回到EXEC_CPU时,将会检查这个
中断FLAG,再读取这个按键资讯字组,然后CPU CORE会根据这个字组,执行ROM
中的程式!

4.5.1 特殊指向装置的处理:
下次再写!

4.5.2 钱币计数器,START键等NMI的处理:
不管是何种NMI,都是设定一个中断FLAG,等到模拟器控制权又回到EXEC_CPU时
,将会检查这个中断FLAG,再读取这个按键资讯字组,然后CPU CORE会根据这个
字组,执行中断服务常式或ROM中的程式!

------------------------------------------------------------------------
4.6 RESET处理
算起来这个最简单,把所有相关的初始函式,全部关在一起呼叫一次,就是RESET
了!如果要处理一些特殊动作,也是在这里处理,当所有初始函式执行完成之后
,再执行想要做初期奇怪动作的函式!这个功能的呼叫方式通常是设置在按键
读入的处理函式中,在CASE-SWITCH结构中,多一个CASE处理RESET!

------------------------------------------------------------------------
4.7 DIP SWITCH作用处理方式:
这个也是满简单的,设计一个以位元操作的方式定义出DIP SWITCH的结构,最
好是附以显而易见的定义,最后是设计一个专门处理设定DIP SWITCH功能的函
式,有专用的画面及控制方式等!比较懒的方式是,让使用者自己计算出要设定
DIP SWITCH功能的十六进位数值,模拟器只要读入这个数值即可!这个功能的
呼叫方式通常是设置在按键读入的处理函式中,在CASE-SWITCH结构中,多一个
CASE处理DIP SWITCH!

------------------------------------------------------------------------
4.8 DMA CONTROLLER运作处理方式
模拟器是不可能会有DMA这种机制的,当DMA功能启动时,实际执行时是从EXEC_CPU
函式中直接转移到DMA控制函式,进行字组的搬移,但是要定时或完成一个动作
时,需要模拟DMA CONTROLLER对CPU发出中断的动作,这个动作就是整个DMA函
式的核心,而资料搬移动作反而是最不重要的,因为这个动作就是资料搬移的
动作:
TARGET_MEM = SOURCE_MEM;

还有一个动作要处理,就是CLOCK TIME的动作,不论DMA多快,都不可能在不经
过任何CLOCK TIME就能完成,我们需要模拟CLOCK TIME经过的动作!

你会发现EXEC_CPU转移到DMA控制函式时,整个模拟器CPU是处于暂停的状态,
这也是符合DMA运作的机制!

前面都是DMA CORTROLLER的内部机制:
A.模拟DMA CONTROLLER对CPU发出中断的动作!
B.资料搬移动作!
C.模拟CLOCK TIME经过的动作
还必须提供CPU使用DMA的机制,CPU对DMA CONTROLLER的控制方式还是以MEMORY
MAP中分配给DMA CONTROLLER的窗口设定DMA CONTROLLER的参数,在DMA
CONTROLLER动作时便按照这些参数运作,需要实作出以下的函式:
A.RESET_DMA:
恢复DMA CONTROLLER成初始状态!
B.DMA 传输模式:
必须提供所使用DMA的所有传输模式!CYCLE STEALING就是搬移一个字组
就把控制权交回EXEC_CPU,BURST MODE除了资料搬移外,还需要处理经过
多少CLOCK TIME!
------------------------------------------------------------------------
4.9 光碟机装置存取方式:
有空再写! :)

------------------------------------------------------------------------
4.A 档案处理:
4.A.0 档案载入
有空再写! :)
4.A.1 LOAD HISCORE FILE
有空再写! :)
4.C.2 LOAD/SAVE 随时记忆档
有空再写! :)

------------------------------------------------------------------------
4.B 特殊功能:
4.B.0 抓图
有空再写! :)
4.A.1 抓音乐档
有空再写! :)
4.B.2 GUI
有空再写! :)
4.B.3 CHEAT CODE
有空再写! :)
4.B.4 CHEAT TOOL
有空再写! :)
4.B.5 DISASM
有空再写! :)

------------------------------------------------------------------------
4.C 可携性版本,WINDOWS版本,更先进的模拟器架构
有空再写! :)
4.C.0 可携性的研究
有空再写! :)
4.C.1 WINDOWS版本
有空再写! :)
4.C.2 更先进的模拟器架构
有空再写! :)

模拟器开发流程2

Posted: Thu Jun 01, 2006 2:33 pm
by zgh4000
[ 第五步 如何偷别人的程式及与别人交换意见 ]
5.0 如何偷别人的程式
5.0.0 模仿是进步最快的方式:
如果能找到现成的模拟器原始码,可以帮助你了解许多事,包括如何套用别人
的CPU CORE?如何写模拟器?不同硬体间如何交换资讯?当然更快的方式便是直
接修改别人的原始档,不过这似乎是满没品的!

------------------------------------------------------------------------
5.0.1 哪里找得到模拟器原始码?
一般都会出现在提供模拟器作者程式设计资料的网页上,或是从模拟器新闻性
网页中也会有模拟器作者释出原始档的消息,可以连到该模拟器的网页上直接
下载!当然还有一个很烂的方式--直接向作者要,不过肯直接给的应该不多吧!

------------------------------------------------------------------------
5.0.2 看得懂别人的程式也不是件容易的事:
不要以为有原始档就OK了!事实上想看懂别人的程式,还是必须要下一番功夫
的!如果弄到的是公开释出的原始档,正常来说,注解及格式都会相当完整,看
懂的机会较高!有几个TIPS可以参考:
A.从档名下手:
公开释出的版本对档名的分类会很清楚,从档名猜看看会得到不小帮助!
B.从INCLUDE档下手:
如果是C/C++语言的话,从INCLUDE档也可以找到不少有用的资讯,因为有许
多结构及函式宣告都会出现在对应的INCLUDE档中!
C.从函式名称下手:
严谨的程式写法对函式名称非常注重,直接从函式名称猜也行!
D.去除不必要的#ifdef等条件编辑的程式区块:
条件编辑在除错及特殊用途时非常有用,但是对想偷程式的人来说,便是累
赘了,适当的去除它们会使程式看起来简单一些!

------------------------------------------------------------------------
5.1 和别人交换意见:
一般说来,找一个模拟器的讨论区,或是直接写信给作者,把所发生的问题"具
体"的描述出来,都应该收到一定程度的答案!当然描述的越清楚,或是附上一
些相关的程式片段或是图片等,会更容易帮助别人厘清问题!

另外一种方式是寻找同好一起发展模拟器,双方在一起脑力激发,会比一个人
闭门造车好一点!

------------------------------------------------------------------------
[ 第六步 实际撰写一个模拟器 ]
呵呵,我没有经验,等我有经验再补这一段!原则上,可以参考一些书籍中有关
程式风格的章节!写的规矩一点会有助于追踪BUG!

------------------------------------------------------------------------
[ 第七步 测试,除错及版本更新 ]
7.0 测试与除错是一条漫长的道路:
很多功能都只是觉得应该是这样,所以便照着自己的想法写程式码,因为找资
料时实,在不容易找到太细部的资料,但是缺少了这些资料,有些功能其实只是
没用到特殊的地方,而写程式时并不晓得,万一有游戏用到,模拟的效果便很差
!问题是就算知道有BUG,就有能力改吗?如果有原来的硬体执行看看,也许会有
些启发!或是去找别人的程式看看!

------------------------------------------------------------------------
7.1 自己测试的盲点:
因为个人的偏好,可能会只针对想测试的游戏测试,对于不喜欢的游戏可能就
没有测试的很完整,如果你找得到朋友帮你测试看看,也许找得到别的bug!

------------------------------------------------------------------------
7.2 版本更新:
当每解决一个BUG,每更新一个地方,应该随时记录下来,一方面作为自己参考
所用,一方面提供别人使用时的参考!因为可能当你解决这个BUG时,又引发另
一个BUG出现,如果有记录版本更新的项目,也可藉此了解BUG出现的方向!

------------------------------------------------------------------------
7.3 最少保留一份每一个版本的原始档及执行档:
这是一方面可以在发展机器发生问题时,还有机会拯救回来,另一方面旧版本
可能在某些地方反而优于新版本,保留旧版本的原始档可以交互参考!

------------------------------------------------------------------------
[ 第八步 撰写说明文件 ]
撰写说明文件是相当重要的事,除非你只是给自己玩而已,否则当需要介绍给
别人时,就要有一份完整的说明文件!一般常见的说明文件有几种:
A.README:
包括测试环境,相容游戏列表,按键说明,选项设定说明,更新项目,版本沿革
,还有相关协助人员的列表!当然相容游戏列表,更新项目也可以直接分离出
来!
B.FAQ:
列举出这个模拟器常见的问题及解决之道!
如果需要翻译成不同语言时,也许应该请别人代笔,即使没有请别人代笔,也应
该请别人看看写的会不会怪怪的!

------------------------------------------------------------------------
[ 第九步 公开你的版本 ]
9.0 建立个人网页:
建立个人网页是一个最快也是相当不错的方式,可以把想让FANS知道的消息,
说明文件,执行效果的图档(SCREENSHOT),提供各版本下载,或作一个留言版,
增加与喜欢这个模拟器的FANS的互动!

------------------------------------------------------------------------
9.1 投稿至模拟器新闻性网页!
直接投稿至模拟器新闻性网页也是相当不错的选择,因为新闻性网页每天观看
的人很多,宣传的效果很好,如果再加上模拟器实在做得不错,一夜成名并不是
梦想!

------------------------------------------------------------------------
[ 后记 ]
看到这里,应该对整个模拟器的发展有点初步的认识!但是想以此写成一个模
拟器,实际上还有大的障碍要通过!当然这篇文章还有很多错误的地方,你应该
保持处处存疑的态度,以免造成你的误解,而影响你写模拟器的进度!最后我希
望这篇文章能对你有些许的帮助!

------------------------------------------------------------------------
[ 常用名词解释 ]
1.EMULATOR & SIMULATOR
这两者中文都叫"模拟",两者有何不同呢?

EMULATOR 注重在内部的运作机制的一致!就巨观的观察来看,是模拟整个硬体
系统,使得同一个硬体系统的程式能在这个程式上正常运作--一如在原本的硬
体系统上执行一样!

SIMULATOR 注重在外在操作与反应上的一致!就巨观的观察来看,是改写程式,
使这个程式表现的方式类似于所模拟的事物!例如飞行模拟FLIGHT-SIM,不管
模拟器再真实,都没有办法真正模拟出飞行的感觉!

而如果我们微观的观察EMULATOR的运作时,实际上,与SIMULATOR的差异只有程
度上的不同!真正在做CPU模拟器的OP CODE时,还是以模拟操作与反应的一致!

2.SCHEMATICS 概图:
利用电子元件的所绘制的线路图!

3.DIP(Dual In-line Package) switch DIP指拨开关
一种位于电路板上的开关,用来选择硬体设定参数!DIP(双排插脚包装)是指这
种开关的外盒是DIP!

4.MEMORY MAP 记忆体对映
主记忆体的分段(SEGMENT)里,用来定义哪些区域做什么用途.

5.ROM IMAGE
一般TVGAME及ARCADE游戏,都是把程式或资料烧录在ROM中-不论是ROM IC颗粒
,还是CD-ROM!很明显的,除了CD-ROM之外,没有办法直接读取其中的指令或资
料(即便是CD-ROM也可能自己搞奇怪格式,所以也可能无法读取),所以需要使
用ROM DUMPER,把ROM中的指令或资料DUMP成二进位制档案,称为"ROM IMAGE"!

6.MEMORY MAPPED I/O
MEMORY MAPPED I/O是指I/O界面位址和记忆体位址使用相同的位址空间,此时
界面暂存器是记忆体系统的一部份!界面单元和记忆体使用相同指令存取,资
料位址指到记忆体,即是和记忆体沟通;如果指到界面暂存器位址范围即是要
进行I/O动作!

7.OP CODE:
CPU中,指明将进行什么运算(如ADD,I/O).以二进制代码来指定一个运算,称为
运算码(OP CODE)!

如何写一个电脑模拟器
前言
怎样,你决定写一个软体模拟器吗?很好的,这篇文件或许能给你一些帮助!它
涵盖了一些当人们问及如何写模拟程式时的通用技术性问题.它同时也提供你
模拟器内部运作的?#123;图,让你多少能够依循!

提纲:
什么能被模拟呢?
什么是" emulation "? 和"simulation "有何不同?
模拟有专利的硬体合法吗?
什么是"interpreting emulator"? 和"recompiling emulator" 有何不同?
我想要写一个模拟器.我该从哪开始?
我该使用何种程式语言?
我要从哪里找到被模拟的硬体的资料?

制作步骤
我该如何模拟一个CPU?
我该如何处理被模拟记忆体(emulated memory)的存取?
周期行程(Cyclic tasks):这是什么?
程式写作技巧
我该如何最佳化C程式码?
什么是LOW/HIGH-ENDIANESS?
如何使程式具有可携性?
为什么我应该使程式模组化?
其他

什么能被模拟?
基本上,任何要被模拟的系统都有一个微处理器.当然,只有执行较具弹性或
较不具弹性程式的装置是我们感兴趣模拟的.包含:
电脑.
计算机.
家用游戏平台.
大型电玩.
其他.
记下你能模拟的任何电脑系统是必要的,即使它十分复杂(如Commodore Amiga
computer).虽然这样的模拟程式效能也许不太好.

什么是" emulation "? 和"simulation "有何不同?
" emulation "是意图模拟一项装置的内部设计."simulation "是意图模拟
一项装置的功能!譬如,一个程式能够模拟"小精灵(PACMAN)"大型电玩硬体和
执行真正"小精灵(PACMAN)"ROM,这个程式就是一个EMULATOR!一个为了你的电
脑而写的"小精灵(PACMAN)"游戏,但使用图像类似于真正的大型电玩,这个游
戏程式就是一个 SIMULATOR!

模拟有专利的硬体合法吗?
这档事其实是界于一个灰色地带,模拟有专利的硬体似乎是合法的,但是不包
含非法使用这些用于有专利硬体上的资讯!你应该也发现到伴随着模拟器一起
散布SYSTEM ROMS(BIOS,或其他)是违法的,如果这些ROM是有版权的!

什么是"interpreting emulator"? 和"recompiling emulator" 有何不同?
有三种基本规划可以用以设计一个模拟器.他们也能相互结合以获得最佳效
果!

直译型(Interpretation)
一个模拟器执行的方式是从记忆体一个BYTE一个BYTE的读入被模拟的程式码
,再解码这被模拟的程式码,再执行适当的指令在被模拟的暂存器,记忆体,及I/O
,这种模拟器就称为直译型模拟器!这类的模拟器一般的演算法如下:

while(CPUIsRunning) /* 利用一个检查CPU是否执行的旗标的无限回圈 */
{
Fetch OpCode /* 抓取运算码 */
Interpret OpCode /* 解译运算码 */
}
这种方式的优点包含易于除错,具可携性,易于同步(synchronization,你
可以简化计算所经过的时序周期,并且固定你的模拟器在同一周期数的延迟)

明显的缺点就是效能.直译型模拟器花费相当多CPU时间,并且你可能需要
十分快的电脑来执行你的程式,才能获得尚可的速度.

静态重编译型(Static Recompilation)
在这个技巧中,你试图将一个由被模拟程式码写成的程式,翻译成你的电脑所
使用的组合语言码.结果将成为一个无须任何特殊工具就可以在你的电脑执行的
可执行档.静态重编译型模拟器听起来似乎不错,不过这通常并不可行的!例如,
你不能完全先行编译会自己修正的程式码(self-modifying code),因为不执行
这些程式码,是不可能知道它们将会变成什么的!为了避免这种状况,你也许可
以将直译型模拟器或动态重编译型模拟器与静态重编译型模拟器组合起来!

动态重编译型(Dynamic Recompilation)
动态重编译型模拟器与静态重编译型模拟器在本质上是相同的,只不过动态
重编译型模拟器出现在程式执行时.为了代替一次就重编译好所有的程式码,我
们可以只在遇到"CALL"或是"JUMP"指令时,立即再作一次重编译工作!为了增加
速度,这种技巧也能与静态重编译型模拟器组合使用.你能在这里找到更多有关
动态重编译型模拟器的资料:
http://www.ardi.com/MacHack/machack.html white paper by Ardi,
creators of the recompiling Macintosh emulator.

我想要写一个模拟器.我该从哪开始?
为了写模拟器,你必须要有良好的电脑程式设计及数位电子学的一般知识背
景.有组合语言程式设计的经验亦十分重要.你先要做到:
选择所使用程式设计语言.
找到所有有关被模拟的硬体的资料.
重写一个CPU的模拟器或是取得已写好的CPU模拟器程式码.
最少先部份地写一些草拟码来模拟硬体的一部份,由这点看来,在写模拟器时同
时作一个拥有允许暂停模拟及看看这个程式正在干嘛这种功能的小型内建除错
器,这样是非常有帮助的.同时你也需要被模拟系统所使用的组合语言的反组译
程式.如果找不到现成的反组译程式,就自己作一个.试着在你的模拟器上执行
程式看看.使用反组译程式及除错器了解程式是如何使用硬体的,并且适当的?#123;
整模拟器的程式码.

我该使用何种程式语言?
最明显的选择方案是 C 及 组合语言.
以下是他们的优缺分析:
组合语言
优:1.可以产生较快速的程式码.
2.用以模拟的CPU的暂存器可以直接用来储存被模拟的CPU暂存器的资料.
3.许多运算码可以用相似的用以模拟的CPU的运算码来模拟.
缺:1.这种程式码没有可携性,也就是不能在不同架构的电脑上执行.
2.程式码的除错与维护不太容易.
C
优:1.可以做成具可携性的程式码,如此便能使用在不同的电脑及作业系统.
2.相对来说,程式码的除错与维护较为容易.
3.可以很快的测试对真实硬体如何运作的不同假设.
缺:一般而言,C 程式码通常比纯组合语言程式码慢一些.

对于写一个可运作的模拟器来说,对所选择的程式语言有深入的认识是绝对
必要的,因为这是十分复杂的计画,并且你的程式码应该尽可能的最佳化使之执
行的更快!说的明白一点,作电脑模拟器不应是一个学习程式语言的计画.

我要从哪里找到被模拟的硬体的资料?
以下列出来的地方,你也许想要看看.
Newsgroups
comp.emulators.misc
这是一个有关电脑模拟一般性讨论的Newsgroup.许多模拟器作者也阅读这里
的讨论,不过这里的废话多了一点!在你想刊登问题在这个讨论区之前,请先看
看这里:
http://www.why.net/home/adam/cem/ c.e.m FAQ

comp.emulators.game-consoles
跟comp.emulators.misc差不多,不过特别注重家用游戏平台模拟器.在你想
刊登问题在这个讨论区之前,也请先看看这里:
http://www.why.net/home/adam/cem/ c.e.m FAQ

comp.sys./emulated-system/
comp.sys.*类阶层包含许多特殊电脑系统的讨论区.

藉由读这些newsgroups,你可以获得很多的有用的技术性资讯.典型的例子如:
comp.sys.msx MSX/MSX2/MSX2+/TurboR computers
comp.sys.sinclair Sinclair ZX80/ZX81/ZXSpectrum/QL
comp.sys.apple2 Apple ][
etc.

郑重呼吁,先看看这些FAQ在你想在这些新闻讨论区发问之前.
Alt.folklore.computers rec.games.video.classic

FTP
ftp://x2ftp.oulu.fi/ ---- 包含游戏平台及游戏程式设计,
位置在 Oulu , Finland

ftp: // ftp.spies.com/ ----- 大型电玩硬体资料库

ftp: // ftp.komkon.org/pub/EMUL8/ ----- 电脑历史及模拟器资料库,
位置在 KOMKON

WWW
http://www.why.net/home/adam/cem/ comp.emulators.misc FAQ
http://www.komkon.org/fms/ My Homepage
http://valhalla.ph.tn.tudelft.nl/emul8/arcade.html
Arcade Emulation Programming Repository

http://www.classicgaming.com/EPR/
Emulation Programmer's Resource

我该如何模拟一个CPU?
首先,如果你只需要模拟一个标准的 Z80 或 6502 CPU,你可以用我写的CPU
模拟器! http:// www.komkon.org/fms/EMUL8/
请先确定一下使用条件,在加入它们之前.

对于那些想要写自己的CPU模拟核心或是对于CPU模拟核心感兴趣的人,以下
我提供一个典型C语言CPU模拟器.在真实的模拟器之中,你也许想要保留它的一
部份,或是加入一部份到你自己的CPU模拟器.

Counter=InterruptPeriod;
PC=InitialPC;

for(;
{
OpCode=Memory[PC++];
Counter-=Cycles[OpCode];

switch(OpCode)
{
case OpCode1:
case OpCode2:
...
}

if(Counter<=0)
{
/* 检查所发生的中断并执行这些中断 */
/* 周期行程(Cyclic tasks)置于这里 */
...
Counter+=InterruptPeriod;
if(ExitRequired) break;
}
}

首先,我们要给定一个初值到CPU周期计数器"Counter",及程式计数器"PC"!

Counter=InterruptPeriod;
PC=InitialPC;

"Counter"包含CPU周期数到下一个可能的中断.注意那些当"Counter"到期时,
不需要发生的中断:你可以使用在很多其他用途上,例如将计时器同步化,或者更
新萤幕上的扫描线."PC"包含我们所模拟的CPU将读入的运算码记忆体位址.

在初值给定后,我们就可以开始主要的回圈:

for(;
{
也可以用这样的方式,

while(CPUIsRunning)
{
"CPUIsRunning"是一个布林变数.这有相当的优点,你可以随时藉由设定
"CPUIsRunning"为"0"终止这个回圈.不幸的,每次回圈经过都必须检查这个变
数,这将花费很多CPU时间,应该尽量避免这样的方式.也不要做这样的回圈:

while(1)
{
因为有些编译器会产生检查"1"是不是表示布林"true"的程式码.你一定不希
望编译器产生这种每经过回圈一次都要做一次不必要工作的程式码.

现在,我们进到回圈了,第一件事就是先读入下一个运算码,同时更改程式计
数器"PC".

OpCode=Memory [ PC++ ] ;

注意这里,虽然这是从被模拟记忆体中读入指令的最简单及最快的方式,不过
这不一定可行就是.更通用的存取记忆体的方式稍后会介绍.

在抓取完运算码之后,我们减少CPU周期数"Counter",减少的数量是这个运算码
所需要的周期数.

Counter-=Cycles [ OpCode ] ;

"Cycles [ ]"表格包含了每个运算码的CPU周期数.提防某些运算码可能因为
参数的不同而有不同的周期数,例如条件跳跃或是副程式呼叫.虽然在程式中稍
后能够再?#123;整.

现在到了解译运算码及执行运算码的时候了:

switch(OpCode)
{
一般人有个误解,以为用switch()结构是没有效率的,因为这会编译成一串
if() ... else if() ...的叙述.当CASE很少时,这倒是真的.不过在大型结构
中(超过100-200个以上的CASE)会编译成一个"JUMP TABLE",这反而十分有效率
.
有两种选择方式来解译运算码.第一种方式是作一个函式表(FUNCTION TABLE)
并且呼叫适当的函数.这种方式比用 SWITCH() 还没效率,因为函式呼叫花费更
大.第二种方式是作一个标签表(LABEL TABLE)并且利用"GOTO"叙述.这种方式
比用 SWITCH() 有效率一点,不过只在编译器支援"预先计算标签(precomputed
labels)这项功能才能执行.有的编译器不允许你做一个标签地址阵列(array of
label addresses).

在我们成功的解译并执行运算码之后,再来是检查需不需要任何岔断.在这个
时刻,你也可以完成任何需要与系统时钟同步的行程.

if(Counter<=0)
{
/* 这里执行检查岔断及完成其他硬体模拟的步骤 */
...
Counter+=InterruptPeriod;
if(ExitRequired) break;
}
这些周期行程(CYCLIC TASKS)稍后会说明.

注意这里,我们并不是简单的使用" Counter=InterruptPeriod; ",而是使用
" Counter+=InterruptPeriod ":这将使得周期计算更为精密,因为在"Counter"
中可能负的周期数出现.

也注意这里:

if ( ExitRequired ) break ;

因为每经过一次回圈就检查一次是否离开程式,这样满浪费的,我们只在
"Counter"到期时做这项工作:这样还是会离开模拟器,当你设定
"ExitRequired=1"时,不过这不会花费太多CPU时间.

我该如何处理被模拟记忆体(emulated memory)的存取?
存取被模拟记忆体最简单的方式是如同一块平坦的byte(words或其他)阵列,
琐碎的处理它,如:

Data=Memory[Address1]; /* 从 Address1 读入 */
Memory[Address2]=Data; /* 写入到 Address2 */

如此简单的记忆体存取实际上并不总是可能的,原因如下:

分页记忆体(Paged Memory)
定址空间可能是零散的在于可切换页(switchable pages,或称BANK).这常被
做为扩充记忆体的方式,当定址空间是小的(64KB).

对映记忆体(Mirrored Memory)
一块记忆体区域可在几种不同的位址存取.例如,你写入到位置 $4000 将也
出现在 $6000 及 $8000.ROM 也可能被对应到不完全的位址解码.

ROM 保护(ROM Protection)
某些卡匣型的软体(如 MSX 游戏)会尝试写入到自己的ROM,并且在写入成功
时拒绝再继续执行.通常这是为了防拷.为了使这样的软体在你的模拟器上继续
执行,你应该阻止写入到ROM中.

Memory-Mapped I/O
在这种系统中,可能有 Memory-Mapped I/O 装置.存取到这样的记忆体位置
会产生"特殊效果",因此这样的存取方式应该要追踪.

为了应付这种问题,我们介绍两种对应的函式:

Data=ReadMemory(Address1); /* 从 Address1 读入 */
WriteMemory(Address2,Data); /* 写入到 Address2 */

所有特殊处理,如页存取,对映,I/O处理等等,都可以由这些函式处理.

ReadMemory() 及 WriteMemory() 两种函式在模拟过程中占用很大一部份,
因为他们很常被呼叫.所以,他们一定要尽可能的做得有效率.这里是用以存取
分页定址空间的函式范例:

static inline byte ReadMemory(register word Address)
{
return(MemoryPage[Address>>13][Address&0x1FFF]);
}

static inline void WriteMemory(register word Address,register byte Value)
{
MemoryPage[Address>>13][Address&0x1FFF]=Value;
}

注意"inline"这个关键字.他告诉编译器将函式直接展开到程式码,而不是以
函式呼叫的方式.如果你所用的编译器不支援"inline"或"_inline",尝试利用
"static function"来定义这样的函式:某些编译器(如 WATCOM C ),会以INLINE
的方式最佳化短的"static function".

在大部分的情况下,ReadMemory() 函式比 WriteMemory() 函式使用的频率
要高出许多倍,你也应该谨记在心.所以,把 ReadMemory() 函式程式码尽可能
写得的比 WriteMemory() 函式短,这样是很值得的.

在记忆体对映(memory mirroring)上还有一个小小的重点:
有此一说,许多电脑都有对映RAM(mirrored RAM),你可以发现某个数值写入
到某个位址却也将会出现在其他位址.你可以在 ReadMemory()函式中处理这样
的状况,但这实在不太值得,因为 ReadMemory()函式呼叫的频率实在大过
WriteMemory() 函式(译按:原意应该是"因为有很多位址都能读到同一个数字
,所以只要读取一个位址就可以,不要读取好几个位址才决定这个数字,
ReadMemory() 函式使用的频率太多,多加入不必要的程式码将严重损及效率")
更有效率的方式将是撰写 WriteMemory() 函式时加入记忆体对映的功能.

周期行程(Cyclic tasks):这是什么?
周期行程是在模拟机器中应该要定期处理的事情,例如:
萤幕更新(Screen refresh)
VBlank 及 HBlank 岔断
更新计时器(Updating timers)
更新音效参数(Updating sound parameters)
更新键盘/摇杆状态(Updating keyboard/joysticks state)
其他
为了模拟这些行程,你应执行在 2.5 MHz的?50Hz 的更新频率( PAL 影像盲郱?,
VBlank岔断应该以这样社W率发生

2500000/50 = 50000 CPU cycles

现在,如果我们假设萤幕是256条扫描线,但是只有212条扫描线是真的显示在
萤幕上的(i.e. 剩下44条被 VBlink 吃了),我们得到你的模拟器应当花费在更
新每条扫描线是
该以适当的CPU周期数固定它们.例如,如果CPU建议
速度,萤幕显示使术?50000/256 ~= 195 CPU cyles

在这之后,你就应该产生一次 VBlink 岔断,并且直到 VBlink 完成前,不做
任何事.(i.e.

( 256-212 ) *50000/256 = 44*50000/256 ~= 8594 CPU cycles

小心的计算每个行程所需的CPU周期数,然后把他们的最大公约数定成
"InterruptPeriod(中断周期)"并且固定所有其他行程到"InterruptPeriod"
(在每次"Counter"到期时,这些行程不需要执行).

我该如何最佳化C程式码?
首先,正确的选择编译器所提供的最佳化选项能增加额外的程式码效能.在我
的经验中,以下的选项组合可以给你最佳的执行速度:

Watcom C++ -oneatx -zp4 -5r -fp3
GNU C++ -O3 -fomit-frame-pointer
Borland C++

如果你发现有其他更好的选项组合,不管是以上的编译器或是其他不同的编
译器,请你告诉我.

一些回圈展开(loop unrolling)的小小建议:
将编译器最佳化的"回圈展开"选项设成"on"可能是很有用的.这项选项将尝
试转换短回圈变成平滑的程式片段.我的经验显示,虽然,这项选项不会产生任
何效能改进.但是设定成"ON"时也可在某些极特殊的条件中中断你的程式码.

最佳化C的原始程式码比选择编译器选项稍微来得好,并且就一般而言,用编
译器编译程式码这是一个与CPU有关的最佳化方式.几个一般性适用于所有CPU
规则如下.不要把它们的运作方式当做绝对的事实,因为你的用途可能不同:

使用程式分析工具(profiler)!
你的程式在程式分析工具程式(记住GPROF)下执行的情形会显示很多很好的
事情,是以前你从未怀疑的.你会发现,看起来微不足道的程式片段执行的频率
比其他部份多的太多了,并且使整个程式慢下来.将这些程式片段最佳化或是改
以组合语言撰写会大幅增加效能.

避免使用C++
避免使用任何结构,这会强迫使用C++编译器代替C编译器去编译你的程式:C++
编译器通常会产生额外的程式码.

整数的尺寸(Size of integer)
尝试只使用一种CPU所支援的基底整数尺寸,i.e. 整数(int)尺寸相对于
"short" 或"long"整数的尺寸.如果使用混合的整数尺寸,这将使编译器产生一
堆不同整数长度间转换的程式码.这也会增加计忆体存取时间,因为某些CPU当
以基底整数读写资料时会因为对齐基本基底位址边界而使执行速度加快.

暂存器定位(Register allocation)
在每一区中,尽可能的减少使用变数,把最常使用的变数宣告为暂存器变数"
register variable"(虽然大部分新的编译器可以自动地把变数变成暂存器变
数 ).这在拥有较多通用暂存器的CPU(PowerPC)比在只有少数专用暂存器的CPU
( Intel 80x86)更有意义.

展开小的回圈(Unroll small loops)
如果你想要使小回圈在很短的时间内执行,以手动的方式展开小回圈成为平
滑的程式片段会是个好主意.看看上面有关自动回圈展开的讨论.

位元移位(Shifts) VS. 乘/除法
当你需要乘或除以2的次方数时,应该以位元移位代替( J/128==J>>7 ).
在大部分CPU下,这些方式会执行得较快.同样的,使用位元"AND"运算代替取余
数的运算( J%128==J&0x7F ).

什么是LOW/HIGH-ENDIANESS?
所有的CPU一般都可分成两种,这与他们如何在记忆体储存资料有关.

High-endian CPU
这种CPU是把一个word的higher byte放在前面的储存方式储存资料.例如,如
果要储存 0x12345678 到这样的CPU,这块记忆体看起来会像这样:

0 1 2 3
+--+--+--+--+
|12|34|56|78|
+--+--+--+--+

Low-endian CPU
这种CPU是把一个word的lower byte放在前面的储存方式储存资料.跟前面一
样的例子在这种CPU会看起来非常不一样:

0 1 2 3
+--+--+--+--+
|78|56|34|12|
+--+--+--+--+

典型的High-endian CPU的例子是 6502 and 65816 ,Motorola 680x0 系列,
PowerPC ,及 Sun SPARC .Low-endian CPU包含Zilog Z80,大部分INTEL晶片(
包含 8080及80x86),DEC Alpha,等等.

当你写一个模拟器,你必须注意被模拟及模拟别人的CPU是何种ENDIANESS,我
们说,你想要模拟Z80 CPU,这是一个Low-endian CPU,也就是储存16-BIT WORD
时LOWER BYTE在前面.如果你使用的是Low-endian CPU(例如,INTEL 80x86),在
这个例子,每件事都发生的很正常.如果你使用的是High-endian CPU( PowerPC),
突然就出现了放置16-bit Z80资料到记忆体的问题.如果你的程式必须在两种
机器下执行,你需要某种方式感觉endianess.

一种处理 endianess问题的方法如下:

typedef union
{

short W; /* Word 存取 */

struct /* Byte 存取... */
{
#ifdef LOW_ENDIAN
byte l,h; /* ...在 low-endian 机器 */
#else
byte h,l; /* ...在 high-endian 机器 */
#endif
} B;

} word;

正如你所看到的,一个"WORD"的资料利用 W 可以整个存取.每次你的模拟程
式需要分开存取BYTE时,你可以使用 B.l 及 B.h 存取资料.如果你的程式将在
不同平台编译时,在执行任何真正重要的事情时,你可能先想要测试一下它是否
以正确的endianess方式编译的.

这里是这种测试的一种方式:

int *T;

T=(int *)"\01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
if(*T==1) printf("This machine is high-endian.\n");
else printf("This machine is low-endian.\n");

Posted: Fri Mar 20, 2009 5:17 am
by smallfish
能寫出這樣文章的人 實屬不易!