北邮2021计导大作业丨C语言实现冯诺依曼式计算机CPU模拟器(一):单核版

一、前言

多核版见我的另一篇文章北邮2021计导大作业丨C语言实现冯诺依曼式计算机CPU模拟器(二):多核版

终于下决心来写这篇文章。对我来说北邮这个大作业实在是太顶了,做的过程中我也确实学到了很多东西(与之相比复旦C语言课程的大作业就实在是太水了)。

该代码为一种可能的实现思路,已经在OJ上提交并且全部AC,仅供大家参考、交流。如有后来者(或许有2022的朋友?)参考此代码,切记不要直接复制粘贴提交!就算是对变量名或函数名称等进行修改,查重系统也是会查到的。还是希望大家只是把这篇文章当做参考,以此启发自己,独立地完成大作业。不要妄自菲薄,不要认定做不出来,你一定能想出自己的思路,尽管它可能是复杂的,尽管它可能不是最好的,但它是你自己的,这一点最重要。

二、课题要求

(一)任务概述

  1. 模拟一个简易的冯诺依曼式计算机CPU的工作。
  2. 该CPU字长为16位,共11个寄存器,其中3个系统寄存器,分别为程序计数器,指令寄存器,标志寄存器;8个通用寄存器,即寄存器1、2、3、4(数据寄存器),寄存器5、6、7、8(地址寄存器)。该CPU至多支持32K内存。内存分两部分,一部分为代码段,从地址0开始。另一部分为数据段,从地址16384开始。
  3. 该CPU所支持的指令集见word文档。每条指令固定由32位(由左至右依次编号为0到31)二进制数组成,其中第0到7位为操作码,代表CPU要执行哪种操作;第8到15位为操作对象,如寄存器,内存地址等;第16到31位为立即数。该CPU有一个输入端口和一个输出端口。输入端口的数据由标准输入设备(键盘)输入,输出端口的数据输出到标准输出设备(显示器)上。

(二)程序的大致步骤

  1. 程序开始时要从指定文件中读入一段用给定指令集写的程序至内存(从地址0开始顺序保存),程序计数器初始值也为0。此功能为指令加载。
  2. 指令加载完成后程序就开始不断重复取指令、分析指令和执行指令的过程。程序每执行一条指令就要输出CPU当前的状态,如各寄存器的值等。当执行到停机指令时,程序按要求输出后就结束了。
  3. 取指令:要求读取程序计数器PC内的指令地址,根据这个地址将指令从内存中读入,并保存在指令寄存器中,同时程序计数器内容加4,指向下一个条指令。(因为我们所有的指令长度固定为4个字节,所以加4)。
  4. 分析指令:是指对指令寄存器中的指令进行解码,分析出指令的操作码,所需操作数的存放位置等信息等。
  5. 执行指令:完成相关计算并将结果写到相应位置。

(三)输入输出方式

1. 输入方式

以文件的方式输入,该文件为一个以停机指令为结尾的指令序列。如:

00001011000100000000000000000000
00000001010100000100000000000000
00000001010100010000000000000000
00001011000100000000000000000000
00000010000101010000000000000000
00001100000100000000000000000000
00000000000000000000000000000000

2. 输出方式

  1. 每执行一条指令后都要输出各寄存器状态,格式见样例

  2. 当执行到输入指令时在用户输入前要输出:in :\n

  3. 当执行到输出时输出前要先输出:out :

  4. 输出指令结束后要输出一个换行符。

  5. 所有指令执行完后要输出当前内存内容 先输出代码段:

    1. 每四个字节一输出(也就是每条指令当成一个整数输出),每行输出8条指令,共输出16行
    2. 然后输出数据段: 每两个字节当成一个整数输出,每行输出16个整数,共
  6. 输出16行。 具体格式见样例。

3.输入输出样例:

  1. 输入指令:(注意in:后的换行符)
in:
20
  1. 输出指令:
out:30
  1. 寄存器状态:
ip = 56
flag = 1
ir = 3104
ax1 = 1 ax2 = 2 ax3 = 3 ax4 = 2
ax5 = 0 ax6 = 16396 ax7 = 0 ax8 = 0

  1. 代码段内存
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

  1. 数据段内存
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. 1-8位:指明任务类型
  2. 9-12位:操作对象1
  3. 13-16位:操作对象2
  4. 17-32位:立即数

以指令00000001001001100000000000000000为例:

  1. 1-8位:00000001,对应数据传送指令
  2. 9-12位:0010,,对应寄存器2(数据寄存器)
  3. 13-16位:0110,对应寄存器6(地址寄存器)
  4. 17-32位:0000000000000000,转换为十进制即为0。因为该条指令的两个操作对象都是寄存器,不涉及立即数的运算,因此为0。

2.指令种类及作用

分析单核版指令集,可以发现指令总体可分为六大类(停机指令此处不予讨论):

  • 数据传送指令
  • 算术运算指令
  • 逻辑运算指令
  • 比较指令
  • 跳转指令
  • 输入输出指令

下面具体分析这六类指令。

(1)数据传送指令

根据指令集的描述,数据传送指令的前1-8为均为00000001,即十进制的1。根据操作对象1(9-12位)与操作对象2(13-16位)的不同,可以分为三大类:

  1. 操作对象2为0000,这表明要将立即数传入操作对象1指代的寄存器内。
  2. 操作对象1转换为十进制为1-4(数据寄存器),操作对象2转换为十进制为5-8(地址寄存器),这表明要将操作对象2(5、6、7、8号寄存器)中地址所指向的内存单元(2个字节)的内容传送至操作对象1(1、2、3、4号寄存器)。
  3. 操作对象1转换为十进制为5-8(地址寄存器),操作对象2转换为十进制为1-4(数据寄存器),这表明将操作对象2(1,2,3,4寄存器)的内容传送至操作对象1(5,6,7,8寄存器)中地址所指向的内存单元(2个字节)。

数据传送指令即为赋值运算

(2)算术运算指令

当指令的1-8位转换为10进制为2-5时,对应算术运算指令:

  1. 00000010:十进制的2,加法运算
  2. 00000011:十进制的3,减法运算
  3. 00000100:十进制的4,乘法运算
  4. 00000101:十进制的5,除法运算

根据操作对象1与操作对象2的不同可分为两大类:

  1. 操作对象1为1-4,操作对象2为0,表明对数据寄存器与立即数进行运算,将操作对象1(数据寄存器)内的数与一个立即数运算(加减乘除),结果保存至操作对象1(数据寄存器)。
  2. 操作对象1为1-3,操作对象2为5-8,表明对数据寄存器与地址寄存器进行运算,将操作对象1(数据寄存器)内的数与操作对象2(地址寄存器)中地址所指向的内存单元(2个字节)里存的数运算(加减乘除),结果保存至操作对象1(数据寄存器)。

算术运算指令即为四则运算

(3)逻辑运算指令

当指令的1-8位转换为十进制为6-8时,对应逻辑运算指令:

  1. 00000110:十进制的6,逻辑与
  2. 00000111:十进制的7,逻辑或
  3. 00001000:十进制的8,逻辑非

对于逻辑与、逻辑或运算指令,按操作对象1与操作对象2的不同可分为以下两类:

  1. 操作对象1为1-4,操作对象2为0,表示用操作对象1(数据寄存器)中的数与立即数做逻辑与(逻辑或)运算,将结果保存在操作对象1(数据寄存器)中(如果结果为真保存为1,结果为假保存为0)。
  2. 操作对象1位1-4,操作对象2为5-8,表示用操作对象1(数据寄存器)中的数与操作对象2(地址寄存器)中地址所指向的内存单元(两个字节)中的数做逻辑与(逻辑或)运算,结果保存在操作对象1(数据寄存器)中(如果结果为真则保存为1,结果为假保存为0)。

对于逻辑非运算指令,按操作对象1与操作对象2的不同也可分为两类:

  1. 操作对象1为1-4,操作对象2为0,表明对操作对象1(数据寄存器)中的数做逻辑非,运算结果保存在操作对象1中(若结果为真则保存为1,结果为假保存为0)。
  2. 操作对象1为0,操作对象2为5-8,表明对操作对象2(地址寄存器)中地址所指向的内存单元(两个字节)中的数做逻辑非,运算结果保存至该内存单元(两个字节)(如果结果为真保存为1,结果为假保存为0).

逻辑运算指令即为与或非

(4)比较指令

当指令1-8位为9,对应逻辑运算指令。按操作对象1与操作对象2的不同,可分为两类:

  1. 操作对象1为1-4,操作对象2为0,表示将操作对象1(数据寄存器)内的数与一个立即数比较,如两数相等,则标志寄存器被修置为0,如操作对象1大,则标志寄存器被置为1,如操作对象1小,则标志寄存器被置为-1。
  2. 操作对象1位1-4,操作对象2为5-8,表示将操作对象1(数据寄存器)内的数与操作对象2(地址寄存器)中地址指向的内存单元(两个字节)比较,如两数相等,则标志寄存器被修置为0,如操作对象1大,则标志寄存器被置为1,如操作对象1小,则标志寄存器被置为-1。

比较指令即为比较运算

(5)跳转指令

当指令1-8位为10,对应跳转指令。根据操作位2的不同可分四类:

  1. 操作对象2为0,即0000,无条件跳转指令,转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。
  2. 操作对象2为1,即0001,如果标志寄存器内的值为0则转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。
  3. 操作对象2为2,即0010,如果标志寄存器内的值为1则转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。
  4. 操作,对象2为3,即0011,如果标志寄存器内的值为-1则转移至程序计数器加一个立即数处执行。也就是说要修改程序计数器。

跳转指令用来实现循环、选择、switch、goto语句等。

(6)输入输出指令

当指令1-8位为11或12,对应输入输出指令:

  1. 00001011:十进制为11,对应输入指令。从输入端口读入一个整数并保存在操作对象1(数据寄存器)中。也就是从键盘读一个整数到寄存器1中。
  2. 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%时,那种成就感与满足感太爽了。

感谢某鱼,感谢北邮,让我学到了这么多东西!

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值