文章目录
一、前言
多核版见我的另一篇文章北邮2021计导大作业丨C语言实现冯诺依曼式计算机CPU模拟器(二):多核版
终于下决心来写这篇文章。对我来说北邮这个大作业实在是太顶了,做的过程中我也确实学到了很多东西(与之相比复旦C语言课程的大作业就实在是太水了)。
该代码为一种可能的实现思路,已经在OJ上提交并且全部AC,仅供大家参考、交流。如有后来者(或许有2022的朋友?)参考此代码,切记不要直接复制粘贴提交!就算是对变量名或函数名称等进行修改,查重系统也是会查到的。还是希望大家只是把这篇文章当做参考,以此启发自己,独立地完成大作业。不要妄自菲薄,不要认定做不出来,你一定能想出自己的思路,尽管它可能是复杂的,尽管它可能不是最好的,但它是你自己的,这一点最重要。
二、课题要求
(一)任务概述
- 模拟一个简易的冯诺依曼式计算机CPU的工作。
- 该CPU字长为16位,共11个寄存器,其中3个系统寄存器,分别为程序计数器,指令寄存器,标志寄存器;8个通用寄存器,即寄存器1、2、3、4(数据寄存器),寄存器5、6、7、8(地址寄存器)。该CPU至多支持32K内存。内存分两部分,一部分为代码段,从地址0开始。另一部分为数据段,从地址16384开始。
- 该CPU所支持的指令集见word文档。每条指令固定由32位(由左至右依次编号为0到31)二进制数组成,其中第0到7位为操作码,代表CPU要执行哪种操作;第8到15位为操作对象,如寄存器,内存地址等;第16到31位为立即数。该CPU有一个输入端口和一个输出端口。输入端口的数据由标准输入设备(键盘)输入,输出端口的数据输出到标准输出设备(显示器)上。
(二)程序的大致步骤
- 程序开始时要从指定文件中读入一段用给定指令集写的程序至内存(从地址0开始顺序保存),程序计数器初始值也为0。此功能为指令加载。
- 指令加载完成后程序就开始不断重复取指令、分析指令和执行指令的过程。程序每执行一条指令就要输出CPU当前的状态,如各寄存器的值等。当执行到停机指令时,程序按要求输出后就结束了。
- 取指令:要求读取程序计数器PC内的指令地址,根据这个地址将指令从内存中读入,并保存在指令寄存器中,同时程序计数器内容加4,指向下一个条指令。(因为我们所有的指令长度固定为4个字节,所以加4)。
- 分析指令:是指对指令寄存器中的指令进行解码,分析出指令的操作码,所需操作数的存放位置等信息等。
- 执行指令:完成相关计算并将结果写到相应位置。
(三)输入输出方式
1. 输入方式
以文件的方式输入,该文件为一个以停机指令为结尾的指令序列。如:
00001011000100000000000000000000
00000001010100000100000000000000
00000001010100010000000000000000
00001011000100000000000000000000
00000010000101010000000000000000
00001100000100000000000000000000
00000000000000000000000000000000
2. 输出方式
-
每执行一条指令后都要输出各寄存器状态,格式见样例
-
当执行到输入指令时在用户输入前要输出:
in :\n
-
当执行到输出时输出前要先输出:
out :
-
输出指令结束后要输出一个换行符。
-
所有指令执行完后要输出当前内存内容 先输出代码段:
- 每四个字节一输出(也就是每条指令当成一个整数输出),每行输出8条指令,共输出16行
- 然后输出数据段: 每两个字节当成一个整数输出,每行输出16个整数,共
-
输出16行。 具体格式见样例。
3.输入输出样例:
- 输入指令:(注意in:后的换行符)
in:
20
- 输出指令:
out:30
- 寄存器状态:
ip = 56
flag = 1
ir = 3104
ax1 = 1 ax2 = 2 ax3 = 3 ax4 = 2
ax5 = 0 ax6 = 16396 ax7 = 0 ax8 = 0
- 代码段内存
codeSegment :
185597952 22036480 22085632 23085058 17825792 23134208 152371200 167903260
36044800 34603009 20316160 36700161 23265280 167804956 203423744 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
- 数据段内存
dataSegment :
5 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
三、指令集分析
(一)指令集(单核版)
类别 | 指令 | 说明 |
---|---|---|
停机指令 | 00000000000000000000000000000000 | 停止程序执行 |
数据传送指令 | 00000001000100000000000000000000 | 将一个立即数(绿色部分)传送至寄存器 |
数据传送指令 | 00000001000101010000000000000000 | 将寄存器5(5、6、7、8号寄存器为地址寄存器)中地址所指向的内存单元(2个字节)的内容传送至寄存器1 |
数据传送指令 | 00000001010100010000000000000000 | 将寄存器1的内容传送至寄存器5中地址所指向的内存单元(2个字节)5、6、7、8号寄存器为地址寄存器)。 |
算术运算指令 | 00000010000100000000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)相加,结果保存至寄存器1 |
算术运算指令 | 00000010000101010000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数相加,结果保存至寄存器1 |
算术运算指令 | 00000011000100000000000000000000 | 将寄存器1内的数减去一个立即数(绿色部分),结果保存至寄存器1 |
算术运算指令 | 00000011000101010000000000000000 | 将寄存器1内的数减去寄存器5中地址所指向的内存单元(2个字节)里存的数,结果保存至寄存器1 |
算术运算指令 | 00000100000100000000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)相乘,结果保存至寄存器1 |
算术运算指令 | 00000100000101010000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数相乘,结果保存至寄存器1 |
算术运算指令 | 00000101000100000000000000000000 | 将寄存器1内的数除以(C语言的整数除法)一个立即数(绿色部分),结果保存至寄存器1 |
算术运算指令 | 00000101000101010000000000000000 | 将寄存器1内的数除以(C语言的整数除法)寄存器5中地址所指向的内存单元(2个字节)里存的数,结果保存至寄存器1 |
逻辑运算指令 | 00000110000100000000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)做逻辑与,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
逻辑运算指令 | 00000110000101010000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数做逻辑与,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
逻辑运算指令 | 00000111000100000000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)做逻辑或,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
逻辑运算指令 | 00000111000101010000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数做逻辑或,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
逻辑运算指令 | 00001000000100000000000000000000 | 将寄存器1内的数做逻辑非,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
逻辑运算指令 | 00001000000001010000000000000000 | 将寄存器5中地址所指向的内存单元(2个字节)里存的数做逻辑非,结果仍保存至寄存器5中地址所指向的内存单元(如果结果为真则保存1,否则保存0) |
比较指令 | 00001001000100000000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)比较,如两数相等,则标志寄存器被修置为0,如寄存器1大,则标志寄存器被置为1,如寄存器1小,则标志寄存器被置为-1。 |
比较指令 | 00001001000101010000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数比较,如两数相等,则标志寄存器被置为0,如寄存器1大,则标志寄存器被置为1,如寄存器1小,则标志寄存器被置为-1。 |
跳转指令 | 00001010000000000000000000000000 | 无条件跳转指令,转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
跳转指令 | 00001010000000010000000000000000 | 如果标志寄存器内的值为0则转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
跳转指令 | 00001010000000100000000000000000 | 如果标志寄存器内的值为1则转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
跳转指令 | 00001010000000110000000000000000 | 如果标志寄存器内的值为-1则转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
输入输出指令 | 00001011000100000000000000000000 | 从输入端口读入一个整数并保存在寄存器1中。也就是从键盘读一个整数到寄存器1中。 |
输入输出指令 | 00001100000100000000000000000000 | 将寄存器1中的数输出到输出端口。也就是将寄存器1中的数以整数的形式输出到显示器上,同时输出一个换行符。 |
(二)指令分析
1.指令结构
所有的指令都是32位二进制数字,具体可拆解为以下四个部分(以下位数均为从左到右):
- 1-8位:指明任务类型
- 9-12位:操作对象1
- 13-16位:操作对象2
- 17-32位:立即数
以指令00000001001001100000000000000000
为例:
- 1-8位:
00000001
,对应数据传送指令 - 9-12位:
0010
,,对应寄存器2(数据寄存器) - 13-16位:
0110
,对应寄存器6(地址寄存器) - 17-32位:
0000000000000000
,转换为十进制即为0
。因为该条指令的两个操作对象都是寄存器,不涉及立即数的运算,因此为0。
2.指令种类及作用
分析单核版指令集,可以发现指令总体可分为六大类(停机指令此处不予讨论):
- 数据传送指令
- 算术运算指令
- 逻辑运算指令
- 比较指令
- 跳转指令
- 输入输出指令
下面具体分析这六类指令。
(1)数据传送指令
根据指令集的描述,数据传送指令的前1-8为均为00000001
,即十进制的1
。根据操作对象1(9-12位)与操作对象2(13-16位)的不同,可以分为三大类:
- 操作对象2为
0000
,这表明要将立即数传入操作对象1指代的寄存器内。 - 操作对象1转换为十进制为1-4(数据寄存器),操作对象2转换为十进制为5-8(地址寄存器),这表明要将操作对象2(5、6、7、8号寄存器)中地址所指向的内存单元(2个字节)的内容传送至操作对象1(1、2、3、4号寄存器)。
- 操作对象1转换为十进制为5-8(地址寄存器),操作对象2转换为十进制为1-4(数据寄存器),这表明将操作对象2(1,2,3,4寄存器)的内容传送至操作对象1(5,6,7,8寄存器)中地址所指向的内存单元(2个字节)。
数据传送指令即为赋值运算。
(2)算术运算指令
当指令的1-8位转换为10进制为2-5时,对应算术运算指令:
00000010
:十进制的2,加法运算00000011
:十进制的3,减法运算00000100
:十进制的4,乘法运算00000101
:十进制的5,除法运算
根据操作对象1与操作对象2的不同可分为两大类:
- 操作对象1为1-4,操作对象2为0,表明对数据寄存器与立即数进行运算,将操作对象1(数据寄存器)内的数与一个立即数运算(加减乘除),结果保存至操作对象1(数据寄存器)。
- 操作对象1为1-3,操作对象2为5-8,表明对数据寄存器与地址寄存器进行运算,将操作对象1(数据寄存器)内的数与操作对象2(地址寄存器)中地址所指向的内存单元(2个字节)里存的数运算(加减乘除),结果保存至操作对象1(数据寄存器)。
算术运算指令即为四则运算。
(3)逻辑运算指令
当指令的1-8位转换为十进制为6-8时,对应逻辑运算指令:
00000110
:十进制的6,逻辑与00000111
:十进制的7,逻辑或00001000
:十进制的8,逻辑非
对于逻辑与、逻辑或运算指令,按操作对象1与操作对象2的不同可分为以下两类:
- 操作对象1为1-4,操作对象2为0,表示用操作对象1(数据寄存器)中的数与立即数做逻辑与(逻辑或)运算,将结果保存在操作对象1(数据寄存器)中(如果结果为真保存为1,结果为假保存为0)。
- 操作对象1位1-4,操作对象2为5-8,表示用操作对象1(数据寄存器)中的数与操作对象2(地址寄存器)中地址所指向的内存单元(两个字节)中的数做逻辑与(逻辑或)运算,结果保存在操作对象1(数据寄存器)中(如果结果为真则保存为1,结果为假保存为0)。
对于逻辑非运算指令,按操作对象1与操作对象2的不同也可分为两类:
- 操作对象1为1-4,操作对象2为0,表明对操作对象1(数据寄存器)中的数做逻辑非,运算结果保存在操作对象1中(若结果为真则保存为1,结果为假保存为0)。
- 操作对象1为0,操作对象2为5-8,表明对操作对象2(地址寄存器)中地址所指向的内存单元(两个字节)中的数做逻辑非,运算结果保存至该内存单元(两个字节)(如果结果为真保存为1,结果为假保存为0).
逻辑运算指令即为与或非。
(4)比较指令
当指令1-8位为9,对应逻辑运算指令。按操作对象1与操作对象2的不同,可分为两类:
- 操作对象1为1-4,操作对象2为0,表示将操作对象1(数据寄存器)内的数与一个立即数比较,如两数相等,则标志寄存器被修置为0,如操作对象1大,则标志寄存器被置为1,如操作对象1小,则标志寄存器被置为-1。
- 操作对象1位1-4,操作对象2为5-8,表示将操作对象1(数据寄存器)内的数与操作对象2(地址寄存器)中地址指向的内存单元(两个字节)比较,如两数相等,则标志寄存器被修置为0,如操作对象1大,则标志寄存器被置为1,如操作对象1小,则标志寄存器被置为-1。
比较指令即为比较运算。
(5)跳转指令
当指令1-8位为10,对应跳转指令。根据操作位2的不同可分四类:
- 操作对象2为0,即
0000
,无条件跳转指令,转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。 - 操作对象2为1,即
0001
,如果标志寄存器内的值为0则转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。 - 操作对象2为2,即
0010
,如果标志寄存器内的值为1则转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。 - 操作,对象2为3,即
0011
,如果标志寄存器内的值为-1则转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。
跳转指令用来实现循环、选择、switch、goto语句等。
(6)输入输出指令
当指令1-8位为11或12,对应输入输出指令:
00001011
:十进制为11,对应输入指令。从输入端口读入一个整数并保存在操作对象1(数据寄存器)中。也就是从键盘读一个整数到寄存器1中。00001100
:十进制为12,对应输出指令。将操作对象1(数据寄存器)中的数输出到输出端口。也就是将寄存器1中的数以整数的形式输出到显示器上,同时输出一个换行符。
输入输出指令对应printf()及scanf()等输入输出函数。
(三)指令部分总结
不难想到,六种指令相互结合,可以实现诸如if-else
语句、循环语句等C语言基本语句,从而实现一些简单的C语言程序。但是,仔细思考不难发现,指令集中的指令只是一部分,并不全,如数据传送指令部分只涉及从立即数到数据寄存器、从数据寄存器到地址寄存器、从地址寄存器到数据寄存器三种,而没有考虑从数据寄存器之间或是地址寄存器之间的传送。当然,既然是模拟CPU,也没有必要搞得那么复杂。
四、实现
(一)顶层设计
1.全局常量定义
#include <stdio.h>
#include <stdlib.h>
#define Max (16384*2) //内存大小
#define N 16384 //数据段起始下标
2.全局数据结构定义
用一个结构体表示CPU,其中定义一个字符型数组,用来表示内存,其中下标0-18363为代码段,18364-36727表示数据段,总共32K。
由于要求模拟的是16位CPU,因此再定义一个short型数组,模拟各个寄存器。
struct _cpu{
char code_data[Max]; //模拟代码段和数据段
short ax[12]; //模拟寄存器 ,0闲置,1-8通用寄存器,9程序计数器(ip),10指令寄存器(ir),11标志寄存器 (flag)
};
typedef struct _cpu* cpuPtr;
3.函数声明
cpuPtr create_cpu(void);
void process(cpuPtr cpu);
void transfer(cpuPtr cpu,short ir[]);
void calculate(cpuPtr cpu,short ir[]);
void logic(cpuPtr cpu,short ir[]);
void comparation(cpuPtr cpu,short ir[]);
void skip(cpuPtr cpu,short ir[]);
void ioput(cpuPtr cpu,short ir[]);
void output(cpuPtr cpu);
void end_output(cpuPtr cpu);
short bin(cpuPtr cpu,int start);
void bin_to_char(cpuPtr cpu,int start,short number);
(二)函数定义及调用关系
main()
函数中,首先调用create_cpu()
函数创建一个结构体指针cpu
,然后将其作为参数传入process()
函数,完成程序任务。
int main(void)
{
cpuPtr cpu=create_cpu();
process(cpu);
return 0;
}
在create_cpu()
函数中,还要完成指令加载任务:
cpuPtr create_cpu(void)
{
char ch;
int i,j;
int temp;
FILE *fPtr = fopen("dict.txt","r");
cpuPtr new_cpu = NULL;
new_cpu = (cpuPtr)malloc(sizeof(struct _cpu));
for(i=0;i<Max;i++)
{
new_cpu->code_data[i] = 0;
}
for(i=0;i<12;i++)
{
new_cpu->ax[i] = 0;
}
for(j=0;!feof(fPtr);)
{
for(i=0;i<8;i++) //因为代码段code为char型数组
{ //所以一条指令需要占用四个数组空间
ch = fgetc(fPtr); //每次读取8位二进制值并将其转换为char存入数组
if(ch=='\n')
{ //如果读取到换行符,则将其丢弃重新读取
i--;
continue;
}
temp = temp*2 + ch -'0'; //每次从文件中读到的其实是'1'或'0'对应的ASCII码,要将其转换为数值类型
}
new_cpu->code_data[j++] = temp; //写入代码段
temp = 0;
}
new_cpu->code_data[j-1] = 0; //最后一次在代码段写入了EOF(即-1),所以要重新将其改写为默认值0
return new_cpu;
}
在process()
函数中,完成取指令、分析指令、执行指令、输出等任务:
void process(cpuPtr cpu)
{
short ir[4] = {0}; //拆解指令,分别为指令类型,操作位1,操作位2,立即数
/*循环,不断取指令分析执行,直到遇到停机指令*/
do{
/*代码段中每条指令占四个字节,取出前两个字节并拼接写入ir(指令寄存器)*/
cpu->ax[10] = cpu->code_data[(cpu->ax[9])++]&0xff;
cpu->ax[10] = ((cpu->ax[10]<<8)&0xff00)+(cpu->code_data[(cpu->ax[9])++]&0xff);
/*取出后两个字节并拼接,即为立即数,赋值给 ir[3]*/
ir[3] = cpu->code_data[cpu->ax[9]++]&0xff;
ir[3] = ((ir[3]<<8)&0xff00)+(cpu->code_data[cpu->ax[9]++]&0xff);
/*指令寄存器储存16位指令,前8位对应操作类型,通过位运算取出赋值给 ir[0]*/
ir[0] = cpu->ax[10]>>8;
/*指令寄存器9-12位、13-16位对应操作位1和操作位2,位运算取出赋值给 ir[1]、ir[2]*/
ir[1] = ((cpu->ax[10]<<8)>>12)&0xf;
ir[2] = ((cpu->ax[10]<<12)>>12)&0xf;
/*分析指令操作类型,调用对应函数*/
switch(ir[0])
{
case 1: transfer(cpu,ir);break; //数据传送指令
case 2:case 3:case 4:case 5: calculate(cpu,ir);break; //算数运算指令
case 6:case 7:case 8: logic(cpu,ir);break; //逻辑运算指令
case 9: comparation(cpu,ir);break; //比较运算指令
case 10: skip(cpu,ir);break; //跳转指令
case 11:case 12: ioput(cpu,ir);break; //输入输出指令
}
/*每条指令执行完输出各寄存器状态*/
output(cpu);
}while(ir[0]);//ir[0]为0则为停机指令,跳出循环
/*最后输出代码段和数据段信息*/
end_output(cpu);
}
(三)完整代码
该代码为一种可能的实现思路,已经在OJ上提交并且全部AC,仅供大家参考、交流。如有后来者(或许有2022的朋友?)参考此代码,切记不要直接复制粘贴提交!就算是对变量名或函数名称等进行修改,查重系统也是会查到的。还是希望大家只是把这篇文章当做参考,以此启发自己,独立地完成大作业。不要妄自菲薄,不要认定做不出来,你一定能想出自己的思路,尽管它可能是复杂的,尽管它可能不是最好的,但它是你自己的,这一点最重要。
#include <stdio.h>
#include <stdlib.h>
#define Max (16384*2)
#define N 16384
struct _cpu{
char code_data[Max]; //模拟代码段和数据段
short ax[12]; //模拟寄存器 ,0闲置,1-8通用寄存器,9程序计数器(ip),10指令寄存器(ir),11标志寄存器 (flag)
};
typedef struct _cpu* cpuPtr;
cpuPtr create_cpu(void);
void process(cpuPtr cpu);
void transfer(cpuPtr cpu,short ir[]);
void calculate(cpuPtr cpu,short ir[]);
void logic(cpuPtr cpu,short ir[]);
void comparation(cpuPtr cpu,short ir[]);
void skip(cpuPtr cpu,short ir[]);
void ioput(cpuPtr cpu,short ir[]);
void output(cpuPtr cpu);
void end_output(cpuPtr cpu);
short bin(cpuPtr cpu,int start);
void bin_to_char(cpuPtr cpu,int start,short number);
int main(void)
{
cpuPtr cpu=create_cpu();
process(cpu);
}
cpuPtr create_cpu(void)
{
char ch;
int i,j;
int temp;
FILE *fPtr = fopen("dict.txt","r");
cpuPtr new_cpu = NULL;
new_cpu = (cpuPtr)malloc(sizeof(struct _cpu));
for(i=0;i<Max;i++)
{
new_cpu->code_data[i] = 0;
}
for(i=0;i<12;i++)
{
new_cpu->ax[i] = 0;
}
for(j=0;!feof(fPtr);)
{
for(i=0;i<8;i++) //因为代码段code为char型数组
{ //所以一条指令需要占用四个数组空间
ch = fgetc(fPtr); //每次读取8位二进制值并将其转换为char存入数组
if(ch=='\n')
{ //如果读取到换行符,则将其丢弃重新读取
i--;
continue;
}
temp = temp*2 + ch -'0'; //每次从文件中读到的其实是'1'或'0'对应的ASCII码,要将其转换为数值类型
}
new_cpu->code_data[j++] = temp; //写入代码段
temp = 0;
}
new_cpu->code_data[j-1] = 0; //最后一次在代码段写入了EOF(即-1),所以要重新将其改写为默认值0
return new_cpu;
}
void process(cpuPtr cpu)
{
short ir[4] = {0}; //拆解指令,分别为指令类型,操作位1,操作位2,立即数
/*循环,不断取指令分析执行,直到遇到停机指令*/
do{
/*代码段中每条指令占四个字节,取出前两个字节并拼接写入ir(指令寄存器)*/
cpu->ax[10] = cpu->code_data[(cpu->ax[9])++]&0xff;
cpu->ax[10] = ((cpu->ax[10]<<8)&0xff00)+(cpu->code_data[(cpu->ax[9])++]&0xff);
/*取出后两个字节并拼接,即为立即数,赋值给 ir[3]*/
ir[3] = cpu->code_data[cpu->ax[9]++]&0xff;
ir[3] = ((ir[3]<<8)&0xff00)+(cpu->code_data[cpu->ax[9]++]&0xff);
/*指令寄存器储存16位指令,前8位对应操作类型,通过位运算取出赋值给 ir[0]*/
ir[0] = cpu->ax[10]>>8;
/*指令寄存器9-12位、13-16位对应操作位1和操作位2,位运算取出赋值给 ir[1]、ir[2]*/
ir[1] = ((cpu->ax[10]<<8)>>12)&0xf;
ir[2] = ((cpu->ax[10]<<12)>>12)&0xf;
/*分析指令操作类型,调用对应函数*/
switch(ir[0])
{
case 1: transfer(cpu,ir);break; //数据传送指令
case 2:case 3:case 4:case 5: calculate(cpu,ir);break; //算数运算指令
case 6:case 7:case 8: logic(cpu,ir);break; //逻辑运算指令
case 9: comparation(cpu,ir);break; //比较运算指令
case 10: skip(cpu,ir);break; //跳转指令
case 11:case 12: ioput(cpu,ir);break; //输入输出指令
}
/*每条指令执行完输出各寄存器状态*/
output(cpu);
}while(ir[0]);//ir[0]为0则为停机指令,跳出循环
/*最后输出代码段和数据段信息*/
end_output(cpu);
}
void output(cpuPtr cpu)
{
printf("ip = %d\n",cpu->ax[9]);
printf("flag = %d\n",cpu->ax[11]);
printf("ir = %d\n",cpu->ax[10]);
printf("ax1 = %d ax2 = %d ax3 = %d ax4 = %d\n",cpu->ax[1],cpu->ax[2],cpu->ax[3],cpu->ax[4]);
printf("ax5 = %d ax6 = %d ax7 = %d ax8 = %d\n",cpu->ax[5],cpu->ax[6],cpu->ax[7],cpu->ax[8]);
}
void end_output(cpuPtr cpu)
{
int temp;
int i,j,k=0;
printf("\ncodeSegment :\n");
for(i=0;i<16;i++) //共输出16行
{
for(j=0;j<7;j++) //每行前7个数后要跟一个空格
{
/*代码段中每条指-令存为4个char,通过位运算将其转换为32位整数并输出*/
temp = (cpu->code_data[k++]<<24)&0xff000000;
temp += (cpu->code_data[k++]<<16)&0xff0000;
temp += (cpu->code_data[k++]<<8)&0xff00;
temp += cpu->code_data[k++]&0xff;
printf("%d ",temp);
}
//每行最后一个数后要跟一个换行符
temp = (cpu->code_data[k++]<<24)&0xff000000;
temp += (cpu->code_data[k++]<<16)&0xff0000;
temp += (cpu->code_data[k++]<<8)&0xff00;
temp += cpu->code_data[k++]&0xff;
printf("%d\n",temp);
}
k=N;
printf("\ndataSegment :\n");
for(i=0;i<16;i++) //共输出16行
{
for(j=0;j<15;j++) //每行前15个数后要跟一个空格
{
printf("%d ",bin(cpu,k)); //调用bin()将数据段中相邻两个char转换为一个整数(short)输出
k += 2;
}
//每行最后一个数后要跟一个换行符
printf("%d\n",bin(cpu,k));
k += 2;
}
}
short bin(cpuPtr cpu,int start)
{
return (cpu->code_data[start]&0xff)*256+(cpu->code_data[start+1]&0xff); //通过位运算将两个char合并成一个整数(short)
}
/*将一个整数(short)拆分为两个char并写入数据段*/
void bin_to_char(cpuPtr cpu,int start,short number)
{
cpu->code_data[start++] = (char)(number>>8); //强制类型转换,short强制转换为char是低位截断
cpu->code_data[start] = (char)(number);
}
/*数据传送指令,其实就是赋值运算*/
void transfer(cpuPtr cpu,short ir[])
{
if(!ir[2])
cpu->ax[ir[1]] = ir[3]; //把立即数赋值给数据寄存器
else if(ir[2]<=4)
//把数据寄存器中的数赋值给地址寄存器中地址指向的位置,即写入数据段
bin_to_char(cpu,cpu->ax[ir[1]],cpu->ax[ir[2]]);
else
//把地址寄存器中地址指向的位置中存放的数赋值给数据寄存器
cpu->ax[ir[1]] = bin(cpu,cpu->ax[ir[2]]);
}
/*代数运算指令,加减乘除*/
void calculate(cpuPtr cpu,short ir[])
{
if(ir[2]){
//操作位1 (一定是数据寄存器)中的数与操作位2(一定是地址寄存器)指向的数进行运算
switch(ir[0])
{
case 2:cpu->ax[ir[1]] += bin(cpu,cpu->ax[ir[2]]);break; //加法
case 3:cpu->ax[ir[1]] -= bin(cpu,cpu->ax[ir[2]]);break; //减法
case 4:cpu->ax[ir[1]] *= bin(cpu,cpu->ax[ir[2]]);break; //乘法
case 5:cpu->ax[ir[1]] /= bin(cpu,cpu->ax[ir[2]]);break; //除法
}
}else{
//操作位1对应的寄存器(一定是数据寄存器)中的数与立即数进行运算
switch(ir[0])
{
case 2:cpu->ax[ir[1]] += ir[3];break; //加法
case 3:cpu->ax[ir[1]] -= ir[3];break; //减法
case 4:cpu->ax[ir[1]] *= ir[3];break; //乘法
case 5:cpu->ax[ir[1]] /= ir[3];break; //除法
}
}
}
/*逻辑运算,与或非*/
void logic(cpuPtr cpu,short ir[])
{
if(!ir[2]) //如果操作位2是0,则操作位1(一定是数据寄存器)与立即数运算
{
switch(ir[0])
{
case 6: //逻辑与
cpu->ax[ir[1]] = cpu->ax[ir[1]]&&ir[3];break;
case 7: //逻辑或
cpu->ax[ir[1]] = cpu->ax[ir[1]]||ir[3];break;
case 8: //逻辑非
cpu->ax[ir[1]] = !cpu->ax[ir[1]];break;
}
}
else //操作位2不为0,则操作位1(一定是数据寄存器)中的数与操作位2(一定是地址寄存器)指向的数运算
{
switch(ir[0])
{
case 6: //逻辑与
cpu->ax[ir[1]] = cpu->ax[ir[1]]&&bin(cpu,cpu->ax[ir[2]]);break;
case 7: //逻辑或
cpu->ax[ir[1]] = cpu->ax[ir[1]]||bin(cpu,cpu->ax[ir[2]]);break;
case 8: //逻辑非
bin_to_char(cpu,cpu->ax[ir[2]],!bin(cpu,cpu->ax[ir[2]]));break;
}
}
}
/*比较运算*/
void comparation(cpuPtr cpu,short ir[])
{
if(!ir[2]){
//操作位2为0,则操作位1(一定是数据寄存器)中的数与立即数比较
if(cpu->ax[ir[1]]>ir[3])
cpu->ax[11] = 1;
else if(cpu->ax[ir[1]]==ir[3])
cpu->ax[11] = 0;
else
cpu->ax[11] = -1;
}else{
//操作位2不为0,则操作位1(一定是数据寄存器)中的数与操作位2(一定是地址寄存器)指向的数比较
if(cpu->ax[ir[1]]>bin(cpu,cpu->ax[ir[2]]))
cpu->ax[11] = 1;
else if(cpu->ax[ir[1]]==bin(cpu,cpu->ax[ir[2]]))
cpu->ax[11] = 0;
else
cpu->ax[11] = -1;
}
}
/*跳转指令*/
void skip(cpuPtr cpu,short ir[])
{
switch(ir[2])
{
case 0:cpu->ax[9] += ir[3]-4;break; //操作位2为0,强制跳转,更新程序计数器ip
case 1: //操作位2为1,如果标志寄存器flag为0才跳转
if(cpu->ax[11]==0){
cpu->ax[9] += ir[3]-4;
}break;
case 2: //操作位2为2,如果标志寄存器flag为1才跳转
if(cpu->ax[11]==1){
cpu->ax[9] += ir[3]-4;
}break;
case 3: //操作位2为3,如果标志寄存器flag为-1才跳转
if(cpu->ax[11]==-1){
cpu->ax[9] += ir[3]-4;
}break;
}
}
/*输入输出指令*/
void ioput(cpuPtr cpu,short ir[])
{
if(ir[0]==11) //task为11,对应输入指令
{
printf("in:\n");
scanf("%hd",&cpu->ax[ir[1]]);
}
if(ir[0]==12) //task为12,对应输出指令
{
printf("out: ");
printf("%d\n",cpu->ax[ir[1]]);
}
}
五、后记
写到这里,算上代码一万六千字了。期末季的大晚上不睡觉不肝ddl跑来写文章也是服了我自己。不过确实挺有成就感的。
作为一个药学专业的外校学生,我这学期才开始学习C语言,北邮的计导大作业对我来说也确实太顶了。记得刚看到课题描述时我花了一两个星期来理解课题的意思、了解冯诺依曼体系;记得我数次通宵修改代码,只为了用不同的思路来完成代码实现课题;记得我花费了一个周末两天的时间,为了debug几乎想破了脑袋,看着OJ上40%,0%,0%,0%的数据一点一点变成100%,100%,100%,100%。这个过程着实艰难,全靠自己摸索的我差点自闭,然而当最后我看到四个绿色的100%时,那种成就感与满足感太爽了。
感谢某鱼,感谢北邮,让我学到了这么多东西!