FPGAを用いた論理回路設計(その1: 回路図による設計)

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のアイコン

回路図の入力

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

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

ではこれにもう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が多く表示されますが、その多くは無視してかまいません。 なお当然ですが、回路図自体の間違い(論理機能の間違い)は、 この変換作業ではエラーとなりませんので、設計者が十分注意をして 設計をする必要があります。

回路の書き込みと動作

続いて、変換が済んだ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ボード上のスイッチは、押すと1、離していると0、 またLEDは1で点灯、0で消灯となります。

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

FPGAによる順序回路の設計と動作

演習1-2

FPGAボード上のスイッチ(SW[0]〜SW[3])とLED(LED[0]〜LED[7])を用いて、 適当な順序回路を設計し、その動作を確認してみましょう。 例えば4桁のカウンタなどを設計するとよいでしょう。 (順序回路で用いるフリップフロップにはいくつかの種類があります。 また同じ名称でも、流儀によって微妙に表記方法と機能が異なる こともあります。 こちら を参考にするとよいでしょう) ちなみに、ある信号を常に"1"や"0"に設定したいときは、 primitives → others の中の、vcc("1")やgnd("0")を 使うことができます。

なおカウンタの入力(いわゆるクロック信号)に、スイッチを用いる場合、 スイッチの「チャタリング」という現象に留意する必要があります。 これは、スイッチを1回押したつもりでも、機械接点の動作上、 複数回のON/OFFが続いてしまう現象です。 このチャタリング現象の解消のためには、S-Rフリップフロップを用いた 回路を利用するのが効果的です。 余裕がある場合は、ぜひ試してみましょう。


FPGAを用いた論理回路設計(その2: HDLによる設計)

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

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

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

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行目のように、//から始まる行はコメントになります。

VerilogHDLの演算子

この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文の順序を入れ替えても、 まったく動作は変わりません。

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

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

演習2-1

適当な機能を持つ組み合わせ論理回路をVerilogHDLで記述し、 その動作を確認してみましょう。

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

上で見てきた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)ができることになります。

演習2-2

適当なデコーダ回路を、VerilogHDLを用いて記述し、その動作を 確認してみましょう。 例えば7セグメントデコーダを設計し、入力としてSW[0]〜SW[3]を 用いるとよいでしょう。

回路どうしの接続

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ゲートを呼び出す記述の順序を逆にしても、 まったく同じ回路が作られます。 またこれは、あくまでも「回路を作る」記述ですので、 先ほどのalways文の中で、次のように使うこともできません。
  always @(d) begin
    case (d)
      2'b00 : begin q <= 4'b0000; and_2 i0(d[0], d[1], hoge); end
      ...
つまり、あくまでも「回路(この例では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;

  assign LED = q;

  always @(negedge SW[0] or negedge SW[1]) begin
    if (SW[0] == 1'b0) begin
      q <= 4'b0000;
    end
    else begin
      q <= q + 1;
    end
  end
endmodule
この例では、always文の括弧内に、negedgeという書いてあります。 これは、信号の立ち下がり(negative edge)を示すもので、 "negedge SW0"とかくと、「信号SW0の立ち下がり」という意味になります。 この例では、2つの条件を"or"で並べていますので、 「SW[0]の立ち下がり、あるいはSW[1]の立ち下がり」のときに、 always文内の記述の動作が行われることになります。

その動作は、if文で2つの場合に分かれていて、 「SW[0]が0のとき」(つまりSW[0]の立ち下がり、のとき)は、 qに4桁の0を代入しています。 この代入は、「<=」という演算子を用いていますが、 これは、とりあえずは「フリップフロップが入る回路のとき」に 使うと理解しておいてください。 つまりSW[0]の立ち下がり、すなわちSW[0]を押したときに、 qが0にリセットされます。

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

演習2-3

4ビットのカウンタをVerilogHDLで記述し、動作を確認してみましょう。

また余裕があれば、前回と同様に、チャタリング防止回路を追加してみましょう。 これは、チャタリング防止回路を別のmoduleとして記述し、 最上位モジュール(sample)から呼び出す、という形式をとると 見やすい記述となるでしょう。

10進カウンタ

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

  counter10 i0(SW[0], LED, co);
endmodule

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

  always @(posedge ck) begin
    if (q == 9) begin
      q <= 0;
      co <= 1;
    end
    else begin
      q <= q + 1;
      co <= 0;
    end
  end
endmodule
後半でcounter10という名称の回路を記述し、それを前半の全体回路である sampleで呼び出して使っています。 ここでcoという、LEDなどには接続されていない信号線がありますが、 これはあとで使いますので、とりあえずいまは無視しておきましょう。

このcounter10では、クロック信号ckの立ち上がりごとに if文を使って、qの値に応じて、次にqをどのような値にするかを 定義しています。 具体的には、q(4ビットの数)が9であれば、qの値を0に更新しますが、 それ以外のときは、次にはqの値をq+1に更新するように 記述しています。 これにより、qの値は、ckの立ち上がりごとに、
0→1→2→・・・→8→9→0→1→・・・
というように変わっていくことになります。

演習2-4

この10進カウンタの出力を、1桁分の7セグメントLEDに数字として 表示させてみましょう。

このためには、4ビットの2進数を7セグメントの点灯パターンに 変換する、7セグメントデコーダ回路seg7_decを作る必要があります。 この回路をmoduleとして記述しておき、全体回路のモジュールである sampleから、counter10とともに呼び出す形にすると、 見通しのよい回路の記述になるでしょう。

カウンタの応用: 分周回路

カウンタは、ただ数を数える回路ですが、他にも使い道があります。 例えば、ある周波数のクロック信号から、それよりも低い周波数の クロック信号を作りたいとしましょう。

例えば先ほどの10進カウンタの桁上がり出力coは、 クロックckの10周期ごとに1クロック分だけ1となります。 つまり、coの周波数は、ckの周波数の1/10、ということになります。 このように、クロック信号の周波数を、カウンタ等を用いて 低くすることを分周と呼びます。

演習2-5

FPGAボード上のクロック信号は、CLK6とCLK30の2つがあり、 それぞれ6MHzと30MHzです。 この周波数のCLK6またはCLK30を用い、LED[0]が1秒間に1回点滅するような 回路を作ってみましょう。 (ヒント: CLK6等を何分の一に分周すればよいかを考え、 そのために必要なビット数のカウンタを使う)

演習2-6

4桁の7セグメントLEDを、ダイナミック駆動で点灯させる回路を 記述してみましょう。

まずは全体の構成を次のように整理しておきましょう。


7セグメントLEDは、上図のように接続されているので、 同時に別々の数字を表示することができません。 そこで、適当な周期で、SA[0]〜SA[3]を順に"0"として 1桁ずつ点灯させることにし、そのタイミングで、その桁に表示するべき 表示素子の点灯パターンをSG[0]〜SG[7]に与えることで、 すべての桁に所望の数字(などのパターン)を表示する方法をとります。 この方法では、ある瞬間に点灯しているのは1つの桁のみであるわけですが、 この切り替えを高速に行うことで、人間の目には、すべての桁が 点灯しているように見せることができます。 このような点灯方式を「ダイナミック駆動」と呼びます。

まず7セグメントLEDの各桁をダイナミック駆動で順次点灯させるために、 4進カウンタcounter4を用いることにします。
この出力qs[1:0]は、(1)表示するべき7セグメントLEDの桁の選択SA[3:0]、 (2)その7セグメントLEDに表示するべき値として、該当する桁のカウンタの 出力を選択するselect4、の2つに使われます。
7セグメントデコーダdecode_7segは、select4によって 選ばれたカウンタの値(q0〜q3のいずれか)に応じて、 7セグメントLEDの点灯パターンを作り、数字を表示します。
全体のカウンタは4桁の10進カウンタですから、 10進カウンタcounter10を4個、呼び出して接続します。 なお、各桁の接続方法には、 カウンタ用クロックがすべての桁のカウンタに入る「同期式」(この図)と、 各桁のカウンタのクロックを、前の桁の桁上げ信号coから与える 「非同期式」があります。 今回は、構成がシンプルな「非同期式」で十分ですが、 前の桁のcoから、次の桁のクロックをどのように作成すればよいか、 十分考えましょう。
なおダイナミック駆動用クロックはCLK6またはCLK30を 適当に分周して作成することとし、 その周波数は1kHz程度がよいでしょう。 (あまり周波数が低いとチラツキが目立ちます) またカウンタ用クロックは、同じくCLK6またはCLK30を適当に分周して作成するか、 あるいはスイッチを用いるとよいでしょう。


戻る