从零开始的模拟器(1)

 关于模拟器

这里说的模拟器,是通过软件模拟CPU、外设、存储系统等硬件的行为,来虚拟出一台设备,并在其上运行操作系统及应用程序。比如 Android 模拟器。其实和平常用的虚拟机没太大区别,不过虚拟机通常是模拟 x86 架构,速度很快;而这里我打算模拟 MIPS 架构,与 x86 差异很大,速度慢。这也是 Android 模拟器慢的原因。

简易 CPU

原理

《计算机组成与设计(硬件/软件接口)》2.1-2.10节
CPU 通过执行指令来完成操作,因此我们应首先看看指令长什么样子:
从零开始的模拟器(1)_第1张图片

R型指令,常用于计算

图片描述

I型指令,常用于访存、分支

图片描述

J型指令,常用于跳转
MIPS 采用了定长的指令结构,所有指令都是32位长,通过识别 op 就能确定指令的类型(在我们要实现的 MIPS 核心指令集上,R型的 opcode 都为0,J型的有两个 j(0x2)、jal(0x3)),进而就能确定每一位的含义。
再看看都有哪些指令:
从零开始的模拟器(1)_第2张图片

操作都比较简单,毕竟这是计算机中最底层的语言了。

实现

为了执行指令,我们还需要模拟寄存器和存储器。
MIPS 寄存器有32个:
从零开始的模拟器(1)_第3张图片

    int regs[32];

就可以模拟。另外还需要模拟 $pc 寄存器指示当前指令的地址。该寄存器不能被直接操作,所以没有列在上面。原本超快速的寄存器放到内存中模拟,速度必然被拖慢了。
存储器直接 malloc 申请一段空间就好了。
利用循环不断取指令,直接用 switch 语句判断 op 字段来译码,用相应的 C 语句模拟指令行为,我们的 CPU 就运行起来了。

是不是太简单了?
确实是,用软件模拟省略了大量的硬件实现细节,我们做的只是描述 MIPS 指令的行为,并让编译器和汇编器帮我们转化成 x86 指令执行,这其中必然引入了大量不必要的操作。

一个更接近真实 CPU 的模型

原理

《计算机组成与设计(硬件/软件接口)》4.1-4.4节
这里就是要模拟传说中的硬布线控制器。
从零开始的模拟器(1)_第4张图片

重点是中间的主控单元,通过对 op 字段的分析,确定每个控制信号有效/无效(即高电位/低点位)。比如 RegWrite 有效,则寄存器堆就会把 WriteData 端口的值写入 Write Register 端口输入的寄存器号中, 无效则执行读取操作;RegDst 有效,则会将 rd 字段输入 Write Register 端口,否则将 rt 字段输入该端口。每个部件都按照指定的方式工作,指令也就被执行了。
此外,每个单元还有一个时钟信号输入(即矩形波),在时钟的上升沿/下降沿,单元的状态改变。PC 寄存器每个周期都会自增,这样就能不断读入新的指令并执行。

实现

一般来说,这种逻辑设计应该使用 Verilog 语言描述。这里我用了一种比较奇葩的方法,使用 C 语言来描述。
每个单元用一个函数模拟,参数就是输入端口上的值,返回值就是输出端口上的值,控制信号作为全局变量,函数调用时的实参就起到了多选器的作用。按上图设计就好了。
需要注意的是,C 语句有先后顺序:必须先执行指令存储器,再执行寄存器,等等。但是在硬件中指定好控制信号,在一个周期内就会达到想要的状态,并没有人为指定先后顺序,有点像原子操作。用 C 模拟还是有点不伦不类的感觉。

这里我模拟了 lw、 sw、 beq、 add、 sub、 and、 or、 slt、 j 指令。其实这一部分完全没有必要,只会增加程序的复杂度。不过通过这样的练习,我们对处理器的认识也会更加深刻些。

具体过程看代码吧:https://github.com/hduhxc/echovm

你可能感兴趣的:(底层,模拟器)