算術邏輯單元

算術邏輯單元（Arithmetic logic unit，縮寫：ALU）是一種可對二進位整數執行算術運算或位運算的組合邏輯數字電路。ALU 與浮點數運算單元（FPU）不同，後者僅對浮點數進行操作。ALU 是許多類型的計算電路的基本部件，這些計算電路包括電腦的中央處理單元（CPU）、浮點處理單元（FPU）和圖形處理單元（GPU）。單個CPU、FPU 或 GPU 可能包含多個 ALU。

ALU 的輸入包括需要運算的數據（也稱為運算數）和表明了運算操作類型的指令碼。ALU 的輸出是其執行運算的結果。在許多的設計中，ALU 還帶有狀態輸入或輸出，可將其之前操作或當前操作的資訊在 ALU 和外部狀態寄存器間傳遞。

## 信號

一個 ALU 具有各種輸入和輸出網路，它們用於在 ALU 和外部電路之間傳送數位訊號。當 ALU 工作時，外部電路在 ALU 的輸入端輸入信號，而 ALU 將產生運算結果，並將信號通過其輸出端輸出至外部電路。

### 數據

一個基本的 ALU 具有三個並行的數據總線，包括兩個輸入操作數（A、B）和結果輸出（Y）。每個數據總線都是一組傳遞了一個二進位整數的信號。通常，A、B和Y的總線寬度（每個總線包含的信號數）是相同的，並且與外部電路（例如，封裝 CPU 或其他處理器）的原生字長相同。

### 操作碼

操作碼通過並行總線輸入，它向 ALU 傳遞選擇的操作的資訊，該操作選擇碼是對 ALU 要執行的算術或邏輯運算的枚舉。操作碼的長度（其總線寬度）決定了 ALU 可以執行的操作的最大數量。例如，一個4位操作碼可最多為 ALU 指定不同的 16 個操作（16=24）。通常，ALU 的操作碼與機器語言的操作碼不同，儘管在某些情況下，它可以直接編碼為機器語言操作碼內的位欄位。

### 狀態

#### 輸出

ALU 的狀態輸出是各類單獨的信號，補充表示了有關當前 ALU 操作結果的資訊。通用 ALU 通常具有以下狀態信號：

- Carry-out，表示由加法運算產生的進位、由減法運算產生的借位或由二進位移位運算產生的溢出位。
- Zero，表示運算結果的所有位均為邏輯零。
- Negative，表示算術運算結果為負。
- Overflow，表示算術運算的結果溢出，超出了 Y 的數值範圍。
- Parity，表示輸出數 Y 的奇偶校驗結果，說明 Y 中的含有邏輯 1 的數量的奇偶性。

在 ALU 的操作結束後，狀態輸出信號通常會被存儲在外部寄存器中，以使其可用於之後的 ALU 操作（例如實現多倍精度運算，或用於控制條件分支）。存儲了狀態輸出的集合的位寄存器通常被視為一整個多位寄存器，稱為「狀態寄存器」或「條件代碼寄存器」。

#### 輸入

狀態輸入使 ALU 在執行操作時可接收其他資訊。通常，這是單個「低位進位」，傳遞了來自上一級 ALU 操作的進位。

## 電路操作

<image>

ALU 是組合邏輯電路。這意味著當輸入變化時，其輸出也將隨之變化。在正常的情況下輸入到 ALU 的信號是穩定的，並且當經過足夠的時間（稱為「傳播延遲」）信號通過 ALU 中的電路傳播後，ALU 操作的結果就會出現在ALU上輸出。連接到 ALU 的外部電路負責確保 ALU 的輸入信號在整個操作過程中是穩定的，並在對 ALU 的輸出進行採樣之前為信號傳播預留足夠的時間。

通常，外部電路通過將信號施加到其輸入端來控制 ALU。外部電路往往採用順序邏輯來控制 ALU 的運作，該操作由足夠低頻率的時鐘信號調整，以確保 ALU 的輸出在最壞情況下也有足夠的時間達到穩定。

例如，在用 ALU 執行加法操作時，CPU 會將操作數（通常是從寄存器）路由到 ALU 的輸入，而控制單元會同時將一個值傳送至 ALU 的操作碼輸入，使其執行加法操作。同時，CPU 還將把 ALU 的計算結果輸出路由到用於接收該結果的目標寄存器。在 CPU 等待下一個時鐘期間，ALU 的輸入信號需一直保持穩定，已使其通過 ALU 併到達目標寄存器。當下一個時鐘到達時，目標寄存器將存儲 ALU 結果，並且由於 ALU 操作已完成，因此此時可以為下一次 ALU 操作設置 ALU 輸入。

## 功能

ALU 通常支持許多基本算術和按位邏輯函數。基本的通用 ALU 通常支持以下操作：

### 算術運算

- 加法：將操作數 A、B 相加，並在 Y 處得到二者的和
- 帶進位加法：將操作數 A、B、進位相加，並在 Y 處得到三者的和
- 減法：將操作數 A、B 相減，並在 Y 處得到二者的差。對於此功能，結轉實際上是「借入」指示器。此操作也可以用來比較A和B的大小；在這種情況下，處理器可能會忽略Y輸出，該處理器僅對操作產生的狀態位（尤其是零和負）感興趣。
- 帶借位減法：借位（進位）從A中減去B（反之亦然），差值出現在Y處並結轉（借入）。
- 二補數（取相反數）：得到 A 或 B 的相反數（將 0 與 A 相減或將 0 與 B 相減），並在 Y 處得到計算結果
- 加 1：將 A（或 B）增加 1，並在 Y 處得到計算結果
- 減 1：將 A（或 B）減小 1，並在 Y 處得到計算結果
- 直通（Pass through）：保持 A（或 B）的所有位不變，並在 Y 處得到原輸入數；該操作常用於對操作數進行奇偶校驗，判斷是否為 0，判斷是否為負數，或者為了將操作數直接加載到寄存器中。

### 按位邏輯運算

- AND：將 A 和 B 按位進行「與」運算，並在 Y 處得到計算結果
- OR：將 A 和 B 按位進行「或」運算，並在 Y 處得到計算結果
- XOR：將 A 和 B 按位進行「異或」運算，並在 Y 處得到計算結果
- 補碼：將 A（或 B）的每一位都反轉，並在 Y 處得到計算結果

### 移位操作

類型｜左移｜右移

算術移位｜

<image>

｜

<image>

邏輯移位｜

<image>

｜

<image>

循環移位｜

<image>

｜

<image>

循環移位 （帶進位）｜

<image>

｜

<image>

在不同的操作碼下，ALU 的移位操作可將操作數 A（或 B）向左或向右移位，移位的結果將出現在 Y 處。簡單的 ALU 通常只能將操作數移位一位，而更複雜的 ALU 使用桶形移位器，在一次操作中可移位任意的位數。在一位的移位操作中，從操作數移出的位會被轉移到進位輸出中。而被移位到操作數中的數位取決於移位的類型，比如：

- 算術移位：操作數被視為二補數整數，其高有效位是符號位，在移位時會被保留。
- 邏輯移位：移位時用邏輯 0 補充操作數，這適合於無符號整數。
- 循環移位：此時操作數被視為一個循環緩衝區，因此在移位時，其最低和最高位就像是相鄰的。
- 循環移位（帶進位）：進位輸入（C）和操作數被視為整個操作數的循環移位。

## 應用

### 多倍精度運算

在整數算術計算中，多倍精度運算（Multiple-precision arithmetic）是對字長大於 ALU 字長的整數進行運算的操作。為此，該演算法將每個操作數視為多個 ALU 能夠處理的操作數片段的集合（並從最高位（MSB）到最低位（LSB）排列，或相反）。例如，在使用 8 位 ALU 的情況下，將 24 位整數`0x123456`視為如下三個8位片段的集合：`0x12`（高位）、`0x34`、`0x56`（低位）。由於片段的大小與該 ALU 字長完全匹配，因此可以直接對這幾個數進行操作。

該演算法使用 ALU 直接對特定的操作數片段進行運算，從而生成多精度結果的相應片段。在操作數的每個片段被計算後，結果都將被保存到對應的位置。對所有操作數片段重複此過程，將這些結果進行拼合，即可得到完整的運算結果。

對於算術運算（如加法和減法），該演算法首先對操作數的最低位片段進行運算，從而生成最低位對應的結果和進位。隨後將該結果儲存至某個指定的位置，而處理器通常會存儲 ALU 狀態輸出中的進位。然後，該演算法繼續計算每個操作數片段集合的下一個片段，並對這些片段調用 ALU 操作，並將先前 ALU 操作的進位一起輸入，從而產生另一個更高位的片段和進位。重複此過程，直到處理完所有操作數片段為止，從而在存儲位置中得到完整的運算結果。

對於移位運算，操作數片段處理的順序取決於移位方向。在左移操作中，首先對最低位片段進行處理，因為必須用低位片段左移的溢出位來得到該運算的最低位。類似地，在右移操作中應首先對操作數的最高位進行處理。

對於按位邏輯運算（如邏輯與、邏輯或操作）中，可以按任何任意順序處理操作數片段，因為計算結果的每個片段僅取決於相應的操作數片段（來自先前 ALU 操作的進位將被忽略）。

### 複雜運算

儘管可以將ALU設計於執行複雜的功能，但在許多情況下，由此導致的更高的電路複雜性，成本，功耗和更大的尺寸使其不切實際。因此，ALU通常限於可以以非常高的速度（傳播延遲更短）執行的簡單功能，而為了執行複雜的運算，外部處理器電路則需要將一系列簡單的ALU操作進行編排。

例如，在ALU的複雜程度不同的情況下，可以通過多種方式來計算數字的平方根：

- 單周期計算：如果一個ALU設計得足夠複雜，那它可能可在一次操作中得到計算結果。
- 計算流水線：可以讓一組結構不那麼複雜的 ALU 逐步地完成平方根計算，計算的中間結果儲存在各級 ALU 上，就像工廠的生產線一樣。該電路可以在完成之前的操作數前接受新的操作數，並得到與複雜的 ALU 一樣快的結果，儘管結果過程會因各級 ALU 的延遲而存在延遲。有關更多資訊，請參見指令流水線。
- 迭代計算：可讓 ALU 在控制單元的控制下經由多步計算平方根，這使其結構更加簡單。

上面的實現從最快和最昂貴的到最慢和最便宜的過渡，在這幾種情況下都可完成平方根的計算，但是結構更加簡單的 ALU 的計算時間往往更長，因為需要執行多步操作。

## 實現

ALU 通常是一個獨立的積體電路（如 74181 晶片），也可能是一個更加複雜的 IC 的一部分。在後一種情況下，ALU 一般由VHDL、Verilog或其他硬體描述語言編寫，並通過綜合該代碼來實例化其電路。例如，以下 VHDL 代碼描述了一個功能非常簡單的8位ALU：

```vhdl
entity alu is
port (  -- 该 ALU 对外部电路的接口
  A  : in  signed(7 downto 0);   -- 操作数 A
  B  : in  signed(7 downto 0);   -- 操作数 B
  OP : in  unsigned(2 downto 0); -- 运算指令
  Y  : out signed(7 downto 0));  -- 运算结果
end alu;

architecture behavioral of alu is
begin
  case OP is  -- 解码运算指令并执行计算操作
    when "000" =>  Y <= A + B;   -- 加法
    when "001" =>  Y <= A - B;   -- 减法
    when "010" =>  Y <= A - 1;   -- 减1
    when "011" =>  Y <= A + 1;   -- 加1
    when "100" =>  Y <= not A;   -- 求补
    when "101" =>  Y <= A and B; -- 位与
    when "110" =>  Y <= A or B;  -- 位或
    when "111" =>  Y <= A xor B; -- 位异或
    when others => Y <= (others => 'X');
  end case; 
end behavioral;
```

## 歷史

馮·諾伊曼在1945年的一份關於新型電腦EDVAC的基礎的報告中提出了 ALU 的概念。

在整個資訊時代的初期，電子電路的成本，尺寸和功耗都相對較高。因此，儘管所有串行電腦和許多早期電腦（例如 PDP-8）都具有一個簡單的 ALU，但它一次只能處理一個數據位，而程式設計師們使用更長的字長。最早的具有多個分立單位 ALU 電路的電腦是1948年的 Whirlwind I，它使用了16個這樣的「數學單元」，以使其能夠對16位數據進行操作。

1967年，仙童半導體推出了第一款用積體電路實現的 ALU，名為 Fairchild 3800，由一個帶累加器的八位 ALU 構成。隨後，其他積體電路 ALU 也相繼出現，包括4位 ALU，如 Am2901 和 74181。這些設備通常具有「位片」功能，這意味著它們可以輸出「超前進位」的信號，從而有助於使用多個 ALU 晶片相互連接以構成字長更寬的 ALU。這些設備迅速流行起來，並廣泛用於位片微型計算機。

1970年代初，微處理器開始出現。雖然電晶體變得越來越小，但裸晶的尺寸並不足以放下全字寬的 ALU。因此，一些早期的微處理器採用了位數更少的 ALU，每條指令因而需要多個周期來執行。比如當時流行的Zilog Z80，它使用4位 ALU 執行8位加法運算。後來，正如摩爾定律預測的那樣，電晶體的尺寸進一步縮小，在微處理器上構建更寬的 ALU 從而也變得可行。

現代的積體電路中電晶體的尺寸比早期微處理器的小几個數量級，從而可以配備高度複雜的 ALU。如今，許多現代ALU的字寬更寬，體系結構的提升（如使用了桶形移位器和二進位乘法器）能使它們能夠在單個時鐘周期內執行需要早期 ALU 進行多次運算才能完成的操作。