P4 流水線暫存器(logisim搭建)
一、寫在前面
-
首先,何為流水線CPU,流水線CPU和單週期CPU有什麼差別?
單週期CPU上所有指令都在一個時鐘週期內完成,所以其時鐘週期一般較長(能夠完成最慢的指令),吞吐量不高。出於增大吞吐量的考慮,引入了流水線CPU,同一時刻有多條指令在其上執行,因此理論上五段流水CPU的吞吐量是單週期的五倍。——指導書
-
那麼,如何實現多條指令同時執行?
五級流水線CPU按照指令執行的五個階段對應地分成了五個頂層模組:
IF(instruction fetch)
、DEC(decode)
、EXE(execute)
、ME(memory)
、WB(write back)
,每個模組執行一條指令的一個階段。
為了實現階段的分割與傳遞性,需要在單週期CPU裡新增4個暫存器:
IF/DEC
、DEC/EXE
、EXE/MEM
、MEM/WB
,給予統一的時鐘與復位訊號。每次時鐘上升沿到來,暫存器便會將前一個階段的資訊傳遞給後一個階段,即將所有指令均向後傳遞一個階段,從而實現互不干擾,階段推進。寫回模組後面不需要流水線暫存器的原因是:其一,它執行的是指令執行的最後一個階段,它的執行資料並不需要給對應指令的下一階段使用;其二,寫回部分是暫存器堆,本身可以看作“第五個流水線暫存器”。——指導書。
第五階段暫存器寫回暫存器不需要再等待一個時鐘上升沿,但讀出的rs與rt暫存器的值卻並不準確,這裡考慮內部轉發,後文介紹,這裡就不贅述了。
注意:①每條指令都要走完5個週期,儘管可能在某些階段沒有操作;②所有對暫存器的寫回,都在
WB
階段進行,這樣才能保證按照指令順序寫回暫存器,不會出現順序錯亂的情況。
-
實驗之前,需要注意什麼?
因為我們是基於已搭建的單週期CPU,將其更改為流水線CPU,為了之後的操作更加順暢,這裡建議:①確保單週期CPU搭建的正確性;②將盡可能多的部分模組化;③合併多選器:將一位選擇指令合併為多位選擇指令。
二、單條指令的新增與測試
單週期CPU已經實現了單條指令單獨走完一個週期的正確性,那麼在流水線CPU裡,是不是不用額外更改就可實現單條指令的完成?當然不是的。
讀者可以注意以下幾點:
-
各條指令資料是否通過暫存器傳遞
-
各條指令的控制訊號是否在正確階段產生
控制訊號由控制模組產生,但一個控制模組只服務於一個指令,而D、E、M、W四個階段分別執行著4條不同的指令(F階段無需生成控制訊號),很顯然一個control模組是不夠用的。這裡不妨採用分散式譯碼的方式(集中式譯碼讀者可自行探索),即放入4個control模組,對每個階段的指令進行譯碼操作。
下圖為M階段的control模組:
因為在進行該階段時,才需要判斷是否寫入儲存器,故我們只需要根據此階段的指令生成一個memw訊號。
-
一些特殊指令:
nop
:什麼都不用做j
、jal
與beq
:根據指令集,我們需要獲取PC高4位與指令生成跳轉地址,因為j
與jal
都在D階段判斷執行跳轉,故此二者都需從D階段取出(而不是F)。對於beq
指令,應注意此時IFU模組裡地址已為PC+4而不是PC。特需注意的是:jal
指令會將PC+4存入暫存器$31
中,而新增延時槽後實際存入的值為PC+8。為什麼是PC+8:因為延時槽的存在,在跳轉或分支指令之後,還會執行一條延時槽指令,該指令在來不及地址跳轉的時候就已經被執行。
IFU模組I/O資料:
-
關於除錯:
關於mips中程式碼的匯出與匯入機器碼,讀者可移步:線上教程 → ROM、RAM的使用和MARS匯出機器碼的講解。
這個階段的測試非常重要!因為還不考慮轉發和阻塞,所以造資料的時候應確保不要衝突,觀察暫存器堆的時候直接雙擊暫存器堆上的放大鏡即可,點選左側的模組是無法檢視實質內容的(哭,記得多加
nop
。
三、轉發
首先,介紹一些概念:
-
tnew和tuse:
對於指令的源暫存器s有時間tuse_D(/E/M),意思是在從D(/E/M)段開始,過幾個時鐘週期需要使用s裡的資料。對於指令的目的暫存器des有資料tnew_D(/E/M),意思是從D(/E/M)段開始,過幾個時鐘週期將要寫入des的資料產生。——指導書
簡而言之,①tuse只在判斷阻塞的時候使用,指離源暫存器被使用的階段還有多少個時鐘週期。舉個例子:對於addu指令而言,
$rs
和$rt
暫存器的值在E階段被使用,故tuse_D_rs=1,因為D離E階段還有一個時鐘週期,而tuse_D_rs=0。②tnew判斷離該階段目的暫存器資料準備好還有多少個時鐘週期,當tnew=0,說明資料已經準備完畢,可以轉發。舉個例子,對於lw指令而言,其在M階段進行取出儲存器裡資料的操作,故在W階段資料準備完畢,則對於lw指令,tnew_D=3,tnew_E=2,tnew_M=1,tnew_W=0。③對於每一個指令來說,其在每一個階段的tuse和tnew固定不變,那麼,如何獲取每一個階段指令的tuse與tnew?在控制模組用與或門的方式,新增控制訊號即可:
-
內部轉發
對於寫回模組到譯碼模組(W到D)的轉發,由於寫回階段寫入和譯碼階段讀出都是對於暫存器堆,我們可以採用內部轉發:通過暫存器堆的結構使得,在同一個時鐘上升沿,若有資料寫入暫存器r,也有從暫存器r讀出資料的請求,把將寫入資料直接讀出。這樣一來,寫回模組轉發到譯碼模組的資料可以不通過外部轉發,當然外部轉發也可。
內部轉發邏輯:暫存器堆的RD1輸出:是0當且僅當A1輸入為0; 是將要寫入暫存器A3的資料當且僅當將要寫入的暫存器編號A3和讀取暫存器編號A1相等; 是暫存器A1的資料當且僅當將要寫入的暫存器編號A3和讀取暫存器編號A1不相等。 暫存器堆的RD2輸出:是0當且僅當A2輸入為0;是將要寫入暫存器A3的資料當且僅當將要寫入的暫存器編號A3和讀取暫存器編號A2相等; 是暫存器A2的資料當且僅當將要寫入的暫存器編號A3和讀取暫存器編號A2不相等。
——指導書
什麼意思呢?我們需要在暫存器堆內部進行改造,看看圖就明白了:
那麼,何時轉發?
-
轉發都是從流水線暫存器出發,傳遞給各個執行階段的
為什麼會這樣?不如先來思考一下,如果想要轉發ALU運算結果,應該在什麼位置轉發?在E階段,資料還在進行運算,只有到E階段末尾,資料才被運算完成。但我們無法判斷E階段末尾,而E/M暫存器中傳遞的值就是已經運算完成的ALU結果,所以應當轉發E/M暫存器右側資料,此時M階段tnew為0,資料轉發是有效的。
那麼,從這個思路出發,我們就會發現:
-
一共有兩種轉發情況
- E/M流水線暫存器轉發: 轉發給E和D階段
- M/W流水線暫存器轉發:轉發給E,M,D階段。其中,轉發給D階段採用內部轉發方式。
可以發現,被轉發的資料只有E階段的運算結果和W階段的寫回資料,為什麼沒有對M階段存入地址的判斷過程?因為儲存器訪問地址不會造成衝突!
下面來看看具體模組:
-
內部:
從此圖我們可以看見模組對轉發操作的實現原理,當源暫存器等於目的暫存器且目的暫存器的值已經準備好的時候(tnew=0),進行轉發操作。當多個階段都滿足轉發條件,選擇資料最新的那個階段。
-
外部
瞭解了內部邏輯,再來看模組外觀,就比較好理解了。
說明:des_E不是指E階段ALU運算結果所在的目的暫存器,而是指M階段當前指令的目的暫存器。其含義雖是前者,但由於實際在M階段轉發,所以採用des_M來直觀表示。
四、阻塞
何時阻塞?
將要使用的資料來不及轉發過來,如果不阻塞就會使用錯誤的資料進行計算。由於分支指令最早在譯碼階段就需要使用暫存器資料計算,暫停階段放在譯碼階段,即DEC模組。
結合開始介紹的概念,每條指令i在譯碼段時(DEC模組執行指令i),要和它前面執行的指令j(前面模組執行的指令j,一般是前面的所有模組)對照判斷,看是否需要暫停:當i的源暫存器和j的目的暫存器相同時(設為暫存器k),i和j存在資料關聯,這時如果i的tuse小於j的tnew,代表k中資料還沒被j準備好(甚至不能轉發過來),這時需要暫停D段指令i,否則i會使用k中舊資料裡錯誤執行。
——指導書
簡而言之:① 資料關聯;② 來不及轉發;③ 如何判斷:tuse < tnew
怎麼阻塞?
將當前指令和其後面的指令全部“凍結”,而其前面的指令正常執行,以等待資料準備好。D段指令凍結也就是F/D暫存器仍然保持當前狀態,而此時F段中的pc已經取出下一條指令地址,為防止丟失,pc也保持當前狀態。再者,為了D段需要暫停的指令無法向後進行,需要在下一上升沿在E段插入氣泡。
故需要將D/E流水線暫存器同步復位(插入氣泡,使當前指令無法向下傳遞),且IF/DEC流水線暫存器和PC鎖定(維持當前狀態,即禁止使能),直到暫停的條件被打破。
——指導書
模組外觀如下,記得新增非門:
五、整體除錯
-
自造刁鑽的資料,① 先全部執行看暫存器堆裡的值是否符合預期、儲存器內的值是否正確,② 再單步除錯看每一步每一階段狀態是否與mars一致(探針是個好東西。
-
看測評結果,測評結果中含有WA測試點的的當前測試指令和相鄰測試指令,將其轉為二進位制,對照指令集可以看出是執行哪個指令的時候出了問題。不過,不一定是該指令本身出了問題,如果檢查後不是指令本身出錯,可能是以下兩種情況:
- 寫入的地址不對:跳轉或分支指令出錯
- 寫入的資料不對:涉及改變暫存器、儲存器資料的所有指令都有出錯可能。
-
這裡給出一組測試資料,讀者可以試試
ori $2,$1,0x1234 lui $1,0x5678 addu $1,$1,$2 jal a nop beq $2,$1,a nop lw $2,0($0) a: sw $1,0($0) lw $2,0($0) sw $2,4($0)
v2.0 raw 34221234 3c015678 00220821 0c000c08 00000000 10410002 00000000 8c020000 ac010000 8c020000 ac020004
output_reg: $1: 0x56781234 $2: 0x56781234 $31: 0x00003014 output_mem: 0x56781234 0x56781234
(當然過了這組資料可能依然過不了...讀者可以根據自己的測評情況製造更刁鑽的資料()