Introduction
在本lab中,您将学习流水线Y86处理器的设计和实现,同时优化它和基准程序以最大化性能。 允许您保留对基准程序的任何语义转换,或者对流水线处理器进行增强,或者两者。 完成lab后,您将对影响程序性能的代码和硬件之间的交互的理解有很大的提升。 lab分为三个part,每个part都有自己的练习。 在part A中,您将编写一些简单的Y86程序并熟悉Y86工具。 在part B中,您将使用两个新指令扩展SEQ仿真器。 这两part将为您准备Part C(lab的核心)做好准备,在part C中,您将优化Y86基准程序和处理器设计。
Handout Instructions
1.首先将文件archlab-handout.tar复制到计划在其中进行工作的(受保护)目录。
2.然后输入命令:tar xvf archlab-handout.tar。 这将导致以下文件解压缩到目录中:README,Makefile,sim.tar,archlab.ps,archlab.pdf和simguide.pdf。
3.接下来,输入命令tar xvf sim.tar。 这将创建目录sim,其中包含您的Y86工具的个人副本。 您将在此目录中进行所有工作。
4.最后,转到sim目录并构建Y86工具:
Part A
在这个part,您将在目录sim / misc中工作。
您的任务是编写和模拟以下三个Y86程序。 这些程序的所需行为由examples.c中的示例C函数定义。 确保在每个程序的开头都将您的姓名和ID放在注释中。 您可以通过以下方式测试您的程序:首先使用程序YAS对其进行汇编,然后使用指令集模拟器YIS运行它们。
在所有Y86函数中,您都应遵循IA32约定,以了解栈帧的结构和寄存器使用说明,包括保存和恢复您使用的任何被调用这保护寄存器。
sum.ys: Iteratively sum linked list elements
编写一个Y86程序sum.ys,以迭代方式求和链表的元素。 您的程序应包含一些代码,这些代码可以设置栈结构,调用一个函数然后停止。 在这种情况下,该函数应该是功能上与图1中的e C sum list等效的函数(sum_list)的Y86代码。请使用以下三元素列表来测试程序:
代码如下:
.pos 0
# 初始化esp和ebp指针
irmovl stack, %esp
irmovl stack, %ebp
call main
halt
# 声明链表
.align 4
ele1:
.long 0x00a
.long ele2
ele2:
.long 0x0b0
.long ele3
ele3:
.long 0xc00
.long 0
main:
# 调用的栈帧操作
pushl %ebp
rrmovl %esp, %ebp
irmovl ele1, %ebx # 参数ls = ele1
pushl %ebx # 参数压栈
call sum_list
popl %ebx
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
sum_list:
# 调用的栈帧操作
pushl %ebp
rrmovl %esp, %ebp
xorl %eax, %eax # val = 0
irmovl $4, %ebx # 设置常数4
mrmovl 8(%ebp), %ecx # 获取参数
jmp test:
loop:
mrmovl (%ecx), %edx # ls->val
addl %edx, %eax # val += ls->val
addl %ebx, %ecx # &(ls->next)
mrmovl (%ecx), %ecx # ls = ls->next
test:
andl %ecx, %ecx # 设置条件码为参数ls
jne loop
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
.pos 0x200
stack:
运行结果如下。
rsum.ys: Recursively sum linked list elements
编写一个Y86程序rsum.ys,该程序递归求和链表的元素。 此代码应与sum.ys中的代码相似,不同之处在于它应使用函数rsum list递归求和一个数字列表,如图1中的C函数rsum list所示。 使用同样的三元素列表测试代码。
.pos 0
# 初始化esp和ebp指针
irmovl stack, %esp
irmovl stack, %ebp
call main
halt
# 声明链表
.align 4
ele1:
.long 0x00a
.long ele2
ele2:
.long 0x0b0
.long ele3
ele3:
.long 0xc00
.long 0
main:
# 调用的栈帧操作
pushl %ebp
rrmovl %esp, %ebp
irmovl ele1, %edi # 参数ls = ele1
pushl %edi # 参数压栈
call rsum_list
popl %edi
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
rsum_list:
# 调用的栈帧操作
pushl %ebp
rrmovl %esp, %ebp
mrmovl 8(%ebp), %ebx # 获取参数
andl %ebx, %ebx # 设置条件码为参数ls
jne recursive # !ls则递归
irmovl $0, %eax
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
recursive:
mrmovl (%ebx), %ecx # val = ls->val
irmovl $4, %edx # 常数4
addl %edx, %ebx # &(ls->next)
mrmovl (%ebx), %ebx # ls->next
pushl %ecx # 保存val
pushl %ebx # 参数压栈
call rsum_list
popl %ebx
popl %ecx # 恢复val的值
addl %ecx, %eax # return val + rest
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
.pos 0x200
stack:
运行结果如下。
copy.ys: Copy a source block to a destination block
编写一个程序(copy.ys),将一个word块从内存的一个部分复制到内存的另一个(不重叠区域)区域,计算所有复制word的校验和(Xor)。
您的程序应由设置栈帧,调用功能复制块然后停止的代码组成。 该函数在功能上应等效于图1所示的C函数复制块。使用以下三元素的源块和目标块测试程序:
代码如下:
.pos 0
# 初始化esp和ebp指针
irmovl stack, %esp
irmovl stack, %ebp
call main
halt
#声明测试数组
.align 4
src:
.long 0x00a
.long 0x0b0
.long 0xc00
dest:
.long 0x111
.long 0x222
.long 0x333
main:
# 调用的栈帧操作
pushl %ebp
rrmovl %esp, %ebp
irmovl src, %ebx # 参数src
irmovl dest, %ecx # 参数dest
irmovl $3, %edx # 参数len
pushl %edx
pushl %ecx
pushl %ebx # 参数压栈
call copy_block
popl %ebx
popl %ecx
popl %edx
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
copy_block:
# 调用的栈帧操作
pushl %ebp
rrmovl %esp, %ebp
xorl %eax, %eax # result = 0
mrmovl 8(%ebp), %ebx # src参数
mrmovl 12(%ebp), %ecx # dest参数
mrmovl 16(%ebp), %edx # len参数
jmp test:
loop:
mrmovl (%ebx), %edi # val = *src
irmovl $4, %esi # 常数4
addl %esi, %ebx # src++
rmmovl %edi, (%ecx) # *dest = val
addl %esi, %ecx # dest++
xorl %edi, %eax # result ^= val
irmovl $1, %esi # 常数1
subl %esi, %edx # len--
test:
andl %edx, %edx # 设置条件码为len
jne loop
# 返回的栈帧操作
rrmovl %ebp, %esp
popl %ebp
ret
.pos 0x200
stack:
结果如下:
Part B
在这个part,您将在目录sim / seq中进行工作。
在part B,您的任务是扩展SEQ处理器以支持两个新指令:iaddl(在作业问题4.47和4.49中描述)和leave(在作业问题4.48和4.50中描述)。 为了添加这些说明,您将修改文件seq-full.hcl,该文件实现CS:APP2e教科书中描述的SEQ版本。 此外,它包含解决方案所需的一些常量的声明。
您的HCL文件必须以包含以下信息的标题注释开头:
•您的name和id。
•iaddl指令所需的计算的描述。 使用CS:APP2e文本中图4.18中对irmovl和OP1的描述作为参考。
•leave指令所需的计算的描述。 使用CS:APP2e文本中图4.20中popl的描述作为参考。
先是iaddl和leave的描述注释:
# id:victorika
# name:wwq
#iaddl:
# fetch: icode:ifunc <-- M1[PC]
# rA:rB <-- M1[PC + 1]
# valC <-- M4[PC + 2]
# valP <-- PC + 6
# decode: valB <-- R[rB]
# execute: valE <-- valC + valB
# Set CC
# memory:
# write back: R[rB] <- valE
# PC update: PC <-- valP
#
#leave:
# fetch: icode:ifunc <-- M1[PC]
# valP <-- PC + 1
# decode: valA <-- R[%ebp]
# execute: valE <-- 4 + valA
# memory: valM <-- M4[valA]
# write back: R[%esp] <-- valE
# R[%ebp] <-- valM
# PC update: PC <-- valP
然后添加指令,修改的代码如下:
bool instr_valid = icode in
{ INOP, IHALT, IRRMOVL, IIRMOVL, IRMMOVL, IMRMOVL,
IOPL, IJXX, ICALL, IRET, IPUSHL, IPOPL, IIADDL, ILEAVE};
bool need_regids =
icode in { IRRMOVL, IOPL, IPUSHL, IPOPL,
IIRMOVL, IRMMOVL, IMRMOVL, IIADDL};
bool need_valC =
icode in { IIRMOVL, IRMMOVL, IMRMOVL, IJXX, ICALL, IIADDL };
int srcA = [
icode in { IRRMOVL, IRMMOVL, IOPL, IPUSHL } : rA;
icode in { IPOPL, IRET } : RESP;
icode in { ILEAVE } : REBP;
1 : RNONE; # Don't need register
];
int srcB = [
icode in { IOPL, IRMMOVL, IMRMOVL, IIADDL } : rB;
icode in { IPUSHL, IPOPL, ICALL, IRET } : RESP;
1 : RNONE; # Don't need register
];
int dstE = [
icode in { IRRMOVL } && Cnd : rB;
icode in { IIRMOVL, IOPL, IIADDL} : rB;
icode in { IPUSHL, IPOPL, ICALL, IRET, ILEAVE } : RESP;
1 : RNONE; # Don't write any register
];
int dstM = [
icode in { IMRMOVL, IPOPL } : rA;
icode in { ILEAVE } : REBP;
1 : RNONE; # Don't write any register
];
int aluA = [
icode in { IRRMOVL, IOPL } : valA;
icode in { IIRMOVL, IRMMOVL, IMRMOVL, IIADDL } : valC;
icode in { ICALL, IPUSHL } : -4;
icode in { IRET, IPOPL, ILEAVE } : 4;
# Other instructions don't need ALU
];
int aluB = [
icode in { IRMMOVL, IMRMOVL, IOPL, ICALL,
IPUSHL, IRET, IPOPL, IIADDL } : valB;
icode in { ILEAVE } : valA;
icode in { IRRMOVL, IIRMOVL } : 0;
# Other instructions don't need ALU
];
bool set_cc = icode in { IOPL, IIADDL };
bool mem_read = icode in { IMRMOVL, IPOPL, IRET, ILEAVE };
int mem_addr = [
icode in { IRMMOVL, IPUSHL, ICALL, IMRMOVL } : valE;
icode in { IPOPL, IRET, ILEAVE } : valA;
# Other instructions don't need address
];
这一步基本是不会错的。。所以要确保你的流程是对的,然后再写代码。。
构建和测试您的解决方案
修改完seq-full.hcl文件后,您将需要基于此HCL文件构建SEQ模拟器(ssim)的新实例,然后对其进行测试:
•构建新的模拟器。 您可以使用make来构建新的SEQ模拟器:
这将构建一个ssim版本,该版本使用您在seq-full.hcl中指定的控制逻辑。 要保存输入,可以在Makefile中分配VERSION = full。
•在一个简单的Y86程序上测试您的解决方案。 对于您的初始测试,我们建议在TTY模式下运行简单的程序,例如asumi.yo(测试iaddl)和asuml.yo(测试leave),并将结果与ISA仿真进行比较:
如果ISA测试失败,则应在GUI模式下通过单步执行模拟器来调试实现。
•使用基准程序重新测试您的解决方案。 一旦模拟器能够正确执行小程序,您就可以在../y86代码中的Y86基准程序上对其进行自动测试:
这将在基准程序上运行ssim,并通过将结果处理器状态与高级ISA仿真中的状态进行比较来检查正确性。 请注意,这些程序均未测试添加的指令。 您只需确保您的解决方案没有为原始说明注入错误。 有关更多详细信息,请参见文件../y86-code/README文件。
•执行回归测试。 一旦可以正确执行基准测试程序,则应在../ptest中运行大量的回归测试。 要测试除iaddl之外的所有内容并离开:
要测试iaddl的实现,请执行以下操作:
有关SEQ模拟器的更多信息,请参阅《 Y86处理器模拟器的CS:APP2e指南》(simguide.pdf)。
测试结果如下:
Part C
这个part您将在sim/pipe目录中工作。
图2中的ncopy函数将len个元素的整数数组src复制到一个不重叠的dst中,返回src中包含的正整数的个数。 图3显示了ncopy的基准Y86版本。 文件pipe-full.hcl包含PIPE的HCL代码的副本,以及常量值IIADDL的声明。
partC中的任务是修改ncopy.ys和pipe-full.hcl,以使ncopy.ys尽可能快地运行。
您将提交两个文件:pipe-full.hcl和ncopy.ys。 每个文件应以带有以下信息的标题注释开头:
•您的name和id。
•您的代码的高级描述。 在每种情况下,请描述修改代码的方式和原因。
Coding Rules
您可以自由地进行任何修改,但有以下限制:
•您的ncopy.ys函数必须适用于任意数组大小。 您可能会想通过简单地编写64个复制指令来为64个元素的数组硬连接解决方案,但这将是一个坏主意,因为我们将根据其在任意数组上的性能来对您的解决方案进行评分。
•您的ncopy.ys函数必须与YIS一起正确运行。 正确地说,我们的意思是它必须正确复制src块并返回(以%eax表示)正确数量的正整数。
•ncopy文件的汇编版本不得超过1000个字节。 您可以使用提供的脚本check-len.pl检查带有ncopy函数嵌入的任何程序的长度:
•您的pipe-full.hclimplementation必须通过../y86代码和../ptest中的回归测试(不需要用于测试iaddl并离开的-il标志)。
除此之外,如果您认为有帮助,可以自由实施iaddl指令。 您可以进行保留到ncopy.ys函数的任何语义转换,例如重新排序指令,用单个指令替换指令组,删除某些指令以及添加其他指令。 您可能会发现在CS:APP2e的5.8节中阅读有关循环展开的信息很有用。
Building and Running Your Solution
为了测试您的解决方案,您将需要构建一个调用ncopy函数的驱动程序。 我们为您提供了gen-driver.pl程序,该程序为任意大小的输入数组生成驱动程序。 例如,输入:
将构造以下两个有用的驱动程序:
•sdriver.yo:一个小型驱动程序,用于在具有4个元素的小型阵列上测试ncopy函数。 如果您的解决方案是正确的,则在复制src数组后,该程序将在寄存器%eax中以2的值停止运行。
•ldriver.yo:大型驱动程序,用于在具有63个元素的较大阵列上测试ncopy函数。 如果您的解决方案是正确的,则该程序将在复制src数组后以%eax寄存器中的值31(0x1f)暂停。
每次修改ncopy.ys程序时,都可以通过键入以下内容来重建驱动程序:
每次修改pipe-full.hcl文件时,您都可以通过键入以下内容来重建模拟器:
如果要重建模拟器和驱动程序,请键入:
要在小型4元素阵列上以GUI模式测试您的解决方案,请键入:
要在更大的63元素数组上测试解决方案,请输入:
一旦模拟器在这两个块长度上正确运行了您的ncopy.ys版本,您将需要执行以下附加测试:
•在ISA模拟器上测试驱动程序文件。 确保您的ncopy.ys函数可与YIS一起正常使用:
•使用ISA模拟器在一系列块长度上测试代码。 Perl脚本correctness.pl生成驱动程序文件,其块长度从0到某个限制(默认为65),再加上一些更大的大小。 它模拟它们(默认情况下为YIS),并检查结果。 它生成一个报告,显示每个块长度的状态:
该脚本生成测试程序,其结果计数从一次运行到另一次运行随机变化,因此它提供了比标准驱动程序更严格的测试。
如果对于某个长度K得出的结果不正确,则可以为该长度的驱动程序文件生成一个包含检查代码的驱动程序文件,并且结果随机变化:
该程序将以具有以下值的寄存器%eax结尾:
•在基准程序上测试管道模拟器。 一旦模拟器能够正确执行sdriver.ys和ldriver.ys,则应使用../y86代码的Y86基准程序对其进行测试:
这将在基准程序上运行psim并将结果与YIS进行比较。
•使用广泛的回归测试来测试管道模拟器。 一旦可以正确执行基准测试程序,则应使用../ptest中的回归测试对其进行检查。 例如,如果您的解决方案实现了iaddl指令,则
•使用管道模拟器在一系列块长度上测试代码。 最后,您可以在管道模拟器上运行与之前使用ISA模拟器进行的代码测试相同的代码
优化点:
1.首先用上一个part的iaddl指令加到这个part里,然后把其中的算数运算操作替换成iaddl,同时因为iaddl有set CC这个步骤,所以对应的andl指令也是不需要的。
2.循环展开,选择循环展开的因子是4,刚好给我卡了过去。
下面直接给汇编代码:
# Loop header
xorl %eax,%eax # count = 0;
iaddl $-3, %edx # len- = 3
jle next_start # if so, goto Done:
Loop1:
mrmovl (%ebx), %esi # read val from src...
mrmovl 4(%ebx), %edi # read val from src + 1
rmmovl %esi, (%ecx) # ...and store it to dst
rmmovl %edi, 4(%ecx) # store it to dst + 1
test1:
andl %esi, %esi # *src <= 0 ?
jle test2
iaddl $1, %eax # count++
test2:
andl %edi, %edi # *(src + 1) <= 0 ?
jle test3
iaddl $1, %eax # count++
test3:
mrmovl 8(%ebx), %esi # read val from src + 2
mrmovl 12(%ebx), %edi # read val from src + 3
rmmovl %esi, 8(%ecx) # store it to dst + 2
rmmovl %edi, 12(%ecx) # store it to dst + 3
andl %esi, %esi # *(src + 2) <= 0?
jle test4
iaddl $1, %eax # count++
test4:
andl %edi, %edi # *(src + 3) <= 0?
jle Npos
iaddl $1, %eax # count++
Npos:
iaddl $16, %ebx # src += 4
iaddl $16, %ecx # dst += 4
iaddl $-4, %edx # len -= 4
andl %edx,%edx # len > 0?
jg Loop1 # if so, goto Loop:
next_start:
iaddl $3, %edx # len += 3
jle Done
loop2:
mrmovl (%ebx), %esi # read val from src
rmmovl %esi, (%ecx) # store it to dst
andl %esi, %esi # *src <= 0 ?
jle test5
iaddl $1, %eax # count++
test5:
iaddl $4, %ebx # src += 1
iaddl $4, %ecx # dst += 1
iaddl $-1, %edx # len -= 1
jg loop2
长度检查。
答案检查。
性能检查。
本来我因子用了2,结果是10多一点,就试着扩大一下因子,就过去了。
Hints
•根据设计,驱动程序和驱动程序都足够小,可以在GUI模式下进行调试。 我们发现最容易在GUI模式下进行调试,建议您使用它。
•如果您在Unix服务器上以GUI模式运行,请确保已初始化DISPLAY环境变量:
•对于某些X服务器,当您在GUI模式下运行psim或ssim时,“程序代码”窗口以关闭的图标形式开始运行。 只需单击图标即可展开窗口。
•对于某些基于Microsoft Windows的X服务器,“内存内容”窗口不会自动调整其大小。 您需要手动调整窗口大小。
•如果您要求psim和ssim模拟器执行不是有效的Y86目标文件的文件,则会因段错误而终止。