第5/6回: 実習(2/3): FPGAへの論理回路の実装(カウンタ・分周回路)

今回は、VHDLを用いて記述した順序回路の例として、 カウンタ回路を動作させてみましょう。

8ビットカウンタ

スイッチでカウントを進められる8ビットカウンタをつくってみます。 カウントしている数値は、サブボードSub1(↓)の8個のLEDに 8bitの二進数として表示させます。 (そのため、メインボードにサブボードSub1をさしておいてください)

※このサブボードSub1のスイッチの番号が、ボード上では「右端がSW(7)〜左端がSW(0)」と なっていますが、すぐ上のLEDと同じく「右端がSW(0)〜左端がSW(7)」が正しいです。

プロジェクトの作成

以前の実習(1)と同様に、 Xilinx ISE Project Navigatorを使ってVHDL記述の論理回路の 設計と論理合成(コンパイル)を行います。 基本的な操作は、実習(1)の手引き書を参考にしてください。 以下では、異なる点のみを記載しておきます。 全体構成の接続関係・信号名を自分で描いてみてよく理解した上で、 実際に動作をさせてみましょう。

シミュレーション

順序回路でもシミュレーションを行うことができます。 今回はシミュレーションを行うのは必須としませんが、 余裕のある人はぜひ試してみてください。 (本来は、シミュレーションで動作検証をしてデバッグをして、 その後で実機に転送して動作させる、という順序とするべきです)

順序回路は、基本的にクロック信号に同期して動作するため、 常時変化するクロック信号を与えることになりますが、 シミュレーションで使うテストベンチに、 0→1→0→1→・・・という変化をならべて書くのは非常に面倒なので、 次のように独立したprocess文をテストベンチ内に書いておくと、 自動的に周期的に変化するクロック信号が生成されるので便利です。

  process begin
    wait for 50 ns;
    clk <= not clk;
  end process;
この例では、50nsごとにclkの値を反転されているため、 結果として100ns周期のクロック信号が生成されます。

分周回路

続いて、カウンタの応用として、周波数を変えたクロック信号をつくる 分周回路をつくって、好きなな速さでカウントが進む回路をつくってみましょう。

プロジェクトの作成

基本的な操作は、実習(1)を参考にしてください。 以下では、異なる点のみを記載しておきます。 全体構成の接続関係・信号名を自分で描いてみてよく理解した上で、 実際に動作をさせてみましょう。

分周回路: 分周比の変更

分周回路の動作原理からわかるように、元のクロック信号を分周して 生成されるクロック信号の周波数は、分周回路の実体である カウンタのカウント動作の上限値、で決まります。 そのため、この上限値を変えれば、好きな周波数のクロック信号を 生成することができます。

そこでdiv.vhdを変更し、別の速さ(例えば10Hzや0.5Hz)でカウント動作を させてみましょう。

ただしそのとき、分周用のカウンタのビット数と取り得る値の範囲には気をつける 必要があります。 div.vhdでは分周用カウンタcntは26ビットとして宣言しています。 26ビットの2進数で扱える値は226≒64Mまでです。 (※210=1024≒1000=1k、という目安を覚えておくと、 2のべき乗の数の目安をつけやすいです。 この例では、226=26×210×210 ≒64×1k×1k=64M、と求められます)

div.vhdではcntで取り得る値は最大で49,999,999(≒50M)ですので、 26ビットで扱える範囲におさまっていますが、もっと分周比を大きくしたい (生成するクロック信号の周波数を低くしたい)ときには、 宣言するcntのビット数をそれにあわせて増やす必要があります。

このように分周回路の速度(分周比)を容易に変えられるのも、 HDLでの論理回路設計のメリットと言えます。

分周回路の応用: ブザーをならす

サブボードSub1には、圧電ブザーが載っています。 この圧電ブザーにある周波数の信号を与えると、 その周波数の音を発します。 (例えば440Hzの信号を与えると440Hzの音(=ラの音)が鳴る) なおこの圧電ブザーは、出力端子BZにつながっていますので、 カウンタの出力の最下位ビットq_cnt(0)を出力BZに以下のように接続するだけで使えます。
  BZ <= q_cnt(0);

そこで所望の周波数の音が鳴るような回路を作って動作させてみましょう。 ただしq_cnt(0)はカウンタの最下位ビットですので、分周回路でつくって カウンタに与えているクロックの周波数の半分の周波数になることに 注意しましょう。

あるいは、分周回路divの記述を以下のようにして、 分周クロックclk_oを、50000000の半分ごとに0/1を切り替える、という 構成として、これをBZに直接つないでもOKです。

    elsif (clk_i'event and clk_i = '1') then
      if (cnt = 50000000 - 1) then
        cnt <= "00000000000000000000000000"; clk_o <= '1';
      else
        cnt <= cnt + 1;
        if (cnt = 25000000 - 1) then
          clk_o <= '0';
        end if;
      end if;
    end if;

(※1日目目安はここまで。余裕があれば先へ進んでおくと2日目が楽になります)


分周回路の応用:スイッチに応じて音を変える

分周回路の分周比は、分周回路divのカウントの上限値によって決まりますので、 この値を変えれば、分周で得られるクロック信号の周波数も変わります。 したがって、count8div_topで、押したスイッチに応じて分周回路cnt_divの 分周比を変えれば、押したスイッチに応じて鳴る音の高さを変えることができます。 (つまり簡単なオルガン的なものになります)

例えば以下のように回路の改造をしてみましょう。

7セグメントLEDでの表示

続いて、カウンタの値を7セグメントLEDに表示する機能を 追加してみましょう。 以下のファイルを使って新規にプロジェクトcount8div7segを作成してみてください。 ただし7セグメントLEDが載っているSub3サブボードをメインボードにさしておきます。 なお4桁の7セグメントLEDの駆動は、一筋縄ではいかず、ダイナミック駆動と呼ばれるテクニックが必要です。 今回は詳細は理解しなくてもよいですが、興味のある人はこちらなどの情報とseg7.vhdの記述を見比べてみてください。

全体構成のcount8div7seg_top.vhdの、さきほどのcount8div_top.vhdからの変更点は以下の3つです。

  1. エンティティ記述:以下のように7セグメントLED関係の信号SA, SGを追加
        Port ( SW : in  STD_LOGIC_VECTOR (7 downto 0);
               CLK50M : in  STD_LOGIC;
               SA : out STD_LOGIC_VECTOR(3 downto 0);
               SG : out STD_LOGIC_VECTOR(7 downto 0));
    
  2. コンポーネント宣言: 7セグメントLED駆動回路seg7の宣言を追加。d0〜d3が、それぞれ1の位(右端)〜1000の位(左端)に表示するべき値(4ビット2進数)。ちなみに0〜9はその数字が、10〜15(16進数でA〜F)は、アルファベットA〜Fが表示される。
        component seg7 is
        Port ( clk : in  STD_LOGIC;
               rst : in  STD_LOGIC;
               d0 : in  STD_LOGIC_VECTOR (3 downto 0);
               d1 : in  STD_LOGIC_VECTOR (3 downto 0);
               d2 : in  STD_LOGIC_VECTOR (3 downto 0);
               d3 : in  STD_LOGIC_VECTOR (3 downto 0);
               sa : out  STD_LOGIC_VECTOR (3 downto 0);
               sg : out  STD_LOGIC_VECTOR (7 downto 0));
        end component;
    
    なおこのseg7のclkには、メインの50MHzクロック信号であるCLK50Mをそのまま与えておき、出力のsaとsgは、count8div7segの出力SAとSGにそのままつないでおく。
  3. インスタンス呼び出し: 7セグメント駆動回路seg7を呼び出し。1の位にはカウンタの出力qの下位4ビット、10の位にはqの上位4ビット、100の位と1000の位には"0"を接続しておく。(カウンタの出力は8bitだが7セグメントLEDは16進数で4桁の16bit分あるため) ※このインスタンス呼び出しでは、port mapで、呼び出される回路のentity記述内の信号名を指定せず、entity記述で宣言されている端子の順番のみを使っている。
      iseg7: seg7 port map(CLK50M, rst_cnt, q_cnt(3 downto 0), q_cnt(7 downto 4), "0000", "0000", SA, SG);
    
なおカウンタの表示は「16進数表記」ですので、そのつもりで動作を確認してみてください。
戻る