第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)の手引き書を参考にしてください。
以下では、異なる点のみを記載しておきます。
- File→New Projectで、新しいプロジェクトの作成を開始します。
プロジェクト名は"count8sw"としておきましょう。
- VHDLファイルは、新規には作成せず、以下のファイルを
ダウンロードし、"Add Copy Of Sources"でプロジェクトに追加しておく。
今回作る回路の全成は3つのコンポーネント(部品)からなり、
3つのVHDLファイルはそれぞれの回路が記述されています。
それぞれのコンポーネントの役割は以下の通りです。
- count8sw_top : 全体のとりまとめ。
実際のスイッチ・LEDがつながっている。
- count8 : 8ビットカウンタの本体。
- sw_clk : スイッチ2個を使って、カウンタで使うクロック信号を
つくっている。
スイッチには1回押しただけのつもりでも何回かのON/OFFを繰り返してしまう
「チャタリング」という現象があるため、スイッチを直接カウンタの
クロック信号として使うと、1回押しただけのつもりでも何回かのクロックの
立ち上がりが入力されてしまい、一気にカウント値が増えてしまう。
そこでsw_clkではRS-FFを使ってチャタリングを除去している。
具体的にはスイッチ0(SW(0))とスイッチ1(SW(1)))を交互に押すと、
一発ずつクロック信号が
出るようになっている。(興味のある人は動作原理を調べてみましょう)
これで生成したクロックは、全体構成count8_topでは"clk_cnt"という名前の
信号にしている。
- あとは全体構成のcount8sw_topを選んだ状態でコンパイルをかけて、
実機(サブボードSub1をとりつけておくこと)に転送すれば動作するはずです。
カウンタの値は8個のLEDに2進数で表記されていて(点灯="1")、
SW(0)とSW(1)を交互に押すと、値が1ずつ増えていくはずです。
SW(7)を押すとリセットがかかります。
全体構成の接続関係・信号名を自分で描いてみてよく理解した上で、
実際に動作をさせてみましょう。
シミュレーション
順序回路でもシミュレーションを行うことができます。
今回はシミュレーションを行うのは必須としませんが、
余裕のある人はぜひ試してみてください。
(本来は、シミュレーションで動作検証をしてデバッグをして、
その後で実機に転送して動作させる、という順序とするべきです)
順序回路は、基本的にクロック信号に同期して動作するため、
常時変化するクロック信号を与えることになりますが、
シミュレーションで使うテストベンチに、
0→1→0→1→・・・という変化をならべて書くのは非常に面倒なので、
次のように独立したprocess文をテストベンチ内に書いておくと、
自動的に周期的に変化するクロック信号が生成されるので便利です。
process begin
wait for 50 ns;
clk <= not clk;
end process;
この例では、50nsごとにclkの値を反転されているため、
結果として100ns周期のクロック信号が生成されます。
分周回路
続いて、カウンタの応用として、周波数を変えたクロック信号をつくる
分周回路をつくって、好きなな速さでカウントが進む回路をつくってみましょう。
プロジェクトの作成
基本的な操作は、実習(1)を参考にしてください。
以下では、異なる点のみを記載しておきます。
- File→New Projectで、新しいプロジェクトの作成を開始します。
プロジェクト名は"count8div"としておきましょう。
- VHDLファイルは、新規には作成せず、以下のファイルを
ダウンロードし、"Add Copy Of Sources"でプロジェクトに追加しておく。
今回作る回路の全成は3つのコンポーネント(部品)からなり、
3つのVHDLファイルはそれぞれの回路が記述されています。
それぞれのコンポーネントの役割は以下の通りです。
- count8div_top : 全体のとりまとめ。
実際のスイッチ・LEDがつながっている。
- count8 : 8ビットカウンタの本体。これは上記のファイルと同じ。
- div : 実習で使っているFPGAボードには、50MHzのクロック信号が
ありますので、これを使います。
ただしこの50MHzのクロック信号を直接カウンタに与えると、
カウンタの出力の変化が速すぎてでみてわからない
(1秒間に50M=5000万回カウントアップ)ので、
もっと周波数の低いクロック信号をつくる(「分周」と呼ぶ)ことにします。
まずは1秒に1ずつカウントアップするように、
1Hzのクロック信号をつくってみます。
このように低い周波数のクロック信号を作る回路を分周回路と呼びますが、
これの実体は、実はカウンタそのものです。
div.vhdの中身をみてよく理解しましょう。
与えるクロック信号clk_i (1kHz)ごとに分周用のカウンタをカウントアップし、
49,999,999(=50,000,000 - 1)の次には0に戻るようにしています。
そしてこのときだけ出力(clk_o)を1にしています。
結果としてclk_oは、clk_iの50,000,000回に1回だけ1、他は0となるため、
周期が50,000,000倍(=周波数が1/50,000,000)の1Hzのクロック信号となります。
これを生成したクロックは、全体構成count8div_topでは"clk_cnt"という名前の
信号にしています。
- あとは全体構成のcount8div_topを選んだ状態でコンパイルをかけて、
実機に転送すれば動作するはずです。
LEDの表示(カウンタの値の2進数表記)が1秒に1ずつ増えていくはずです。
またSW(7)を押すとリセットがかかります。
全体構成の接続関係・信号名を自分で描いてみてよく理解した上で、
実際に動作をさせてみましょう。
分周回路: 分周比の変更
分周回路の動作原理からわかるように、元のクロック信号を分周して
生成されるクロック信号の周波数は、分周回路の実体である
カウンタのカウント動作の上限値、で決まります。
そのため、この上限値を変えれば、好きな周波数のクロック信号を
生成することができます。
そこで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の
分周比を変えれば、押したスイッチに応じて鳴る音の高さを変えることができます。
(つまり簡単なオルガン的なものになります)
例えば以下のように回路の改造をしてみましょう。
- div.vhd : 入力信号に、分周比cnt_div(26ビット)を追加する。
(以下は追加後のdivエンティティ記述。もちろんこの分周比divを
分周回路の中身にも反映させるように改造)
entity div is
Port ( clk_i : in STD_LOGIC;
rst : in STD_LOGIC;
cnt_div : in STD_LOGIC_VECTOR(25 downto 0);
clk_o : out STD_LOGIC);
end div;
- count8div_top : 押したスイッチSW(0)〜SW(7)に応じて、
divに与える分周比cnt_divを変える。
(以下はSWに応じて分周比cnt_divを変えるような記述の一部。
第2回資料中の「エンコーダ」の記述例が参考になる)
architecture Behavioral of count8div_top is
....
signal cnt_div : std_logic_vector(25 downto 0);
...
begin
process (SW) begin
if (SW(0) = '1') then cnt_div <= "???????"; -- SW(0)を押したときの分周比(26bit)
elsif (SW(1) = '1') then cnt_div <= "??????"; -- SW(1)を押したときの分周比(26bit)
...
7セグメントLEDでの表示
続いて、カウンタの値を7セグメントLEDに表示する機能を
追加してみましょう。
以下のファイルを使って新規にプロジェクトcount8div7segを作成してみてください。
ただし7セグメントLEDが載っているSub3サブボードをメインボードにさしておきます。
なお4桁の7セグメントLEDの駆動は、一筋縄ではいかず、ダイナミック駆動と呼ばれるテクニックが必要です。
今回は詳細は理解しなくてもよいですが、興味のある人はこちらなどの情報とseg7.vhdの記述を見比べてみてください。
全体構成のcount8div7seg_top.vhdの、さきほどのcount8div_top.vhdからの変更点は以下の3つです。
- エンティティ記述:以下のように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));
- コンポーネント宣言: 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にそのままつないでおく。
- インスタンス呼び出し: 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進数表記」ですので、そのつもりで動作を確認してみてください。
戻る