先週の実験では、コンピュータ上で回路図を設計した。しかし、大規模な回路の作成に回路図は向いていない。多くの場合、ハードウェア記述言語 (HDL, Hardware Description Language) と呼ばれるプログラミング言語を用いて記述される。今週は HDL の1種である Verilog を用いて回路設計を行う。Verilog は C言語に似た言語であり、C言語修得者にとっては感覚的に扱いやすい言語であろう。
これから 2入力セレクタを題材として Verilog による基本的な回路設計ついて説明する。2入力セレクタの回路図と真理値表を図1と表1に示す。2入力セレクタは入力 S の値に応じ、入力 A,B のどちらかの値をセレクトし出力 Y から出力する。
図1. 2入力セレクタの回路図
表1. 2入力セレクタの真理値表
A | B | S | Y |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 |
1 | 0 | 0 | 1 |
1 | 1 | 0 | 1 |
0 | 0 | 1 | 0 |
0 | 1 | 1 | 1 |
1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
使用するツールは先週同様 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. プログラムエディタ
まずは生成されたモジュールのひな型を眺める。図3のうち、ひな型が表示されている部分を図4に拡大して示す。
図4. モジュールのひな型
1行目の "`timescale 1ns / 1ps" はタイムスケールを設定している。本実験ではこの行を変更する必要はない。
2行目以降 "//" で始まる行はコメントである。無視して良い。なお、Verilog でのコメントの書き方は C++ と同じである。すなわち、"/* 〜 */" と "// 〜 (文末)" はコメントと認識され無視される。
21行目のmodule文ではモジュールの開始を宣言している。"module" の後にモジュール名が続き、括弧内に入出力ポート名が列挙される。続く、22〜25行目のinput文, output文 では入出力の方向 (入力か出力か) が指定される。最後に 28行目、"endmodule" でモジュールの終了が宣言される。input文, output 文と endmodule の間、すなわち26,27行目に、回路をプログラミングすることになる。
図5に2入力セレクタのプログラムを示す。
図5. 2入力セレクタの Verilog 記述
図5 のプログラムを順に追っていく。module 文、input 文、output 文はひな型にあったとおりである。
続く wire文では回路内部の信号線を宣言している。図1を見るとセレクタ内の信号線は SB, C, D の3本であり、これらの信号線がここで宣言されている。
assign 文 (継続代入文) で回路の接続を記述できる。例えば、"assign SB = ~S;" は、信号線 S をNOTゲート (~) を経由して信号線 SB に接続することを意味している。assign 文の右辺の記述法は C言語におけるビット演算子の記述法と同じである。すなわち表2のとおりである。
表2. Verilog で使えるビット演算子
AND | OR | NOT | XOR |
---|---|---|---|
& | | | ~ | ^ |
以上までを元に図1と図5を見比べ、図5のプログラムが図1を実現していることを確認して欲しい。なお、回路中の各ゲートにおいては、入力値が変わり次第直ちに、それが出力値に反映される。C言語など手続き型言語のように、「今プログラムのどこを処理している」という概念はなく「この文を実行した後、この文を実行する」という概念もない。よって、図5のように演算の順序に関係なく assign 文を並べてもよい。(正確には後述するように、 Verilog 言語にも処理順という概念はある。)
なお、例えば "assign Y = (A & ~S) | (B & S);" のようにassign 文の右辺は複数の演算子や括弧を使いながら複雑に書いても良い。優先順位は、括弧 > NOT(~) > AND(&) = XOR(^) > OR(|) の順である。
まずは図5のプログラムを入力、保存する。続いて文法チェックする。プロセス窓から "Implement Design → Synthesize - XST → Check Syntax" とする。図6のように、チェック結果がプロセス窓にアイコンで表示されると同時に、トランスクリプト窓に詳細結果が表示される。図のように緑のレ印が出た場合は文法チェックに成功している。赤い×印が出た場合は、文法エラーがある。トランスクリプト窓のエラーメッセージを見ながらプログラムを修正すること。
図6. 文法チェック成功例
文法チェックに成功した後はシミュレーションにより動作検証を行う。シミュレーションのやり方は先週と同じである。
デバッグ中には、図1の信号線 C, D のような、入出力信号以外の信号線の値を見たくなることも多々ある。これらの値を表示させるためには以下のようにすればよい。
まずは1通り、シミュレーションを行う。図7 のようにプロセス窓 Sim Hierarchy タブ中、"UUT (モジュール名)"中から表示させたい信号線名を選び、ワークスペースの信号線名が並んでいる列へドラッグアンドドロップする。信号値を表示するための行が表示されるが、まだ信号値は表示されていない。シミュレーション実験を再度行い、信号値を表示させる。
図7. 内部信号線の値の表示法
Verilog における階層的回路設計およびバスを用いた回路設計を、4入力セレクタを題材に説明する。4入力セレクタの回路図を図8に示す。4入力セレクタは2ビット幅のバス S の値に応じて出力値を選択する機能を持ち、3個の 2入力セレクタから構成される。
図8. 4入力セレクタの回路図
まずはプロジェクト SELECTOR4 を新規作成する。続いて、"Project → Add Copy of Source" より、フォルダ SELECTOR2 の中にあるファイル SELECTOR2.v を選ぶ。後は肯定的に応える。なお、2週目の Schematic による回路設計においては Symbol Wizard よりシンボル作成を行ったが、Verilog による回路設計では相当する操作はない。
次にモジュール SELECTOR4 を作成する。入出力ポートは図9の通りに指定する。入力 S は 1ビット目から 0ビット目までのバスとして指定される。作成されたひな型をみると、入力 S は "input [1:0] S;" と宣言されているのが分かる。これは wire文でも使用できる書式である。例えば、"wire [3:0] W;" と書けばバス幅4ビットの内部信号バスを作成することができる。
図9. 4入力セレクタの入出力ポート設定
図10に4入力セレクタのプログラムを示す。
図10. 4入力セレクタの Verilog 記述
別のモジュールを用いるとき、以下の書式で記述する。
モジュール名 インスタンス名 (.ポート名(信号名), ...)
インスタンス名とは、各インスタンス(部品として用いられるモジュール)を識別するための名前であり、モジュール内でユニークな名前であれば何でも良い。ただし、信号線名との重複は許さない。図10 では3個の2入力セレクタに対しそれぞれ U1, U2, U3 という名前を付けている。各インスタンスの入出力ポートと信号線の接続は ".ポート名(信号名)" という形式で行う。例えば図9のインスタンス U2 における ".A(C)" は SELECTOR2 の入力ポート A に 信号線 C を接続することを意味する。
Schematic Editor による回路設計では、バス A の i 番目の信号線を A(i) と表した。一方、Verilog では A[i] と [] を用いて表す。注意されたい。
以上までを元に図8と図10を見比べ、図10のプログラムが図8を実現していることを確認して欲しい。プログラムが完成した後は、これを保存し、文法チェックし、シミュレーションにより動作検証を行う。
バスに関する主な記述法の内、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 となる。
半加算器、全加算器、4ビット加算器を Verilog で記述せよ。ただし、後述する手続きブロックと算術演算子を用いてはいけない。
4ビット乗算器を作成せよ。ただし、後述する乗算演算子を用いてはいけない。
Verilog プログラム中には手続きブロックと呼ばれる、C言語など手続き型言語のような考え方が適用できるブロックを設けることができる。手続きブロックを用いることにより、さらに柔軟な回路記述を行うことが可能になる。ここでは、SELECTOR2 を題材に手続きブロックを用いたプログラミングを学ぶ。
図11 は if文を用いたプログラミング例である。
図11. if文を用いた 2入力セレクタの Verilog 記述
手続きブロックは always 文で始まる。always @(*) 続く begin に対応する end までが手続きブロックとなる。C言語においてブロックは中括弧 {} で示されたが、Verilog では begin 〜 end で示す。この手続きブロックは、信号値に変化がある毎に実行される。
if 文の書き方は C言語とほぼ同じである。if に続く () 内が 1 (真) であるとき、直後の文、またはブロックが実行される。また 0 (偽) であるとき、else に続く文・ブロックが実行される。
手続きブロック内で更新されるバス・信号線は wire 型ではなく reg型で宣言される必要がある。また、手続きブロック内でモジュールの出力ポートの値を更新する場合は、そのバス・信号線は、例の出力 Y のようにように、output 文と reg 文の両方で宣言する必要がある。階層的記述において、reg 型で宣言された出力を持つモジュールを利用する場合、利用した側の信号線は wire型になる。つまり、図11の SELECTOR2 を用いて SLECTOR4を設計するとき、図10の信号線 E, F, Y はすべて wire 型のままでよい。
手続きブロック内では、値の代入に <= を用いる。これはノンブロッキング代入と呼ばれる。左辺値は代入文が実行された時点ではなく、手続きブロック内のすべての処理が終わった直後に更新される。つまり例えば、(A,B) = (0,1) のとき、 B <= A; if(B) 〜 else … と実行するとif文実行時には B の値がまだ更新されていないため、else に続くブロック … ではなく、if(B) に続くブロック 〜 が実行される。
1個のreg型変数は 2個以上の always 文内で更新されてはいけない。(文法上の問題はないが、ISE を含め、少なくないツールがそのような記述をサポートしない。)
図12 は case 文を用いたプログラミング例である。
図12. case文を用いた 2入力セレクタの Verilog 記述
case 文は C言語の switch-case 文に似ている。case に続く () 内の条件値に応じて分岐を行う。図12 の場合、信号線 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 のみ用いられる。
図11と図12から図1の回路は想像できない。図11はC言語プログラムもどきであり、図12は単に真理値表を書いただけである。このことから、図11や図12のようなプログラムを作成することは回路設計ではないと思うかも知れない。しかし、図11や図12の作成は立派な回路設計である。
図11や図12、また図5、図10を含め、本実験で扱っている Verilog 記述は RTL レベル (Register Transfer Level) と呼ばれるレベルの記述法であり、回路とは一対一の関係を取っていない。実際の設計工程では、RTL レベルで回路を設計した後、論理合成ツールを用いて論理合成を行い、回路と一対一の対応が取れるゲートレベル(ネットリストレベル)の記述へ変換する。この際、論理合成ツールは、伝搬遅延時間やコンデンサの容量など、さまざまな要因を考慮しながら、また、なるべく面積が小さくなるよう、実際に使用するゲートを選択する。
実際に論理合成を行ってみる。図11、図12、どちらのプログラムでも良いので入力し、また、正しく動作することをシミュレーション実験で、確認すること。なお、プロジェクト作成時にデバイスが spartan-2 用ではなく第2週図4 のように Cool-Runner2 用になっていることを確認すること。
プロセス窓から "Implement Design → Synthesize - XST → View Technology Schematic" とする。図13のような画面になる。ワークスペースに出現した矩形をダブルクリックすると図14のようになる。IBUF, OBUF など入出力バッファが挿入されている他は、図1に書いた回路図とほぼ同じ回路図が得られている。
図13. 論理合成された2入力セレクタ (シンボル)
図14. 論理合成された2入力セレクタ (回路図)
論理合成ツールを用いることにより、いちいちカルノー図などを書く手間が省ける他、人間でもなかなか思いつけないような回路量の小さい回路構成を得ることができる。回路設計者は、伝搬遅延時間やコンデンサの容量など、ゲートレベルの様々な細かい問題をいちいち強く意識する必要がなく、大局的な視点からの回路設計に専念することができる。
合成技術の発展はめざましく、近年では C/C++ 等で書かれたプログラム、つまりクロック信号など回路の構成に必要な事柄を考慮せずに組まれたプログラムを、タイミング、スケジューリング等の問題を解決しながら RTLレベルやゲートレベルに変換する高位合成の開発研究も進んでいる。
手続きブロックを用いて7セグメントレジスタデコーダを作成せよ。7セグメントレジスタデコーダについての説明は先週の課題1を見よ。
代入文(assign 文、ノンブロッキング代入文)の右辺などには C言語同様、+-*/ などの算術演算子を用いることができる。この場合、演算子の左右のバスは 2進表記された数値であると見なされ、それぞれの算術演算が行われる。
幅がmビットのバス A, B について、A+B と A-B は m+1 ビットのバスとなる。最上位ビットはキャリー出力である。例えば、4ビット加算器は Verilog では図15のように記述できる。
module ADDER4(A, B, S, CO); input [3:0] A; input [3:0] B; output [3:0] S; output CO; assign {CO, S} = A + B; endmodule |
図15. 算術演算子を用いた4ビット加算器
幅がm ビットのバス A, B について、A*B は 2m ビットのバスとなる。図16に 4ビット乗算器の Verilog 表記を示す。Verilog 上では原則、符号無し数値が扱われる。つまり、扱う数値は正数または 0 として扱われる。負数(2の補数表現)を扱うためには、バスの宣言時に signed である旨を併せて宣言する必要がある。図16では、バス A, B, Y の値が符号付き数値であるとみなされる。
module SIGNED_MULTIPLIER4(A, B, Y); input signed [3:0] A; input signed [3:0] B; output signed [7:0] Y; assign Y = A * B; endmodule |
図16. 算術演算子を用いた符号付き4ビット乗算器
注意。乗算演算子 * の左右が符号付きバスと符号無しバスであるときは、符号無し乗算が行われる。符号付きバス A, B について、例えば A[3:2] や {A, B} は符号無しバスと見なされる。このようなバスを符号付きバスとして扱いたい場合は、符号付きバスとして宣言された別のバスを用意し、それに一旦代入する他ない。
Verilog では他に、シフト演算子 (>>, <<) 、条件(三項)演算子 (?:)、比較演算子 (==, !=, <, >, <=, >=)、論理演算子 (!, &&, ||) を用いることができる。インクリメント・デクリメント演算子 (++, --)は用いることができない。また、501, 503 にインストールしている ISE では除算記号 (/) を扱うことはできない。(シミュレーションは実行可能である。)除算を行いたいときは IP コア (C言語でのライブラリ関数のようなもの) を用いる必要がある。一般に回路にとって除算は簡単な演算ではない。
図16 の4ビット乗算器について、"signed" を用いた場合と、用いない場合の動作を比較せよ。
複素数の和を求める回路と積を求める回路を作成せよ。ただし、入出力は 32ビットバスとし、 上位 16ビットを実数部、下位 16ビットを虚数部とみなし、それぞれ2の補数表記された整数値であるとみなす。例えば、バス A[31:0] = (0001fffe)16 は (fffe)16=-2 より、A[31:16] + j×A[15:0] = 1-2jなる複素数であるとみなす。また、演算結果の実数部虚数部の値が -32768〜32767 の範囲を超えるような場合は無視せよ。
難波 一輝 (助教・伊藤・北神・難波研究室)
工学部1号棟4階409号室、内線3255、043-290-3255、namba@ieee.org