ALU
ALUとは
前節までで、加算器を作り上げることができました。加算器は、2つの数を足し合わせることができる回路です。しかし、実際のCPUは、足し算だけでなく、さまざまな計算を行うことができます。例えば、ANDなどの論理演算や、引き算が挙げられます。これらの計算を行うためには、もちろん加算器だけでは不十分です。そこで、加算器以外にも色々な回路を組み合わせることで、さまざまな計算を行うことができる回路を作り上げます。この回路のこ とを、ALU(算術論理演算器)と呼びます。CPUの根幹は「計算」にあるので、このALUこそがCPUのもっとも重要な部分といっても過言ではありません。では、そんなALUを実際に作っていきましょう。
ALUの設計
ALUにどのような機能を持たせるかは、言ってしまえばCPUの設計者次第です。とはいえ、ALUがあまりにも単純で機能の少ないものだと、後々CPU全体の設計で困ることがあるでしょう。ここでは、最低限ALUが持っていてほしい機能を実装していきます。『コンピュータシステムの理論と実装 - モダンなコンピュータの作り方』(Noam Nisan氏・Shimon Schocken氏 著 斎藤康毅氏 訳)という書籍におけるALUの設計を参考にします。
ALUの機能
まず、ALUに持たせる機能についてです。
ここでは、2つの入力を受け取るような演算機能として、足し算、引き算、AND、ORの機能を持たせることにします。
また、2つの入力を受け取ってそれらに対し何らかの演算を行う以外にも、1つの入力のみに対し何らかの演算を行う機能も持たせます。そのような機能として、恒等関数(入力の値をそのまま出力)、1の加算(インクリメント)、1の減算(デクリメント)、ビット反転、符号反転を実装することにします。恒等関数やインクリメントなどがどのような役に立つかは今はわからないかもしれませんが、後々、これらの機 能があることでCPUの設計が楽になることがわかるでしょう。
さらに、入力によらず常に定数を出力するような機能も必要になってくるので、それも実装しておきます。具体的には、0, -1, 1の3つの定数を出力するような機能を持たせます。
ALUの入力
機能を決めたところで、次にALUへの入力をどんなものにするかを決めましょう。
ALUは、足し算などの2つの入力を受け取るような演算を行うことができるので、少なくとも2つのnビットの入力A, Bは要るでしょう。
また、ALUは色んな演算を行えるので、それらの内どの演算を行うのかを指定できるような入力も必要になります。このような入力は制御ビットと呼ばれます。上で述べた機能は10種類以上あったことを踏まえると、制御ビットは1つではなく複数必要になりそうです。ここでは、下のような6種類の制御ビットを使うことにしましょう。
- ZeroA: 入力Aを0にする
- InversionA: 入力Aを反転する
- ZeroB: 入力Bを0にする
- InversionB: 入力Bを反転する
- Function: 出力をA&B(0)にするかA+B(1)にするかを決める
- InversionOut: 出力を反転する
これらの制御ビットに基づいて、AとBの値に対し計算を行っていきます。なお、これらの制御ビットによる入力や出力の値の変更は、上の並び順に行われるということに注意してください。例えば、ZeroAを1、InversionAを1にすると、まずAの値を0にしたあとで、その値を反転します。ZeroB、InversionBについても同様で す。その後FunctionによってA+BかA|Bを計算し、最後にInversionOutが1なら前の計算結果を反転し、出力とします。
さて、これらの制御ビットにより、ALUの機能がすべて実装できます。例えば、A-Bを計算したいときは、InversionA、Function、InversionOutを1にし、他は0にすれば良いです。これは、!A = -A - 1、!A + B = -A - 1 + B、!(-A - 1 + B) = !(-(A - B) - 1) = A - Bが成り立つことに基づきます。
全体としては、このALUは、次のような表で表される計算を行うことになります。
ZeroA | InversionA | ZeroB | InversionB | Function | InversionOut | 出力 |
---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | 1 | 0 | 0 |
1 | 1 | 1 | 1 | 1 | 1 | 1 |
1 | 1 | 1 | 0 | 1 | 0 | -1 |
0 | 0 | 1 | 1 | 0 | 0 | A |
1 | 1 | 0 | 0 | 0 | 0 | B |
0 | 0 | 1 | 1 | 0 | 1 | !A |
1 | 1 | 0 | 0 | 0 | 1 | !B |
0 | 0 | 1 | 1 | 1 | 1 | -A |
1 | 1 | 0 | 0 | 1 | 1 | -B |
0 | 1 | 1 | 1 | 1 | 1 | A+1 |
1 | 1 | 0 | 1 | 1 | 1 | B+1 |
0 | 0 | 1 | 1 | 1 | 0 | A-1 |
1 | 1 | 0 | 0 | 1 | 0 | B-1 |
0 | 0 | 0 | 0 | 1 | 0 | A+B |
0 | 1 | 0 | 0 | 1 | 1 | A-B |
0 | 0 | 0 | 1 | 1 | 1 | B-A |
0 | 0 | 0 | 0 | 0 | 0 | A&B |
0 | 1 | 0 | 1 | 0 | 1 | A|B |
本当にこの表のようにALUが動くのかどうか疑問に感じる人もいるかもしれません。もしそうなら、制御ビットを各行のように定めたとき、出力がどうなるかを確かめてみてください。
ALUの出力
さて、ALUの機能と入力まで決まったので、最後にALUの出力をどのようにするかを決めましょう。
先ほども述べたように、出力はA、Bおよび6つの制御ビットに基づいた計算結果として決まります。これをCとします。
Cの他にも、計算結果が0かどうかを表すZeroと、計算結果が負かどうかを表すNegativeという2つのビットを出力として持たせることにします。例えばAとBのどちらが大きいかということを調べたいとき、A-BをこのALUで計算し、Negativeの値を見れば分かります。Negativeが0ならAはB以上であり、1ならAはB未満だからです。ZeroやNegativeの出力を用意しておくことで、このような入力AとBの比較が容易になります。
ALUを実装しよう
ここまでで、ALUの実装に必要な情報はすべて決まりました。それでは、実際にALUを作成していきましょう。