以下是作业要求:
作业要求
概述
- 模拟一个简易的冯诺依曼式计算机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)。
- 分析指令:是指对指令寄存器中的指令进行解码,分析出指令的操作码,所需操作数的存放位置等信息等。
- 执行指令:完成相关计算并将结果写到相应位置。
初步计划开发两个版本:单核版本和双核版本。
单核版本:为必须完成的内容,程序的正确性可以通过OJ验证。
双核版本:或者叫多线程版本,为选做内容。如果我们这学期能够回到学校上课,可以现场验收。所以这块内容为暂定。
输入方式
以文件的方式输入,该文件为一个以停机指令为结尾的指令序列。如:
00001011000100000000000000000000
00000001010100000100000000000000
00000001010100010000000000000000
00001011000100000000000000000000
00000010000101010000000000000000
00001100000100000000000000000000
00000000000000000000000000000000
输出方式
- 每执行一条指令后都要输出各寄存器状态,格式见样例
- 当执行到输入指令时在用户输入前要输出:
in :\n
- 当执行到输出时输出前要先输出:
out :
- 输出指令结束后要输出一个换行符。
- 所有指令执行完后要输出当前内存内容
- 先输出代码段:每四个字节一输出(也就是每条指令当成一个整数输出),每行输出8条指令,共输出16行
- 然后输出数据段:每两个字节当成一个整数输出,每行输出16个整数,共输出16行。
- 具体格式见样例。
输出样例:
- 输入指令:
in:
20
- 输出指令:
out:30
其中20为用户输入的数值,30为需要输出的数值
-
寄存器状态
ip = 44
flag = -1
ir = 20316160
ax1 = 4 ax2 = 6 ax3 = 3 ax4 = 0
ax5 = 16384 ax6 = 16386 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
说明
- 指令寄存器(ir,Instruction Register)为16位,所以只保存我们的32位指令的前16位,后16位的立即数部分不保存。
- 立即数部分为补码。
- 数据传送指令只有三种情况,即①把立即数传送给寄存器1到8;②把寄存器1到4中的数传送到寄存器5到8所指向的内存单元;③把寄存器5到8所指向的内存单元中的数传送到寄存器1到4中。
- 碰到输入指令时要先输出in:回车。
- 碰到输出指令时要先输出out: (注意,冒号后有一个空格),然后输出需要输出的内容,最后再输出一个换行符。
- 输出codeSegment :和dataSegment :前先输出一个换行符。
- dict.dic为保存指令序列的文件,该文件为文本文件。格式为每条指令占一行,每行末尾都有换行符。
- 给的dict.dic样例中除了有指令序列外还有相应的解释,output.txt为模拟器基于样例指令序列的实际运行结果(输入的整数为5)。
指令集示例
指令 | 说明 |
---|---|
00000000 00000000 0000000000000000 | 停止程序执行。 |
00000001 00010000 0000000000000000 | 将一个立即数(绿色部分)传送至寄存器1 |
00000001 00010101 0000000000000000 | 将寄存器5(5、6、7、8号寄存器为地址寄存器)中地址所指向的内存单元(2个字节)的内容传送至寄存器1 |
00000001 01010001 0000000000000000 | 将寄存器1的内容传送至寄存器5中地址所指向的内存单元(2个字节)5、6、7、8号寄存器为地址寄存器)。 |
00000010 00010000 0000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)相加,结果保存至寄存器1 |
00000010 00010101 0000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数相加,结果保存至寄存器1 |
00000011 00010000 0000000000000000 | 将寄存器1内的数减去一个立即数(绿色部分),结果保存至寄存器1 |
00000011 00010101 0000000000000000 | 将寄存器1内的数减去寄存器5中地址所指向的内存单元(2个字节)里存的数,结果保存至寄存器1 |
00000100 00010000 0000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)相乘,结果保存至寄存器1 |
00000100 00010101 0000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数相乘,结果保存至寄存器1 |
00000101 00010000 0000000000000000 | 将寄存器1内的数除以(C语言的整数除法)一个立即数(绿色部分),结果保存至寄存器1 |
00000101 00010101 0000000000000000 | 将寄存器1内的数除以(C语言的整数除法)寄存器5中地址所指向的内存单元(2个字节)里存的数,结果保存至寄存器1 |
00000110 00010000 0000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)做逻辑与,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
00000110 00010101 0000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数做逻辑与,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
00000111 00010000 0000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)做逻辑或,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
00000111 00010101 0000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数做逻辑或,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
00001000 00010000 0000000000000000 | 将寄存器1内的数做逻辑非,结果保存至寄存器1(如果结果为真则保存1,否则保存0) |
00001000 00000101 0000000000000000 | 将寄存器5中地址所指向的内存单元(2个字节)里存的数做逻辑非,结果仍保存至寄存器5中地址所指向的内存单元(如果结果为真则保存1,否则保存0) |
00001001 00010000 0000000000000000 | 将寄存器1内的数与一个立即数(绿色部分)比较,如两数相等,则标志寄存器被修置为0,如寄存器1大,则标志寄存器被置为1,如寄存器1小,则标志寄存器被置为-1。 |
00001001 00010101 0000000000000000 | 将寄存器1内的数与寄存器5中地址所指向的内存单元(2个字节)里存的数比较,如两数相等,则标志寄存器被置为0,如寄存器1大,则标志寄存器被置为1,如寄存器1小,则标志寄存器被置为-1。 |
00001010 00000000 0000000000000000 | 无条件跳转指令,转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
00001010 00000001 0000000000000000 | 如果标志寄存器内的值为0则转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
00001010 00000010 0000000000000000 | 如果标志寄存器内的值为1则转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
00001010 00000011 0000000000000000 | 如果标志寄存器内的值为-1则转移至程序计数器加一个立即数(绿色部分)处执行。也就是说要修改程序计数器。 |
00001011 00010000 0000000000000000 | 从输入端口读入一个整数并保存在寄存器1中。也就是从键盘读一个整数到寄存器1中。 |
00001100 00010000 0000000000000000 | 将寄存器1中的数输出到输出端口。也就是将寄存器1中的数以整数的形式输出到显示器上,同时输出一个换行符。 |
我的实验报告
高层数据结构设计
全局常量/变量定义
main.c
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
宏定义 | MEMORY_SIZE |
模拟电脑的内存大小 |
模块常量与变量定义
type/simulator.h
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
枚举 | InstructionType |
指令类型枚举 | ||
结构体 | _Registers,Registers,*pRegisters |
寄存器结构体 | ||
结构体 | _Instruction,Instruction,*pInstruction |
指令结构体 | ||
结构体 | _Simulator,Simulator,*pSimulator |
模拟器类(结构体) |
type/memory_man.h
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
结构体 | _MemoryMan,MemoryMan,*pMemoryMan |
内存管理器类(结构体) |
系统模块划分
系统模块结构图
入口 main.c
功能:程序入口
CPU 模拟器 simulator.c
功能:模拟取指令、分析指令、执行指令
内存控制 memory_man.c
功能:分配、释放、读、写内存
各模块函数说明
type/simulator.h
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
函数 | _instruction_type_string |
指令类型枚举字符串形式(用于调试) | type: 类型: InstructionType 指令类型 |
返回值类型: string 指令类型枚举字符串形式 |
函数 | _simulator_get_this |
获取当前模拟器对象 | 返回值类型: pSimulator 模拟器引用 |
|
函数 | select_simulator |
选取模拟器对象 | ins: 类型: pSimulator 所选实例 |
返回值类型: pSimulator 模拟器引用 |
函数 | _simulator_init |
初始化模拟器对象 | name: 类型: char* 名称 memsize: 类型: |
返回值类型: pSimulator 模拟器引用 |
函数 | _simulator_load |
载入指令文件 | file_name: 类型: char* 文件名 |
返回值类型: pSimulator 模拟器引用 |
函数 | _simulator_run |
运行模拟器 | 返回值类型: pSimulator 模拟器引用 |
|
函数 | _simulator_analyze_instruction |
分析一条指令 | 32: 类型: 指令的 位整数表示 |
返回值类型: pInstruction 指令结构体的引用 |
函数 | _simulator_execute |
运算型指令处理(即返回运算结果) | type: 类型: InstructionType 指令的类型 操作数: 类型: 操作数: 类型: |
返回值类型: int16_t 运算结果 |
函数 | _simulator_calculate |
运算型指令处理(即返回运算结果) | type: 类型: InstructionType 指令的类型 操作数: 类型: 操作数: 类型: |
返回值类型: int16_t 运算结果 |
函数 | _simulator_get_register_value_by_id |
通过 id 取寄存器引用 | id: 类型: int8_t 寄存器编号 |
返回值类型: int16_t* 寄存器引用 |
函数 | _simulator_print_registers |
打印寄存器状态 | 返回值类型: pSimulator 模拟器引用 |
|
函数 | _simulator_display |
打印内存状态 | 返回值类型: pSimulator 模拟器引用 |
|
函数 | simulator_free |
释放模拟器和寄存器占用的内存 | 返回值类型: void
|
type/debugger.h
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
函数 | debugf |
调试时打印 | fmt: 类型: char* 要打印的内容 |
返回值类型: void
|
函数 | releasef |
正式发行时打印 | fmt: 类型: char* 要打印的内容 |
返回值类型: void
|
type/base.h
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
函数 | void* |
设置当前对象引用 | obj: 类型: void* 设置为的对象引用 |
返回值类型: void* 当前对象引用 |
type/memory_man.h
类型 | 名称 | 说明 | 参数 | 返回值 |
---|---|---|---|---|
函数 | _memory_man_clean |
清理内存 | 返回值类型: pMemoryMan 内存管理器引用 |
|
函数 | _memory_man_get_this |
获取当前内存管理器引用 | 返回值类型: pMemoryMan 内存管理器引用 |
|
函数 | _memory_man_init |
初始化内存管理器对象 | size: 类型: int64_t 内存空间大小(字节) |
返回值类型: pMemoryMan 内存管理器引用 |
函数 | _memory_man_read_int16 |
读 16 位整数 | offset: 类型: int64_t 偏移量(字节) |
返回值类型: int16_t 读取结果 |
函数 | _memory_man_read_int32 |
读 32 位整数 | offset: 类型: int64_t 偏移量(字节) |
返回值类型: int32_t 读取结果 |
函数 | _memory_man_set_int16 |
写 16 位整数 | data: 类型: int16_t 要写入的数据 offset: 类型: |
返回值类型: pMemoryMan 当前对象引用 |
函数 | _memory_man_set_int32 |
写 32 位整数 | data: 类型: int32_t 要写入的数据 offset: 类型: |
返回值类型: pMemoryMan 当前对象引用 |
函数 | MemoryManConstruct |
内存管理器构造函数 | 返回值类型: pMemoryMan 当前对象引用 |
|
函数 | select_memory_man |
选取内存管理器 | ins: 类型: pMemoryMan 要选取的对象引用 |
返回值类型: pMemoryMan 当前对象引用 |
函数调用图示及说明
-
主程序通过
SimulatorConstruct
构造函数创建模拟器对象,构造函数中又调用MemoryManConstruct
构造函数创建内存管理器对象。 -
调用模拟器的初始化和文件读取,载入指令。
-
调用模拟器的
Run
方法,分析和执行指令。 -
最后释放资源退出。
高层算法设计
程序执行流程如图:
内存管理
内存使用 void *
进行模拟。当进行读写时,转换为 int8_t *
以便以一个字节为单位进行处理。
指令读取
读取文件,每读到一个有效字符之后,转换为二进制,通过 <<
和 +
追加到一个整数上,当 32 个字符读取完之后,一条指令就读好了。
指令储存
通过一个计数变量offset
记录字节为单位的偏移量。读完一个指令,调用内存管理器对象的 writeInt32
方法将指令以 32 位整数形式,写入代码段的偏移量之所指。
指令分析
通过一个结构体变量保存每一条指令的具体信息。
利用 <<
和 >>
的不可逆性,实现提取指令中的所需连续位图:
// 取出 立即数 bit[0-15]
inst->imm = (instRaw << 16) >> 16;
// 取出 源寄存器编号和目标寄存器编号 bit[16-19] bit[20-23]
inst->source = (instRaw << 12) >> 28;
inst->target = (instRaw << 8) >> 28;
// 取出 指令类型编号 bit[24-31]
inst->type = instRaw >> 24;
指令执行
指令被分为五个大类:
- 数据传送。
- 数值计算。
- 跳转。
- 输入。
- 输出。
执行完毕之后指令指针寄存器自增 4 个字节,或者跳转到指定字节。同时释放存储该指令使用的内存。
代码
考虑到大家作业大都还没做完,我就不放出来了,否则可能要被骂。需要的可以找我要哦。
对了,这个可以给大家,是我用来生成代码文档的,毕竟手动写表格好累……
import glob
import os
import sys
outtype = ["function"]
titleprefix = "#### "
def getDoc(result):
if not result['type'] in outtype:
return ""
s = ""
if result['type'] == "none":
return s
if result['type'] == "struct":
s = s + "|结构体|"
s = s + "`" + result['normal'][0] + "`|"
s = s + result['normal'][1] + "|"
s = s + "||"
if result['type'] == "enum":
s = s + "|枚举|"
s = s + "`" + result['normal'][0] + "`|"
s = s + result['normal'][1] + "|"
s = s + "||"
if result['type'] == "define":
s = s + "|宏定义|"
s = s + "`" + result['normal'][0] + "`|"
s = s + result['normal'][1] + "|"
s = s + "||"
if result['type'] == "function":
s = s + "|函数|"
s = s + "`" + result['func'][0] + "`|"
s = s + result['func'][1] + "|"
for param in result['param']:
s = s + \
("**{1}**: 类型: `{0}` <br> {2}<br><br>").format(
param[0], param[1], param[2])
s = s + "|"
s = s + \
"返回值类型: `{}` <br>{}<br><br>".format(
result['ret'][0], result['ret'][1]) + "|"
return s+"\n"
cwd = os.getcwd()
print('---------------------------------')
print(" 文档生成器 - by pluveto")
print(" 工作目录:" + cwd)
print('---------------------------------')
files = glob.glob(cwd + "/**/*.*", recursive=True)
out = open("document.md", "w")
print("发现文件:")
for fn in files:
print("\t." + fn[len(cwd):])
for fn in files:
flag = False
fd = open(fn, "r")
docType = "none"
func = []
normal = []
param = []
ret = []
title = (titleprefix + "{0}\n\n".format(fn[len(cwd)+1:]))
lineNum = 1
head = ('|类型|名称|说明|参数|返回值|\n|---|---|---|---|---|\n')
alldoc = ""
while True:
line = fd.readline()
if not line:
break
lineNum = lineNum + 1
if line.startswith("/**"):
flag = True
continue
elif flag and line.lstrip().startswith("*/"):
result = {
"type": docType,
"func": func,
"normal": normal,
"param": param,
"ret": ret
}
doc = getDoc(result)
if (len(doc)):
alldoc = alldoc + (doc)
docType = "none"
func = []
normal = []
param = []
ret = []
flag = False
continue
if not flag:
continue
try:
line = line.strip(' \t\n\r*')
if line.startswith("@struct"):
docType = "struct"
line = line.split(" ", 2)
normal = ([line[1], line[2]])
continue
if line.startswith("@enum"):
docType = "enum"
line = line.split(" ", 2)
normal = ([line[1], line[2]])
continue
if line.startswith("@define"):
docType = "define"
line = line.split(" ", 2)
normal = ([line[1], line[2]])
continue
if line.startswith("@function"):
docType = "function"
line = line.split(" ", 2)
func.append(line[1])
func.append(line[2])
continue
if line.startswith("@param"):
line = line.rsplit(" ", 3)
param.append([line[1], line[2], line[3]])
continue
if line.startswith("@return"):
line = line.split(" ", 3)
if line[1] == "void":
ret = ([line[1], ""])
else:
ret = ([line[1], line[2]])
continue
pass
except:
print("Unexpected error:", sys.exc_info()[
0], "file: ", fn, " line: ", lineNum)
if (len(alldoc)):
out.write(title + head + alldoc + "\n")
fd.close()
out.close()
print("处理完毕 ( ゚д゚)つBye")
您好,可以请教一下为什么用宏定义MEMORY_SIZE(CPU内存大小)呢?后续您是怎么用的呢?
MEMORY_SIZE 决定了 malloc 申请的内存大小。之后使用指针修改和读取指定 offset 位置的内存即可。
谢谢!
您好,不知道能不能参考一下您的代码呢?