CSAPP_lab ArchitectureLab
Last updated on 8 months ago
arclab
准备
所有文件均可以从官网上直接下载:Lab Assignments
环境配置
进行这个实验,我们首先需要安装 flex
和
bison
:
1 |
|
之后运行 make clean ; make
,得到报错:
1 |
|
这是由于 gcc
版本过老导致的,解决方式如下:
将 misc/Makefile
中
1 |
|
改为:
1 |
|
对于其他的 pipe
和 seq
中的
Makefile
文件,我们也需要同样做更改
原贴如下:Fail to compile the Y86 simulatur (CSAPP)
对于全局变量来说,如果初始化为不为零的值,那么保存在
data
段,初始化为零值,保存在BSS
段,如果没有初始化仅仅是声明,那么保存在common
段,等到链接时再放入BSS
段
这个实验的模拟器有两种:TTY
和 GUI
如果需要用 GUI
,那么可以安装 Tcl/Tk
:
1 |
|
我们并没有安装,因此用 TTY
我们将 pipe/Makefile
和 pipe/Makefile
中的
GUIMODE, TKLIBS, TKINC
全部注释掉
将 sim/Makefile
从原先的:
1 |
|
修改为:
1 |
|
执行 make clean ; make
后编译成功:
1 |
|
实验开始
这个实验的
part A
和part B
很简单,重点在于part C
Part A
参照 PDF 中
4
的部分
我们需要用 Y86-64
汇编代码写三个程序,分别为:sum.ys, rsum.ys, copy.ys
前两个的测试数据如下:
1 |
|
第三个的测试数据如下:
1 |
|
我们只需要将这些数据放在汇编代码里面即可,这里面的每一个标号都表示一个地址,我们可以直接用
irmovq
对寄存器赋值,例如:irmovq ele1, %rsi
这一部分非常简单,根据书中 254
页的代码参考一下就好,这里我们直接给出答案
只要最后 %rax
的值为
0xcba
,那么答案就是正确的
sum.ys
1 |
|
文件的最后需要空一行,测试结果如下:
1 |
|
可以看到最后 %rax
的值为
0xcba
,说明答案正确
rsum.ys
1 |
|
测试结果:
1 |
|
最后 %rax
的值依旧为 0xcba
,结果正确
copy.ys
1 |
|
测试结果:
1 |
|
%rax
的值依然为 0xcba
,结果正确
Part B
参照 PDF 中
5
的部分
我们需要修改 seq/seq-full.hcl
文件,使得其能够执行
iaddq
指令
指令的字节表示在书中练习题 4.3
本质上,iaddq
指令是 irmovq
和
OPq
指令的结合,我们按照书中的五个阶段分别写出其表示即可:
Fetch
阶段:icode:ifun <- M[PC]
rA:rB <- M[PC + 1]
valC <- M[PC + 2]
valP <- PC + 10
Decode
阶段:valB <- R[rB]
Execute
阶段:valE <- valB + valC
set CC
Memory
阶段:NULL
Write back
阶段:R[rB] <- valE
PC update
阶段:PC <- valP
在 seq-ful.hcl
文件中,iaddq
指令用
IIADDQ
表示,我们依次修改即可:
Fetch
阶段:
1 |
|
Decode & Write back
阶段:
1 |
|
Execute
阶段:
1 |
|
PC update & Memory
阶段无需更改
如果要测试,我们需要先创建一个新的 SEQ simulator
:
1 |
|
随后先测试一个简单的小程序:
1 |
|
运行成功
随后我们去测试标准程序:
1 |
|
虽然有警告,但我们的测试还是成功了
接着我们测试除 iaddq
以外的其他指令:
1 |
|
全部成功
最后我们测试 iaddq
指令:
1 |
|
同样成功
至此该实验的前两个 part
完成
Part C
参照 PDF 中
6
的部分
本阶段需要我们修改 ncopy.ys
和
pipe-full.hcl
,使得该代码能够尽可能运行的更快
我们首先需要修改 pipe-full.hcl
,使得其能够支持
iaddq
指令,这样我们可以少写一条汇编指令
修改部分如下:
1 |
|
这里与 Part B
基本一致,只要那个能做,这里也没问题
我们首先给出如下代码:
1 |
|
运行结果:
1 |
|
需要说明的几点:
- 这里我们循环展开的此时为
6
次,可以继续增大展开次数以此提升CPE
- 循环展开并不会降低总的操作次数,只会降低循环的次数,循环的索引计算和分支计算都会减少
- 在单个
Loop
中,我们连续对两个内存进行读取,随后再执行判断语句。这么做是为了消除加载/使用冒险- 如果前一条语句从内存当中读取某个值到寄存器,下一条马上使用该寄存器,那么流水线控制逻辑会在其中插入一个气泡。使得当前一条指令进入写回阶段时,后一条指令进入译码阶段
- 我们直接使用
len
是否小于零来进行循环次数的判断,这样可以执行更少的指令
我们从循环展开部分的代码可以得知,对单个元素进行操作时,必须要执行五条汇编指令
上面代码的问题是,我们六路循环展开后会得到一些剩余元素,元素个数为从
0
到 5
,我们对这些元素采取的是循环的方式
我们知道,循环是会产生额外的判断和索引计算,因此对于最后的元素,根据其执行次数不同,我们可以将其倒序进行排列,然后依据剩余元素个数跳转到对应的位置即可
我们将循环展开的次数提升到八次,写出如下代码:
1 |
|
测试结果:
1 |
|
需要说明的是:
数据的输入个数为
0
到64
,并且我们是求每次算出来的CPE
的平均值,因此所有数量的权重相同,我们应当尽可能减小余数为0, 1, 2, 4
的CPE
,也就是让这些情况提前跳转在控制逻辑的跳转部分,我们先让
2
和3
进行跳转,随后才是1
,这样子总的CPE
会更小
可以看到这次的 CPE
有了很大的提升,我们将循环展开增加到
10
次
1 |
|
测试结果:
1 |
|
上面的代码中,在
1 |
|
当中还是插入了一个气泡,这是因为第一条语句对 %r8
进行写入后,第二条语句马上就要使用
我们必须在 mrmovq
到达访存阶段时,才能执行
andq
的译码阶段
我们考虑去消除这个气泡,由于每一个循环必须要执行 5
条指令,因此如果我们当前这个循环执行的是上一条语句的跳转指令的话,也就可以避免当前循环中跳转指令与访存指令的冲突
这样有一个问题是,当前周期会执行一次上一周期的跳转,为了避免执行该额外的跳转,我们的控制逻辑需要保证每次跳转的状态码都是小于等于零
1 |
|
测试结果:
1 |
|
这个代码的长度达到了 999
个字节,已经是达到极限了
对应的 CPE
也达到了 7.6
,距离满分的
7.5
只差
0.1
,如果需要继续优化的话,只能从控制逻辑上下手
去寻找一个更优的控制逻辑,或者尝试去实现对 pipe-ful.hcl
进行修改,实现条件传送指令
尾声
在我们对 psim
执行测试时,有如下输出:
1 |
|
但我们最后显示 600
条 ISA
全部测试成功
在 Part B
中我们知道,这里只有 600
条指令,全部测试成功表示通过,并且我们在 ncopy.ys
中使用
je, jl, jg
并没有报错
也就是这三条指令是被正确执行的,这里显示错误我确实十分的疑惑,但我确实不知道为什么,所以只能留在这里了