自己动手写CPU(2)流水线数据相关问题

本文分析了流水线中的数据相关问题,包括RAW、WAR、WAW三种类型,并重点讨论了OpenMIPS五级流水线中仅存在的RAW相关。针对RAW相关,提出了数据前推的解决方案,通过修改数据流图和译码模块,确保在执行和访存阶段的结果能及时传递到译码阶段,从而避免数据冲突。经过测试,修改后的设计能够正确处理相邻及相隔指令间的数据相关,提高了流水线性能。
摘要由CSDN通过智能技术生成

自己动手写CPU(2)流水线数据相关问题

问题定义

流水线中经常有一些被称为“相关”的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些“相关”会降低流水线的性能。流水线中的相关分为以下三种类型。

  1. 结构相关:指的是在指令执行的过程中,由于硬件资源满足不了指令执行的要求,发生硬件资源冲突而产生的相关。比如:指令和数据都共享一个存储器,在某个时钟周期,流水线既要完成某条指令对存储器中数据的访问操作,又要完成后续的取指令操作,这样就会发生存储器访问冲突,产生结构相关。
  2. 数据相关:指的是在流水线中执行的几条指令中,一条指令依赖于前面指令的执行结果。
  3. 控制相关:指的是流水线中的分支指令或者其他需要改写PC的指令造成的相关。

本节重点分析数据相关的问题,流水线数据相关又分为三种情况:RAW、WAR、WAW

  • RAW,即 Read After Write,假设指令j是在指令i后面执行的指令,RAW表示指令i将数据写入寄存器后,指令j才能从这个寄存器读取数据。如果指令j在指令i写入寄存器前尝试读出该寄存器的内容,将得到不正确的数据。
  • WAR,即 Write After Read,假设指令j是在指令i后面执行的指令,WAR表示指令i读出数据后,指令j才能写这个寄存器。如果指令j在指令i读出数据前就写该寄存器,将使得指令i读出的数据不正确。
  • WAW,即 Write After Write,假设指令j是在指令i后面执行的指令,WAW表示指令i将数据写入寄存器后,指令j才能将数据写入这个寄存器。如果指令j在指令i之前写该寄存器,将使得该寄存器的值不是最新值。

对我们之前建立的原始OpenMIPS五级流水线而言,只有在流水线回写阶段才会写寄存器,因此不存在WAW相关;又因为只能在流水线译码阶段读寄存器、回写阶段写寄存器,不存在WAR相关;所以OpenMIPS的流水线只存在RAW相关。RAW相关有三种情况:

1、相邻指令间存在数据相关

ori $1,$0,0x1100
ori $2,$1,0x0020

1.png

2、相隔1条指令的指令间存在数据相关

ori $1,$0,0x1100
ori $3,$0,0xffff
ori $2,$1,0x0020

2.png

3、相隔2条指令的指令间存在数据相关

ori $1,$0,0x1100
ori $3,$0,0xffff
ori $4,$0,0xffff
ori $2,$1,0x0020

3.png

上述所说的相隔两条指令存在数据相关的问题已经在之前设计的Regfile模块中得到了解决,此处不再赘述;

对于相邻指令间存在数据相关、相隔1条指令的指令间存在数据相关这两种情况,有三种解决方法。

  1. 插入暂停周期:当检测到相关时,在流水线中插入一些暂停周期;
  2. 编译器调度:编译器检测到相关后,可以改变部分指令的执行顺序;
  3. 数据前推:将计算结果从其产生处直接送到其他指令需要处或所有需要的功能单元处,避免流水线暂停;

4.png

解决方案

我们采用数据前推的方式解决流水线数据相关问题。完善后的数据流图如下所示,主要是将执行阶段的结果、访存阶段的结果前推到译码阶段,参与译码阶段选择运算源操作数的过程。

5.png

主要的修改在两个方面:

  1. 将处于流水线执行阶段的指令的运算结果,包括:是否要写目的寄存器wreg_o、要写的目的寄存器地址wd_o、要写入目的寄存器的数据wdata_o等信息送到译码阶段。
  2. 将处于流水线访存阶段的指令的运算结果,包括:是否要写目的寄存器wreg_o、要写的目的寄存器地址 wd o、要写入目的寄存器的数据wdata_o等信息送到译码阶段。

6.png

测试结果

用如下的汇编代码进行程序的测试

	.org 0x0
.global _start
   .set noat
_start:
   ori $1,$0,0x1100        # $1 = $0 | 0x1100 = 0x1100
   ori $1,$1,0x0020        # $1 = $1 | 0x0020 = 0x1120
   ori $1,$1,0x4400        # $1 = $1 | 0x4400 = 0x5520
   ori $1,$1,0x0044        # $1 = $1 | 0x0044 = 0x5564

指令的注释给出了预期的执行效果,我们首先用上章的项目工程进行测试,波形图如下:

失败波形图.png

由图可见,译码阶段的reg1_o有X阶段,这是由于读reg1_o时,第一条指令还未进行到reg1的回写阶段,所以第二和第三条指令读取reg1的值时为X,当第四条指令读取reg1时,第一条指令处于回写阶段,这种冲突我们在之前的Regfile里解决过了,所以第四条指令才能读到reg1的值。可见由于数据间冲突,指令的结果不正确,我们要对译码模块做适当修改。

module id(
    ...
    //处于执行阶段的指令的运算结果
    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
);
	...

//确定进行运算的操作数1
//给reg1_o赋值的过程增加了两种情况
//1.如果Regfile模块读端口1要读取的寄存器就是执行阶段要写的目的寄存器,
//那么直接把执行阶段的结果ex_data_i作为reg1_o的值
//2.如果Regfile模块读端口1要读取的寄存器就是访存阶段要写的目的寄存器,
//那么直接把访存阶段的结果mem_data_i作为reg1_o的值
always @(*) begin
    if(rst == `RstEnable) begin
      reg1_o = `ZeroWord;
    end
    else if((reg1_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable) && (ex_wd_i == reg1_addr_o))begin
      reg1_o = ex_wdata_i;
    end
    else if((reg1_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable) && (mem_wd_i == reg1_addr_o)) begin
      reg1_o = mem_wdata_i;
    end
    else if(reg1_read_o == `ReadEnable) begin
      reg1_o = reg1_data_i;
    end
    else if(reg1_read_o == `ReadDisable) 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 == `ReadEnable) && (ex_wreg_i == `WriteEnable) && (ex_wd_i == reg2_addr_o))begin
      reg2_o = ex_wdata_i;
    end
    else if((reg2_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable) && (mem_wd_i == reg2_addr_o)) begin
      reg2_o = mem_wdata_i;
    end
    else if(reg2_read_o == `ReadEnable) begin
      reg2_o = reg2_data_i;
    end
    else if(reg2_read_o == `ReadDisable) begin
      reg2_o = imm;
    end
    else begin
      reg2_o = `ZeroWord;
    end
end

endmodule

其中执行阶段判断的优先级比访存阶段高的原因是执行阶段在流水线中比访存阶段靠前,写入寄存器的值也是最新的值,所以体现在if上优先级就高于访存阶段。再修改顶层模块OpenMIPS对应的代码,增加连线关系即可,修改后的代码跑出的波形图如下图所示:

成功波形图.png

由上图可见,reg1的值与预期一致,解决了数据冲突的问题。写入寄存器的值也是最新的值,所以体现在if上优先级就高于访存阶段。再修改顶层模块OpenMIPS对应的代码,增加连线关系即可,修改后的代码跑出的波形图如下图所示:
成功波形图.png

由上图可见,reg1的值与预期一致,解决了数据冲突的问题。

项目链接

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日拱一卒_未来可期

若复习顺利望有闲钱的同学支持下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值