FPGAを用いた論理回路設計

FPGAとディジタル回路

近年、身の回りに急速に増えてきた、携帯電話、パソコン、 携帯オーディオ機器などのいわゆる情報機器の中では、 かなり多くの部分で、「ディジタル」な信号処理が行われています。 この「ディジタル」な信号処理は、「ディジタル回路」で行われる わけですが、それは、「論理回路」の講義でみなさんが習ったような、 いわゆる「論理回路」として実装されています。

論理回路は、インバータ、ANDゲートなどの論理ゲートを基本とし、 フリップフロップなどの記憶素子を組み合わせて設計します。 その設計手法としては、カルノー図による論理関数の簡略化、 順序回路の設計のための状態遷移図、など、多くの手法がありました。

それらの設計の結果を実際の回路として動作をさせるためには、 「回路図」を描き、「論理ゲート」が入っている部品(論理IC)を プリント基板の上に実装し、配線を行って回路を製作することになります。 しかしこの手法は、回路の「デバッグ」、つまり回路が期待した動作を しないときの間違い探しや、それに伴う回路の修正が大変面倒である、 という問題があります。 近年、情報機器がますます複雑化・高機能化してきて、この傾向は ますます顕著になりつつあります。

ところが近年、このような「古典的」な論理回路の設計手法とは別の、 全く新しいタイプの回路設計手法が現れ、ここ数年で急速に一般的に なってきました。 それは、「回路を自由にプログラムできる」論理素子、を 使うものです。 それは、インバータ、ANDゲート、フリップフロップのように、 機能が決まっている部品(論理IC)ではなく、あたかも白紙の設計図のように、 最初は機能が全く決まっていなくて、あとから「プログラムを書く」ように 機能を決められる論理ICです。 このタイプの論理ICを、総称してPLD (Programmable Logic Device)と 呼びます。 このPLDには、進化の歴史的経緯から、大きく分けて次の2つに 分類されます。

その両者の詳細については、別の講義(「集積回路設計及び演習」(4年前期)など)に ゆずりますが、この実験では、後者のFPGAと呼ばれる論理ICを用いて 実験を進めることにします。 この実験では、Altera社の CycloneIIという名称のFPGAを用い、このFPGAのほかに、 LEDやスイッチなどが載っているボードを用いることにします。

FPGAによるディジタル回路設計

FPGAを用いて論理回路を作るするためには、 FPGAに書き込む(プログラムする)論理回路を設計する必要があります。 この論理回路の設計のための方法は、大きく分けて次の2つがあります。 まずは、みなさんが慣れ親しんでいる「回路図」を用いる方法で、 早速実験を始めることにしましょう。
※論理回路などの復習のため、教科書・参考書などを持参するとよいでしょう

回路図を用いた論理回路設計

回路設計の開始

※プログラムの作成に先立って、まず実験用のフォルダを作成しておくと よいでしょう。 マイコンピュータ→Zドライブの中、または、「マイ ドキュメント」の中に、 実験第3用のフォルダを作成します。 今後の作業はすべてこの中で行うことにします。 なお、「Zドライブ」または「マイ ドキュメント」に置いたファイルはどの PCでも共有されます。

サンプルのファイル一式(sample_sch.zip)を ここから取得し、展開します。 すると sample.bdf, sample.qpf, sample.qsf の3つのファイルが 現れますので、それを作業用フォルダを作成してそこにコピーをします。 なお今後、いろいろな回路を作っていくことになりますが、その都度、 新しいフォルダを作り、この3つのファイルをコピーしてから 作業を開始します。 新しい回路を作り始めるとき、 回路図のファイル名を変更して保存するだけではいけません。 必ず、別のフォルダで作業を開始しましょう。

その後、sample.qpfをダブルクリックすると、Altera社の FPGA設計用(CAD: Computer Aided Design)ソフトウエアQuartusIIが 起動します。 (あるいはデスクトップ上のQuartusIIのアイコンをダブルクリックし、 メニューのFile → Open Projectで、sample.qpfを選択します)
※下図のsample.qpfのアイコンを選択すること。別のアイコンに注意。
sample.qpfのアイコン

なお初めてQuartusIIを起動したときだけ、ライセンスの設定をする必要があります。 起動時にライセンスをどうするか聞かれた場合は、"If you have valid license..."を選びます。 聞かれなかった場合は、起動後にTools→License Setupから、ライセンス設定画面に入り、 "License file"欄に、"1800@winsv1"(1800 あっと winsv いち)と入力しておきます。

回路図の入力

早速回路図を見てみましょう。 このサンプルでは、スイッチとLEDをインバータで接続しただけの 回路が入力されています。 QuartusIIの画面の左上の Project Navigator部に表示されている "sample"をダブルクリックすると、この回路図が表示されます。 (この回路図は、sample.bdfという名称のファイルとして保存されています)

この回路図中の左側は "SW[0]"という名称がついた端子があります。 これは、実際にはFPGAボード上のSW0と書いてあるスイッチに接続されている 端子(入力)です。 同様に右側の"LED[0]"という名称の端子は、FPGAボード上の LED0と書いてあるLEDに接続されている端子(出力)です。 つまりこの回路は、SW[0]の値(押すと0、離していると1; 普通と逆(負論理)なので注意)を反転して LED[0]に出力(1で点灯、0で消灯)する、という回路です。

ではこれにもう1つ回路を追加してみましょう。 回路図の左側に並んでいるツールボタンの中から、 "Symbol tool"(ANDゲートのマーク)を選びます。

すると入力するべき論理素子などを選択する画面が現れます。

この左側のLibrariesの中のツリー構造を 開いていくと、入力することができる回路要素が現れます。 まずはこの中から、primitives → logic と選び、その中の not(インバータ)を選択してOKを押しましょう。 するとインバータが回路図上に現れ、適当な場所でクリックすると 回路図上に配置されます。 同様に、入力端子(primitives → pin のinput)と 出力端子(primitives → pin のoutput)も配置しておきます。 そして入力端子、出力端子をそれぞれダブルクリックし、 Pin nameを、それぞれSW[1]、LED[1]に変更しておきます。

なお回路図中の配置済みの回路要素は、ドラッグして選択後、 コピー&ペーストも可能ですので活用すると便利でしょう。 まずは今回は、これで回路図入力を終わりにすることにします。

なお使用可能な回路要素は、これ以外にも論理ゲート、 フリップフロップや、論理IC(いわゆる74シリーズ)の機能ブロックなど 多数があります。 これらの名称と機能は、Help→Contentsの、「目次」の中の下のほうに あるPrimitivesの項にまとめられていますので、参考にしてください。 (英文ですが、要点だけであればそれほど苦労はしないはず)

ちなみに、いわゆる74シリーズの機能ブロックは、 回路要素を配置するときに、others → maxplus2の中から選べます。 74シリーズの型番と機能との対応は、 Google先生に聞くか、 Wikipediaを参考にするとよいでしょう。 例えば、4ビットの2進数(10進数で0〜9)を、7セグメントLEDの 点灯パターンに変換する機能を持つ論理IC(デコーダ)として、 7447というものがあります。

なお74シリーズの各論理ICの機能の詳細は、 それぞれのデータシート(仕様書)を読むことになります。 それらは、Web上(例えばTexas Instruments社のWebページ(英文)中の検索("Enter Part Number"))で 見つけられるでしょう。

コンパイル(インプリメンテーション)

入力した回路図を、FPGA上で実際に動作する論理回路とするためには、 まずは入力した回路をFPGAの設定情報に「コンパイル(compile)」 (または「インプリメンテーション(implementation)」と呼ぶ)し、 その後、それをFPGAに書き込むする必要があります。

まずは、コンパイルを行いましょう。

QuartusIIの画面の上のほうに並んでいるボタンの中から、 "Start Compilation"ボタンを押します。 すると、入力した回路をFPGAの設定情報に変換する作業が始まります。 右側中央の"Status"画面に、この作業の進捗状況が表示されますので、 すべてが"100%"になるまで、待つことにします。 すべての作業が無事終わると、"Full compilation was successful"の ダイアログが現れます。 もし入力した回路図に、変換作業が不可能な間違いがある場合は エラーが表示されて変換作業が中断しますので、適宜修正を加えます。


いくつかのWarningが多く表示されていると思います。 無視してよいWarningもありますが、回路の設計ミスに起因するものも あるので、Warningをチェックするクセをつけておいた方がよいでしょう。 QuartusIIの下のMessage画面のWarningタブを選ぶと、 コンパイル時に現れたWarningを確認することができます。 sample_sch.zip内のプロジェクトをそのままコンパイルしたときには、 以下のようなWarningが出ているかと思います。

Warning: Ignored locations or region assignments to the following nodes
	Warning: Node "CLK6" is assigned to location or region, but does not exist in design
	Warning: Node "LED[1]" is assigned to location or region, but does not exist in design
        ...(以下略)
これは、以下の信号(node)のlocation(FPGAのICのピン番号の指定)をignore(無視)する、というWarningで、具体的にはCLK6やLED[1]など多数の信号のlocationが指定されているが設計したデータ(design)にはその信号がないので無視する、という旨のメッセージが表示されています。 たしかに今回の設計では、CLK6やLED[1]などの信号は使っていませんが、今後のために、これらの信号のlocationをプロジェクトファイルとして指定してあります。(FPGAボードに載ってるLEDやスイッチ等が実際につながっているピン番号にあわせてある)したがって今回のこのWarningは、確かに使っていない信号に関するものなので、無視してよいことになります。

なお当然ですが、回路図自体の間違い(論理機能の間違い)は、 この変換作業ではエラーとなりませんので、設計者が十分注意をして 設計をする必要があります。

シミュレーション

設計した回路が正しく動作するかを、FPGAボードで動作させる前に シミュレーションで検証することができます。 特にこの実験の後半で作るような大規模な回路では、 シミュレーションでの検証とデバッグが便利ですので、 今のうちに使い方に慣れておきましょう。

シミュレーションでは、まず回路に与える入力信号を作り、 それに対して得られる出力(や内部信号)をシミュレーションで求め、 それが正しいかどうかを検証します。

まず回路に与える入力信号をつくります。 File→Newで、"Verification..."グループの中から"Vector Waveform File"を選びます。

↑このような波形ウインドウが表示されます。 ここの"Name"欄の空白のところで右クリック→Insert→Node or Busで 信号を追加します。 ここでは、回路で使っている入力であるSW[0]をつくっておきます。 続いて、虫眼鏡ボタンのZoom Toolで、200nsぐらいまでの範囲が 表示されるようにズームアウトしておきます。

続いて、この信号の値を変化させます。 ↑このWaveform Editing Toolsを選びます。

↑その状態で、100ns〜200nsの範囲をドラッグすると、その範囲の 値が反転されて"1"になります。 これで、0〜100nsは"0"、100〜200nsは"1"となる信号SW[0]が 作られました。 これをsample.vwfとして保存しておきます。

続いて、シミュレーションを実行します。

回路をつくって最初にシミュレーションを行うときだけ、↑Processing→"Start Complilation&Simulation"を実行しておきます。(Ctrl+Shift+Kで実行できる)

同じ回路に対して与える波形を変更しただけならば、↑このボタンでシミュレーションできます。(回路を変更したら↑↑を行う)

↑このように、与えた入力に対する出力LED[0]の波形が得られます。 今回の回路では、LED[0]はSW[0]のNOTをとったものでしたので、 たしかに正しく動作していることが確認できます。 (SW[0]の変化から少し遅れてLED[0]が変化しているのは、 回路の遅延が再現されているものです。 実際の回路でもこの程度の遅延は生じます)

回路の書き込みと動作

最後に、検証が済んで正しく動作する回路のFPGA設定情報をFPGAに書き込みます。 まずはFPGAボードとPCをUSBケーブルで接続します。 その後、書き込みプログラムBBCを、メニューバーの アイコンから起動します。 その後、「Device」欄で、"EDA-003/EDX-005"を選んでおきます。

続いて、「File Open」コマンドを選び、FPGA設定情報(拡張子が *.rbfのファイル; sample.rbfなど)を選びます。 そして「Download」ボタンを押すと、書き込みが行われます。

書き込みが終了すると、FPGAは、直ちに動作を開始します。 すなわち、入力した回路図どおりの動作をするはずです。 スイッチSW[0], SW[1]を押し、LED[0], LED[1]の点灯の様子を確認しましょう。

このようにFPGAを用いた論理回路設計は、次の手順で行うことになります。

  1. 回路の入力
  2. コンパイル(インプリメンテーション)
  3. プログラム(書き込み)
したがって、回路の動作が予期したものと異なる場合や、 回路の仕様を変更したい場合でも、回路図を書き換えてこの手順を ふむだけで、すぐに新しい論理回路を動作させることができるわけです。

FPGAによる組合せ論理回路の設計と動作

演習1-1 FPGAボード上のスイッチ(SW[0]〜SW[3])とLED(LED[0]〜LED[7])を用いて、 適当な組み合わせ論理回路を設計し、シミュレーションで動作を検証し、 また実機でその動作を確認してみましょう。 なおFPGAボード上のスイッチは、押すと0、離していると1、 またLEDは1で点灯、0で消灯となります。

※スイッチ・LEDは、0〜3を、0から順に使う数の分だけ番号をつけないと、 うまくいかないようです(仕様)。 つまり、例えばSW[0], SW[2]の2個を使い、SW[1]を使わない、という 回路は避け、2個のスイッチを使うのであればSW[0], SW[1]を使うのが 無難です。

HDL用いた論理回路設計

以上では、自由に論理回路を設計し、動作させることができる FPGAを用いて、いくつかの論理回路の設計を行いました。 ただ、前回行ったような回路図を用いた論理回路の設計は、 どうしても煩雑になり、特に回路の規模が大きくなると、 間違いを起こしやすくなります。 また「論理回路の構造」をそのまま回路図として描いているため、 「作りたい機能」を見通しにくくなってしまいます。

ここでは、これらの問題を解決するものとして近年急速に普及してきた、 言語(Hardware Description Language; HDL)による論理回路設計を 行ってみます。 この実験では、特にVerilogHDLというHDL言語を用いることにします。

※注意
ここから先は、各回路の完成までのデバッグの過程をすべて記録をとること。 特にソースファイル(*.v)は、修正を加える前に以下の手順で、現時点の ファイルのコピーをつくり、そのファイルに対して修正を加えていくこと。

  1. いま編集しているソースファイル(*.v)を、File→Save as...で、番号をつけて保存(例:sample.vをsample2.vとして保存)
  2. QuartusIIのプロジェクトのファイルの指定から、古いほうのファイル(この例ではsample.v)を右クリック→"Remove File from Project"で削除する(以下の手順)。
  3. 現在表示されている新しいファイル(この例ではsample2.v)に修正を加え、コンパイル(論理合成)などを行う。

組合せ論理回路の構造記述

HDLを用いた論理回路の設計方法には、何通りかがあります。 まずはもっとも直感的な、「構造記述」というものを 用いてみることにしましょう。 おそらくまずは実例を見たほうがわかりやすいと思いますので、 例をあげます。
// sample module
module sample(SW, LED);
  input  [1:0] SW;
  output [1:0] LED;

  assign LED[0] = SW[0];
  assign LED[1] = SW[1];
endmodule
この例では、"sample"という名前の回路(module)を定義しています。 最初のmoduleの後に回路の名称を書き、そのあとの括弧内に、 その回路の入力と出力の名称を記述します。 ここでは、FPGAボード上にある、前回も用いたLED[0]〜LED[1]と SW[0]〜SW[1]を用いることを記述しています。

続いて、回路の記述に入りますが、まずは入力と出力の名称と、 それらが入力と出力のいずれかなのか、を記述しています。 この例では、SW[0]〜SW[1]を入力(input)、LED[0]〜LED[1]を出力(output)と 記述しています。

その後がいよいよ回路自体の記述になります。 この場合は、assgin(割り当て)文という記述方法を用いて、 出力であるLED[0]に、入力であるSW[0]を割り当て、すなわち 接続しています。 同様にLED[1]にはSW[1]を接続しています。

最後は、endmodule文で、回路の記述が終わることを示します。 ちなみに1行目のように、//から始まる行はコメントになります。

このassign文では、ただ接続する以外に、論理和・論理積などの 論理演算をふくめた、論理式を記述することができます。 論理演算は、それぞれ次の記号を用います。
否定(NOT)~
論理積(AND)&
論理和(OR)|
排他的論理和(XOR)^
例えば、SW[0]とSW[1]の値の論理和をとってLED[0]に接続したい場合は、 次のように記述をすることになります。

  assign LED[0] = SW[0] | SW[1];
このように、VerilogHDLでは、ほとんど論理式をそのまま書くだけで 回路として記述が完了することになります。 さらに、論理式の簡略化は、自動的に行ってくれますので、 カルノー図などとにらめっこをする必要は、ほとんどありません。

なお回路の中には、inputやoutputで定義される入出力以外にも、 たとえば次のように、回路の途中の「ノード(信号)」が あることもあります。

このような、途中のノード(信号)は、wire文で宣言をすることができます。 たとえばこの回路中の、赤矢印の信号線に hoge という名前をつけて、 この回路全体を記述すると、次のようになります、

module sample(SW, LED);
  input  [3:0] SW;
  output [3:0] LED;
  wire         hoge;

  assign hoge = SW[0] & SW[1];
  assign LED[0] = hoge & SW[2];
endmodule
assign文が2つあり、1つ目で左側のANDゲート、 2つ目で右側のANDゲートを記述しています。 なおこのassign文は、C言語などのプログラムにおける「代入」のように 見えますが、実際には、この書いてある順序で「値の代入」が起こるのではなく、 あくまでも「ANDゲートという回路がある」ことを記述しているのです。 つまり、1つ目のassign文で、まずはhogeの値が確定し、 それを使って2つ目のassign文でLED[0]の値を求める、というわけでは ありませんので、この2つのassign文の順序を入れ替えても、 まったく動作は変わりません。

これは、このように考えると理解しやすいかと思います。

HDLを用いた論理回路設計・実装

まずサンプルのQuartusIIプロジェクトファイル一式(sample_hdl.zip)を ここから取得して、作業用フォルダを 作成して、そこに展開します。 そしてsample.qpfをQuartusIIからプロジェクトして開きます。 ここまでは、前回の回路図入力の場合と同一ですが、 ここでプロジェクトを構成するファイルの中のsampleをダブルクリックすると、 sample.vが開きます。 つまりこのプロジェクトでは、設計対象の論理回路が VerilogHDLで記述されているわけです。 そこで、必要に応じて、sample.vの中身を書き換え、 その後は回路図入力の場合と同様に、コンパイル→書き込み、と進みます。

※注意
先ほどの注意の通り、デバッグの過程でのファイルの修正は、すべて記録をとること。 特にソースファイル(*.v)は、修正を加える前に以下の手順で、現時点の ファイルのコピーをつくり、そのファイルに対して修正を加えていくこと。 (module名の「sample」は変更してはいけない)

  1. いま編集しているソースファイル(*.v)を、File→Save as...で、番号をつけて保存(例:sample.vをsample2.vとして保存)
  2. QuartusIIのプロジェクトのファイルの指定から、古いほうのファイル(この例ではsample.v)を右クリック→"Remove File from Project"で削除する(以下の手順)。
  3. 現在表示されている新しいファイル(この例ではsample2.v)に修正を加え、コンパイル(論理合成)などを行う。

演習1-2 適当な機能を持つ組み合わせ論理回路をVerilogHDLで記述し、 その動作を確認してみましょう。 またその際に出るWariningを確認し、問題があるものがないかを確認しましょう。

例えば以下のようなWarningが出ているかと思います。

Warning (10034): Output port "LED[7]" at sample.v(4) has no driver
↑これは、出力LED[7]につながっている信号線(driver)がないよ?という意味です。
Warning: Output pins are stuck at VCC or GND
	Warning: Pin "LED[4]" stuck at GND
↑これは、出力LED[4]がGND("0")から変化しない(stuck)よ?という意味です。
Warning: Design contains 1 input pin(s) that do not drive logic
	Warning: No output dependent on input pin "CLK6"
↑これは、入力CLK6がどこにもつながってないよ?という意味です。
Warning: Following 16 pins have nothing, GND, or VCC driving datain port -- changes to this connectivity may change fitting results
	Info: Pin LED[4] has GND driving its datain port
↑これは、LED[4]がGNDに繋がっちゃってる(=常に"0")よ?という意味です。 これらの内容が、設計どおりであれば、無視しても構いませんが、 設計どおりではない場合は、回路設計のミスが原因の場合が多いので、 回路の設計を見直すべきです。

組合せ論理回路の動作記述

上で見てきたVerilogHDLでの論理回路の記述では、 例えば7セグメントデコーダを設計しようとすると、 いちいち論理式で記述する必要があり、あまり効率的とはいえません。 そこで、VerilogHDLには、もう少し抽象度の高いレベルで、 論理回路の機能を記述することができます。

例として、2ビットの入力(d[0], d[1])に応じて、4本の出力(q[0]〜q[3]) のうちの1つのみが1となるような「2 to 4デコーダ」をみてみましょう。 この回路の真理値表は次のようになります。
d[1]d[0]q[0]q[1]q[2]q[3]
001000
010100
100010
110001

module sample(SW, LED);
  input  [1:0] SW;
  output [3:0] LED;

  wire   [1:0] d;
  reg    [3:0] q;

  assign d = {SW[1], SW[0]};
  assign LED = q;

  always @(d) begin
    case (d)
      2'b00 : q <= 4'b0001;
      2'b01 : q <= 4'b0010;
      2'b10 : q <= 4'b0100;
      2'b11 : q <= 4'b1000;
    endcase
  end
endmodule
一気に複雑度が増しましたが、順を追ってみていきましょう。 最初のmodule文、input文、output文は、先ほどと同じです。

続くwire文では、回路の中で用いる「ノード」(信号)として"d"を 宣言しています。 しかも"wire [1:0]"と記述することで、実は「1番」から「0番」の 2本をまとめて、配列のように取り扱うことができます。 この例では、実際にはd[0]とd[1]を宣言していることになります。

続くreg文では、wire文と同様に、回路の中で用いるノードとして、 4ビット幅のqを宣言していますが、wire文と異なり、 その値が、後で出てくるalways文の中で変更(定義)することができる、 という性質を持ちます。 このwireとregの使い分けを説明するのはなかなか難しいので、 実例をいろいろ見るのが有効でしょう。 とりあえずは

というものだと思っておいてください。

続いて、wire文で宣言した2ビット幅のノードdに、SW[1]とSW[0]を 接続しています。 この例では、中括弧{}を用いていますが、これにより、 2つ以上の信号線をまとめて取り扱うことができます。 もちろん両辺の幅が同じ場合は、次のように記述してもかまいません。 具体的には、この記述は、実際には次のものと等価になります。

  assign d = SW;
続くassign文でも、出力であるLED[0]〜LED[3]に、ノードq[0]〜q[3]を 接続しています。

続いて、この回路の記述の中心部である、always文になります。 これは、英文のように記述を読むと意味がわかりやすいでしょう。 "always @(=at) d"、つまり、「dでいつも」という意味になりますが、 これは「dが変化するときはいつも」と読み替えます。

その後のbegin〜endではさまれた部分で、 そのdが変化するときに、実際に行う動作を記述しています。 ここでは、case文を用いて、dの値に応じて、場合分けをしています。 具体的には、dが2'b00(「2桁の2進数(binary)の00」 の意味)のときには、qに4'b0001(「4桁の2進数の0001」)を 代入する、と定義しています。 (4'b0001は、最下位のみが1で、この1はq[3]ではなく、q[0]に代入されることに 注意しましょう。つまり最上位がq[3]、最下位がq[0]です。逆ではありません。) 同様に、dが2'b01、2'b10、2'b11の場合も、qに代入されるべき 値を定義しています。 最後に、end文でbegin文を閉じ、さらにendcase文でcase文を閉じています。

これにより、2桁の2進数であるdの値に応じて、 q[0]〜q[3]のいずれかのみが1となる回路、すなわち デコーダ(2 to 4 decoder)ができることになります。 なおこのHDLで記述される論理回路は次のようになることになります。

演習1-3 このデコーダ回路のHDL記述を参考に、7セグメントデコーダseg7decを設計し、 入力としてSW[0]〜SW[3]を4ビットの値として、それに応じた 数字を7セグメントLEDに表示する回路を実装して実際に表示させてみましょう。 ただし7セグメントLEDの1つの数字の各LEDはSG[7:0]に、以下のように つながっていますので、このSGを出力するように設計します。 また詳細は省略しますが、出力SAには4'b0001を与えておくようにしてください。 また10〜15(16進数で0xa〜0xf)は、表示しないようにしても 「A」〜「F」の値を表示するようにしても構いません。

回路どうしの接続

VerilogHDLでは、回路をmoduleとして記述しますが、 moduleどうし、つまり回路どうしを接続することもできます。 次の例をみてみましょう。
module sample(SW, LED);
  input  [1:0] SW;
  output [1:0] LED;

  inv i0(SW[0], LED[0]);
  inv i1(SW[1], LED[1]);
endmodule

module inv(a, x);
  input a;
  output x;

  assign x = ~a;
endmodule
この例では、後半で記述している"inv"という名前のモジュール(実体は インバータ)を、前半のモジュールsample内で用いています。 まず最初に、i0という名称でモジュールinvの機能を持つ回路を作り、 その入力(この場合は第1引数)と出力(この場合は第2引数)に、 それぞれSW[0]とLED[0]を接続しています。 同様に、もう1つのインバータi1を作って、その入力にSW[1]を、出力にLED[1]を 接続しています。

この回路をコンパイルすると、最上位モジュール(他から 呼び出されていないモジュール)が、全体回路として扱われることになります。 結果として、次のような回路が作られることになります。

なおここでは、回路を「呼び出す」という表現を使いましたが、 実際には、C言語などの関数を呼び出して値の代入を実行する、 というわけではなく、この回路図のように、 「インバータが2つ作られる」(「インバータが2つあることを記述している」) ことに注意しましょう。

このような回路の呼び出しで接続されるノードは、必ずwire型を 用います。 たとえば先ほどの2つのANDゲートからなる回路(途中ノードはhoge)は 次のように書くこともできます。

module sample(SW, LED);
  input  [3:0] SW;
  output [3:0] LED;
  wire         hoge;

  and_2 i0(SW[0], SW[1], hoge);
  and_2 i1(hoge, SW[2], LED[0]);
endmodule

module and_2(a, b, x);
  input  a, b;
  output x;

  assign x = a & b;
endmodule
なおこのような回路の呼び出しでも、実際には「ANDゲートが2つ作られる」 (「ANDゲートが2つある回路を記述している」)わけですから、 2つのANDゲートを呼び出す記述の順序を逆にしても、 まったく同じ回路が作られます。

また間違えがちな点として、HDLはあくまでも「回路を作る」記述ですので、 先ほどのalways文の中で、次のように使うこともできません。

  always @(d) begin
    case (d)
      2'b00 : begin q <= 4'b0000; and_2 i0(d[0], d[1], hoge); end
      ...
この例では、case文の中でand_2を「呼び出して」いますが、 このように条件に応じて回路を作ったり作らなかったり、ということは できません(それを実現する方法がない)。 この点が、関数呼び出しとHDLの大きな違いですので、 くれぐれも注意するべきです。 つまり、「回路(この例ではi0という名前のand_2)を作る」 ことを書くのは1回だけしかできず、それはalways文の外で書くべきもので、 always文などの中で「ある条件のときだけ回路作る」ことはできません。

順序回路

順序回路も、VerilogHDLで記述することができます。 これも、まずは例を見ていくことにしましょう。 これは4桁の2進カウンタをVerilogHDLで記述した例です。
module sample(SW, LED);
  input  [3:0] SW;
  output [3:0] LED;
  reg    [3:0] q;
  wire clk, rst;

  assign LED = q;
  assign clk = ~SW[0], rst = ~SW[1];

  always @(posedge clk or posedge rst) begin
    if (rst == 1'b1) begin
      q <= 4'd0;
    end
    else begin
      q <= q + 4'd1;
    end
  end
endmodule
この例では、always文の括弧内に、posedgeと書いてあります。 これは、信号の立ち上がり(positive edge)を示すもので、 "posedge clk"とかくと、「信号clkの立ち上がり」という意味になります。 この例では、2つの条件を"or"で並べていますので、 「clkの立ち上がり、あるいはrstの立ち上がり」のときに、 always文内の記述の動作が行われることになります。 (ちなみにclkはnot SW[0]ですので、SW[0]を押す←→clk=1、というですので、 このposedge clkは、「SW[0]を押したとき」という意味になります。 rstも同様。)

その動作は、if文で2つの場合に分かれていて、 「rstが1のとき」(つまりSW[1]を押したとき)は、qに4ビットの0を代入しています。 (4'dは、4ビットの10進数表記(decimal)の意味。 VerilogHDLでは、このように値のビット数を明示的に書かない場合は、 まわりから自動的に判断してくれますが、 ミスの原因となりがちなので、明示的にビット数を指定した方がよいです)

この代入は、「<=」という演算子を用いていますが、 これは、さきほどのcase文のときと同様に「always文の中で使う代入」と 理解しておいてください。

それ以外(else)、すなわち、もう1つの条件である 「clkが1のとき」(つまりSW[0]を押したとき)は、 qに1を加えたものを、次のqに代入しています。 すなわちclkの立ち上がり、すなわちSW[1]を押すたびに、 qの値は1ずつふえていく、つまりカウンタとして動作をすることになります。

演習1-4 上記の4ビットのカウンタの記述を理解し、実際の動作を確認してみましょう。

※なおclkのSW[0]を押すたびに、値が不規則に変わるように見えるかもしれません。 これは、スイッチを1回押したつもりでも機械接点のバウンドで 複数回のON/OFFを繰り返すチャタリングと呼ばれる現象が原因です。 余裕があれば、チャタリング防止回路を別回路としてmoduleで追加し、 スイッチとclkの間につないでみましょう。 チャタリング防止回路としては、RSフリップフロップを用いる方法が 一般的です(ただしスイッチが2個必要)。

10進カウンタ

このalways文をうまく使うと、任意の数字までカウントするカウンタを 容易に作ることができます。 例えば次の回路を見てみましょう。
module sample(SW, LED);
  input  [3:0] SW;
  output [3:0] LED;
  wire         co, clk, rst;
  assign clk = ~SW[0], rst = ~SW[1];

  counter10 i0(clk, rst, LED, co);
endmodule

module counter10(ck, rst, q, co);
  input ck, rst;
  output [3:0] q;  // counter output
  output       co; // carry out
  reg    [3:0] q;
  reg          co;

  always @(posedge ck or posedge rst) begin
    if (rst == 1'b1) q <= 4'd0;
    else begin
      if (q == 9) begin
        q <= 4'd0;
        co <= 1'b1;
      end
      else begin
        q <= q + 4'd1;
        co <= 0;
      end
    end
  end
endmodule
後半でcounter10という名称の回路を記述し、それを前半の全体回路である sampleで呼び出して使っています。

このcounter10では、rst=1のときにはリセットをかけ、 それ以外のとき(クロック信号ckの立ち上がりごと)に if文を使って、qの値に応じて、次にqをどのような値にするかを 定義しています。 具体的には、q(4ビットの数)が9であればqの値を0に更新しますが、 それ以外のときは、次にはqの値をq+1に更新するように 記述しています。 これにより、qの値は、ckの立ち上がりごとに、
0→1→2→・・・→8→9→0→1→・・・
というように変わっていくことになり、10進カウンタとして 動作することになります。 演習1-5 この10進カウンタの出力を、1桁分の7セグメントLEDに数字として 表示させてみましょう。

さきほどの演習1-3で作成した7セグメントデコーダを seg7decという別の回路(module)として追加し、 トップ階層のsampleから、カウンタcounter10とともに インスタンス呼び出しして接続する、という構成にすると 見通しのよい記述となってよいでしょう。

演習1-6 既存のVerilogHDLファイルをプロジェクトに追加して ライブラリのように利用する方法を理解しましょう。 まず peripheral.vをダウンロードし、 新しく展開したsample_hdlフォルダ(の名前を変更したフォルダ)に 移動しておきます。

その後、QuartusIIのFile→Openでファイルを開く際に、 上図のように"Add file to current project"をチェックした上で 開くと、そのファイルがプロジェクトに追加されて ライブラリのように利用することができます(下図:赤丸内のFilesタブを 選ぶとプロジェクト内のファイル一覧が見られる)。

このperipheral.vでは、次の2つの回路が記述されています。

module seg7(CLK6, d3, d2, d1, d0, SG, SA);
module sw_clk(CLK6, a, q);
seg7は4桁の7セグメントLEDに入力であるd0〜d3の値を表示する回路です。 (4桁の表示をダイナミック駆動と呼ばれる手法で制御している。 最下位(右端)がd0、最上位(左端)がd3) またsw_clkは、入力aの立ち上がりごとに一定幅(150ms)のパルスを qに出力することでチャタリングのないクロック信号を生成する回路です。 いずれも、FPGAボード上にある6MHzのクロック信号CLK6を用います。

この2つの回路を使って、適当な値を表示する回路を設計し 動作させてみましょう。 まずは各要素回路の接続関係を図で描いて整理してから 設計をはじめるとよいでしょう。

例えばd0には10進カウンタの値(SW[0]でカウント)を与え、 d1とd2には常時"1"と"2"の値を与え、 d3にはSW[2]を押すと"0"、離すと"3"を与え、 これらを表示する、など。


戻る