前言
二周目玩家,浅试一下这次的原型机实验。总体感觉跟上一年的很相似,但还是有所不同。
可以比较明显地感觉到,这个界面越来越好看了,可操作与可探索的功能也越来越多了。
我们HNU的SYSTEM真的越来越好了!!!(doge)
(1)vspm,miniCC
在试玩之前,先明确vspm、miniCC这些都是什么。
vspm是一个小型的原型机,模拟了一个有6个寄存器,有cpu等基础配置的计算机,使用汇编代码来逐步或多步执行,用于教学作用。(vspm读取.txt配置文件并逐步执行)
miniCC是编译器,以近似C的语法执行编译,将我们写的代码逐步编译成四元组,vspm的汇编指令,配置文件,以一些其它的指令可以做一些别的操作,如指定输出文件名,打印内容等。它做到的事情类似于gcc,但有所不同,gcc是逐步生成预编译文件,汇编代码与可执行文件的过程,和用作教学作用的vspm不同。
但miniCC编译器确实存在一些严重的问题,相信喜欢探索的同学已经碰到了,这个问题坦白来说确实存在,而且短时间内没法解决,可能到23级的同学的时候就能修好了。目前一个问题是在for循环的判定条件内只能识别++或者--,不能识别 i = i + 1这种。
下面是gcc逐步生成可执行文件的过程。
gcc -E helloworld.c -o helloworld.i
gcc -S helloworld.i -o helloworld.s
gcc -c helloworld.s -o helloworld.o
gcc helloworld.o -o helloworld
其实感觉形式上还是有点像的,虽然内容完全不同。
(2)环境配置
还有环境配置,这个环境要求要java环境(因为vspm使用java编写),版本21以上,包括ubuntu环境的配置,是一个很大的工程(新手基本暴毙在环境配置上)。我的好同学,同时也是计科某班的助教LIU,写了一个《how to build java21 on ubuntu20.04》,有机会可以读一下。
下面就是试玩报告啦:
试玩报告
实验1.原型机vspm1.0
【实验目的】
- 了解冯诺伊曼体系结构;
- 理解指令集结构及其作用;
- 理解计算机的运行过程,就是指令的执行过程,并初步掌握调试方法。
【实验准备】
- 阅读教材,掌握冯诺伊曼体系的相关内容;
- 学习课程《最小系统与原型机I》。
【实验步骤】
- 进入终端,使用cd vspm1.0进入目录;
使用./vspm a-inst.txt来运行原型机1.0的模拟器,其中a-inst.txt为代码文件,
6
in R1 #输入a到R1
movi 1 #设置R0为1
add R2,R1 #R2存放累加值
sub R1,R0 #R1的值即a减去1,此时会设置G值
movd #将当前PC值保存在R3中
movi -3 #存放-3到R0中,跳转到第二行
add R3,R0 #R3减去3,注意此时不能用SUB指令,会影响G值
jg #如果R1的值还大于1,则跳到第2行去执行
out R2 #如果R1的值此时小于等于1,则准备输出
halt #停机
第一行的6,表示分配6个字节的数据段;
后面的均为原型机指令,每一行指令代表的意义及整体执行结果在《最小系统与原型机I》中已经进行了详细说明。
运行后界面如下图所示。
- 在运行后,提示将要执行的指令地址及内容,在本例中,提示要执行位于内存00000110处的指令“in R1”,即等待输入一个整数值。此时输入si则表示执行此指令,同时也可以输入其他的指令,使用help可以查看此模拟器支持的命令:
- 此时输入i r,可以查看各个寄存器的值,而输入x 6 0000则表示查看从0000开始的连续6个内存地址值,结果如图所示。
- 输入si,则表示运行一条指令,例如此时运行的指令是“in R1”,表示等待输入,输入一个值5,在输入完成后将此数值存至R0寄存器,运行完成后,再运行i r指令,就可以看到输入的值5确实是已经存在R0寄存器中,每个寄存器的值都用十进制和十六进制表示,如下图所示。
- 后续程序的执行过程可参照视频进行操作。
- 程序执行完毕后,可以使用q退出。
三、练习思考
1.练习内容
1.按照上述的实验步骤,完成相关操作;
【参考回答】上述a-inst.txt完成的工作是计算自然数列1+2+……+n的和,n由输入读入。
2.在目录下还有b-inst.txt和c-inst.txt,请运行并调试,并对这些代码所做的工作进行解释;
b-inst.txt(完成比较a和b大小的工作,并输出较小的值)
6
in R1 #输入第一个数a
in R2 #输入第二个数b
mova R0,R1 #在R0保存a
sub R1,R2 #a-b,此时会设置G
mova R1,R0 #a保存到R1
movd #保存当前的PC值到R3
movi 6 #R0的值设置为6,即跳转到11行
add R3,R0 #R3的值加6
mova R0,R2 #b的值保存到R0
jg #如果a的值比b大,就跳转
#跳转地址
mova R0,R1 #将a的值保存到R0
out R0 #输出R0
halt
c-inst.txt(完成两个大于1的正数相乘的工作,并输出结果)
8
#两个大于1的正数相乘
in R1 #乘数a
movb R0,R1 #乘数a存放到内存0000 0000
in R1 #被乘数b
movi 1
movb R0,R1 #被乘数b存放在内存0000 0001
#结果存放在内存 0000 0010
#开始循环
movi 1 #R0中的值为1
movc R1,R0 #从内存中取出值b
movi 1 #设置R0中的值为1
sub R1,R0 #R1即b值减1,此时设置G值
movi 1
movb R0,R1 #b值需要保存回去
movi 0 #R0中设置为0,即内存地址0
movc R2,R0 #取出a值
movi 2 #R0中设置为2,即内存地址0000 0010
movc R1,R0 #取出结果
add R1,R2 #做加法
movb R0,R1 #将结果存回去
movd #保存当前的PC值到R3
movi -12 #R0的值设置为-12
add R3,R0 #R3的值加-12
jg #如果第12行的减法设置G为1,就跳转
#循环结束
movi 2 #R0中设置为2,即内存地址0000 0010
movc R1,R0 #取出结果
out R1 #打印结果
halt
3.完成实验报告。
2.思考问题
(1) 如果基于这些指令实现两个整数的乘法与除法?
【参考想法】
实际上可以先用c++写好,然后使用hnu-vspm编译器来进行编译。
分别使用下述命令,若没有编译报错,则可以最终生成配置文件(供vspm读取的汇编代码)
./miniCC -c1 mytest.c -o mytest.q
./miniCC -svspm mytest.q -o mytest.hnus
./miniCC -lvspm mytest.hnus -o mytest.txt
最后再使用如下指令就可以像上面的a-inst等一样单步运行了。
./vspm mytest.txt
前提是没有出编译错误。
上述过程大致可以截图如下:
然后就可以试试自己的代码能不能成功运行,是不是可以转成合适的vspm汇编代码。
(2) vspm1.0的指令集是否完备?如果是,那么如何证明(提示:搜索并阅读“可计算性理论”)?如果不是,那么要增加哪些指令?
(3) 如果一台计算机只支持加法、减法操作,那么能否计算三角函数,对数函数?(提示:搜索并阅读“泰勒级数展开”等内容)
(4)对于某个需要完成的功能,如果既可以通过硬件上增加电路来实现,也可以通过其他已有指令的组合来实现,那么如何判断哪一种比较合适?(提示:搜索并阅读RISC与CISC)。
【这算是预告了,下次不该放这个】此外还有一些比较有趣的点值得关注
比如数据段是什么,配置文件的第一个数字6是什么意思,PC寄存器和G寄存器是干什么的……
验收思路
作为22某班级的本科生实验验收助教,参与了实验验收,为验收提供了一些思路,摘抄如下
(作用:供之后助教参考,供之后同学研究,协同进化,另供我无事消遣自娱自乐)
(注意:实验评价已经上交,以下内容不以任何方式影响已经成立的实验评价)
(1)整体概览
vspm是一个小型的原型机,模拟了一个有6个寄存器,有cpu等基础配置的计算机,使用汇编代码来逐步或多步执行,用于教学作用。(vspm读取.txt配置文件并逐步执行)
miniCC是编译器,以近似C的语法执行编译,将我们写的代码逐步编译成四元组,vspm的汇编指令,配置文件,以一些其它的指令可以做一些别的操作,如指定输出文件名,打印内容等。它做到的事情类似于gcc,但有所不同,gcc是逐步生成预编译文件,汇编代码与可执行文件的过程,和用作教学作用的vspm不同。
但miniCC编译器确实存在一些严重的问题,相信喜欢探索的同学已经碰到了,这个问题坦白来说确实存在,而且短时间内没法解决,可能到23级的同学的时候就能修好了。目前一个问题是在for循环的判定条件内只能识别++或者--,不能识别 i = i + 1这种。
【参考问题】
- 代码段,数据段是什么?分别放在哪里?分别保存什么?6个寄存器(R0-R3,G,PC)分别什么作用?
- MOVA,MOVB,MOVC,MOVD,MOVI分别是什么作用?覆盖了寻址方式的哪几种?
- 如果我是这个vspm,从初上电(即使用./vspm调用),我是怎么完成我的任务的?(初始化,装载指令,等)。
(2)环境配置
遇到了什么问题,如何解决
- (同学问的最多的问题)我为什么要安装Java环境,我写的不是C代码嘛?
(3)a-inst.txt
①完成工作
a-inst.txt完成的工作是计算自然数列1+2+……+n的和,n由输入读入
②时间复杂度
O(n)【该项目由刘助教提供】
③改进方法,以及需要对指令集做出什么改进,改进后的时间复杂度
等差数列求和,需要乘法指令imull,改进后可以到O(1)【该项目由刘助教提供】
(4)b-inst.txt
①完成工作
完成比较a和b大小的工作,并输出较小的值
②PC寄存器与跳转原理
简述PC寄存器与跳转的基本原理【如果这个不会,只能认为基本没有理解指令集】
③如何改写成输出较大的值
【心血来潮想的小问题,主要目的是检测有没有自己了解b-inst这个结构,有同学认为太简单就没有看,因此不能很快反应过来,但是后面的同学都有抗性,全部都会了】
调换这里两个mova的顺序(本来就是一个分支结构)
mova R0,R2 #b的值保存到R0 jg #如果a的值比b大,就跳转 #跳转地址 mova R0,R1 #将a的值保存到R0
④如何改写成输出两值的差(正数)
基于判断再多加一步,让最终保存到R0的值变成大数-小数即可。
⑤有关跳转的问题
jg是什么指令,完成什么工作?什么时候跳转?(寄存器G)寄存器G是怎么设置的?我怎么知道要跳转到哪里?(确定跳转时R3会放入PC,这里考察指令集看了没有,指令集的基础问题)PC是什么?PC怎么被加上偏置的?(就jg的前面几步)【有部分同学基础不是很牢,这一系列问题都不太会,如果这里不会的话基本可以认为指令集基础不够】
(5)c-inst.txt
①完成工作
完成两个大于1的正数相乘的工作,并输出结果
②如何改变体系结构使其支持乘法
增加一个什么部件?阵列乘法器(全加器放在一起)【这也是刘助教提供的问题,但早上我看他问太多了,下午我就没再采用了】
③关于乘法的思路
化乘为加,是后面写除法的基础,要求比较熟练。
【有同学做了扩展,探索了有负数的乘法以及有0的乘法的实现,给你们点个赞】
(6)思考问题
① 如果基于这些指令实现两个整数的乘法与除法?
【参考问题】
-
考察是否实现,考察是否考虑特殊情况(3/5,0/5,5/0等),考虑是否实现输出商和余数?
-
简述思路?
-
代码是否自己完成,是否能描述细节?
-
有没有遇到整除问题?如何解决整除问题?
【参考用例】
17/3,16/4
【参考思路】
可以循环先减,减过头了把这个数取反作为余数,也可以在减过头之前就停止
【参考代码】
这是一种基于预先做下一次减法但不保存的方法来实现的循环减法,通过预先做下一次减法,判断下一次是否会减成负数,如果减成负数就不做下一次了。为了解决整除问题,在预先判断时先+1。
# written by wolfvoid 8 # 整数除法 in R1 # 被除数a(最终存放余数) movi 0 movb R0,R1 # 被除数a存放到内存0000 0000 in R1 # 除数b movi 1 movb R0,R1 # 除数b存放在内存0000 0001 movi 0 mova R1,R0 # 预先设置为0 movi 2 movb R0,R1 # 商存放在内存 0000 0010 #开始循环 movi 1 movc R2,R0 # 从内存中取出值b->R2 movi 0 movc R1,R0 # 从内存中取出值a->R1 sub R1,R2 # 做a-b movi 0 movb R0,R1 # a-b结果存回内存 movi 1 # 预先加1,避免整除误判的问题 add R1,R0 sub R1,R2 # 预先做减法,看看是否会超出,设置G值 movi 2 movc R1,R0 # 取出商(用作累加) movi 1 add R1,R0 # 商加一,表示多了一次可以整除 movi 2 movb R0,R1 # 将商存回 movd # 保存当前的PC值到R3 movi -16 # R0的值设置为-16 add R3,R0 # R3的值加-16 jg # 如果减法设置G为1,就跳转 #循环结束 movi 2 # R0中设置为2,即内存地址0000 0010 movc R1,R0 # 取出结果 out R1 # 打印结果 movi 0 # R0中设置为2,即内存地址0000 0010 movc R1,R0 # 取出结果 out R1 # 打印结果 halt
【参考评分】
独立完成,通过用例,解释逻辑,处理余数【好多同学没有做处理余数,确实题目没要求,但我还是推荐考虑一下,这里我评分时没做要求。另外有同学做了负数的,甚至处理了溢出,点赞】
这是4个层次
②vspm1.0的指令集是否完备?如果是,那么如何证明(提示:搜索并阅读“可计算性理论”)?如果不是,那么要增加哪些指令?
比较浅薄地观察可以发现,缺少了一些基本的逻辑指令,比如按位运算指令的按位与、按位或、按位非、求补、求反这些都无法完成。
【我在几位同学的实验报告上看到了原封不动的上面这句话】
我抄录了一下刘助教的答案:
-
可以通过使用正确的文法测试,以检查指令集是否可以从一组有限的输入序列产生任意可能的输出序列。另外,可以通过检查指令集是否支持所有功能,而不需要引入额外的指令,来判断指令集是否完备。
-
原型机Ⅰ的指令集需要补充位运算(左移右移,按位与或非,异或)指令,还需要有处理中断,故障和超时的异常,以及内存访问异常,指令异常的相关指令。
坦白说,我主要在引导同学思考,并没有去介入评判。主要在于逻辑是否自洽,是否出现常识性的问题。显然这道题存在一个标准的答案,但我懒得去找了。
③如果一台计算机只支持加法、减法操作,那么能否计算三角函数,对数函数?(提示:搜索并阅读“泰勒级数展开”等内容)
可以计算三角函数,对三角函数与对数函数可以进行麦克劳林展开,可以在舍弃无穷小的情况下近似为只含四则运算的运算式,而由模型机可知乘法与除法可由加法与减法来实现。
综上可知,该计算机可以计算三角函数与对数函数。
会产生什么问题:精度
④对于某个需要完成的功能,如果既可以通过硬件上增加电路来实现,也可以通过其他已有指令的组合来实现,那么如何判断哪一种比较合适?(提示:搜索并阅读RISC与CISC)。
i 怎么样比较合适?
性能 <> 成本
ii RISC与CISC
常见的ISA
-
RISC(Reduced Instruction Set Computing):ARM、MIPS、RISC-V
-
CISC(Complex Instruction Set Computing):x86(为什么叫x86:Intel早期的8086,286,386~586等架构)
【很多同学没有反应过来,不怪你们,但这些有必要了解一下】
RISC是“精简指令运算集”,CISC就是“复杂指令运算集”。【有同学从头到尾说反的】RISC的指令系统相对简单,它只要求硬件执行很有限且最常用的那部分指令,大部分复杂的操作则使用成熟的编译技术,由简单指令合成。关于RISC与CISC的比较如下
-
RISC更能充分利用VLSI芯片的面积。CISC的控制器大多采用微程序控制,其控制存储器在CPU芯片内所占的面积为50%以上,而RISC控制器采用组合逻辑控制,其硬布线逻辑只占CPU芯片面积的10%左右。
-
RISC更能提高运算速度。RISC的指令数、寻址方式和指令格式种类少,又设有多个通用寄存器,采用流水线技术,所以运算速度更快,大多数指令在一个时钟周期内完成。
-
RISC便于设计,可降低成本,提高可靠性。RISC指令系统简单,故机器设计周期短;其逻辑简单,故可靠性高。
-
RISC有利于编译程序代码优化。RISC指令类型少,寻址方式少,使编译程序容易选择更有效的指令和寻址方式,并适当地调整指令顺序,使得代码执行更高效化。
验收总结
首先感觉,在我验收的班级,大部分同学都认真完成了实验。虽然可能有些同学对于一些细节没有了解的特别清楚。
客观评价,这个实验还算比较简单的,跟后面的比较起来。然后,大部分同学都达到了我心理预期的要求:指令集的知识,代码操作,汇编代码的认识与使用,这些基本都能达到要求。总的来说,我认为基本这个实验的作用在我验收的班级已经发挥地差不多了。
有的同学会比较紧张,实际上我认为没有必要。验收的本质还是交流。如果能比较好地进行交流,对于感兴趣关注的内容进行分享,对于疑惑的地方进行探讨,也是一件比较好的事情。
有的同学的实验报告似曾相识……甚至十分熟悉……
个人感觉,我在评价时,还是做到了较为全面地进行评估。当然,可能会有一部分没有兼顾到,实为抱歉。