01
ALU は「計算の切り替えスイッチ」
ALU(Arithmetic Logic Unit/算術論理演算ユニット)は、CPU の中で実際に計算を行う部分である。 新しい計算方法を発明するわけではない。すでに作った回路を並べておき、opcode という選択信号でどれか1つの結果を出す——それが ALU である。
これまで(演習A・B・C)
加算器は足し算だけ
減算器は引き算だけ
比較器は比較だけ
減算器は引き算だけ
比較器は比較だけ
ALU(この演習)
6つの計算を1つの箱に入れ
opcode で選ぶ
= CPU の命令の正体
opcode で選ぶ
= CPU の命令の正体
02
opcode と演算の対応
| opcode | 演算 | 式 | a=10, b=3 のときの result |
|---|---|---|---|
| 3'b000 | ADD | a + b | 13 |
| 3'b001 | SUB | a − b | 7 |
| 3'b010 | AND | a & b | 2 |
| 3'b011 | OR | a | b | 11 |
| 3'b100 | XOR | a ^ b | 9 |
| 3'b101 | NOT | ~a | 245 |
| 3'b110 / 111 | 未使用 | — | 0(default) |
zero フラグは「結果がちょうど 0 であったか」を表す1ビットである。
CPU はこれを見て「等しかったら分岐する」を判断する。演習Cで eq を作ったのと同じ考え方である。
03
作るモジュールの仕様
- module
- alu8
- input
- a [7:0] / b [7:0] / opcode [2:0]
- output
- result [7:0] — 演算結果 / cout — 加減算の桁上がり(論理演算では 0) / zero — 結果が 0 なら 1
04
書いて、その場でチェック
加算・減算の 9 ビット結果(add_result / sub_result)と zero フラグはあらかじめ書いてある。 あなたが書くのは always @(*) の中の case 文——opcode に応じてどの結果を result と cout に流すか、という部分である。
module alu8 (
input [7:0] a,
input [7:0] b,
input [2:0] opcode,
output reg [7:0] result,
output reg cout,
output zero
);
endmodule
Ctrl / ⌘ + Enter でも実行
答え合わせ(自分で試してから開く)
always @(*) begin case (opcode) 3'b000: {cout, result} = add_result; 3'b001: {cout, result} = sub_result; 3'b010: begin result = a & b; cout = 0; end 3'b011: begin result = a | b; cout = 0; end 3'b100: begin result = a ^ b; cout = 0; end 3'b101: begin result = ~a; cout = 0; end default: begin result = 8'd0; cout = 0; end endcase end
default を書く理由:すべての opcode を書かないと、指定されなかった場合に「前の値を保持する回路(ラッチ)」が勝手に作られてしまう。 組み合わせ回路のつもりが記憶回路になる、という有名な落とし穴である。
05
考えてみよう
- SUB(3'b001)で zero が 1 になるのはどんなときですか。それは演習Cのどのフラグと同じ意味でしょうか。
- この ALU に 左シフト(a << 1)を追加するとしたら、opcode をいくつにして、case にどう書き足しますか。実際に足してチェッカーで試してみましょう。
- default を消すと何が起きますか。チェッカーで確かめてから、なぜそうなるか説明してみましょう。
- ADD と SUB で cout の意味が違う(片方は桁上がり、もう片方は桁借りの裏返し)。CPU はこの1本の線をどう使い分けているでしょうか。
発展:本物のツール(EDA Playground)でも動かす 任意
実際のシミュレータで波形も見たい人は、EDA Playground に設計を貼り付けて実行できる(Gmail アカウントが必要)。 手順は 使い方ガイド へ。
TESTBENCH(左パネル)
module tb_alu8; reg [7:0] a, b; reg [2:0] opcode; wire [7:0] result; wire cout, zero; alu8 uut (.a(a), .b(b), .opcode(opcode), .result(result), .cout(cout), .zero(zero)); initial begin $dumpfile("dump.vcd"); $dumpvars(0, tb_alu8); $monitor("op=%b a=%0d b=%0d | result=%0d cout=%0d zero=%0d", opcode, a, b, result, cout, zero); a = 8'd10; b = 8'd3; opcode = 3'b000; #10; opcode = 3'b001; #10; opcode = 3'b010; #10; opcode = 3'b011; #10; opcode = 3'b100; #10; opcode = 3'b101; #10; a = 8'd5; b = 8'd5; opcode = 3'b001; #10; $display("--- テスト完了 ---"); $finish; end endmodule