みなさんこんにちは!
今回はVerilogHDLというハードウェア記述言語を使って、nビット加算器を作っていきたいと思います。
nビット加算器を作るために利用した半加算器や全加算器についての説明していくので
論理回路についてあまり詳しくない人でも、簡単に理解できると思いますよ!
nビット加算器のソースコードと実行結果
さっそくなのですが、
まずはnビット加算器の完成版のソースコードと、その実行結果を載せていきたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
`define N 10 module module_top(); reg [`N - 1:0] reg_a, reg_b; wire [`N - 1:0] wire_s; initial begin #100 reg_a <= 1024; reg_b <= 2048; #100 reg_a <= 1023; reg_b <= 1; #100 reg_a <= 1024; reg_b <= 1; #100 reg_a <= 2047; reg_b <= 0; #100 reg_a <= 100000; reg_b <= 10000; end always@(*) #1 $write("%4d %4d -> %d\n", reg_a, reg_b, wire_s); module_n_adder module_n_adder0 (reg_a, reg_b, wire_s); endmodule module module_half_adder (a, b, s, c); input wire a, b; output wire s, c; assign c = a & b; assign s = a ^ b; endmodule module module_full_adder (a, b, cin, s, cout); input wire a, b, cin; output wire s, cout; wire e, g, f; module_half_adder module_half_adder0(a, b, e, f); module_half_adder module_half_adder1(cin, e, s, g); assign cout = g || f; endmodule module module_n_adder (a, b, s); input wire [`N - 1:0] a, b; output wire [`N - 1:0] s; wire [`N:0] cin; assign cin[0] = 0; generate genvar g; for (g = 0; g < `N; g = g + 1) begin : gen module_full_adder module_full_adder0(a[g], b[g], cin[g], s[g], cin[g + 1]); end endgenerate endmodule |
このソースコードを丸々コピペして、
ターミナルでコンパイルして実行してみると、以下のような結果になると思います。
上記の場合は、N=10のとき、すなわり10ビット加算器として実行した結果なのですが
nビット加算器のソースコードの一番最初の行に書いてある
1 2 3 |
`define N 10 |
という部分を10から11に変更すると、11ビット加算器として実行できるようになります。
それを実行した結果が以下のようになります。
実行結果の3行目では1024+1を計算しているのですが、N=10のときだと0+1という表示がされていますよね。
これは10ビット加算器では0~1023(2^10-1)までの数字しか表現できないことを表しているのです。
一方でN=11の場合は0~2047(2^11-1)までの数字を表現することができるので、正しく1024+1を計算することができるのです。
半加算器と全加算器がどうなっているのか理解しよう
nビット加算器のソースコードだけを見ても、なにがどうなっているのか分からないと思うので
まずはnビット加算器を作るために利用されている全加算器、そして全加算器を作るために利用されている半加算器が、
どのような仕組みで構成されているのかを理解していきましょう!
半加算器とは?
画像: http://kccn.konan-u.ac.jpより引用
半加算器とは、上記の画像のような論理回路で構成されるハードウェアのことを言います。
Verilogでコーディングするとこんな感じになります。
1 2 3 4 5 6 7 8 |
module module_half_adder (a, b, s, c); input wire a, b; output wire s, c; assign c = a & b; assign s = a ^ b; endmodule |
半加算器は、
入力がAとB、出力がSとCになっています。
AとBは足される数のことで、SはAとBの和、Cは桁上がりを表現します。
また、真理値表は以下のようになっています。
A | B | S | C |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
SがAとBを足したときの値を、CがAとBを足したときの桁上がりの数を、
しっかりと表してくれていることが分かりますよね。
しかし2ビット以上の入力を足し算するとき(100+101など)には、どうすればいいのでしょうか?
この半加算器ではAとBを足し合わせることができますが、
AとBとC(下の位から来た桁上がり)を同時に足し合わせることができませんよね。
つまり、半加算器はAとBと前のCを同時に足し合わせることができないので、2ビット以上のものを足し算することができないのです!!
全加算器とは?
画像: http://kccn.konan-u.ac.jpより引用
そこで半加算器の問題を解決するために登場するのが、『全加算器』です。
Verilogでコーディングするとこんな感じになります。
1 2 3 4 5 6 7 8 9 10 |
module module_full_adder (a, b, cin, s, cout); input wire a, b, cin; output wire s, cout; wire e, g, f; module_half_adder module_half_adder0(a, b, e, f); module_half_adder module_half_adder1(cin, e, s, g); assign cout = g || f; endmodule |
また真理値表は以下のようになっています。
a | b | cin | s | cout |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
全加算器は、
a, b, cinの3つの入力とs, coutの2つの出力を持っているのですが
半加算器を二つ使うことによって、aとbを足し合わせたあとに、その和とcinを足しています。
つまり半加算器では2ビット以上の値を足し合わせることができなかったのに対し、
全加算器では下位の桁からの桁上がりを含めた足し算をできるようになったわけです。
この全加算器を使うことで、ようやくnビットの加算器を作ることができるようになるんですね!
複数の全加算器を使ってnビット加算器を作る
半加算器を二つ使って全加算器を作ることができるという説明をしてましたが、
全加算器は下位の桁上がり(cin)を入力として受け取り、上位の桁上がり(cout)を出力してくれるんでしたよね!
つまり、もう一つ全加算器を用意してあげて、前の全加算器のcoutをcinとして入力・・・・
これを繰り返していくとnビットの加算器を作ることができるんですよ!
論理回路で表現するとこんな感じになります。
画像: https://monoist.atmarkit.co.jpより引用
この図では4ビット加算器となっています。
A0, A1, A2, A3・・・とありますが
これは『1100』という4ビットの値があったときに、1桁目がA0、2桁目がA1、3桁目がA2、4桁目がA3ということに対応しています。
例えばこの加算器にAに0100とBに1111を入力すると、
それぞれの位が足し合わされて、最終的にはSとして0011とCout(桁上がり)として1が出力されます。
1ビット目の全加算器のCinに0が入力されているのは、1ビット目より下位の位は存在しないからと考ればいいと思います。
これをVerilogを表現すると以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module module_n_adder (a, b, s); input wire [`N - 1:0] a, b; output wire [`N - 1:0] s; wire [`N:0] cin; assign cin[0] = 0; generate genvar g; for (g = 0; g < `N; g = g + 1) begin : Gen module_full_adder module_full_adder0(a[g], b[g], cin[g], s[g], cin[g + 1]); end endgenerate endmodule |
Verilogでのモデュールは、
『module名 インスタンス名 (引数)』
というふうに使うのですが、
上記のコードでは、for文でN回、module_full_adder0というインスタンスを生成しています。
本来ならば同じモデュールを何度も呼び出していることになり、これではnビット加算器を生成していることにならないのですが
for()文をgenerate文の中で記述することによって、
Gen0のmodule_full_adder0、
Gen1のmodule_ful_adder1、
Gen2のmodule_ful_adder1・・・・・
という感じでそれぞれ独立したインスタンスにすることができます。
作成したnビット加算器のモジュールを操作しよう!
半加算器を2つ使って全加算器、全加算器をn個使ってnビット加算器、という流れで
論理回路図とともにVerilogのソースコードの解説していきましたが、
次は完成したnビット加算器のモジュールを実際に使っていきましょう!
先ほど作成したmodule_n_adder()を使ってnビットの加算を行うソースコードは以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
`define N 10 module module_top(); reg [`N - 1:0] reg_a, reg_b; wire [`N - 1:0] wire_s; initial begin #100 reg_a <= 1024; reg_b <= 2048; #100 reg_a <= 1023; reg_b <= 1; #100 reg_a <= 1024; reg_b <= 1; #100 reg_a <= 2047; reg_b <= 0; #100 reg_a <= 100000; reg_b <= 10000; end always@(*) #1 $write("%4d %4d -> %d\n", reg_a, reg_b, wire_s); module_n_adder module_n_adder0 (reg_a, reg_b, wire_s); endmodule |
このmodule_top()というモジュールは、
nビット加算器のモジュールであるmodule_n_adder()のインスタンスを生成・利用して、
実際にnビットの計算をシミュレーションしているモジュールです。
initial begin〜で100単位時間ごとにaとbに数値を代入し、
それぞれの操作の1単位時間後に$write()で入力のa, bと結果のsを画面に書き出しています。
実行結果は冒頭での出しましたが
define N 10(=10ビット加算器)にすると、
define N 11(=11ビット加算器)にすると、
という結果になります。
10ビットの表現範囲できる数値が2^10個、すなわち0~1023だったのに対し
11ビットにすると2^11個、すなわち0~2047まで表現できるようになっていることからも
先ほど作ったnビット加算器のモジュールがしっかりと動作していることが分かりますよね!
まとめ
今回はVerilogでnビット加算器のモジュールを作ってみよう!という内容でしたが、いかがだったでしょうか?
Verilogはハードウェア記述言語と呼ばれる種類の言語で
そういう仕事で働いている人か、論理回路の設計をするのが趣味の人くらいしか触る機会がないと思うのですが
論理回路の設計や考え方に親しむことはそれ以外の分野でも役にたつので、いい機会なのではないでしょうか?
最後まで読んでいただきありがとうございました!