根据给出的数据移动类指令,填写正确参数,完成指令的运行。
为了完成本关任务,你需要掌握:1.如何理解 TOY 类汇编指令,2.如何理解数据移动类 mov 指令的含义。
mov1 Rx mem
,将主存单元 mem 中的值移入寄存器 Rx 中,这里的 mem 是主存单元地址;mov2 mem Rx
,将寄存器 Rx 中的值移入主存单元 mem 中,这里的 mem 是主存单元地址;mov3 Rx n
,将数字 n 放入寄存器 Rx 中。000 mov3 1 12
:
000
表示的是该指令放置在主存的哪个单元中。000
, 接着 CPU 的控制器根据程序计数器中的值到对应的主存单元中读取该指令放入指令寄存器 iReg 中,然后进行指令译码、执行和写结果这些后续操作。在右侧弹出的操作页面左上部给出了本关需要完成的指令,在右侧给出的主存区间中该指令被标黄显示。点击右侧“启动”按钮,在弹出的页面中根据要完成的指令内容在各个编辑框中填写正确的内容,填写完成后点击“确认”按钮。若填写错误,请根据系统给出的提示信息进行修改;若填写正确,系统将给出该指令执行的完整过程演示,请仔细观察并理解该指令的含义。
开始你的任务吧,祝你成功!
完成指令解析 009 mov2 100 2
pReg:(009)
寄存器:(2) 值:(100)
内存地址:(100)
根据给出的运算类指令,填写正确参数,完成指令的运行。
为了完成本关任务,你需要掌握:1.如何运行汇编运算式指令。
加法指令:add Rx Ry ,寄存器 Rx 中的值加上 Ry 中的值,结果存入 Rx;
减法指令:sub Rx Ry ,寄存器 Rx 中的值减去 Ry 中的值,结果存入 Rx;
乘法指令:mul Rx Ry ,寄存器 Rx 中的值乘以 Ry 中的值,结果存入 Rx;
除法指令:div Rx Ry ,寄存器 Rx 中的值除以 Ry 中的值,结果存入 Rx;
002 add 0 1
:
002
表示的是该指令存放在主存单元地址。002
, 接着 CPU 的控制器根据程序计数器中的值到对应的主存单元中读取该指令放入指令寄存器 iReg 中,然后进行指令译码、执行和写结果这些后续操作。在右侧弹出的操作页面左上部给出了本关需要完成的指令,在右侧给出的主存区间中该指令被标黄显示。点击右侧“启动”按钮,在弹出的页面中根据要完成的指令内容在各个编辑框中填写正确的内容,填写完成后点击“确认”按钮。若填写错误,请根据系统给出的提示信息进行修改;若填写正确,系统将给出该指令执行的完整过程演示,请仔细观察并理解该指令的含义。
开始你的任务吧,祝你成功!
完成指令解析 005 operator 1 2
pReg:(005)
操作数1:(1) 操作数2:(2)
选择:op(+)
根据给出的跳转类指令,填写正确参数,成功运行条件跳转指令和无条件跳转指令。
无条件跳转指令:jmp mem
,程序直接跳转到主存地址 mem 处继续执行,即将程序计数器中的值改写为 mem,这样,下一条要执行的指令就是存放在主存地址 mem 处的指令;
条件跳转指令:jz Rx mem
,程序是否跳转要根据寄存器 Rx 中的值进行判定;若寄存器 Rx 中的值为 0 ,则跳转到主存地址 mem 处执行,否则不跳转,顺序执行下一条指令。
012 jz 4 009
:
012
表示的是该跳转指令存放在主存单元的地址。012
, 接着 CPU 的控制器根据程序计数器中的值到对应的主存单元中读取该指令放入指令寄存器 iReg 中,然后进行指令译码、执行这些后续操作。在右侧弹出的操作页面左上部给出了本关需要完成的指令,在右侧给出的主存区间中该指令被标黄显示。点击右侧“启动”按钮,在弹出的页面中根据要完成的指令内容在各个编辑框中填写正确的内容,填写完成后点击“确认”按钮。若填写错误,请根据系统给出的提示信息进行修改;若填写正确,系统将给出该指令执行的完整过程演示,请仔细观察并理解该指令的含义。
开始你的任务吧,祝你成功!
完成指令解析 003 jmp 003
pReg:(003)
内存地址:(003)
根据给出的输入输出指令,填写正确参数,完成指令的运行。
输入指令:in Rx
,读取键盘输入的整数,放入寄存器 Rx 中;
输出指令:out Rx
,将寄存器 Rx 中的值输出到屏幕。
020 out 1
:
020
表示的是该输出指令存放在主存单元的地址。020
, 接着 CPU 的控制器根据程序计数器中的值到对应的主存单元中读取该指令放入指令寄存器 iReg 中,然后进行指令译码、执行这些后续操作。在右侧弹出的操作页面左上部给出了本关需要完成的指令,在右侧给出的主存区间中该指令被标黄显示。点击右侧“启动”按钮,在弹出的页面中根据要完成的指令内容在各个编辑框中填写正确的内容,填写完成后点击“确认”按钮。若填写错误,请根据系统给出的提示信息进行修改;若填写正确,系统将给出该指令执行的完整过程演示,请仔细观察并理解该指令的含义。
开始你的任务吧,祝你成功!
完成指令解析 008 out 1
pReg:(008)
寄存器:(1)
带输出值:(1)
根据操作提示,运行一个能够执行完整运算功能的程序。
为了完成本关任务,你需要掌握 TOY 指令集中的指令含义。
关于 TOY 指令集中各条指令的含义详见本实训首页介绍,以及第 1 关至第 4 关的相关知识介绍,此处不再重复介绍。
本关使用 TOY 指令集中的指令编写了一段包含 5 条指令的程序代码,可实现加、减、乘、除四则运算功能。你需要为这一段程序代码选择正确的 TOY 指令。具体操作步骤和要求如下:
开始你的任务吧,祝你成功!
+ 1 2
mov3
mov3
add
out
halt
对使用变量模拟的 TOY 计算机的部分硬件装置,进行初始化赋值。
图 1 TOY 计算机结构
如图 1 所示,TOY 计算机的核心硬件装置主要包括主存和 CPU。 TOY 主存用来存储正在执行的 TOY 程序和相关数据, TOY 的主存共包含 1000 个主存单元,主存单元的地址依次为 000 ~ 999,每个主存单元可以存储一条 TOY 指令或一个相关数据。 TOY 的 CPU 是用来执行 TOY 指令的,每次执行一条指令。在 CPU 中,通用寄存器是用来存储临时数据的,如执行完 mov3 1 12
和 mov3 2 13
两条指令后,第 1 号和第 2 号寄存器中的值分别为 12 和 13,这两个数据会在后面的指令中被用到,TOY 计算机中共有 10 个通用寄存器,编号从 0 到 9;指令寄存器中存储了正在被执行的指令,如图 1 中,正在被 CPU 执行的指令是 add 1 2
指令;程序计数器用来存储下一条指令的地址,如图 1 中,程序计数器的值为 003,表示下一条被执行的指令是第 003 号主存单元中的指令。
在右侧编辑区的 Begin-End 区间中,补全函数init()
的代码,该函数使用mem
列表模拟 TOY 计算机 1000 个主存单元,reg
列表模拟 CPU 的 10 个通用寄存器,pReg
变量模拟程序计数器,iReg
变量模拟指令寄存器,请为上述这些变量进行初始化赋值,要求全部赋值为 0 。注意:不要改动 Begin-End 区间之外的代码。
本关将测试上述变量的赋值结果,与编程要求相同则通过测试。
开始你的任务吧,祝你成功!
# 初始化
def init():
########## Begin ##########
mem = [0] * 1000 # 主存,1000个单元
reg = [0] * 10 # 通用寄存器,10个
pReg = 0 # 程序计数器,1个
iReg = 0 # 指令寄存器,1个
########## End ##########
print(mem, reg, pReg, iReg)
将存放在文件中的程序指令加载到 TOY 计算机的主存中。
加载 TOY 程序是指把 TOY 程序从外存载入到 TOY 的主存,从而使 CPU 可以执行程序中的各指令,其实就是把 .toy 文件中的程序指令放入到对应的主存单元(即列表 mem 中的对应位置),如果没有特意说明,就表示从主存地址 000 对应的单元开始连续存放。例如,若将图 1 中 add.toy 文件加载到主存,则 add.toy 文件第 3 行002 add 1 2
中的指令add 1 2
将被放入主存第 002 号单元,也就是将列表 mem 中下标为 2 的元素赋值为add 1 2
。
图 1 TOY 程序示例
【小贴士】严格来说, .toy 文件中指令前的地址是逻辑地址,主存单元的地址是物理地址,一条指令的物理地址不一定等于它的逻辑地址,将逻辑地址转换为物理地址的过程称为地址重定位。本实训中,不同的测试集会给出程序加载到主存时第一条指令存放的物理地址,例如,若程序的第一条指令存放在物理地址 100 对应的单元中,则表明从主存物理地址 100 开始的主存单元连续存放该程序的所有指令,这时程序中一条指令的逻辑地址和其物理地址相差 100。图 2 给出了 add.toy 程序加载到主存物理地址 100 的存放示意图。这时,如果 CPU 执行 add.toy 程序,则必须首先将程序计数器 pReg 的值设为 100,使其能够将程序的第一条指令mov3 1 12
取到指令寄存器 iReg。
图 2 程序装载到主存地址重定位示例图
下面先介绍 Python 读写文件的方法和全局变量的概念。
利用 Python 读写文件的过程一般是先打开文件,然后进行读写,最后关闭文件。下面的程序 1 给出了写文件的示例。
程序 1
txt = open('D:/MyPython/add.toy', 'w') # 打开文件
txt.write('000 mov3 1 12\n') # 写文件
txt.write('001 mov3 2 13\n')
txt.write('002 add 1 2\n')
txt.write('003 out 1\n')
txt.write('004 halt\n')
txt.close() # 关闭文件
该程序首先用 open(file, mode)
函数打开一个文件,file
给出的是文件的路径,通常若程序 1 所在目录为程序执行目录,则当程序 1 和 add.toy 文件存放在同一个目录下时只需要写文件名即可,如程序 2 中所示;mode
表示打开文件的模式,主要有:
所以,程序 1 第 1 行的意思是以写模式打开D:\MyPython\add.toy
文件,若文件不存在则自动创建该文件,若文件已存在则清空之前的内容;然后利用 write
函数往文件中写入 5 条 TOY 指令,write
函数在写文件时不会自动换行,所以要在每条指令最后加上换行符\n
,否则 5 条指令会被写到同一行;最后关闭文件。所以程序执行完毕后,在D:\MyPython
文件夹下会存在add.toy
文件,文件内容就是图 1 中add.toy
的内容。 程序 2 给出的是读文件的示例,程序首先以读模式打开文件,然后读取并打印文件中所有内容,最后关闭文件。
程序 2
# add.toy文件和程序2文件存放在同一目录,且程序2所在目录为执行目录
txt = open('add.toy', 'r')
while True:
line = txt.readline() # 整行读取
if line == '': # 此处是两个单引号,判断是否读到文件尾
break
print(line) # print函数会在输出结束后再打印输出一个换行符
txt.close()
程序 2 的执行结果如下,:
000 mov3 1 12
001 mov3 2 13
002 add 1 2
003 out 1
004 halt
函数 readline
的功能是读取文件中的某一行,首次使用 readline
函数时读取的是文件第 1 行,下次再使用 readline
时会读文件第 2 行,第 i 次调用 readline
读取的就是文件第 i 行。因此,可以利用循环依次读取文件中的每一行。因为在读取之前不知道文件总共有多少行,也就是不知道确切的循环次数,所以程序使用了 while
循环。while
的条件表达式为 True
,即条件永远成立,所以该条件表达式不能使循环结束,使循环结束的是循环体中的 break
语句,执行 break
语句的条件是line==''
,即读出来的这一行是空字符串,也就是什么都没读到,这表示文件已经读到最后,此时可结束循环。 除 readline
之外,还可以使用函数 read()
和 readlines()
读取文件,这两个函数的功能都是读取文件中所有内容,区别在于以何种形式存放读取到的内容,read
函数将文件中的所有内容存放在一个字符串中,而 readlines
将结果存于一个列表,列表中的一个元素对应文件中的一行。例如,add.toy 是程序 1 生成的文件,则在程序 3 中,变量 s
的值为'000 mov3 1 12\n001 mov3 2 13\n002 add 1 2\n003 out 1\n004 halt\n'
,而在程序 4 中,s
的值为['000 mov3 1 12\n', '001 mov3 2 13\n', '002 add 1 2\n', '003 out 1\n', '004 halt\n']
。
程序 3
txt = open('add.toy', 'r')
s = txt.read()
print(s)
txt.close()
程序 3 的执行结果如下,注意输出时换行符'\n'直接执行换行操作:
000 mov3 1 12
001 mov3 2 13
002 add 1 2
003 out 1
004 halt
程序 4
txt = open('add.toy', 'r')
s = txt.readlines()
print(s)
txt.close()
程序 4 的执行结果如下:
['000 mov3 1 12\n', '001 mov3 2 13\n', '002 add 1 2\n', '003 out 1\n', '004 halt\n']
在函数体中可以读取函数外部定义的变量(但一般不建议这么做,一般建议通过参数传递的方式将外部变量的值传递到函数体中),如在程序 5 中,变量 a
是在 test
函数外部定义的变量,而在 test
函数中,可以读取 a
的值,所以该程序执行结束后,会打印 a
的值 1。
程序 5
a = 1
def test():
b = a # 读取外部变量a
print(b)
test() # 打印结果为1
程序 5 的执行结果如下:
1
但是,在函数体中修改外部变量的值并不会对外部变量起作用,如在程序 6 中,a
为 test
函数外部的变量,虽然在函数体中将 a
的值修改为 2,但在函数体外,该修改并不会生效,所以最后的 print 语句打印出的还是 1。
程序 6
a = 1
def test():
a = 2 # 修改外部变量a
test()
print(a) # 打印结果仍为1
程序 6 的执行结果如下:
1
有些时候,希望函数体中对外部变量的修改能够在函数外部生效,此时可使用关键字 global
进行全局变量的声明。例如,在程序 7 中,test
函数首先声明变量 a
为全局变量,然后再对 a
进行修改,此时的修改会在函数外部生效,所以 print
语句打印出的是修改后的结果 2。
程序 7
a = 1
def test():
global a # 声明a为全局变量
a = 2 # 对a的修改会在外部生效
test()
print(a) # 打印结果为2,证明函数体中的修改已生效
程序 7 的执行结果如下:
2
和本实训的第一关一样,我们继续使用mem
列表模拟 TOY 计算机的主存。本关需要补全右侧编辑区loadProgram
函数中 Begin-End 之间的代码,将存放在文件file
中的程序指令放入 TOY 计算机主存mem
中,放入的起始地址为address
,从而实现模拟计算机程序加载的功能。其中,file
文件名、address
地址由测试用例输入,经函数loadProgram
参数传入,你无需编写相关输入代码。
提醒:
1.文件
file
中每行指令从左至右依次由逻辑地址、指令操作码、指令操作数 1、指令操作数 2、组成,各部分之间使用若干数量不等的空格相间隔,将指令加载到主存mem
时,需要将 每行指令前面的逻辑地址去除掉,只加载指令本身;同时,需要将指令操作码左侧、最后 一个操作数右侧可能存在的空格去除掉。2.不要改动 Begin-End 区间之外的代码。
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。测试样例如下所示:
第一个输入的是需要加载的程序所在文件名,第二个输入的是程序第一条指令加载到主存的物理地址。
测试输入:
add.toy
0
预期输出:
['mov3 1 12', 'mov3 2 13', 'add 1 2', 'out 1', 'halt', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
关于平台执行用户程序的说明:(与本地执行不同) 用户点击“评测”按钮,平台会进入用户的版本库代码下载目录
/data/workspace/myshixun
,在此启动 python、java、c,或者其它程序,我们将这个目录称为程序工作目录。因此:(1)若要在命令行加载用户程序
/data/workspace/myshixun/step1/fileop.py
,应进入/data/workspace/myshixun
目录,执行python step1/fileop.py
命令。(2)用户编写代码时应将此程序工作目录视为根目录,例如,若用户程序
fileop.py
放在/data/workspace/myshixun/step1/
文件夹中,执行时需要读取文件夹/data/workspace/myshixun/
下的a.txt
文件,则打开文件的代码为open(“a.txt”, ‘r’)
即可;若a.txt
文件与fileop.py
在同一个文件夹/data/workspace/myshixun/step1/
中,则打开文件的代码应改写为open(“step1/a.txt”, ‘r’)
。
开始你的任务吧,祝你成功!
# 程序加载
def loadProgram(file, mem, address):
########## Begin ##########
txt = open(file, 'r')
s = txt.readlines()
for x in s:
l = len(x)
bg, L = 0, []
while bg <= l - 1:
mdl = ''
flag = False
while x[bg] != ' ':
mdl = mdl + x[bg]
bg += 1
if bg == l - 1:
flag = True
break
while x[bg] == ' ':
if bg == l - 1:
flag = True
break
bg += 1
L.append(mdl)
if flag:
break
# L
L.pop(0)
mem[address] = ' '.join(L)
address = address + 1
########## End ##########
print(mem)
模拟计算机取指令操作,将内存中的指令取到指令寄存器中来,同时程序计数器加一,指向下一条要执行的指令地址。
CPU 的功能是依次执行程序中的各条指令,在执行一条指令时其工作过程如下:
在右侧编辑区函数fetch
中补全 Begin-End 区间的代码,模拟实现 CPU 取指令的操作,根据程序计数器pReg
给出的指令地址,从主存mem
中取出指令放入指令寄存器iReg
,注意,指令取出后,程序计数器pReg
需要自增 1 。
说明:
mem
列表:模拟 TOY 计算机的主存;pReg
变量:模拟程序计数器;iReg
变量:模拟指令寄存器。 平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。 mem
列表模拟的 TOY 计算机主存中存放的程序来自于测试用例输入的.toy
文件,测试程序将调用 2 次函数fetch
,实现 2 次取指令操作,因此程序执行后将输出.toy
文件中的前 2 条指令。 测试样例如下所示:
测试输入:add.toy
预期输出:
mov3 1 12
mov3 1 13
开始你的任务吧,祝你成功!
# 取指令:根据程序计数器给出的地址取出主存对应的指令放入指令寄存器
def fetch(mem, pReg):
########## Begin ##########
# print(pReg)
iReg = mem[pReg]
pReg = pReg + 1
########## End ##########
print(iReg)
return pReg, iReg
对程序寄存器中的指令进行解析,得到操作码和操作数。
参考之前关卡。
在右侧编辑区补全函数 decode
中 Begin-End 区间的代码,模拟实现 CPU 指令译码的操作,对参数 iReg
中存放的指令进行解析,并将指令操作码和操作数以元组的方式返回。函数返回的元组应包含 3 个元素,其中第 1 个元素为操作码,第 2、3 个元素分别对应第 1 、2 个操作数,若操作数不存在,则用 None
表示。 提醒:由于使用列表模拟通用寄存器和主存,因而当 TOY 计算机指令中的操作数为某个寄存器和主存单元地址时,使用列表元素下标表示,故此时操作数的数据类型应为整型。
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。测试样例如下所示:
测试输入:mov3 1 0
# 函数参数iReg
中存放的待解析指令
预期输出:('mov3', 1, 0)
测试输入:out 1
# 函数参数iReg
中存放的待解析指令
预期输出:('out', 1, None)
开始你的任务吧,祝你成功!
# 指令译码:解析指令寄存器中的指令
def decode(iReg):
########## Begin ##########
l = len(iReg)
cnt, i = 0, 0
L = []
while i < l:
while i < l and iReg[i] == ' ':
i = i + 1
if i == l:
break;
cnt = cnt + 1;
mdl = ''
while i < l and iReg[i] != ' ':
mdl = mdl + iReg[i]
i = i + 1
if cnt > 1:
bg = 0
while bg < len(mdl) and mdl[bg] == '0':
bg = bg + 1
mdl = mdl[bg:]
L.append(mdl)
while cnt < 3:
L.append('None')
cnt = cnt + 1
ret = '(' + "'" + L[0] + "'" + ',' + ' ' + L[1] + ',' + ' ' + L[2] + ')'
return ret
########## End ##########
根据 TOY 计算机指令译码结果,编程模拟指令执行及写结果的操作。
参考实训首页 TOY 计算机指令集介绍,及前面关卡的相关知识。
在右侧编辑区补全函数 execute
中 Begin-End 区间的代码,结合上图对指令功能的描述编程模拟实现 CPU 指令执行及写结果的操作。其中,参数opcode
对应指令操作码、op1
、op2
对应 2 个操作数,参数 reg
、mem
、pReg
分别表示通用寄存器、主存和程序计数器。 execute
函数的返回值规定如下:若操作码是add
、sub
、mul
、div
、mov1
、mov2
、mov3
、in
、out
,则返回第 1 个操作数对应位置存放的值;若操作码是jmp
、jz
,返回程序计数器pReg
中的值;若操作码是halt
,返回False
。
说明:
execute
函数中,opcode
,op1
, op2
的参数值来自于第 4 关指令译码函数decode
执行后得到的元组(opcode,op1, op2)
。pReg
中。000
开始存放,即指令在程序中的逻辑地址(.toy 文件中指令前面的编号)和其被装入主存后的物理地址一致。 平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。 本关卡共有 5 个测试集,测试集输入数据主要分为 2 个部分,第 1 部分是使用 TOY 计算机指令集编写的测试代码文件,分别命名为 test1.toy ~ test5.toy,这些代码文件主要用于测试各种指令是否能够正确执行,故不考虑代码的完整性和功能性;第 2 部分是测试代码需要的输入数据,若程序代码中无in
输入指令,则缺省第 2 部分的输入数据。
测试样例如下所示。 测试输入:test1.toy
预期输出:10
测试输入:test5.toy
10
预期输出:40
test1.toy ~ test5.toy 的测试代码如下所示。
#test1.toy
000 mov3 0 1
001 mov3 1 11
002 sub 1 0
003 mov2 2 1
#test2.toy
000 mov3 3 10
001 mov3 4 3
002 div 3 4
003 add 4 4
004 jz 3 002
005 out 3
#test3.toy
000 mov3 1 22
001 mov2 0 1
002 mov1 0 0
#test4.toy
000 mov3 0 4
001 jmp 003
002 mov2 990 0
003 halt
#test5.toy
000 mov3 8 4
001 in 9
002 mul 8 9
开始你的任务吧,祝你成功!
# 执行和写结果
def execute(opcode, op1, op2, reg, mem, pReg):
########## Begin ##########
global iReg
if opcode == 'mov1':
reg[op1] = eval(mem[op2])
result = reg[op1]
elif opcode == 'mov2':
mem[op1] = str(reg[op2])
result = mem[op1]
elif opcode == 'mov3':
reg[op1] = op2
result = reg[op1]
elif opcode == 'add':
reg[op1] += reg[op2]
result = reg[op1]
elif opcode == 'sub':
reg[op1] -= reg[op2]
result = reg[op1]
elif opcode == 'mul':
reg[op1] *= reg[op2]
result = reg[op1]
elif opcode == 'div':
reg[op1] = reg[op1] // reg[op2]
result = reg[op1]
elif opcode == 'jmp':
iReg = mem[op1]
# pReg = eval(mem[op1])
return pReg
elif opcode == 'jz':
if reg[op1] == 0:
# pReg = eval(mem[op2]
iReg = mem[op2]
result = pReg
elif opcode == 'in':
reg[op1] = eval(input())
result = reg[op1]
elif opcode == 'out':
# print(reg[op1])
result = reg[op1]
else:
result = False
########## End ##########
return result
本关任务是将前面关卡的功能结合起来,模拟一个程序在 CPU 中的完整执行过程。
参考之前关卡。
仔细阅读右侧编辑器给出的代码框架及注释,在指定的 Begin-End 区间编写程序,将使用 TOY 计算机指令集编写的.toy
文件,根据给定的主存地址address
加载到主存mem
指定区域(连续存放),并控制 CPU 执行.toy
文件中的程序代码,得到执行结果。 你需要补充完整 5 个函数:
loadProgram(file, mem, address)
实现程序加载功能:从主存mem
的address
地址开始存放file
文件,无返回值。fetch(mem, pReg)
实现取指令功能:根据程序计数器给出的地址取出主存对应的指令放入指令寄存器,程序计数器自增 1,函数返回pReg
和iReg
的值。decode(iReg)
实现指令译码功能:解析指令寄存器中的指令,不存在的操作数置为None
,函数返回操作码opcode
,和 2 个操作数op1
,op2
。execute(opcode, op1, op2, reg, mem, address)
实现执行和写结果功能:根据指令解析的操作码执行对应的操作,若为停机指令返回False
,其余指令返回 True
。特别说明:1)执行跳转指令jmp
和jz
时,需要考虑程序中代码的逻辑地址(.toy 文件中指令前面的编号)和放入主存后的物理地址的不一致之处,两个地址间存在固定的偏移量差值(address
);2)指令mov1
和mov2
读写的是主存中存放的数据(不是指令),这里直接使用指令中给出的物理地址实现读写访问。run(file, addr)
实现完整过程模拟功能:程序加载、取指令、指令译码、指令执行和写结果,无返回值。注意,在执行程序前,需要将第一条指令代码在主存中的地址(即addr
)放入程序计数器pReg
中,为第一次取指令做好准备。
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。 本关卡共有 3 个测试集,测试集的输入数据中,第 1 个是使用 TOY 计算机指令集编写的测试代码文件,分别命名为 add.toy、sum100.toy、sum.toy,这些代码文件主要用于测试模拟程序的编写是否正确,故不考虑代码的完整性和功能性;第 2 个是给定的主存地址address
;若.toy
中的测试代码需要输入数据,则排在第 3 个位置。评测输出为.toy
中测试代码的输出内容,即out
指令的执行结果。 测试样例如下所示:
测试输入:add.toy
0
预期输出:25
测试输入:sum.toy
20
51
预期输出:1275
add.toy、sum100.toy、sum.toy 的测试代码如下所示。
#add.toy
000 mov3 1 12
001 mov3 2 13
002 add 1 2
003 out 1
004 halt
#sum100.toy
000 mov3 1 0
001 mov3 2 1
002 mov3 3 1
003 add 1 2
004 add 2 3
005 mov3 4 101
006 sub 4 2
007 jz 4 009
008 jmp 003
009 out 1
010 halt
#sum.toy
000 in 4
001 mov2 999 4
002 mov3 1 0
003 mov3 2 1
004 mov3 3 1
005 add 1 2
006 add 2 3
007 mov1 4 999
008 sub 4 2
009 jz 4 011
010 jmp 005
011 out 1
012 halt
开始你的任务吧,祝你成功!
# 模拟 CPU 执行完整程序代码的全部过程
# 初始化主存、通用寄存器、指令寄存器和程序计数器
mem = [''] * 1000 # 主存
reg = [0] * 10 # 通用寄存器
pReg = 0 # 程序计数器
iReg = '' # 指令寄存器
def loadProgram(file, mem, address):
txt = open(file, 'r')
s = txt.readlines()
for x in s:
l = len(x)
bg, L = 0, []
while bg < l:
mdl = ''
while bg < l and x[bg] != ' ':
mdl = mdl + x[bg]
bg += 1
while bg < l and x[bg] == ' ':
bg += 1
L.append(mdl)
L.pop(0)
mem[address] = ' '.join(L)
address = address + 1
def fetch(mem, pReg):
iReg = mem[pReg]
return pReg, iReg
def decode(iReg):
l = len(iReg)
cnt, i = 0, 0
L = []
while i < l:
while i < l and iReg[i] == ' ':
i = i + 1
if i == l:
break
cnt = cnt + 1;
mdl = ''
while i < l and iReg[i] != ' ':
mdl = mdl + iReg[i]
i = i + 1
if cnt > 1:
bg = 0
while bg < len(mdl) and mdl[bg] == '0':
bg = bg + 1
mdl = mdl[bg:]
L.append(mdl)
tmp = cnt
while cnt < 3:
L.append('None')
cnt = cnt + 1
if tmp == 1:
return L[0], 0, 0
elif tmp == 2:
if L[1] == '' or L[1] == '\n':
return L[0], 0, 0
return L[0], eval(L[1]), 0
else:
ret1, ret2 = 0, 0
if L[1] != '' and L[1] != '\n':
ret1 = eval(L[1])
if L[2] != '' and L[2] != '\n':
ret2 = eval(L[2])
return L[0], ret1, ret2
def execute(opcode, op1, op2, reg, mem, addr):
global pReg
flag = False
if opcode == 'mov1':
reg[op1] = eval(mem[op2])
elif opcode == 'mov2':
mem[op1] = str(reg[op2])
elif opcode == 'mov3':
reg[op1] = op2
elif opcode == 'add':
reg[op1] += reg[op2]
elif opcode == 'sub':
reg[op1] -= reg[op2]
elif opcode == 'mul':
reg[op1] *= reg[op2]
elif opcode == 'div':
reg[op1] = reg[op1] // reg[op2]
elif opcode == 'jmp':
iReg = mem[op1]
pReg = op1 + addr
flag = True
elif opcode == 'jz':
if reg[op1] == 0:
flag = True
iReg = mem[op2]
pReg = op2 + addr
elif opcode == 'in':
reg[op1] = eval(input())
elif opcode == 'out':
print(reg[op1])
if flag == False:
pReg = pReg + 1
if opcode != 'halt' and opcode != 'halt\n':
result = True
else:
result = False
return result
def run(file, addr):
global pReg, iReg
pReg = addr
loadProgram(file, mem, addr)
while True:
pReg, iReg = fetch(mem, pReg)
L, op1, op2 = decode(iReg)
mdl = execute(L, op1, op2, reg, mem, addr)
if mdl == False:
break
file = input()
address = int(input())
run(file, address)
本关任务:修改给定的程序代码,使其支持执行使用 TOY 计算机扩展指令集编写的程序代码。
下面的表 1 列出了 TOY 计算机指令集中的所有指令。
表1 TOY计算机的指令集
本关在上面表 1 指令集的基础上新增 3 条指令,如表 2 所示。
表2 TOY计算机的扩展指令
仔细阅读右侧编辑区给出的程序代码框架,基于第 6 关实现的 TOY 计算机模拟程序,添加表 2 中的 3 条指令,使其能够正确执行使用 TOY 扩展指令集编写的程序。你需要在 Begin-End 区间补充完整 6 个函数的代码:
init()
实现初始化工作:将主存mem
( 1000 个单元),通用寄存器reg
( 10 个),程序计数器pReg
,指令寄存器iReg
,状态寄存器CF
,全部初始化为 0 值,无返回值。loadProgram(file, mem, address)
实现程序加载功能:从主存mem
的address
地址开始存放file
文件,无返回值。fetch()
实现取指令功能:根据程序计数器给出的地址取出主存对应的指令放入指令寄存器,程序计数器自增 1,无返回值。decode()
实现指令译码功能:解析指令寄存器中的指令,不存在的操作数置为None
,函数返回操作码和 2 个操作数。execute(opcode,op1,op2,address)
实现执行和写结果功能:根据指令解析的操作码执行对应的操作,若为停机指令返回False
,其余指令返回 True
;要求能够模拟 TOY 扩展指令集 15 条指令的执行。特别说明:1)执行跳转指令jmp
和jz
时,需要考虑程序中代码的逻辑地址(.toy 文件中指令前面的编号)和放入主存后的物理地址的不一致之处,两个地址间存在固定的偏移量差值(address
);2)指令mov1
和mov2
读写的是主存中存放的数据(不是指令),这里直接使用指令中给出的物理地址实现读写访问。run(file, addr)
实现完整过程模拟功能:程序加载、取指令、指令译码、指令执行和写结果,无返回值。注意,在执行程序前,需要将第一条指令代码在主存中的地址(即addr
)放入程序计数器pReg
中,为第一次取指令做好准备。评测文件会调用你写的函数实现从初始化、读取 .toy 程序文件名称,到执行 .toy 程序的全部过程。
提醒:不能将第 6 关代码直接放到本关,两关代码存在一定差异。
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。
本关卡共有 3 个测试集,测试集的输入数据中,第 1 个是使用 TOY 计算机指令集编写的测试代码文件,分别命名为 expand.toy、expand2.toy、expand3.toy,这些代码文件主要用于测试模拟程序的编写是否正确,故不考虑代码的完整性和功能性;第 2 个是给定的主存地址address
;若 .toy 中的测试代码需要输入数据,则排在第 3 个位置。评测输出为 .toy 中程序代码的输出内容,即out
指令的执行结果。
测试样例如下所示:
测试输入:expand.toy
2
预期输出:500500
测试输入:expand3.toy
10
3
10
预期输出:3
expand.toy、expand2.toy、expand3.toy 的代码指令如下所示。
#expand.toy
000 mov3 0 0
001 mov3 1 1
002 add 0 1
003 add2 1 1
004 cmp 1 1000
005 mov3 2 002
006 ble 2
007 out 0
008 halt
#expand2.toy
000 mov3 4 1
001 mov3 5 2
002 mul 4 5
003 add2 5 1
004 cmp 5 6
005 mov3 6 002
006 ble 6
007 out 4
008 halt
#expand3.toy
000 in 0
001 in 1
002 div 1 0
003 out 1
004 halt
开始你的任务吧,祝你成功!
mem = [''] * 1000 # 主存
reg = [0] * 10 # 通用寄存器
pReg = 0 # 程序计数器
iReg = '' # 指令寄存器
CF = 0
def init():
global mem, reg, pReg, iReg, CF
########## Begin ##########
mem = [''] * 1000 # 主存
reg = [0] * 10 # 通用寄存器
pReg = 0 # 程序计数器
iReg = '' # 指令寄存器
CF = 0
def loadProgram(file, mem, address):
txt = open(file, 'r')
s = txt.readlines()
for x in s:
l = len(x)
bg, L = 0, []
while bg < l:
mdl = ''
while bg < l and x[bg] != ' ' and x[bg] != '\t':
mdl = mdl + x[bg]
bg += 1
while bg < l and (x[bg] == ' ' or x[bg] == '\t'):
bg += 1
L.append(mdl)
L.pop(0)
mem[address] = ' '.join(L)
address = address + 1
# 取指令:根据程序计数器给出的地址取出主存对应的指令放入指令寄存器
def fetch():
global mem, pReg, iReg
iReg = mem[pReg]
return pReg, iReg
# 指令译码:解析指令寄存器中的指令,不存在的操作数置为None
def decode():
global iReg
l = len(iReg)
cnt, i = 0, 0
L = []
while i < l:
while i < l and iReg[i] == ' ':
i = i + 1
if i == l:
break
cnt = cnt + 1;
mdl = ''
while i < l and iReg[i] != ' ':
mdl = mdl + iReg[i]
i = i + 1
if cnt > 1:
bg = 0
while bg < len(mdl) and mdl[bg] == '0':
bg = bg + 1
mdl = mdl[bg:]
L.append(mdl)
tmp = cnt
while cnt < 3:
L.append('None')
cnt = cnt + 1
if tmp == 1:
return L[0], 0, 0
elif tmp == 2:
if L[1] == '' or L[1] == '\n':
return L[0], 0, 0
return L[0], eval(L[1]), 0
else:
ret1, ret2 = 0, 0
if L[1] != '' and L[1] != '\n':
ret1 = eval(L[1])
if L[2] != '' and L[2] != '\n':
ret2 = eval(L[2])
return L[0], ret1, ret2
########## End ##########
# 执行和写结果:根据指令解析的操作码执行对应的操作,若为停机指令返回 False,其余指令返回 True
##opcode为操作码,op1为第一个操作数,op2为第二个操作数
def execute(opcode, op1, op2, address):
global reg, pReg, CF, mem
flag = False
if opcode == 'mov1':
reg[op1] = eval(mem[op2])
elif opcode == 'mov2':
mem[op1] = str(reg[op2])
elif opcode == 'mov3':
reg[op1] = op2
elif opcode == 'add':
reg[op1] += reg[op2]
elif opcode == 'sub':
reg[op1] -= reg[op2]
elif opcode == 'mul':
reg[op1] *= reg[op2]
elif opcode == 'div':
reg[op1] = reg[op1] // reg[op2]
elif opcode == 'jmp':
iReg = mem[op1]
pReg = op1 + address
flag = True
elif opcode == 'jz':
if reg[op1] == 0:
flag = True
iReg = mem[op2]
pReg = op2 + address
elif opcode == 'in':
reg[op1] = eval(input())
elif opcode == 'out':
print(reg[op1])
elif opcode == 'add2':
reg[op1] += op2
elif opcode == 'cmp':
if reg[op1] <= op2:
CF = 1
else:
CF = 0
elif opcode == 'ble':
if CF:
pReg = reg[op1] + address
flag = True
if flag == False:
pReg = pReg + 1
if opcode != 'halt' and opcode != 'halt\n':
result = True
else:
result = False
########## End ##########
return result
# 完整过程模拟:程序加载、取指令、指令译码、指令执行和写结果
def run(file, addr):
global pReg, iReg, reg
######## Begin ########
pReg = addr
loadProgram(file, mem, addr)
while True:
pReg, iReg = fetch()
L, op1, op2 = decode()
mdl = execute(L, op1, op2, addr)
if mdl == False:
break
########## End ##########
本文灵感(特别鸣谢):【Educoder作业】※ 冯·诺依曼体系结构模拟