Game boy模拟器:中断
Posted 妇男主人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Game boy模拟器:中断相关的知识,希望对你有一定的参考价值。
在上一部分中,通过引入精灵,为模拟游戏奠定了基础。但是,仿真器缺少一个方面:垂直消隐中断。这部分会从整体上介绍中断,特别是实现消隐中断;完成此操作后,模拟器将运行俄罗斯方块。
想象一下,您有一台带有网卡的计算机,以及一些处理来自网络的数据的软件。从计算机的角度来看,数据只是每隔一段时间就会进来,因此您需要某种方式让软件知道新数据已经到达。有两种方法可以实现:
轮询:软件每隔一段时间询问网卡是否有新数据到达。这是一种简单的做事方式,但有缺点:
- 软件在定期检查之前不知道新数据,这意味着数据到达计算机与软件处理之间存在延迟;
- 必须定期抽出时间进行检查,即使没有数据到达,也要从其他工作中抽出时间;
- 如果轮询进程每次检查都处理一个新数据,但数据到达速度更快,则网卡中会产生数据积压,并且有可能丢失某些数据;
- 如果没有其他工作要做,软件仍然要检查数据,这使计算机保持全速运行而无需做任何工作。
中断:网卡通知软件有新数据到达。这是一种更复杂的数据接收方式,涉及的步骤更多,但它减轻了轮询的所有缺点:
- 新数据一到就可以处理,到达和处理数据之间没有延迟;
- 当确实有数据要处理时,软件只需要花时间处理数据,并且可以根据需要随时调用处理例程以清除任何积压;
- 如果没有其他工作要做,计算机可以进入低功耗模式,直到网卡唤醒它以获取新数据。
中断和中断处理程序
中断的硬件实现
很明显,中断的概念是一个有用的概念,但是中断的工作同时有硬件和软件要求。在硬件方面,当中断到达时,CPU 必须暂时停止执行它正在执行的操作,而是开始执行中断处理程序(有时称为中断服务程序)。在上述场景中,网卡和 CPU 之间运行了一条线,允许网卡在数据到达时通知 CPU。
CPU中断处理流程图
CPU 将在每条指令结束时检查其中断输入。如果某个附加的外围设备(如网卡)发出了中断信号,则 CPU 将采取步骤启动中断处理程序:CPU 将保存它停止正常执行的位置,注册中断发生的事实,以及跳转到处理程序。
GameBoy 中的中断
在 GameBoy 中,有五种不同的中断线,从各种外设馈入。每个都有自己的 ISR,位于内存中的不同地址;中断列表如下。
打断 | ISR 地址(十六进制) |
---|---|
垂直空白 | 0040 |
LCD 状态触发 | 0048 |
定时器溢出 | 0050 |
串行链接 | 0058 |
手柄按键 | 0060 |
在垂直空白的情况下,在LCD底部穿入一根电线;一旦 GPU 完成对所有 LCD 线的扫描并运行到屏幕底部,中断就会触发并且 CPU 跳转到0040,执行消隐 ISR。
实现:中断标志
大多数 CPU 包含用于中断的“主标志”:如果启用此标志,它们将仅由 CPU 处理。GameBoy 中的 Z80 也不例外,但还有一些额外的寄存器可以处理 GameBoy 中可用的各个中断。这些是内存寄存器,因此它们由内存管理单元处理:
MMU 中的中断标志
登记 | 地点 | 笔记 | 细节 | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
中断 使能 | FFFF | 当位被设置时,可以触发 相应的 中断 |
| ||||||||||||||||||
中断 标志 | FF0F | 当位被设置时,发生 了中断 | 位的 顺序与 FFFF |
由于这些是内存寄存器,因此它们的实现是针对 MMU 的:
MMU.js:中断标志
MMU = {
_ie: 0,
_if: 0,
rb: function(addr)
{
switch(addr & 0xF000)
{
...
case 0xF000:
switch(addr & 0x0F00)
{
...
// Zero-page
case 0xF00:
if(addr == 0xFFFF)
{
return MMU._ie;
}
else if(addr >= 0xFF80)
{
return MMU._zram[addr & 0x7F];
}
else
{
// I/O control handling
switch(addr & 0x00F0)
{
case 0x00:
if(addr == 0xFF0F) return MMU._if;
break;
...
}
return 0;
}
}
}
},
...
};
Z80 的“主启用”开关以类似的方式用于 Z80 实现。CPU 为软件提供操作码以将主使能切换到开或关位置,因此还需要实现这些:
Z80.js:中断主使能
Z80 = {
_r: {
ime: 0,
...
},
reset: function()
{
...
Z80._r.ime = 1;
},
// Disable IME
DI: function()
{
Z80._r.ime = 0;
Z80._r.m = 1;
Z80._r.t = 4;
},
// Enable IME
EI: function()
{
Z80._r.ime = 1;
Z80._r.m = 1;
Z80._r.t = 4;
}
};
实现:中断处理
中断标志就位后,可以重新开发主执行循环,使其更符合 CPU中断处理流程图 中的执行路径。执行后,需要检查中断标志以查看是否发生了启用的中断;如果有,则可以调用其处理程序。
Z80.js:Vblank 中断处理程序
Z80 = {
_ops: {
...
// Start vblank handler (0040h)
RST40: function()
{
// Disable further interrupts
Z80._r.ime = 0;
// Save current SP on the stack
Z80._r.sp -= 2;
MMU.ww(Z80._r.sp, Z80._r.pc);
// Jump to handler
Z80._r.pc = 0x0040;
Z80._r.m = 3;
Z80._r.t = 12;
},
// Return from interrupt (called by handler)
RETI: function()
{
// Restore interrupts
Z80._r.ime = 1;
// Jump to the address on the stack
Z80._r.pc = MMU.rw(Z80._r.sp);
Z80._r.sp += 2;
Z80._r.m = 3;
Z80._r.t = 12;
}
}
};
while(true)
{
// Run execute for this instruction
var op = MMU.rc(Z80._r.pc++);
Z80._map[op]();
Z80._r.pc &= 65535;
Z80._clock.m += Z80._r.m;
Z80._clock.t += Z80._r.t;
Z80._r.m = 0;
Z80._r.t = 0;
// If IME is on, and some interrupts are enabled in IE, and
// an interrupt flag is set, handle the interrupt
if(Z80._r.ime && MMU._ie && MMU._if)
{
// Mask off ints that aren't enabled
var ifired = MMU._ie & MMU._if;
if(ifired & 0x01)
{
MMU._if &= (255 - 0x01);
Z80._ops.RST40();
}
}
Z80._clock.m += Z80._r.m;
Z80._clock.t += Z80._r.t;
}
下一次:更大的游戏
模拟器已经达到了一个合理的阶段:它至少能够以某种形式模拟已发布的游戏。但是,它确实存在游戏大小的问题。俄罗斯方块是一个 32kB ROM,完全适合内存映射中的“ROM”空间。游戏往往具有比这更大的 ROM,并且卡带遵循将 ROM 的一部分映射到内存的过程。下一次,我将研究 GameBoy 最简单的可用 ROM 映射形式,以及它在 64kB 游戏 ROM 上的实现。
以上是关于Game boy模拟器:中断的主要内容,如果未能解决你的问题,请参考以下文章