(这里插一嘴,我已经决定跳槽Ubuntu20.4.3版本了,因为Ubuntu12.04版本的许多问题在网上实在是太难找到解决方式了!!!!!)
ok这里是2023/3/24,忘了说了,由于大部分实验还是32位机器上的,为了避免出现问题,所以我还是决定一直使用Ubuntu12.04 32位机器
资料:
链接:https://pan.baidu.com/s/16Oc3hAC8lm7CUjsVnmDscA
提取码:8888
实验项目一
项目名称
实验1.1 原型机I
实验目的
(1) 了解冯诺伊曼体系结构;
(2) 理解指令集结构及其作用;
(3) 理解计算机的运行过程,就是指令的执行过程,并初步掌握调试方法。
实验资源
(1) 阅读教材,掌握冯诺伊曼体系的相关内容;
(2) 学习课程《最小系统与原型机I》。
(3) Ubuntu12.04 32位版本虚拟机
(4) Ubuntu20.4.3 64位版本虚拟机
2. 实验任务
2.1 实验任务A
任务名称:配置1.config,调试并运行其中的a.txt。
按照实验报告上所列出的步骤操作即可:
(1) 进入终端,切换文件夹至./Desktop/32bit
(2) 使用./vm32 1.config来运行原型机I的模拟器,
运行后界面如下图所示。
(3) 在运行后,提示将要执行的指令地址及内容,在本例中,提示要执行位于内存0011处的指令“1”,即等待输入一个整数值。此时输入si则表示执行此指令,同时也可以输入其他的指令,使用help可以查看此模拟器支持的命令:
(4) 此时输入i r,可以查看各个寄存器的值,而输入x 6 0000则表示查看从0000开始的连续6个内存地址值,结果如图所示。
(5) 输入si,则表示运行一条指令,例如此时运行的指令是“1”,表示等待输入,输入一个值3,在输入完成后将此数值存至R0寄存器,运行完成后,再运行i r指令,就可以看到输入的值5确实是已经存在R0寄存器中,每个寄存器的值都用十进制和十六进制表示,如下图所示。
(6) 继续使用si指令单步执行指令 5 R00000, 表示将R0寄存器中的值5传送到内存地址0000处,并且以二进制形式进行存储。
(7) 继续si执行下一条指令 4 1 R2,表示将立即数1传送到寄存器R2中,结果如下图所示,注意观察寄存器R2值的变化情况。
(8) 继续si执行下一条指令 2 R0 R1,表示将R0与R1的值相加,结果放于R1中,注意观察寄存器R1值的变化情况。
(9) 继续si执行下一条指令 3 R2 R0,表示将R0的值减去R2的值,结果放于R0中,注意观察寄存器R0值与R3值的变化情况。
(10) 继续si执行下一条指令 6 -2,即有条件跳转,如果R3的值为1,则需要跳转,此时跳转的值为-2,则表示需要向前跳转两条指令,再去执行2 R0 R1指令。从整体上来看,我们的任务是要执行累加和3+2+1,截止到目前,我们已经完成了3+2+1中的第一步计算,并保存结果到了R1中,现在准备去执行+2这一操作,执行完6 -2这一操作后,PC值发生变化,在下图中观察到了PC值的改变。
(11) 在计算完3+2+1后,这时候R0的值也变成了1,执行 3 R2 R0时,R0的值变为0,而R3的值也变成了0,表示刚才相减的结果为0。
(12) 这时候执行下一条指令 6 -2时,由于R3的值为0,不满足跳转的条件,因此会执行下一条语句5 R1 0001,表示将寄存器的值保存到内存中,以便于其他的代码调用。
(13) 继续si或者使用si 4等来批量执行指令,或者是使用c来执行指令直到程序结束。由于最后我们使用了8 R1来输出最后结果,因此会打印出6这一结果。
(14) 程序执行完毕后,可以使用q退出。
(15) 综上所述,a.txt实现的是从输入x至1的公差为1的数列的求和运算。
2.2 实验任务B
任务名称:调试并运行2.config,调试并运行其中的b.txt。
(1) 进入终端,切换文件夹至./Desktop/32bit
(2) 使用./vm32 1.config来运行原型机I的模拟器,
运行后界面如下图所示。
(3)要执行的第一条指令依旧是输入一个值并将其保存在R0中:
我们输入9:
(4)接着依旧是将R0中的数据放到内存地址为0000处:
0000处存放的值前后的变化,可以看到由0变为了9(以二进制存储):
(5)接着是将R0的数据传送给R1:
可以看到R1也变成了9;
(6)接着又是一个输入操作,依旧是将输入值赋给R0:
这次我们输入6:
可以看到R0内存储的数据变成了6
(7)接下来是将现在R0中存储的数据存放到内存中地址为0001处:
可以看到0001处的值变为了6(以二进制形式存储)。
(8)执行R0-R1,并将结果赋值给R0:
由于R0为6,R1为9,所以相减的结果是-3,此时R3中存放-1
(9)由于R3中的值不为1,所以下一条条件跳转指令不会被执行,而是直接顺序执行:
(10)将内存地址为0000处的值赋给内存地址为0010处:
注意0010处值的改变;
(11)将内存地址为0010处的值赋给R0:
则此时R0变为9:
(12)输出R0:
(13)上述情况是第一次输入的值比第二次输入的值大,会输出较大的值,那么如果第一次输入比第二次输入小,会不会也如此呢,经过试验后发现是这样的:
该情况是第一次输入15,第二次输入25,则输出25,为较大的数。
所以b.txt实现的是用一次减法来判断两个数的大小的操作。
2.3 实验任务C
任务名称:调试并运行3.config,调试并运行其中的c.txt。
(由于一开始c.txt存在问题,所以我直接跳过去做了实验1.2,在做实验1.2的时候发现修改cpu.c后make有问题,所以我直接跳槽到了Ubuntu 20.04版本)
(1)进入对应文件夹:
(2)使用./vm64 3.config运行模型机:
(3)等待输入:
本例子中输入一个4;下一步该输入被存储到内存地址为00000的位置上;
(4)再次输入,本次输入为2,直接存储在R0中
(5)将内存地址为00000处的值赋值给地址为00001处
并且将内存地址为00001处的值交给寄存器R1,此时寄存器里的值如图最下面的绿色方框所示。
(6)R1-R0=>R1,并且将R3的值设为1;
因为R3的值为1,所以条件跳转语句“6 2”得以执行;
(7)将内存地址00010处的地址赋给寄存器R2,将1直接赋值给寄存器R3;
(8)R2+R3=>R2;
(9)将R2的值赋值给内存地址为00010处,将R1的值赋值给内存地址为00001处。
(10)若某次循环中R1变为0,则R3为0,语句“6 2”无法进行有条件跳转,则顺序执行语句“7 7”,实现无条件跳转;
(11)接下来做两步赋值操作(绿色框)和一步相减R2-R1=>R2
(12)后面在进行几步赋值操作后进行一步R2+R3=>R2,之后在进行几步赋值操作;最终输出结果为2;
(13)后续又换了两组输入,一组输入64 16,输出为4;一组输入126 2,输出位63
所以c.txt实现的功能是第一个输入值作为被除数第二个数作为除数的除法运算,但个人认为有点刻意保留了之前代码的执行过程,造成了许多步骤的冗余。
3. 总结
实验中出现的问题
实验任务A:
一开始未切到目标目录中,再加上指令输入有误,导致无法实现正常调用:
目录和指令有误:
指令有误:
实验任务B:
未遇到问题。
实验任务C:
有些刻意保留原来错误代码执行过程,造成了许多不必要的冗余操作,一开始有点晦涩难懂。
心得体会
本次实验让我初步感受了模型机配置以及运行过程,初步熟悉了Linux的一些常用指令。了解了模型机执行加法和减法时的内存与寄存器之间的调用过程,为后续1.2实现乘法和除法打下了基础。通过解读指令加深了对原型机指令集的理解与记忆。
4. 思考问题
(1) 如果基于这些指令实现两个整数的乘法与除法?
答:
乘法:思路是将乘法分解为加法,例如对于5*6,执行6次加5的操作:5*6=5+5+5+5+5+5
除法:思路是将除法分解为减法,例如对于24/2,可以拆成24对2循环做减法,利用一个寄存器作为计数器,计算循环次数,最终输出寄存器中存放的循环次数。
(2) 原型机I的指令集是否完备?如果是,那么如何证明(提示:搜索并阅读“可计算性理论”)?如果不是,那么要增加哪些指令?
答:
我认为不是,应加强乘除运算和指数运算指令。
要判断一个模型的指令集是否是图灵完备的,需要检查该指令集是否能够模拟一台图灵机,也就是说,该指令集是否具有图灵机的计算能力。如果能够模拟一台图灵机,那么该指令集就是图灵完备的。
一个模型的指令集是图灵完备的,需要满足以下条件:
①该指令集能够进行任意复杂度的算术运算,包括加减乘除、指数等操作;
②该指令集能够进行条件判断和跳转操作;
③该指令集能够实现数据的读写操作。
如果一个模型的指令集可以满足以上三个条件,那么它就是图灵完备的。
但显然该原型机I的指令集无法直接实现乘除和指数操作。虽然我们可以用加减运算来实现乘除操作,但也仅仅局限于一些简单的乘除问题。当遇到较为复杂的乘除问题时就无法得以解决。