CS:APP Chapter 4 Y86-64處理器設計-讀書筆記


4 處理器體系結構

第四章的目標是設計一個 Y86-64 的處理器,並運行設計好的 Y86-64 的指令集。

什么是指令集

指令集 ISA,也就是處理器可以處理的指令的集合,Y86-64 的指令是簡化版的 X86-64 指令,他把許多指令都細化了,例如 movq 拆分成了多個 irmovq,rrmovq 等等,直接在指令中寫清楚兩個操作數的來源以及他們的轉移方向。

簡化的指令集也讓處理器的設計更加簡潔和方便,本章主要是設計順序處理器與流水線處理器,並不涉及亂序處理器的設計。而順序處理器也分為兩個版本SEQ初始版本以及SEQ+版本,流水線處理器分為PIPE-PIPE版本,也就是說一共有四種不同的處理器版本。

什么是順序處理器

所有指令都是串行執行的,執行完上一條指令然后再執行下一條。

優點是設計比較簡單,不需要考慮數據冒險,控制冒險之類的問題。

缺點是對處理器的利用效率比較低下,因為一個指令分為多個階段,由於是順序執行,導致會讓一部分的階段處理硬件空閑,所以可以進行一些改進。

什么是流水線處理器

這里我們設計的流水線處理器是將順序執行的指令處理階段拆分成多個不同階段,使得處理器在同一時間內,不同階段可以處理不同的指令,讓所有硬件都得到充分的利用。

優點是利用效率比較高,指令執行速度更快,吞吐量更大。

缺點是設計比較復雜,會有很多的異常情況需要處理,需要處理數據冒險,控制冒險,組合冒險等等情況。

前置知識與指令集設計

涉及到 HCL 硬件控制語言的定義,邏輯門,組合邏輯電路,時鍾寄存器等粗淺的硬件知識,還要有對處理器整體的認知。我們使用組合邏輯電路,時鍾寄存器,隨機訪問存儲來組成一個最簡單的 CPU。

有了對硬件的初步理解之后,再來設計 Y86-64 匯編代碼到機器碼的指令集。

Y86-64 的指令由 10 個字節組成,第一個字節代表指令類型,也就是icode+ifun的組合,第二個字節rA+rB是源操作數與目標操作數的寄存器代碼,寄存器代碼就是一個數組的下標,整個寄存器組組成了寄存器文件,相當於我們使用寄存器代碼去訪問寄存器文件得到寄存器文件中對應下標的數據。不過也不是所有的指令都有這個字節,例如retjXX兩個指令就不需要寄存器,對於剩下的 8 個字節或者 9 個字節,進入處理器處理的時候就成了valC,所以在順序處理器中更新 PC 的時候有些指令要再加上 10 。


設計 SEQ 處理器

實際上 SEQ 也分了很多的階段,這樣做的目的是用盡可能少的硬件去執行盡可能多的不同的指令。

SEQ 的硬件設計中包括了這些:

  • 隨機訪問存儲,其中就包含了數據內存與指令內存,他們分屬不同的區域,但確實都在 RAM 中,甚至有些指令可以更改指令內存。

  • PC 增加器,用於計算下一條指令的 PC

  • 寄存器文件,通過接受外部輸入的寄存器名稱和值來讀寫寄存器。

  • ALU 也就是算法邏輯單元,用於計算數值,地址,計算狀態碼,跳轉狀態等等。

這些簡單的硬件就組成了我們的 SEQ 處理器,所有的指令都在一個時鍾周期中完成,並且數據流動是自底向上的,而數據反饋寫入是自頂向下的。

為了讓指令更加統一,我們將它們分為了六個階段。

取指 Fetch

將程序計數器寄存器作為地址,指令內存讀取指令的字節,PC 增加器計算 valP 也就是下一條指令的地址。

譯碼 Decode

寄存器文件有兩個讀端口 A 和 B,從這兩個端口中同時讀取寄存器的值 valA 和 valB,傳入的是 srcA,srcB 這兩個讀取地址一般是來自從指令中解析出來的 rA 和 rB,但有的時候並不需要讀取兩個值,只需要一個就可以,所以在這種情況下,另一個空閑的讀取端口就會被設置為 15,這也就是一開始設計寄存器的時候只設計了 15 個但卻保留了 r15 而不使用的原因。

執行 Execute

在這一階段會根據指令的類型,將算數 / 邏輯單元 ALU 用於不同的目的,對於整數加減之類的操作,它會執行指令指定的運算,而對於其他的指令,ALU 作為加法器來增加或減少棧指針,計算有效的內存地址,或是不對操作數進行改變,僅僅對它加個 0(為了滿足統一的加法格式而且不改變操作數的值),將輸入傳遞到輸出。

同時也會在這一階段根據 ALU 計算得到的結果來設置條件碼寄存器的值,然后可以計算得到分支跳轉信號 Cnd(如果需要跳轉的話)。

訪存 Memory

通過上一階段得到的計算結果,或者是直接用指令中解析出來的 valC 地址,訪問內存,讀出或者寫入一個內存字,指令內存和數據內存訪問的是相同的位置,但是用於不同的目的。

寫回 Write Back

這一階段的寫回指定的是寫入寄存器文件,這與之前的譯碼階段相對應,在譯碼階段只可以進行讀取操作,而寫入寄存器的操作只可以在這一階段執行。

寄存器文件有兩個寫端口,其中端口 E 用來寫 ALU 計算出來的值,而端口 M 用來寫入從數據內存中讀取出的值。這兩個端口都是傳輸要寫入的數據!而不是要寫入的地址。

PC 更新 Program Counter

程序計數器的下一個值有多個可能,有可能是 PC 增加器(一般指令)計算得到的值,也可能是在當前指令中指定的那個值 valC(對應 jXX 跳轉指令),也可以是從內存中讀取出來的值 valM(對應 ret 指令)。

而對於考試而言,我看到的一些有關這一章的題目是考的指令分階段實現。

設計流水線處理器

在流水線化的系統中,待執行的任務被划分成了若干個獨立的階段,這些階段通常會允許多個任務同時執行,而不是需要等到一個任務完成了所有階段的任務才會開啟下一個任務(這樣的處理叫做串行處理,並行流水線處理要比串行處理快得多),不過在這樣的流水線系統中,任務難免要經過那 些並不需要的環節,例如上面的 OP 操作就根本沒有訪存階段,而它還是要有這個過程,並浪費這么多的時間。

總的來說,流水線化的系統大大提高了系統的吞吐量,也就是單位時間內處理的指令的數量,不過帶來的弊端是會輕微增加任務的總體延遲(Latency)也就是處理單條指令的時間。

通過這兩張圖就能很方便的比較順序與流水線化的區別。

流水線的局限性

不一致的划分

我們的划分往往是假設每個階段耗時都是一樣的,這樣才能充分利用給出的時鍾周期,但實際情況中並不那么完美,就像有的指令根本沒有訪存階段一樣,不可能所有划分出來的階段耗時都是一樣的,所以就會造成浪費,但是如果不按照最長耗時的階段來給定時鍾周期,那么有的階段就會完不成,導致流水線出錯。

流水線過深,收益下降

我們將計算划分成 6 個階段,每個階段需要 50ps,再在每對階段之間插入流水線寄存器,我們就得到了新的六階段的流水線,這個系統的最小時鍾周期達到了 70ps,吞吐量為 14.29GIS,吞吐量也就是最小時鍾周期的倒數。

雖然我們把三階段的流水線提升為六階段的流水線,但是我們的吞吐量並沒有翻倍,是中間插入的寄存器,也就是流水線延遲過多,導致延遲在整個時鍾周期的占比提高(20/70=0.286),最后的吞吐量沒有得到兩倍提升。

流水線階段及流程設計

這一階段的流水線設計增加了流水線寄存器(F,D,E,M,W),然后將 PC 的更新移動到了取指階段,也就是在開始的時候計算當前要執行的指令的地址。

反饋與冒險

反饋是一種依賴,是后執行的指令依賴先前執行指令的結果,因為代碼是人寫的,人的慣性思維往往是線性的,后來者依賴先行者是理所當然的事情,但由於流水線的划分和流水線並發的特性,導致后執行的指令在譯碼階段等讀取數據的時候往往前面的指令還沒有完成寫回操作去更改寄存器文件,也就出現了冒險。

控制冒險

  • 結果沒有被及時的反饋給下一個操作。

  • 流水線改變了系統的行為。

數據冒險

  • 一條指令的結果作為另一條指令的操作數(一般是讀后寫數據相關)。

  • 我們需要處理這類問題,目標是得到正確的結果,並最小化對流水線性能的影響。

冒險處理手段

  • 添加氣泡 bubble

  • 暫停 stalling

  • 數據轉發 forward,增加旁路路徑來把后續指令需要的數據從前置指令中轉發出來,而不需要暫停等待前置指令更新寄存器。

    • 轉發源:e_valE, m_valM, M_valE, W_valM, W_valE

    • 轉發目的地:val_A, val_B

    • 其中開頭大寫的是流水線寄存器中的值,小寫的是流水線階段中產生的信號。val_A, val_B 是 ALU 的操作數。

冒險具體類型

加載 / 使用冒險 Load/Use Hazard

檢測手段:在執行階段判斷當前執行的指令是不是 mrmovq 或者 popq 指令,以及指令要寫入的目標地址是不是譯碼階段給的源地址。

解決方法

  • 將指令暫停在取指和譯碼階段

  • 在執行階段的那條指令加入氣泡,等待數據加載完成。

分支預測錯誤 Mispredicted Branch

對於分支預測有很多的策略,例如永遠選擇 Always Taken,也有永不選擇 Never Taken 或是其他的更加復雜的策略,前者的預測正確率大概為 60%,后者為 40% 左右,Y86-64 中使用的是前者,但不管哪個策略都會往下執行兩條指令,因為只有分支跳轉指令完成執行階段(Execute)才能算出 Cnd 的准確值,所以肯定有兩個新的指令已經加入流水線了,如果那兩條指令不是正確的指令,那么就要取消他們的執行,不過根據我們設計的六階段流水線處理器,他們並不會改變寄存器和狀態,所以只要單純地取消即可。

檢測手段

解決方法

  • 在執行階段檢測到未選擇該分支

  • 在緊跟着的指令周期中,將處於執行和譯碼階段的指令用氣泡替換掉,氣泡指令實際上就是 nop 指令。

  • 此處不會出現預測錯誤的副作用,所以不需要接着處理。

ret 指令

因為 ret 指令需要到達寫回階段才算結束,而在它之后執行的三條指令需要暫停,也就是插入氣泡,讓接下來的三條指令都暫停在取指階段,前面的指令不受影響,繼續正常執行。

檢測手段

解決手段

  • 當 ret 經過的時候,接下來的指令都暫停在取指階段。

  • 在后三條指令的譯碼階段插入氣泡。

  • 當 ret 指令執行到寫回階段的時候釋放暫停。

冒險控制小結

組合情況的處理

在一個時鍾周期內多種不同的流水線冒險同時出現

  • 組合 A

    • 不選擇分支

    • 位於分支中的 ret 指令

  • 組合 B

    • 指令從內存讀取到 % rsp

    • 緊跟着 ret 指令

異常處理

我們需要遵循的異常處理原則是出現異常的指令的后續指令不能改變處理器的狀態,所以我們應該禁用對條件碼寄存器的修改以及數據內存的修改。

一條指令在流水線的某個階段產生了異常,那么就應該將異常狀態寫入到流水線寄存器的狀態碼中,然后讓這個狀態碼隨着流水線傳播,直到協會階段再寫入處理器的狀態寄存器 Stat。


還有很多像PC的預測,處理器性能分析之類的沒有寫,不過多是細枝末節的,重要的以后看到了還是要補充。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM