(一)从零开始设计RISC-V处理器——指令系统
(二)从零开始设计RISC-V处理器——单周期处理器的设计
(三)从零开始设计RISC-V处理器——单周期处理器的仿真
(四)从零开始设计RISC-V处理器——ALU的优化
(五)从零开始设计RISC-V处理器——五级流水线之数据通路的设计
(六)从零开始设计RISC-V处理器——五级流水线之控制器的设计
(七)从零开始设计RISC-V处理器——五级流水线之数据冒险
(八)从零开始设计RISC-V处理器——五级流水线之控制冒险
(九)从零开始设计RISC-V处理器——五级流水线之分支计算前移
(十)从零开始设计RISC-V处理器——五级流水线之静态预测
上一篇文章详细介绍了处理中的冒险,包括数据冒险,控制冒险和结构冒险,并且针对数据冒险进行分类,细分了5种情况,其中3种情况可以通过添加数据前递单元来解决,1种情况可以通过寄存器堆的前递机制进行解决,1种情况需要进行流水线的停顿。在上一篇文章中已经解决了数据冒险中的4种情况,本篇文章重点解决控制冒险以及数据冒险的最后一种情况。
上一篇文章中提到的控制冒险定义如下:
控制冒险,在遇到条件分支指令时,由于跳转条件是否成立在执行阶段才知道,因此流水线就需要停顿或者冲刷指令才能正确运行。
但是在实际设计的时候发现,对于我们现阶段的处理器来说,控制冒险不仅仅发生在“条件分支指令”中,对于无条件跳转指令,如jal,jalr,我们在解决控制冒险时也应当将其考虑在内,因为无条件跳转的目标地址同样在执行阶段才知道。
如果我们把无条件跳转指令的跳转地址在取指阶段进行计算,那么我们考虑控制冒险的时候便只需要考虑条件跳转指令了,这将会在最后一篇博文《从零开始设计RISC-V处理器——五级流水线之静态预测》中介绍。
下面再来详细分析一下控制冒险的现象(请大家自行画出时序图进行理解):
1.在第一个时钟周期的开始,取指阶段取出一条跳转指令。
2.在第二个时钟周期的开始,跳转指令开始译码,同时取指阶段取出一条新的指令Instr1。
3.在第三个时钟周期的开始,跳转指令进入执行阶段,同时取指阶段取出一条新的指令Instr2,在第三个时钟周期结束的时候,跳转的目标地址已经产生。
4.在第四个时钟周期的上升沿,取指令阶段即将取出跳转的目标地址对应的指令,此时Instr1即将进入执行阶段,Instr2即将进入译码阶段。
对于条件分支指令,如果跳转条件不成立,那么此时无需干预,如果跳转条件成立,则Instr1和Instr2是多余取出的指令。
对于无条件跳转指令,Instr1和Instr2同样是多余取出的指令。
那如何解决控制冒险呢?
1.最简单粗暴的方法,每次遇到跳转指令就将流水线停顿,等目标地址计算出来之后,再取新的指令。这无疑会大大降低流水线的运行速度。
2.总是假设不跳转,就像上面分析的,遇到分支指令之后,先正常的去取指令,如果分支不发生,则流水线正常运行,如果发生跳转,则冲刷掉多取的两条指令。这便是本篇文章要解决的方案。
3.仍然假设不跳转,但是将分支地址的计算前递到译码阶段,这样在分支目标地址结果出来之前,仅仅会多余的取出一条指令,那么如果发生跳转,则只需要冲刷掉多取的这一条指令。这是下一篇文章要解决的方案。
4.在第3种方案的基础上,加入“假设反向跳转,假设前向不跳转”的静态预测机制,这样,在预测正确的情况下,流水线能够全速运行,在预测错误的情况下,只需要冲刷掉一条指令。这是最后一篇文章要解决的方案。
当遇到控制冒险时,如过采用假设分支不发生的方法,当分支跳转发生时,就需要冲刷掉多余的两条指令,那么具体怎样实现冲刷呢
事实上,如果将控制信号置零,那么对应的指令也就无效了,处理器既不会访存,也不会写回寄存器堆。
正如上面分析的,在第四个时钟周期的上升沿,取指令阶段即将取出跳转的目标地址对应的指令,此时Instr1即将进入执行阶段,Instr2即将进入译码阶段。
此时,Instr1已经完成译码并且将控制信号输入到了ID/EX流水线寄存器。Instr1即将进入执行阶段,换一种说法就是,第四个时钟周期的上升沿,ID/EX流水线寄存器即将采集Instr1的控制信号,所以只需要在此时将ID/EX流水线寄存器中的控制信号置0而不采集Instr1的控制信号,即可实现对Instr1指令的冲刷。
此时,Instr2已经完成了取指令并且将指令输入到了IF/ID流水线寄存器。Instr2即将进入译码阶段,换一种说法就是,第四个时钟周期的上升沿,IF/ID流水线寄存器即将采集Instr2的32位二进制指令,所以只需要在此时将IF/ID流水线寄存器中的指令置0,即可实现对Instr1指令的冲刷。(指令全为0,即是一条空指令,译码出来的控制信号也是全0)
那么,如何判断控制冒险的到来呢,由于我们现阶段的处理器,把无条件跳转也考虑到了控制冒险中,所以只要发生跳转,就需要冲刷掉多余的两条指令,所以可以直接把前面设计的branch_judge模块的输出信号jump_flag当作发生控制冒险的信号。
下面贴出关键代码,除此之外还需要修改顶层模块,请读者自行完成。
branch_judge模块代码如下:
module branch_judge(
beq,
bne,
blt,
bge,
bltu,
bgeu,
jal,
jalr,
zero,
ALU_result_sig,
jump_flag
);
input beq;
input bne;
input blt;
input bge;
input bltu;
input bgeu;
input jal;
input jalr;
input zero;
input ALU_result_sig;
output jump_flag;
assign jump_flag = jal |
jalr |
(beq && zero)|
(bne && (!zero))|
(blt && ALU_result_sig)|
(bge && (!ALU_result_sig))|
(bltu && ALU_result_sig)|
(bgeu && (!ALU_result_sig));
endmodule
将IF/ID模块的代码修改如下:
`include "define.v"
module if_id_regs(
input clk,
input rst_n,
input jump_flag,
input [31:0]pc_if_id_i,
input [31:0]instr_if_id_i,
output reg [31:0]pc_if_id_o,
output reg [31:0]instr_if_id_o
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
pc_if_id_o<=`zeroword;
else
pc_if_id_o<=pc_if_id_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
instr_if_id_o<=`zeroword;
else if(jump_flag)
instr_if_id_o<=`zeroword;
else
instr_if_id_o<=instr_if_id_i;
end
endmodule
将ID/EX模块的代码修改如下:
`include "define.v"
module id_ex_regs(
input clk,
input rst_n,
input jump_flag,
input [31:0]pc_id_ex_i,
input [31:0]imme_id_ex_i,
input [31:0]Rd_data1_id_ex_i,
input [31:0]Rd_data2_id_ex_i,
input [4:0]Rd_id_ex_i,
input [4:0]Rs1_id_ex_i,
input [4:0]Rs2_id_ex_i,
output reg [31:0]pc_id_ex_o,
output reg [31:0]imme_id_ex_o,
output reg [31:0]Rd_data1_id_ex_o,
output reg [31:0]Rd_data2_id_ex_o,
output reg [4:0]Rd_id_ex_o,
output reg [4:0]Rs1_id_ex_o,
output reg [4:0]Rs2_id_ex_o,
//control signals
input ALUSrc_id_ex_i,
input [3:0]ALUctl_id_ex_i,
input beq_id_ex_i,
input bne_id_ex_i,
input blt_id_ex_i,
input bge_id_ex_i,
input bltu_id_ex_i,
input bgeu_id_ex_i,
input jal_id_ex_i,
input jalr_id_ex_i,
input MemRead_id_ex_i,
input MemWrite_id_ex_i,
input [2:0]RW_type_id_ex_i,
input lui_id_ex_i,
input U_type_id_ex_i,
input MemtoReg_id_ex_i,
input RegWrite_id_ex_i,
output reg ALUSrc_id_ex_o,
output reg [3:0]ALUctl_id_ex_o,
output reg beq_id_ex_o,
output reg bne_id_ex_o,
output reg blt_id_ex_o,
output reg bge_id_ex_o,
output reg bltu_id_ex_o,
output reg bgeu_id_ex_o,
output reg jal_id_ex_o,
output reg jalr_id_ex_o,
output reg MemRead_id_ex_o,
output reg MemWrite_id_ex_o,
output reg [2:0]RW_type_id_ex_o,
output reg lui_id_ex_o,
output reg U_type_id_ex_o,
output reg MemtoReg_id_ex_o,
output reg RegWrite_id_ex_o
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
pc_id_ex_o<=`zeroword;
else
pc_id_ex_o<=pc_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
imme_id_ex_o<=`zeroword;
else
imme_id_ex_o<=imme_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rd_data1_id_ex_o<=`zeroword;
else
Rd_data1_id_ex_o<=Rd_data1_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rd_data2_id_ex_o<=`zeroword;
else
Rd_data2_id_ex_o<=Rd_data2_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rd_id_ex_o<=5'd0;
else
Rd_id_ex_o<=Rd_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rs1_id_ex_o<=5'd0;
else
Rs1_id_ex_o<=Rs1_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rs2_id_ex_o<=5'd0;
else
Rs2_id_ex_o<=Rs2_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n )
ALUSrc_id_ex_o<=`zero;
else if(jump_flag)
ALUSrc_id_ex_o<=`zero;
else
ALUSrc_id_ex_o<=ALUSrc_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
ALUctl_id_ex_o<=4'b0000;
else if(jump_flag)
ALUctl_id_ex_o<=4'b0000;
else
ALUctl_id_ex_o<=ALUctl_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
beq_id_ex_o<=`zero;
else if(jump_flag)
beq_id_ex_o<=`zero;
else
beq_id_ex_o<=beq_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bne_id_ex_o<=`zero;
else if(jump_flag)
bne_id_ex_o<=`zero;
else
bne_id_ex_o<=bne_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
blt_id_ex_o<=`zero;
else if(jump_flag)
blt_id_ex_o<=`zero;
else
blt_id_ex_o<=blt_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bge_id_ex_o<=`zero;
else if(jump_flag)
bge_id_ex_o<=`zero;
else
bge_id_ex_o<=bge_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bltu_id_ex_o<=`zero;
else if(jump_flag)
bltu_id_ex_o<=`zero;
else
bltu_id_ex_o<=bltu_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bgeu_id_ex_o<=`zero;
else if(jump_flag)
bgeu_id_ex_o<=`zero;
else
bgeu_id_ex_o<=bgeu_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
jal_id_ex_o<=`zero;
else if(jump_flag)
jal_id_ex_o<=`zero;
else
jal_id_ex_o<=jal_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
jalr_id_ex_o<=`zero;
else if(jump_flag)
jalr_id_ex_o<=`zero;
else
jalr_id_ex_o<=jalr_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
MemRead_id_ex_o<=`zero;
else if(jump_flag)
MemRead_id_ex_o<=`zero;
else
MemRead_id_ex_o<=MemRead_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
MemWrite_id_ex_o<=`zero;
else if(jump_flag)
MemWrite_id_ex_o<=`zero;
else
MemWrite_id_ex_o<=MemWrite_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
RW_type_id_ex_o<=3'b000;
else if(jump_flag)
RW_type_id_ex_o<=3'b000;
else
RW_type_id_ex_o<=RW_type_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
lui_id_ex_o<=`zero;
else if(jump_flag)
lui_id_ex_o<=`zero;
else
lui_id_ex_o<=lui_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
U_type_id_ex_o<=`zero;
else if(jump_flag )
U_type_id_ex_o<=`zero;
else
U_type_id_ex_o<=U_type_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
MemtoReg_id_ex_o<=`zero;
else if(jump_flag)
MemtoReg_id_ex_o<=`zero;
else
MemtoReg_id_ex_o<=MemtoReg_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
RegWrite_id_ex_o<=`zero;
else if(jump_flag)
RegWrite_id_ex_o<=`zero;
else
RegWrite_id_ex_o<=RegWrite_id_ex_i;
end
endmodule
上一篇文章中已经详细介绍了加载-使用型冒险,最后得出的结论是,需要对流水线进行停顿一个周期。
下面再来详细分析一下加载-使用型冒险时流水线的运行情况(请大家自行画出时序图进行理解):
1.在第一个时钟周期的开始,取指阶段取出一条load指令。
2.在第二个时钟周期的开始,load指令开始译码,同时取指阶段取出一条新的指令Instr1。
3.在第三个时钟周期的开始,load指令进入执行阶段,Instr1开始译码,同时取指阶段取出一条新的指令Instr2。在此周期内便可判断出加载-使用型冒险。
4.在第四个时钟周期的上升沿,Instr1即将进入执行阶段,Instr2即将进入译码阶段,同时取指阶段即将取出Instr3。
此时我们想要在Instr1和load指令之间插入一条空指令,即Instr1不能进入执行阶段,那么按照控制冒险的冲刷指令的思路,只需要将ID/EX流水线寄存器的控制信号清零即可。
同时,我们不想冲刷掉Instr1和Instr2,而希望两条指令在插入的nop之后继续执行,那么对于IF/ID流水线寄存器来说,我们希望他不采集Instr2的32位指令信号而是保持Instr1的32位指令信号,对于取指部件来说,我们不希望他即将取出Instr3,而希望他再次取一条Instr2,所以要让pc信号保持而不更新。
以上就是停顿流水线的方法。
总结一下,在执行load指令阶段,判断出加载-使用型冒险并产生一个标志信号,在下一个时钟周期的上升沿采集到这个信号,然后清除ID/EX流水线寄存器的控制信号,暂停IF/ID流水线寄存器的更新,暂pc的更新。
判断出加载-使用型冒险代码如下:
//以上就是我们面临的五种冒险的分析,简单总结如下:
//a.在一个周期开始,EX 阶段要使用上一条处在 EX 阶段指令的执行结果,此时我们将 EX/MEM 寄存器的数据前递。
//b.在一个周期开始,EX 阶段要使用上一条处在 MEM 阶段指令的执行结果,此时我们将 MEM/WB 寄存器的数据前递。
//c.在一个周期开始,EX 阶段要使用上一条处在 WB 阶段指令的执行结果,此时不需要前递(寄存器堆前递机制)
//d.在第一种情况下,如果是上一条是访存指令,即发生加载—使用型冒险。则需要停顿一个周期。
//e.在发生加载——使用型冒险的时候,如果是load后跟着store指令,并且load指令的rd与store指令的rs1 不同而与rs2相同,则不需要停顿,只需要将MEM/WB 寄存器的数据前递到MEM阶段。
module forward_unit(
input [4:0]Rs1_id_ex_o,
input [4:0]Rs2_id_ex_o,
input [4:0]Rd_ex_mem_o,
input [4:0]Rd_mem_wb_o,
input RegWrite_ex_mem_o,
input RegWrite_mem_wb_o,
input MemWrite_id_ex_o,
input MemRead_ex_mem_o,
output [1:0]forwardA,
output [1:0]forwardB,
output forwardC,
input [4:0]Rs1_id_ex_i,
input [4:0]Rs2_id_ex_i,
input [4:0]Rd_id_ex_o,
input MemRead_id_ex_o,
input MemWrite_id_ex_i,
input RegWrite_id_ex_o,
output load_use_flag
);
assign forwardA[1]=(RegWrite_ex_mem_o &&(Rd_ex_mem_o!=5'd0)&&(Rd_ex_mem_o==Rs1_id_ex_o));
assign forwardA[0]=(RegWrite_mem_wb_o && (Rd_mem_wb_o !=5'd0) &&(Rd_mem_wb_o==Rs1_id_ex_o));
assign forwardB[1]=(RegWrite_ex_mem_o &&(Rd_ex_mem_o!=5'd0)&&(Rd_ex_mem_o==Rs2_id_ex_o));
assign forwardB[0]=(RegWrite_mem_wb_o && (Rd_mem_wb_o !=5'd0) &&(Rd_mem_wb_o==Rs2_id_ex_o));
//load后紧跟sw但是不需要停顿
assign forwardC=(RegWrite_ex_mem_o &&(Rd_ex_mem_o!=5'd0)&&(Rd_ex_mem_o!=Rs1_id_ex_o)&& (Rd_ex_mem_o==Rs2_id_ex_o)&& MemWrite_id_ex_o && MemRead_ex_mem_o );
//load-use
assign load_use_flag= MemRead_id_ex_o & RegWrite_id_ex_o & (Rd_id_ex_o!=5'd0) //load
&(!MemWrite_id_ex_i) //非store
& ((Rd_id_ex_o ==Rs1_id_ex_i) | (Rd_id_ex_o ==Rs2_id_ex_i))
|
MemRead_id_ex_o & RegWrite_id_ex_o & (Rd_id_ex_o!=5'd0) //load
&(MemWrite_id_ex_i) //store
& (Rd_id_ex_o ==Rs1_id_ex_i);
endmodule
ID/EX流水线寄存器修改代码如下:
`include "define.v"
module id_ex_regs(
input clk,
input rst_n,
input jump_flag,
input [31:0]pc_id_ex_i,
input [31:0]imme_id_ex_i,
input [31:0]Rd_data1_id_ex_i,
input [31:0]Rd_data2_id_ex_i,
input [4:0]Rd_id_ex_i,
input [4:0]Rs1_id_ex_i,
input [4:0]Rs2_id_ex_i,
output reg [31:0]pc_id_ex_o,
output reg [31:0]imme_id_ex_o,
output reg [31:0]Rd_data1_id_ex_o,
output reg [31:0]Rd_data2_id_ex_o,
output reg [4:0]Rd_id_ex_o,
output reg [4:0]Rs1_id_ex_o,
output reg [4:0]Rs2_id_ex_o,
//control signals
input ALUSrc_id_ex_i,
input [3:0]ALUctl_id_ex_i,
input beq_id_ex_i,
input bne_id_ex_i,
input blt_id_ex_i,
input bge_id_ex_i,
input bltu_id_ex_i,
input bgeu_id_ex_i,
input jal_id_ex_i,
input jalr_id_ex_i,
input MemRead_id_ex_i,
input MemWrite_id_ex_i,
input [2:0]RW_type_id_ex_i,
input lui_id_ex_i,
input U_type_id_ex_i,
input MemtoReg_id_ex_i,
input RegWrite_id_ex_i,
output reg ALUSrc_id_ex_o,
output reg [3:0]ALUctl_id_ex_o,
output reg beq_id_ex_o,
output reg bne_id_ex_o,
output reg blt_id_ex_o,
output reg bge_id_ex_o,
output reg bltu_id_ex_o,
output reg bgeu_id_ex_o,
output reg jal_id_ex_o,
output reg jalr_id_ex_o,
output reg MemRead_id_ex_o,
output reg MemWrite_id_ex_o,
output reg [2:0]RW_type_id_ex_o,
output reg lui_id_ex_o,
output reg U_type_id_ex_o,
output reg MemtoReg_id_ex_o,
output reg RegWrite_id_ex_o,
input load_use_flag
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
pc_id_ex_o<=`zeroword;
else
pc_id_ex_o<=pc_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
imme_id_ex_o<=`zeroword;
else
imme_id_ex_o<=imme_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rd_data1_id_ex_o<=`zeroword;
else
Rd_data1_id_ex_o<=Rd_data1_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rd_data2_id_ex_o<=`zeroword;
else
Rd_data2_id_ex_o<=Rd_data2_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rd_id_ex_o<=5'd0;
else
Rd_id_ex_o<=Rd_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rs1_id_ex_o<=5'd0;
else
Rs1_id_ex_o<=Rs1_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Rs2_id_ex_o<=5'd0;
else
Rs2_id_ex_o<=Rs2_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n )
ALUSrc_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
ALUSrc_id_ex_o<=`zero;
else
ALUSrc_id_ex_o<=ALUSrc_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
ALUctl_id_ex_o<=4'b0000;
else if(jump_flag | load_use_flag)
ALUctl_id_ex_o<=4'b0000;
else
ALUctl_id_ex_o<=ALUctl_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
beq_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
beq_id_ex_o<=`zero;
else
beq_id_ex_o<=beq_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bne_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
bne_id_ex_o<=`zero;
else
bne_id_ex_o<=bne_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
blt_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
blt_id_ex_o<=`zero;
else
blt_id_ex_o<=blt_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bge_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
bge_id_ex_o<=`zero;
else
bge_id_ex_o<=bge_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bltu_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
bltu_id_ex_o<=`zero;
else
bltu_id_ex_o<=bltu_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
bgeu_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
bgeu_id_ex_o<=`zero;
else
bgeu_id_ex_o<=bgeu_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
jal_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
jal_id_ex_o<=`zero;
else
jal_id_ex_o<=jal_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
jalr_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
jalr_id_ex_o<=`zero;
else
jalr_id_ex_o<=jalr_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
MemRead_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
MemRead_id_ex_o<=`zero;
else
MemRead_id_ex_o<=MemRead_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
MemWrite_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
MemWrite_id_ex_o<=`zero;
else
MemWrite_id_ex_o<=MemWrite_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
RW_type_id_ex_o<=3'b000;
else if(jump_flag | load_use_flag)
RW_type_id_ex_o<=3'b000;
else
RW_type_id_ex_o<=RW_type_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
lui_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
lui_id_ex_o<=`zero;
else
lui_id_ex_o<=lui_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
U_type_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
U_type_id_ex_o<=`zero;
else
U_type_id_ex_o<=U_type_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
MemtoReg_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
MemtoReg_id_ex_o<=`zero;
else
MemtoReg_id_ex_o<=MemtoReg_id_ex_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
RegWrite_id_ex_o<=`zero;
else if(jump_flag | load_use_flag)
RegWrite_id_ex_o<=`zero;
else
RegWrite_id_ex_o<=RegWrite_id_ex_i;
end
endmodule
IF/ID流水线寄存器代码修改代码如下:
`include "define.v"
module if_id_regs(
input clk,
input rst_n,
input jump_flag,
input [31:0]pc_if_id_i,
input [31:0]instr_if_id_i,
output reg [31:0]pc_if_id_o,
output reg [31:0]instr_if_id_o,
input load_use_flag
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
pc_if_id_o<=`zeroword;
else
pc_if_id_o<=pc_if_id_i;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
instr_if_id_o<=`zeroword;
else if(jump_flag)
instr_if_id_o<=`zeroword;
else if(load_use_flag)
instr_if_id_o<=instr_if_id_o;
else
instr_if_id_o<=instr_if_id_i;
end
endmodule
pc寄存器修改代码如下:
`include "define.v"
module pc_reg(
clk,
rst_n,
pc_new,
pc_out,
load_use_flag
);
input clk;
input rst_n;
input [31:0]pc_new;
input load_use_flag;
output reg [31:0]pc_out;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
pc_out<=`zeroword;
else if(load_use_flag)
pc_out<=pc_out;
else
pc_out<=pc_new;
end
endmodule
1.第一组指令
addi x1,x0,1
addi x2,x0,1
beq x1,x2,label0
addi x3,x0,3
addi x4,x0,4
label0:
addi x5,x0,5
bne x1,x5,label1
addi x6,x0,6
addi x7,x0,7
label1:
addi x8,x0,8
jal x31,label2
addi x9,x0,9
addi x10,x0,10
label2:
addi x11,x0,11
beq x1,x3,label3
addi x12,x0,12
addi x13,x0,13
label3:
addi x14,x0,14
仿真波形如下:
可以看到最终运行结果完全正确,能够正常冲刷指令。
2.第二组指令
addi x1,x0,1
addi x2,x0,2
jal x31,label1
addi x3,x0,3
label1:
addi x4,x0,4
add x5,x2,x2
beq x4,x5,label2
addi x6,x0,6
label2:
bne x4,x5,label3
addi x7,x0,7
label3:
bne x7,x6,label4
addi x8,x0,8
label4:
addi x9,x0,0x30
jalr x10,x9,12
addi x11,x0,11
addi x12,x0,-12
addi x13,x0,-13
blt x13,x12,label5
addi x14,x0,-14
label5:
bltu x13,x12,label6
addi x15,x0,-15
label6:
bltu x12,x13,label7
addi x16,x0,-16
label7:
bge x12,x13,label8
addi x17,x0,-17
label8:
bge x1,x2,label9
addi x18,x0,-18
label9:
bgeu x12,x13,label10
addi x19,x0,-19
label10:
bgeu x13,x12,label11
addi x20,x0,-20
label11:
addi x21,x0,-20
addi x22,x0,-20
bge x21,x22,label12
addi x23,x0,-23
label12:
addi x24,x0,-24
仿真波形如下:
观察仿真波形,可以发现,只有一个寄存器的值与仿真器的结果不一样,那就是X10,对应的指令为jalr x10,x9,12,但是此处能够正常跳转,经过分析发现,这里错误的原因是,由于jalr指令是将当前的pc加4的值写回寄存器,但是由于流水线机制,我们错误的把取指阶段的pc加4写回了寄存器,正确的结果应该是把执行阶段的pc加4写回寄存器,所以会出现错误的结果64,比正确结果56,正好大8(对应两条指令),在执行阶段修改代码之后再次仿真,结果完全正确。
测试指令
lw x1,0,x0
addi x2,x1,1
addi x3,x1,2
addi x4,x1,3
仿真波形如下:
可以看到,在load_use_flag信号拉高后,处理器进行了一个周期的停顿,并且执行结果完全正确。
以上就是对于流水线冲刷和流水线停顿的处理方法,关于数据冒险,到这里已经全部解决,而控制冒险目前采用的方法是假设分支不发生,这种情况下每次跳转都要冲刷掉两条指令,下一篇文章我们将把分支地址计算前移到译码阶段,提前得到跳转的目标地址,这样在发生跳转时仅仅需要冲刷一条指令。