HNU计算机体系结构-实验一
- 前言
- 1.实验目的
- 2.实验步骤
- 1.安装模拟器Ripes
- 2.生成汇编指令
- 3.思考问题
- 1)指令`add x15, x14, x15`
- 2)指令`bge x15 x14 -68`
- 3)指令`lw x15, -20 x8`
- 4)指令`sw x15, -20 x8`
- 5)简述BranchE信号的作用
- 6)NPC Generator
- 4.附加思考题:
- 1)插入气泡
- 2)branch指令
- 3.实验总结
前言
体系结构的第一个实验,实际上是中科大的实验,GitHub直接搜ustc_ca可以搜出很多,我也是借鉴了几位大佬的。这个课是计科第一年开课的,所以以后的实验内容可能会有所调整。
这次实验的内容也很简单,用ripes跑一遍即可,这个软件还是很智能的,每一步骤干什么,用的哪个寄存器,以及数值是多少都能显示出来。
闲麻烦不想在GitHub上下载的,直接放到网盘了。
具体用法是先打开ripes.exe,左上角editor->setiing,然后

选择另一个文件夹里的编译器,然后就可以运行代码了。

一般这个实验都在期中前做的,所以一定要认认真真的跑一遍流水线,把一些基本指令的流程都弄懂,期中必考的。
还有一个坑就是bge指令的立即数那里,它那个是要左移一位的,最高位补的数字和次高位一样
1.实验目的
参考提供为了更好的理解RISC-V,通过学习RV32I Core的设计图,理解每条指令的数据流和控制信号,为之后指令流水线及乱序发射实验打下基础。
看不清的可以下载
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b2v9Zhr2-1686648622231)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\1680504587112.png)]](https://img-blog.csdnimg.cn/d4d72ba34eab4ea39ff53d7dde8d14a4.png)
各部分的部件的主要控制信号如下:
1.HarzardUnit
流水线冲突处理模块,基本手段:(1)插入气泡stall,(2)定向路径(前递,转发)forward,(3)冲刷流水段flush,组合逻辑电路,信号说明:
输入:
CpuRst: 外部信号,用来初始化CPU,当CpuRst1时CPU全局复位清零(所有段寄存器flush),Cpu_Rst0时cpu开始执行指令ICacheMiss, DCacheMiss:为后续实验预留信号,暂时可以无视,用来处理cache missBranchE,JalrE,JalD: 控制相关处理信号Rs1D,Rs2D,Rs1E,Rs2E,RdE,RdM,RdW: 译码,执行,访存,写会阶段处理数据相关的信号,对应的源寄存器和目标寄存器号码。RegReadE: 标记A1和A2对应的寄存器值是否被用到。MemToRegE: 标志EX段从data mamory加载数据到寄存器RegWriteM,RegWriteW: 标记MEM段和WB段是否有目标寄存器写入操作。
输出:
StallF,FlushF: IF段插入气泡(维持状态不变)/冲刷(清零)StallD,FlushD: ID段插入气泡/冲刷StallE,FlushE: EX段插入气泡/冲刷StallM,FlushM: MEM段插入气泡/冲刷StallW,FlushW: WB段插入气泡/冲刷Forward1E,Forward2E: 定向路径控制信号
2.ControlUnit
控制模块(译码器),根据指令的操作码部分Op,func3部分Fn3和func7部分Fn7产生如下控制信号:
输入:
Op:是指令的操作码部分Fn3:是指令的func3部分Fn7:是指令的func7部分
输出:
JalD==1: 标志Jal指令到达指令ID译码阶段JalrD==1: 标志Jalr指令到达指令ID译码阶段RegWriteD: 表示指令ID译码阶段的寄存器写入模式MemToRegD==1: 标志ID阶段指令需要从data memory读取数据到寄存器MemWriteD: 共4bit,为1的部分有效,指示data memory的四个字节中哪些需要写入LoadNpcD: 标志将NextPC输出到ResultMRegReadD: 标志两个源寄存器的使用情况,RegReadD[1]== 1,表示A1对应的寄存器值被使用到了,RegReadD[0]== 1,表示A2对应的寄存器值被使用到了,用于forward处理BranchTypeD: 表示不同分支类型(参见BranchDecisionMaking部分)AluContrlD: 表示不同算数逻辑运算种类(参见ALU部分)AluSrc2D: Alu输入源Operand2的选择AluSrc1D: Alu输入源Operand1的选择ImmType: 立即数编码格式类型
3.NPC_Generator
用来生成Next PC值的模块,根据不同的跳转信号选择不同的新PC值
输入:
PCF:旧的PC值JalrTarget:jalr指令的对应的跳转目标BranchTarget:branch指令的对应的跳转目标JalTarget:jal指令的对应的跳转目标BranchE==1:Ex阶段的Branch指令确定跳转JalD==1:ID阶段的Jal指令确定跳转JalrE==1:Ex阶段的Jalr指令确定跳转
输出:
PC_In:NPC的值
4.RegisterFile
上升沿写入,异步读的寄存器堆,0号寄存器值始终为32’b0
5.ImmOperandUnit
利用正在被译码的指令的部分编码值,生成不同类型的32bit立即数
输入:
IN:是指令除了opcode以外的部分编码值Type:表示立即数编码类型,全部类型定义在Parameters中
输出:
OUT:表示指令对应的立即数32bit实际值
6.BranchDecisionMaking
跳转判断单元,根据控制信号BranchTypeE指定的分支类型,对操作数Operand1和Operand2进行比较并决定是否跳转,将判断结果通过BranchE输出。各分支类型对应的控制信号如下
7.ALU
算数逻辑运算单元,接受Operand1和Operand2两个操作数,按照控制信号AluContrl执行对应的算术逻辑运算,将结果从AluOut输出
8.DataExt
输入:
IN:是从Data Memory中load的32bit字LoadedBytesSelect:等价于AluOutM[1:0],是读Data Memory地址的低两位,因为DataMemory是按字(32bit)进行访问的,所以需要把字节地址转化为字地址传给DataMem,DataMem一次返回一个字,低两位地址用来从32bit字中挑选出我们需要的字节RegWriteW:表示不同的寄存器写入模式,所有模式定义在Parameters中
输出:
OUT:表示要写入寄存器的最终值
2.实验步骤
参考提供的RISC-V 32I的设计图,思考每条指令的数据通路,熟悉RISC-V电路图,并且为后续动态分支预测和Tomasulo实验打下基础。
1.安装模拟器Ripes
具体步骤见https://github.com/mortbopet/Ripes
已提供riscv32gcc编译器的ubuntu版本和windows版本,其余版本下载参考:
https://github.com/mortbopet/Ripes/blob/master/docs/c_programming.md
下载后页面如下:

2.生成汇编指令
输入以下代码后生成对应汇编指令
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;
}
生成的汇编指令如下:
00010144 <main>://主函数的开始
//x2的值减去432,然后存回x2。x2寄存器是栈指针,这条指令是为了分配栈空间
10144: e5010113 addi x2 x2 -432
//x8的值存储到x2加上428的内存地址。x8寄存器是帧指针,所以这条指令是为了保存帧指针。
10148: 1a812623 sw x8 428 x2
//x2寄存器的值加上432,然后存回x8寄存器。这条指令是为了更新帧指针
1014c: 1b010413 addi x8 x2 432
//x0寄存器的值存储到x8寄存器减去20的内存地址。这条指令初始化一个局部变量i为0
10150: fe042623 sw x0 -20 x8
//无条件跳转到当前地址加上40的位置,并将返回地址存储到x0寄存器。
//因为x0寄存器是零寄存器,所以返回地址会被丢弃。这条指令是为了跳过下面的循环体。
10154: 0280006f jal x0 40
//将x8寄存器减去20的内存地址的值加载到x15寄存器。这条指令是为了读取局部变量i的值
10158: fec42783 lw x15 -20 x8
//将x15寄存器的值左移两位,然后存回x15寄存器。这条指令相当于将i乘以4,因为每个整数占4个字节
1015c: 00279793 slli x15 x15 2
//将x8寄存器的值减去16,然后存回x14寄存器。这条指令是为了计算数组A在栈上的起始地址
10160: ff040713 addi x14 x8 -16
//将x14寄存器和x15寄存器的值相加,然后存回x15寄存器。这条指令是为了计算A[i]在栈上的地址
10164: 00f707b3 add x15 x14 x15
//将x8寄存器减去20的内存地址的值加载到x14寄存器。这条指令又一次读取局部变量i的值
10168: fec42703 lw x14 -20 x8
//将x14寄存器的值存储到x15寄存器减去404的内存地址。这条指令相当于执行A[i]=i
1016c: e6e7a623 sw x14 -404 x15
//将x8寄存器减去20的内存地址的值加载到x15寄存器。这条指令又一次读取局部变量i的值
10170: fec42783 lw x15 -20 x8
//将x15寄存器的值加上1,然后存回x15寄存器。这条指令相当于执行i++
10174: 00178793 addi x15 x15 1
//将x15寄存器的值存储到x8寄存器减去20的内存地址。这条指令相当于更新局部变量i的值
10178: fef42623 sw x15 -20 x8
//将x8寄存器减去20的内存地址的值加载到x14寄存器。这条指令又一次读取局部变量i的值
1017c: fec42703 lw x14 -20 x8
//将x0寄存器的值加上99,然后存回x15寄存器。这条指令相当于将99赋值给一个临时变量
10180: 06300793 addi x15 x0 99
//比较x15寄存器和x14寄存器的值,如果x15大于等于x14,就跳转到当前地址减去44的位置。
//这条指令相当于执行if(i<100) goto loop
10184: fce7dae3 bge x15 x14 -44
//将x0寄存器的值加上1,然后存回x15寄存器。这条指令相当于初始化一个局部变量j为1
10188: 00100793 addi x15 x0 1
//将x15寄存器的值存储到x8寄存器减去20的内存地址。这条指令相当于更新局部变量j的值
1018c: fef42623 sw x15 -20 x8
//无条件跳转到当前地址加上64的位置,并将返回地址存储到x0寄存器。
//因为x0寄存器是零寄存器,所以返回地址会被丢弃。这条指令是为了跳过下面的循环体。
10190: 0400006f jal x0 64
//将x8寄存器减去20的内存地址的值加载到x15寄存器。这条指令是为了读取局部变量j的值
10194: fec42783 lw x15 -20 x8
//将x15寄存器的值减去1,然后存回x15寄存器。这条指令相当于执行j–1
10198: fff78793 addi x15 x15 -1
//将x15的值左移两位,然后存回x15寄存器。这条指令相当于将j-1乘以4,因为每个整数占4个字节
1019c: 00279793 slli x15 x15 2
//将x8寄存器的值减去16,然后存回x14寄存器。这条指令是为了计算数组A在栈上的起始地址
101a0: ff040713 addi x14 x8 -16
//将x14寄存器和x15寄存器的值相加,然后存回x15寄存器。这条指令是为了计算A[j-1]在栈上的地址
*101a4: 00f707b3 add x15 x14 x15
//将x15寄存器减去404的内存地址的值加载到x15寄存器。这条指令相当于获得A[j-1]的值
101a8: e6c7a783 lw x15 -404 x15
//将x15寄存器的值加上1000,然后存回x14寄存器。这条指令相当于将A[j-1]+1000的值存在x14中
101ac: 3e878713 addi x14 x15 1000
//将x8寄存器减去20的内存地址的值加载到x15寄存器。这条指令又一次读取局部变量j的值
101b0: fec42783 lw x15 -20 x8
//将x15寄存器的值左移两位,然后存回x15寄存器。这条指令相当于将j乘以4,因为每个整数占4个字节
101b4: 00279793 slli x15 x15 2
//将x8寄存器的值减去16,然后存回x13寄存器。这条指令是为了计算数组A在栈上的起始地址
101b8: ff040693 addi x13 x8 -16
//将x13寄存器和x15寄存器的值相加,然后存回x15寄存器。这条指令是为了计算A[j]在栈上的地址
101bc: 00f687b3 add x15 x13 x15
//将x14寄存器的值存储到x15寄存器减去404的内存地址。这条指令相当于执行A[j]=A[j-1]+1000
101c0: e6e7a623 sw x14 -404 x15
//将x8寄存器减去20的内存地址的值加载到x15寄存器。这条指令又一次读取局部变量j的值
*101c4: fec42783 lw x15 -20 x8
//将x15寄存器的值加上1,然后存回x15寄存器。这条指令相当于执行j++
101c8: 00178793 addi x15 x15 1
//将x15寄存器的值存储到x8寄存器减去20的内存地址。这条指令相当于更新局部变量j的值
*101cc: fef42623 sw x15 -20 x8
//将x8寄存器减去20的内存地址的值加载到x14寄存器。这条指令又一次读取局部变量j的值
101d0: fec42703 lw x14 -20 x8
//将x0寄存器的值加上99,然后存回x15寄存器。这条指令相当于将99赋值给一个临时变量
101d4: 06300793 addi x15 x0 99
//比较x15寄存器和x14寄存器的值,如果x15大于等于x14,就跳转到当前地址减去68的位置。
//这条指令相当于执行if(j<100) goto loop;
*101d8: fae7dee3 bge x15 x14 -68
//将x0寄存器的值加上0,然后存回x0寄存器。这条指令没有实际作用,只是为了占位
101dc: 00000013 addi x0 x0 0
//将x2寄存器加上428的内存地址的值加载到x8寄存器。这条指令是为了恢复帧指针
101e0: 1ac12403 lw x8 428 x2
//将x2寄存器的值加上432,然后存回x2寄存器。这条指令是为了释放栈空间
101e4: 1b010113 addi x2 x2 432
//跳转到x1寄存器加上0的地址,并将返回地址存储到x0寄存器。
//因为x0寄存器是零寄存器,所以返回地址会被丢弃。这条指令是为了从main函数返回
101e8: 00008067 jalr x0 x1 0
3.思考问题
找出循环A[i]=A[i-1]+1000;对应的汇编代码,思考以下问题:
1)指令add x15, x14, x15
问题描述:分析指令add x15, x14, x15(x是指以x开头的通用寄存器),写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路
答:这条指令的作用是将x14寄存器中的值加上x15寄存器中的值存到x15寄存器中,其中x14寄存器存入的是数组A的基址地址,x15存入的是偏移量,两者相加后获得A[j-1]的地址
| R-型指令 | funct7 | rs2 | rs1 | funct3 | rd | opcode |
|---|---|---|---|---|---|---|
| add | 0000000 | 01111 | 01110 | 000 | 01111 | 0110011 |
这条指令是一条R类型的指令
-
IF阶段:
- 数据通路:
- 根据
PCF,从指令存储器中取出指令00000000111101110000011110110011- PC寄存器的值加4,更新为下一条指令的地址
- 控制信号:无
-
ID阶段:
- 数据通路:
- 从
IF/ID寄存器中译码出指令的操作码(inst[6:0])- 在寄存器文件中读出源寄存器地址(
inst[24:20],inst[19:15])对应地址的值,送入A1和A2寄存器,送往ID/EX寄存器 - 将目的寄存器地址(
inst[11:7]) 送往ID/EX寄存器 - 将
IF/ID中得到指令段,送往Control Unit控制单元产生控制信号
- 在寄存器文件中读出源寄存器地址(
- 控制信号:
ImmType=3'd0(代表是R类型指令,无需生成立即数)
-
EX阶段:
- 数据通路:
- 从
A1和A2寄存器中取出两个源操作数,送入ALU进行加法运算,得到结果,送入ALUOutE,结果送入EX/MEM寄存器。 - 上一级传入的控制信号传入下一阶段
- 从
ID/EX中得到Rd(目的寄存器地址),送往EX/MEM寄存器
- 从
- 控制信号:
AluContrlE=3,ALU执行加法操作Forward1E=2'b00,第一个操作数选择来自A1寄存器的值Forward2E=2'b00,第二个操作数选择来自A2寄存器的值ALUSrc1E=1,ALU第一个操作数来自A1寄存器ALUSrc2E=2'00,ALU第二个操作数来自A2寄存器RegReadE[1]=1,A1对应的寄存器值被使用到了RegReadE[0]=1,A2对应的寄存器值被使用到了
- 数据通路:
-
MEM阶段:
- 数据通路:
- 从
EX/MEM寄存器中取出运算结果ALUOutM,送入数据存储器和MemData多路选择器,存入MEM/WB寄存器中。 - 上一级控制信号传入下一阶段
- 从
EX/MEM中得到Rd(目的寄存器地址),送往MEM/WB寄存器
- 从
- 控制信号:
LoadNpcD=1: 将下一条指令的地址存储到ResultM中,以便在WB阶段更新PC寄存器的值MemWriteM=4'0,不写内存
- 数据通路:
-
WB阶段:
-
数据通路:
- 从
MEM/WB寄存器中取出写回数据,根据RegDst信号选择目的寄存器地址,将数据写入寄存器文件。
- 从
-
控制信号:
MemToRegW=1,选择将ALU运算结果传入寄存器文件中RegWriteW=1,写回目的寄存器
-
2)指令bge x15 x14 -68
问题描述:分析指令bge x15, x14, -68,写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路
答:这条指令的作用是比较x15寄存器和x14寄存器的值,如果x15大于等于x14,就跳转到当前地址减去68的位置。这条指令相当于执行if(j<100) goto loop。
指令输入SB-型指令,只会执行IF,ID和EX段。
- IF阶段:
- 数据通路:
- 根据
PCF,从指令存储器中取出指令11111010111001111101111011100011。 PC寄存器的值加4,更新为下一条指令的地址。
- 根据
- 控制信号:无
- 数据通路:
- ID阶段:
- 数据通路:
- 寄存器堆
A1,A2地址分别为rs1,rs2,读出待比较的值RegOut1D和RegOut2D - Immediate Generate生成立即数并传入
ImmD,左移一位后与PCD相加得到JalNPC,传入段寄存器 - 指令
Inst传给Controller Decoder,生成控制信号
- 寄存器堆
- 控制信号:
- ImmTypeD=3’b011,Imm左移两位并符号扩展,传入
ImmD,结果为-224
- ImmTypeD=3’b011,Imm左移两位并符号扩展,传入
- 数据通路:
- EX阶段:
- 数据通路:
- Branch Decision单元执行
Reg1-Reg2,如果结果大于等于0,则BrE信号应该被设置为1,表示应该跳转。 - 否则BrE应该被设置为0,表示不跳转,指令继续按地址的顺序执行
- Branch Decision单元执行
- 控制信号:
- BranchTypeE=3’001,代表BEQ指令
- BrE=1,代表跳转,
- BrT信号应该被设置为当前指令地址加上偏移量(-68),即
PC + ImmE,即-68<<2(PC是当前指令的地址,左移两位是因为指令存储器是四字节地址寻址的) - BrE=0,代表不跳转, 则BrT信号被设置为下一条指令的地址,即PC+4
Forward1E=2'b00,第一个操作数选择来自A1寄存器的值Forward2E=2'b00,第二个操作数选择来自A2寄存器的值ALUSrc1E=1,reg1来自A1寄存器ALUSrc2E=2'00,reg2来自A2寄存器
- 数据通路:
3)指令lw x15, -20 x8
问题描述:分析指令lw x15, -20 x8,写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路
答:指令的作用是将x8寄存器减去20的内存地址的值加载到x15寄存器。其中x8寄存器减去20的内存地址的值即为局部变量i的值,所以这里是将i的值赋给x15寄存器
| I-型指令 | immediate | rs1 | funct3 | rd | opcode |
|---|---|---|---|---|---|
| lw | 111111101100 | 01000 | 010 | 01111 | 0000011 |
这是一条I-型指令:
- IF阶段:
- 数据通路:
- 根据
PCF,从指令存储器中取出指令11111110110001000010011110000011 - PC寄存器的值加4,更新为下一条指令的地址。
- 根据
- **控制信号:**无
- 数据通路:
- ID阶段:
- 数据通路:
- 指令
Inst传给Controller Decoder,生成控制信号 - A1读入源寄存器地址(
inst[19:15]),将读出的值RegOut1D送入ID/EX寄存器 - 将目的寄存器地址(
inst[11:7])rd送往ID/EX寄存器 Immediate Generate生成立即数,将立即数扩展为32位,送入ImmD,接着送入ID/EX寄存器。
- 指令
- 控制信号:
ImmType=3'd1,代表是I类型指令,需要生成立即数RegWriteW=1,结果写回目的寄存器x15
- 数据通路:
- EX阶段:
- 数据通路:
- 从A1寄存器和
ImmOperandUnit中取出两个源操作数,送入ALU进行加法运算,得到内存地址,送入ALUOutE,结果送入EX/MEM寄存器。 - 从上一级获得控制信号并传入下一级
- 从
ID/EX中得到Rd地址,送往EX/MEM寄存器
- 从A1寄存器和
- 控制信号:
AluContrlE=4'b0011,ALU执行加法操作Forward1E=2'b00,第一个操作数选择来自A1寄存器的值Forward2E=2'b01,第二个操作数选择来自立即数ALUSrc1E=1'b1,ALU第一个操作数来自A1寄存器ALUSrc2E=2'b00,ALU第二个操作数来自Imm寄存器RegReadE[1]=1,A1对应的寄存器值被使用到了
- 数据通路:
- MEM阶段:
- 数据通路:
- 从
EX/MEM中得到ALU运算结果,送往数据存储器取出对应地址内容,内容送往MEM/WB寄存器 - 从上一级获得控制信号并传入下一级
- 从
EX/MEM中得到Rd地址,送往MEM/WB寄存器
- 从
- 控制信号:
LoadNpcD=0,将NextPC输出到ResultMMemWriteM=4'b0000,不写内存
- 数据通路:
- WB阶段:
- 数据通路:
- 从
MEM/WB中得到存储器内容,送往DataExt数据扩展单元扩展后送往寄存器堆 - 从
MEM/WB中得到Rd写回地址,送往寄存器堆用于写回上方内容
- 从
- 控制信号:
MemToRegW=0,标志WB阶段指令需要从内存读取数据到寄存器RegWriteW=1,写回目的寄存器x15LoadedBytesSelect=4,相当于ResultW[3:0],即选取要取的4字节数据
- 数据通路:
4)指令sw x15, -20 x8
问题描述:分析指令sw x15, -20 x8,写出该指令在流水线五个阶段(IF、ID、EX、MEM和WB)关键的控制信号(参考RISC V电路设计图),并通过分析指出数据通路
答:这是一条S-型指令,它的作用是将寄存器x15中的值存储到地址为寄存器x8中的值减去20处的内存中。这条指令相当于更新局部变量i的值
- IF阶段:
- 数据通路:
- 根据
PCF,从指令存储器中取出指令11111110111101000010011000100011 - PC寄存器的值加4,更新为下一条指令的地址。
- 根据
- 控制信号:无
- 数据通路:
- ID阶段:
- 数据通路:
- 指令
Inst传给Controller Decoder,生成控制信号 - A2读入源寄存器地址(
inst[24:20]),将读出的值RegOut2D送入ID/EX寄存器 - A1读入目的寄存器地址(
inst[19:15]),将读出的值RegOut1D送入ID/EX寄存器 Immediate Generate生成立即数,将立即数扩展为32位,送入ImmD,接着送入ID/EX寄存器。
- 指令
- 控制信号:
ImmType,S类型指令,需要生成立即数
- 数据通路:
- EX阶段:
- 数据通路:
- 从
A1寄存器和ImmOperandUnit中取出两个源操作数,送入ALU进行加法运算,得到要写入的内存地址,送入ALUOutE,结果送入EX/MEM寄存器。 - 从上一级获得控制信号并传入下一级
- 从
ID/EX中得到A2寄存器读出的值RegOut2E,送往EX/MEM寄存器
- 从
- 控制信号:
AluContrlE=3,代表ALU执行ADD运算Forward1E=2'b00,第一个操作数选择来自A1寄存器的值Forward2E=2'b00,将A2寄存器读出的值RegOut2E传递下去AluSrc1E=1'b1,代表ALU第一个操作数来自A1寄存器AluSrc2E=2'b10,代表ALU第二个操作数来自Imm寄存器RegReadE[1]=1,A1对应的寄存器值被使用到了
- 数据通路:
- MEM阶段:
- 数据通路:
- 从
EX/MEM寄存器读出要写入内存的值,通过StoreDataM写入Data Memory的WD接口 - 从
EX/MEM寄存器读出要写入的内存地址,AluOutM写入A接口 - 从上一级获得控制信号并传入下一级
- 从
- 控制信号:
LoadNpcD=1,将NextPC输出到ResultMMemWriteM=1,本条指令需要将数据写入内存
- 数据通路:
- WB阶段:
- 数据通路:无需进行写回操作。
- 控制信号:
MemToRegW=0,不需要从内存读取数据到寄存器RegWriteW=0,不需要写回目的寄存器
5)简述BranchE信号的作用
BranchE信号是指令执行(EX)阶段的一个控制信号,用于判断是否需要进行分支跳转。在流水线的EX阶段
- 如果指令是分支指令,例如beq、bne等,那么BranchE信号将被设置为1,表示需要进行分支跳转。同时,根据指令的操作数和ALU的计算结果,计算出是否需要跳转,即将BrT信号设置为1或0。
- 如果需要跳转,则将跳转目标地址计算出来,并将其写入PC寄存器,以便在下一次IF阶段时取出目标指令进行执行。
- 否则,PC寄存器继续累加4,直接取出下一条指令进行执行
6)NPC Generator
NPC Generator 中对于不同跳转 target 的选择有没有优先级?如果有,请举例并分析。如果没有,请解释原因
答:有。
JalrT、BrT 和 JalT 是 RISC-V CPU 中用于控制跳转指令的信号。
- JalrT:Jalr Target,用于指示 CPU 是否需要跳转到 Jalr 指令中给定的目标地址。当 JalrT 信号为高电平时,CPU 将跳转到 Jalr 指令中给定的目标地址。
- BrT:Branch Target,用于指示 CPU 是否需要跳转到分支指令中给定的目标地址。当 BrT 信号为高电平时,CPU 将跳转到分支指令中给定的目标地址。
- JalT:Jal Target,用于指示 CPU 是否需要跳转到 Jal 指令中给定的目标地址。当 JalT 信号为高电平时,CPU 将跳转到 Jal 指令中给定的目标地址。
Jalr 指令的目标地址是通过寄存器的内容计算得到的,而 Jal 指令的目标地址则是在指令中直接给出的。
branch和jalr是EX段跳转,而jal是在ID段跳转。所以必须设置优先级,使得在后的指令先跳转。因此JalrT和BrT的优先级高于JalT
同时,若修改数据通路,使得br,jal,jalr均在EX段跳转,则不会有冲突,此时也就不需要设置优先级。
4.附加思考题:
1)插入气泡
Harzard模块中,有哪几类冲突需要插入气泡(NOP指令),分别使流水线停顿几个周期。(提示:有三类冲突)
答:
- RAW类冲突:如Load和ALU指令,在ALU计算时,操作数还未读出来。在EX段Stall,使流水线停顿1个周期
- 控制相关的冲突:如在跳转时,需要插入气泡,flush掉IF段取的指令,停顿1个周期
- 条件转移的冲突:在条件转移时,需要插入气泡,flush掉IF,ID段取的指令,停顿2个周期
2)branch指令
Harzard模块中,采用静态分支预测器,即默认不跳转,遇到branch指令时,如何控制flush和stall信号?
答:
Branch指令在EX段判断。如果发生分支,则需要Flush IF/ID和ID/EX段寄存器来保证数据不被后方指令错误使用。否则不需要flush或stall。
3.实验总结
在实验中,我学习了RISC-V指令集架构的基本概念和特点,了解了RISC-V Core的基本组成和工作原理,学习了每条指令的数据流和控制信号。
总的来说,通过本实验的学习,我对RISC-V指令集架构有了更深入的理解,了解了每条指令的数据流和控制信号,同时也对RISC-V Core的组成和工作原理有了更清晰的认识。在实验中,我还了解了一些常用的数字电路和计算机体系结构的概念,例如时钟信号、寄存器、ALU等等。
计算时,操作数还未读出来。在EX段Stall,使流水线停顿1个周期
- 控制相关的冲突:如在跳转时,需要插入气泡,flush掉IF段取的指令,停顿1个周期
- 条件转移的冲突:在条件转移时,需要插入气泡,flush掉IF,ID段取的指令,停顿2个周期



















