將陸續上傳本人寫的新書《自己動手寫CPU》(尚未出版),今天是第15篇,我盡量每周四篇
上一章建立了原始的OpenMIPS五級流水線結構,可是僅僅實現了一條ori指令,從本章開始,將逐步完好。
本章首先討論了流水線數據相關問題。然后改動OpenMIPS以解決該問題。並在5.3節驗證了解決效果。接着對邏輯、移位操作與空指令的指令格式、使用方法、作用進行了一一說明。在5.5節通過擴展OpenMIPS實現了這些指令,最后編寫測試程序,對實現效果進行了檢驗。
5.1 流水線數據相關問題
我們在第4章實現的五級流水線結構非常easy。假設依照“簡單即美(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是在指令i后面運行的指令,WAR表示指令i讀出數據后,指令j才干寫這個寄存器。如果指令j在指令i讀出數據前就寫該寄存器,將使得指令i讀出的數據不對。
- 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( ...... ); ...... /**************************************************************** *********** 第三段:讀port1的讀操作 ********* *****************************************************************/ // raddr1是讀地址、waddr是寫地址、we是寫使能、wdata是要寫入的數據 always @ (*) begin ...... end else if((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin rdata1 <= wdata; ...... end /**************************************************************** *********** 第四段:讀port2的讀操作 ********* *****************************************************************/ // 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對數據相關問題的解決措施,敬請關注!