CPU

  • 模型
    • 内部状态值
    • 操作模拟
  • 内存接口
  • 调度和重置
    • 重置
    • 调度

本文通过为模拟物理机的每个部分奠定基础,着手实现 GameBoy 模拟的基础。起点是CPU。

模型

计算机的传统模型是一个处理单元,由指令程序告诉它该做什么;程序可能会使用自己的特殊内存进行访问,也可能与普通内存位于同一区域,具体取决于计算机。每条指令都需要很短的时间来运行,并且它们都被一条一条地运行。从 CPU 的角度来看,计算机一打开就会启动一个循环,从内存中获取一条指令,计算出它所说的内容并执行它。

为了跟踪 CPU 在程序中的位置,CPU 保存了一个数字,称为程序计数器 (PC)。从内存中取出一条指令后,PC 会前进由构成该指令的字节数。

原版GameBoy中的CPU是改装的Zilog Z80,所以有以下几点是相关的:

  • Z80 是一个 8 位芯片,所以所有的内部工作都是一次一个字节的;
  • 存储器接口最多可寻址 65,536 字节(16 位地址总线);
  • 程序通过与普通内存相同的地址总线进行访问;
  • 一条指令可以是一到三个字节之间的任何地方。

除了PC之外,CPU内部还有其他一些可以用来计算的数字,它们被称为寄存器:A、B、C、D、E、H和L。每个都是一个字节,所以每一个都可以保存一个从 0 到 255 的值。Z80 中的大多数指令都是用来处理这些寄存器中的值:从内存中加载一个值到一个寄存器中,加减值等等。

如果指令的第一个字节中有 256 个可能的值,那么基本表中就有 256 个可能的指令。该表在Gameboy Z80操作码图中有详细说明。这些中的每一个都可以由 JavaScript 函数模拟,该函数对寄存器的内部模型进行操作,并对内存接口的内部模型产生影响。

Z80 中还有其他寄存器处理保持状态:标志寄存器 (F),其操作将在下面讨论;以及与 PUSH 和 POP 指令一起使用的堆栈指针 (SP),用于值的基本 LIFO 处理。因此,Z80 仿真的基本模型需要以下组件:

  • 内部状态:
  •  用于保持寄存器当前状态的结构;
    
  •  执行最后一条指令所用的时间;
    
  •  CPU 运行的总时间;
    
  • 模拟每条指令的功能;
  • 将所述函数映射到操作码映射上的表;
  • 与模拟内存对话的已知接口。

内部状态可以保持如下:

内部状态值

Z80 = {// Time clock: The Z80 holds two types of clock (m and t)_clock: {m:0, t:0},// Register set_r: {a:0, b:0, c:0, d:0, e:0, h:0, l:0, f:0,    // 8-bit registerspc:0, sp:0,                                // 16-bit registersm:0, t:0                                   // Clock for last instr}
};

标志寄存器 (F) 对处理器的功能很重要:它根据上次操作的结果自动计算某些位或标志。 Gameboy Z80 中有四个标志:

  • 零 (0x80):如果上次操作产生的结果为 0,则设置;
  • 操作 (0x40):设置最后一个操作是否为减法;
  • 半进位 (0x20):设置是否在上次操作的结果中,字节的下半部分溢出超过 15;
  • 进位 (0x10):如果最后一次操作产生的结果大于 255(加法)或小于 0(减法),则设置。

由于基本计算寄存器是 8 位的,进位标志允许软件计算出如果计算结果溢出寄存器的值发生了什么变化。考虑到这些标志处理问题,下面显示了一些指令模拟示例。这些例子是简化的,不计算半进位标志。

操作模拟

Z80 = {// Internal state_clock: {m:0, t:0},_r: {a:0, b:0, c:0, d:0, e:0, h:0, l:0, f:0, pc:0, sp:0, m:0, t:0},// Add E to A, leaving result in A (ADD A, E)ADDr_e: function() {Z80._r.a += Z80._r.e;                      // Perform additionZ80._r.f = 0;                              // Clear flagsif(!(Z80._r.a & 255)) Z80._r.f |= 0x80;    // Check for zeroif(Z80._r.a > 255) Z80._r.f |= 0x10;       // Check for carryZ80._r.a &= 255;                           // Mask to 8-bitsZ80._r.m = 1; Z80._r.t = 4;                // 1 M-time taken}// Compare B to A, setting flags (CP A, B)CPr_b: function() {var i = Z80._r.a;                          // Temp copy of Ai -= Z80._r.b;                             // Subtract BZ80._r.f |= 0x40;                          // Set subtraction flagif(!(i & 255)) Z80._r.f |= 0x80;           // Check for zeroif(i < 0) Z80._r.f |= 0x10;                // Check for underflowZ80._r.m = 1; Z80._r.t = 4;                // 1 M-time taken}// No-operation (NOP)NOP: function() {Z80._r.m = 1; Z80._r.t = 4;                // 1 M-time taken}
};

内存接口

可以操作自身内部寄存器的处理器很好,但它必须能够将结果放入内存才能有用。同理,上面的CPU仿真需要一个接口来仿真内存;这可以由内存管理单元 (MMU) 提供。由于 Gameboy 本身不包含复杂的 MMU,因此模拟单元可以非常简单。

此时,CPU 只需要知道存在一个接口即可;Gameboy 如何将内存和硬件组映射到地址总线的细节与处理器的操作无关。CPU 需要进行四项操作:

MMU.js:内存接口

MMU = { rb: function (addr) { /* 从给定地址读取 8 位字节 */ }, rw: function (addr) { /* 从给定地址读取 16 位字 */ }, wb: function (addr, val) { /* 将 8 位字节写入给定地址 */ }, ww: function (addr, val) { /* 将 16 位字写入给定地址 */ } };

有了这些,就可以模拟其余的 CPU 指令。其他几个例子如下所示:

Z80.js:内存处理指令

    // 将寄存器 B 和 C 压入堆栈 (PUSH BC) PUSHBC: function () { Z80._r.sp--;                               // 删除堆栈MMU.wb(Z80._r.sp, Z80._r.b);               //写B Z80._r.sp--;                               // 删除堆栈MMU.wb(Z80._r.sp, Z80._r.c);               // 写 C Z80._r.m = 3 ; Z80._r.t = 12 ;               // 进行了 3 M 次},// 从堆栈中弹出寄存器 H 和 L (POP HL) POPHL: function () { Z80._r.l = MMU.rb(Z80._r.sp);              // 读取 LZ80._r.sp++;                               // 向上移动堆栈Z80._r.h = MMU.rb(Z80._r.sp);              // 读取 H Z80._r.sp++;                               // 向上移动堆栈Z80._r.m = 3 ; Z80._r.t = 12 ;               // 3 M 次使用}// 从绝对位置读取一个字节到 A (LD A, addr) LDAmm: function () {var addr = MMU.rw(Z80._r.pc);              // 从指令中获取地址Z80._r.pc += 2 ;                            //前进PC Z80._r.a = MMU.rb(addr);                   // 从地址Z80._r.m = 4读取;Z80._r.t= 16 ;                 // 4 M 次使用}

调度和重置

指令就位后,CPU 剩下的难题就是在 CPU 启动时重置 CPU,并向仿真例程提供指令。具有复位例程允许 CPU 停止并“倒带”到执行开始;一个例子如下所示。

重置

reset: function() {Z80._r.a = 0; Z80._r.b = 0; Z80._r.c = 0; Z80._r.d = 0;Z80._r.e = 0; Z80._r.h = 0; Z80._r.l = 0; Z80._r.f = 0;Z80._r.sp = 0;Z80._r.pc = 0;      // Start execution at 0Z80._clock.m = 0; Z80._clock.t = 0;}

为了运行仿真,它必须仿真前面详述的获取-解码-执行序列。“执行”由指令仿真功能负责,但提取和解码需要一段专门的代码,称为“调度循环”。这个循环接受每条指令,解码它必须发送到哪里执行,然后将它分派给相关函数。

调度

while(true)
{var op = MMU.rb(Z80._r.pc++);              // Fetch instructionZ80._map[op]();                            // DispatchZ80._r.pc &= 65535;                        // Mask PC to 16 bitsZ80._clock.m += Z80._r.m;                  // Add time to CPU clockZ80._clock.t += Z80._r.t;
}Z80._map = [Z80._ops.NOP,Z80._ops.LDBCnn,Z80._ops.LDBCmA,Z80._ops.INCBC,Z80._ops.INCr_b,...
];

如果没有模拟器来运行它,实现 Z80 仿真核心是没有用的。在本系列的下一部分中,模拟 Gameboy 的工作将开始:我将查看 Gameboy 的内存映射,以及如何通过 Web 将游戏图像加载到模拟器中。

Game boy模拟器(1):CPU相关推荐

  1. NES模拟器开发-CPU笔记

    我的项目XNES已经开始动手编码了,目前的进度大概是cpu的模拟完成了大概10~20%左右.简单记录一下CPU模拟过程中遇到的问题和思考. 原理: cpu模拟实际就是模拟cpu处理opcode的过程, ...

  2. 华为ensp模拟器占用CPU高问题处理

    在使用华为Esnp模拟器时发现启动3个路由器,CPU使用率居然高达90-100%,后续启动的路由器卡顿无法使用,我们可以通过如果方法解决: 1.在电脑BIOS中开启超线程HT和虚拟化VT,电脑厂商不同 ...

  3. android 模拟器虚拟CPU的修改

    现在很多应用检测模拟器的时候都会读取 /proc/cpuinfo中的信息来作为是应用是否运行在模拟器上的检测.其中最主要是检测 其中的 model name 这一行 是否为 Android virtu ...

  4. Appium移动自动化测试(三)--安装Android模拟器(转)

    Appium移动自动化测试(三)--安装Android模拟器 2015-06-08 10:33 by 虫师, 30828 阅读, 9 评论, 收藏, 编辑 当Android SDK安装完成之后,并不意 ...

  5. 如何预编译 Android 模拟器专用内核

    I. 辅助脚本 我们现在提供了一个辅助脚本来重新构建内核,其位于 $AOSP/prebuilts/qemu-kernel/build-kernel.sh. 请确保使用了 aosp/master 的 c ...

  6. CPU/ABI显示No system images installed for this target的解决方案

    CPU/ABI显示No system images installed for this target的解决方案 手动下载image http://www.androiddevtools.cn/ SD ...

  7. “不懂 CPU 工作原理又如何,直接用代码模拟一个!”

    近日,一位来自 BBC 的软件工程师 Daniel Harper 从浅入深,分享了以代码的方式来实现 CPU 所有功能的可行性,希望对大家了解计算机的内容原理有所帮助. 作者 | Daniel Har ...

  8. 逍遥模拟器使用指南(二、电脑浏览器直接和模拟器互传方法)

    1.模拟器 设置桥接后使用 2.工具下载 https://www.lanzou.com/b0264a1dc  密码:8888 链接:https://share.weiyun.com/7vK5iwVH ...

  9. iOS设备的CPU架构

    模拟器: 4s-5: i386 5s-6s Plus: x86_64 真机(iOS设备): 真机(iOS设备): armv6: iPhone.iPhone 2.iPhone 3G.iPod Touch ...

  10. android模拟器 对比,安卓模拟器多开用哪个模拟器好?实测数据对比哪个好用

    长期电脑上挂机的一直都是在寻找哪个安卓模拟器挂机好?一般我们都是使用速度快流畅更省电脑资源的模拟器多开,省资源我们就可以多开更多,挂机的时间也更长更稳定.目前常用的模拟器中MUMU模拟器是没有多开功能 ...

最新文章

  1. Deep learning的一些教程 (转载)
  2. FreeSql.Generator命令行代码生成器是如何实现的
  3. mysql 修复表 阿里云_MySql数据表修复方法-阿里云开发者社区
  4. 9. OD-PEID的入门及BASIC(VB)开发的程序破解
  5. 从0开始安卓开发之路_Android Studio安装包
  6. bpmn js 生成json_js处理的8种跨域方法
  7. 博弈论(Game Theory)入门学习笔记(持续更新)
  8. vue中使用阿里巴巴矢量图标库的图标
  9. wordpress 上传图片时提示“无法建立目录wp-content/uploads/2019/03。有没有上级目录的写权限?
  10. 第一代电子计算机主要使用,第一代电子计算机主要采用的电子元件是什么?
  11. LeetCode练习题:斐波那契数列
  12. 给所有大龄测试员写的一份信
  13. 通过 ICMP 协议实现 Ping Tunnel 建立可穿透网络隧道
  14. JS 中的 False 与空值
  15. CentOS8搭建实现私有CA和证书申请
  16. excel快捷键附录笔记
  17. Visual Studio 2017、2019 调试Docker无法启动,卡在vsdbg\vs2017u5 exists, deleting.
  18. 区块链上的虚拟开放世界游戏是怎样的?| TVP思享
  19. Qt 简单计算器实现 附源码
  20. 科技创梦 乐赢未来!第十九届ChinaJoy如期开幕

热门文章

  1. 【信息论基础】离散信息的度量—自信息和互信息
  2. 蓝汛CEO推动CHN-IX发展,为互联网行业增效减负
  3. c语言中单引号b是啥意思,c语言中单引号和双引号的区别和应用?
  4. 找谷歌地图上任意点的经纬度
  5. 做题记录uva1626
  6. (原)hisi3531立体声pcm实现播放方式
  7. 大数据——Flink dataStream 中窗口函数的使用
  8. android 百度地图 自定义地图标注,百度地图自定义标注
  9. 【阶段一】java之面向对象上
  10. 【转载】一个软件测试工程师的学习体验 (受用)