指令漫游流水線CPU奇遇記

作者:    所在單位:中國科學技術大學

前言

本文可以看作計算機專業學生的“計算機組成原理”一課內容的總結概括。手搓CPU可謂是計算機專業學生的一大浪漫,因為在未接觸這門課以前,我們總是將CPU看得非常的高大上。無論哪家科技公司,只要有芯片相關業務,他們的各種型號的處理器的命名都十分的“炫酷”?!翱犷!?,“驍龍”,“銳龍”......仿佛CPU完全是一個魔法造就的神秘黑盒,但是有著無與倫比的運行速度并且牢牢占據著我們對一臺電子設備的關注焦點。但是今天我們可以從底層來打開這個黑盒,發現其中的奇妙世界以及理解脈沖,信號之間協同工作,嚴絲合縫運轉的巧妙之處。

正文

嗨!各位科技迷們!今天我們要揭秘的是計算機世界里的一顆璀璨明珠——流水線CPU!它可是決定計算機運行性能的大殺器哦!

首先,讓我們穿越時空,回到上世紀60年代。那時候,計算機還比較“憨憨”,一次只能做一件事。直到人們開始意識到,如果能同時處理多個任務,那豈不是美滋滋?于是,流水線概念應運而生!

流水線CPU就像一條裝配線,它把任務拆成一小塊一小塊,然后交給不同的處理器段來同時處理。就好像工廠里的工人,各司其職,高效無比!這么做有什么好處呢?首先,大大提高了計算機的處理速度!它能夠同時進行多個任務,讓我們的電腦跑得飛快!其次,它還讓計算機更節約能源,不浪費一點兒嘞!

首先,我們有“取指令階段(IF)”這個階段就像是在超市拿購物清單一樣,告訴計算機接下來要做啥。

接下來是“解碼指令階段(ID)”這一環節就是將指令翻譯成計算機能聽懂的語言,讓它知道具體要做啥。

然后是“執行指令階段(EX)”這相當于工人們照著清單去操作機器,將任務具體完成。

最后,我們有“寫回結果階段(WB)”這就像是把購物清單上的物品帶回家一樣,把結果存起來,供后續使用。

所以說,流水線CPU就像是一支充滿活力的樂隊,各司其職,合作無間,讓我們的計算機在各種任務面前游刃有余!歷經時光的洗禮,它已經成為了計算機世界的明日之星!毫不夸張地說,在現代所有類型的CPU中都占據絕對的統治地位!

接下來,讓我們分階段來更加詳細的揭開流水線處理器的秘密吧!

第一節 流水線從取指令開始

晚上剛送完指令的取指單元現在很悠閑,他的工作很簡單,在整個流水線中央處理器中都是有名的閑職:每天早上從PC寄存器拿到一份貼著地址的空紙箱,然后沿著線網送到不遠處的指令高速緩存(ICache)倉庫門口就可以了,PC是program counter的縮寫,盡管這并沒有準確傳達其功能的含義——它的作用是指示程序執行到的位置,但是已經成為了約定俗成的名字?,F在的取指單元正維持著電平不變,已經進入了夢鄉。它的延遲很低,也可以說工作很少,所以可以很快上床躺平,只要明天早上時鐘網絡一拉高,然后經過不長的線延遲后就可以把工作完成了。只是窗外偶爾傳來的忙碌加班的聲音漸漸地傳入了進入取值單元的夢鄉,這是別的關鍵路徑上的部門還在加班干活,但是又很快地消失了。

這里有一個定死的規矩,所有人都必須在時鐘電平跳變之間進入穩定狀態,也就是必須把活干完然后在第二天時鐘拉起來的上升沿之前去睡覺,不得時序違例。這個規矩有多么嚴格呢?曾經有一個部門十分臃腫,由于招了太多人,攬了太多活,天天加班007,結果某個月的任務(指令)非常密集,導致某天晚上這個部門還在加班,一直到時鐘拉起,它前面的部門的今天的快遞都已經塞進大門了,但是這個違例的部門內部的電平還是沒有穩定下來,導致兩天的工作混在了一塊。當設計師發現了刺眼的critical warning后氣不打一處來,當場就把這個部門解散了。

早睡早起身體好,這是取指單元不被裁員的座右銘。

但是取指單元的歲月靜好,是由于指令緩存的負重前行,我們很快就會看到這一點。

第二節 指令高速緩存

?ICache,指令高速緩存部門一大早就看到了門口的貼著PC的快遞盒?!斑@個取指單元的爺真是爺,天天除了吃就是睡,沒別噠,我們部門都忙不過來了,四路倉庫按索引查都沒有這個PC指示的地址的指令,這下又得等好長時間從主存里調貨了”。但是接到的PC不能不接,否則會丟指令。ICache派了個寄存器先存著這個指令,然后對信號傳遞使能的保安allowin說:"下次碰到那個老頭不要接他的單,什么時候我們的指令從主存里取出來了或者我們的四路組相聯的庫里還有存貨(指令)再允許他派單"。allowin是一個很聽話的保安,第二天取指單元大爺來送貼著PC的快遞盒時,allowin禮貌而堅定地拒收了,大爺只好悻悻地把空紙盒又拿回去了。

至于調貨的事情,就需要ICache通過一種事先定好的協議,比如AXI協議和主存溝通了。AXI是一個非常講究儀式而又慢性子的中介,與他溝通不僅兩人要先握手寒暄一下,然后給定好數據傳輸的地址和長度,之后經過數十個甚至上百個周期,來自遠方主存的數據才會姍姍來遲。這是一種叫做存儲層次結構的深層原因所導致的,在CPU內部,只需要用寄存器,或者緩存就可以以接近時鐘的頻率讀寫數據,但是這種方法所能使用的空間有限,而且單位造價也很昂貴,于是如果使用稍慢”一點“的內存的話,雖然讀寫速度會降低一個或兩個數量級,但是可以獲得更大的存儲空間。這種關系同樣存在與內存和硬盤之間:速度快,空間大以及價格低是一組不可能三角,如果你想速度快,空間大,那么英特爾昂貴的傲騰存儲也許能滿足你,如果想要速度快,價格低,那么只有小空間的片上寄存器資源可以供你使用,否則就是速度慢但是價格低,容量大的硬盤,磁帶等存儲介質了。

高速緩存有兩家部門,一家是在前端的指令高速緩存,另一家是在后端的數據高速緩存,他們在CPU中占有重要地位,擔任著提高性能的主要責任。

流水線中央處理器的時間節奏是很快的,即使是很佛系的處理器,也是以百兆赫茲的頻率快速地更替日月。而從消費級一直到企業級的英特爾或者AMD處理器的時鐘頻率可以達到GHZ,也就是不到一納秒的時間。這是什么概念呢?如果一個人一秒算一次加減法的話,現代CPU可以算10億次,而且是很保守的估計。但是與之相連的存儲和外設速度卻要慢若干個數量級,因此ICache取指和DCache訪存如果不命中,那么就不得不花費大量時間。這個部門因此會花費大量時間并阻塞,所以增大倉庫容量,增加路數是提高命中率的常規操作。

當然,如果倉庫里查到PC所對應的指令的話,皆大歡喜,指令很快地就可以打包然后繼續送到下一站:譯碼單元。

第三節 指令譯碼

譯碼單元里有許許多多的選擇器,這些選擇器通過識別指令來標注指令類型,目標寄存器和源寄存器編號等信息。這些快遞盒通過譯碼器去后會被分成各種類型,有ALU算數邏輯指令,有訪存指令,有跳轉指令,甚至有特權指令。有時候指令的長度甚至有不一樣長,導致快遞盒大小差異很大,比如x86指令集就是這樣。這會給譯碼帶來額外的困難,因為大小不一的快遞盒可能超出了安檢機的入口大小,所以不得不分幾次來譯碼。有時譯碼單元會發現按照指令集手冊查不到當前指令的任何信息,那么譯碼單元會毫不猶豫地給這個快遞件貼上”指令不存在例外“的異常標簽。

指令譯碼單元是CPU里出名的血汗工廠,里面擠著大量的選擇器工人,他們的工位往往僅能看到不長的三五個比特,然后就要根據設計師定好的選擇邏輯來確定指令的一部分信息。這里的工人沒有時間睡覺,因為他們都是組合邏輯,所以沒有寄存器可以讓他們在時鐘跳變間隙休息。許多工人忙到汗流浹背,彼此之間空位很小,你仿佛可以看到譯碼單元里產生的熱氣在密密麻麻的線網中逐漸地氤氳......

第四節 讀寄存器,執行運算

譯碼出的寄存器編號隨后放在寄存器堆門口,寄存器堆顧名思義就是一堆寄存器堆在一起,不同寄存器之間有不同的編號來區分。里面存放的值一般隨后用來作為執行階段的操作數,可以進行尋址,加減,乘除,移位等操作。

執行階段聽起來很像個龐然大物,畢竟各種類型的指令需要使用大量的硬件資源來計算。但是這部分可能反而是最好實現的部分,因為各種IP和模版已經十分成熟,設計師實現某個類型的運算可能只是一行代碼的工作量。

深入到硬件看,這里同樣也是十分熱鬧。還記得之前譯碼器標記上的類型嗎?毫無疑問,運算的類型由譯碼器指定,隨后從寄存器堆得到的數據或者伴隨指令的立即數會作為各種運算的操作數進入到運算單元。雖然在高級語言內我們可以使用諸如三角函數,指數函數,對數函數等等算術,但是CPU只會進行簡單的幾種基本運算。但是別擔心,我們的程序可以根據各種級數展開將復雜的函數轉換成CPU內部若干條基本運算的迭代循環。這些基本運算一般都包括加減,乘除,和一些位運算。

這個過程中,還有許許多多別的細節,比如除零運算我們可能認為需要停止計算并給這個指令標記上算術運算錯然后后傳,但是也有處理器允許計算結果是不確定的值并且不觸發任何例外;還有越界時的處理,比如乘法結果太大無法用數據寄存器完整保存,那么一般就是截斷數據。

這個運算單元的結果還另外有大用,不僅要正常向后傳遞,也需要經過前遞回傳到執行單元的輸入選擇器上。這是對于流水線的CPU,盡管早先進入的指令還沒有執行結束,但是新的指令已經進入了流水線,新指令所需要的操作數可能已經被臨近的舊指令修改了,但是還沒有來得及寫回使新指令獲得最新的值,所以需要使用前遞來保證結果的正確性。如果不前遞會怎么樣?也不會怎么樣,無非就是CPU算出一個錯值(問題大了)或者每有一對相鄰的相關指令,就不得不等到新的值更新完畢再繼續傳遞指令。這樣看來前遞還是十分重要的,但是總有一些指令的相關問題是前遞都解決不了的,我們只能退而求其次,等若干個周期,寧肯慢,絕不錯。

我們知道,所有程序都可以由三種基本類型的結構所實現:順序結構,分支結構和循環結構。其中分支和循環結構就是對應指令中的分支和跳轉指令。分支指令往往需要比較兩個數的大小,如果滿足條件,比如這個指令類似BLT(Branch less than),如果兩個操作數的大小關系確實是小于的話,程序的PC就會跳轉到指定的位置。這個指定的位置可能是當前的PC加上一個立即數,也可能是從一個寄存器中獲得要跳轉的位置。還有一些跳轉是無條件的,他們可以有更大的跳轉范圍但是只能使用特定的寄存器。

再次提醒,我們的CPU是流水線的,指令不管分支結果怎么樣已經填入了CPU,我們怎么保證分支指令后的指令是正確的呢?很遺憾,沒有辦法百分百地確保這一點。我們能做的就是如果錯了就發一個令CPU聞之色變的信號:flush。也可以理解為清空前半段的流水線,無效掉由于分支預測失敗讀進來的錯誤指令來保護CPU的正確性,這樣做的代價就是指令高速緩存可能需要重新與主存溝通,同時流水線之前的大部分工作都白費了。

等等,分支預測失???如果我不失敗,幾乎次次都猜對,那不是很好嗎?

確實如此,即使使用很簡單的局部分支預測器,比如”吃一塹長一智“的方法,或者說只要一次預測失敗就記住這個位置,下次改變跳轉的采取與否選擇,也可以大大提高循環體結構中的預測準確率。也有另外一種方法,也許可以稱之為”吃兩塹長一智“,也叫2bit飽和預測,正如字面所說,即使預測失敗,還給予一次機會做出與上次相同的預測。分支預測器放置在取指單元之前,每一拍都會根據當前PC預測下一次PC,大部分情況下都是+4字節(32位處理器),也就是連續取指。

?另外對于訪存指令來說,同樣有一個數據高速緩存來進行對接。數據高速緩存(DCache)相比與ICache,需要新增臟位標識,來指示數據不一致的情況。為什么ICache不需要呢?因為指令一般是固定的,從內存中讀出來后不會被改寫,所以可以認為是只讀的,所以不會產生內存和緩存數據不一致的情況。除臟塊的寫回以外,ICache和DCache的架構就沒有什么更大的區別了。讀取數據同樣使用地址的低位作為索引查到到對應的行,比較每一路的標簽,如果命中就返回對應位置的數據。但是如果是寫數據,要寫的目標地址正好在緩存的塊中,也還好辦,但是如果不在,而且現在索引對應的幾路全都”臟“了該怎么辦呢?那么就需要把臟行寫回到主存,然后從主存取出正確位置所在的一行,然后再在緩存中寫。這樣看起來與主存的溝通不僅要寫還要讀,顯然比ICache要復雜一些,同時要花費更多的時間與主存溝通。問題不僅如此,數據段和指令段的讀寫都要經過AXI協議的話,究竟誰先誰后呢?這個過程還需要仲裁一下,一般是寫數據優先,因為DCache位于流水線的后段,所以對應的是先執行的指令,當然要讓寫數據的效果對后執行的指令可見。

我們不妨再看看一類特殊的指令:特權指令。這類指令是觸發例外最頻繁的因素之一。特權指令有很高的權限,可以改變一般指令不能改變的狀態寄存器的值和CPU的工作模式,所以如果不在特權模式下執行特權指令會報特權等級錯例外。你可能發現我們上述所講的CPU似乎除了從主存中拿出指令然后循環,計算一通寫回主存后沒有別的與外部交流的方式了,這與我們實際生活中能放視頻,操作界面,播放音樂,打印文件的個人電腦差了很多。其實只要補上最后一片拼圖:中斷,就可以完成上述所有的一切;而中斷怎樣處理,正是由特權指令所設置的。你想要打印一串字符?把字符串的起始位置存到指定的寄存器里,把打印程序的指令起始位置設置好,然后觸發系統調用例外,隨后的事情就交給中斷處理程序和外設吧!從這個角度看,其他的交互都只不過是執行中斷程序和提供數據(比如要打印的字符,屏幕某個像素點的顏色,位置)后,實際功能外包給各種外設罷了。當然各種驅動程序怎么實現,傳輸速率如何的問題,那就屬于程序員適配軟件和工程師測試硬件的范疇了。

第五節 寫回與異常處理

天下沒有不散的筵席,也沒有執行不完的指令。指令即使不斷地被flush沖刷或者不停地被阻塞,在經過執行單元后,還是會進入到寫回段。寫回段需要根據指令的類型來判斷其是否需要更新寄存器堆。也許你會疑問為什么不早點更新,不是不想早一些,而實在是做不到??!

如果剛從寄存器讀出來就更新的話,不好意思,數據甚至都沒有算好,更新個寂寞。也許我們需要再次說明一下,在宏觀上CPU執行某條指令就像是一個周期就瞬間算好了,但是微觀上只有工程師才知道在時序,頻率和資源權衡下,布線之后的真實CPU中信號的傳遞是一拍一拍的。換一個視角,宇宙間最快的速度——光速,在10GHZ的頻率下,每一個時鐘周期期間也只能移動 的長度,你可以想一想一塊計算機CPU的邊長是不是接近這個頻率對應的信息傳遞上限,再加上內部布線的彎彎繞繞,是不是覺得現代計算機的頻率已經接近到了某種不可思議的程度?

?那么如果從執行單元出來就更新,這下數據都準備好了,沒有理由不更新寫回到寄存器堆了吧?還真有。本來執行段就有自己的任務,如果直接把數據接回去,這不是給執行單元這個部門加班了嗎?別忘了我們的規矩!在時鐘周期之間要把任務都執行完,如果加班要不然會造成時序違例,要不然只能降低時鐘頻率,而降頻就意味著損失性能。幸運的是,我們并沒有非這樣不做的理由,或者說在執行段后寄存一級,讓指令以及他們攜帶著的一大堆數據睡一覺,第二天再上路也是非常合理的。這里可以體現出流水線設計的精髓:找到耗時最長的單元,我們往往稱之為關鍵路徑,切一刀分成兩部分,讓每個單元在同一個時鐘周期內做更少的事來減少延遲,是提高流水線頻率的基本操作。

在寫回段還有另一件重要的事,那就是異常處理。我們已經見過指令不存在例外,特權等級錯例外,以及系統調用例外,但是還有一些此處未提到過的例外。處理這些例外時我們可以通過隨指令一起的標簽來判斷例外類型。不過一般我們都需要讓PC跳轉到例外入口地址處去處理。例外入口地址的設置有不同的實現方法,在龍芯精簡指令集中是使用特權指令來實現。入口的那一邊是什么?當然就是處理例外的程序了,當這段程序處理完后,還需要回到產生例外的原來的位置繼續執行。不出意外的,原來的位置需要使用特權指令來讀取。我們可能發現并不容易區分異常和中斷兩種機制的處理流程的區別,但實際上也確實如此。他們的處理流程都是離開原來的程序流,進入到預先設置好的處理程序,執行完后再回到原來的位置,就好像在調用一段函數一樣。我們也將中斷分為外中斷和內中斷,由外設引起的中斷可以叫外中斷,而各種異常也叫做內中斷。

第六節 流水線處理器的優化

到此為止,我們已經見識了一個可以作為本科生課程實驗程度的簡易流水線是如何工作的。如果你曾經自己實現過一個這樣的處理器,燒錄到FPGA上并且真正執行了一段程序時,內心會很有成就感??赡苓€不由得鄙夷AMD,INTEL這樣的大廠,什么檔次,我們實現流水線CPU,他們也實現流水線CPU。但是請保持謙遜,別忘了人家的流水線可以達到GHZ的頻率,同時有大小核,多核并行的并行技術。即使只拿出人家三十年前的處理器,也已經采用了亂序超標量的技術。什么?你沒有聽說過亂序超標量嗎?不要緊,簡單理解,我們之前的講述都只是一條一條指令在斷斷續續地執行,但是超標量可以同時發射兩條以上的指令(盡管也可能是斷斷續續地)。直覺上是可以想到,如果有一串指令序列,奇數位置PC的指令與偶數位置PC的指令使用的寄存器,訪存的地址都互不干擾,也不改變執行順序,那么同時發射兩條就可以很暴力地提高一倍的性能。但是會帶來一系列的問題,因為顯然實際情況中我們的假設并不總是成立,甚至大部分時間都不成立,那么請你想一想,如果不成立,是否有方法解決呢?至少我們總是可以用空泡(即不會產生任何有意義變化的無意義指令)來填充那些沒辦法同時執行的指令。即使如此簡單的處理,也會帶來一系列的小問題,這些問題單獨拿出來都不難,但是非常繁雜,實際實現時會以你意料不到的方式不斷地摧毀你的奇思妙想與天才設計。我們以上所說的都是順序發射的處理器,那么什么是亂序呢?當然就是在不影響正確性的前提下,應發盡發,充分地利用執行單元的資源,不發白不發,發了還想發。這需要引入新的技術,比如記分板,重命名和重排序技術。對于高速緩存的設計技術,還有大量的技巧,比如很典型的使用多級緩存,比如L2 Cache甚至L3 Cache,或者使用異構混合的緩存,比如使用一種叫做Victim Cache的結構來彌補路數不足的缺陷。此外,還有指令緩存(不是ICache)可以用來放在ICache的下一段,其實就是一個先進先出的隊列,可以讓前端在不命中而取指令的時間后端從隊列中拿出指令還有活干;或者后端阻塞的情況下前端還能源源不斷的去取出指令,總之就是更充分的壓榨流水線和存儲的性能。

本文省略了一些重要的部件的相關內容,比如TLB(快表),因為沒有區分虛地址和實地址,進而地沒有介紹地址翻譯模式,連帶著地址翻譯相關的常見異常也沒有介紹。各種指令集中往往有各自獨特的特色指令,雖然使用頻率不高,但是卻有可能對CPU的設計架構產生關鍵的影響,比如柵障指令,計數器讀指令等等,更不用說難以歸類的一些雜項指令了。

寫在后面

盡管CPU中有數不清的設計方法和時序優化技巧弄得人眼花繚亂,但是有幾個重要的基本概念,可以說深刻影響了CPU的現狀,過去以及未來。一是層次化存儲結構,如果沒有這種結構金字塔的存在,我們完全沒有必要設計緩存,并且與之類似的各種緩存技術將毫無存在意義。二是流水線結構,從單個指令的執行過程來看,流水線并不能減少單個指令從取指到寫回所需要的時間,但是在大量指令的運行過程中,由于流水線深度填充而產生的影響將被無限地抹平,看起來完全就是一條指令剛進入流水線,寫回段就完成了一條指令,因此毫不夸張地說流水線是人類偉大的工程設計思想。三是沒有最好的設計,只有更好的設計。小容量的Cache容易導致頻繁不命中而大大降低程序運行速度,但是大容量的Cache又會占用大量資源,影響布線進而降低頻率,同樣會降低運行速度。選擇合適容量和設計的緩存是一門藝術,也是一種實驗科學??偠灾?,你不會找到一種可以完美解決所有方案的設計,但是在與無窮無盡的問題的斗爭中,我們設計CPU的技藝將不斷精進,能夠取得在有限資源下兼顧能耗和速率的更加優秀的設計,這也是計算機科學的魅力之一。

參考資料:

中國科學技術大學 王超老師 PPT,張俊霞老師 PPT

《計算機組成原理與設計》 David A. Patterson,John L.Hennessy

龍芯杯指令集手冊 龍芯中科有限公司

《計算機體系結構》 龍芯中科

《超標量處理器設計》,姚永斌

亚洲国产午夜中文,国产熟女一区二区三区四区五区,被按摩师玩弄到潮喷在线播放,欧美一区二区三区久久综合