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当位被设置时,可以触发
相应的
中断
少量当 0当 1
0空白关闭空白
1液晶显示器关闭液晶显示
2定时器关闭打开定时器
3串行关闭连续上
4手柄关闭手柄开启
中断
标志
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模拟器:中断的主要内容,如果未能解决你的问题,请参考以下文章

Game boy模拟器:运行内存

Game boy模拟器:图形

Game boy模拟器:内存池

Game boy模拟器:完整的 Z80 内核CPU源码

Game boy模拟器:CPU

Game boy模拟器:输入