自己动手写CPU(2)流水线数据相关问题
自己動手寫CPU(2)流水線數據相關問題
問題定義
流水線中經常有一些被稱為“相關”的情況發生,它使得指令序列中下一條指令無法按照設計的時鐘周期執行,這些“相關”會降低流水線的性能。流水線中的相關分為以下三種類型。
本節重點分析數據相關的問題,流水線數據相關又分為三種情況: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,0x00202、相隔1條指令的指令間存在數據相關
ori $1,$0,0x1100 ori $3,$0,0xffff ori $2,$1,0x00203、相隔2條指令的指令間存在數據相關
ori $1,$0,0x1100 ori $3,$0,0xffff ori $4,$0,0xffff ori $2,$1,0x0020上述所說的相隔兩條指令存在數據相關的問題已經在之前設計的Regfile模塊中得到了解決,此處不再贅述;
對于相鄰指令間存在數據相關、相隔1條指令的指令間存在數據相關這兩種情況,有三種解決方法。
解決方案
我們采用數據前推的方式解決流水線數據相關問題。完善后的數據流圖如下所示,主要是將執行階段的結果、訪存階段的結果前推到譯碼階段,參與譯碼階段選擇運算源操作數的過程。
主要的修改在兩個方面:
測試結果
用如下的匯編代碼進行程序的測試
.org 0x0 .global _start.set noat _start:ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100ori $1,$1,0x0020 # $1 = $1 | 0x0020 = 0x1120ori $1,$1,0x4400 # $1 = $1 | 0x4400 = 0x5520ori $1,$1,0x0044 # $1 = $1 | 0x0044 = 0x5564指令的注釋給出了預期的執行效果,我們首先用上章的項目工程進行測試,波形圖如下:
由圖可見,譯碼階段的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 @(*) beginif(rst == `RstEnable) beginreg1_o = `ZeroWord;endelse if((reg1_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable) && (ex_wd_i == reg1_addr_o))beginreg1_o = ex_wdata_i;endelse if((reg1_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable) && (mem_wd_i == reg1_addr_o)) beginreg1_o = mem_wdata_i;endelse if(reg1_read_o == `ReadEnable) beginreg1_o = reg1_data_i;endelse if(reg1_read_o == `ReadDisable) beginreg1_o = imm;endelse beginreg1_o = `ZeroWord;end end//確定進行運算的操作數2 //給reg2_o賦值的過程與reg1_o同理 always @(*) beginif(rst == `RstEnable) beginreg2_o = `ZeroWord;endelse if((reg2_read_o == `ReadEnable) && (ex_wreg_i == `WriteEnable) && (ex_wd_i == reg2_addr_o))beginreg2_o = ex_wdata_i;endelse if((reg2_read_o == `ReadEnable) && (mem_wreg_i == `WriteEnable) && (mem_wd_i == reg2_addr_o)) beginreg2_o = mem_wdata_i;endelse if(reg2_read_o == `ReadEnable) beginreg2_o = reg2_data_i;endelse if(reg2_read_o == `ReadDisable) beginreg2_o = imm;endelse beginreg2_o = `ZeroWord;end endendmodule其中執行階段判斷的優先級比訪存階段高的原因是執行階段在流水線中比訪存階段靠前,寫入寄存器的值也是最新的值,所以體現在if上優先級就高于訪存階段。再修改頂層模塊OpenMIPS對應的代碼,增加連線關系即可,修改后的代碼跑出的波形圖如下圖所示:
由上圖可見,reg1的值與預期一致,解決了數據沖突的問題。寫入寄存器的值也是最新的值,所以體現在if上優先級就高于訪存階段。再修改頂層模塊OpenMIPS對應的代碼,增加連線關系即可,修改后的代碼跑出的波形圖如下圖所示:
由上圖可見,reg1的值與預期一致,解決了數據沖突的問題。
項目鏈接
總結
以上是生活随笔為你收集整理的自己动手写CPU(2)流水线数据相关问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己动手写CPU(1)五级流水线及CPU
- 下一篇: 自己动手写CPU(3)逻辑、移位操作与空