在创建仿真器的过程中,我从炸弹杰克街机上学到了什么
我最近为Bomb Jack机器编写了一个小型仿真器,主要目的是弄清这些最初的8位街机游戏机在设计上与8位家用计算机有何不同。
正如我后来发现的那样,在我家乡的一次夏季博览会上与炸弹杰克(Bomb Jack)等街机一起开会是改变我命运的那一刻。 在一个正常的夏日里,我将全部硬币都花在街机上,然后回到家中,头上充满了鲜花和声音效果。 我试图了解这些游戏的运作方式。 然后直到年末,我放学后将所有时间都花在家用计算机上创建这些褪色的街机游戏副本。 我就像来自太平洋岛屿的货运崇拜者一样,他想用棍子创建一个美国军事广播电台。
最初,我想到了创建
Pengo模拟器的想法,因为与Bomb Jack相比,这款游戏给我十几岁的大脑留下了深刻的印象(顺便说一下,这是我
崇拜的Pengo版本 )。 但是Pengo街机设备需要创建用于音频和视频的新芯片仿真器,而对于Bomb Jack,我已经拥有足够的零件(Z80作为CPU,而AY-3-8910用于声音),因此我是第一个采用Bomb Jack的人。
此外,炸弹杰克是向我的Z80仿真器最终添加NMI(不可屏蔽中断)支持的绝佳机会。 我以前模拟的基于Z80的机器都不使用NMI,因此重新创建此功能没有多大意义-我仍然无法检查其功能。
如果您不知道炸弹杰克是什么,那么这个游戏就是这样的(不确定我是否选择了正确的长宽比):
WebAssembly上的仿真器版本可以在以下位置找到:
https://floooh.imtqy.com/tiny8bit/bombjack.html在完成加载过程并出现高分表后,按
1放硬币,然后
按Enter (或箭头和空格键以外的任何其他键)以开始游戏。
在游戏内部,使用
箭头键更改方向,并使用
空格键跳转。 在空中时,按
空格键可以减慢跌落的速度。
源代码在这里:
https://github.com/floooh/chips-test/blob/master/examples/sokol/bombjack.c它使用
芯片接头提供对Z80和AY-3-8910的仿真,以及
sokol接头作为跨平台包装(用于输入应用程序,渲染,输入和声音)。
步骤1:研究
“研究”一词太大了:我刚刚访问了Google的“炸弹手街机硬件规格”。
与80年代流行的家用计算机(甚至神秘的东欧计算机,这些计算机通常仍然具有活跃的社区)相比,互联网上关于炸弹杰克的信息很少。
我发现了两个非常重要的信息:机器的
电路图 ,当然还有
MAME仿真器的
源代码 。
还有一个
在FGPA上实现
炸弹杰克的项目,我从VHDL源中设法找出了电路图中未包含的细节。
理解MAME源代码会很棘手,因为街机游戏模拟器通常只是一堆宏,用于描述各种设备之间的交互方式,但是
源代码并不多。
尽管如此,设备的宏描述(尤其是注释)对于理解硬件的操作仍然非常有用,并且它们变得过于神秘(例如,
有关视频解码的
部分 ),反复试验就足够了,以及详细研究这个概念。
硬件总览
关于Bomb Jack硬件,最有趣的事情是它实际上是
两台通过胶带彼此连接的
计算机 :一个带有Z80 CPU和视频解码设备的
主板 ,另一个是带有自己的Z80 CPU和三个
声卡的声卡 (是的,三个!)声音芯片AY-3-8910。
视频解码设备并未实现为集成电路-只是许多小型通用芯片(其电路在设备电路图的10页中占据了6页)。 在创建仿真器时,我决定走一个短路:我没有仿真视频解码设备的各个部分,而是仅仿真了其行为,从输入数据中创建了相应的输出,而不用担心设备本身在中间如何工作。
这种简化的解决方案非常适合于单独的街机,该街机仅设计为运行一个程序。 如果游戏开始并正常运行,则可以认为该仿真“足够好”。
此外,这种简化的方法与大多数家用计算机的仿真有一个重要的区别:某些游戏比其他游戏需要更精确的仿真,例如,诸如C64或Amstrad CPC之类的机器需要在时钟周期之前进行非常精确的仿真,因此某些游戏和图形的视频系统演示正常工作。
这也意味着,对于Bomb Jack来说,我现成的CPU和声音芯片仿真器实际上是多余的工作,例如,使用Z80 CPU实施机器周期分数运算是过分的,在指令级别上更简单,更快速的分段就足够了。
主板
通常,在编写新的仿真器时,我首先要弄清楚的是内存分配方案(其中ROM和RAM,视频内存和特殊地址或输入/输出端口的区域)。
Bomb Jack主板上只有一个“有趣的”芯片-Z80 CPU以4 MHz运行。 主板上的所有剩余空间都被视频解码设备占用(一对RAM和ROM芯片除外)。
16位地址空间如下:
- 0000..7FFF :32 KB ROM
- 8000..8FFF :4 KB的通用RAM
- 9000..93FF :1 KB的视频内存
- 9400..97FF :1 KB彩色RAM
- 9820..987F :96字节的精灵RAM
- 9C00..9CFF :256字节的RAM调色板
- 9E00,B000..B005,B800 :输入输出端口
- C000..DFFF : 8KB ROM
I / O端口区域如下。 有些端口是只写的,有些是只读的,有些在读写时具有不同的功能:
- 9E00 :写入:当前背景图像编号,读取:-
- B000 :读取:玩家1游戏杆的状态,写入:启用/禁用NMI掩码
- B001 :读取:玩家2操纵杆的状态,写入:-
- B002 :读取:硬币和开始按钮,写入:-
- B003 :读取:CPU看门狗,写入:???
- B004 :读取:DIP开关1,写入:开关屏幕
- B005 :读取:拨码开关2,写入:-
- B800 :写:命令声卡,读:-
这里值得一提的是:
- 该设备具有很多ROM(40 KB)和很少的RAM(大约7KB,其中只有4KB是“通用RAM”)。
- 显示屏的RAM仅分配了2 KB,分为1 KB的两个片段,这对于256x256全彩色显示屏来说似乎很小,其中似乎是逐像素设置颜色的
- 这是一个I / O系统中的内存分配方案!
对于Z80机器,内存分配方案中的I / O有点不寻常,因为Z80的标志之一是其单独的16位I / O地址空间。 这样做是为了节省宝贵的内存地址空间。 内存分配方案中的I / O通常在具有6502处理器的计算机中找到。
看一下电路图,可以确认这一点:在主板的CPU上未检测到IORQ引脚,仅连接了MREQ引脚(用于初始化对存储器的读取或写入):
这意味着我们不必担心模拟器中主板的CPU定时器功能的I / O请求,而只需处理内存请求。
研究了电路图后,我发现了有关主板CPU的另一个有趣的细节:
仅连接了NMI引脚,而INT引脚始终保持高电平的时钟信号/它保持无效状态(这意味着不执行“正常”屏蔽中断,并且仅发生非屏蔽中断):
对于配备Z80的汽车来说,这也是非常不寻常的。 在我过去处理过的所有基于Z80的家用计算机中,情况恰恰相反-它们仅使用可屏蔽中断,而从未使用不可屏蔽中断。 与“私生子” Intel 8080或竞争对手MOS 6502的原始中断系统相比,屏蔽中断Z80是非常灵活和认真的改进。但是,这种增加的灵活性也更难以在设备中实现(除非作为中断源)使用Z80系列的其他芯片,其中通过总线连接时已经内置了复杂的中断协议。
哦,好,关于设备的足够详细信息,让我们继续进行仿真器!
开机程序
确定内存配置之后的下一步是将仿真的CPU连接到仿真的内存分配方案,记录视频内存内容的某种可视化,并开始CPU周期。
令人惊讶的是,这种粗略的方法通常足以完成加载过程并在屏幕上显示
某些内容。 在设计Bomb Jack仿真器时,我只是将1KB视频存储器的内容作为32x32字节矩阵从0x9000到0x93FF范围内。 当字节为0时,我渲染了一个黑色像素块8x8,否则,渲染了一个白色像素块。
然后,我只运行了仿真CPU,并希望达到最佳状态。 看哪! 出现了一些清晰的图片:
顶部图像看起来像是启动时的硬件测试屏幕,而底部图像看起来像是在完成启动过程后出现的成绩记录屏幕:
...但旋转了90度(这是合乎逻辑的,因为街机的屏幕通常处于垂直的“人像”方向)。
太好了,开始是有希望的!
下一步是弄清楚如何将这些白色块转换为彩色像素……(这是一个巨大的步骤,下面将在视频解码部分中详细介绍)。
最初,一切都进行得非常快,在测试屏幕上,加载过程中显示了像素和颜色(后来我注意到颜色解码是完全错误的,但是...):
但是当出现录制屏幕时,我得到了黑屏。 修改背景颜色以使其“不是黑色”时,我发现已渲染像素,但整个调色板都是黑色。 嗯...
在看了几分钟之后,我记得高分屏幕上的某些颜色是动画的,当有动画时,应该有某种计时器。 此设备配置中的逻辑时间源是VSYNC显示信号,并且VSYNC连接到CPU的NMI引脚(或者不是VSYNC,而是VBLANK,这是VSYNC信号和阴极射线束移动到左上角之间的短暂时刻)。
而且我还没有实现所有这一切...
第二天晚上,当我将NMI处理的第一个版本添加到Z80仿真并将其连接到主板的CPU计时器功能中的第一个计数器vsync / vblank时,突然开始发生很多事情!
首先,颜色出现在“记录”屏幕上,其中一些具有动画效果:
几秒钟后,更令人兴奋的事情开始了! 高分消失了,显示了第一个地图的奇怪可视化效果。 显然,这是一种引人注目的街机游戏演示模式-我看到了几枚带有彩色动画的炸弹,当一个假想的炸弹杰克跳到一张收集这些炸弹的地图上时就消失了:
颜色仍然完全错误,但这是进步的!
现在是时候进行其余的视频解码了:
视频铁
乍看之下,对于1984年的8位机器,炸弹杰克的视频处理设备看起来非常强大:尽管分辨率仅为256x256像素,但它可以同时显示128种(4096种)颜色,并可以渲染多达24个硬件精灵(尺寸为16x16)。或32x32)。
当时的8位家用计算机具有大致相同的显示分辨率,但是它们有许多颜色限制。 将ZX Spectrum和Amstrad CPC的炸弹杰克版本与街机的版本进行比较时,这些限制非常明显可见:
ZX Spectrum的
版本具有很好的像素分辨率(256x192),但是颜色很少,并且遭受了Spectrum的典型“颜色冲突”影响(尽管开发人员已尽力使它不那么引人注目):
Amstrad CPC的版本是全彩色的,但是为了获得更多的颜色,开发人员不得不切换到低分辨率显示模式(160x200)。 结果,杰克和怪物变成了难以辨认的一堆像素:
将此与街机的版本进行比较,该版本具有与ZX Spectrum相同的像素分辨率,但是具有更多的颜色
和更高的逐像素颜色分辨率:
这里有趣的是,街机版本具有更好的图形,不是因为它可以在功能更强大的硬件上工作(它具有更多的ROM用于存储更多的图形数据,但是“计算能力”大致相同),而是因为设备的开发人员可以专注于在为一种特定类型的游戏生产专用机器的过程中,他们不需要创建通用的通用家用计算机。
显示硬件的工作原理如下(至少在我的高级解释中):
三层显示
完成的Bomb Jack视频信号由三层组成:背景层,前层和子画面层。
这种层系统具有两个主要优点:
- 它实现了相当棘手的硬件图像压缩,可以从非常少量的数据中生成“高分辨率”全彩色图像。
- 它显着减少了更新动态屏幕元素所需的CPU工作量(即使在4 MHz的频率下,一个8位CPU也没有足够的功率来移动频率为60 Hz的256x256显示器上的许多对象)
视频熨斗与我在8位家用计算机中看到的完全不同,但是在MAME中实现了此类设备的通用辅助类,因此我可以假定它在街机中非常普遍。
背景层
背景层可以渲染ROM中嵌入的5个背景图像中的1个。 通过在地址0x9E00处写入一个从1到5的值来选择背景图像(值0看起来很特殊,并呈现出全黑背景)。
实际上,该设备似乎可以渲染7个不同的图像,但游戏中只使用了5个,所以我秘密地希望在ROM中找到以前未检测到的图像数据。 但可惜的是,它们并不在那里(是的,也许我不是第一个在那里找到它们的人)。
这是第一张地图的背景层在没有其他两层的情况下的样子:
背景层由
16x16像素的图块组成。
从图块构建背景图像的优点是同一图块可以多次使用,因此可以在ROM中存储较少的数据。 请注意,蓝天,金字塔的一部分和金字塔下的沙子使用相同的图块:
为了节省内存,背景层设备实施了另一个技巧-可以水平翻转图块。 我在实现过程中几乎错过了这一点,因为我假设该软件未使用此硬件功能,但是注意到第三张卡的背景中有一个小错误:
我在第五张地图上使用了相同的技巧,但是如果您不知道要查找的内容,在这里要注意一点:
前层:
在背景层的上方是“前层”,它渲染了屏幕的所有固定部分,但是这些部分必须由CPU更新(主要是文本,平台和炸弹)。 从RAM中读取布局(从1 KB RAM和1 KB彩色RAM的片段中读取)。
这是第一张地图的绝缘前层的外观:
前层还包括图块(以及背景),但它使用的是较小的8x8图块:
将背景和前面板分为不同的层的主要优点是,在创建或删除前面板元素时,CPU无需担心存储和恢复背景像素。
精灵层
最后,硬件精灵会在顶层渲染。 屏幕上移动的所有内容均由精灵实现。 Bomb Jack设备最多可以渲染24个精灵,每个精灵的大小可以为16x16或32x32像素。 在这种情况下,可以以逐像素精度定位子画面:
8x8瓦片解码器
视频解码设备的核心是具有128个元素的调色板和8x8像素的图块解码器。 瓦片解码器的任务是为瓦片的64个像素中的每个像素生成7位调色板索引。
这些8x8拼贴是屏幕上所有内容的构建块-16x16背景拼贴,8x8前层拼贴和16x16或32x32硬件精灵。这是用于渲染顶层的8x8瓦片解码器的框图(据我所知):自上而下的框图说明:- 解码过程从顶部开始,从视频存储器中读取“平铺代码”的字节(组织为32x32瓦片代码矩阵),然后从彩色RAM中读取一个单独的字节(也是32x32矩阵)。从视频存储器获取图块和颜色的代码仅发生在前层,但是我添加了它以使整体图像更易于理解。8x8瓦片解码器本身仅在输入处需要瓦片和颜色代码。
- . ( ). , , ( ).
- 8 , 8 ( ). , , 8x8 24 (3 ).
- 64 7- . 3 , 4 — . , , 16 «», 8 . 8 .
- 7- , , 12- RGB- (4 ). ( , , ; , ).
这是三个显示层中的每个显示层使用的常规切片解码方案,但是每个层的解码略有不同:- 前层实际上可以渲染512个不同的8x8切片。这需要9位图块代码,但是视频内存每个图块仅提供8位。第九位从颜色值的第五位“借用”(因为仅使用颜色值的4位来构造调色板索引,所以还有4位用于其他目的)。如果来自8x8瓦片位层的所有3位都等于零,则前像素被视为透明的,而背景像素则“通过”它。
- 16x16, 16x16=256 256 (512 ). , 16x16 8x8, . , ; «» : 7 , .
- 16x16 32x32 , 4 16 8x8 . , 16x16 96 , 32x32 — 384 . , 3 , .
为了更好地了解图块位层的外观,我编写了一个小型C程序,将ROM图块转换为PNG文件(每个像素3位转换为8个灰度级)。下面显示了顶层的ROM磁贴。我们会看到数字和文本字体,平台图块,炸弹(分为两半),炸弹杰克屏幕保护程序中徽标的一部分以及出现在屏幕顶部的点乘数的数据(顺便说一下,所有内容都旋转了90度,因为整个屏幕也旋转了) ):接下来,考虑背景的ROM磁贴。看起来不太清楚,因为我们观察到的实际上是将16x16的图块解码为8x8的图块。每个16x16磁贴都是由四个相邻的8x8磁贴创建的。但是您可以从地图2识别希腊神庙的一部分,从地图3识别城堡,从地图4识别摩天大楼。最后,ROM精灵图块。16x16子画面占据上半部分,而32x32子画面占据下半部分。炸弹杰克屏幕保护程序的一个有趣之处是,徽标是由前砖和精灵拼凑而成的。我认为开发人员用尽了前ROM,但Sprite ROM中几乎没有剩余空间:雪碧装备
与当时的家用计算机相比,Bomb Jack Sprite设备非常强大:- 它最多可以渲染24个硬件精灵。似乎对每条扫描线的子画面数量没有限制。
- 子画面的大小可以为16x16像素或32x32像素
- 每个子画面可以在一个通用调色板中选择8种颜色的16个插槽之一
- 子画面具有逐像素的颜色分辨率。
- 每个精灵可以垂直或水平翻转
- 每个精灵可以从ROM中刷新的128个精灵图像中选择一个。
在对Sprite系统的像素和Sprite进行解码时,将使用与背景层和顶层相同的8x8基础图块。子画面属性位于0x9820到0x987F的地址范围内-96个字节,每个子画面4个字节。据我所知,该区域仅用于记录。至少CPU不对该存储范围执行读访问。每个精灵由4个字节描述:- 字节0:
- 位7:如果设置,则为32x32子画面,否则为16x16
- 6..0位:7位用于设置用于搜索ROM块中子画面图像位层的子画面块的代码。
- 字节1:
- Bit 7:如果设置,则将精灵水平翻转
- 位6:如果设置,则将精灵垂直翻转
- 3..0位:4位,用于设置瓦片解码器的颜色值
- 字节2:屏幕上X轴精灵的位置
- 字节3:精灵在屏幕上沿Y轴的位置
尚不清楚字节1的第4位和第5位做什么,MAME中的注释表示:
e ? (, )
f ? (, (B)?)
内存I / O端口
有关主板输入/输出端口的几点说明。 如上所述,I / O端口如下所示:
- 9E00 :写入:当前背景图像编号,读取:-
- B000 :读取:玩家1游戏杆的状态,写入:启用/禁用NMI掩码
- B001 :读取:玩家2操纵杆的状态,写入:-
- B002 :读取:硬币和开始按钮,写入:-
- B003 :读取:CPU看门狗,写入:???
- B004 :读取:DIP开关1,写入:开关屏幕
- B005 :读取:拨码开关2,写入:-
- B800 :写:命令声卡,读:-
上面我们已经考虑过的地址0x9E00(选择背景图像),以及我们将在下一部分中考虑的地址0xB800(命令声卡)。 保留地址从0xB000到0xB005:
从地址0xB000和0xB001读取将返回两个操纵杆的当前状态。 设置字节表示闭合的操纵杆开关:
- 位0 :正确方向
- 位1 :左方向
- 位2 :向上方向
- 位3 :向下方向
- 位4 :按下跳转按钮
其余3位将被忽略。
从0xB002读取将返回硬币接收器的状态和“开始”按钮:
- bit 0 :掷出玩家1硬币
- bit 1 :掷出玩家2硬币
- 位2 :播放器1开始按钮
- 位3 :播放器2开始按钮
从地址0xB004和0xB005读取将返回DIP开关的状态,这些DIP开关用于配置街机的行为:
- B004 :
- 0,1位 :一枚硬币(1、2、3或5)给出了多少个“游戏”
- 2,3位 :与玩家2相同
- 4,5位 :每场比赛有多少生命(3、4、5或2)
- bit 6 :街机的位置:“鸡尾酒桌”或“垂直”。
- bit 7 :是否在待机模式下播放声音
- B005 :
- 3.4位 :难度1(飞鸟速度)
- 5,6位 :难度2(敌人的数量和速度)
- bit 7 :特定硬币的出现频率
最后,从地址
B003读取将实现软件看门狗。 CPU必须经常从该地址读取,否则街机将执行硬件复位。 如果由于某种原因游戏崩溃,设备将自动重启。
您可以写入一些I / O端口地址:
- B000 :是否在vblank期间生成NMI; 似乎仅在引导过程中被禁用
- B004 :翻转整个屏幕; 我从未见过使用此功能,但对此有一个理论(请参阅下文)
屏幕翻转功能有点令人困惑,因为在玩游戏时,我从未见过它的用途。 但是,我对他的工作有预感,但是为了确认这一点,您需要编写代码。 当街机处于“鸡尾酒桌”配置时,两个玩家彼此相对坐着。 因此,我建议当游戏从玩家1切换到玩家2时,此功能会翻转屏幕。 但是,我还没有在模拟器中实现两人游戏模式。
音板
声卡本身是一台功能齐全的计算机,具有Z80 CPU(以3 MHz的频率运行),三个声卡(AY-38910以1.5 MHz的频率运行)以及RAM和ROM。 声卡的内存分配方案看起来非常简单:
- 0000..2000 :8 KB ROM
- 4000..4400 :1 KB的RAM
- 6000 :来自主板的声音命令
由于在0x8000地址上方的内存分配方案中没有任何有趣的内容,因此甚至没有连接CPU的最高地址触点:
特殊地址0x6000是内存中的I / O端口(8位锁存器),它不对应于实际RAM。 这是位于主板上0xB800的同一端口。 这是主卡和声卡之间的通信通道。
这三个声音芯片由这些Z80输出指令控制,而不是通过内存端口控制。 AY-3-8910仅打开两个I / O端口,第一个用于存储寄存器号,第二个用于写入或读取第一个端口指定的寄存器的内容。
I / O电路如下:
- 0x00 :第一个声音芯片:寄存器选择
- 0x01 :第一个声音芯片:访问所选寄存器
- 0x10 :第二声音芯片:寄存器选择
- 0x11 :第二声音芯片:访问所选寄存器
- 0x80 :第三声音芯片:寄存器选择
- 0x81 :第三声音芯片:访问所选寄存器
关于声音芯片AY-3-8910的几句话:
这是一个相当标准的设备,在当时的家用计算机中非常流行(例如,在Amstrad CPC,ZX Spectrum 128,MSX计算机等中)。 AY-3-8910产生了许多变体和克隆(例如,Yamaha YM2149,它本身成为整个更强大的声音芯片家族的基础)。
AY-3-8910具有3个矩形信号通道,一个可与三个通道混合的噪声发生器,以及一个包络发生器。 由于所有三个通道只有一个包络发生器,因此它不是特别有用,并且大多数游戏都使用CPU来调制音调和音量。
这意味着AY-3-8910芯片需要更多的CPU干预才能生成高质量的声音(与C64计算机中的更多独立SID芯片不同)。
看到在三个相当简单的声音芯片以及控制它们的CPU上可以完成的工作,真是太神奇了。 炸弹杰克的音乐和声音效果比我在大多数家用电脑游戏中所听到的要丰富得多。
该声卡唯一真正有趣的是它从主板接收命令的方式。
声音指令锁存器
“声音锁存器”是主卡和声卡通用的单字节存储(8位锁存器)。 锁存器绑定到主板上的地址0xB800和声卡上的地址0x6000。
当使用VSYNC打开NMI中断时,声卡执行一个非常简单的中断服务例程,该例程读取硬件锁存器,将其写入普通内存地址并设置“信号位”,该信号位告诉“主循环”已接收到新的声音命令:
ex af,af' ;0066 exx ;0067 ld hl,04390h ;0068 set 0,(hl) ;006b ld a,(06000h) ;006d ld (04391h),a ;0070 exx ;0073 ex af,af' ;0074 retn ;0075
NMI触点激活方法与主板方法略有不同:
在主板上,NMI引脚在VBLANK运行期间一直处于活动状态。
但是,在声卡上,NMI在触发VSYNC时被激活,并在VBLANK期间保持激活状态,直到中断服务过程从0x6000的锁存器读取数据为止。
设备识别出从地址0x6000读取时,将执行两个硬编码操作:
实际上,这是消除触点弹跳的简单方法,它不允许一个声音命令执行两次。
唯一的问题仍然存在:主板多久编写一次新命令(因为实现两个板的仿真方式取决于此)。
使用printf调试后,我发现主板每60 Hz帧最多记录一个声音命令。 这大大简化了仿真器“主周期”的结构。
两台必须相互交换数据的仿真计算机共同工作的问题是,一台计算机的仿真只有在一次可以执行许多周期而没有干扰的情况下才有效。
例如,最坏的情况是:
- 我们在计算机1中执行一条指令
- 我们在计算机2中执行一条指令
- 重复...
我的Z80仿真器并未针对每个指令的退出和进入仿真进行优化,因为在这种情况下,它应该刷新到内存中并从内存中加载每个指令开头和结尾的CPU状态。 如果CPU可以无干扰地处理许多指令,则可以将CPU的大部分状态存储在寄存器中,并将状态重置为最后一条指令的存储器。
也就是说,理想的情况是:我们在主机系统的整个帧中执行仿真系统而没有干扰(对于频率为4 MHz且频率为60 Hz的CPU,这意味着每帧大约6.7万个周期,或者从3000到1.6万说明Z80)。
使用炸弹杰克时,我需要确保声卡能够读取最后一条命令之前,主板上没有记录新命令。 在我发现主板每帧记录不超过一个命令之前,我考虑过需要创建一个复杂的命令队列,以拦截主板声音锁存器中的录音并将周期数和命令字节存储在队列中。
然后,在声卡执行其帧时,当达到命令周期号时,它将从命令队列中获取新命令。
这样的系统可以工作并且“正确”,但是会大大增加代码的复杂性。
最后,我决定使用没有任何队列的简单得多的解决方案。 由于主板每帧只记录一个命令,因此我在两台计算机上交替执行,以便每台计算机每帧执行两个时间片:
- 在主板上执行框架的前半部分
- 在声卡上执行帧的前半部分
- 在主板上执行框架的后半部分
- 在声卡上执行帧的后半部分
这样可以确保声卡正确看到主板记录的每个命令,并且可以同时执行每个仿真数千个周期。
当然,主机系统以60 Hz的帧频运行的事实是一个非常大胆的假设:)
最后一个...
关于WebAssembly上的仿真器版本的最后一个有趣的事实:
在WebAssembly上运行模拟器时,所有下载文件的压缩大小
大约等于113 KB:
- HTML,CSS和“手写” JS大约需要2.5 KB
- 每个emscripten运行时JS文件26.8 kb
- 每个.wasm文件83.7 KB
WASM文件包含街机的内置ROM。
未经压缩,这些ROM占用112 KB。
也就是说,带有集成ROM的
整个压缩仿真器几乎与未压缩ROM占据相同的体积:)
112 KB的ROM被压缩到大约57 KB,也就是说,没有ROM数据的WASM中压缩代码的真实大小小于30 KB(84-57)。
在我看来,对于8位系统的完整仿真器来说相当不错;)