VHDLカウンタの完璧マスター!10のサンプルコードで理解

VHDLでカウンタを作成するための10のサンプルコードVHDL
この記事は約26分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラム(回路記述)の基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

VHDLのカウンタ設計は、デジタルロジック設計の基礎として非常に重要です。

特に初心者にとって、この技術の習得はその後の設計活動の助けとなるでしょう。

本記事では、VHDLカウンタの基本から応用、カスタマイズの方法までを、10の実用的なサンプルコードとともに解説します。

●VHDLカウンタの基礎

○VHDLとは

VHDLは、VHSIC Hardware Description Languageの略であり、複雑なデジタルシステムを記述するための言語です。

FPGAやASICの設計に広く利用されており、明確な構文と豊富なライブラリが特徴です。

○カウンタの仕組み

カウンタは、デジタルロジックの中でも基本的なコンポーネントの一つで、入力されたクロック信号の立ち上がりや立ち下がりを検出して、内部の値を増減させる仕組みを持っています。

●VHDLカウンタの作り方

○基本のカウンタ

□サンプルコード1:基本的なアップカウンタ

このコードでは、クロック信号の立ち上がりを検出してカウンタの値を1ずつ増やすアップカウンタを紹介しています。

この例では、4ビットのカウンタを実装しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;

entity up_counter is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(3 downto 0));
end up_counter;

architecture Behavioral of up_counter is
    signal tmp_count : STD_LOGIC_VECTOR(3 downto 0) := "0000";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            tmp_count <= "0000";
        elsif rising_edge(clk) then
            tmp_count <= tmp_count + 1;
        end if;
    end process;
    count <= tmp_count;
end Behavioral;

このコードを実行すると、クロック信号の立ち上がりごとにcountの値が1ずつ増えていくことが確認できるでしょう。

リセットがアクティブになった場合、カウンタの値は”0000″に戻ります。

□サンプルコード2:ダウンカウンタ

このコードでは、クロック信号の立ち上がりを検出してカウンタの値を1ずつ減らすダウンカウンタを紹介しています。

この例では、4ビットのカウンタを実装しています。

-- (中略: ライブラリやエンティティの宣言はサンプルコード1と同様)

architecture Behavioral of down_counter is
    signal tmp_count : STD_LOGIC_VECTOR(3 downto 0) := "1111";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            tmp_count <= "1111";
        elsif rising_edge(clk) then
            tmp_count <= tmp_count - 1;
        end if;
    end process;
    count <= tmp_count;
end Behavioral;

このコードを実行すると、クロック信号の立ち上がりごとにcountの値が1ずつ減っていくことが確認できます。

リセットがアクティブになった場合、カウンタの値は”1111″に戻ります。

●応用例とサンプルコード

VHDLを使用してカウンタを設計する際の応用例や多様なサンプルコードを解説します。

基本的なカウンタの動作を理解した上で、様々な条件や環境に対応するためのカウンタを作成する方法を学んでいきましょう。

○拡張したカウンタの利用

通常のアップカウンタやダウンカウンタの他にも、特定の条件で動作を変更するなど、拡張機能を持ったカウンタも設計可能です。

そのような応用的なカウンタのサンプルコードをいくつか紹介します。

□サンプルコード3:一定値でリセットするカウンタ

このコードでは、カウンタが一定の値に達した時にリセットする機能を持つカウンタを紹介しています。

この例では、カウンタが10に達した場合に0に戻る動きをします。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity reset_counter is
    Port ( clk : in STD_LOGIC;
           rst : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(3 downto 0));
end reset_counter;

architecture Behavioral of reset_counter is
    signal tmp_count : STD_LOGIC_VECTOR(3 downto 0) := "0000";
begin
    process(clk, rst)
    begin
        if rst = '1' then
            tmp_count <= "0000"; -- リセット
        elsif rising_edge(clk) then
            if tmp_count = "1010" then -- 10に達した場合
                tmp_count <= "0000"; -- 0に戻す
            else
                tmp_count <= tmp_count + 1;
            end if;
        end if;
    end process;
    count <= tmp_count;
end Behavioral;

このコードの動きは、クロックの立ち上がりエッジ毎にカウンタがインクリメントされるものです。

そして、カウンタが10(“1010”)に達すると、次のクロックの立ち上がりエッジでカウンタが0にリセットされます。

□サンプルコード4:エッジ検出を利用したカウンタ

このコードでは、入力信号の立ち上がりエッジを検出してカウンタをインクリメントする機能を持つカウンタを紹介しています。

この例では、外部の入力信号input_signalの立ち上がりエッジを検出し、カウンタを加算しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity edge_detect_counter is
    Port ( clk : in STD_LOGIC;
           input_signal : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(3 downto 0));
end edge_detect_counter;

architecture Behavioral of edge_detect_counter is
    signal tmp_count : STD_LOGIC_VECTOR(3 downto 0) := "0000";
    signal prev_input_signal : STD_LOGIC := '0';
begin
    process(clk)
    begin
        if rising_edge(clk) then
            if input_signal = '1' and prev_input_signal = '0' then
                tmp_count <= tmp_count + 1;
            end if;
            prev_input_signal <= input_signal;
        end if;
    end process;
    count <= tmp_count;
end Behavioral;

このコードを使用すると、外部の入力信号input_signalの立ち上がりエッジ毎にカウンタがインクリメントされる動作を確認できます。

□サンプルコード5:外部入力を持つカウンタ

VHDLを使用して、外部入力を持つカウンタを設計することは、多くのプロジェクトで非常に有用です。

外部からの信号を受け取ってカウンタを制御することで、さまざまな応用シナリオに適応できます。

このコードでは外部からの入力を使ってカウンタの動作を制御する方法を紹介しています。

具体的には、外部入力がHIGHのときにカウンタが動作し、LOWのときには停止する仕組みを取り入れています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity ext_input_counter is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           ext_input : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(3 downto 0));
end ext_input_counter;

architecture Behavioral of ext_input_counter is
    signal tmp_count : STD_LOGIC_VECTOR(3 downto 0) := "0000";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            tmp_count <= "0000";
        elsif rising_edge(clk) then
            if ext_input = '1' then
                tmp_count <= tmp_count + 1;
            end if;
        end if;
    end process;

    count <= tmp_count;
end Behavioral;

上記のコードでは、ext_inputという名前の外部入力ポートを持っています。

この入力が’1’のとき、カウンタはカウントアップします。

一方、この入力が’0’の場合、カウンタは動作を停止し、現在の値を保持します。

リセット信号resetがアクティブになると、カウンタは0にリセットされます。

この設計を使用すれば、外部の条件に基づいてカウンタを動作させることができます。

例えば、センサーからの入力がある場合や、特定の操作が行われたときなど、実際の状況に合わせてカウンタを制御することができます。

実際にFPGAやASICの実装を行う場合、この外部入力が’1’の時にカウンタが増加するのを確認することができます。

反対に、’0’の時はカウンタが増加しないことも確認できるでしょう。

□サンプルコード6:多ビットカウンタ

多ビットカウンタとは、一般的なカウンタよりも多くのビット数を持つカウンタのことを指します。

このタイプのカウンタは、より高い数値をカウントする場合や、特定のビット位置での操作を行いたい場合などに使用されます。

このコードでは、8ビットのアップカウンタをVHDLを使って設計しています。

この例では、0から255までの数値をカウントアップしています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity multi_bit_counter is
    Port ( clk : in STD_LOGIC;
           rst : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(7 downto 0));
end multi_bit_counter;

architecture Behavior of multi_bit_counter is
    signal tmp_count : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
begin
    process(clk, rst)
    begin
        -- リセットがアクティブの場合
        if rst = '1' then
            tmp_count <= "00000000";
        -- クロックの立ち上がりエッジでカウントアップ
        elsif rising_edge(clk) then
            tmp_count <= tmp_count + 1;
        end if;
    end process;

    count <= tmp_count;
end Behavior;

このコードにおいては、tmp_countという8ビットの内部信号を利用して、カウントアップを行っています。

また、外部にはcountというポートを持ち、このポートを通じて現在のカウント値を出力しています。

また、rstポートを使用して、カウンタのリセットが行えます。

リセットがアクティブになると、カウンタは0に戻ります。

このような多ビットカウンタは、特定の条件でカウンタをリセットしたり、特定のビット位置で操作を行いたい場合などに有効です。

例えば、特定のカウント値に到達した時に何らかの動作を起こす、といった応用が考えられます。

このコードをFPGAやCPLDに実装し、実際に動作させると、clkポートにクロックを供給するたびにcountの値が1ずつ増えていくことを確認できます。

そして、rstをアクティブにすることで、カウンタの値が0にリセットされることも確認できるでしょう。

□サンプルコード7:モジュラーカウンタ

モジュラーカウンタは、特定の数値に到達したらカウントをリセットする特性を持ちます。

これは、例えば、10進数のカウンタで「9」に到達した後は「0」に戻る動きを再現するのに適しています。

VHDLでモジュラーカウンタを実装する方法を見てみましょう。

このコードでは、モジュラーカウンタの実装方法を紹介しています。

この例では、クロックの立ち上がりエッジごとにカウントを増加させ、指定したモジュール値に到達したらカウントをリセットしています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity modular_counter is
    Port ( clk : in  STD_LOGIC;
           rst : in  STD_LOGIC;
           count : out STD_LOGIC_VECTOR(3 downto 0));
end modular_counter;

architecture Behavior of modular_counter is
    signal tmp_count : STD_LOGIC_VECTOR(3 downto 0) := "0000"; -- 初期値0の4ビットカウンタ
    constant MOD_VALUE : STD_LOGIC_VECTOR(3 downto 0) := "1001"; -- カウント値が9になったらリセット
begin
process(clk, rst)
begin
    if rst = '1' then  -- リセットがアクティブなら
        tmp_count <= "0000";  -- カウントを0にする
    elsif rising_edge(clk) then  -- クロックの立ち上がりエッジで
        if tmp_count = MOD_VALUE then  -- カウント値が9 (1001) なら
            tmp_count <= "0000";  -- カウントを0にリセット
        else
            tmp_count <= tmp_count + 1;  -- それ以外はカウントアップ
        end if;
    end if;
end process;
count <= tmp_count;  -- 出力にカウント値をアサイン
end Behavior;

このモジュラーカウンタは、カウントが9 (二進数で1001) になると0にリセットされます。

そのため、出力のcountは0から9までの10進数として動作します。例えば、カウントが8の場合、次のクロックエッジで9となり、さらに次のエッジで0にリセットされます。

次に、このモジュラーカウンタが実際にどのように動作するか見てみましょう。

例として、連続する10のクロックエッジを考えます。最初はカウントが0です。

1つ目のエッジで1に、2つ目のエッジで2に、というように増加し、9つ目のエッジで9になります。

そして、10つ目のエッジでカウントは0にリセットされます。

この動作は、モジュラーカウンタが期待通りに動作していることを示しています。

このモジュラーカウンタは、時間を示すカウンタや一定の間隔でリセットが必要な場面など、様々なアプリケーションで役立ちます。

必要に応じてモジュール値を変更することで、さまざまなカウントアップの範囲を持つカウンタを簡単に実装することができます。

□サンプルコード8:ランダムカウンタ

ランダムカウンタは、通常のカウンタとは異なり、ランダムな値に増減します。

このタイプのカウンタは、乱数生成やテストシナリオなど、特定の用途に適しています。

このコードでは、ランダムな値を生成するための機能とカウンタ機能を組み合わせています。

この例では、乱数ジェネレータを使用してランダムな値を生成し、その値をカウンタに加算しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity random_counter is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(7 downto 0));
end random_counter;

architecture Behavioral of random_counter is
    signal counter : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
    signal random_value : STD_LOGIC_VECTOR(7 downto 0);
begin
    process(clk, reset)
    begin
        -- ランダムな値の生成
        random_value <= STD_LOGIC_VECTOR(unsigned(counter) mod 256);
        if reset = '1' then
            counter <= "00000000";
        elsif rising_edge(clk) then
            counter <= STD_LOGIC_VECTOR(unsigned(counter) + unsigned(random_value));
        end if;
    end process;

    count <= counter;
end Behavioral;

このサンプルコードでは、8ビットのランダムカウンタを実装しています。

リセットがアクティブになると、カウンタは0にリセットされます。

それ以外の場合、カウンタは毎クロックサイクルでランダムな値が追加されます。

このランダムカウンタを実際にFPGAボードで動作させると、LEDやディスプレイ上でランダムに増減する数字を観察することができます。

クロックの毎サイクルでカウンタの値が不規則に変動する様子は、ランダム性の確認やテストの際に非常に役立ちます。

しかし、ランダムカウンタは予測不能な値になることが期待されますが、VHDLのこの実装は真のランダム性を持つわけではありません。

乱数の品質やランダム性の要件に応じて、より高度な乱数生成アルゴリズムを使用することが推奨されます。

また、ランダムカウンタの応用例としては、ハードウェアのテストやデバッグ、暗号化のための乱数生成、ゲームの乱数生成などが考えられます。

このランダムカウンタをベースにして、特定の範囲や条件のランダムな数値を生成するカスタマイズも可能です。

たとえば、1から100の間のランダムな値だけを生成するようなカウンタや、特定の条件下でのみランダムな値を生成するカウンタなど、さまざまなカスタマイズが考えられます。

□サンプルコード9:デバウンス付きカウンタ

スイッチのような機械的な入力デバイスを使用する場合、入力信号にノイズが混ざることがあります。

このノイズを「デバウンス」と呼び、これにより予期しないカウンタのインクリメントやデクリメントが発生する可能性があります。

VHDLを使用して、このデバウンスを処理するカウンタを作成する方法を見てみましょう。

このコードでは、デバウンスロジックを使って、外部からのノイジーな入力をクリーンに保つ方法を示しています。

この例では、スイッチ入力が一定時間安定していることを確認し、その後でカウンタをインクリメントまたはデクリメントします。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity debounce_counter is
    Port ( clk : in STD_LOGIC;
           switch_input : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(7 downto 0));
end debounce_counter;

architecture Behavioral of debounce_counter is
    signal internal_count : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
    signal stable_switch : STD_LOGIC := '0';
    signal previous_switch : STD_LOGIC := '0';
    signal debounce_timer : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
begin
    process(clk)
    begin
        if rising_edge(clk) then
            if switch_input = previous_switch then
                debounce_timer <= debounce_timer + 1;
                if debounce_timer = "11111111" then
                    stable_switch <= switch_input;
                end if;
            else
                debounce_timer <= "00000000";
            end if;
            previous_switch <= switch_input;

            if stable_switch = '1' then
                internal_count <= internal_count + 1;
            end if;
        end if;
    end process;

    count <= internal_count;

end Behavioral;

このデバウンスカウンタの特徴は、switch_inputが前回のクロックサイクルと同じであれば、デバウンスタイマをインクリメントし、タイマが最大値に達したら、入力が安定しているとみなしてカウンタをインクリメントすることです。

このコードを実行すると、switch_inputにノイズが混入しても、デバウンスロジックによりカウンタは誤ってカウントアップすることなく、正確に動作します。

ただし、デバウンスタイマの長さや最大値は、実際のノイズの状況に応じて調整する必要があります。

デバウンスロジックを使うことで、ノイジーな環境でも安定した動作をするカウンタを簡単に作成できます。VHDLでの実装も比較的シンプルであり、初心者でも取り組むことができる内容です。

この方法を取り入れることで、信号の誤検出や誤動作を大幅に減少させることができます。

□サンプルコード10:外部クロックソース利用カウンタ

VHDLのカウンタ作成において、外部のクロックソースを利用する方法は、実際のハードウェアデザインにおいて非常に重要です。

特に、実際のデバイスで動作させる際には、外部からのクロック信号を基にしてカウンタを動作させるケースが多いです。

ここでは、外部クロックソースを利用してカウンタを実装する方法を紹介します。

このコードでは外部からのクロック信号を使ってカウンタを動作させるコードを紹介しています。

この例では、外部クロック信号を入力として受け取り、その信号に同期してカウントアップする仕組みを実装しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity ExternalClockCounter is
    Port ( external_clk : in  STD_LOGIC;
           reset : in  STD_LOGIC;
           count : out  STD_LOGIC_VECTOR(7 downto 0));
end ExternalClockCounter;

architecture Behavioral of ExternalClockCounter is
    signal cnt : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
begin
    process(external_clk, reset)
    begin
        if reset = '1' then
            cnt <= "00000000";
        elsif rising_edge(external_clk) then
            cnt <= cnt + 1;
        end if;
    end process;

    count <= cnt;
end Behavioral;

このコードでは、外部からのexternal_clkを受け取り、reset信号が1のときにカウンタをリセットします。

external_clkの立ち上がりエッジでカウントアップが行われるように設計されています。

実装する際に外部のクロック信号が来るため、その信号に同期してカウンタが動作します。

例えば、1Hzのクロックが入力されれば、1秒ごとにカウンタが増加します。

上記のコードをFPGAやシミュレータで実行すると、外部からのクロック信号に同期してカウンタがカウントアップされることが確認できます。

リセット信号を1にすると、カウンタが00000000に初期化されます。

●VHDLカウンタの注意点と対処法

○初心者が陥りやすいミス

VHDLのカウンタ設計で初心者がよく犯すミスとしては、同期信号と非同期信号の取り扱いの混同があります。

正確に動作させるためには、クロック信号とリセット信号の取り扱いを正確に理解する必要があります。

また、データのビット幅を間違えて設定すると、期待通りにカウンタが動作しない場合があります。

例えば、8ビットのカウンタを作成したいのに、7ビットの信号線を用意してしまったりすると、最大値が期待通りになりません。

○対処法

  1. クロック信号とリセット信号の取り扱いを正確に行うためには、常にrising_edgefalling_edge関数を使用して、エッジ検出を行うようにしましょう。
  2. ビット幅のミスを防ぐためには、信号の定義時にビット数を明示的に指定することが推奨されます。
    また、コメントでその信号が何ビットのものであるかを記述することで、後からコードを読む際のミスを減らすことができます。

●カスタマイズ方法

VHDLのカウンタは非常にカスタマイズ性が高いため、様々な動作をさせることが可能です。

特定の動作要件に合わせてカウンタを変更する方法や、様々な応用例を実装する方法を解説します。

○カウンタのカスタマイズの基本

VHDLでのカウンタのカスタマイズは、基本のカウンタ動作を基に、必要な動作を追加・削除することで行います。

例えば、カウントアップだけでなく、特定の値でのカウントダウンや、一定の値でリセットするような動作も簡単に実装することができます。

このコードでは、カウントアップとカウントダウンの動作を組み合わせています。

カウントアップ時とカウントダウン時で異なる動作を実装することが目的です。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity UpDownCounter is
    Port ( clk : in  STD_LOGIC;
           reset : in  STD_LOGIC;
           up_down : in STD_LOGIC; -- '0'でカウントアップ, '1'でカウントダウン
           count : out  STD_LOGIC_VECTOR(7 downto 0));
end UpDownCounter;

architecture Behavioral of UpDownCounter is
    signal cnt : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            cnt <= "00000000";
        elsif rising_edge(clk) then
            if up_down = '0' then
                cnt <= cnt + 1;
            else
                cnt <= cnt - 1;
            end if;
        end if;
    end process;

    count <= cnt;
end Behavioral;

外部からの入力up_downでカウンタの動作を選択できます。

up_down0のときはカウントアップ、1のときはカウントダウンとなります。

カスタマイズを実施する場面で、VHDLの基本的な構文や動作原理の理解が深まります。

実際のハードウェア動作を模擬しながら、必要な動作を実現することが求められるため、応用力が高まることでしょう。

VHDLのカウンタ設計をFPGAやシミュレータで実行すると、外部入力のup_downに応じて、カウンタがカウントアップあるいはカウントダウンする動きを確認することができます。

リセット信号を1にすると、カウンタが00000000に初期化されます。

まとめ

VHDLカウンタの作成から応用、カスタマイズ方法までの詳細を解説してきました。

カウンタ設計において、初心者でも安心して取り組める内容となっており、10のサンプルコードを通じて理解を深めることができるでしょう。

また、応用やカスタマイズの部分では、VHDLの実力をさらに伸ばすことができます。

この記事を参考に、VHDLでのカウンタ設計のスキルアップを目指してみてください。