情報画像学実験II

実験II-1. 論理回路

第3週目 Verilog による組合せ回路設計


今週の目的

先週の実験では、コンピュータ上で回路図を設計した。しかし、大規模な回路の作成に回路図は向いていない。多くの場合、ハードウェア記述言語 (HDL, Hardware Description Language) と呼ばれるプログラミング言語を用いて記述される。今週は HDL の1種である Verilog を用いて回路設計を行う。Verilog は C言語に似た言語であり、C言語修得者にとっては感覚的に扱いやすい言語であろう。


Verilog による基本的な回路設計

これから 2入力セレクタを題材として Verilog による基本的な回路設計ついて説明する。2入力セレクタの回路図と真理値表を図1と表1に示す。2入力セレクタは入力 S の値に応じ、入力 A,B のどちらかの値をセレクトし出力 Y から出力する。


図1. 2入力セレクタの回路図

表1. 2入力セレクタの真理値表
ABSY
0000
0100
1001
1101
0010
0111
1010
1111

プロジェクトとモジュールの作成

使用するツールは先週同様 Xilinx ISE WebPack である。まずは、プロジェクトを作成する。ISE を起動し、"File → New Project" を選ぶ。プロジェクト名は SELECTOR2 とする。Top-Level Source Type について、先週は "Schematic" と答えたが、今週は "HDL" と答える。後は先週と同じである。次に、モジュールを作成する。"Project → New Source" とする。New Source Wizard が起動する。ファイル名は SELECTOR2 とする。またソースタイプとして "Schematic" ではなく "Verilog Module" を選ぶ。

続いて、図2の通り入出力ポートを指定する。


図2. 2入力セレクタの回路図

残りは肯定的に応える。図3のように、プログラムエディタが現れる。


図3. プログラムエディタ

Verilog プログラム - ひな型

まずは生成されたモジュールのひな型を眺める。図3のうち、ひな型が表示されている部分を図4に拡大して示す。


図4. モジュールのひな型

1行目の "`timescale 1ns / 1ps" はタイムスケールを設定している。本実験ではこの行を変更する必要はない。

2行目以降 "//" で始まる行はコメントである。無視して良い。なお、Verilog でのコメントの書き方は C++ と同じである。すなわち、"/* 〜 */" と "// 〜 (文末)" はコメントと認識され無視される。

21行目のmodule文ではモジュールの開始を宣言している。"module" の後にモジュール名が続き、括弧内に入出力ポート名が列挙される。22〜25行目のinput, output はそれぞれ入出力の方向 (入力か出力か) を指定している。26行目の右括弧は 21行目最後の左括弧に対応している。26行目のセミコロンにより、21行目か始まっていた module 文が終了する。最後に 29行目、"endmodule" でモジュールの終了が宣言される。module文と endmodule の間、すなわち27,28行目に、回路をプログラミングすることになる。

Verilog プログラミング

図5に2入力セレクタのプログラムを示す。


図5. 2入力セレクタの Verilog 記述

図5 のプログラムを順に追っていく。module 文はひな型にあったとおりである。

続く wire文では回路内部の信号線を宣言している。図1を見るとセレクタ内の信号線は SB, C, D の3本であり、これらの信号線がここで宣言されている。

assign 文 (継続代入文) で回路の接続を記述できる。例えば、"assign SB = ~S;" は、信号線 S をNOTゲート (~) を経由して信号線 SB に接続することを意味している。assign 文の右辺の記述法は C言語におけるビット演算子の記述法と同じである。すなわち表2のとおりである。

表2. Verilog で使えるビット演算子
ANDORNOTXOR
&|~^

以上までを元に図1と図5を見比べ、図5のプログラムが図1を実現していることを確認して欲しい。なお、回路中の各ゲートにおいては、入力値が変わり次第直ちに、それが出力値に反映される。C言語など手続き型言語のように、「今プログラムのどこを処理している」という概念はなく「この文を実行した後、この文を実行する」という概念もない。よって、図5のように演算の順序に関係なく assign 文を並べてもよい。(正確には後述するように、 Verilog 言語にも処理順という概念はある。)

なお、例えば "assign Y = (A & ~S) | (B & S);" のようにassign 文の右辺は複数の演算子や括弧を使いながら複雑に書いても良い。優先順位は、括弧 > NOT(~) > AND(&) = XOR(^) > OR(|) の順である。

文法チェックとシミュレーション実験

まずは図5のプログラムを入力、保存する。続いて文法チェックする。 View を Implementation とし、"Implement Design → Synthesize - XST → Check Syntax" とする。図6のように、チェック結果がアイコンで表示されると同時に、トランスクリプトに詳細結果が表示される。図のように緑のレ印が出た場合は文法チェックに成功している。赤い×印が出た場合は、文法エラーがある。トランスクリプトのエラーメッセージを見ながらプログラムを修正すること。


図6. 文法チェック成功例

文法チェックに成功した後はシミュレーションにより動作検証を行う。シミュレーションのやり方は先週と同じである。ちなみに検証用モジュールを記述している言語もまた Verilog である。つまり、コメント文を C++ と同様に記述することもできる。

デバッグ中には、図1の信号線 C, D のような、入出力信号以外の信号線の値を見たくなることも多々ある。これらの値を表示させるためには以下のようにすればよい。

まずは1通り、シミュレーションを行う。図7 のようにパネルのタブから "Instance and Processes" を選択し、パネル中 "uut" (= Unit Under Testing、つまり検証対象回路のこと) を選択する。Objects に回路中の全信号線名が表示されるので、その中から表示させたい信号線名を選び、ワークスペースの信号線名が並んでいる列へドラッグアンドドロップする。信号値を表示するための行が表示されるが、まだ信号値は表示されていない。シミュレーション実験を再度行い、信号値を表示させる。再実行は "Simulation → Restart" と "Simulation -> Run All" を続けて実行することによって行うことができる。


図7. 内部信号線の値の表示法


階層的回路設計を用いた回路設計

C言語ではプログラムをある程度のまとまり毎に関数に分けることが通常である。同様に ISE での回路設計では、回路をある程度のまとまり毎にモジュールに分け、階層的に設計することが通常である。次に階層的回路設計について説明する。題材として4入力セレクタを用いる。4入力セレクタの回路図を図8に示す。4入力セレクタは制御信号 S0, S1 の値に応じて出力値を選択する機能を持ち、3個の 2入力セレクタから構成される。


図8. 4入力セレクタの回路図

図8の4入力セレクタでは、前段の2セレクタにおいて、S0 の値に応じ、選択候補を A, C または B, D に絞り、後段のセレクタにおいて S1 の値に応じ最終的に選択する入力値を決定している。すなわち、真理値表を見ながら1から回路構成を考えてはいない。

まずはプロジェクト SELECTOR4 を新規作成する。続いて、既に作成した2入力セレクタ SELECTOR2 を再利用するため、SELECTOR2 をプロジェクトに加える。"Project → Add Copy of Source" より、フォルダ SELECTOR2 の中にあるファイル SELECTOR2.v を選ぶ。"Adding Source Files..." というダイアログが開くので、"Association" が "All" になっていることを確認し、"OK" する。パネル Design タブ中 Hierarchy より、プロジェクトに SELECTOR2 が読み込まれたことが確認できる。

次にモジュール SELECTOR4 を作成し、図9のように記述する。


図9. 4入力セレクタの Verilog 記述

別のモジュールを用いるとき、以下の書式で記述する。

モジュール名 インスタンス名 (.ポート名(信号名), ...)

インスタンス名とは、各インスタンス(部品として用いられるモジュール)を識別するための名前であり、モジュール内でユニークな名前であれば何でも良い。ただし、信号線名との重複は許さない。図9 では3個の2入力セレクタに対しそれぞれ U1, U2, U3 という名前を付けている。各インスタンスの入出力ポートと信号線の接続は ".ポート名(信号名)" という形式で行う。例えばインスタンス U2 における ".A(C)" は SELECTOR2 の入力ポート A に 信号線 C を接続することを意味する。

以上までを元に図8と図9を見比べ、図9のプログラムが図8を実現していることを確認して欲しい。プログラムが完成した後は、これを保存し、文法チェックし、シミュレーションにより動作検証を行う。


今週の課題 - その1

第3週目-課題1

半加算器、全加算器を Verilog で記述せよ。ただし、後述する手続きブロックと算術演算子を用いてはいけない。ここで全加算器とは半加算器同様、入力信号 A,B の和を S、桁上がりを CO に返す回路である。ただし、下位桁からの桁上げ CI を加える点で半加算器と異なる。A,B,CI の 3値の和を求める回路と理解しても良い。全加算器の回路図と真理値表をそれぞれ、図10, 表2 に示す。


図10. 全加算器の回路図

表2. 全加算器の真理値表
ABCICOS
00000
01001
10001
11010
00101
01110
10110
11111


バスを用いた回路設計

1本の信号線が表すことができる情報量は1ビットである。すなわち 0か 1のみである。2ビット以上の情報を扱うためには2本以上の信号線を用いることになる。複数本の信号線をまとめた束をバスと呼ぶ。また、まとめた信号線の本数をバス幅と呼ぶ。次にバスについて説明する。題材として4ビット加算器を用いる。4ビット加算器の回路図を図11に示す。4ビット加算器はバス幅4ビットの入力A, B と出力 S を持つ。バス A (図中では A[3:0]) は A[3], A[2], A[1], A[0] の4ビットの信号線からなる。また、バス A の値は各信号線の値により2進表記された数値、すなわち23×A[3]+22×A[2]+21×A[1]+20×A[0] であると解釈する。他のバス B, S も同様である。なお、図中ではバスは太線で表現され、タップと呼ばれる三角形において、細線で表されるバスを構成する1本の信号線に分岐する。4ビット加算器は3個の全加算器と1個の半加算器からなる。それぞれの加算器では入力バス A, B の i (0≦i<4) ビット目の信号線、A[i], B[i] が入力として与えられ、出力バス S の iビット目, S[i] にその和を出力する。また、互いの加算器はキャリー信号線により、直列に接続される。


図11. 4ビット加算器の回路図

バスを用いたモジュールの作成

まずは、新規にプロジェクトを作成する。また、課題1で作成した FULL_ADDER.v と HALF_ADDER.v をプロジェクトに加える。4ビット加算器を設計するためのモジュールを新規作成する。ここで、入出力ポートは図12の通りに指定する。入力 A, B と出力 S は 3ビット目から 0ビット目までのバスとして指定される。作成されたひな型をみると、A, B, S は "input [3:0] A," などと宣言されているのが分かる。これは wire文でも使用できる書式である。例えば、"wire [3:0] W;" と書けばバス幅4ビットの内部信号バスを作成することができる。


図12. 4ビット加算器の入出力ポート設定

図13に4ビット加算器のプログラムを示す。図11と図13を見比べ、図13のプログラムが図11を実現していることを確認して欲しい。


図13. 4ビット加算器の Verilog 記述

シミュレーション

回路設計後、HALF_ADDER, FULL_ADDER 同様にシミュレーション実験を行う。図14 のようなシミュレーション結果が得られるだろう。バス名の左隣の三角印をクリックすることにより、バスを構成する信号線毎の波形を見ることができる。また、バス名の右に書かれた数値を右クリックし、"Radix" の指定を変えることにより、2進表記 (Binary) や16進表記 (Hexadecimal)、10進表記 (Decimal) など、好みの表記を選択できる。


図14. バスを用いたときのシミュレーション結果

検証用モジュールについて、バス A, B の入力データを A[0] = 0; A[1] = 1; などと1ビットずつ入力してもよいが、まとめて入力することもできる。例えば、A = 4'b0011; と書けば、A[3], A[2] に 0, A[1], A[0] に 1 を与えたことになる。ここで、m'bxxx の m はバス幅を表し、xxx は入力データを表す。入力データは添字が大きい方から順に列挙される。

バスに関するその他の記述法

バスに関する主な記述法の内、4ビット加算器で用いなかったものについて説明する。

複数の信号線、およびバスをカンマで区切りながら列挙し、中括弧 {} で括ることにより、これらの信号線、バスを束ねた新たなバスを作ることができる。例えば、幅が3であるバス A と、信号線 B, C, D について

assign A = {B, C, D};

assign A[2] = B;
assign A[1] = C;
assign A[0] = D;

と等価である。

また列挙する信号線、バスについて、これをさらに中括弧でくくり、その直前に数字を付すことにより、その数字の回数だけ、この信号線、バスを繰り返すことができる。例えば、幅が3であるバス A と信号線 B について、

assign A = {3{B}};

assign A[2] = B;
assign A[1] = B;
assign A[0] = B;

と等価である。

バス A について、A[m:n] は A の n ビット目から mビット目までの信号線からなる幅 m-n+1 のバスを意味する。例えばバス A, B について、

assign B = A[5:3];

assign B[2] = A[5];
assign B[1] = A[4];
assign B[0] = A[3];

は等価である。

assign 文において、信号線の代わりに同一幅のバスを記述することができる。この場合、各ビットについてそれぞれ assign文を記述した場合と等価になる。すなわち、例えば幅が3であるバス A, B, Y について

assign Y = A & B;

assign Y[2] = A[2] & B[2];
assign Y[1] = A[1] & B[1];
assign Y[0] = A[0] & B[0];

と等価である。

演算子の左右のバス幅が異なる場合、幅が等しくなるよう、幅が短い方の上位に 0 が補われる。例えば幅が3 であるバス A, Y と信号線(つまり、幅が1であるバス) B について、

assign Y = A & B;

assign Y[2] = A[2] & 0 = 0;
assign Y[1] = A[1] & 0 = 0;
assign Y[0] = A[0] & B;

と等価である。

assign Y[2] = A[2] & B;
assign Y[1] = A[1] & B;
assign Y[0] = A[0] & B;

と等価な表記は

assign Y = A & {3{B}};

であるので注意すること。

代入文の左辺より右辺の幅が短い場合もまた、幅が等しくなるよう右辺の上位に 0 が補われる。また、右辺の幅が長い場合は、幅が等しくなるよう右辺の上位ビットが捨てられる。

バスとしてまとめて扱われる信号線の値は、しばしば 2進表記された数値として解釈される。例えば、幅が3であるバス A について、A[2] = 1, A[1] = 1, A[0] = 0 であるなら、バス A の値は、(110)2 = 6 と解釈される。バス幅不一致時に 上位 に 0 を補ったり、上位ビットから捨てたりしたのは、なるべくバスの値を変えないように、バス幅を調整しているからである。例えば、110 の下位桁に 0 を補えば (1100)2=12 となるが、上位桁に 0 を補えば (0110)2=6 となる。

階層的回路設計において、入出力にバスを持つモジュールを用いるとき、ポート名としてバス名を用いる。バス内の個別の信号線名を用いてはいけない。例えば、入力バス A を持つモジュール EXAMPLE をバス X に接続して用いるときは、以下のように記述する。

EXAMPLE U0 (.A(X), ...);

以下のように記述してはいけない。

EXAMPLE U0 (.A[0](X[0]), .A[1](X[1]), ...); /* 許されない記述 */

今週の課題 - その2

課題2は選択問題である。課題2A の方が簡単である。自信のある者は是非課題2Bの方に挑戦して欲しい。

第3週目-課題2A

4ビット加減算器を作成せよ。ここで、加減算器とは制御入力の値に応じ、加算器または減算機となる回路である。

第3週目-課題2B

4ビット乗算器を作成せよ。ただし、後述する乗算演算子を用いてはいけない。


手続きブロックを用いたプログラミング

Verilog プログラム中には手続きブロックと呼ばれる、C言語など手続き型言語のような考え方が適用できるブロックを設けることができる。手続きブロックを用いることにより、さらに柔軟な回路記述を行うことが可能になる。ここでは、SELECTOR2 を題材に手続きブロックを用いたプログラミングを学ぶ。

図15 は if文を用いたプログラミング例である。


図15. if文を用いた 2入力セレクタの Verilog 記述

手続きブロックは always 文で始まる。always @(*) 続く begin に対応する end までが手続きブロックとなる。C言語においてブロックは中括弧 {} で示されたが、Verilog では begin 〜 end で示す。この手続きブロックは、信号値に変化がある毎に実行される。

if 文の書き方は C言語とほぼ同じである。if に続く () 内が 1 (真) であるとき、直後の文、またはブロックが実行される。また 0 (偽) であるとき、else に続く文・ブロックが実行される。

手続きブロック内で更新されるバス・信号線は wire 型ではなく reg型で宣言される必要がある。また、手続きブロック内でモジュールの出力ポートの値を更新する場合は、そのバス・信号線は、例の出力 Y のようにように、"output reg" と宣言する必要がある。階層的記述において、reg 型で宣言された出力を持つモジュールを利用する場合、利用した側の信号線は wire型になる。つまり、図15の SELECTOR2 を用いて SLECTOR4を設計するとき、図9の信号線 E, F, Y はすべて wire 型のままでよい。

手続きブロック内では、値の代入に <= を用いる。これはノンブロッキング代入と呼ばれる。左辺値は代入文が実行された時点ではなく、手続きブロック内のすべての処理が終わった直後に更新される。つまり例えば、(A,B) = (0,1) のとき、 B <= A; if(B) 〜 else … と実行するとif文実行時には B の値がまだ更新されていないため、else に続くブロック … ではなく、if(B) に続くブロック 〜 が実行される。

1個のreg型変数は 2個以上の always 文内で更新されてはいけない。(文法上の問題はないが、ISE を含め、少なくないツールがそのような記述をサポートしない。)

図16 は case 文を用いたプログラミング例である。


図16. case文を用いた 2入力セレクタの Verilog 記述

case 文は C言語の switch-case 文に似ている。case に続く () 内の条件値に応じて分岐を行う。図16 の場合、信号線 A, B, S から 幅3ビットのバスを作成し、その値を条件値として分岐を行う。ここで、m'bxxx は xxx において 2進表記された定数値を持つ mビット (m自体は10進数表記) のバスを表す。例えば、3'b100 は 定数値 (100)2 を持つ 3ビットバスである。よって、3'b100: は条件値が (100)2 と一致するとき、つまり A=1, B=0, S=0 のとき、続く文またはブロックを実行せよ、という意味である。

C言語のswitch-case 文とは異なり、break 文がなく、1文/ブロックを実行すると case 文から抜ける。条件値が指定されたどの値とも一致しない場合、default: に続く文・ブロックを実行する。case 文は endcase で終わる。endcase にはセミコロン (;) を付けない。

なお、定数値表記について、b の代わりに o, d, h を用いることにより、8進、10進、16進で表記することができる。また、3'b などを付けずに単に数値だけを書いた場合、10進数表記された32ビットの定数値とみなされる。たとえば Y<=1; の右辺は (00…001)2 (0 は 31個) を意味している。この場合、左辺が信号線 (幅が 1ビットのバス) であるため、0 からなる上位 31ビットは捨てられており、最下位ビットの 1 のみ用いられる。

論理合成

図15と図16から図1の回路は想像できない。図15はC言語プログラムもどきであり、図16は単に真理値表を書いただけである。このことから、図15や図16のようなプログラムを作成することは回路設計ではないと思うかも知れない。しかし、図15や図16の作成は立派な回路設計である。

図15や図16、また図5、図9を含め、本実験で扱っている Verilog 記述は RTL レベル (Register Transfer Level) と呼ばれるレベルの記述法であり、回路とは一対一の関係を取っていない。実際の設計工程では、RTL レベルで回路を設計した後、論理合成ツールを用いて論理合成を行い、回路と一対一の対応が取れるゲートレベル(ネットリストレベル)の記述へ変換する。この際、論理合成ツールは、伝搬遅延時間やコンデンサの容量など、さまざまな要因を考慮しながら、また、なるべく面積が小さくなるよう、実際に使用するゲートを選択する。

実際に論理合成を行ってみる。図15、図16、どちらのプログラムでも良いので入力し、また、正しく動作することをシミュレーション実験で、確認すること。なお、プロジェクト作成時にデバイスが spartan-2 用ではなく第2週図4 のように Cool-Runner2 用になっていることを確認すること。

パネルのタブ Design 中 "Implement Design → Synthesize - XST → View Technology Schematic" とする。図17 のダイアログが開くので "Start with a schematic of the top-level block" を選ぶ。


図17. 論理合成対象回路指定ダイアログ

図18のような画面が現れるはずである。ワークスペースに出現した矩形をダブルクリックすると図19のようになる。IBUF, OBUF など入出力バッファが挿入されている他は、図1に書いた回路図とほぼ同じ回路図が得られている。


図18. 論理合成された2入力セレクタ (シンボル)


図19. 論理合成された2入力セレクタ (回路図)

論理合成ツールを用いることにより、いちいちカルノー図などを書く手間が省ける他、人間でもなかなか思いつけないような回路量の小さい回路構成を得ることができる。回路設計者は、伝搬遅延時間やコンデンサの容量など、ゲートレベルの様々な細かい問題をいちいち強く意識する必要がなく、大局的な視点からの回路設計に専念することができる。

合成技術の発展はめざましく、近年では C/C++ 等で書かれたプログラム、つまりクロック信号など回路の構成に必要な事柄を考慮せずに組まれたプログラムを、タイミング、スケジューリング等の問題を解決しながら RTLレベルやゲートレベルに変換する高位合成の開発研究も進んでいる。


今週の課題 - その3

第3週目-課題3

手続きブロックを用いて7セグメントレジスタデコーダを作成せよ。7セグメントレジスタデコーダについての説明は先週の課題1を見よ。


算術演算

代入文(assign 文、ノンブロッキング代入文)の右辺などには C言語同様、+-*/ などの算術演算子を用いることができる。この場合、演算子の左右のバスは 2進表記された数値であると見なされ、それぞれの算術演算が行われる。

幅がmビットのバス A, B について、A+B と A-B は m+1 ビットのバスとなる。最上位ビットはキャリー出力である。例えば、4ビット加算器は Verilog では図20のように記述できる。

module ADDER4(
        input [3:0] A,
        input [3:0] B,
        output [3:0] S,
        output CO
        );
        
        assign {CO, S} = A + B;
endmodule

図20. 算術演算子を用いた4ビット加算器

幅がm ビットのバス A, B について、A*B は 2m ビットのバスとなる。図21に 4ビット乗算器の Verilog 表記を示す。Verilog 上では原則、符号無し数値が扱われる。つまり、扱う数値は正数または 0 として扱われる。負数(2の補数表現)を扱うためには、バスの宣言時に signed である旨を併せて宣言する必要がある。図21では、バス A, B, Y の値が符号付き数値であるとみなされる。

module SIGNED_MULTIPLIER4(
        input signed [3:0] A,
        input signed [3:0] B,
        output signed [7:0] Y 
        );
        
        assign Y = A * B;
endmodule

図21. 算術演算子を用いた符号付き4ビット乗算器

注意。乗算演算子 * の左右が符号付きバスと符号無しバスであるときは、符号無し乗算が行われる。符号付きバス A, B について、例えば A[3:2] や {A, B} は符号無しバスと見なされる。このようなバスを符号付きバスとして扱いたい場合は、符号付きバスとして宣言された別のバスを用意し、それに一旦代入する他ない。

Verilog では他に、シフト演算子 (>>, <<) 、条件(三項)演算子 (?:)、比較演算子 (==, !=, <, >, <=, >=)、論理演算子 (!, &&, ||) を用いることができる。インクリメント・デクリメント演算子 (++, --)は用いることができない。また、501, 503 にインストールしている ISE では除算記号 (/) を扱うことはできない。(シミュレーションは実行可能である。)除算を行いたいときは IP コア (C言語でのライブラリ関数のようなもの) を用いる必要がある。一般に回路にとって除算は簡単な演算ではない。


今週の課題 - その4

第3週目-課題4

図21 の4ビット乗算器について、"signed" を用いた場合と、用いない場合の動作を比較せよ。


第2週目 ISE Schematic Editor を用いた論理回路設計に戻る
第4週目 Verilog による順序回路設計に進む
実験II-1トップページに戻る
難波担当実験・演習のページに戻る

難波 一輝 (助教・伊藤・北神・難波研究室)
工学部1号棟4階409号室、内線3255、043-290-3255、namba@ieee.org