2004/11/14 【ソフトウエア編TOPに戻る】
今回はカウンタ回路についてご紹介しましょう。カウンタとはマイコンの世界では良く耳にする言葉ですね。H8などでもITUなどのタイマーカウンタによって時間の計測やパルス波形の生成など、色々と便利な機能が提供されています。カウンタ回路はそれらの最も基本となる部分で、動的な機能を実現させるためには欠かせない技術になっています。
カウンタとは「カウントするもの」「数を数えるもの」「計数器」といった意味になりますが、ロジック回路で数を数える仕組みを理解することが大切です。仕組みを理解すれば色々と応用が広がることでしょう。カウンタとはマイコンが勝手に数えてくれるものだと思っている方、ちょっとここで立ち寄って見て下さい。
ここではカウント動作の簡単な仕組みと、VHDLでの記述方法、そして簡単な応用例をご紹介することにします。CPLDにおいてもカウンタ機能を駆使することでその用途が一気に広がります。ENC/DEC回路とは一味違った動きを楽しむことができます。(^^
415−1.D−FFによるカウンタ回路
カウンタ回路には色々な種類のものがありますが、最も基本的なものとしてはDフリップフロップを下図のように多段に接続した回路があります。

ここではD−FFを3段直列に接続しており、3ビット分のカウンタを構成しています。「3ビットカウンタ」ということは、数にして0から7までを数えられるという意味になります。もちろん、2進数で数えます。
D−FFとは、CLKクロック入力パルスの立ち上がりタイミングで入力Dの信号がQ端子に出力されて保持されます。Qバー出力は常にQ出力と逆の反転出力です。D-FF0の接続を見るとQバー出力が入力Dに戻されています。この回路でCLK端子に連続したパルス信号が入力されるとD-FF0は以下のような動作になります。
1.Qバー出力が1の時 → CLK入力の立ち上がりでQは1、Qバーは0になる。
2.Qバー出力が0の時 → CLK入力の立ち上がりでQは0、Qバーは1になる。
つまり、CLKパルスの立ち上がりタイミングごとに、出力信号が1と0の反転動作を繰り返します。これはD-FF1とD-FF2についても同じですが、D-FF1やD-FF2にとってのCLKパルスは、前段のフリップフロップのQバー出力になっているところがミソです。さて、この回路の動作波形は下図の通りになります。

OUT_D0波形はD-FF0のQ出力波形です。従ってこれはCLK信号の各立ち上がりタイミングごとに出力が反転していますね。次にOUT_D1波形ですが、これはD-FF1のQ出力波形です。D-FF1はD-FF0のQバー出力をCLKとしているため、タイミングとしてはOUT_D0波形の立下りタイミングごとに出力が反転しています。分かりますか? OUT_D2についても同様に、OUT_D1波形の立下りタイミングごとに出力が反転しています。つまり、CLKパルスの入力によって、OUT_DO〜D2の各出力波形を生成しているということになりますね。
さて、それがいったい何だと言うのだ?と言われそうですが、上のパルス波形には意味があります。上の出力波形を再度下記に示しましょう。

それぞれ縦の赤線のタイミングで、OUT_D0からD2の出力信号の状態を並べてみたのが青文字で示した3ビットの情報です。どこかで見たことがあるような0と1の並びですが、これはまぎれも無く2進数による0〜7の値を示しています。つまりこれらの出力波形は、CLK信号の立ち上がりの数を数えていたということが言えます。つまりカウンタ動作ですね。
あともうひとつ。OUT_D1のパルス波形の周期は、OUT_D0の倍の周期になっています。更にOUT_D2の周期はOUT_D1の倍です。つまりD-FF出力が1段後ろになるほど、パルスの周期は倍になっていきます。これを分周(ぶんしゅう)と言い、やはりロジック回路を構成する上では重要な動作のひとつです。
■まとめ
1.カウンタ回路ではCLK入力の立ち上がりタイミングごとにカウントアップ動作を行う。
2.後段のビット出力ほど周期が倍倍で長くなる分周動作を行う。
ここでは3ビットのカウンタ回路をご紹介しましたが、直列に接続するD-FFの数を増やせば、その分もっと大きなカウンタ回路を簡単に構築することができます。ただし、ここでご紹介したカウンタ回路は実用には使われません。なぜかというと、各D−FFごとに持つ信号遅延が後段に行くほど蓄積され、出力信号の変化タイミングにバラツキが出るためです。カウンタの各出力ビット同士をANDするような処理に使うと、タイミングのズレによって余計なヒゲ(極小のパルス信号)が発生してしまい、誤動作の元になってしまいます。実際には外部のクロック信号に完全に同期した動作を行うカウンタが利用されますが、ここではカウンタの基本を理解して頂くために上記のような簡単な回路(リップルカウンタ回路と言います。)をご紹介しました。また、カウントダウンしてくれるカウンタなどもあり、色々な種類のカウンタ回路が存在しています。興味を持たれた方は是非専門書を紐解いて見て下さい。
415−2.VHDLによるカウンタ回路の記述
では、VHDLによるカウンタ回路動作の記述についてご紹介しましょう。今回のVHDLでは8ビット分のD-FFを準備しておき、外部クロックCLKに同期させてカウントアップ動作を行わせています。8ビット分のD-FFの出力はCPLDから出力信号として取り出し、外部で確認できるようにしています。
ここをクリックするとダウンロードできます。CNT1.VHD
| ------------------------------------------------- -- CNT1 2004.11.12 -- RIKIYA ------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity CNT1 is port( CLK : in std_logic; OUT_D : out std_logic_vector(7 downto 0)); end CNT1; ------------------------------------------------- architecture RTL of CNT1 is signal DFF_8 : std_logic_vector(7 downto 0); begin OUT_D <= DFF_8; process(CLK) begin if (CLK'event and CLK = '1') then DFF_8 <= DFF_8 + '1'; end if; end process; end RTL; |
■ それでは、ポイントだけご説明しましょう。
entity CNT1 is
port( CLK : in std_logic;
OUT_D : out std_logic_vector(7
downto 0));
end CNT1;
エンティティ部分では、CLK入力とOUT_Dとして0〜7の8ビット分の出力端子を定義しています。
signal DFF_8 : std_logic_vector(7 downto 0);
アーキテクチャ分では、内部信号としてDFF_8という8ビット分の信号を定義しています。ここではこれを8ビットカウンタ用のD-FFとして使います。
OUT_D <= DFF_8;
これは内部の8ビットカウンタの値をCPLDの出力端子から出力させるための接続処理です。
process(CLK)
begin
if (CLK'event and CLK = '1') then
DFF_8 <= DFF_8
+ '1';
end if;
end process;
process文については今までもご紹介してきましたが、CLK信号に変化があったときに、以下のbegin 〜 end process; 間の処理を実行するのでした。そして、if (CLK'event and CLK = '1') then では、CLK信号の立ち上がりタイミングを検出するための処理でした。つまりここでは、CLK信号の立ち上がりタイミングにDFF_8 <= DFF_8 + '1' が実行されることになります。ところで、DFF_8 <= DFF_8 + '1' はインクリメント(カウントアップ)処理ですね。DFF_8を8ビットの変数(レジスタ)とみなし、その値を1づつ増やしていくということを簡単明快に表記している構文です。VHDLではこれだけでカウンタ動作を実現させることができます。
ただし、VHDLの先頭部分にご注目下さい。いつもは記載しない下記のライブラリを読み込んでいます。
use ieee.std_logic_unsigned.all;
DFF_8 <= DFF_8 + '1' のようなインクリメント構文をコンパイルするために必要になるので忘れないようにしてください。
■使用されるマクロセル数
さて、上記のVHDLをコンパイルして、Fitterレポートを見てみましょう。下記のような記述の報告が出されます。
+---------------------------------------------------------------+
; Fitter Summary
;
+-----------------------+---------------------------------------+
; Fitter Status ; Successful - Sat Nov 13 00:46:20
2004 ;
; Revision Name ; CNT1
;
; Top-level Entity Name ; CNT1
;
; Family ; MAX7000S
;
; Device ; EPM7032SLC44-5
;
; Total macrocells ; 8 / 32 ( 25 % )
;
; Total pins ; 13 / 36 ( 36 %
)
;
+-----------------------+---------------------------------------+
今回のコンパイルではEPM7032というマクロセルが32個あるデバイスをターゲットとしました。マクロセルとはCPLD内部に保有するD-FFのことです。レポートの下から2行目にTotal macrocells の項目があり、8 / 32 との記載があります。これは32個のうち8個を使ったということですが、今回の設計通りの消費数になっていますね。もっと沢山のD-FFを使うのであれば、もっと沢山のマクロセルを持つデバイスにしなければなりません。
415−3.クロック分周を使ったカウンタ回路の記述
さて、先のCNT1.VHDをまじめに実機で試してみた方は首を捻ったかも知れません。特にCPLDのカウンタ出力をLEDに繋いで動作状態を見ようとした方は、「あれ?8個のLEDが全部点灯するだけじゃん?」などと思ったかも知れません。無理もありません。通常CPLDに供給しているクロックパルスは最低でも1MHz程度にするのが一般的です。TekuRoboで製作した実験回路でも1MHzのクロックとしています。このクロック周期で動く8ビットカウンタの動作を目で見ようとしても無理な話で、全部のLEDが点灯しているだけにしか見えません。オシロスコープなどがある方は動作の確認ができますが、それではつまらないですね。そこで、今度は十分に目でカウントアップ動作が確認できるカウンタ回路をご紹介しましょう。
原理は簡単です。カウンタ動作を行うためのクロックパルスの周期を遅くします。といっても実験基板に載せている発信器を遅いものに載せ換える訳ではありません。ここでは、1MHzのクロックパルスを分周して遅くなったパルスでカウント動作を行わせます。先ほどCPLDのマクロセルには24個のあまりがりました。そこで、16ビットのカウンタを新たに準備して16段の分周を行います。1MHzを16段で分周すると、約15.3Hzまで遅くすることができます。これなら十分に目で動きが見えるはずですね。
ここをクリックするとダウンロードできます。CNT2.VHD
| ------------------------------------------------- -- CNT2 2004.11.12 -- RIKIYA ------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity CNT2 is port( CLK : in std_logic; OUT_D : out std_logic_vector(7 downto 0)); end CNT2; ------------------------------------------------- architecture RTL of CNT2 is signal DFF_8 : std_logic_vector(7 downto 0); signal DFF_16 : std_logic_vector(15 downto 0); begin --------------------------------------OUTPUT----- OUT_D <= DFF_8; --------------------------------------BUNSYUU---- process(CLK) begin if (CLK'event and CLK = '1') then DFF_16 <= DFF_16 + '1'; end if; end process; --------------------------------------COUNT----- process(DFF_16(15)) begin if (DFF_16(15)'event and DFF_16(15) = '1') then DFF_8 <= DFF_8 + '1'; end if; end process; end RTL; |
■では、ポイントをご説明します。
entity CNT2 is
port( CLK : in std_logic;
OUT_D : out std_logic_vector(7
downto 0));
end CNT2;
エンティティ文は前項と同じですね。エンティティ名が変わっているだけです。
もちろん先頭での、use ieee.std_logic_unsigned.all; のライブラリ読み込みも忘れずに。
signal DFF_8 : std_logic_vector(7 downto 0);
signal DFF_16 : std_logic_vector(15 downto 0);
アーキテクチャ文では、内部処理信号としてDFF_8とDFF_16を宣言しています。DFF_8はカウント動作用の8ビットカウンタで、DFF_16はクロックを分周するための16ビットカウンタとして使います。
OUT_D <= DFF_8;
内部処理用の8ビットカウンタ出力を、CPLDの出力端子に接続する処理です。これも前項と変わりません。
process(CLK)
begin
if (CLK'event and CLK = '1') then
DFF_16 <= DFF_16
+ '1';
end if;
end process;
ここでは、CPLDに入力されているクロックによって、内部のDFF_16をカウントアップ動作させています。前項のDFF_8のカウントアップ動作そのままですね。
process(DFF_16(15))
begin
if (DFF_16(15)'event and DFF_16(15)
= '1') then
DFF_8 <= DFF_8
+ '1';
end if;
end process;
さて、ここが今回のDFF_8をカウントアップさせる部分です。注目はprocess文のトリガがDFF_16(15)になっている点です。つまり、DFF_8のカウントアップ用のクロックとして、分周用カウンタDFF_16の15ビット目の出力パルスを用いている点です。if (DFF_16(15)'event and DFF_16(15) = '1') then の部分でも15段目の分周パルスの立ち上がりに同期させて処理しています。 これで目に見える速度でカウントアップ動作を行ってくれるようになります。
■使用されるマクロセル数
さて今回も、上記のVHDLをコンパイルして、Fitterレポートを見てみましょう。下記のような記述の報告が出されています。
+---------------------------------------------------------------+
; Fitter Summary
;
+-----------------------+---------------------------------------+
; Fitter Status ; Successful - Sat Nov 13 00:58:16
2004 ;
; Revision Name ; CNT2
;
; Top-level Entity Name ; CNT2
;
; Family ; MAX7000S
;
; Device ; EPM7032SLC44-5
;
; Total macrocells ; 24 / 32 ( 75 % )
;
; Total pins ; 13 / 36 ( 36 %
)
;
+-----------------------+---------------------------------------+
今回は8ビットと16ビットのカウンタを使ったので、24個のマクロセルが使われていますね。
415−4.カウンタを使ったパターン波形の出力
それでは次に、カウンタを利用したパターン波形の出力事例を見てみましょう。今回もCPLDの出力を8ビットのLEDに接続することを前提としてみます。パターン波形といっても別に難しいものではなく、8個のLEDを使ってイルミネーションのパターンを作ってみようというものです。ここでは8個のLEDを左右に行ったり来たりするように点灯させてみましょう。ちなみに、そういう点灯のさせ方を専門用語でナイトライダーと呼びます(^^; 。(公の場で言うと笑われるかもしれませんが...)
今回のVHDLでは次のような考え方で動作させます。先ず16ビットカウンタで外部クロックを分周し、その遅いクロックで4ビットカウンタを動かします。4ビットカウンタでは0〜7の数字を繰り返し数え続けますが、例えば「カウント値が0の時は8ビットの出力を0000 0001にしろ」などという命令を0から7までについて設定します。すると、設定したパターン通りの出力信号が連続して得られるという訳です。わかりますか?
ここをクリックするとダウンロードできます。PATT1.VHD
| ------------------------------------------------- -- PATT1 2004.11.14 -- RIKIYA ------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity PATT1 is port( CLK : in std_logic; OUT_D : out std_logic_vector(7 downto 0)); end PATT1; ------------------------------------------------- architecture RTL of PATT1 is signal DFF_16 : std_logic_vector(15 downto 0); signal DFF_4 : std_logic_vector(3 downto 0); begin -----------------------------------BUNSYUU------- process(CLK) begin if (CLK'event and CLK = '1') then DFF_16 <= DFF_16 + '1'; end if; end process; -----------------------------------COUNT--------- process(DFF_16(15)) begin if (DFF_16(15)'event and DFF_16(15) = '1') then DFF_4 <= DFF_4 + '1'; case DFF_4 is when "0000" => OUT_D <= "00000001"; when "0001" => OUT_D <= "00000010"; when "0010" => OUT_D <= "00000100"; when "0011" => OUT_D <= "00001000"; when "0100" => OUT_D <= "00010000"; when "0101" => OUT_D <= "00100000"; when "0110" => OUT_D <= "01000000"; when "0111" => OUT_D <= "10000000"; when "1000" => OUT_D <= "10000000"; when "1001" => OUT_D <= "01000000"; when "1010" => OUT_D <= "00100000"; when "1011" => OUT_D <= "00010000"; when "1100" => OUT_D <= "00001000"; when "1101" => OUT_D <= "00000100"; when "1110" => OUT_D <= "00000010"; when "1111" => OUT_D <= "00000001"; end case; end if; end process; end RTL; |
■さて、ポイントだけを解説しましょう。
プログラムのほとんどが今まで出てきた内容と一緒ですね。ここではプログラムの心臓部である、この部分だけを見てみましょうか。
if (DFF_16(15)'event and DFF_16(15)
= '1') then
DFF_4 <= DFF_4
+ '1';
case DFF_4 is
when
"0000" => OUT_D <= "00000001";
when
"0001" => OUT_D <= "00000010";
when
"0010" => OUT_D <= "00000100";
when
"0011" => OUT_D <= "00001000";
when
"0100" => OUT_D <= "00010000";
when
"0101" => OUT_D <= "00100000";
when
"0110" => OUT_D <= "01000000";
when
"0111" => OUT_D <= "10000000";
when
"1000" => OUT_D <= "10000000";
when
"1001" => OUT_D <= "01000000";
when
"1010" => OUT_D <= "00100000";
when
"1011" => OUT_D <= "00010000";
when
"1100" => OUT_D <= "00001000";
when
"1101" => OUT_D <= "00000100";
when
"1110" => OUT_D <= "00000010";
when
"1111" => OUT_D <= "00000001";
end case;
end if;
といっても特に解説の必要もありませんね。DFF_4の4ビットカウンタをカウントアップすると同時に、その時のDFF_4の値によって、OUT_Dの出力ビットパターンを設定しているだけです。OUT_Dに設定されるビットパターンが、カウントが進むごとに右から左へ、そして左から右へ変化するパターンになっているのがわかりますか? ここのビットパターンを色々と工夫することによって、色々なイルミネーションに変化します。
今回はカウンタを使った動的な回路の一端をご紹介しました。外部クロックを使ってカウンタを動かすことにより、エンコーダやデコーダなどの受動的な回路とは違った、自ら積極的に動く回路ができるのがお分かり頂けたでしょうか。まさに「心臓の鼓動を得て自ら動く力を持った」という感じでしょうか。この他にも「カウント値がいくつからいくつまでの間だけ出力ビットを1にする」といった考え方で、多チャンネルのPWM波形などを出力させることも可能ですし、工夫次第で用途が広がります。しかし、ここらへんからVHDLによるプログラミングの難しさに直面してくるところでもあります。 今後も少しずつ動的なプログラミングのご紹介などを進めて行きたいと思いますので、お楽しみに。(^^