
コンピューティングにおいて、算術論理演算ユニット(ALU)は、整数バイナリ数に対して算術演算とビット演算を実行する組み合わせデジタル回路です。[ 1 ] [ 2 ]これは、浮動小数点数を扱う浮動小数点演算ユニット(FPU)とは対照的です。ALUは、コンピュータの中央処理装置(CPU)、FPU、グラフィックス処理装置(GPU)など、多くの種類のコンピューティング回路の基本的な構成要素です。[ 3 ]
ALUへの入力は、演算対象となるデータ(オペランド)と、実行する演算を示すコード(オペコード)です。ALUの出力は、実行された演算の結果です。多くの設計では、ALUにはステータス入力または出力、あるいはその両方があり、それぞれ以前の演算または現在の演算に関する情報をALUと外部ステータスレジスタ間で伝達します。
ALU(算術論理演算装置)は、さまざまな入出力ネットを備えています。これらは、 ALUと外部回路間でデジタル信号を伝送するために使用される電気導体です。ALUが動作しているとき、外部回路はALUの入力に信号を入力し、それに応じてALUは信号を生成し、出力を介して外部回路に伝達します。
基本的なALUは、2つの入力オペランド(AとB)と1つの出力結果(Y )からなる3つの並列データバスを備えています。各データバスは、1つの2進整数を伝送する信号のグループです。通常、A、B、Yバスの幅(各バスを構成する信号の数)は同じで、外部回路(例えば、カプセル化CPUやその他のプロセッサ)のネイティブワードサイズと一致します。
オペコード入力は、演算選択コードをALUに伝える並列バスです。演算選択コードは列挙値であり、ALUが実行する算術演算または論理演算を指定します。オペコードのサイズ(バス幅)によって、ALUが実行できる異なる演算の最大数が決まります。例えば、4ビットのオペコードでは、最大16種類のALU演算を指定できます。一般的に、ALUオペコードは機械語命令とは異なりますが、場合によっては、そのような命令内のビットフィールドとして直接エンコードされることもあります。
ステータス出力は、現在のALU演算の結果に関する補足情報を伝える様々な個別の信号です。汎用ALUには、一般的に次のようなステータス信号があります。
ステータス入力により、演算実行時にALUに追加情報を提供できます。通常、これは単一の「キャリーイン」ビットであり、前回のALU演算で格納されたキャリーアウトです。

ALUは組み合わせ論理回路であり、入力の変化に応じて出力が非同期的に変化します。通常動作では、安定した信号がすべてのALU入力に印加され、信号がALU回路を伝搬するのに十分な時間(「伝搬遅延」と呼ばれる)が経過すると、ALU演算の結果がALU出力に現れます。ALUに接続された外部回路は、動作全体を通してALU入力信号の安定性を確保し、ALU出力をサンプリングする前に信号がALU回路を伝搬するのに十分な時間を確保する役割を担います。
一般的に、外部回路はALUの入力に信号を加えることでALUを制御します。通常、外部回路は順序論理を用いてALUの動作を制御する信号を生成します。外部順序論理は、最悪の場合(すなわち、伝搬遅延が最大となる場合)でもALUの出力が安定するのに十分な時間を確保できるよう、十分に低い周波数のクロック信号によって制御されます。
例えば、CPUは加算演算を開始する際、オペランドをそのソース(通常はプロセッサレジスタ)からALUのオペランド入力にルーティングすると同時に、加算演算を実行するようにALUのオペコード入力に値を適用します。同時に、CPUは演算完了時にALUの出力(加算演算の結果)を格納できるように、宛先レジスタを有効にします。ALUの入力信号は次のクロックまで安定しており、CPUが次のクロックを待っている間、ALUを通って宛先レジスタに伝播します。次のクロックが到着すると、宛先レジスタにALUの演算結果が格納され、ALU演算が完了したため、ALU入力は次のALU演算のために設定できます。
ALUは、多くの基本的な算術演算とビット単位の論理演算を一般的にサポートしています。基本的な汎用ALUは、通常、以下の演算をレパートリーに含んでいます。[ 1 ] [ 2 ] [ 4 ]
ALUのシフト演算では、オペランドA(またはB)が(オペコードに応じて)左または右にシフトされ、シフト後のオペランドがYの位置に表示されます。単純なALUは通常、オペランドを1ビットだけシフトできますが、より複雑なALUはバレルシフタを使用して、1回の演算でオペランドを任意のビット数だけシフトできます。すべての1ビットシフト演算では、オペランドからシフトアウトされたビットがキャリーアウトに現れます。オペランドにシフトインされたビットの値は、シフトの種類によって異なります。

各ALU演算が完了すると、ALUの状態出力信号は通常、外部レジスタに格納され、将来のALU演算(例えば、多倍長演算の実装)や条件分岐の制御に利用されます。状態出力信号を格納するビットレジスタは、多くの場合、単一のマルチビットレジスタとして扱われ、「状態レジスタ」または「条件コードレジスタ」と呼ばれます。
実行されるALU演算の種類によっては、ステータスレジスタのビットの一部が変更され、他のビットは変更されない場合があります。例えば、ANDやORなどのビット単位の論理演算では、キャリーステータスビットはこれらの演算とは関係がないため、通常は変更されません。
CPUでは、格納されたキャリー出力信号は通常、ALUのキャリー入力ネットに接続されます。これにより、キャリーステータスビットに基づく条件分岐によるキャリー伝播のソフトウェア管理が不要になるため、多倍長演算を実行する際に、キャリー(加算キャリー、減算ボロー、シフトオーバーフローなどを表す場合がある)の効率的な伝播が可能になります。

ALUのオペランドのソースとALU結果のデスティネーションは、カプセル化するプロセッサのアーキテクチャと実行される演算によって異なります。プロセッサのアーキテクチャは多岐にわたりますが、汎用CPUでは、ALUは通常、レジスタファイル(プロセッサレジスタの配列)またはアキュムレータレジスタと連携して動作し、ALUはこれらをオペランドのソースと結果のデスティネーションの両方として頻繁に使用します。他のオペランドソースに対応するため、マルチプレクサが一般的に使用され、各マシン命令の要求に応じて、レジスタファイルまたは代替のALUオペランドソースのいずれかを選択します。
例えば、右図に示すアーキテクチャでは、2つの読み出しポートを備えたレジスタファイルを使用しており、任意の2つのレジスタ(または同じレジスタ)に格納された値をALUオペランドとして使用できます。また、ALUオペランドは、即値オペランド(マシン命令に直接エンコードされた定数値[ 5 ])またはメモリから取得することも可能です。ALUの結果は、レジスタファイル内の任意のレジスタ(レジスタファイルの書き込みポート経由)またはメモリに書き込むことができます。
整数演算において、多倍長演算は、ALUワードサイズよりも大きい整数に対して演算を行うアルゴリズムです。このアルゴリズムでは、各整数を、最上位(MS)から最下位(LS)へ、またはその逆の順序で並べられた、ALUサイズのフラグメントの集合として扱います。たとえば、8ビットALUの場合、24ビット整数は、0x1234563つの8ビットフラグメント0x12(MS、0x34LS 0x56)の集合として扱われます。フラグメントのサイズはALUワードサイズと完全に一致するため、ALUはこのオペランドの「断片」に対して直接演算を行うことができます。
このアルゴリズムは、ALUを使用して特定のオペランド断片に直接演算を行い、多倍長演算結果の対応する断片(「部分演算」)を生成します。生成された各部分演算結果は、多倍長演算結果用に指定された記憶領域に書き込まれます。このプロセスはすべてのオペランド断片に対して繰り返され、多倍長演算の結果である部分演算結果の完全な集合が生成されます。
算術演算(加算、減算など)では、まずオペランドの最下位フラグメントに対してALU演算を実行し、最下位部分とキャリービットを生成します。アルゴリズムは部分値を指定されたストレージに書き込み、一方、プロセッサの状態マシンは通常、キャリービットをALUステータスレジスタに格納します。次に、アルゴリズムは各オペランドのコレクションの次のフラグメントに進み、これらのフラグメントに対して、前のALU演算で格納されたキャリービットとともにALU演算を実行し、別の(より上位の)部分値とキャリービットを生成します。前と同様に、キャリービットはステータスレジスタに格納され、部分値は指定されたストレージに書き込まれます。このプロセスは、すべてのオペランドフラグメントが処理されるまで繰り返され、ストレージに部分値の完全なコレクションが生成され、これが多倍長演算の結果となります。
多倍長シフト演算では、オペランド断片の処理順序はシフト方向によって異なります。左シフト演算では、各部分演算の最下位ビット(格納されたキャリービットを介して伝達される)は、以前に左シフトされた下位オペランドの最上位ビットから取得する必要があるため、断片はまず最下位ビットから処理されます。逆に、右シフト演算では、各部分演算の最上位ビットは、以前に右シフトされた上位オペランドの最下位ビットから取得する必要があるため、オペランドはまず最上位ビットから処理されます。
ビット単位の論理演算(論理AND、論理ORなど)では、各部分演算は対応するオペランド断片のみに依存するため(前のALU演算からのキャリービットは無視される)、オペランド断片は任意の順序で処理できます。
バイナリ固定小数点値は整数で表現されます。したがって、任意の固定小数点スケール係数(または暗黙の基数小数点位置)に対して、ALUは2つの固定小数点オペランドを直接加算または減算し、固定小数点の結果を生成できます。この機能は、固定小数点演算と浮動小数点演算の両方で一般的に使用されています。
浮動小数点加算および減算では、小さい方のオペランドの仮数は、大きい方のオペランドの固定小数点スケール係数と一致するように右シフトされます。次に、ALUは位置合わせされた仮数を加算または減算して、結果仮数を生成します。結果仮数は、他のオペランド要素とともに正規化および丸められ、浮動小数点演算結果が生成されます。
複雑な機能を実行できるALUを設計することは可能ですが、回路の複雑さ、消費電力、伝搬遅延、コスト、サイズが増大するため、通常は実用的ではありません。したがって、ALUは通常、非常に高速(つまり、非常に短い伝搬遅延)で実行できる単純な機能に限定され、より複雑な機能はソフトウェアまたは外部回路の役割となります。例えば、次のようになります。
グラフィックス処理ユニット(GPU)には、多くの場合、数百または数千のALUが搭載されており、これらを同時に動作させることができます。アプリケーションとGPUアーキテクチャによっては、ALUは無関係なデータを同時に処理したり、関連するデータに対して並列に動作させたりすることができます。後者の例としてはグラフィックスレンダリングがあり、複数のALUがピクセル群に対して同じ演算を並列に実行し、各ALUはシーン内のピクセルに対して動作します。[ 6 ]
ALUは通常、74181のようなスタンドアロンの集積回路(IC)として、またはより複雑なICの一部として実装されます。後者の場合、ALUは通常、 VHDL、Verilog、またはその他のハードウェア記述言語で記述された記述から合成することによってインスタンス化されます。たとえば、次のVHDLコードは、非常にシンプルな8ビットALUを記述しています。
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 ; -- デクリメントwhen "011" => Y <= A + 1 ; -- インクリメントwhen "100" => Y <= not A ; -- 1 の補数when "101" => Y <= A and B ; -- ビットごとの AND when "110" => Y <= A or B ; -- ビットごとの OR when "111" => Y <= A xor B ; -- ビットごとの XOR when others => Y <= ( others => 'X' ); end case ; end behavioral ;数学者のジョン・フォン・ノイマンは、 1945年にEDVACと呼ばれる新しいコンピュータの基礎に関する報告書の中でALUの概念を提案した。[ 7 ]
情報化時代の黎明期を通じて、電子回路のコスト、サイズ、消費電力は比較的高かった。そのため、初期のコンピュータはすべて、プログラマにとってはより広いワードサイズで表示されることが多かったものの、一度に1ビットずつデータを処理するシリアルALUを備えていた。複数の並列ディスクリート1ビットALU回路を搭載した最初のコンピュータは、1951年のWhirlwind Iであり、16個の「演算ユニット」を用いて16ビットワードの処理を可能にした。
1967年、フェアチャイルドは集積回路として実装された最初のALUライクなデバイスであるフェアチャイルド3800を発表しました。これはアキュムレータを備えた8ビット演算ユニットで構成されていました。加算と減算のみをサポートし、論理関数はサポートしていませんでした。[ 8 ]
やがて、 Am2901や74181といった4ビットALUを含む、完全集積回路型のALUが登場した。これらのデバイスは一般的に「ビットスライス」に対応しており、複数のALUチップを相互接続してより広いワードサイズのALUを構築するための「キャリー先読み」信号を備えていた。これらのデバイスは急速に普及し、ビットスライス型ミニコンピュータで広く使用されるようになった。
マイクロプロセッサは1970年代初頭に登場し始めました。トランジスタは小型化しましたが、フルワード幅のALUを搭載するにはダイスペースが不足することがあり、その結果、初期のマイクロプロセッサの中には、マシン語命令ごとに複数のサイクルを必要とする狭いALUを採用したものもありました。その例として、4ビットALUで8ビット加算を実行した人気のZilog Z80が挙げられます。 [ 9 ]時が経つにつれ、ムーアの法則に従ってトランジスタの形状はさらに縮小し、マイクロプロセッサ上に幅の広いALUを構築することが可能になりました。
現代の集積回路(IC)のトランジスタは、初期のマイクロプロセッサのトランジスタに比べて桁違いに小さく、非常に複雑なALUをIC上に搭載することが可能になりました。今日では、多くの最新のALUは広いワード幅を持ち、バレルシフタやバイナリ乗算器といったアーキテクチャ上の改良により、以前のALUでは複数の演算が必要だった演算を、1クロックサイクルで実行できるようになっています。
ALUは機械回路、電気機械回路、または電子回路として実現することができ[ 10 ]、近年では生物学的ALUの研究が行われています[ 11 ] [ 12 ](例えば、アクチンベース)。[ 13 ]