这是 CSAPP Lab 记录的第四弹————ArchLab。
有参考 https://www.caiwen.work/post/csapp-4
从 https://csapp.cs.cmu.edu/3e/labs.html 上下载 https://csapp.cs.cmu.edu/3e/archlab-handout.tar,参考资料 https://csapp.cs.cmu.edu/3e/archlab.pdf。
CSAPP 定义了一套类似 x86-64 的指令集架构 Y86-64。
从 0x0 到 0xf 有 15 个寄存器,分别是 %rax, %rcx, %rdx, %rbx, %rsp, %rbp, %rsi, %rdi, %r8, %r9, %r10, %r11, %r12, %r13 和 %r14,0xf 代表无寄存器。
有以下指令:
halt->0 0nop->1 0rrmovq rA, rB->2 0 rA rBirmovq V, rB->3 0 F rB Vrmmovq rA, D(rB)->4 0 rA rB Dmrmovq D(rB), rA->5 0 rA rB DOPq rA, rB->6 fn rA rBjXX Dest->7 fn DestcmovXX rA, rB->2 fn rA rBcall Dest->8 0 Destret->9 0push rA->A 0 rA Fpop rA->B 0 rA F
V和D是 8 字节的立即数,fn是功能码,F代表无寄存器。
其中操作指令 OPq 包括:
addq->6 0subq->6 1andq->6 2xorq->6 3
分支指令 jXX 包括:
jmp->7 0jle->7 1jl->7 2je->7 3jne->7 4jge->7 5jg->7 6
移动指令 cmovXX 包括:
rrmovq->2 0cmovle->2 1cmovl->2 2cmove->2 3cmovne->2 4cmovge->2 5cmovg->2 6
此外还有条件寄存器:
ZF-> zero flag(当结果为零时置位)SF-> sign flag(当结果为负数时置位)OF-> overflow flag(当结果溢出时置位)
还有 PC 寄存器,保存下一个指令的地址。
逻辑门没有状态,只有输入和输出,但运算是有状态的,可以用寄存器来实现状态的保存。
但与此同时,逻辑门的耗时并不相同:假如有 xor 门,输入 A 的逻辑电路耗时 1s 输出为 1,输入 B 的逻辑电路耗时 2s 输出为 1(在前 2s 还是 0),那么在 1.5s 的时候,xor 门的输出会是 1 xor 0 = 1,在 2s 的时候,输出才会变成 1 xor 1 = 0。
设计寄存器在控制端高电平时触发采样,低电平即使输入端变更了也不更新寄存器的值。控制端接上脉冲电路,让周期为最长的逻辑门耗时,使得所有的输入都稳定了之后再更新寄存器的值。这就是时序电路。
形象来说,就是在电路稳定时,在寄存器内保存快照。
将指令都分为六个阶段:
-
取指(Fetch):根据 PC 寄存器中的地址,从内存中取出吓一跳要执行的指令
-
译码(Decode):按照指令,取出指令中指定的寄存器的值
-
执行(Execute):根据指令的功能码,执行相应的运算(包括条件寄存器、计算有效地址、计算栈指针)
-
访存(Memory):RAW,读取内存和写入内存
-
写回(Write Back):将结果写回寄存器堆
-
更新 PC(PC Update):根据指令的类型,更新 PC 寄存器的值
需要六个周期才能完成一条指令的执行。
CSAPP 用自己设计的 HCL 语言来设计 Y86-64 的时序电路。
要具体设计这六个阶段,首先要考虑以上的各个指令在各个阶段做了什么。
各个指令的设计是:
| 阶段 | OPq rA, rB |
rrmovq rA, rB |
irmovq V, rB |
|---|---|---|---|
| 取指 | |||
| 译码 | |||
| 执行 | Set CC |
||
| 访存 | |||
| 写回 | |||
| 更新 PC |
| 阶段 | pushq rA |
popq rA |
|---|---|---|
| 取指 | ||
| 译码 | ||
| 执行 | ||
| 访存 | ||
| 写回 | ||
| 更新 PC |
| 阶段 | jXX Dest |
call Dest |
ret |
|---|---|---|---|
| 取指 | |||
| 译码 | |||
| 执行 | |||
| 访存 | |||
| 写回 | |||
| 更新 PC |
| 阶段 | cmovXX rA, rB |
rmmovq rA, D(rB) |
mrmovq D(rB), rA |
|---|---|---|---|
| 取指 | |||
| 译码 | |||
| 执行 | |||
| 访存 | |||
| 写回 | |||
| 更新 PC |
那么按照以上来设计各个阶段:
TODO 咕咕咕
接下来是 ArchLab,分为 ABC 三个 Part。
A 简单,B 也不太难,C 很大坨
首先应该解压 sim.tar tar xvf sim.tar,然后 cd sim && make clean && make 构建所需的 Y86-64 工具,接下来的任务都在 sim 目录下完成。
如果缺少依赖报错可以安装以下软件包:
1
2
3 sudo apt install tcl tcl-dev tk tk-dev
sudo apt install flex
sudo apt install bison
如果提示类似
1
2 /usr/bin/ld: yas.o:/home/z0z0r4/CSAPP-LAB/archlab/sim/misc/yas.h:13: multiple definition of `lineno';
yas-grammar.o:(.bss+0x0): first defined here这个重复定义报错,是由于 GCC 10 之前默认
-fcommon选项导致的,GCC 10 之后默认-fno-common,解决方法是将-fcommon添加到 Makefile 的 CFLAGS 和 LLCFLAGS 中。(注意不止一个地方需要添加)
源码兼容的是 tk8.5,但是现在 apt 只有 8.6,有 breaking change,可以参考 tk.h looks for tcl.h in /usr/include but tcl.h is in /usr/include/tcl. I don’t have write tk.h privilege to fix code 或者从源码编译安装 tk8.5 tcltk downloadnow85(但还不如直接不用 GUI 了…如果不需要 GUI 则可以在 Makefile 中注释掉
GUIMODE、TKLIBS、TKINC。)
Part A
Part A 有要实现下面三个函数:
sum_list: 计算链表元素的和rsum_list: 递归计算链表元素的和copy_block: 将 src 中的内容复制到 dest 中,并返回 src 的异或校验和
examples.c
1 | /* |
以下是实现,我本来想参考 gcc -S 生成的汇编代码来写,但发现它生成的代码也挺难看的,还是对照下指令集开始写了。
sum_list
ans-sum.ys
1 | .pos 0 |
注意:先
./yas ans.ys检查、编译,然后再./yis ans.yo运行。
./yis ans-sum.yo 运行结果
1 | ❯ ./yis ans-sum.yo |
rsum_list
注意多了压栈弹栈。
ans-rsum.ys
1 | .pos 0 |
./yis ans-rsum.yo 运行结果
1 | ❯ ./yis ans-rsum.yo |
copy_block
注意不能直接 addq $8, %rdi,因为 addq 只能是寄存器相加,所以需要先 irmovq $8, %r9,再 addq %r9, %rdi。
ans-copy.ys
1 | .pos 0 |
./yis ans-copy.yo 运行结果
1 | ❯ ./yis ans-copy.yo |
Part B
要实现 iaddq V, %rB 指令,功能是将立即数 V 加到寄存器 rB 中,并更新条件码。
可以参考 irmovq 和 addq 来实现。
| 阶段 | OPq rA, rB |
irmovq V, rB |
iaddq V, rB |
|---|---|---|---|
| 取指 | |||
| 译码 | |||
| 执行 | Set CC |
Set CC |
|
| 访存 | |||
| 写回 | |||
| 更新 PC |
按照以上设计更改 sim/seq/seq-full.hcl
sim/seq/seq-full.hcl
1 | #/* $begin seq-all-hcl */ |
然后 sim/ptest/README 中有讲到如何测试:
1 | ❯ make SIM=../seq/ssim TFLAGS=-h |
iaddq implemented successfully!
Part C
咕咕咕!
说些什么吧!