问题引入
流水线中经常有一些被称为“相关”的情况发生,它使得指令序列中下一条指令无法按照设计的时期周期执行,这些“相关”会降低流水线的性能。流水线中的相关分为下述三个类型
结构相关
指令执行过程中,硬件资源满足不了指令执行需求,发生硬件资源冲突而产生的相关。
比如:指令和数据共享一个存储器,在某个时钟周期,流水线既要完成某条指令对存储器中数据的访问操作,又要完成后继的取指令操作,这样发生存储器访问冲突,产生结构相关。
数据相关
指在流水线执行的几条指令中,一条指令依赖于前面指令的结果,本文主要解决数据相关问题。
控制相关
指的是流水线中的分支指令或者其他需要改写PC的指令造成的相关。本文不讨论这个。
流水线数据相关
流水线数据相关分为三种情况,RAW,WAR,WAW。
RAW
RAW(read after write)
假设j是在i后执行的指令,RAW表示指令i将数据写入寄存器后,指令j才能从这个寄存器中读取数据。如果j在i之前写入寄存器尝试读出寄存器的内容,将读不到正确的数据。
WAR
WAR(write after read)
假设j是在i后面执行的指令,WAR表示指令i从寄存器中读取到数据后,指令j才能写这个寄存器。如果j在i读出数据前些这个寄存器,指令i中读取到的数据则不正确。
WAW
WAW(write after write)
j假设j是在i后面执行的指令,WAW表示指令i将数据写入寄存器之后,指令j才能将数据写入这个寄存器。如果j在i之前写这个寄存器,那么该寄存器的值不是最新值。
就OpenMIPS五级流水线而言,只有在流水线回写阶段才会写寄存器,因此不存在WAW
又因为只能在流水线译码阶段读寄存器、回写阶段写寄存器,因此不存在WAR
RAW相关存在三种情况:
相邻指令间存在数据相关
第一条ori指令写$1,第二条指令ori读$1数据,但是第一条ori指令在回写阶段才会将其运算结果写入$1,第二条ori指令要在译码阶段就需要读取$1的值,此时第一条ori还处于执行阶段,必然出错。这种情况称为相邻指令间存在数据相关。即流水线译码、执行阶段存在数据相关。

相隔1条指令的指令间存在数据相关
第一条ori指令将写寄存器$1,第三条指令在译码阶段需要读取$1,此时第一条ori还在访存阶段,所以结果错误。
称之为流水线译码、访存阶段的数据相关。

相隔2条指令存在数据相关
第一条ori指令写入寄存器$1,第四条ori指令在译码阶段需要读取$1,此时的第一条指令在回写阶段,在回写阶段最后的时钟上升沿才会将结果写入$1所以第四条ori指令读取的不是正确的$1值。即可称之为流水线译码、回写数据相关。
相隔2条指令数据相关的情况,需要对Regfile模块进行修改,代码之后放。
主要解决方法是:在读操作中有一个判断,如果要读取的寄存器是在下一个时钟上升沿要写入的寄存器,那么就将要写入的数据作为结果直接输出
对于前两个数据相关有三种解决办法
- 插入暂停周期

- 编译器调度

- 数据前推

数据前推

OpenMIPS处理器采用数据前推的方法来解决流水线数据相关问题,在原始数据流图的情况上,添加部分信号使得可以完成数据前推的工作,主要是讲执行阶段的结果、访存阶段的结果前推到译码阶段,参与译码阶段选择源操作数的过程
如图:
通过代码体现,其实就是在译码阶段添加了一些后面阶段的接口。。
下面的id.v代码并未在modelsim上面编译过,只是在ultraedit上面写了出来,具体正误得等编译并且测试通过才知道,还没学呢。。
`include "defines.v"
module id(
input wire rst,//复位信号
input wire[`InstAddrBus] pc_i,//译码阶段的指令对应地址
input wire[`InstBus] inst_i,//译码阶段指令
//读取Regfile的值
input wire[`RegBus] reg1_data_i,//从Regfile输入的第一个读寄存器端口的输入
input wire[`RegBus] reg2_data_i,
//处于执行阶段的指令的运算结果
input wire ex_wreg_i,
input wire[`RegBus] ex_wdata_i,
input wire[`RegAddrBus] ex_wd_i,
//处于访存阶段的指令运行结果
input wire mem_wreg_i,
input wire[`RegBus] mem_wdata_i,
input wire[`RegAddrBus] mem_wd_i,
//输出到Regfile的信息
output reg reg_read_o,//第一个读寄存器端口的读使能信号
output reg reg2_read_o,
output reg[`RegAddrBus] reg1_addr_o,//读地址
output reg[`RegAddrBus] reg2_addr_o,
//送到执行阶段的信息
output reg[`AluOpBus] aluop_o,
output reg[`AluSelBus] alusel_o,
output reg[`RegBus] reg1_o,
output reg[`RegBus] reg2_o,
output reg[`RegAddrBus] wd_o,
output reg wreg_o
);
//取得指令的指令码 功能码
//对于ori指令只需要通过判断第26-31bit的值 即可判断是否是ori的指令
wire[5:0] op=inst_i[31:26];
wire[4:0] op2=inst_i[10:6];
wire[5:0] op3=inst_i[5:0];
wire[4:0] op4=inst_i[20:16];
//保存指令执行需要的立即数
reg[`RegBus] imm;
//指示指令是否有效
reg instvalid;
/***************************第一段:对指令进行译码*********************************/
always @ (*)
begin
if(rst==`RstEnbale)
begin
aluop_o <= `EXE_NOP_OP;//6'b000000
alusel_o <= `EXE_RES_NOP;//3'b000
wd_o <= `NOPRegAddr;//译码阶段指令是否有要写入的目的寄存器地址
wreg_o <=`WriteDisable;//禁止写
instvalid <=`InstValid;//指令有效
reg1_read_o<=1'b0;
reg2_read_o<=1'b0;
reg1_addr_o<=`NOPRegAddr;
reg2_addr_o<=`NOPRegAddr;
imm <= 32'h0;
end
else
begin
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
wd_o <= inst_i[15:11];//要写入的目的寄存器地址
wreg_o <=`WriteDisable;
instvalid <=`Instvalid;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
reg1_addr_o <= inst_i[25:21];//默认通过Regfile读端口1读取的寄存器的地址reg1_addr_o的值是指令21-25bit
reg2_addr_o <= inst_i[20:16];//rt
imm <= `ZeroWord;
case (op)
`EXE_SPECIAL_INST://如果指令码是SPECIAL
begin
case(op2)
5'b00000:
begin
case(op3) //依据功能码判断是哪种指令
`EXE_OR://or指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_OR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;//???
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_AND:
begin //and指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_AND_OP;
alusel_o <= `EXE_RES_LOGIC;//逻辑运算
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_XOR:
begin//xor指令
wreg_o <= `WriteEnable;
aluop <= `EXE_XOR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_NOR:
begin//nor指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_NOR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SLLV:
begin//sllv指令
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLL_OP;
alusel_o <= `EXE_RES_SHIFT;//要执行的是逻辑左移操作
reg1_read_o <= 1'b1;
reg2_read_o <=1'b1;
instvalid <= `InstValid;
end
`EXE_SRLV://srlv指令
//以srlv指令为例:将地址为rt的通用寄存器的值向右移位 空出来的位置使用0填充 将结果保存到地址为rd的通用寄存器中
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRL_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b1;
instvalid <= `InstValid;
end
`EXE_SRAV://srav指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRA_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b1;
reg2_read_o <=1'b1;
instvalid <= `InstValid;
end
`EXE_SYNC://sync指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
instvalid < =`InstValid;
end
default:
begin
end
endcase//case(op3)
end
`EXE_ORI: //EXE_ORI 6'b001101 //指令ori的指令码
begin
//ori指令需要将结果写入目的寄存器,所以wreg_o为WriteEnable
wreg_o <= `WriteEnable;
//运算的子类型为逻辑“或”运算
aluop_o <= `EXE_OR_OP;//8'b00100101
//运算类型是逻辑运算
alusel_o <=`EXE_RES_LOGIC;// EXE_RES_LOGIC 3'b001
//需要通过Regfile的读端口1读取寄存器
reg1_read_o<=1'b1;
//不需要通过Regfile的端口2读取寄存器
reg2_read_o<=1'b0;
//指令执行需要的立即数
imm <= {16'h0,inst_i[15:0]};
//指令执行要写的目的寄存器地址
wd_o <= inst_i[20:16];
//ori指令是有效指令
invalid <= `InstValid;
end
`EXE_XORI://xori指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_XOR_OP;
alusel_o <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;
reg2_read_o <= 1'b0;
imm <= {16'h0,inst_i[15:0]};
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_ANDI://andi指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_AND_OP;
alusel <= `EXE_RES_LOGIC;
reg1_read_o <= 1'b1;//andi指令只需读取rs寄存器的值
reg2_read_o <= 1'b0;//暗含使用立即数作为运算的操作数
imm <= {16'h0,inst_i[15:0]};//imm就是指令中的立即数进行零扩展之后的值
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_LUI://lui指令 OpenMIPS将lui指令转化为ori指令执行(lui rt,immediate=ori rt,$0,(immediate||0^16)
//将立即数左移16bit,与$0寄存器进行逻辑或运算
reg_o <= `WriteEnable;
aluop_o <= `EXE_OR_OP;
alusel_o <= `EXE_RES_LOGIC;
//需要读取寄存器$0的值
reg1_read_o <= 1'b1;
reg2_read_o M= 1'b0;
imm <= {inst_i[15:0],16'h0};//imm为指令的立即数左移16位的值
wd_o <= inst_i[20:16];
instvalid <= `InstValid;
end
`EXE_PREF://pref指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_NOP_OP;
alusel_o <= `EXE_RES_NOP;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b0;
instvalid <= `InstValid;
end
default:
begin
end
endcase//case (op)
if(inst_i[31:21]==11'b00000000000)
begin
if(op3==`EXE_SLL)//sll指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SLL_OP;
alusel_o <= `EXE_RES_SHIFT;//要执行的是左移操作
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
imm[4:0] <= inst_i[10:6];//sa
wd_o <= inst_i[15:11];
instvalid <= `InstValid;
end
else if(op3==`EXE_SRL)//srl指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRL_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
imm[4:0] <= inst_i[10:6];
wd_o <= inst_i[15:11];
instvalid <= `InstValid;
end
else if(op3==`EXE_SRA)//sra指令
begin
wreg_o <= `WriteEnable;
aluop_o <= `EXE_SRA_OP;
alusel_o <= `EXE_RES_SHIFT;
reg1_read_o <= 1'b0;
reg2_read_o <= 1'b1;
imm[4:0] <= inst_i[10:6];
wd_o <= inst_i[15:11];
instvalid <= `InstValid;
end
end//if
end//always
/************************第二段:确定进行运算的源操作数1**********************/
/*给reg1_o赋值过程增加了两种情况*/
always @ (*)
begin
if(rst==`RstEnable)//复位信号有效
begin
reg1_o <= `ZeroWord;//源操作数1置零
end
else if((reg1_read_o==1'b1)&&(ex_wreg_i==1'b1)&&(ex_wd_i==reg1_addr_o))//如果Regfile模块读端口1要读取的寄存器就是执行阶段要写的目的寄存器
begin
reg1_o <= ex_wdata_i;//直接把执行阶段的结果ex_wdata_i作为reg1_o的值
end
else if((reg1_read_o==1'b1)&&(mem_wreg_i==1'b1)&&(mem_wd_i==reg1_addr_o))//如果Regfile模块读端口1要读取的寄存器就是访存阶段要写的目的寄存器
begin
reg1_o <= mem_wdata_i;//直接把访存阶段的结果mem_wdata_i作为reg1_o的值
end
else if(reg1_read_o==1'b1)
begin
reg1_o <= reg1_data_i;
end
else if(reg1_read_o==1'b0)
begin
reg1_o <= imm;
end
else
begin
reg1_o <= `ZeroWord;
end
end
/********************第三段:确定进行运算的源操作数2**************************/
//reg2_o的操作情况和reg1_o基本相同
always @ (*)
begin
if(rst==`RstEnable)
begin
reg2_o <= `ZeroWord;
end
else if((reg2_read_o==1'b1)&&(ex_wreg_i==1'b1)&&(ex_wd_i==reg2_addr_o))
begin
reg2_o <= ex_wdata_i;
end
else if((reg_read_o==1'b1)&&(mem_wreg_i==1'b1)&&(mem_wd_i==reg2_addr_o))
begin
reg2_o <= mem_wdata_i;
end
else if(reg2_read_o==1'b1)
begin
reg2_o <= reg2_data_i;
end
else if(reg2_read_o==1'b0)
begin
reg2_o <= imm;
end
else
begin
reg2_o <= `ZeroWord;
end
end
endmodule
/*对于相隔两条2条指令存在数据相关的情况 添加了数据前推 通过添加部分信号使得可以完成数据前推的工作,将执行阶段阶段的结果、访存阶段结果前推到译码阶段,
参与译码阶段选择运算源操作数的过程
将流水线执行阶段的指令的运算结果和访存阶段的指令的运算结果等信息送到译码阶段,因此ID模块要增加一些接口,上述代码会有所体现*/


被折叠的 条评论
为什么被折叠?



