1. 程式人生 > >基於DLX指令集的5級流水線CPU設計與實現

基於DLX指令集的5級流水線CPU設計與實現

一、淵源

誕生於1977年的英特爾8086以現在微電子專業本科生的水平完全可以做出來,龍芯的負責人胡偉武的畢業設計作品就是8086CPU。

我們學過的大三的時候看了《編碼》後覺得比較有感覺就寫了一篇文章叫作《從零開始構建一臺計算機》,主要說了一下對編碼思想的理解,只記得當時心情相當激動,好像二進位制世界剛剛向我開啟。很重要的一部分是以自己的理解說了一下CPU與介面的相互作用關係,這是因為那時與微控制器正打得火熱。當時對於CPU的理解幾乎為零,所以一筆帶過了CPU的構造與工作原理,騙自己說那是非常複雜的東西,一直把它奉作系統大腦,卻從不知道它到底是什麼。但對CPU原理的理解對於寫出高效的程式是很關鍵的。上個學期,一個“神童”級的人物出現了,王超。這個感覺還不如我們年齡大的科大博士後,教我們《現代微處理器體系結構》,不得不說收穫很大,對如何設計、實現、測試、分析、評估、優化一個CPU有了比較清晰的認識。考試完之後一直想整理整理,一直懶得動,這項任務像一塊石頭一樣堵在心口,現在我想好好寫寫,作為上個學期的真正結束。基礎部分主要是之前課上課下的的筆記,實現部分主要是實驗室老大單麾揚的傑作,他用了兩天時間在modelsim下用verlog語言編寫了整個工程,這個西工大的哥哥,真是各種令人折服。

二、基礎

1、從系統角度和程式執行角度體會CPU概貌

我們沿用至今的馮諾依曼提出的計算機系統硬體結構:運算器、控制器、儲存器、輸入裝置、輸出裝置。其中運算器和控制器從功能角度來說就是中央處理單元CPU。儲存器指的是記憶體,即RAM,如果是哈佛結構則程式碼空間和資料空間是隔離的。若是馮諾依曼結構則是程式碼和資料混合儲存哈佛結構的微處理器通常具有較高的執行效率。其程式指令和資料指令分開組織和儲存的,執行時可以預先讀取下一條指令。而馮諾依曼結構實現起來簡單而且成本較低。我們的設計用的是哈佛結構。CPU就像是輸入與輸出之間的一個黑盒子,這個黑盒子實現了編制的演算法,對輸入進行運算,並輸出。

從程式的執行流程更能體會出CPU的角色,高階語言(如C或Java)寫的程式經編譯器(*.c->*.s),彙編器(*.s->*.o),連結器(*.o->*.elf),生成了可執行檔案,生成的可執行檔案放到記憶體裡。

一句C語言編譯成組合語言的舉例:

c=a+b;

LD R1,8000  ;a

LD R2,8001  ;b

ADD R3,R1,R2

SD R3,8003  ;C

對於不同的CPU,認識的指令不同,助記符也不同,上面是隨意舉例。彙編器將助記符變成二進位制程式碼時,將會根據指令集的格式要求變成由操作碼和運算元組成的01序列,CPU對操作碼進行譯碼就知道該做些什麼事情(邏輯電路部分開關決定功能流向)。在進行會變執行時,CPU從記憶體中取指令、取資料,進行取指、譯碼、執行(計算、訪存)、寫回等操作,I/O訪問記憶體,將資訊讀出,顯示給使用者。可以看出CPU的能力,就是訪存和計算(加法,減法可以用加法實現,乘法可以分成加法,除法同理)所以資料交換和計算配合專用外圍功能模組就可以實現多姿多彩的計算機世界。縱觀CPU的發展:

1971年:4004,4位CPU,,2300個電晶體,46條指令,4004+RAM+ROM+暫存器晶片=第一臺微型計算機。

1972年:8008最早的8位微處理器。

1973年:8080 效能是4004的20倍,得益於MOS管電路的發展。

1977年:16位的8086,VLSI工藝取得了突破進展…

從4004的2300個電晶體到今天擁有4億1000萬電晶體的雙核微處理器,工作電壓從12伏特到1.2伏特。現在的發展趨勢:核整合度、頻率擴增、核擴增。而CPU發展存在的障礙也變得十分突出,工藝不能做的無限小:這是自然規律,細菌-病毒-蛋白-..原子。導線越細電阻越大。頻率不能無限增大:不考慮設計因素,主頻的提升帶來功耗飛昇。DRAM和磁碟的整合度雖然提高,但是訪問速度在過去的多少年間並沒有大的提升,所以CPU與記憶體的矛盾越來越加劇(因為流水線的頻率是由關鍵路徑段決定的,訪存不能太久,單純以增加深度提高頻率容易產生分支預測、功耗等問題,因此流水線長度的尺度把握一直是處理器設計中的一個重要核心問題)。

2、CPU設計的重點:指令集和流水線,指令集就是協議

設計CPU的關鍵就是採用的指令集(ISA)和流水線(pipeline)結構。根據所採用的指令集,設計流水線實現它,並消除結構相關、資料相關、控制相關、等。

指令集是CPU能夠識別的指令的命令的集合。是由操作碼和運算元組成的序列二進位制碼,從助記符根本看不出來,程式編譯的時候會把組合語言變成標準的指令格式。指令集代表了CPU做事的方法和能力。指令集描述了一種二進位制指令及其執行規則,大家都拿這個規範去設計自己硬體電路,實現所規定的功能,這個過程中最重要的是解決結構相關、資料相關、。相當於一種“通訊協議”,你愛遵守不遵守,你遵守了之後就可以用一樣的編譯器,就可以互跑對方的程式,就像遵守一樣的通訊協議就能通訊,一樣的程式,一樣的指令集去編譯,用不同的硬體去跑,執行的結果一樣(除了某些浮點數計算的協議不一樣),但實現的過程不盡相同,就像分別用STM32和51去實現MODBUS協議,能達到一樣的目的,但走不同的路。

很多人看了,龍芯採用的MIPS指令集,就大感失望,以為核心技術又是用的人家的。其實不然,設計一個指令集是一件很簡單的事情,你可以,我可以,大家都可以。就相當於寫一個協議,但如果這個協議只有你自己遵守,那就完全沒有意思了。一個CPU是否強悍是否自主研發,不在於其採用了什麼指令集,而在於它對指令集的實現能力!就好像一本小說是否精彩,不在於使用英文字母書寫還是用阿拉伯字母書寫的一樣!定義指令集沒什麼技術含量,但是實現這個指令集就需要科研人員付諸巨大的努力了!舉個最簡單的例子:Intel和AMD這麼多年都採用的是Intel的X86指令集,但是CPU的效能卻越來越強,主頻越來越高,換句話說這麼多年Intel和AMD都是在研發X86指令集的更強實現!真正有挑戰性的工作是研發指令集的實現!這才是自主研發的核心。其中邏輯設計、數字積體電路設計等都是關鍵,一點點的時延、功耗都可能帶來效能的驟降。

為什麼要買指令集授權?

龍芯買的其實根本不是專利授權,而是商標授權。買了之後,龍芯可以標上“MIPS相容”的標識。這意味著,以前為MIPS架構開發的軟體都可以直接在龍芯上用了。沒這個標識,別人是不敢用龍芯的。有些人認為自主產權就該開發一套自己的架構。這是完全不考慮生存的人才能幹的事情。自己的一套架構,開發工具、中介軟體和應用軟體都只能自己開發了。你還沒佔領市場,誰閒的沒事給你開發軟體啊?你沒有應用軟體,你佔領哪門子市場啊?明眼人一眼就看出,龍芯買授權,一定是為大規模商業行動做的準備。有了MIPS授權,就好賣了。MIPS前些日子剛剛和如日中天的Android牽了手,MIPS架構的處理器又佔據了PS/3這類產品的市場很久,龍芯打上“MIPS”的標籤,不久可以在很多場合大賣。估計一年之內就會有這類的訊息了。所以不必為龍芯是不是自主智慧財產權操心了。關注一下龍芯如何跟英特爾這些霸王對決倒是正著。

指令集分成RISC、CISC。CISC與RISC只是一種設計理念的區別,沒有十分具體,界限明顯的區別。一個是讓CPU做更多更復雜的事情(執行那樣的指令),硬體電路比較複雜。一個是CPU做的事情都是基礎的(28原則有20%的指令使用頻率最大,佔執行時間的80%),編譯器做的更多些。基於CISC的CPU已經不多見,這是有時代背景的,早期儲存器容量非常小,程式碼不能太長,所以要設計一些功能強大的指令,用硬體實現其功能,打個比方,乘法可以分拆成若干次加法,CISC就單獨做一條乘法指令,而RISC在編譯階段就用多條加法指令代替該懲罰指令。

這種時代的背景可以從另一角度的指令集的分類上體會。根據CPU中用來儲存運算元的儲存單元來分類,典型的CPU提供的暫存器單元通常有:暫存器、堆疊和累加器(堆疊只是一種身份),這三種不同的類的指令集反映了當時的工藝和體系結構水平。分別看:堆疊型、累加器、暫存器型指令執行加法指令的區別,以認識它們的分別實現相同計算的過程,體會CPU做事方式與能力跟指令集是密切相關的。

累加器型:

LOAD A     //LOAD   X      AC<--M[x]

ADD  B     //ADD    X     AC<--(AC)+ M[x]

Store C    //STORE  X      M[x]<--(AC)

堆疊型:

PUSH A    //PUSH   x         stack[sp]<--M[x];sp=sp-1

PUSH B                 

ADD                       stack[sp+1]<--stack[sp]+stack[sp+1];sp=sp+1

POP   C                   M[x]<--stack[sp];sp=sp+1

暫存器型的指令所有運算元均需命名,且要顯式表示(隱式的意思是我要操作的數已經約定好在堆疊或者累加器中了),因而指令比較長。可以生成比較高效的程式碼。暫存器型的指令集流行的原因:早期的大多數機器都是採用堆疊型或累加器型指令集結構,但是自1980年以來的大多數機器均採用的是暫存器型指令集結構。原因是,積體電路技術飛速發展。暫存器和CPU內部其它儲存單元一樣,要比儲存器快。對編譯器而言,可以更容易有效地分配和使用暫存器。

可以將當前大多數通用暫存器型指令集結構進一步細分為三種類型:

1、暫存器-暫存器型ADD R1,R2,R3

2、暫存器-儲存器型ADD R2,R1,C

3、儲存器-儲存器型ADD A,B,C

第一種的優點:簡單,指令字長固定,是一種簡單的程式碼生成模型,各種指令的執行時鐘週期數相近。缺點:通過Load/Store指令條數多,因而其目的碼較大。第二種的優點:可以直接對儲存器運算元進行訪問,容易對指令進行編碼,且其目的碼較小。缺點:指令中的運算元型別不同。在一條指令中同時對一個暫存器運算元和儲存器運算元進行編碼,將限制指令所能夠表示的暫存器個數。由於指令的運算元可以儲存在不同型別的儲存器單元,所以每條指令的執行時鐘週期數也不盡相同。第三種的優點:是一種最緊密的編碼方式,無需“浪費”暫存器儲存變數。缺點:指令字長多種多樣。每條指令的執行時鐘週期數也大不一樣,對儲存器的頻繁訪問將導致儲存器訪問瓶頸問題。

設計指令集除了要設計指令集結構和功能,包括:指令寬度、型別、條數、操作碼、運算元等,還要關心定址技術。定址就是如何根據指令的操作碼的指示尋找運算元?它是CPU在實現指令功能時暗含的操作。比較常見的定址方式如下:

暫存器定址 Add R4 , R3

立即數定址 Add R4 , #3

直接定址(絕對定址)   Add R1 , (1001)

暫存器間接定址    Add R4 , (R1)

偏移定址Add R4 , 100(R1)

到此,已經對指令集的概念做了比較詳細的敘述,對於DLX指令集,分為I型、J型、R型指令,它們的指令格式分別如下:

如果按照功能分類的話,DLX指令又可以分為ALU指令,訪存指令、跳轉指令,指令的功能和具體的操作如下:

CPU的硬體就是用來實現上述指令所規定的功能。DLX指令集結構的指令格式、定址方式和操作都非常簡單。也許有人會擔心,這些特性會使得目的碼中指令條數增多,導致程式執行時間加長,從而使這種指令集結構的機器效能並不會太高。

3、流水線—20世紀最偉大的發明

發明流水線的竟然是通用汽車的福特,大大提高了生產效率。當然汽車生產流水線跟CPU中的流水線是不同的概念,但道理都一樣。假設裝配一輛汽車有十個步驟,每個步驟耗時1小時,一個人裝配一整輛汽車就需要十小時。而若是有10個人,每個人只幹一個步驟,幹完之後傳遞給下一個人,這樣,滿負荷下1個小時就能生產一輛汽車。電視劇《我的兄弟叫順溜》,順溜拿子彈、裝子彈、瞄準發射各需要一分鐘,也就是打一個子彈需要三分鐘,他叫來兩個隊友,一個負責遞子彈給另一個,另一個負責裝子彈,順溜只負責瞄準發射。這樣1分鐘就可以打出一發子彈。指令的執行一般分為:取指、譯碼、執行、寫回。若是每次只執行一條指令,則指令執行時間將大大增加。即相當於有四個人,每個人只幹一樣活,第一個週期第一條指令取指、第二個週期第二條指令取指,第一條指令譯碼,第三個週期第一條指令執行,第二條指令譯碼,第三條指令取指…依次流動。可以看出流動的頻率取決與這四個階段中需要執行的最長時間的那個。比如四個階段分別需要執行1、1、2、1個週期,那麼流動週期就是2。真實的pipeline電路使用暫存器實現了各段的隔離,是流動的關鍵(因為暫存器在每個時鐘週期的上升沿或下降沿才將資料鎖存輸出)

對於DLX指令集,設計其擁有將指令執行劃分為5個階段:

取指令週期(IF)

指令譯碼/讀暫存器週期(IR)

執行/有效地址計算週期(EX)

儲存器訪問/分支完成周期(MEM)

寫回週期(WB)

實現這五個階段的一個簡單的資料通路如下:

資料通路圖可以看做是靜態的,包含所有情況的通路,實際上每段對第N個任務進行處理時,操作碼也是在流水線暫存器中同步流動的,對操作碼進行譯碼(switch),每個clock換了新任務都switch一下這非常關鍵。決定使用哪些通路完成響應功能。其中IF 和ID段中不管是什麼指令做的事情都一樣。加了暫存器起了隔離(這個週期計算、下個週期要用,免遭破壞,自己的結果跟自己走)和流動的作用。加了流水線暫存器的通路如下:

這就是CPU的基本的流水線結構了。下面看看對於不同的指令,譯碼後各段分別做些什麼事情。

IF段根據PC指標的地址讀取指令

ID

讀IR暫存器(指令暫存器)、讀暫存器、並將讀出結果放入兩個臨時暫存器A和B中。同時對IR暫存器中內容的低16位進行符號擴充套件,然後將符號擴充套件之後的32位立即值儲存在臨時暫存器Imm中。

A←Regs[IR6..10]

B←Regs[IR11..15]

Imm←((IR16)16##IR16..31)

EX

1、對於訪存指令要計算有效地址

   LW R1,100(R2)   ALUoutput←A+Imm

   SW 100(R2), R1

2、對於暫存器-暫存器的ALU指令  ADD R1,R2,R3         ALUoutput←A op B

3、對於暫存器-立即數的ALU指令

   ADDI R1,R2,#3   ALUoutput←A op Imm

4、對於分支指令

   BEQZ R1, #800        ALUoutput←NPC+Imm     Cond←(A op 0)

MEM

1、訪存指令

Load:    LMD←LMDMEM[ALUoutput]

Store:    Mem[ALUoutput]←B

2、分支指令(說放在IF階段也沒事)

if (Cond) PC←ALUoutput

else PC←NPC

WB

1、暫存器-暫存器型ALU指令: Reg[IR16..20]←ALUoutput

2、暫存器-立即值型ALU指令:Reg[IR11..15]←ALUoutput

3、Load指令:   Reg[IR11..15]←LMD

注意到流水線資料通路中有四個多路選擇器。選擇器1和2都位於EX階段,上邊是1,控制是A參與加法運算還是NPC參與,如果是分支指令則NPC,暫存器-暫存器和暫存器-立即數的ALU指令都是A參與。2 則是控制是B參與運算還是立即數參與運算,當時暫存器-暫存器型的ALU指令時B參與。分支和暫存器-立即數的指令都是立即數參與運算。選擇器3是MEM段的,判斷EX段計算出的CON是不是成立,若是成立的話則將aluout給PC,若是不成立,則將NPC給PC。選擇器4則是在WB階段起作用的,看是將ALUout寫回到暫存器還是將LMD寫回。前者是ALU指令,後者是load指令。

4、流水線帶來的煩惱—相關

你現在有1000條指令,每條指令都3週期,執行週期就是3000嗎,基本上不可能!!存在相關和其帶來的停頓!!優化進行中....

指令之間存在的相關,限制了流水線的效能。流水線中的相關是指相鄰或相近的兩條指令因存在某種關聯,後一條指令不能在既定的時鐘週期開始執行,否則出錯。消除相關的基本方法——暫停暫停流水線中某條指令及其後面所有指令的執行,該指令之前的所有指令繼續執行。

1、結構相關:

結構相關:當指令在重疊執行過程中,硬體資源滿足不了指令重疊執行的要求,發生資源衝突。

2、資料相關:因一條指令需要用到前面指令的結果,而無法與產生結果的指令重疊執行。

3、控制相關:當流水線遇到分支指令和其它會改變PC值的指令時就發生控制相關。

當指令在流水線中重疊執行時,流水線有可能改變指令讀/寫運算元的順序,使之不同於它們在非流水實現時的順序,舉例說明資料相關:

ADD R1,R2,R3

SUB R4, R1,R5

表面上看,順序執行沒什麼錯誤,但是R1在上一條指令的正確結果要WB才寫回。但SUB在ID就要R1。硬體才不管,來取就給,不管對錯。SUB在EX段需要R1時,此時ADD在MEM段,正確的值在EX/MEM.aluout中,扯線過去。

兩條指令的資料i和j資料相關可能發生的4種情況:

(1)i讀,j讀  不會衝突

(2)i寫,j寫  對於DLX不會發生錯誤,因為都是隻有在WB階段寫回。

(3)i先讀,j後寫  DLX不會錯,因為永遠都是ID讀,WB寫回。

(4)i先寫,j後讀  會發生衝突。

採用定向技術,主動去找正確的資料,將計算結果從其產生的地方直接送到真正需要它的地方,這是硬體的支援。以此減少暫停帶來的損失。(查詢本週期內我需要的正確資料在哪,可不能穿越時空)

並不是所有的都可以用定向技術解決,比如當前你需要的資料在本週起都還沒開始計算,怎麼辦?到哪裡去取?如果是已經計算出來了,怎麼扯線都可以。

如LW R1 0(R2)

  SUB R4 R1 R5

R1的正確值至少到MEM段末才能取到,SUB就算是在EX時也看不見啊看不見。所以要用互鎖部件提供暫停(載入延遲)之後再取。

檢測資料相關:ID段可以檢測所有資料相關。儘早檢測相關、指導決策。

針對DLX流水線出現的所有相關,設計的所有定向路徑:

3、控制相關

一旦分支轉移成功,正確的地址要在Mem段的末尾才會被寫入PC。一旦ID段檢測到分支指令,就暫停執行其後的指令,直到分支指令達到Mem段,確定新的PC為止,(也就是分支指令到達WB段,新的PC值順利到達MEM/WB,再傳達PC,下一條指令才進行IF)分支轉移成功將導致DLX流水線暫停3個週期。怎麼能儘早判斷分支是否成功,成功了又怎麼儘早計算出轉移地址?將“=0?”測試提前到ID段,在ID段增加一個加法器,計算分支目標地址。開銷減少一拍。再改通路再減一拍。記住IF一開始就要定址的,所以在ID內將轉移結果給PC可以再EX開始的時候就成功轉移,所以只需要暫停ID的一拍(修改通路2)。

這個過程用下面三個圖來表示:分支暫停3個週期

 

修改通路1,減一拍,如下圖

 

修改通路2,再減一拍

還有別的減少流水線分支損失的方法:

(1) 凍結或排空流水線,思路:在流水線中停住或刪除分支後的指令,直到知道轉移目標地址。

(2)預測分支轉移失敗           

(3)預測分支轉移成功,沒有好處。

(4)延遲分支,分支開銷為n的分支指令後緊跟有n個延遲槽,流水線遇到分支指令時,按正常方式處理(沒有載入延遲),順帶執行延遲槽中的指令,從而減少分支開銷,關鍵是怎麼安排延遲指令,哪些指令可以作為延遲指令?有三種排程方法:從前排程、從目標處排程、從失敗處排程

從前排程:將分支指令之前的拿下來當延遲指令,反正早晚要執行,這種方法不會有任何損失。

從目標處排程:將要跳轉的程式碼當延遲指令,如果成功則不會有影響,反正執行的是成功時的程式碼。這樣如果分支轉移失敗,將多執行一條指令。

從失敗處排程:如果分支失敗,則不會有影響,反正執行的是失敗時的指令。如果成功了則會多執行程式碼。看起來這相當於什麼都沒做,但排程的作用是讓執行的延遲指令在分支成功跳轉的情況下沒有任何影響。

要保證失敗了多執行的指令對系統無害。

解決分支開銷的過程:我在屋子裡按牆格順序扔飛鏢,有個教練來了(分支指令),喊了暫停,本來需要暫停(stall)扔飛鏢3秒,後來2秒,現在1秒。暫停之後教練再告訴我應該接著剛剛的目標扔還是扔到教練號指向的地方去。現在我嫌浪費時間不想停下來,那我扔到哪去呢?我可以挑原本無論如何要扎的地方放在暫停的時候扎(從前排程),也可以繼續紮下一個目標(從失敗處排程),也可以向教練號指向的地方扔(從成功處排程)。

5、流水線結構的顛覆:記分牌與tomasulo演算法(與設計無關)

捋一捋,自從認識到相關之後,就要解決它。首先解決結構相關,重複設定功能部件的方法。解決資料相關用定向路徑,但是定向路徑也有解決不了的時候,會帶來暫停(在某條指令插入一個stall,就是一個週期內流水線對於該條指令和以後的所有指令什麼都不做)。對於分支開銷,從3個暫停硬體優化到1個暫停,同時可以通過分支預測、延遲槽+指令排程的方式來解決掉這一個暫停。但是相關會帶來暫停這個東西是必然的,那麼有什麼方法可以減少由相關帶來的暫停,來增加指令執行的並行度呢?有別的策略:從軟體方面我可以從編譯器的角度進行程式碼的靜態排程,比如通過沒有迴圈間相關的迴圈展開可以將stall降到很低的水平。但是有沒有一整套方法不是優化,而是進行變革!直接解決掉相關及其帶來的停頓問題!那就是硬體排程的方法,如記分牌演算法。記分牌演算法不光是一種排程演算法,它已經不是在原來的5級流水線上做優化,而是硬體的大變革,排程方式的變革,相當於給CPU移植了“作業系統”管家讓指令的執行更加有效率。但事實上下面的記分牌演算法只是針對浮點指令的,即PC取出指令後判斷是浮點指令的話,就交給記分牌演算法去處理。原來的定點指令還採取流水線的策略。因為記分牌雖然消除了相關,但是邏輯電路比較複雜,判斷週期等都需要考慮。對於簡單的定點指令,可能流水線結構處理起來更有優勢。所以一般來說記分牌和TOMASULO演算法都只是針對浮點指令。tomasulo演算法是記分牌的改進,即通過資料匯流排直接對暫存器進行存取,而且不對真正的暫存器操作,而是採用備份(暫存器重新命名,緩衝)的方法,這樣在建立依賴關係的基礎上就解決了WAR,WAR相關。如果一個CPU的定點指令用流水線計算,浮點指令用下面兩種演算法,那麼如果定點指令和浮點指令之間出現相關該如何處理呢?

關係類似於這樣

它的特點:(思想是檢測所有的相關,並讓沒有相關的指令先去執行,不用等待)

硬體組成:記分牌控制器+兩個乘法部件+一個除法部件+一個ADD部件+一個integer部件做訪存。每一個部件都相當於一個四級結構。在控制器的控制下,不同的指令被髮射到各個功能部件。如果要執行的指令存在結構相關,即比如一條加法指令要使用ADD部件,但是ADD部件在被使用,相當於存在結構相關,那麼該條指令不執行且以後的所有指令都不執行。正常的情況下是一個時鐘週期給往後的一條指令分配一個部件,然後開始部件級的並行(多個部件共同工作,各自在四級的路上往前走)。這就不同於之前的按週期的流動分別將一條條的指令投入同一個5級流水線,那樣是流水線的各個部分並行。要理解這兩種指令並行的不同。

每一個部件的四級結構(4個階段)

檢測結構相關。相當於IF,要取指令。也是這個週期要檢測結構相關。即之前的指令有沒有佔用我要使用的部件。這種檢測可以通過functional unit status來完成,可以看出各個部件是不是busy。(該狀態表還能看出,如果busy的話,目標暫存器,源暫存器是哪些?源是不是準備好,如果沒有準備好,那麼哪個部件產生我要的運算元。)如果結構相關,即要用的部件busy則什麼都不做,一直等到該部件被別人用完。不但該條指令不執行,其後的所有指令都不執行。

根據目的暫存器在result register statu相應位,判斷寫後寫相關WAW。如果出現寫後寫相關那麼也會停住。沒有結構相關、沒有寫後寫相關。(寫後寫能不能放在寫回階段?那樣比較麻煩,還需另外的資料結構)

記分牌第一個階段:Issue。下面填表(這些表都是資料結構)。

1、如果可以執行,則控制器將這條指令發射到部件,將functional unit status的響應部件置busy。

2、根據目的暫存器在result register statu相應位

3、源暫存器是哪些?源是不是準備好,如果沒有準備好,那麼哪個部件產生我要的運算元(通過result register statu判斷)。將functional unit status,的相應運算元置NO和YES。若NO則將依賴的部件再寫上。即防止寫後讀相關。

檢測的方法猜測為:在這個階段,要檢測RAW相關,即寫後讀相關,在讀之前,判斷之前指令的目的暫存器,是不是跟你要讀的一樣(通過result register statu判斷)。如果一樣就相關。因為之前指令存在,說明還沒有WB。它還沒寫,說明你讀就錯,所以資料相關。判斷出資料相關那麼我這個部件在這個週期就什麼都不做,並置狀態位。

記分牌的第二個階段:Read operands。也就是讀運算元,相當於譯碼階段。根據若是源運算元都是yes則進行該階段。否則要等源運算元都準備好了,即檢測到都變成YES了才能執行(即產生源運算元的指令將該數寫回,執行完畢)。

記分牌第三個階段:Execution即執行階段。加法指令執行需要2個週期,乘法需要10個週期,除法需要40個週期。即某部件的該階段是可能佔用多個週期的,N個週期過後,才會在instruction status中用當前週期數置相應階段,表示完成。一上來就對運算元狀態由YES置NO。

記分牌第四階段:Write result即寫回階段。檢測讀後寫相關WAR。查表看前邊的指令有沒有要讀的還沒讀走(導致沒有讀走的原因肯定是另外一個運算元,因為若是因為我要寫的這個運算元還沒準備後,說明再之前它就沒寫回,那麼我當前指令肯定就因為寫後寫相關不能流出了)。寫回之後,指令從functional unit status消失。而且將等待寫回後再讀的指令的源運算元的NO改成YES,Qj和Qk也清除。result register statu也消失。

tomasulo演算法與記分牌的區別

1、部件多了

2、源運算元不用再去暫存器裡取了,不是記錄暫存器名字,而是通過data bus直接將值放在保留站裡,所以 沒有了讀暫存器階段。保留站中的資料是隨著寫回階段通過匯流排更新。(所以寫回的資料有可能進三個地方,一個是目標暫存器,一個是等待指令的第一個源運算元,一個是等待指令的第二個源運算元)

3、對於記分牌,結構衝突和寫後寫相關都是在issue之前判斷的,RAW寫後讀相關的避免是讀運算元階段用查表並等待的方法解決的。WAR讀後寫相關是在寫回階段判斷的,存在相關則等待。即都是通過等待來解決,是因為對真實暫存器的值沒有備份和緩衝到保留站中。

對於tomasulo演算法,相當於記分牌的改進,暫存器重新命名,緩衝源運算元,避免暫存器成為瓶頸,也就是說誰也不去操作真實的暫存器,搞個備份,誰也不會覆蓋誰~避免了Scoreboard中無法解決的 WAR, WAW。有結構衝突的時候不發射。發射之間只檢測結構相關,即有空閒的部件就可以。

設計與實現

所設計的CPU的特性:

基於DLX指令集(指令格式)、五級流水線、實現了6條指令;用定向路徑解決寫後讀資料相關(要用暫停解決的資料相關沒有解決)、分支用3個暫停解決

上升沿寫暫存器,下降沿讀暫存器。

硬體整體:

實現的六條指令的指令格式:

解決的所有的資料相關

工程介面:

Modelsim工程中檔案含義:

dlx.v:該檔案實現了一個基本的5級流水線

memory.v:該檔案用來模擬RAM

regfile.v:該檔案用來模擬register file

testbench.v:該檔案用來做頂層測試

系統框圖如下:

測試程式碼截圖: