DE0-NanoでCPU自作を目指す(2) - AND/OR/NOT回路を作る:論理ゲートの動作をDE0-Nanoで確認する
前回は、Quartusのプロジェクトを作成し、DIPスイッチの状態をLEDへそのまま出しました。書いた回路はほとんど配線だけでしたが、プロジェクト作成からピン割り当て、コンパイル、SOF書き込み、実機確認までを一通り通せたので、今回からようやく入力値を加工する回路へ進みます。
最初に作るのはAND、OR、NOTです。
論理ゲートの説明だけを見ると、真理値表を覚えて終わりにしたくなります。自分も以前は「これは知っているから飛ばしていいだろう」と思っていました。ただ、Verilogで書いた式がどのような回路になるか、複数の出力が同時に存在する感覚を実機で確認しておかないと、後で加算器やALUを読んだときに結局ここへ戻ってくることになります。今回はその手戻りを先に潰しておく回、というくらいの位置づけです。
今回はDIPスイッチのうち2個を入力A、Bとして使い、AND、OR、NOTの結果をLEDへ表示します。クロックは今回も使いません。
今回作るもの
入力はsw[0]とsw[1]です。
led[0]:A AND B
led[1]:A OR B
led[2]:NOT A
led[3]:NOT B
led[4]:入力Aの確認用
led[5]:入力Bの確認用
led[7:6]:0固定
入力値そのものもLEDへ出しておくのは、演算結果だけを見て「スイッチのONとOFFを逆に解釈していた」という混乱を避けるためです。地味な工夫ですが、後で回路が複雑になったときほど効いてきます。
回路の中身
今回から、トップモジュールの中へ演算式を全部書かず、論理ゲートを小さなモジュールとして分けます。
最終的なCPUでは、今回のゲートモジュールをそのままトップ階層へ並べるわけではありません。ただし、半加算器、全加算器、ALUという上位の回路を、小さな部品から組み立てる練習になります。
ファイル構成は次のようにします。
de0_nano_cpu
├── 01_top.v
├── and_gate_1bit.v
├── or_gate_1bit.v
└── not_gate_1bit.vトップモジュール名は第1回と同じde0_nano_cpuにします。QSFの信号名を変えずに済むので、以後も可能な限りこの名前を維持するつもりです。
Verilogコード
and_gate_1bit.v
module and_gate_1bit(
input wire a,
input wire b,
output wire y
);
assign y = a & b;
endmoduleor_gate_1bit.v
module or_gate_1bit(
input wire a,
input wire b,
output wire y
);
assign y = a | b;
endmodulenot_gate_1bit.v
module not_gate_1bit(
input wire a,
output wire y
);
assign y = ~a;
endmodule01_top.v
module de0_nano_cpu(
input wire clk_50,
input wire [3:0] sw,
output wire [7:0] led
);
wire and_y;
wire or_y;
wire not_a;
wire not_b;
and_gate_1bit u_and_gate (
.a(sw[0]),
.b(sw[1]),
.y(and_y)
);
or_gate_1bit u_or_gate (
.a(sw[0]),
.b(sw[1]),
.y(or_y)
);
not_gate_1bit u_not_gate_a (
.a(sw[0]),
.y(not_a)
);
not_gate_1bit u_not_gate_b (
.a(sw[1]),
.y(not_b)
);
assign led[0] = and_y;
assign led[1] = or_y;
assign led[2] = not_a;
assign led[3] = not_b;
assign led[4] = sw[0];
assign led[5] = sw[1];
assign led[7:6] = 2'b00;
endmoduleclk_50はトップポートに残していますが、今回の回路では使っていません。Quartusから未使用入力の警告が出ることがありますが、これは意図したものなので気にしなくて大丈夫です。
演算子の意味
Verilogでは、ANDに&、ORに|、NOTに~を使います。
assign y = a & b;
assign y = a | b;
assign y = ~a;ここで使っているのはビット単位の演算子です。特にNOTは!ではなく~です。!は値全体を真か偽かで判定する論理否定で、~は各bitを反転します。今回は1bitなので見た目の結果は同じですが、4bitや8bitを扱うようになると意味が変わってきます。
後で4bit ALUを作るときは、例えば次のように各bitを一度にANDできます。
assign y = a & b;このときaとbが4bitであれば、4組のAND回路が並列に作られます。1本のassign文が「1回実行される」のではなく「4組の回路がそのまま並ぶ」というのが、ソフトウェア脳で読むと一番つまずきやすいところだと思います。
真理値表
ANDとORの動作は次のようになります。
A | B | A AND B | A OR B |
0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 |
1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 |
NOTは入力を反転するだけです。
A | NOT A |
0 | 1 |
1 | 0 |
実機ではsw[0]とsw[1]を4通りに切り替えれば、全パターンを確認できます。
Quartusでの手順
第1回のプロジェクトをそのまま使います。
01_top.vを書き換え、and_gate_1bit.v、or_gate_1bit.v、not_gate_1bit.vを追加する
QSFのピン割り当ては変更しない
Compile Designを実行する
Programmerで新しいSOFを書き込む
複数ファイルへ分けたときに自分がよくやるのは、エディタで保存しただけでQuartusのプロジェクトへ追加したつもりになることです。ファイルが同じフォルダに置いてあるだけでは合成対象にならないので、Project NavigatorのFiles一覧に本当に3つとも並んでいるか、コンパイル前に一度目で確認する癖をつけています。
実機での確認
最初にsw[0]=0、sw[1]=0へ揃えます。このとき、
ANDは消灯
ORは消灯
NOT Aは点灯
NOT Bは点灯
になります。
次に片方ずつONへ切り替えます。ORだけが点灯し、ANDは消灯したままです。両方をONにすると、ANDとORが両方点灯します。
反応が期待と違う場合は、まずled[4]とled[5]を見て、入力AとBを意図どおり認識できているか確認してください。入力確認用LEDが正しくて演算結果だけがおかしければコード側、入力確認用LEDのほうから既におかしければスイッチの向きやピン割り当て側を疑う、という切り分けができます。
つまずきやすいところ
&と&&、|と||は見た目が似ていますが、用途が違います。&と|はビット単位の演算、&&と||は値全体を真偽として扱う論理演算です。1bit入力だけを見ている間はどちらを使っても同じように動いてしまうことがあるため、これが一番間違いに気付きにくいポイントだと思います。4bit以上を扱い始めてから急におかしくなって、原因を遡ったらここだった、というのはよくある話です。
もう一つは、複数のassign文が上から順番に実行されると思ってしまうことです。今回のAND、OR、NOT回路は、すべて同時に存在しています。DIPスイッチを動かすと、それぞれのゲートを通った結果が並列にLEDへ現れます。この感覚は、頭で分かっていても実機でスイッチをカチカチ動かしてみるまで、なかなか馴染みませんでした。
次回予定
次回はXOR回路を作ります。XORは2つの入力が異なるときだけ1になる、という性質を持っていて、これが2進数の足し算における「和」のbitとちょうど一致します。
次回はXOR単体の動作を確認すると同時に、今回作ったAND回路を再利用して、半加算器へつながる形まで整理していく予定です。


