自己動手寫CPU之第五階段(1)——流水線資料相關問題
將陸續上傳本人寫的新書《自己動手寫CPU》(尚未出版),今天是第15篇,我儘量每週四篇
上一章建立了原始的OpenMIPS五級流水線結構,但是隻實現了一條ori指令,從本章開始,將逐步完善。本章首先討論了流水線資料相關問題,然後修改OpenMIPS以解決該問題,並在5.3節驗證瞭解決效果。接著對邏輯、移位操作與空指令的指令格式、用法、作用進行了一一說明,在5.5節通過擴充套件OpenMIPS實現了這些指令,最後編寫測試程式,對實現效果進行了檢驗。
5.1 流水線資料相關問題
我們在第4章實現的五級流水線結構很簡單,如果按照“簡單即美(Simple is Beautiful
流水線中經常有一些被稱為“相關”的情況發生,它使得指令序列中下一條指令無法按照設計的時鐘週期執行,這些“相關”會降低流水線的效能。流水線中的相關分為三種類型。
(1)結構相關:指的是在指令執行的過程中,由於硬體資源滿足不了指令執行的要求,發生硬體資源衝突而產生的相關。比如:指令和資料都共享一個儲存器,在某個時鐘週期,流水線既要完成某條指令對儲存器中資料的訪問操作,又要完成後續的取指令操作,這樣就會發生儲存器訪問衝突,產生結構相關。
(2)資料相關:指在流水線中執行的幾條指令中,一條指令依賴於前面指令的執行結果。
(3)控制相關:指流水線中的分支指令或者其他需要改寫PC的指令造成的相關。
結構相關、控制相關將在後續指令分析中討論,本節重點討論資料相關的問題。流水線資料相關又分為三種情況:RAW、WAR、WAW。
- RAW:Read After Write,假設指令j是在指令i後面執行的指令,RAW表示指令i將資料寫入暫存器後,指令j才能從這個暫存器讀取資料。如果指令j在指令i寫入暫存器前嘗試讀出該暫存器的內容,將得到不正確的資料。
- WAR:Write After Read,假設指令j
- WAW:Write After Write,假設指令j是在指令i後面執行的指令,WAW表示指令i將資料寫入暫存器後,指令j才能將資料寫入這個暫存器。如果指令j在指令i之前寫該暫存器,將使得該暫存器的值不是最新值。
對於第4章建立的原始OpenMIPS五級流水線而言,從ori指令的實現過程可以知道,只有在流水線回寫階段才會寫暫存器(實際上其餘指令也是一樣的,在後面實現其餘指令時,對這一點會更加清楚),因此不存在WAW相關。又因為只能在流水線譯碼階段讀暫存器、回寫階段寫暫存器,所以不存在WAR相關,所以OpenMIPS的流水線只存在RAW相關。RAW相關有三種情況。
(1)相鄰指令間存在資料相關
考慮如下程式碼。
1 ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100
2 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120
第1條ori指令會寫暫存器$1,隨後的第2條ori指令需要讀出$1的資料,但是第1條ori指令在回寫階段才會將其運算結果寫入$1,而第2條ori指令在譯碼階段就需要讀取$1的值,此時第1條ori指令還處於執行階段,所以得到的必然不是第1條ori指令計算得出的結果,按這個值運算,必然會出錯。如圖5-1所示。這種情況可以稱為相鄰指令間存在資料相關,針對OpenMIPS具體情況,也可以稱為流水線譯碼、執行階段存在資料相關。
(2)相隔1條指令的指令間存在資料相關
考慮如下程式碼。
1 ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100
2 ori $3,$0,0xffff # $3 = $0 | 0xffff = 0xffff
3 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120
第1條ori指令會寫暫存器$1,第3條ori指令在譯碼階段需要讀取暫存器$1,此時第1條ori指令還處於訪存階段,所以得到的必然也不是正確的值。如圖5-2所示。這種情況可以稱為相隔1條指令的指令間存在資料相關,針對OpenMIPS具體情況,也可以稱為流水線譯碼、訪存階段存在資料相關。
(3)相隔2條指令的指令間存在資料相關
考慮如下程式碼。
1 ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100
2 ori $3,$0,0xffff # $3 = $0 | 0xffff = 0xffff
3 ori $4,$0,0xffff # $4 = $0 | 0xffff = 0xffff
4 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120
第1條ori指令會寫暫存器$1,第4條ori指令在譯碼階段需要讀取暫存器$1,此時第1條指令處於回寫階段,在回寫階段最後的時鐘上升沿才會將運算結果寫入$1,所以第4條ori指令得到的不是正確的暫存器$1的值。如圖5-3所示。這種情況可以稱為相隔2條指令的指令間存在資料相關,針對OpenMIPS具體情況,也可以稱為流水線譯碼、回寫階段存在資料相關。
其中相隔2條指令存在資料相關(即流水線譯碼、回寫階段存在資料相關)這種情況,在第4章設計的Regfile模組中已經得到了解決,Regfile模組部分程式碼如下。
module regfile(
......
);
......
/****************************************************************
*********** 第三段:讀埠1的讀操作 *********
*****************************************************************/
// raddr1是讀地址、waddr是寫地址、we是寫使能、wdata是要寫入的資料
always @ (*) begin
......
end else if((raddr1 == waddr) && (we == `WriteEnable)
&& (re1 == `ReadEnable)) begin
rdata1 <= wdata;
......
end
/****************************************************************
*********** 第四段:讀埠2的讀操作 *********
*****************************************************************/
// raddr2是讀地址、waddr是寫地址、we是寫使能、wdata是要寫入的資料
always @ (*) begin
......
end else if((raddr2 == waddr) && (we == `WriteEnable)
&& (re2 == `ReadEnable)) begin
rdata2 <= wdata;
......
end
endmodule
在讀操作中有一個判斷,如果要讀取的暫存器,是在下一個時鐘上升沿要寫入的暫存器,那麼就將要寫入的資料直接作為結果輸出。如此就解決了相隔2條指令存在資料相關的情況。
對於相鄰指令間存在資料相關、相隔1條指令的指令間存在資料相關這兩種情況,有三種解決方法。
(1)插入暫停週期:當檢測到相關時,在流水線中插入一些暫停週期,如圖5-4所示。
(2)編譯器排程:編譯器檢測到相關後,可以改變部分指令的執行順序,如圖5-5所示。
(3)資料前推:將計算結果從其產生處直接送到其他指令需要處或所有需要的功能單元處,避免流水線暫停。如圖5-6所示的例子,新的$1值實際在第1條ori指令的執行階段已經計算出來了,可以直接將該值從第1條ori指令的執行階段送入第2條ori指令的譯碼階段,從而使得第2條ori指令在譯碼階段得到$1的新值。也可以直接將該值從第1條ori指令的訪存階段送入第3條ori指令的譯碼階段,從而使得第3條ori指令在譯碼階段也得到$1的新值。
讀者需要注意,第(3)種方法有一個前提就是新的暫存器的值可以在執行階段計算出來,如果是載入指令,那麼就不滿足這個前提,因為載入指令在訪存階段才能獲得最終結果,這是一種load相關,本書將在實現載入儲存指令的時候考慮這種情況,本章暫不考慮。
下一次將介紹OpenMIPS對資料相關問題的解決措施,敬請關注!