Site Overlay

计导作业:冯诺依曼式计算机CPU模拟器

以下是作业要求:

作业要求

概述

  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. 执行指令:完成相关计算并将结果写到相应位置。

初步计划开发两个版本:单核版本和双核版本。

单核版本:为必须完成的内容,程序的正确性可以通过OJ验证。

双核版本:或者叫多线程版本,为选做内容。如果我们这学期能够回到学校上课,可以现场验收。所以这块内容为暂定。

输入方式

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

00001011000100000000000000000000
00000001010100000100000000000000
00000001010100010000000000000000
00001011000100000000000000000000
00000010000101010000000000000000
00001100000100000000000000000000
00000000000000000000000000000000

输出方式

  1. 每执行一条指令后都要输出各寄存器状态,格式见样例
  2. 当执行到输入指令时在用户输入前要输出:in :\n
  3. 当执行到输出时输出前要先输出:out :
  4. 输出指令结束后要输出一个换行符。
  5. 所有指令执行完后要输出当前内存内容
    1. 先输出代码段:每四个字节一输出(也就是每条指令当成一个整数输出),每行输出8条指令,共输出16行
    2. 然后输出数据段:每两个字节当成一个整数输出,每行输出16个整数,共输出16行。
  6. 具体格式见样例。

输出样例:

  • 输入指令:
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

说明

  1. 指令寄存器(ir,Instruction Register)为16位,所以只保存我们的32位指令的前16位,后16位的立即数部分不保存。
  2. 立即数部分为补码。
  3. 数据传送指令只有三种情况,即①把立即数传送给寄存器1到8;②把寄存器1到4中的数传送到寄存器5到8所指向的内存单元;③把寄存器5到8所指向的内存单元中的数传送到寄存器1到4中。
  4. 碰到输入指令时要先输出in:回车。
  5. 碰到输出指令时要先输出out: (注意,冒号后有一个空格),然后输出需要输出的内容,最后再输出一个换行符。
  6. 输出codeSegment :和dataSegment :前先输出一个换行符。
  7. dict.dic为保存指令序列的文件,该文件为文本文件。格式为每条指令占一行,每行末尾都有换行符。
  8. 给的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 内存管理器类(结构体)

系统模块划分

系统模块结构图

image-20200416200254728

入口 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: 类型: int64_t
内存空间大小(字节)

返回值类型: pSimulator
模拟器引用

函数 _simulator_load 载入指令文件 file_name: 类型: char*
文件名

返回值类型: pSimulator
模拟器引用

函数 _simulator_run 运行模拟器 返回值类型: pSimulator
模拟器引用

函数 _simulator_analyze_instruction 分析一条指令 32: 类型: 指令的
位整数表示

返回值类型: pInstruction
指令结构体的引用

函数 _simulator_execute 运算型指令处理(即返回运算结果) type: 类型: InstructionType
指令的类型

操作数: 类型: opr1
1

操作数: 类型: opr2
2

返回值类型: int16_t
运算结果

函数 _simulator_calculate 运算型指令处理(即返回运算结果) type: 类型: InstructionType
指令的类型

操作数: 类型: opr1
1

操作数: 类型: opr2
2

返回值类型: 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: 类型: int64_t
偏移量(字节)

返回值类型: pMemoryMan
当前对象引用

函数 _memory_man_set_int32 写 32 位整数 data: 类型: int32_t
要写入的数据

offset: 类型: int64_t
偏移量(字节)

返回值类型: pMemoryMan
当前对象引用

函数 MemoryManConstruct 内存管理器构造函数 返回值类型: pMemoryMan
当前对象引用

函数 select_memory_man 选取内存管理器 ins: 类型: pMemoryMan
要选取的对象引用

返回值类型: pMemoryMan
当前对象引用

函数调用图示及说明

  1. 主程序通过 SimulatorConstruct 构造函数创建模拟器对象,构造函数中又调用 MemoryManConstruct 构造函数创建内存管理器对象。

  2. 调用模拟器的初始化和文件读取,载入指令。

  3. 调用模拟器的 Run 方法,分析和执行指令。

  4. 最后释放资源退出。

高层算法设计

程序执行流程如图:

Untitled Diagram

内存管理

内存使用 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;

指令执行

指令被分为五个大类:

  1. 数据传送。
  2. 数值计算。
  3. 跳转。
  4. 输入。
  5. 输出。

执行完毕之后指令指针寄存器自增 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")

4 thoughts on “计导作业:冯诺依曼式计算机CPU模拟器

发表评论

电子邮件地址不会被公开。 必填项已用*标注