計算機組成
7 流水線處理器
7.1 流水線的基本原理

流水線作為一種生產管理的模式,對於提高生產效率有着非常大的幫助,最早是興起於汽車制造廠,現在已在很多的行業得到了廣泛的應用。 那在處理器設計當中也借鑒了流水線的概念,以提升性能。今天我們就來看一看流水線處理器是如何設計的。

這位老朋友大家還記得吧?他曾經為我們展示過精湛的廚藝。那今天我們就再來欣賞一下他做菜的過程。他做菜分為這么幾步,首先是洗菜,然后是切菜,第三步是炒菜,最后是裝盤。我們假設這每一步都要花費1分鍾,那這位廚師做一道菜就需要4分鍾,那如果他總共做四道菜,就需要16分鍾。現在呢,他掌勺的這個小餐館生意非常好,客人很多,但是大家抱怨上菜太慢了,這怎么辦呢?而我正好是這個餐館的老板,我就得思考如何解決這個問題。首先我發現,在我能請得起的廚師當中,這位大廚已經是動作很快的了。所以,我就想從生產管理模式方面做一些改變。 那現在當然是非流水線的操作方式,那如何來改造成流水線的操作。

我們先來看一看這個廚房里有哪些用具。我們有洗菜用的水池,切菜用的刀和板,炒菜用的廚具,還有最后裝盤的容器。那在剛才那位大廚的工作過程中,在每一個時刻他都只能使用其中的一樣,另外三樣都是空閑的。那看來從硬性條件來說,是適合改造成流水線的操作了。那我剛才說了,我這是一個小本經營,我沒辦法供四個廚師分別來做這四件事情,可以從另一個角度來說,如果我雇得起四個這樣的大廚,我還不如讓他們同時做四道菜呢,沒必要這么麻煩,引入什么流水線的操作。那怎么辦呢?那我就把這個大廚給解雇了,然后用同樣的錢雇來四個人。現在大廚很全能,什么都會干,而新雇的這四位每一位只會干一種工作,那好,現在人員設備配置到位,那我就希望這四個步驟能夠流水化地操作起來。 他們每一個人在做完自己手頭的工作之后,將完成的成果交給下一個階段繼續進行下一步的操作,當然這樣的交接需要一個統一的指揮,所以還得有一個發號施令的人,這個人是不用新雇的。剛才那位大廚每四分鍾做完一道菜,所以 這位司號員每四分鍾吹一次號,以指揮那位大廚去做下一道菜。而現在我們假設每個步驟和剛才那個大廚工作的方式一樣,同樣也要花費一分鍾的時間,所以這司號員要改成每一分鍾吹一次號,流水線上的工作人員每聽到號響,就將自己的工作成果轉交給下一階段的人,當然,他必須保證在號響之前,已經順利地完成了自己的工作。 這個司號員就好比CPU當中的時鍾,如果這么來看,那我們現在的時鍾頻率已經提升為原來的4倍了。那我們就來看一看我們新改造的這個廚房是如何工作的。

現在要做的菜單已經送過來了,工作開始。
首先第一道菜的原料送到洗菜的環節,這時候后面的各個環節都處於空閑狀態,一分鍾之后,洗菜完成。

第一道菜的原料進入切菜環節,與此同時,第二道菜的原料進入洗菜環節。然后再過了一分鍾,第一道菜已經切完了,與此同時,第二道菜也洗完了。

當迎來下一次號響的時候,第一道菜就會進入炒菜環節,第二道菜進入切菜環節,同時第三道菜進入洗菜環節,然后再過一分鍾。

四道菜都進入了流水線當中,這個時候整條流水線的各個環節都開始工作了,那我們就可以說,這個流水線已經被填滿了。而之前的這個過程就是填充流水線的過程,那再過一分鍾。

第一道菜就完成了所有的工序,可以上菜了。而且之后每過一分鍾,我們都可以上一道菜。

第2道

第3道

然后是第4道,7分鍾后,這四道菜都完成了。那因為我們目前的任務只有四道菜,所以在剛才流水線被填滿之后,又經歷了一個流水線被排空的過程。當然如果這餐館的客人很多,源源不斷的有要做的菜單送來,那這個流水線就可以一直保持着充滿的狀態,每分鍾都能送出一盤菜來。

那我們就來分析一下這個流水線的性能。現在我們采用這樣的流水線的方式,做四道菜用了7分鍾,平均每道菜用時不到2分鍾,而且在流水線填滿之后,可以做到每一分鍾上一道菜。而之前采用非流水線的方式,是每四分鍾才能上一道菜,那如果我們能保證流水線長期處於填滿的狀況,那現在的性能就可以達到原先的4倍,而我們的硬件資源的投入並沒有明顯的變化。當然我們要注意的是,采用流水線的方式,雖然可以做到每分鍾上一道菜,但是單獨針對某一道菜,其實還是需要4分鍾,這個時間並沒有縮短。那看完了這個廚房的例子,我們再來看一看流水線的原理是如何運用到真實的處理器結構上的。

這是我們之前已經構建的單周期的處理器,我們是可以正確地運行一些MIPS的指令的。

而MIPS指令的執行,可以分為這樣5個步驟。
第一步是取指;
第二步是譯碼;
第三步是執行;
第四步是訪存;
第五步是回寫。
那我們還是結合數據通路圖來看一看這樣的步驟。

相比剛才的結構圖,我們做了一點小小的變化,也就把IFU內部的結構進行適度的展開。
對照這個圖,取指的階段,就是用PC的值去訪問指令存儲器,從而得到指令的編碼,同時還需要生成PC的更新值。
在譯碼階段,不僅需要把指令編碼進行分解,而且還需要從寄存器堆當中讀出所需的寄存器的值。
第三步執行,主要在 ALU 當中完成。對於算術邏輯運算指令,就是完成對應的運算;而對於訪存指令,則是計算出訪存的地址。
第四步是訪存,對於load指令,是從數據存儲器當中讀出對應的數據;而對於store指令,則是將數據送到數據存儲器當中去;而其他指令在這一步沒有實質的操作。
最后一步是寫回,對於要改寫寄存器的指令,在這一步會將數據寫入到寄存器堆當中指定的位置。
我們要注意的是,雖然分成了這五步,但只是為了便於描述而已。所有的信號都必須要在這條指令執行的過程中保持穩定,例如從PC寄存器送到指令存儲器的 這個地址信號,如果在指令執行完成前,它就發生了改變,那指令存儲器送出的指令編碼(Instruction Word)也就會發生改變,從而造成寄存器堆選取了不同編號的寄存器,送出了不同的值,ALU也可能執行了不同的操作,那這條指令就可能執行錯誤了。所以,對於單周期處理器來說,這一條指令執行的過程中,所有的信號都是必須要保持穩定的。
而我們要進行流水線的改造的話,我們同樣也會發現,這不同階段所用到的硬件資源基本上是相互獨立的。如果我們能把指令存儲器輸出的指令編碼事先保存下來,那我們就可以提前更新PC寄存器的值,並用這新的值去指令存儲器當中取出一個新的指令,而在取新指令的同時,剛才取出的那條指令的編碼就會被分解成不同位域,而寄存器堆也會根據輸入送出對應寄存器的內容。所以,跟剛才的流水線原理的分析類似,如果我們想把這些硬件資源充分地利用起來,我們就需要把它拆分成若干個階段。
那在這個電路的結構上要進行拆分,我們就在每一個階段之間添加上寄存器,這就被稱為流水線寄存器,這些寄存器用於保存前一個階段要向后一個階段傳送的所有的信息。

我們還是以取指到譯碼的這個階段為例,我們將指令存儲器的輸出接到一個寄存器上(圖中標號2處),那當一個時鍾上升沿來臨的時候,指令存儲器輸出的指令編碼就會被保存到這個寄存器當中。那么在這個上升沿之后,指令存儲器的地址輸入(1處)如果發生改變,隨之影響的指令存儲器的輸出,也不會被存到這個寄存器當中去(2處)。所以,在這個時候,我們可以用新的PC來訪問這個指令存儲器,從而得到下一條指令的二進制編碼。而在這個同時,前一條指令的編碼已經在這個(2處)流水線寄存器的輸出上,並且經過相應的電路,切分成不同的位域。那其中有一個位域就會通過rs連到了寄存器堆,並且選中對應的寄存器,把其中的內容放到busA這根信號上,而這根信號也會被接到一個流水線的寄存器上(3處)。那么當下一個時鍾上升沿來臨的時候, 當前這條指令所需要的rs寄存器的值,就會被保存到這個(3處)流水線寄存器當中。與此同時,下一條指令的二進制編碼也會保存到這個(2處)流水線寄存器中。那么在很短的
Clock-to-Q 時間之后,譯碼階段所看到的指令的編碼(Instruction Word)就已經變成第二條指令了。所以很快,寄存器堆得到的rs的寄存器編號也發生了改變,但是這沒有關系,第一條指令所需的寄存器的值已經保存到了這個(3處)流水線寄存器當中,而且在這個時候,也應該會被送到了ALU的輸入端(4處)。所以,這樣通過添加流水線寄存器,我們就先從大體上把這個單周期處理器改造成了一個流水線的處理器。

那我們對這個流水線的處理器進行一些簡單的性能分析。比如說我們要執行這么三條指令,我們把時間軸畫出來,從0時刻開始,每一格是200ps。
那如果是在單周期的處理器上,執行一條指令需要這樣五步,也就是取指、譯碼、執行、訪存和寫回。假設每個階段都正好需要200ps,那執行完這條指令,就總共過去了1000ps,然后我們才可以執行第二條指令,又用去1000ps,然后是執行第三條指令,那這樣每條指令都需要花1000ps的時間,這個單周期處理器,它的時鍾周期就需要被設置為1000ps。從外界看來,這個處理器每1000ps可以完成一條指令。
而對於流水線處理器,我們同樣來執行這三條指令。先看第一條指令,那要完成這條指令所需要的步驟是一樣的,同樣也需要這五步,也同樣需要花1000ps的時間。但是不同在於,在過去200ps之后,當第一條指令完成了取指階段,而進入到譯碼階段的時候,實際上取指部件已經空閑下來,我們就可以開始第二條指令的取指工作,也就是說第二條指令在此時,已經開始執行了。同樣,再過了200ps,第一條指令完成了譯碼,進入到了執行階段,這樣第二條指令也正好完成了取指,可以進入譯碼階段,而此時,第三條指令的取指也可以開始了。這樣對一個流水線處理器,雖然一條指令總共也是需要花1000ps,但是每200ps就可以開始執行一條指令,而且當流水線填滿之后,每200ps也就可以完成一條指令。所以,對於這樣一個流水線處理器,它的時鍾周期可以設為200ps。因此,這個處理器的主頻就是剛才這個單周期處理器的5倍。

當然這只是理想情況,現實中的性能提升幅度並沒有這么大, 其中一個原因就是這些新插入的流水線寄存器,它自身也會帶來一些新的延遲。我們假設這些寄存器的延遲是50ps,那我們再來看一看這個處理器的性能有什么樣的變化。

這是剛才沒有考慮流水線寄存器延遲的情況下分析的性能表現,那如果我們加上流水線寄存器的延遲,同樣還是執行這幾條指令,那就需要每隔 250ps 才可以開始一條新的指令。所以,時鍾周期應該設為250ps,而且對於每條指令本身來說,需要花1250ps(250*5,5個階段每階段時間開銷一個時鍾周期)才能夠完成。在這一點上,是比剛才在單周期處理器還要更慢一些的。

因此對於流水線處理器來說,因為各個處理部件可以並行工作,從而可以使得整個程序的執行時間縮短。但是流水線並不會縮短單條指令的執行時間,相反,還會增加這個時間。因此,采用流水線的方式,實際上是提高了指令的吞吐率,從而從整體上縮短了程序的執行時間,提高了系統的性能。

現在我們已經了解了流水線的基本原理,而且分析了一個五級流水線的大致框架,這也是早期流水線處理器的實現結構。后來,流水線處理器的設計又發生了很多的發展和變化,那我們在下一節將要進一步探討這些問題。
