ALU 坐在哪裡,它是做什麼的
在邏輯那一階,你已經能用 全加器 拼出加法器;在 ISA 那一階,你知道像「add x5, x6, x7」這樣的指令是一紙合約:取兩個暫存器裡的值、把它們結合、再把結果寫回去。真正動手做「結合」的那塊邏輯,就是 算術邏輯單元(arithmetic logic unit,ALU)。把它想成 資料路徑 的隨身計算機——一整塊組合邏輯,吃進兩個數字輸入,外加幾條告訴它要做哪種運算的控制線,吐出一個結果。它就是「算點東西出來」實際發生的地方。
關鍵在於,ALU 是 組合邏輯,不是循序邏輯:它沒有時脈、沒有閂鎖、沒有對一奈秒前所做之事的記憶。把兩個數字和一個運算碼放上它的輸入,等訊號穿過閘門安定下來,答案就出現在輸出端。換掉輸入,輸出立刻跟著變。這正是我們說「一台沒有自己記憶的計算機」的意思——它從不記得上一次的和,只是不斷重算此刻線上的東西。任何必須被記住的東西(運算元、結果)都住在 ALU 之外,住在餵給它的 暫存器檔 裡。
ALU 究竟算些什麼
「算術」那一半做的就是名字所說的:二進位加法 與 二進位減法,每支程式的家常便飯——把迴圈計數器加一、算記憶體位址、取兩數之差。「邏輯」那一半做的是逐位元的布林工作:對每個位元位置各自獨立地施以 AND、OR、XOR 與 NOT,這些是你用來遮罩、設定、清除、測試單一位元的工具。一個典型的 ALU 也會做比較(a 是否小於 b?),常常還會做位移(把所有位元整批往左或往右滑),雖然在許多設計裡是由獨立的位移器來處理後者。
這些運算共用一個結構:把每一個候選結果都平行算出來,再讓一個 控制 碼挑出哪一個抵達輸出。想像一排小電路——一個加法器、一組 AND 閘陣列、一組 OR 閘陣列、一個比較器——全都同時咀嚼著同樣的兩個輸入,末端再由一個多工器選出一個放行。運算選擇線是方向盤;資料線是道路。ALU 不決定要算什麼,它是被告知的。
a[n-1..0] b[n-1..0]
| |
+-------v----------v-------+
| add and or xor cmp | <- all compute in parallel
+----+----+----+----+----+-+
| | | | |
+----+--MUX--+-+----+ <- ALUop selects one
|
result flags: Z N C V
ALUop=0010 -> result = a + b (add)
ALUop=0110 -> result = a - b (subtract / compare)
ALUop=0000 -> result = a & b (and)旗標:ALU 悄聲回報的狀態
在數值結果旁邊,ALU 還放出一小撮一位元的 條件旗標(condition flags),它們描述結果,本身卻不是結果的一部分。四個經典的旗標是:零旗標(Z,當每個結果位元都是 0 時設立)、負旗標(N,結果符號位元的副本)、進位旗標(C,當無號加法從最高位溢出、或減法發生借位時設立),以及溢位旗標(V,當有號結果再也裝不下時設立)。這些幾乎是免費算出來的,是運算的副作用——例如零旗標,不過就是把所有結果位元做一個大大的 NOR。
何必費這個事?因為旗標正是算術與控制流溝通的方式。當你寫「如果 a 等於 b」,機器會計算 a 減 b,再檢查零旗標;「如果 a 小於 b」則一起讀 負旗標與溢位旗標。一條分支指令不會重新審視那些數字——它只是查閱前一次 ALU 運算留下的旗標。所以 ALU 不只是一台計算機,更是程式所做每一個決定的眼睛。
ALU 是怎麼被告知該做什麼的
運算選擇線不是憑空冒出來的;它們是由一小塊叫做 ALU 控制 的邏輯產生的。當一條指令被解碼,主控制單元會看運算碼,推敲出大致的意圖——這是算術指令、載入、還是分支?接著它把一個精簡的提示交給 ALU 控制,而 ALU 控制有時還會偷瞄指令裡額外的功能位元,產生出 ALU 所需的精確運算碼。例如一條載入指令,需要 ALU 把基底暫存器加上一個偏移量來組成位址,於是控制邏輯就悄悄選了「加」,儘管程式設計師寫的是一次記憶體存取,而不是一次求和。
- 兩個來源運算元抵達 ALU 的資料輸入端,它們是在同一個時脈週期內從暫存器檔讀出來的。
- ALU 控制把運算碼放上選擇線——比方說,代表「減法」的那個圖樣。
- 訊號穿過閘門擴散;經過最壞情況的延遲(關鍵路徑)之後,結果與旗標安定到它們的最終值。
- 資料路徑擷取結果——寫進某個暫存器,或往前送往記憶體——並可能把旗標暫存起來,供下一條分支讀取。
注意這帶來的時序意涵。因為 ALU 是組合邏輯,它最慢的那條路徑——通常是加法器的進位一路從最低位漣漪到最高位——往往為「時脈週期最短能多短」設下了下限。這就是為什麼有那麼多工程心力投入在讓加法變快上,也就是本階稍後進位前瞻加法器的主題。更快的 ALU 可能意味著更快的時脈,而這正好繞回你早先見過的效能鐵律:縮短週期時間,是影響執行時間的三根槓桿之一。
誠實的侷限,以及接下來會發生什麼
別把單一個 ALU 能做的事誇大了。一個基本的整數 ALU 能在一趟之內處理加、減、各種逐位元邏輯運算與比較,但更難的算術是刻意不放進去的。乘法與除法需要許多步驟或多得多的硬體,通常住在獨立的單元裡(這是稍後某篇指南的主題),而浮點數——帶著它的指數與捨入——則需要一整個屬於自己的 浮點運算單元。樸素的 ALU 是又快又常見的情況;那些罕見而昂貴的運算被安置在別處,好讓常見的路徑保持精瘦。
再把一條誠實的話講清楚:ALU 是計算的硬體,但它並不詮釋意義。不論你把兩個位元圖樣當成無號計數、有號數還是位址,它都樂於把它們相加——位元進去、位元出來,同一個加法器服務這三種讀法。意義住在程式與 ISA 所約定的慣例裡,正如你早先在位元圖樣那裡學到的。ALU 是輝煌而刻意的笨:它會加,但它不理解。
握著這張地圖,本階接下來的內容會逐一拉近看每個零件。下一篇打開加法與減法、以及進位對溢位的謎題;再下一篇用快速加法器以閘換速;接著是乘法與除法,你會看到為什麼除法是算術裡那個慢吞吞的孩子;最後是浮點數,那些誠實的驚奇(0.1 加 0.2 並不恰好是 0.3)終於要回家落腳。你剛認識的這個 ALU,正是這一切上演的舞台。