読み込み中...

VHDLを使った60進カウンタの設計方法と活用12選

60進カウンタ 徹底解説 VHDL
この記事は約44分で読めます。

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

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

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

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

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

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

●VHDLとは?

VHDLは、デジタル回路設計の分野で広く使用されるハードウェア記述言語です。

VHSIC Hardware Description Languageの略称で、1980年代に米国防総省によって開発されました。

○VHDLの基本概念と特徴

VHDLの主要な特徴は、高度な抽象化と階層的な設計が可能な点です。

回路の構造や動作を詳細に記述でき、大規模で複雑なシステムの設計に適しています。

並列処理を自然に表現できることもVHDLの強みです。

実際のハードウェアの動作に近い形でコードを記述できるため、設計者の意図を正確に反映させやすくなっています。

さらに、VHDLはシミュレーション機能が充実しています。

設計した回路の動作を実機に実装する前に、ソフトウェア上で検証できるのが大きな利点です。

○60進カウンタの重要性と応用例

60進カウンタは、時間計測システムにおいて重要な役割を果たします。

1分が60秒、1時間が60分という時間の単位に合わせて設計されているためです。

応用例としては、デジタル時計やストップウォッチが挙げられます。

このデバイスでは、60進カウンタが秒や分の計測に使用されています。

産業用機器の制御システムでも60進カウンタは活躍します。

例えば、製造ラインでの生産時間の管理や、プロセス制御での待機時間の計測などに利用されています。

天文学の分野でも60進カウンタは重要です。

天体の位置を表す赤経や赤緯の計測に用いられ、精密な観測データの記録に貢献しています。

○サンプルコード1:基本的なVHDL構文

VHDLの基本的な構文を理解するために、簡単な例を見てみましょう。

ここでは、2ビットのカウンタを実装するVHDLコードを紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

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

architecture Behavioral of simple_counter is
    signal counter : unsigned(1 downto 0) := "00";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            counter <= "00";
        elsif rising_edge(clk) then
            counter <= counter + 1;
        end if;
    end process;

    count <= STD_LOGIC_VECTOR(counter);
end Behavioral;

このコードでは、まずライブラリの宣言を行っています。

IEEE.STD_LOGIC_1164とIEEE.NUMERIC_STDは、VHDLで頻繁に使用される標準パッケージです。

エンティティ宣言部分では、回路の外部インターフェースを定義しています。

この例では、クロック入力(clk)、リセット入力(reset)、2ビットのカウント出力(count)を持つカウンタを定義しています。

アーキテクチャ部分では、回路の内部動作を記述しています。

プロセス文を使用して、クロックの立ち上がりエッジでカウンタをインクリメントする動作を実装しています。

●60進カウンタの設計手法

60進カウンタの設計には、いくつかの重要な考慮点があります。

ここでは、カウンタ回路の基本原理から始めて、段階的に60進カウンタの実装方法を見ていきましょう。

○カウンタ回路の基本原理

カウンタ回路は、デジタル回路の中でも基本的かつ重要な構成要素です。

その動作原理は単純で、クロック信号に同期して内部の状態を変化させ、一定の順序で値を数え上げていきます。

60進カウンタの場合、0から59までの値を順番にカウントアップし、59の次は0に戻るという動作を繰り返します。

この動作を実現するためには、次の要素が必要となります。

  1. クロック同期 -> カウンタの状態更新はクロック信号に同期して行います。
  2. リセット機能 -> カウンタを初期状態(通常は0)に戻す機能が必要です。
  3. カウント上限の検出 -> 59に達したことを検出し、次のクロックで0に戻す処理が必要です。

これらの要素を踏まえて、実際のVHDLコードで60進カウンタを実装していきましょう。

○サンプルコード2:シンプルな60進カウンタ

ここでは、基本的な60進カウンタのVHDLコードを紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

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

architecture Behavioral of sixty_counter is
    signal counter : unsigned(5 downto 0) := (others => '0');
begin
    process(clk, reset)
    begin
        if reset = '1' then
            counter <= (others => '0');
        elsif rising_edge(clk) then
            if counter = 59 then
                counter <= (others => '0');
            else
                counter <= counter + 1;
            end if;
        end if;
    end process;

    count <= STD_LOGIC_VECTOR(counter);
end Behavioral;

このコードでは、6ビットの信号counterを使用して0から59までのカウントを実現しています。

プロセス文の中で、リセット信号が’1’の場合にカウンタをゼロにリセットし、クロックの立ち上がりエッジでカウントアップする動作を実装しています。

カウンタが59に達すると、次のクロックでゼロにリセットされます。

これで、60進カウンタの基本的な動作が実現されています。

○サンプルコード3:非同期リセット機能の実装

実際の回路設計では、非同期リセット機能が要求されることがあります。

非同期リセットは、クロック信号に関係なく即座にカウンタをリセットする機能です。

ここでは、非同期リセット機能を追加した60進カウンタのコードをみてみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity async_reset_counter is
    Port ( clk : in STD_LOGIC;
           async_reset : in STD_LOGIC;
           count : out STD_LOGIC_VECTOR(5 downto 0));
end async_reset_counter;

architecture Behavioral of async_reset_counter is
    signal counter : unsigned(5 downto 0) := (others => '0');
begin
    process(clk, async_reset)
    begin
        if async_reset = '1' then
            counter <= (others => '0');
        elsif rising_edge(clk) then
            if counter = 59 then
                counter <= (others => '0');
            else
                counter <= counter + 1;
            end if;
        end if;
    end process;

    count <= STD_LOGIC_VECTOR(counter);
end Behavioral;

このコードでは、プロセスの感度リストにasync_reset信号を追加し、if文の最初で非同期リセットの処理を行っています。

これで、async_reset信号が’1’になると、クロックの状態に関わらず即座にカウンタがリセットされます。

○サンプルコード4:クロック分周器との統合

実際のシステムでは、システムクロックが高速すぎる場合があります。

そのような場合、クロック分周器を使用してカウンタの動作速度を調整する必要があります。

クロック分周器を統合した60進カウンタのコードをみてみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

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

architecture Behavioral of clock_divider_counter is
    signal counter : unsigned(5 downto 0) := (others => '0');
    signal clk_divider : unsigned(23 downto 0) := (others => '0');
    signal slow_clk : STD_LOGIC := '0';
begin
    -- クロック分周器
    process(clk, reset)
    begin
        if reset = '1' then
            clk_divider <= (others => '0');
            slow_clk <= '0';
        elsif rising_edge(clk) then
            if clk_divider = 49999999 then  -- 1秒ごとにslow_clkが変化
                clk_divider <= (others => '0');
                slow_clk <= not slow_clk;
            else
                clk_divider <= clk_divider + 1;
            end if;
        end if;
    end process;

    -- 60進カウンタ
    process(slow_clk, reset)
    begin
        if reset = '1' then
            counter <= (others => '0');
        elsif rising_edge(slow_clk) then
            if counter = 59 then
                counter <= (others => '0');
            else
                counter <= counter + 1;
            end if;
        end if;
    end process;

    count <= STD_LOGIC_VECTOR(counter);
end Behavioral;

このコードでは、クロック分周器を使用してシステムクロックを分周し、slow_clk信号を生成しています。

60進カウンタは、このslow_clk信号に同期して動作します。

この例では、50MHzのシステムクロックを1Hzに分周していますが、必要に応じて分周比を調整することができます。

●出力表示システムの構築

60進カウンタを設計した後、次のステップは結果を視覚的に表示することです。

出力表示システムは、カウンタの動作を確認し、ユーザーに情報を伝える重要な役割を果たします。

LEDマトリクスや7セグメントLEDなどの表示デバイスを使用することで、効果的な出力システムを構築できます。

○LEDマトリクスの制御技術

LEDマトリクスは、複数のLEDを格子状に配置した表示デバイスです。

VHDLを使用してLEDマトリクスを制御することで、数字やパターンを表示できます。

LEDマトリクスの制御には、行と列を順次走査する方法がよく用いられます。

LEDマトリクスの制御では、ダイナミック点灯方式が一般的です。

多数のLEDを同時に点灯させるのではなく、高速で順次点灯させることで、人間の目には同時に点灯しているように見せかけます。

VHDLでLEDマトリクスを制御するためには、タイミング制御や走査パターンの生成が必要です。

○サンプルコード5:7セグメントLED制御回路

7セグメントLEDは、数字や一部のアルファベットを表示できる便利なデバイスです。

VHDLを使用して7セグメントLEDを制御する回路を設計してみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity seven_segment_controller is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           bcd_in : in STD_LOGIC_VECTOR(3 downto 0);
           segments : out STD_LOGIC_VECTOR(6 downto 0));
end seven_segment_controller;

architecture Behavioral of seven_segment_controller is
begin
    process(clk, reset)
    begin
        if reset = '1' then
            segments <= "1111111";  -- すべてのセグメントを消灯
        elsif rising_edge(clk) then
            case bcd_in is
                when "0000" => segments <= "1000000";  -- 0
                when "0001" => segments <= "1111001";  -- 1
                when "0010" => segments <= "0100100";  -- 2
                when "0011" => segments <= "0110000";  -- 3
                when "0100" => segments <= "0011001";  -- 4
                when "0101" => segments <= "0010010";  -- 5
                when "0110" => segments <= "0000010";  -- 6
                when "0111" => segments <= "1111000";  -- 7
                when "1000" => segments <= "0000000";  -- 8
                when "1001" => segments <= "0010000";  -- 9
                when others => segments <= "1111111";  -- エラー状態
            end case;
        end if;
    end process;
end Behavioral;

このコードでは、4ビットのBCD入力を受け取り、対応する7セグメントLEDの点灯パターンを出力します。

各セグメントは0で点灯、1で消灯を表します。

リセット信号が入力されると、全セグメントが消灯状態になります。

実行結果としては、例えば、bcd_in が “0101” (5を表す) の場合、segments 出力は “0010010” となり、5という数字が7セグメントLEDに表示されます。

○サンプルコード6:マルチプレクサを用いた表示切替

複数の7セグメントLEDを使用する場合、マルチプレクサを用いて表示を切り替える技術が有効です。

次のコードは、4桁の7セグメントLED表示を制御する回路です。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity multiplexed_seven_segment is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           bcd_in : in STD_LOGIC_VECTOR(15 downto 0);
           anodes : out STD_LOGIC_VECTOR(3 downto 0);
           segments : out STD_LOGIC_VECTOR(6 downto 0));
end multiplexed_seven_segment;

architecture Behavioral of multiplexed_seven_segment is
    signal counter : unsigned(1 downto 0) := (others => '0');
    signal current_bcd : STD_LOGIC_VECTOR(3 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            counter <= (others => '0');
            anodes <= "1111";
            segments <= "1111111";
        elsif rising_edge(clk) then
            counter <= counter + 1;
            case counter is
                when "00" => 
                    anodes <= "1110";
                    current_bcd <= bcd_in(3 downto 0);
                when "01" => 
                    anodes <= "1101";
                    current_bcd <= bcd_in(7 downto 4);
                when "10" => 
                    anodes <= "1011";
                    current_bcd <= bcd_in(11 downto 8);
                when "11" => 
                    anodes <= "0111";
                    current_bcd <= bcd_in(15 downto 12);
                when others => 
                    anodes <= "1111";
                    current_bcd <= "0000";
            end case;

            case current_bcd is
                when "0000" => segments <= "1000000";  -- 0
                when "0001" => segments <= "1111001";  -- 1
                when "0010" => segments <= "0100100";  -- 2
                when "0011" => segments <= "0110000";  -- 3
                when "0100" => segments <= "0011001";  -- 4
                when "0101" => segments <= "0010010";  -- 5
                when "0110" => segments <= "0000010";  -- 6
                when "0111" => segments <= "1111000";  -- 7
                when "1000" => segments <= "0000000";  -- 8
                when "1001" => segments <= "0010000";  -- 9
                when others => segments <= "1111111";  -- エラー状態
            end case;
        end if;
    end process;
end Behavioral;

このコードでは、16ビットのBCD入力(4桁の数字)を受け取り、4つの7セグメントLEDに順次表示します。

counterを使用して表示する桁を切り替え、anodesで活性化するLEDを選択しています。

実行結果として、例えばbcd_in が “0001001000110100” (1234を表す) の場合、4つの7セグメントLEDに順次1、2、3、4が表示されます。

高速で切り替えることで、すべての数字が同時に表示されているように見えます。

●FPGAでの実装とテスト戦略

FPGAでVHDLコードを実装する際、適切なテスト戦略を立てることが重要です。

シミュレーションを活用することで、実機にプログラムを書き込む前に動作を検証できます。

○シミュレーション環境の構築方法

シミュレーション環境を構築するには、まず適切なツールを選択する必要があります。

Xilinx Vivadoや Intel Quartus Primeなどの統合開発環境には、シミュレーション機能が組み込まれています。

シミュレーション環境の構築手順は次の通りです。

  1. プロジェクトの作成 -> 新しいプロジェクトを作成し、設計ファイルを追加します。
  2. テストベンチの作成 -> 設計した回路をテストするためのテストベンチを作成します。
  3. シミュレーション設定 -> 使用するシミュレータを選択し、シミュレーション時間などのパラメータを設定します。
  4. 波形表示の設定 -> 確認したい信号を波形表示に追加します。
  5. シミュレーションの実行 -> 設定が完了したら、シミュレーションを実行します。

○サンプルコード7:テストベンチの作成

テストベンチは、設計した回路の動作を検証するためのVHDLコードです。

ここでは、60進カウンタのテストベンチの例を見てみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity sixty_counter_tb is
end sixty_counter_tb;

architecture Behavioral of sixty_counter_tb is
    component sixty_counter
        Port ( clk : in STD_LOGIC;
               reset : in STD_LOGIC;
               count : out STD_LOGIC_VECTOR(5 downto 0));
    end component;

    signal clk : STD_LOGIC := '0';
    signal reset : STD_LOGIC := '0';
    signal count : STD_LOGIC_VECTOR(5 downto 0);

    constant clk_period : time := 10 ns;
begin
    uut: sixty_counter port map (
        clk => clk,
        reset => reset,
        count => count
    );

    clk_process: process
    begin
        clk <= '0';
        wait for clk_period/2;
        clk <= '1';
        wait for clk_period/2;
    end process;

    stim_proc: process
    begin
        reset <= '1';
        wait for 100 ns;
        reset <= '0';
        wait for 1000 ns;
        wait;
    end process;
end Behavioral;

このテストベンチでは、クロック信号を生成し、リセット信号を制御しています。

シミュレーションを実行すると、カウンタの動作を観察できます。

○サンプルコード8:波形表示によるデバッグ手法

波形表示は、信号の変化を視覚的に確認するための強力なツールです。

次のコードは、波形表示用の設定を追加したテストベンチの例です。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use STD.TEXTIO.ALL;
use IEEE.STD_LOGIC_TEXTIO.ALL;

entity sixty_counter_tb_wave is
end sixty_counter_tb_wave;

architecture Behavioral of sixty_counter_tb_wave is
    component sixty_counter
        Port ( clk : in STD_LOGIC;
               reset : in STD_LOGIC;
               count : out STD_LOGIC_VECTOR(5 downto 0));
    end component;

    signal clk : STD_LOGIC := '0';
    signal reset : STD_LOGIC := '0';
    signal count : STD_LOGIC_VECTOR(5 downto 0);

    constant clk_period : time := 10 ns;

    file wave_file : TEXT;
begin
    uut: sixty_counter port map (
        clk => clk,
        reset => reset,
        count => count
    );

    clk_process: process
    begin
        clk <= '0';
        wait for clk_period/2;
        clk <= '1';
        wait for clk_period/2;
    end process;

    stim_proc: process
    begin
        reset <= '1';
        wait for 100 ns;
        reset <= '0';
        wait for 1000 ns;
        wait;
    end process;

    wave_process: process
        variable l : LINE;
    begin
        file_open(wave_file, "wave_output.txt", WRITE_MODE);

        while now < 1100 ns loop
            wait for clk_period;
            write(l, now, right, 15);
            write(l, string'(" "));
            write(l, count);
            writeline(wave_file, l);
        end loop;

        file_close(wave_file);
        wait;
    end process;
end Behavioral;

この拡張されたテストベンチでは、波形データをテキストファイルに出力しています。

シミュレーション結果を波形ビューアで表示することで、信号の変化を視覚的に確認できます。

実行結果として、”wave_output.txt”ファイルにシミュレーション時間とカウント値が記録されます。

このデータを波形ビューアで表示すれば、カウンタの動作を時系列で確認できます。

●よくあるエラーと対処法

VHDLを使用した60進カウンタの設計や実装において、様々なエラーに遭遇することがあります。

エラーを適切に理解し、効果的に対処することで、より堅牢なシステムを構築できます。

ここでは、頻繁に発生するエラーとその解決策について詳しく解説します。

○タイミング違反の解決策

タイミング違反は、FPGAの設計において最も一般的な問題の一つです。

信号が目的地に到達する前にクロックエッジが発生すると、タイミング違反が起こります。

解決策として、パイプライン化が効果的です。

長い組み合わせ論理を複数のステージに分割し、各ステージ間にレジスタを挿入することで、クリティカルパスを短縮できます。

例えば、複雑な演算を行うカウンタの場合、次のようにパイプライン化を実装できます。

architecture pipelined of complex_counter is
    signal stage1_result, stage2_result : unsigned(5 downto 0);
begin
    process(clk)
    begin
        if rising_edge(clk) then
            -- Stage 1: 複雑な演算の前半
            stage1_result <= complex_function_part1(counter);

            -- Stage 2: 複雑な演算の後半
            stage2_result <= complex_function_part2(stage1_result);

            -- 最終結果の更新
            counter <= stage2_result;
        end if;
    end process;
end architecture pipelined;

この実装により、一つのクロックサイクルで行っていた複雑な演算を2つのステージに分割し、タイミング違反のリスクを軽減しています。

○合成エラーの原因と修正方法

合成エラーは、VHDLコードをハードウェアに変換する際に発生します。

よくある原因として、不適切な変数型の使用や、合成ツールがサポートしていない構文の使用が挙げられます。

例えば、整数型を使用した場合、合成ツールが適切なビット幅を決定できないことがあります。

代わりに、明示的にビット幅を指定したunsigned型やsigned型を使用することで、この問題を回避できます。

-- 問題のあるコード
signal counter : integer range 0 to 59;

-- 改善されたコード
signal counter : unsigned(5 downto 0);

また、初期化ブロックは合成時に無視されることがあるため、注意が必要です。

代わりに、リセット信号を使用して初期化を行うことをお勧めします。

-- 問題のあるコード
signal counter : unsigned(5 downto 0) := (others => '0');

-- 改善されたコード
process(clk, reset)
begin
    if reset = '1' then
        counter <= (others => '0');
    elsif rising_edge(clk) then
        -- カウンタの更新ロジック
    end if;
end process;

○シミュレーションと実機の動作の不一致

シミュレーションで正常に動作するコードが、実機で予期せぬ動作をすることがあります。

この不一致の主な原因は、タイミングの問題やリセット条件の違いです。

解決策として、タイミング制約を適切に設定し、スタティックタイミング解析を実行することが重要です。

また、非同期リセットの使用や、クロックドメイン間のデータ転送時にクロック同期化回路を使用することで、不安定な動作を防ぐことができます。

ここでは、クロックドメイン間のデータ転送を安全に行うための二段フリップフロップの例を紹介します。

architecture safe_transfer of clock_domain_crossing is
    signal data_sync1, data_sync2 : std_logic;
begin
    process(clk_dest)
    begin
        if rising_edge(clk_dest) then
            data_sync1 <= data_src;
            data_sync2 <= data_sync1;
            data_out <= data_sync2;
        end if;
    end process;
end architecture safe_transfer;

この実装により、異なるクロックドメイン間でのデータ転送時に発生するメタステーブル状態のリスクを軽減できます。

●60進カウンタの高度な応用例

60進カウンタの基本的な実装を理解したら、より高度な応用へと進むことができます。

ここでは、実用的なプロジェクトに適用可能な高度な60進カウンタの例を紹介します。

○サンプルコード9:時分秒カウンタの実装

時分秒カウンタは、60進カウンタを拡張して実現できます。

時、分、秒の3つのカウンタを連携させることで、24時間形式の時計を作成できます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity time_counter is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           seconds : out STD_LOGIC_VECTOR(5 downto 0);
           minutes : out STD_LOGIC_VECTOR(5 downto 0);
           hours : out STD_LOGIC_VECTOR(4 downto 0));
end time_counter;

architecture Behavioral of time_counter is
    signal sec_count : unsigned(5 downto 0) := (others => '0');
    signal min_count : unsigned(5 downto 0) := (others => '0');
    signal hour_count : unsigned(4 downto 0) := (others => '0');
begin
    process(clk, reset)
    begin
        if reset = '1' then
            sec_count <= (others => '0');
            min_count <= (others => '0');
            hour_count <= (others => '0');
        elsif rising_edge(clk) then
            if sec_count = 59 then
                sec_count <= (others => '0');
                if min_count = 59 then
                    min_count <= (others => '0');
                    if hour_count = 23 then
                        hour_count <= (others => '0');
                    else
                        hour_count <= hour_count + 1;
                    end if;
                else
                    min_count <= min_count + 1;
                end if;
            else
                sec_count <= sec_count + 1;
            end if;
        end if;
    end process;

    seconds <= STD_LOGIC_VECTOR(sec_count);
    minutes <= STD_LOGIC_VECTOR(min_count);
    hours <= STD_LOGIC_VECTOR(hour_count);
end Behavioral;

この時分秒カウンタは、1秒ごとにカウントアップし、分と時間も適切に更新します。

24時間経過すると、0時0分0秒にリセットされます。

○サンプルコード10:アラーム機能の追加

時計にアラーム機能を追加することで、実用性が高まります。

ここでは、指定した時刻になるとアラームを発生させる機能を持つ拡張版時分秒カウンタを紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity alarm_clock is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           set_alarm : in STD_LOGIC;
           alarm_hour : in STD_LOGIC_VECTOR(4 downto 0);
           alarm_minute : in STD_LOGIC_VECTOR(5 downto 0);
           seconds : out STD_LOGIC_VECTOR(5 downto 0);
           minutes : out STD_LOGIC_VECTOR(5 downto 0);
           hours : out STD_LOGIC_VECTOR(4 downto 0);
           alarm_trigger : out STD_LOGIC);
end alarm_clock;

architecture Behavioral of alarm_clock is
    signal sec_count : unsigned(5 downto 0) := (others => '0');
    signal min_count : unsigned(5 downto 0) := (others => '0');
    signal hour_count : unsigned(4 downto 0) := (others => '0');
    signal alarm_hour_reg : unsigned(4 downto 0);
    signal alarm_minute_reg : unsigned(5 downto 0);
    signal alarm_set : STD_LOGIC := '0';
begin
    process(clk, reset)
    begin
        if reset = '1' then
            sec_count <= (others => '0');
            min_count <= (others => '0');
            hour_count <= (others => '0');
            alarm_set <= '0';
            alarm_trigger <= '0';
        elsif rising_edge(clk) then
            if set_alarm = '1' then
                alarm_hour_reg <= unsigned(alarm_hour);
                alarm_minute_reg <= unsigned(alarm_minute);
                alarm_set <= '1';
            end if;

            if sec_count = 59 then
                sec_count <= (others => '0');
                if min_count = 59 then
                    min_count <= (others => '0');
                    if hour_count = 23 then
                        hour_count <= (others => '0');
                    else
                        hour_count <= hour_count + 1;
                    end if;
                else
                    min_count <= min_count + 1;
                end if;
            else
                sec_count <= sec_count + 1;
            end if;

            if alarm_set = '1' and hour_count = alarm_hour_reg and min_count = alarm_minute_reg and sec_count = 0 then
                alarm_trigger <= '1';
            else
                alarm_trigger <= '0';
            end if;
        end if;
    end process;

    seconds <= STD_LOGIC_VECTOR(sec_count);
    minutes <= STD_LOGIC_VECTOR(min_count);
    hours <= STD_LOGIC_VECTOR(hour_count);
end Behavioral;

このアラーム付き時計は、set_alarm信号が’1’になると、指定された時刻(alarm_hourとalarm_minute)にアラームをセットします。

設定された時刻になると、alarm_trigger信号が’1’になります。

○サンプルコード11:温度センサーとの連携

60進カウンタを温度センサーと組み合わせることで、定期的に温度を測定し記録するシステムを作成できます。

1分ごとに温度を測定し、最高温度と最低温度を記録するシステムの例を見てみましょう。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity temp_monitor is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           temp_in : in STD_LOGIC_VECTOR(7 downto 0);
           current_temp : out STD_LOGIC_VECTOR(7 downto 0);
           max_temp : out STD_LOGIC_VECTOR(7 downto 0);
           min_temp : out STD_LOGIC_VECTOR(7 downto 0));
end temp_monitor;

architecture Behavioral of temp_monitor is
    signal sec_count : unsigned(5 downto 0) := (others => '0');
    signal min_count : unsigned(5 downto 0) := (others => '0');
    signal temp_current : unsigned(7 downto 0);
    signal temp_max : unsigned(7 downto 0);
    signal temp_min : unsigned(7 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            sec_count <= (others => '0');
            min_count <= (others => '0');
            temp_current <= (others => '0');
            temp_max <= (others => '0');
            temp_min <= (others => '1');
        elsif rising_edge(clk) then
            if sec_count = 59 then
                sec_count <= (others => '0');
                if min_count = 59 then
                    min_count <= (others => '0');
                else
                    min_count <= min_count + 1;
                end if;

                -- 1分ごとに温度を測定
                temp_current <= unsigned(temp_in);

                -- 最高温度と最低温度の更新
                if unsigned(temp_in) > temp_max then
                    temp_max <= unsigned(temp_in);
                end if;
                if unsigned(temp_in) < temp_min then
                    temp_min <= unsigned(temp_in);
                end if;
            else
                sec_count <= sec_count + 1;
            end if;
        end if;
    end process;

    current_temp <= STD_LOGIC_VECTOR(temp_current);
    max_temp <= STD_LOGIC_VECTOR(temp_max);
    min_temp <= STD_LOGIC_VECTOR(temp_min);
end Behavioral;

このシステムは、60進カウンタを使用して1分ごとのタイミングを生成し、温度センサーからの入力(temp_in)を読み取ります。

現在の温度、最高温度、最低温度を常に更新し、出力します。

○サンプルコード12:Wi-Fi経由の時刻同期機能

Wi-Fi経由で時刻を同期する機能を60進カウンタに追加することで、常に正確な時刻を維持できるシステムを構築できます。

ここでは、Wi-Fi経由での時刻同期機能を持つ拡張版60進カウンタの例を紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity wifi_sync_clock is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           wifi_sync : in STD_LOGIC;
           wifi_time : in STD_LOGIC_VECTOR(31 downto 0);
           seconds : out STD_LOGIC_VECTOR(5 downto 0);
           minutes : out STD_LOGIC_VECTOR(5 downto 0);
           hours : out STD_LOGIC_VECTOR(4 downto 0));
end wifi_sync_clock;

architecture Behavioral of wifi_sync_clock is
    signal sec_count : unsigned(5 downto 0) := (others => '0');
    signal min_count : unsigned(5 downto 0) := (others => '0');
    signal hour_count : unsigned(4 downto 0) := (others => '0');
begin
    process(clk, reset)
    begin
        if reset = '1' then
            sec_count <= (others => '0');
            min_count <= (others => '0');
            hour_count <= (others => '0');
        elsif rising_edge(clk) then
            if wifi_sync = '1' then
                -- Wi-Fi経由で受信した時刻で同期
                sec_count <= unsigned(wifi_time(5 downto 0));
                min_count <= unsigned(wifi_time(11 downto 6));
                hour_count <= unsigned(wifi_time(16 downto 12));
            else
                -- 通常のカウントアップ処理
                if sec_count = 59 then
                    sec_count <= (others => '0');
                    if min_count = 59 then
                        min_count <= (others => '0');
                        if hour_count = 23 then
                            hour_count <= (others => '0');
                        else
                            hour_count <= hour_count + 1;
                        end if;
                    else
                        min_count <= min_count + 1;
                    end if;
                else
                    sec_count <= sec_count + 1;
                end if;
            end if;
        end if;
    end process;

    seconds <= STD_LOGIC_VECTOR(sec_count);
    minutes <= STD_LOGIC_VECTOR(min_count);
    hours <= STD_LOGIC_VECTOR(hour_count);
end Behavioral;

このWi-Fi同期時計システムは、通常の60進カウンタとしての機能に加えて、Wi-Fi経由で受信した時刻情報で同期する機能を備えています。

wifi_sync信号が’1’になると、wifi_timeから受信した時刻情報でカウンタを更新します。

wifi_time信号は32ビットの

ベクトルで、下位6ビットが秒、次の6ビットが分、さらに次の5ビットが時間を表します。

Wi-Fi経由の時刻同期機能により、長期間使用しても時刻のずれを最小限に抑えることができます。

また、タイムゾーンの変更やサマータイムの調整も、Wi-Fi経由で受信した時刻情報を基に自動的に行うことが可能です。

まとめ

VHDLを使用した60進カウンタの設計と実装について、基本的な概念から高度な応用例まで幅広く解説しました。

60進カウンタは、時間計測システムの基礎となる重要な要素であり、多様な分野で活用されています。

本記事で学んだ内容を実際のプロジェクトに適用する際は、具体的な要件や制約条件に応じて適切にカスタマイズすることを忘れないでください。

各プロジェクトには固有の課題があり、それらに柔軟に対応することが、成功への鍵となります。