计算机体系结构实验一
一.实验目的
理解RISC-V的指令执行的数据流和控制信号,熟悉指令流水线的工作过程。
二.实验过程
1.RISC-V的相关指令
实验的模拟器使用RISC-V指令集,为了便于后续分析,首先学习实验中使用的RISC-V指令。
基本RISC-V使用32位固定长度指令。但标准RISC-V编码模式支持变长指令,可以使用16位指令,这些指令被称为标准压缩指令,被命名为C。这些指令通常是常用操作,可以减少静态和动态的代码大小。以下所有形式为"C.inst"的指令都属于这类指令。
实验中的汇编代码主要包含以下几类指令,
- 控制转移指令:c.j,c.jr,bge
- 算数与逻辑运算指令:c.slli,c.addi
- 整数生成指令:c.li
- 访存指令:lw,sw
控制转移指令
c.j指令是一个无条件跳转指令,使用PC相对跳转,PC与偏移量相加形成跳转目标地址,等价于Jal指令,跳转后会将PC+4的值写入rd寄存器,对于c.j,rd=x0,而x0不可写入,相当于该指令只完成跳转。c.jr指令执行一个无条件控制跳转取出rs1的值并将最低位设置为0,作为跳转地址,等价于Jalr指令,同样在跳转后会将PC+4的值写入rd寄存器,但rd为x0。bge指令按照有符号数比较rs1和rs2寄存器的值,如果rs1大于等于rs2,就跳转到目标地址。
算数与逻辑运算指令
c.slli指令对rd中的值进行逻辑左移操作,并将结果写入rd,指令中的shamt字段为移位次数。c.addi,add等指令都是加法指令,只有操作数有不同。
整数生成指令
c.li指令将符号扩展的6位立即数写入寄存器rd,并且只在rd!=x0时有效。
访存指令
lw指令从存储器中读出以rs1+offset为地址的值,存入rd寄存器。sw指令则是把rs2的值写入存储器,地址为rs1+offset。
2.汇编代码分析
对实验所给出的代码转为汇编后的代码进行分析,可以找到两个循环的汇编指令部分。
第一个循环的部分如下:
1014c: fe042623 sw x0 -20 x8 #x8-20 : i
10150: a005 c.j 32 #-> 10170
10152: fec42783 lw x15 -20 x8
10156: 078a c.slli x15 2 #x15 = 4i
10158: ff040713 addi x14 x8 -16
1015c: 97ba c.add x15 x14
1015e: fec42703 lw x14 -20 x8
10162: e6e7a623 sw x14 -404 x15 #A[i] = i
#i++
10166: fec42783 lw x15 -20 x8
1016a: 0785 c.addi x15 1
1016c: fef42623 sw x15 -20 x8
#if(i<100)
10170: fec42703 lw x14 -20 x8
10174: 06300793 addi x15 x0 99
10178: fce7dde3 bge x15 x14 -38 #i<99 -> 10152
第二个循环的部分如下:
1017c: 4785 c.li x15 1
1017e: fef42623 sw x15 -20 x8 #x8-20: i
10182: a80d c.j 50 #-> 101B4
10184: fec42783 lw x15 -20 x8
10188: 17fd c.addi x15 -1
1018a: 078a c.slli x15 2
1018c: ff040713 addi x14 x8 -16
10190: 97ba c.add x15 x14
10192: e6c7a783 lw x15 -404 x15 #x15: A[i-1]
10196: 3e878713 addi x14 x15 1000
1019a: fec42783 lw x15 -20 x8
1019e: 078a c.slli x15 2
101a0: ff040693 addi x13 x8 -16
101a4: 97b6 c.add x15 x13
101a6: e6e7a623 sw x14 -404 x15 #A[i] = A[i-1]+1000
#i++
101aa: fec42783 lw x15 -20 x8
101ae: 0785 c.addi x15 1
101b0: fef42623 sw x15 -20 x8
#if(i<100)
101b4: fec42703 lw x14 -20 x8
101b8: 06300793 addi x15 x0 99
101bc: fce7d4e3 bge x15 x14 -56 #i<99 -> 10184
3.RISC-V电路分析
RISC-V的电路设计图如下:
取址阶段
逐个阶段进行电路的分析,首先分析取址阶段。取值阶段的NPC Generator产生下一条指令的地址,而地址来源于Jal跳转的地址计算,Jalr跳转的地址计算,分支的地址计算,通过控制单元和分支选择器的选择信号选择下一条指令的地址。以NPC产生的PC值为地址从指令存储器中取值,传递给译码阶段。NPC的PCF接入的是分支预测,对于BGE这种条件分支,如果预测选中,可以不用等到执行阶段判断条件,可以直接在ID阶段计算出跳转地址就写入NPC。
译码阶段
译码阶段完成的工作是产生控制信号和读取操作数,主要的工作单元为寄存器组和立即数运算单元。寄存器组的RegWriteW和WD3和A3是写回阶段的信号,这些信号是从译码阶段一直传递到写回阶段的。而立即数运算单元完成立即数的扩展,传递给执行阶段,并且在这里可以直接完成Jal跳转目标地址的计算,传给NPC generator,对于无条件跳转Jal,控制单元在这个阶段就可以发出控制信号JalD,进行跳转了。
执行阶段
执行阶段完成三个工作:选择操作数并完成ALU运算;分支选择;Jalr指令的目标地址计算。操作数的来源有ID阶段读取的寄存器值、EX阶段和MEM阶段的数据前推结果、立即数。所有的控制信号都是译码阶段控制单元产生后传递到执行阶段的各部件的。
分支选择器根据分支类型比较两个寄存器的值(可能来自取值阶段,也可能来自执行和访存阶段),选择是否进行分支跳转,并将分支信号发给NPC generator,由于分支选择是在这个阶段进行的,分支目标地址也在这个阶段传给NPC generator的,为EX输出的BrNPC,值为译码阶段得到的立即数。如果一开始动态预测未选中,然而分支计算结果又是选中,则预测错误,在EX这个阶段写入NPC跳转目标地址。
Alu运算从数据前推和译码阶段读取的寄存器值中选择操作数,控制信号Forward由harzard单元发出,如果当前写入的目的寄存器(上/前一条指令)和当前运算的源寄存器是相同的,就选择访存和写回阶段的结果。AluSrc信号选择操作数是寄存器/PC/立即数,AluContrl给出计算类型。如果是Jalr指令,将rs1的值的最低位设置为0后通过AluOut传递给NPC generator,进行跳转。
访存阶段
访存阶段的主要工作是存储器的读取和写入,同时还完成执行阶段结果的传递。写使能信号是译码阶段传递过来的。
对于运算类指令,执行阶段的结果直接传递到写回阶段,写入rd寄存器,而对于jal,jr这类指令,会把PC+4写入rd寄存器,控制单元译码时发出LoadNpc控制信号进行这二者之间的选择。
写回阶段
写回阶段在访存结果和ALU运算/PC+4这两个结果之间选择,传输给寄存器组,MemToReg为选择信号,由控制单元产生并传递到写回阶段。
控制单元
控制单元如下:
其中部分信号的功能如下:
-
RegWriteD:寄存器组的写使能信号
-
MemToRegD:寄存器组写入的数据是否来自存储器
-
LoadNpcD:是否计算PC+4存入rd寄存器
-
RegReadD:是否读取寄存器,传给harzard单元
-
BranchTypeD:表明分支类型
-
AluSrc1D,AluSrc2D:ALU操作数的来源选择信号,Alusrc1D可选来源有寄存器值、PC值;Alusrc2D可选来源有寄存器值、立即数
-
AluContrlD:ALU的具体运算选择信号
-
ImmType:立即数的类型(长度)
4.指令的执行过程
a.add x15, x14, x15的执行过程
该指令位于:
10188: 17fd c.addi x15 -1
1018a: 078a c.slli x15 2
1018c: ff040713 addi x14 x8 -16
10190: 97ba c.add x15 x14
取指阶段,PC值为10190,读取该地址的指令,将指令传送到ID阶段。关键控制信号:
- JalrE,JalE,BrE:均为非使能状态,因为前几条指令没有分支与跳转
译码阶段,A1的值为0xE,表示x14寄存器,A2的值为0xF,表示x15寄存器。从寄存器组中读取这两个寄存器的值,将值传递给EX阶段,同时还会将add指令相应的控制信号传递到EX阶段。此时寄存器组的写入控制信号为前一条指令的写入信号,对于add指令来说,该阶段没有重要的控制信号。
执行阶段,寄存器的值从RegOut1E和RegOut2E进入选择器,由于操作数x14是上一条指令的结果,选择的是上一条指令访存阶段前推的值;操作数x15是前一条指令的结果,选择写回阶段前推的结果。AluContrlD选择加法运算,将两个操作数相加。关键控制信号:
- Forward1E:选择访存阶段前推的值作为寄存器读取值
- ForWard2E:选择写回阶段前推的值作为寄存器读取值
- AluSrc1E、AluSrc2E:都选择寄存器的值
- AluContrlD:ADDOP信号,ALU进行加法运算
访存阶段,由于不需要访存,MemWriteM信号处于非使能状态,写入的数据为ALU运算结果,而不是PC+4的值,关键信号为:
- LoadNPCM:非使能,选择ALU运算结果传递到写回阶段
写回阶段,MemToRegW为非使能,选择器选择ALU运算的结果输出给寄存器组的WD3接口,寄存器组的A3为写入寄存器的地址(0xF),RegWriteW信号也一直传递下来,接入寄存器组并为写使能,向x15寄存器写入add指令的加法结果。关键信号:
- RegWriteW:使能状态
- MemToRegW:非使能状态,选择ALU运算结果
其整个过程的数据通路为:
b.bge x15, x14, -56指令的执行过程
该指令及前两条指令为:
101b4: fec42703 lw x14 -20 x8
101b8: 06300793 addi x15 x0 99
101bc: fce7d4e3 bge x15 x14 -56
取指阶段关键信号为:
- JalrE,JalE,BrE:均为非使能状态,因为前几条指令没有分支与跳转
译码阶段,A1的值为0xF,表示x15寄存器,A2的值为0xE,表示x14寄存器。从寄存器组中读取这两个寄存器的值,将值传递给EX阶段,并计算PC-56的值传递给EX阶段,也传递给NPC,作为分支目标地址。此时PCF预测是否进行跳转。如果进行跳转,则写入NPC。
执行阶段,x14和x15的值都是前两条指令更新的,因此选择访存和写回阶段的值,操作数选择寄存器的值,分支选择器比较两值判断是否需要进行跳转。关键信号为:
- Forward1E:选择访存阶段前推的值作为寄存器读取值
- ForWard2E:选择写回阶段前推的值作为寄存器读取值
- AluSrc1E、AluSrc2E:都选择寄存器的值
- BrType,BrE:bge类型分支,比较两个操作数的值,如果op1>=op2,BrE使能,应该进行跳转,否则不应该跳转
如果预测跳转且满足分支条件发生了跳转,流水线将被冲刷(harzard部件产生flush信号),取指结果为跳转目标,bge指令的下一条指令无效。如果预测跳转且分支条件不满足,预测错误,要在下一周期重新取指。如果预测不跳转且条件不满足,则继续正常指令。如果预测不跳转但条件满足了,预测错误,要在EX阶段将跳转目标写入NPC。下面假设预测不跳转但预测错误,在EX阶段写入NPC。
访存阶段,不需要访存,MemWriteM信号处于非使能状态,该指令不写入寄存器,关键信号为:
-
LoadNPCM:不写入,因此选择任意数据都没有影响,视为默认非使能,传递ALU结果
写回阶段,不需要写入数据,RegWriteW为非使能。关键信号: -
RegWriteW:非使能状态,不写入
-
MemToRegW:无影响
整个过程的数据通路为(预测不跳转但预测错误,在EX阶段写入跳转地址):
c.lw x15, -20 x8的执行过程
该指令及前两条指令为:
101a4: 97b6 c.add x15 x13
101a6: e6e7a623 sw x14 -404 x15 #A[i] = A[i-1]+1000
101aa: fec42783 lw x15 -20 x8
取指阶段关键信号为:
- JalrE,JalE,BrE:均为非使能状态,因为前几条指令没有分支与跳转
译码阶段,A1的值为0x8,表示x8寄存器,从寄存器组中读取该寄存器的值,将值传递给EX阶段。另一个操作数是立即数,通过立即数单元传递给EX阶段,该阶段没有重要的控制信号。
执行阶段,操作数1选择从寄存器中读取的x8的值,操作数2选择立即数的值,运算类型为加法,将结果传递到访存阶段。关键信号:
- Forward1E:选择寄存器读取的值
- ForWard2E:无影响
- AluSrc1E、AluSrc2E:OP1选择寄存器的值,OP2选择立即数
- AluContrl:ADDOP信号,加法运算
访存阶段,将ALU运算的结果作为地址从存储器中读取值,传递给写回阶段。关键信号:
- LoadNPCM:无影响,从存储器读取数据
- MemWriteM:非使能,读取数据
写回阶段将访存结果传给寄存器组,写入寄存器x15。关键信号:
- RegWriteW:使能状态,写入寄存器
- MemToRegW:使能状态,选择访存结果
整个过程的数据通路为:
d.sw x15 -20 x8指令的执行过程
该指令及前两条指令为:
101aa: fec42783 lw x15 -20 x8
101ae: 0785 c.addi x15 1
101b0: fef42623 sw x15 -20 x8
取指阶段关键信号为:
- JalrE,JalE,BrE:均为非使能状态,因为前几条指令没有分支与跳转
译码阶段,A1的值为0x8,读出x8寄存器,A2的值为0XF,读取x15寄存器的值,另一个操作数是立即数,读取与传递的过程与lw指令相同。没有重要的控制信号。
执行阶段,需要计算存储地址,前两条指令未写入x8,因此选择译码阶段读取的x8的值和立即数作为操作数进行运算。关键信号为:
- Forward1E:选择寄存器读取的值
- ForWard2E:无影响
- AluSrc1E、AluSrc2E:OP1选择寄存器的值,OP2选择立即数
- AluContrl:ADDOP信号,加法运算
访存阶段,需要向存储器写入,写入的值是上一条指令写回的数据,地址为ALU计算的结果。关键信号:
- LoadNPCM:无影响
- MemWriteM:写使能,写入数据
写回阶段不需要写入寄存器。关键信号:
- RegWriteW:非使能状态,不需要写入寄存器
其数据通路为:
蓝色的为上一条指令在写回阶段的结果,传给StoreDataM作为写入数据,原电路图中未画出StoreDataM的数据来源,应该和执行阶段的操作数选择一样,可以选择寄存器读取的值或先前指令的计算结果。
4.BranchE信号的作用
BranchE信号的作用是判断分支条件是否正确。对于分支指令,需要在执行阶段比较rs1和rs2的值,根据分支类型进行两个值的比较,判断是否需要跳转,如果需要跳转,BrE信号将处于使能状态,NPC generator应该写入跳转的目标地址,对于动态分支预测,可能这个值在ID阶段就已经写入了,EX阶段只是验证预测正确,如果失败了,则要重新取指,冲刷流水线。
如果预测正确并发生了跳转,那么跳转指令之后的一条指令是无效的,因此需要冲刷流水线,取消后一条指令的执行,因此BrE信号也传递给harzard单元,当需要跳转时,接收到跳转信号的harzard单元会向流水线中间寄存器发出flush信号,冲刷流水线,取消正在执行的指令,重新读取跳转目标地址的指令继续执行。
预测选中且预测正确
CLK1 | CLK2 | CLK3 | CLK4 | CLK5 | CLK6 | CLK7 | |
---|---|---|---|---|---|---|---|
pipline i1 | IF(NPC=NPC+4) | ID(分支预测,分支地址计算) | EX(计算分支条件) | MEM | WB | ||
pipline i2 | IF(根据预测结果更新NPC=NPC+4/branch target) | ID | flush | flush | flush | ||
pipline i3 | IF(NPC=PC+4) | ID(branch target) | EX | MEM | WB |
预测不选中且预测正确
CLK1 | CLK2 | CLK3 | CLK4 | CLK5 | CLK6 | CLK7 | |
---|---|---|---|---|---|---|---|
pipline i1 | IF(NPC=NPC+4) | ID(分支预测,分支地址计算) | EX(计算分支条件) | MEM | WB | ||
pipline i2 | IF(根据预测结果更新NPC=NPC+4/branch target) | ID | EX | MEM | WB | ||
pipline i3 | IF(NPC=NPC+4) | ID(LAST NPC) | EX | MEM | WB |
5.NPC Generator跳转目标的选择
NPC generator有四种可选的下址目标:
- PC+4:默认执行下一条指令
- BrT:分支跳转地址
- JalrT:无条件跳转地址,目标地址为寄存器的值设置最低位为0,执行阶段计算
- JalT:无条件跳转地址,目标地址在译码阶段计算,为PC+Imm
对应的有三种使能信号和一个分支预测信号,只要有分支和跳转使能或预测跳转信号,就不使用PC+4作为下址。分支计算的结果验证和Jalr的目标地址都要到执行阶段才能得到,Jal指令的目标地址在译码阶段就计算好了,控制信号也是译码阶段就传递给NPC generator,此时可能同时有处于执行阶段的另外两种指令之一也确定要发生跳转,处于执行阶段的指令是先执行的,因此应该按照执行阶段的跳转目标地址进行跳转,因此如果存在使能信号,BrT和JalrT的选择优先于JalT的选择。
指令 | |||
---|---|---|---|
BrT | IF | ID | EX: BrE true |
Jal | IF | ID: JalE true |
此处BrT是预测未选中,预测错误的情况,是最晚确定跳转的情况,如果预测跳转且正确,在ID就将跳转,不会产生冲突。
6.Harzard单元(附加思考题)
冲突处理
流水线有三类冲突:
- 结构冲突:由资源冲突产生
- 数据冲突:相邻指令对相同的数据对象读写产生的冲突
- 控制冲突:分支和跳转指令修改NPC导致的冲突
使用分离的指令存储器和数据存储器,以及寄存器读写在一个时钟周期的前半和后半进行避免了结构冲突,但仍然可能存在结构冲突。当发生指令缓存未命中时,下一条指令不能进入取值阶段,当发生数据缓存未命中时,下一条指令不能进入访存阶段,因此当Harzard单元接收到DCacheMiss或ICacheMiss时,需要让流水线暂停,需要暂停多个周期。
数据冲突共有三种:RAW,WAR,WAW。其中WAW和WAR在顺序执行的标量处理器中不会出现。重点考虑RAW写后读冲突。写后读冲突出现在当前指令的源操作数是先前指令的结果时,例如以下情形:
c.slli x15 2
addi x14 x8 -16
c.add x15 x14
x15和x14的更新值都还未被写入,而add指令已经要在执行阶段使用这两个值了。这种冲突通过数据前推解决。当Harzard单元接收到RegWrite和RegRead信号同时为使能状态,并且源寄存器和写入目的寄存器相同时,就通过forward信号选择数据前推的结果。
还有前推不能处理的RAW冲突,如下:
lw x15 -20 x8
addi x14 x15 -16
lw指令在访存阶段后读出x15的新值,但此时add指令已经在执行阶段,需要这个值了,此时就只能暂停流水线。当harzard单元收到MemToRegE为使能状态,rs寄存器和rd寄存器为同一个寄存器时,就能发现该冲突,发出stall信号,让执行阶段及之前的所有阶段暂停,而访存和写回阶段继续,只需要暂停一个周期,写回的结果就可以前推回执行周期,流水线就可以继续工作了。
控制冲突由分支和跳转指令产生,当BrE,JalE,JalrE有一个处于使能状态,harzard单元就检测到了分支跳转,发出flush信号,冲刷执行阶段之前的部分,同时访存阶段和写回阶段继续工作,完成之前的指令。
预测未命中
如果采用预测未命中机制,下一条指令正常执行,在需要跳转时,后两条指令无效,写入NPC,对于IF,ID和EX,flush信号为true,冲刷流水线。不需要暂停流水线。下一周期重新取址执行新的指令。
指令 | ||||||
---|---|---|---|---|---|---|
BrT | IF | ID | EX: BrE true -> flush | |||
Jal | IF | ID: JalE true | ||||
add | IF | |||||
… | ||||||
BrTnext | IF | ID | EX… |