Skip to content

Latest commit

 

History

History
298 lines (237 loc) · 12 KB

archlab.md

File metadata and controls

298 lines (237 loc) · 12 KB

Architecture Lab (Y86-64) 笔记

这个 lab 涉及了 Y86-64 的实现。题目难度不大,做题的主要困难在实验环境安装和测试,做之前要仔细阅读文档
首先建立实验环境,解压 sim 包,这里是所使用的工具,需要 make ,刚开始我总是出错,后来从网上找到如下解决方法: 修改Makefile文件( sim 目录下),注释掉(因为 ubuntu 没有安装相关库,这样模拟器就不支持 GUI 界面了):

#GUIMODE=-DHAS_GUI
#TKLIBS=-L/usr/lib -ltk -ltcl
#TKINC=-isystem /usr/include

然后

unix> make clean;make

如果仍出错,可能是没装相关的依赖软件,安装 flexbison 试试。
这次 lab 分了三个部分: Part A 、B 、C
Part A 要求将 C 代码翻译成 Y86-64 ,Part B 要求实现 SEQ 命令,这两部分都为 Part C 做铺垫,Part C 要求修改 PIPE 和 Y86-64 实现程序的优化。

参考资料:
Seterplus/CSAPP
CSAPP:Architecture Lab - IT閱讀
csapp archlab 模拟器安装
深入理解计算机系统:体系结构实验

Part A

要求将 C 代码翻译成 Y86-64,工作目录在 ../sim/misc 中,第一题题解在 sum.ys 中,是 Y86-64 形式的汇编代码,使用 make sum.yo./yas sum.ys 编译,使用 ./yis sum.yo 查看模拟器运行结果。

第一题

第一题要求写个计算链表和的函数,给了该函数的 C 代码。
可以考虑将题目的 C 代码用 gccobjdump 翻译成汇编代码,然后再将汇编翻译成 Y86-64 ;这里程序较简单,可以直接写出代码。
下面详细讲解第一题的题解。(这份题解参考了不少资料)
首先是指令的开头:

# Execution begins at address 0
.pos 0
irmovq stack, %rsp
# Set up stack pointer

设置栈指针,栈的位置在最后一行设置了:

# Stack starts here and grows to lower addresses
.pos 0x200
stack:

然后调用 main ,结束:

        call main
# Execute main program
        halt
# Terminate program

这是整个代码的框架;

# Sample linked list
        .align 8
  ele1:
        .quad 0x00a
        .quad ele2
  ele2:
        .quad 0x0b0
        .quad ele3
  ele3:
        .quad 0xc00
        .quad 0

上面定义了链表,并赋了初值;

main:
        irmovq ele1,%rdi # 传参数值
        call sumlist     # 调用函数
        ret

这是 main 的定义,其中调用了 sumlist 函数,该函数定义为如下:

sumlist:
        xorq    %rax,%rax     # 设置 sum 初值为 0,long val = 0
# sum = 0 
        andq    %rdi , %rdi   # 判断 ls (就是链表指针,最初为传入值,链表的首地址)是否为 0
        je  end               # ls 为 0 时直接返回
loop:   mrmovq  (%rdi) , %rcx # 循环: 保存 ls->val
        addq    %rcx , %rax   # 给 sum 累加值,就是 val += ls->val
        irmovq  $8 , %rbx     # 保存 8
        addq    %rbx , %rdi   # 链表指针加 8 ,就是 &(ls->next)
        mrmovq (%rdi),  %rdi  # ls=ls->next
        andq    %rdi , %rdi   # 判断 ls 是否为 0
        jne loop              # ls 不为 0 时循环
end:
        ret                   # 返回

可以看到题解就是直接翻译了 C 代码,运行测试,模拟器得到了结果:

Stopped in 31 steps at PC = 0x13.  Status 'HLT', CC Z=1 S=0 O=0
Changes to registers:
%rax:	0x0000000000000000	0x0000000000000cba
%rcx:	0x0000000000000000	0x0000000000000c00
%rbx:	0x0000000000000000	0x0000000000000008
%rsp:	0x0000000000000000	0x0000000000000200

Changes to memory:
0x01f0:	0x0000000000000000	0x000000000000005b
0x01f8:	0x0000000000000000	0x0000000000000013

第二题

这题要求实现第一题的递归版本,题解在 rsum.ys 中,同样直接翻译 C 代码,但是要实现递归时一定要注意分清哪些变量应该是 调用者保存的
下面是递归版本的 rsumlist 函数的实现:

rsumlist:
        pushq   %rcx          # val: 是调用者保存的寄存器
        andq    %rdi , %rdi   # 判断条件
        je  end               # 直接返回
        mrmovq  (%rdi) , %rcx # ls->val
        irmovq  $8 , %rbx     
        addq    %rbx , %rdi
        mrmovq  (%rdi),  %rdi # ls->next
        call    rsumlist      # 递归
        addq    %rcx , %rax   # return val+rest;
end:
        popq    %rcx
        ret

测试得到 sum 和第一题相同。

第三题

实现一个将源数组(src)复制到目标数组(dest)的函数,并计算原数组中所有项的异或(Xor)值,这题的题解保存在 copy_block.ys 中,和前两题一样,直接翻译原函数。
这里调用函数时要分别传入三个值,实现 main 如下:

main:
        irmovq src , %rdi
        irmovq dest , %rsi
        irmovq $3 , %rdx
        call   copyblock
        ret

调用的函数实现如下:

copyblock:
        xorq    %rax , %rax    # long result = 0;
loop:                          # 循环
        andq    %rdx , %rdx    # len
        jle     end            # 判断 len 是否 > 0
        mrmovq  (%rdi) , %rcx  # long val = *src;
        irmovq  $8 , %rbx      
        addq    %rbx , %rdi    # src++;
        rmmovq  %rcx , (%rsi)  # *dest = val;
        addq    %rbx , %rsi    # dest++;
        xorq    %rcx , %rax    # result ˆ= val;
        irmovq  $1 , %rbx      
        subq    %rbx , %rdx    # len--;
        jmp     loop
end:
        ret

测试得到结果:

Stopped in 45 steps at PC = 0x13.  Status 'HLT', CC Z=1 S=0 O=0
Changes to registers:
%rax:	0x0000000000000000	0x0000000000000cba
%rcx:	0x0000000000000000	0x0000000000000c00
%rbx:	0x0000000000000000	0x0000000000000001
%rsp:	0x0000000000000000	0x0000000000000200
%rsi:	0x0000000000000000	0x0000000000000048
%rdi:	0x0000000000000000	0x0000000000000030

Changes to memory:
0x0030:	0x0000000000000111	0x000000000000000a
0x0038:	0x0000000000000222	0x00000000000000b0
0x0040:	0x0000000000000333	0x0000000000000c00
0x01f0:	0x0000000000000000	0x000000000000006f
0x01f8:	0x0000000000000000	0x0000000000000013

Part B

工作目录在 sim/seq 中,要求将 SEQ 扩展以支持 iaddq 指令(该指令在书上家庭作业的 4.51 和 4.52 说明了,第二版书在 4.48 和 4.50 上,我这里的文档只要求实现 iaddq ,参考资料里是二版的题,还实现了 leave 指令),该指令要求一步实现将常数值添加到目的寄存器。做法就是修改目录下的 seq-full.hcl ,它实现了一个和书上一样的 SEQ 。
参考书上 OP1mrmovl 的实现,可以写出 iaddq 的实现步骤如下:
iaddq A , rB

icode:ifun <-- M1[PC] # 取指
rA:rB <-- M1[PC+1]    
valC <-- M8[PC+2]  
valP <-- PC+10

valB <-- R[rB]        # 译码

valE <-- valB + valC  # 执行

R[rB] <-- valE        # 写回
PC <-- valP           # 更新 PC

然后根据实现修改 hcl 代码:

# 取指
# 指令是否有效?
bool instr_valid = icode in
        { INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ, IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ,IIADDQ };

# Does fetched instruction require a regid byte?
bool need_regids =
        icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ, IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ };

# Does fetched instruction require a constant word?
bool need_valC =
        icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL, IIADDQ };

# 译码和写回,指定读入和写入
## What register should be used as the B source?
word srcB = [
        icode in { IOPQ, IRMMOVQ, IMRMOVQ ,IIADDQ } : rB;
        icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
        1 : RNONE;  # Don't need register
];

## What register should be used as the E destination?
word dstE = [
        icode in { IRRMOVQ } && Cnd : rB;
        icode in { IIRMOVQ, IOPQ,IIADDQ } : rB;
        icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
        1 : RNONE;  # Don't write any register
];

# 执行
## Select input A to ALU
word aluA = [
        icode in { IRRMOVQ, IOPQ } : valA;
        icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IIADDQ } : valC;
        icode in { ICALL, IPUSHQ } : -8;
        icode in { IRET, IPOPQ } : 8;
        # Other instructions don't need ALU
];

## Select input B to ALU
word aluB = [
        icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL, IPUSHQ, IRET, IPOPQ, IIADDQ } : valB;
        icode in { IRRMOVQ, IIRMOVQ } : 0;
        # Other instructions don't need ALU
];

## Should the condition codes be updated?
bool set_cc = icode in { IOPQ, IIADDQ };

完成后 make ,注意如果无法使用 gui 界面修改 Makefile ,注释掉相关内容,使用官方文档所给的命令 unix> ./ssim -t ../y86-code/asumi.yo 在命令行下测试得到 ISA Check Succeeds ,再用 unix> (cd ../y86-code; make testssim)unix> (cd ../ptest; make SIM=../seq/ssim) 测试均成功。

Part C

这部分工作目录在 sim/pipe 下,题目给定了 ncopy 函数的 C 代码,这个函数和 Part A 的第三题差不多,将 src 数组复制到 dest 数组,并返回数组中的正数的总数。题目还给定了这个函数的 Y86-64 代码,并在文件 pipe-full.hcl 中实现了一个包含 IIADDQ 常量的 PIPE 。题目要求修改 ncopy.yspipe-full.hcl ,使得 ncopy.ys 运行得尽可能快。
这题的测试命令比较多,按需要参考官方文档:
使用 unix> make VERSION=full 重建测试环境;
然后使用 unix> ./psim -t sdriver.younix> ./psim -t ldriver.yo 在命令行下模拟运行和测试 PIPE 是否正确,同时得到 CPI ;
使用 unix> ./correctness.pl 测试 ncopy.ys 代码是否正确;
使用 unix> ./benchmark.pl 自动测试得到平均 CPE 。
最初用给定的代码测试得到 CPI ( cycles per instruction ) 为 1.14 ,平均 CPE 约为 15.18 。这里要拿满分平均 CPE 应在 7.5 以下。
首先和 Part B 一样实现 iaddq 指令,此时平均 CPE 降为 13.70 ,将减法改为 iaddq 得到 CPE 为 12.70 由于 iaddq 会更新状态码,可以利用它减少一个比较指令,得到 CPE 为 11.70 。
考虑将循环展开,这里展开八次,CPE 降为 8.81(这里有个问题,使用 %r15 寄存器会出现错误,不知道什么原因),最终优化的函数保存在 ncopy.ys 中,通过了所有的测试,得分为 33.9/60.0 。得分有点低,说明还有很大的优化空间,进一步优化的尝试参见附[1]。

小结

这个 lab 难度不大,但是上手不易,开始看到文档内容那么多,一度想放弃。上手要看懂文档,准备好安装环境,还要熟悉很多测试命令,可以参考他人经验快速上手。熟悉后,题目和之前几个 lab 相比就简单多了,这里难题主要在 Part C ,Part C 还很开放,要得高分不容易,还很耗时间。
通过这个 lab ,可以熟悉 Y86-64 指令集,对 SEQ 和 PIPE 的实现方式也能有更深的理解。

附:

[1]可以很容易看出,循环展开之后的余项可能有八位之多,对性能造成了较大的影响,可以进一步将余项循环展开四次。这里我写了一个四次展开的版本,保存在 4ncopy.ys 中,也保存了一份八次展开的版本在 8ncopy.ys 中,这两个版本都通过了测试,将八次展开和四次展开综合起来,就得到一个优化版本,保存在 mncopy.ys 中,但是这个版本只能部分通过测试,这个 bug 太玄学了。。经过近两小时的挣扎,我最终放弃治疗,有时间再看吧。