【BUAA_CO_LAB】p5p6碎碎念
文章目錄
- 【BUAA_CO_LAB】p5&p6碎碎念
- 寫在前面的話
- 流水線知識(shí)
- 流水級(jí)與命名
- 流水級(jí)寄存器
- R型計(jì)算指令
- I型計(jì)算指令
- 內(nèi)存訪寫指令
- Branch類指令
- Jump指令
- Control Unit
- 轉(zhuǎn)發(fā)
- 阻塞
- p5の完結(jié)
- 改裝p6
- 乘除模塊
- 模仿乘除法運(yùn)算延遲
- 按字節(jié)訪存
- 課上指令之套路
- 運(yùn)算類
- 條件跳轉(zhuǎn)類
- 條件訪存類
【BUAA_CO_LAB】p5&p6碎碎念
寫在前面的話
首先前面必須要附上一段致歉。其實(shí)這篇文章應(yīng)該早在兩三個(gè)星期之前就更出來(lái)了,但是由于期末月的到來(lái),許多課程面臨結(jié)課大作業(yè)/考試等任務(wù),博主前段時(shí)間忙于各種大作業(yè)與其他事務(wù),也沒(méi)能很好地管理自己的時(shí)間(指周末睡大覺(jué)的屑),這個(gè)時(shí)候再發(fā)布,我估計(jì)大部分人其實(shí)應(yīng)該都已經(jīng)做完了這一部分了,所以這篇文章或許更多的意義在于“造福后人”罷。
不過(guò)看到了[p3&p4碎碎念]這篇博文下面的一些反饋,我個(gè)人還是很感動(dòng)的,至少發(fā)現(xiàn)自己寫的東西有人在看,有幫助到一些像我當(dāng)初一樣有困難的同學(xué),個(gè)中情愫實(shí)在是無(wú)以言表。這也給了我繼續(xù)寫沒(méi)什么技術(shù)含量的技術(shù)博客的動(dòng)力,以及繼續(xù)攻克計(jì)算機(jī)學(xué)習(xí)路上各種困難的決心!再次衷心感謝我的那幾位讀者!!(淚)
從p5開始,我們就從單周期CPU邁入了流水線CPU的旅程。這里的風(fēng)景是獨(dú)特的,其中的轉(zhuǎn)發(fā)、阻塞等原理或許會(huì)讓初遇的同學(xué)感到不知所措。剛開始寫的時(shí)候我也迷茫了很久,但其實(shí)只要細(xì)心體會(huì)個(gè)中原理,就會(huì)豁然開朗。我的架構(gòu)沒(méi)有借鑒gxp老師的PPT和黑書相關(guān)內(nèi)容,所以如果你已經(jīng)照著這些教程做出架構(gòu)了,就沒(méi)必要完全按照此文來(lái)。
我不是那種思維跳躍的聰明同學(xué),所以理解都是非常簡(jiǎn)樸的,在那些聰明同學(xué)看來(lái)或許會(huì)顯得過(guò)于繁瑣冗雜而可笑,但我希望記錄下自己的思路,以為后來(lái)似我者之鑒(至少我在掙扎于這兩p的時(shí)候是很希望有這樣一種胎教級(jí)教程可以看看的)。同時(shí),因?yàn)閜6事實(shí)上是在p5的基礎(chǔ)上加裝乘除相關(guān)模塊,沒(méi)有什么需要特別分開說(shuō)明的技術(shù)細(xì)節(jié),因此我采取了以p5為主的敘述模式,與p6有關(guān)的“加裝”部分我單獨(dú)放在**[改裝p6]**模塊里說(shuō)。
現(xiàn)在手握p4代碼的你,就可以跟著本胎教教程著手改造自己的單周期CPU,進(jìn)入流水線的新階段了。當(dāng)然,如有錯(cuò)誤,還望在評(píng)論區(qū)不吝指正。
流水線知識(shí)
這個(gè)時(shí)候大家應(yīng)該已經(jīng)在理論課學(xué)過(guò)流水線相關(guān)知識(shí),我就不再講述流水線的必要性、效率、基礎(chǔ)架構(gòu)之類的東西了。我們直接進(jìn)入流水線跟單周期CPU最不一樣的三點(diǎn):流水級(jí)、轉(zhuǎn)發(fā)與阻塞。
流水級(jí)與命名
理論課中已經(jīng)學(xué)到,為了使指令能夠像PPT例子中的洗衣服那樣同時(shí)安排(后面將大量使用“洗衣服”的例子),我們要細(xì)分出幾個(gè)“洗衣步驟”或者說(shuō)“流水線上的車間”,這就是我們的流水級(jí)。在書寫正式的流水線代碼之前,我們先基于p4代碼做出一個(gè)“Pipeline Without Hazard”,也就是完全不考慮轉(zhuǎn)發(fā)與阻塞,只是分出了流水級(jí)的CPU來(lái)。在課上,我們學(xué)到流水線CPU的基礎(chǔ)流水級(jí)架構(gòu)是這樣的:
傳統(tǒng)CPU有四大功能:取指、譯碼、執(zhí)行計(jì)算、存取,這不就剛好可以分成四個(gè)“洗衣步驟”嗎?同時(shí),由于后續(xù)轉(zhuǎn)發(fā)的特殊性,我們?cè)黾恿艘粋€(gè)寫回級(jí)(Write Back)。這樣就構(gòu)成了五個(gè)基礎(chǔ)的流水級(jí)。接下來(lái),我們就往五個(gè)流水級(jí)里塞更細(xì)的“洗衣步驟”,也就是各個(gè)模塊(當(dāng)然,別忘了重要的控制信號(hào)生成單元Control Unit)。4個(gè)流水級(jí)寄存器,不過(guò)是我們用來(lái)傳遞“衣服”,或者說(shuō)流水線上的“工件”的傳送帶而已,前一周期中的上一階段所傳來(lái)的數(shù)據(jù),和后一周期中為下一階段提供的數(shù)據(jù),都在且必須在這條傳送帶上流淌。無(wú)論是數(shù)據(jù)還是信號(hào),都需要在寄存器中進(jìn)行保存,直至不再需要。
增添模塊如下圖所示。
可以看到,除了綠色的[比較模塊CMP]以外其他都已經(jīng)在p4里寫過(guò)了,基礎(chǔ)架構(gòu)拿過(guò)來(lái)用就行。這里我們還要注意一點(diǎn),每個(gè)“洗衣小車間”是獨(dú)立的(請(qǐng)務(wù)必,務(wù)必,務(wù)必記住這個(gè)概念),只通過(guò)流水線傳送帶——即流水級(jí)寄存器連接,也就是說(shuō)在每一級(jí)產(chǎn)生的控制信號(hào)是具有其獨(dú)立性的。因此我們只需要寫出一個(gè)單獨(dú)的Control Unit,然后在頂端模塊中的不同流水級(jí)里對(duì)Control Unit分別進(jìn)行不一樣的實(shí)例化調(diào)用。(閱讀課設(shè)教程,你會(huì)發(fā)現(xiàn)這種寫法叫做“分布式”)
在把它們組合起來(lái)之前,要先給它們改個(gè)名字,同時(shí),輸入輸出各個(gè)模塊的信號(hào)名字都請(qǐng)命名成形如**[Pipeline Level] _ [Module name] _ [Signal name]**的形式(如E_ALU_Result),以辨析各個(gè)流水級(jí),避免后續(xù)接線之類的錯(cuò)誤。課程組教程的命名建議就非常好。我的命名是如圖中紅字所示。
-
綠色的[比較模塊CMP]
它是我們用來(lái)判斷Branch類跳轉(zhuǎn)是否發(fā)生的,它會(huì)通過(guò)對(duì)輸入的rs和rt值進(jìn)行判斷,給出一個(gè)“branch_or_not”信號(hào)指導(dǎo)跳轉(zhuǎn)的發(fā)生與否。有的同學(xué)可能在p3、p4時(shí)期就已經(jīng)開辟過(guò)類似模塊做過(guò)這件事了。下面給出我的代碼(p6版本),相信大家看了以后就明白它的作用了。這個(gè)模塊在上機(jī)的時(shí)候也會(huì)幫你大忙。
對(duì)了,請(qǐng)務(wù)必不要一字不差地復(fù)制,咱的課設(shè)還沒(méi)結(jié)束呢orz
我給出了這個(gè)新的模塊的參考寫法,剩下的模塊中,除了五個(gè)流水級(jí)寄存器和Control Unit,都可以基本照搬p4了,只需要按照給出的指令集要求稍作修飾即可(比如指令類型、所需的控制信號(hào)類型、存儲(chǔ)器的大小等)。
-
有一個(gè)值得注意的小地方是PC值運(yùn)算模塊NextPC。它的寫法與p4略有不同。有些指令的跳轉(zhuǎn)地址選擇的 PC 指令是它自己這個(gè)小車間對(duì)應(yīng)的相對(duì)獨(dú)立的“當(dāng)前指令”,而不是電路中宏觀的當(dāng)前指令,比如需要被 CMP 模塊判斷的分支跳轉(zhuǎn)指令和 J 型指令,它們的各類判斷發(fā)生在 D 級(jí)流水級(jí),因此跳轉(zhuǎn)地址對(duì)應(yīng)的“當(dāng)前指令”也應(yīng)該是 D 級(jí)流水級(jí)中的 PC 值 D_PC,而不是直接從F 級(jí)流水級(jí)的 IM 模塊發(fā)出的 F_PC。 其實(shí)這一點(diǎn),只要理解了前面“小車間獨(dú)立性”的敘述,就非常容易理解了對(duì)不對(duì)?洗衣服の例子,yyds……
`timescale 1ns / 1ps`define Branch 3'd0 `define J 3'd1 `define Jr 3'd2 `define Normal 3'd3module NextPC(input [31:0] D_PC,input [31:0] F_PC,input [25:0] Addr, //Addr or Imm16(Addr[15:0])input [31:0] reg_rs,input [2:0] NextType,input branch_or_not,output [31:0] NPC );assign NPC = (NextType == `Normal) ? (F_PC + 4) :(NextType == `J) ? {D_PC[31:28], Addr, 2'b00} :(NextType == `Jr) ? reg_rs :((NextType == `Branch) && (branch_or_not == 1'b1)) ? (D_PC + 4 + {{14{Addr[15]}}, Addr[15:0], 2'b00}) : (F_PC + 4);endmodule
下面介紹五個(gè)流水級(jí)寄存器和Control Unit的寫法。
流水級(jí)寄存器
首先我們應(yīng)該明確模塊的輸入輸出,也就是說(shuō),這個(gè)寄存器究竟要存“哪些衣服”?剛過(guò)過(guò)水的?剛打過(guò)泡的?這個(gè)時(shí)候,只要分析一下指令在流水線中流淌的過(guò)程就可以明確,也即,我希望將什么樣的信號(hào)傳給下游的“車間”來(lái)處理(請(qǐng)務(wù)必記住每個(gè)流水級(jí)是相對(duì)獨(dú)立的五個(gè)小車間!它們之間唯一的聯(lián)系就是一條流水線傳送帶!)。下面將指令分為R型計(jì)算指令、I型計(jì)算指令、內(nèi)存訪問(wèn)指令、Branch類指令和Jump類指令分析。
前文提到過(guò),為保證“小車間獨(dú)立性”,每一個(gè)流水級(jí)里都需要單獨(dú)實(shí)例化Control Unit,而光它一個(gè)人就需要指令編碼Ins[31:0]了吧,因此下文不再重復(fù)提及其必要性。而我想,理論課上應(yīng)該也已經(jīng)講過(guò)流水線CPU需要將當(dāng)前PC值不斷流水的特性,因此后面也不在每一級(jí)贅述了。它們是基礎(chǔ)。
時(shí)鐘信號(hào)clk、復(fù)位信號(hào)Reset和寫使能信號(hào)WE更是基礎(chǔ)中的基礎(chǔ),不必再說(shuō)了。
同時(shí),由于流水級(jí)寄存器為所有指令所共用,因此我會(huì)直接列出該流水級(jí)寄存器需要存儲(chǔ)的所有信號(hào),并在對(duì)應(yīng)的指令分析部分對(duì)該指令需要的信號(hào)進(jìn)行下劃線處理,望讀者辨析。
R型計(jì)算指令
- IF_ID - Ins[31:0] + PC[31:0]
- ID_EXE - Ins[31:0] + PC[31:0] + rs[31:0] + rt[31:0] + Ext[31:0]
- 眾所周知,R型指令的R指的就是Register,從通用寄存器堆里取出來(lái)的數(shù)據(jù),為了在E級(jí)對(duì)它們進(jìn)行算術(shù)運(yùn)算,GRF[rs]和GRF[rt]值是一定要傳遞到下一個(gè)小車間去的。
- EX_MEM - Ins[31:0] + PC[31:0] + ALU[31:0] + rt[31:0] + Ext[31:0]
- 接收ALU運(yùn)算結(jié)果
- MEM_WB - Ins[31:0] + PC[31:0] + ALU[31:0] + DM[31:0] + Ext[31:0]
- 接收ALU運(yùn)算結(jié)果,把ALU運(yùn)算結(jié)果寫回通用寄存器堆
I型計(jì)算指令
- IF_ID - Ins[31:0] + PC[31:0]
- ID_EXE - Ins[31:0] + PC[31:0] + rs[31:0] + rt[31:0] + Ext[31:0]
- I指立即數(shù)Immediate,我們需要從Ext模塊取出的處理好了的立即數(shù),在E級(jí)和GRF[rs]進(jìn)行算術(shù)運(yùn)算
- EX_MEM - Ins[31:0] + PC[31:0] + ALU[31:0] + rt[31:0] + Ext[31:0]
- 接收ALU運(yùn)算結(jié)果
- MEM_WB - Ins[31:0] + PC[31:0] + ALU[31:0] + DM[31:0] + Ext[31:0]
- 接收ALU運(yùn)算結(jié)果,把ALU運(yùn)算結(jié)果寫回通用寄存器堆
內(nèi)存訪寫指令
- IF_ID - Ins[31:0] + PC[31:0]
- ID_EXE - Ins[31:0] + PC[31:0] + rs[31:0] + rt[31:0] + Ext[31:0]
- 訪問(wèn):需要從Ext模塊取出的處理好了的立即數(shù),在E級(jí)和GRF[rs]進(jìn)行算術(shù)運(yùn)算,獲得訪問(wèn)數(shù)據(jù)存儲(chǔ)器的地址,具體參見指令集的RTL描述細(xì)節(jié)
- 寫入:需要將從通用寄存器堆里取出來(lái)的數(shù)據(jù)在E級(jí)進(jìn)行算術(shù)運(yùn)算,rs和rt都要
- EX_MEM - Ins[31:0] + PC[31:0] + ALU[31:0] + rt[31:0] + Ext[31:0]
- 訪問(wèn):接收ALU運(yùn)算結(jié)果
- 寫入:接收ALU運(yùn)算結(jié)果還有rt值,用于計(jì)算寫入地址
- MEM_WB - Ins[31:0] + PC[31:0] + ALU[31:0] + DM[31:0] + Ext[31:0]
- 接收內(nèi)存訪問(wèn)結(jié)果,把結(jié)果寫回通用寄存器堆
Branch類指令
不用麻煩后面,一切在D級(jí)完結(jié)(唯一需要麻煩后面的是轉(zhuǎn)發(fā)問(wèn)題,后面再說(shuō))
Jump指令
不用麻煩后面,一切在D級(jí)完結(jié)(唯一需要麻煩后面的是轉(zhuǎn)發(fā)問(wèn)題,后面再說(shuō))
這時(shí)你會(huì)發(fā)現(xiàn)好像還有幾個(gè)地方?jīng)]被標(biāo)過(guò)下劃線,沒(méi)關(guān)系,那是接下來(lái)即將要講的轉(zhuǎn)發(fā)部分需要的。先寫上吧!
Control Unit
對(duì)于這一部分,我覺(jué)得美工非常重要。整潔的排版可以有效地增加上機(jī)加指令的效率。總體上說(shuō),跟p4部分的架構(gòu)其實(shí)差不多,只是需要進(jìn)行一些排版。比如說(shuō),我們可以合并所有的R型指令為Cal_r等,這樣不僅有利于各種控制信號(hào)的生成,而且對(duì)后面要講的阻塞有大用處。下面我貼上我的“排版”,大家予以參考即可。
- Part_1: 宏定義
- Part_2: 信號(hào)定義分割
- Part_3: 中間信號(hào)分類
-
Part_4: 控制信號(hào)生成(寫之前一定要先列好表噢)
p.s. 在這里,我建議把所有需要用MUX選擇的部分(不管是選擇寫入地址也好,寫入數(shù)據(jù)也好),統(tǒng)統(tǒng)集成到這個(gè)地方來(lái),而不是再去費(fèi)勁心機(jī)地寫好多個(gè)不同位寬的MUX,吃力不討好,而且會(huì)顯得邏輯很亂。就如下圖的第一條assign一樣,把本該通過(guò)頂層模塊中MUX來(lái)選擇的寄存器堆寫入地址RegDst直接采用這種方式定義出來(lái),就非常方便,要用的時(shí)候直接引出一個(gè)RegDst就好了,根本不用在外面判斷。
類似的例子還有很多,比如MemToReg呀,ALU_Src_A和ALU_Src_B呀,都可以集成到CU這里來(lái)。
最后,我給出我對(duì)控制信號(hào)做出的安排(p5版本),以供大家參考。
至此,能吃p4老本的地方結(jié)束。
轉(zhuǎn)發(fā)
- 通俗的講解
轉(zhuǎn)發(fā)是流水線 CPU 中至關(guān)重要的環(huán)節(jié)。數(shù)據(jù)冒險(xiǎn)的嚴(yán)格定義請(qǐng)自行復(fù)習(xí),我在這里只作我非常簡(jiǎn)樸的講解。我認(rèn)為轉(zhuǎn)發(fā)需要存在的根源在于:流水線是一級(jí)一級(jí)地往下流的,有的時(shí)候當(dāng)前我們需要的GRF[rs]值或者GRF[rt]值還在后邊剛被算出來(lái),還沒(méi)來(lái)得及被傳回到通用寄存器堆里去,它事實(shí)上已經(jīng)產(chǎn)生了,只是流水線太拖拉,我們還夠不到它而已,因此我們需要一條捷徑,把已經(jīng)產(chǎn)生了但走得太慢,還來(lái)不及回到通用寄存器堆的數(shù)據(jù)迅速地傳到我們手中。
這就好比我們的洗衣服流水線,ID_EXE級(jí)的老哥負(fù)責(zé)把洗好的衣服打包送給客戶。現(xiàn)在客戶催著他要衣服,最后一級(jí)的老哥其實(shí)已經(jīng)把衣服洗好了,但衣服還在流水線上流著呢,偷工減料的黑心流水線走得過(guò)于慢了,還有好一會(huì)兒衣服才能到ID_EXE老哥這里。因此現(xiàn)在ID_EXE級(jí)老哥叫了一個(gè)叫轉(zhuǎn)發(fā)的小工蹲在最后一級(jí)的老哥那里,他一洗好,就火速奪過(guò)衣服跑步送給ID_EXE級(jí)老哥。在我們的流水線里,E級(jí)、M級(jí)和W級(jí)都蹲著一個(gè)叫轉(zhuǎn)發(fā)的小工,隨時(shí)準(zhǔn)備將處理好的衣服奪過(guò)來(lái)跑步送給寄存器,這就是轉(zhuǎn)發(fā)的意義所在。畢竟時(shí)間就是金錢,效率就是生命嘛。(怪不得叫暴力轉(zhuǎn)發(fā))
當(dāng)然,在這里我們假設(shè)的是客戶來(lái)要衣服的時(shí)候,衣服必然已經(jīng)洗好了。如果沒(méi)洗好,小工啥也沒(méi)拿到呢?這時(shí)就不得不讓客戶再等等了,這就是下一部分要講的“阻塞”問(wèn)題。判斷是否洗好的部分交給阻塞模塊,在轉(zhuǎn)發(fā)模塊里,我們默認(rèn)所有的衣服都已經(jīng)洗好了,這就是教程里所描述的“全力”轉(zhuǎn)發(fā)。
為了實(shí)現(xiàn)這一點(diǎn),我們首先要將“衣服”,也就是通用寄存器組的讀出值GPR[rs]和 GPR[rt]不斷地在各個(gè)流水級(jí)寄存器之間流水并且存儲(chǔ),然后通過(guò)當(dāng)前的指令生成的控制信號(hào)(一般是RegDst)來(lái)判斷是不是要發(fā)生轉(zhuǎn)發(fā)。如果我們現(xiàn)在需要的寫入地址值,也就是“客戶”,剛好跟某個(gè)流水級(jí)里的RegDst一樣,就說(shuō)明該客戶的衣服在這里洗好了,我正要往客戶那里送呢,那么轉(zhuǎn)發(fā)小工,就拿上衣服走人吧。
- 代碼細(xì)節(jié)
需要發(fā)生轉(zhuǎn)發(fā)的時(shí)間節(jié)點(diǎn)是:當(dāng)前的“不夠”的時(shí)候,也就是當(dāng)某一部件需要使用 GPR[rs]或GPR[rt]時(shí),這個(gè)值還沒(méi)來(lái)得及寫入 GPR 的時(shí)候。這個(gè)時(shí)候,我們需要通過(guò)轉(zhuǎn)發(fā)將這個(gè)值從流水線寄存器中送到該部件的輸入處。這也就說(shuō)明,**在每一個(gè)需要使用 GPR[rs]或者 GPR[rt]的端口節(jié)點(diǎn)前面,我們都可以對(duì)當(dāng)前的 rs 和 rt 值做出一個(gè)判斷,判斷是可以使用當(dāng)前的值,還是說(shuō)當(dāng)前的“不夠”,需要提前獲取后面的值。**教程中的原意應(yīng)該是用 MUX 來(lái)實(shí)現(xiàn),但是由于目標(biāo)信號(hào)數(shù)量的不定性,我認(rèn)為 MUX 模塊的設(shè)置并不太方便,同時(shí)我也不喜歡把轉(zhuǎn)發(fā)也集成成為一個(gè)模塊(線太多了),因此我選擇直接在頂層模塊連線的時(shí)候,在對(duì)應(yīng)端口模塊實(shí)例化的前面先對(duì)輸入信號(hào)做出一個(gè)判斷,如下圖所示。
根據(jù)對(duì)數(shù)據(jù)通路的分析,這樣的位點(diǎn)其實(shí)很有限,只有 D 級(jí) CMP 模塊的輸入、E 級(jí) ALU 模塊的輸入和 M 級(jí) DM 模塊的輸入(就是要用rs、rt值的地方嘛)。只要當(dāng)前位點(diǎn)的讀取寄存器地址和某轉(zhuǎn)發(fā)輸入來(lái)源的寫入寄存器地址相等且不為 0,就選擇該轉(zhuǎn)發(fā)輸入來(lái)源;在有多個(gè)轉(zhuǎn)發(fā)輸入來(lái)源都滿足條件時(shí),最新產(chǎn)生的數(shù)據(jù)優(yōu)先級(jí)最高,以實(shí)現(xiàn)全力轉(zhuǎn)發(fā)。
同時(shí)為了滿足“最新產(chǎn)生的數(shù)據(jù)”這一點(diǎn),我們需要在 GRF 的內(nèi)部實(shí)現(xiàn)內(nèi)部轉(zhuǎn)發(fā),也就是將輸入的數(shù)據(jù)實(shí)時(shí)反饋到輸出端口,避免數(shù)據(jù)的滯塞。
需要注意的是對(duì)當(dāng)前流水級(jí)來(lái)說(shuō),能夠選擇的轉(zhuǎn)發(fā)來(lái)源是“它之后與 GRF 之前”的流水級(jí)區(qū)間(小工總不可能把不洗衣服的車間里的衣服也搶了吧),因?yàn)槿绻麛?shù)據(jù)都到了 GPR 就不存在所謂的轉(zhuǎn)發(fā)與否的問(wèn)題了,衣服都已經(jīng)流到我手上了,直接拿不香嗎……因此對(duì) D 是 E、M(由于 W 的回寫行為與它直接對(duì)接,通過(guò) GPR 的反饋不存在轉(zhuǎn)發(fā)的問(wèn)題),對(duì) E 是 M、W,對(duì) M 是 W,而 W 級(jí)已經(jīng)不存在需要轉(zhuǎn)發(fā)的數(shù)據(jù)了。也就是說(shuō),關(guān)鍵其實(shí)就是“我需要的數(shù)據(jù)可能滯留在哪里”。
- 后邊加指令的時(shí)候也是一個(gè)道理,這一級(jí)要用,我就全力轉(zhuǎn)發(fā)(比如某些指令可能在M級(jí)也需要rs值,那你就需要多寫一個(gè)M_FWD_rs了),轉(zhuǎn)發(fā)的范圍是我之后到GRF之前,從新到舊,最后到我自己(本級(jí)的值)。就是這么簡(jiǎn)單的思路。
阻塞
比起轉(zhuǎn)發(fā),阻塞的道理其實(shí)簡(jiǎn)單得多,就是在轉(zhuǎn)發(fā)沒(méi)有辦法解決問(wèn)題的時(shí)候,比如說(shuō)新的數(shù)據(jù)根本還沒(méi)有產(chǎn)生的時(shí)候,向進(jìn)程中插入“氣泡”暫緩流水,等信號(hào)產(chǎn)生了以后再開始流動(dòng),并實(shí)現(xiàn)數(shù)據(jù)的獲取(通過(guò)轉(zhuǎn)發(fā)等方式)。而 “新的數(shù)據(jù)根本還沒(méi)有產(chǎn)生”的條件是【Tuse < Tnew 且滿足轉(zhuǎn)發(fā)條件】,就是說(shuō)需要用數(shù)據(jù)了,客戶來(lái)了,但我的衣服還踏馬沒(méi)洗好呢。
在CPU中的行為就是把指令暫停在 D 級(jí),也即用一個(gè) Stall 信號(hào)來(lái)參與控制該級(jí)流水寄存器的寫入數(shù)據(jù)行為,使得后面的值不會(huì)被更新,相當(dāng)于插入了一個(gè) nop,或者說(shuō)是一個(gè)“氣泡”,讓中間的模塊安靜地執(zhí)行完現(xiàn)在的進(jìn)程,等“氣泡”消除了之后,把新的值寫到流水寄存器中繼續(xù)開始流水。
插入“氣泡”的具體操作是:凍結(jié)PC值停止流水線運(yùn)行(流水線再走衣服真的洗不完了哥,停一會(huì)兒罷),清空ID_EXE(后邊的慢慢洗罷,不再給你們派新活了,把手頭的洗完再說(shuō))。看到這個(gè)描述,你應(yīng)該非常清楚前者通過(guò)WriteEnable寫使能信號(hào)完成,后者通過(guò)Reset復(fù)位信號(hào)完成。因此我們只需要新建一個(gè)能夠生成阻塞信號(hào)的阻塞模塊,然后將它產(chǎn)生的阻塞信號(hào)分別與部分寄存器的WE信號(hào)和部分寄存器的Reset信號(hào)與一下就可以了。
- 與之類似的一個(gè)操作叫做【清空延遲槽】,它的動(dòng)作是清空IF_ID,但是并不凍結(jié)PC值,更不會(huì)清空ID_EXE,請(qǐng)閱讀英文指令集有關(guān)內(nèi)容自行思考。
Tuse:指令進(jìn)入 D 級(jí)后,其后的某個(gè)功能部件再經(jīng)過(guò)多少時(shí)鐘周期就必須要使用寄存器值。對(duì)于有兩個(gè)操作數(shù)的指令,其每個(gè)操作數(shù)的 Tuse 值可能不等(如 store 型指令 rs、rt 的 Tuse 分別為 1 和 2 )。用D級(jí)的信號(hào)判斷。
Tnew:位于 E 級(jí)及其后各級(jí)的指令,再經(jīng)過(guò)多少周期就能夠產(chǎn)生要寫入寄存器的結(jié)果。在我們目前的 CPU 中,W 級(jí)的指令 Tnew 恒為 0;對(duì)于同一條指令,Tnew@M = max(Tnew@E - 1, 0)。 后面每一級(jí)都要具體判斷。
似乎有很多人喜歡在這里畫表,但我感覺(jué)不用吧,自己想一想就能明白了,如下圖所示。比如對(duì)于Tuse,branch類和J類的跳轉(zhuǎn)與否的判斷在D級(jí)就可以完成,自然是0(不用再找后面的了),需要E級(jí)的ALU運(yùn)算結(jié)果的就再往后一個(gè)周期,就是1,后面的以此類推了。需要注意的是對(duì)于無(wú)關(guān)的東西我們要把Tuse設(shè)置為一個(gè)很大的值(比如我的3’d5),相當(dāng)于一直塞著。
根據(jù) Tuse 和 Tnew 所提供的信息,可以得出:當(dāng)滿足轉(zhuǎn)發(fā)條件(詳見“轉(zhuǎn)發(fā)”部分),且 D 級(jí)指令的 Tuse 小于對(duì)應(yīng) E 級(jí)或 M 級(jí)指令的 Tnew時(shí),我們就在 D 級(jí)暫停指令。在其他情況下,數(shù)據(jù)冒險(xiǎn)都可以通過(guò)轉(zhuǎn)發(fā)解決。
從 Tuse 和 Tnew 的表達(dá)式,我們也能看出需要在每一個(gè)流水級(jí)都判斷一下當(dāng)前的 Tuse 和 Tnew 值,以完成比對(duì)。這整個(gè)過(guò)程可以看成一個(gè)這樣的流程:
當(dāng)然了,在“全力轉(zhuǎn)發(fā)”的思路下,我們的代碼里已經(jīng)沒(méi)有GRF后面的“可與不可”的判斷了(管它現(xiàn)在有沒(méi)有呢,滿足轉(zhuǎn)發(fā)條件的直接轉(zhuǎn)發(fā)),這個(gè)圖只是對(duì)轉(zhuǎn)發(fā)必要性的一個(gè)更直觀的詮釋。
p5の完結(jié)
最后,在頂端模塊里把所有的模塊都實(shí)例化并連接起來(lái)吧——你的流水線CPU就這樣誕生了。如果不清楚接線細(xì)節(jié),可以參考PPT等資源,但我感覺(jué)只要把上面的邏輯捋清了,再結(jié)合p4的基礎(chǔ),搭出美麗的p5、p6并不是問(wèn)題。接下來(lái)就是測(cè)試等環(huán)節(jié),討論區(qū)的同學(xué)珠玉在前,我也不在此班門弄斧了,請(qǐng)大家好好利用身邊同學(xué)的資源吧!!
本學(xué)期課設(shè)結(jié)束后,我會(huì)把我的代碼上傳到Github上去,屆時(shí)會(huì)在本博文評(píng)論區(qū)里附上網(wǎng)址,以供后來(lái)人參考。
改裝p6
p6跟p5的區(qū)別在于多了很多指令,以及乘除相關(guān)的{mult, div, multu, divu, mthi, mtlo, mfhi, mflo}。對(duì)于前面新增的指令,相信只要通過(guò)了p5課上的同學(xué)都能輕松地照葫蘆畫瓢給它加上去,在此就不贅述了。唯一麻煩的就是乘除相關(guān)模塊的加裝以及訪存數(shù)據(jù)存儲(chǔ)器時(shí)對(duì)不同位寬數(shù)據(jù)的處理。我們?cè)趐5的基礎(chǔ)上進(jìn)行改裝,從而實(shí)現(xiàn)這兩個(gè)地方。
乘除模塊
乘除法是一種算術(shù)運(yùn)算指令,因此我們把單獨(dú)開辟出來(lái)的乘除模塊放在E級(jí),我給它取名叫E_HILO。在這里,我們需要完成乘除法的算術(shù)運(yùn)算,并且模仿乘除法運(yùn)算延遲的行為和從HI、LO寄存器中取值的行為。
模仿乘除法運(yùn)算延遲
乘除法運(yùn)算在電路中進(jìn)行得很慢,所以我們需要進(jìn)行一個(gè)模仿。這里我想到了之前學(xué)過(guò)的有限狀態(tài)機(jī),我們可以設(shè)置一個(gè)Cycle狀態(tài)信號(hào),執(zhí)行過(guò)乘除法后,它就充當(dāng)一個(gè)計(jì)時(shí)器,等Cycle里的延遲時(shí)間走完了之后,我們?cè)龠M(jìn)行后面的操作。乘除法部件中內(nèi)置了 HI 和 LO 兩個(gè)寄存器,這兩個(gè)寄存器,同時(shí)也是這個(gè)模塊與外界溝通的唯一窗口。
我們假定乘 / 除部件的執(zhí)行乘法的時(shí)間為 5 個(gè) cycle (包含寫入內(nèi)部的 HI 和 LO 寄存器),執(zhí)行除法的時(shí)間為 10 個(gè) cycle。你在乘 / 除部件內(nèi)部必須模擬這個(gè)延遲,即通過(guò) Busy 輸出標(biāo)志來(lái)反映這個(gè)延遲。
想明白了“計(jì)時(shí)器”的實(shí)現(xiàn)后,代碼寫起來(lái)就沒(méi)什么技術(shù)含量,下面是我的,僅供參考。
always@(posedge clk)beginif (reset)begin//Reset all the Signalsendelse beginif (cycle == 0)beginif (HILO_mthi)//Operationelse if (HILO_mtlo)//Operationelse if (HILO_mult)beginbusy <= 1'b1;cycle <= 5;//Operation of MULTendelse if (HILO_multu)beginbusy <= 1'b1;cycle <= 5;//Operation of MULTUendelse if (HILO_div)beginbusy <= 1'b1;cycle <= 10;//Operation of DIV (Attention: Split the result into HI and LO!!!!)endelse if (HILO_divu)beginbusy <= 1'b1;cycle <= 10;//Operation of DIVU (Attention: Split the result into HI and LO!!!!) endendelse if (cycle == 1)beginbusy <= 1'b0;cycle <= 0;HI <= tmp_hi;LO <= tmp_lo;endelsecycle <= cycle - 1;endend按字節(jié)訪存
由于我們這一學(xué)期的p6改為了【存儲(chǔ)器外置】的形式,據(jù)我所知跟之前有所不同,我也不確定以后是不是這樣,所以我就不寫存儲(chǔ)器外置的寫法了(課程組可能會(huì)有具體要求,實(shí)現(xiàn)也不難的,在頂端模塊里處理就行)。在這里貼上一個(gè)通用版本的支持按字節(jié)訪存的DM代碼。(好像p4已經(jīng)貼過(guò)了?)
`timescale 1ns / 1ps`define BW_word 2'b00 `define BW_halfword 2'b01 `define BW_byte 2'b01`define word DataMemory[DM_A[15:2]] `define halfword `word[15 + 16 * DM_A[1] -:16] //-意為從此開始往下16位,同理,如果是+就是往上16位。這么寫的原因是因?yàn)镈M_A并非是一個(gè)constant,在Verilog的語(yǔ)法定義里,不可以用變量的值表示向量位寬,除非采用這種寫法,不然必報(bào)錯(cuò) `define half_sign `word[15 + 16 * DM_A[1] -:1] `define byte `word[7 + 8 * DM_A[1:0] -:8] `define byte_sign `word[7 + 8 * DM_A[1:0] -:1]module M_DM(input [31:0] DM_A,input [31:0] DM_WD,input [1:0] BW,input [31:0] PC,input MemWrite,input Clk,input Reset,output [31:0] DM_out );reg [31:0] DataMemory [3071:0];assign DM_out = (BW == `BW_word) ? (`word) :(BW == `BW_halfword) ? {{16{`half_sign}}, `halfword} :(BW == `BW_byte) ? {{16{`byte_sign}}, `byte} : `word;integer i;initial beginfor (i = 0; i < 3072; i = i + 1)beginDataMemory[i] = 32'd0;endendalways@(posedge Clk)beginif (Reset)beginfor (i = 0; i < 3072; i = i + 1)beginDataMemory[i] = 32'd0;endendelse if (MemWrite)begin$display("%d@%h: *%h <= %h", $time, PC, DM_A, DM_WD); `word <= DM_WD;`halfword <= DM_WD[15:0];`byte <= DM_WD[7:0];endendendmodule到這里,其實(shí)p6就完成了,沒(méi)有想象中那么難罷。
但是注意:p6的指令很多,加的時(shí)候一定要看清楚機(jī)器碼等細(xì)節(jié),尤其是在處理控制信號(hào)的時(shí)候。
課上指令之套路
課上指令可以總結(jié)為三類:R型/I型運(yùn)算類(運(yùn)算),B+alr類(條件跳轉(zhuǎn)),lw/sw + [condition]類(條件訪存)。它們都有自己對(duì)應(yīng)的套路。
運(yùn)算類
如果你在Control Unit里做好了中間信號(hào)分類,那么運(yùn)算類就是小菜一碟。在Control Unit里先宏定義好新指令信號(hào),將其合并入cal_r或者cal_i中,這樣你就不用再理會(huì)任何與阻塞/轉(zhuǎn)發(fā)有關(guān)的東西了。然后,為它分配一個(gè)新的ALUOp碼,去ALU模塊里定義好它的具體運(yùn)算操作,基本上就完成了。一般來(lái)說(shuō)加這個(gè)運(yùn)算類指令可以在5分鐘內(nèi)解決戰(zhàn)斗——只要你課下沒(méi)錯(cuò)。所以,做好測(cè)試!!!
運(yùn)算類也可能會(huì)出現(xiàn)“條件寫”的要求,比如運(yùn)算結(jié)果滿足某條件才寫回,這里只需要在E級(jí)把你運(yùn)算得到的結(jié)果轉(zhuǎn)化成條件真值,然后傳到E級(jí)Control Unit里去指導(dǎo)RegDst就可以了。
條件跳轉(zhuǎn)類
一般是B+alr類。首先,在Control Unit里將它合并到branch類中,此后阻塞不必再理。然后為它分配合適的BranchType等控制信號(hào),到對(duì)應(yīng)的模塊中判斷跳轉(zhuǎn)是否發(fā)生,并且生成好NextPC的值(根據(jù)題干具體定)。這時(shí),我們可以得到來(lái)自CMP模塊的判斷信號(hào)branch_or_not(根據(jù)判斷條件的不同,可以酌情向CMP模塊中加入信號(hào))。用這個(gè)branch_or_not信號(hào),我們可以在D級(jí)中定義是否發(fā)生跳轉(zhuǎn)的信號(hào)為一個(gè)check信號(hào):wire check =branch_or_not,然后將這個(gè)信號(hào)流水下去。如果新的指令需要你在條件跳轉(zhuǎn)的同時(shí)寫寄存器(比如寫$31),那么就在每個(gè)流水級(jí)都將這個(gè)check信號(hào)傳入到控制信號(hào)生成器中去,和指令信號(hào)一起指導(dǎo)RegDst等信號(hào)的生成。
條件訪存類
在這里,第一步依然是合并指令到load類或者store類中,進(jìn)行一些常規(guī)的控制信號(hào)處理操作。不過(guò)這時(shí)我們不能再對(duì)阻塞模塊和轉(zhuǎn)發(fā)置之不理。因?yàn)楦鶕?jù)題目的特定要求,它有的時(shí)候在M級(jí)需要rs值(只是舉個(gè)例子),這個(gè)時(shí)候我們就需要在M級(jí)加上對(duì)rs的轉(zhuǎn)發(fā);
在阻塞模塊里,我們也要做出修改,因?yàn)槲覀冊(cè)贛級(jí)才能得到條件真值,這個(gè)時(shí)候如果D級(jí)有需要rs、rt的動(dòng)作(有客戶來(lái)取衣服了),E級(jí)的情形就是不確定的,因此我們需要在E級(jí)進(jìn)行一個(gè)保守的條件約束:如果E級(jí)也是該條指令,并且此時(shí)D級(jí)要用該條指令要寫入的寄存器,我們就stall。形如:
當(dāng)然根據(jù)不同指令的要求不同,這里給E級(jí)強(qiáng)加的暫停條件也不同,請(qǐng)大家根據(jù)指令的RTL描述自行變通。但我認(rèn)為條件暫停的根本是只約束E級(jí),因?yàn)槭聦?shí)上只有這里可以產(chǎn)生RegDst等控制信號(hào)的不定值(也有人認(rèn)為M級(jí)也要約束,但對(duì)于課上測(cè)試結(jié)果應(yīng)該是一樣的)。
本文到此就結(jié)束了,如果文中有錯(cuò)誤,或者有我還說(shuō)得不清楚的地方,歡迎大家在評(píng)論區(qū)留言告知。雖然也沒(méi)幾個(gè)人看,但是已有的讀者的反饋真的讓我感到非常感動(dòng),今后我也會(huì)努力學(xué)習(xí)并分享各種知識(shí)(寒假我會(huì)選擇分享一些獨(dú)立游戲制作的內(nèi)容,比如unity3d和GMS2),力求能夠幫助到有需要的人!謝謝!!(鞠躬
總結(jié)
以上是生活随笔為你收集整理的【BUAA_CO_LAB】p5p6碎碎念的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何使用MonoDevelop调试Uni
- 下一篇: 前端学习(1903)vue之电商管理系统