C语言模拟最简单的计算机
以下内容参考南大"计算机系统基础"实验:不停计算的机器
概述
如下面的伪代码所示,计算机运行程序的过程为取指令-->运行指令-->更新PC的值。
c
while (1) {
从PC指示的存储器位置取出指令;
执行指令;
更新PC;
}
取指(instruction fetch, IF)
执行一条指令前, 首先要拿到这条指令。指令究竟在哪里呢? 还记得冯诺依曼体系结构的核心思想吗? 那就是"存储程序, 程序控制".
这两句话告诉我们, 指令被放在存储器中, 由PC指出当前指令的位置. 事实上, PC就是一个指针!!!
取指令要做的事情自然就是将PC指向的指令从内存读入到CPU中。
译码(instruction decode, ID)
在取指阶段, 计算机拿到了将要执行的指令. 让我们来看看这个指令的·面貌, 睁大眼睛一看, 竟然是个0和1组成的比特串!
c
10111001 00110100 00010010 00000000 00000000
计算机也只是个巨大的数字电路, 它只能理解0和1。
拿到指令后,我们需要对指令进行翻译。指令通常包含操作码和操作数两个部分的信息。根据操作码的含义去执行对应的操作,操作数一般作为该操作需要的数据。
执行(execute, EX)
经过译码之后, CPU就知道当前指令具体要做什么了, 执行阶段就是真正完成指令的工作. 现在TRM只有加法器这一个执行部件, 必要的时候,
只需要往加法器输入两个源操作数, 就能得到执行的结果了. 之后还要把结果写回到目的操作数中, 可能是寄存器, 也可能是内存.
更新PC
执行完一条指令之后, CPU就要执行下一条指令. 在这之前, CPU需要更新PC的值, 让PC加上刚才执行完的指令的长度,
即可指向下一条指令的位置.
代码实操
这里直接从南京大学"计算机系统基础"实验搬过来:不停计算的机器
为南大打call !!!
模拟的计算机有4个8位的寄存器, 一个4位PC, 以及一段16字节的内存. 它支持R型和M型两种指令格式, 4条指令. 其指令手册如下:
c
4 2 0
| | | +----+--+--+
mov rt,rs | R[rt] <- R[rs] | R-type | |0000|rt|rs|
| | | +----+--+--+
| | | +----+--+--+
add rt,rs | R[rt] <- R[rs] + R[rt] | R-type | |0001|rt|rs|
| | | +----+--+--+
| | | +----+--+--+
load addr | R[0] <- M[addr] | M-type | |1110| addr|
| | | +----+--+--+
| | | +----+--+--+
store addr | M[addr] <- R[0] | M-type | |1111| addr|
| | | +----+--+--+
c
#include <stdint.h>
#include <stdio.h>
#define NREG 4
#define NMEM 16
// 定义指令格式
typedef union {
struct { uint8_t rs : 2, rt : 2, op : 4; } rtype;
struct { uint8_t addr : 4 , op : 4; } mtype;
uint8_t inst;
} inst_t;
#define DECODE_R(inst) uint8_t rt = (inst).rtype.rt, rs = (inst).rtype.rs
#define DECODE_M(inst) uint8_t addr = (inst).mtype.addr
uint8_t pc = 0; // PC, C语言中没有4位的数据类型, 我们采用8位类型来表示
uint8_t R[NREG] = {}; // 寄存器
uint8_t M[NMEM] = { // 内存, 其中包含一个计算z = x + y的程序
0b11100110, // load 6# | R[0] <- M[y]
0b00000100, // mov r1, r0 | R[1] <- R[0]
0b11100101, // load 5# | R[0] <- M[x]
0b00010001, // add r0, r1 | R[0] <- R[0] + R[1]
0b11110111, // store 7# | M[z] <- R[0]
0b00010000, // x = 16
0b00100001, // y = 33
0b00000000, // z = 0
};
int halt = 0; // 结束标志
// 执行一条指令
void exec_once() {
inst_t this;
this.inst = M[pc]; // 取指
switch (this.rtype.op) {
// 操作码译码 操作数译码 执行
case 0b0000: { DECODE_R(this); R[rt] = R[rs]; break; }
case 0b0001: { DECODE_R(this); R[rt] += R[rs]; break; }
case 0b1110: { DECODE_M(this); R[0] = M[addr]; break; }
case 0b1111: { DECODE_M(this); M[addr] = R[0]; break; }
default:
printf("Invalid instruction with opcode = %x, halting...\n", this.rtype.op);
halt = 1;
break;
}
pc ++; // 更新PC
}
int main() {
while (1) {
exec_once();
if (halt) break;
}
printf("The result of 16 + 33 is %d\n", M[7]);
return 0;
}