系列文章目录

(一)从零开始设计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种方案的基础上,加入“假设反向跳转,假设前向不跳转”的静态预测机制,这样,在预测正确的情况下,流水线能够全速运行,在预测错误的情况下,只需要冲刷掉一条指令。这是最后一篇文章要解决的方案。

二、控制冒险之假设分支不发生

1.冲刷流水线(flush)

当遇到控制冒险时,如过采用假设分支不发生的方法,当分支跳转发生时,就需要冲刷掉多余的两条指令,那么具体怎样实现冲刷呢
事实上,如果将控制信号置零,那么对应的指令也就无效了,处理器既不会访存,也不会写回寄存器堆。
正如上面分析的,在第四个时钟周期的上升沿,取指令阶段即将取出跳转的目标地址对应的指令,此时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当作发生控制冒险的信号。

2.代码示例

下面贴出关键代码,除此之外还需要修改顶层模块,请读者自行完成。

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.停顿流水线(stall)

上一篇文章中已经详细介绍了加载-使用型冒险,最后得出的结论是,需要对流水线进行停顿一个周期。

下面再来详细分析一下加载-使用型冒险时流水线的运行情况(请大家自行画出时序图进行理解):
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的更新。

2.代码示例

判断出加载-使用型冒险代码如下:


//以上就是我们面临的五种冒险的分析,简单总结如下:
//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.控制冒险仿真

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(对应两条指令),在执行阶段修改代码之后再次仿真,结果完全正确。

2.加载-使用型冒险仿真

测试指令

lw x1,0,x0
addi x2,x1,1
addi x3,x1,2
addi x4,x1,3

仿真波形如下:
在这里插入图片描述
在这里插入图片描述
可以看到,在load_use_flag信号拉高后,处理器进行了一个周期的停顿,并且执行结果完全正确。

总结

以上就是对于流水线冲刷和流水线停顿的处理方法,关于数据冒险,到这里已经全部解决,而控制冒险目前采用的方法是假设分支不发生,这种情况下每次跳转都要冲刷掉两条指令,下一篇文章我们将把分支地址计算前移到译码阶段,提前得到跳转的目标地址,这样在发生跳转时仅仅需要冲刷一条指令。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐