计算机体系结构实验1:RISC-V指令理解
计算机体系结构实验1:RISC-V指令理解
RISC-V是一种开源指令集架构,近年来在学术界和工业界都受到了广泛关注。为了更好地理解RISC-V指令集,本文通过学习RV32I Core的设计图,详细分析了每条指令的数据流和控制信号,为之后的指令流水线及乱序发射实验打下基础。
实验目的
参考提供为了更好的理解RISC-V,通过学习RV32I Core的设计图,理解每条指令的数据流和控制信号,为之后指令流水线及乱序发射实验打下基础。
实验说明和步骤
参考提供的RISC-V 32I的设计图,思考每条指令的数据通路,熟悉RISC-V电路图,并且为后续动态分支预测和Tomasulo实验打下基础。
安装模拟器Ripes,具体步骤见https://github.com/mortbopet/Ripes
已提供riscv32gcc编译器的ubuntu版本和windows版本,其余版本下载参考:
https://github.com/mortbopet/Ripes/blob/master/docs/c_programming.md输入以下代码后生成对应汇编指令:
void main()
{
int A[100];
int i;
for(i=0;i<100;i++)
A[i]=i;
for(i=1;i<100;i++)
A[i]=A[i-1]+1000;
}
- 找出循环A[i]=A[i-1]+1000;对应的汇编代码,思考以下问题:
a) 分析指令add x15, x14, x15(x是指以x开头的通用寄存器),写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路。
IF提取指令周期。将程序计数器pc发送到存储器,从存储区提取当前指令为0x0001015c,然后向计数器加2(因为它只有16字节,所以是两个bit),结果变成0x0001015e将程序计数器更新到下一个连续程序计数器。
ID指令译码/寄存器提取周期。对指令进行译码,并从寄存器堆中读取与寄存器源说明符相应的寄存器。这里R1idx里面存的是0x0f(15),而R2idx里面存的是0x02(14)。然后寄存器的值变成这两条指令的和。Wr idx是目标寄存器,是x15(值为0x0f)。
EX执行/有效地址周期。从上一条输出的register的值(应该是从c.slli x15 2 就是x15左移两位得到的数值)0x00000004(x15的值为0x00000000再左移两位就是0x00000004)和上一条addi x14 x8 -16的写回的值0x7fffffe0到ALU做加法输出值应该是为0x7fffffe4。
MEM存储器访问/分支完成计算。因为add不是载入指令,也不是存储指令,所以直接到MEM/WB,输出的值应该是0x7fffffe4。
WB写回。从MEM阶段来的值0x7fffffe4写入寄存器堆中。然后写回到wr data寄存器的值,然后wr idx是寄存器名字,是x15,值为0x0f,他是从开始就最下面传过来的。
b) 分析指令bge x15, x14, -56,写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路。
判断x15大于等于x14的时候减去56。
- IF提取指令周期。将程序计数器pc发送到存储器,从存储区提取当前指令为0x000101bc,然后向计数器加4(因为它326字节,所以是4个bit),结果变成0x000101c0将程序计数器更新到下一个连续程序计数器。
(2)ID指令译码/寄存器提取周期。对指令进行译码,并从寄存器堆中读取与寄存器源说明符相应的寄存器。这里R1idx里面存的是0x0e(x14),而-56是个立即数,所以它存到IMM里面值为0xffffffc8。Wr idx是目标寄存器,是x15(值为0x0f)。
(3)EX执行/有效地址周期。发现ALU的A段的值是从pc直接传过来的,ALU的B段的值是-56的二进制(0xffffffc8),然后这两个值去ALU之前我们先是在Branch处判断(也就是从regiser出来的值跟-56进行判断,如果x15(0x00000003)大于等于x14(0x0000003)(这里取的值是从MEM阶段来的,判断时先进一步看看寄存器里的值再进行判断)的话加-56所以他是直接跳转,所以输出的ALU的值为0x00010184。最后它不会到MEM阶段直接跳转到最开始的PC处,值为0x00010184。
c) 分析指令lw x15, -20 x8,写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路。
(1)IF提取指令周期。将程序计数器pc发送到存储器,从存储区提取当前指令为0x0001910a,然后向计数器加4(因为它有32字节,所以是4个bit),结果变成0x0001910e将程序计数器更新到下一个连续程序计数器。
(2)ID指令译码/寄存器提取周期。对指令进行译码,并从寄存器堆中读取与寄存器源说明符相应的寄存器。这里R1idx里面存的是0xffffffff0(x8),而-20是个立即数,所以它存到IMM里面值为0xffffffec。Wr idx是目标寄存器,是x15(值为0x0f)。
(3)EX执行/有效地址周期。从上一条输出的register的值(应该是x8的值0xfffffff0)和从立即数那里传过来的值0xffffffec在ALU里面做加法结果为0x7fffffdc。
(4)MEM存储器访问/分支完成计算。从ALU来的值0x7fffffdc进到内存memory里面取0x00000002。
(5)WB写回。从MEM阶段来的值0x00000002写入寄存器堆中。然后写回到wr data寄存器的值(可以从最后的表格里面看出来),然后wr idx是写回的是寄存器名字,是x15,值为0x0f,他是从开始就最下面传过来的。
d) 分析指令sw x15, -20 x8,写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路。
(1)IF提取指令周期。将程序计数器pc发送到存储器,从存储区提取当前指令为0x000101b0,然后向计数器加4(因为它32字节,所以是4个bit),结果变成0x000101b4将程序计数器更新到下一个连续程序计数器。
(2)ID指令译码/寄存器提取周期。对指令进行译码,并从寄存器堆中读取与寄存器源说明符相应的寄存器。这里R1idx里面存的是0xffffffff0(x8),而-20是个立即数,所以它存到IMM里面值为0xffffffec。Wr idx是目标寄存器,是x15(值为0x0f)。
(3)EX执行/有效地址周期。从上一条输出的register的值(应该是x8的值0xfffffff0)和从立即数那里传过来的值0xffffffec在ALU里面做加法结果为0x7fffffdc。
(4)MEM存储器访问/分支完成计算。从ALU来的值0x7fffffdc进到内存memory里面取0x00000002。
因为这是存储指令所以只执行4个周期。
e) 简述BranchE信号的作用。
答:
BranchE是分支控制信号,当BranchE为1时,表明分支将要跳转,此时PC_ln = PCF + BranchTarget
f) NPC Generator 中对于不同跳转 target 的选择有没有优先级?如果有,请举例并分析。如果没有,请解释原因。
答:
有,如果同时遇到BranchE / JarlE信号和JalD信号,应该前者的指令更早执行,即,他们在原来的指令顺序中更加靠前,所以优先级更高。
附加思考题:
1 Harzard模块中,有哪几类冲突需要插入气泡(NOP指令),分别使流水线停顿几个周期。(提示:有三类冲突)
答:
- 结构冲突。资源独占,多条指令使用相同资源。
- 数据冲突。后面的指令等待前面的指令结果时发生的冲突。
- 控制冲突。分支指令流水化时发生的冲突。
一类,只有load指令+需要用到 load的目标值的指令(写后读相关)需要插入气泡。
在有转发-旁路的情况下,只用插入一个气泡,使流水线停顿一个时钟周期即可。
2 Harzard模块中,采用静态分支预测器,即默认不跳转,遇到branch指令时,如何控制flush和stall信号?
答:
跳转指令进入ID阶段时,CPU发现这是一个跳转指令。
EX阶段时,IR正常更新,跳转指令不跳转的下一条指令进入译码阶段。
若跳转结果为不跳转,则无flush。
若跳转结果为跳转,则flush lF和ID段(此时是跳转指令不跳转的下一条指令),PC更新为新的指令地址。
参考资料
1 RISC-V 32I Core 设计图
2 RISC-V 32I指令集