読み込み中...

VHDLシミュレーションの手引き10選

初心者も理解できるVHDLシミュレーションのイラスト VHDL
この記事は約24分で読めます。

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

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

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

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

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

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

はじめに

VHDLはデジタル回路の設計やシミュレーションに使用される言語です。

この記事では、VHDLシミュレーションの基本から、実用的なサンプルコード10選、注意点、カスタマイズ方法までを詳しく解説します。

●VHDLシミュレーションの基本

○VHDLの基礎知識

VHDLは「VHSIC Hardware Description Language」の略で、高速集積回路のハードウェア記述言語として知られています。

デジタルロジックを効率的に表現することができ、さまざまな回路設計やシミュレーションで広く利用されています。

○シミュレーションの流れ

VHDLシミュレーションを行うには、次のステップが一般的です。

  1. VHDLコードの作成:目的の回路機能を記述します。
  2. コンパイル:VHDLコードをシミュレータに合わせてコンパイルします。
  3. シミュレーション実行:テストベンチを使用して、シミュレーションを実行します。
  4. 結果の確認:シミュレーション結果を確認し、必要に応じてコードの修正を行います。

●実用的なサンプルコード10選

○サンプルコード1:基本的なLED点滅

このコードではVHDLを使って、基本的なLEDの点滅を実現するコードを表しています。

この例ではクロック信号に同期してLEDを点滅させています。

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

entity LED_blink is
    Port ( CLK : in STD_LOGIC;
           LED : out STD_LOGIC);
end LED_blink;

architecture Behavioral of LED_blink is
    signal cnt : std_logic_vector(25 downto 0) := "00000000000000000000000000";
begin
    process(CLK)
    begin
        if rising_edge(CLK) then
            cnt <= cnt + 1;
            LED <= cnt(25);
        end if;
    end process;
end Behavioral;

このコードは25ビットカウンタを利用して、クロック信号に同期してLEDを点滅させます。

cntの25ビット目がLEDに接続されており、カウンタがオーバーフローするとLEDが点滅します。

このコードを実行すると、クロック信号に同期してLEDが定期的に点滅することが確認できます。

○サンプルコード2:スイッチ入力の取得

VHDLシミュレーションを学び進める中で、スイッチ入力の取得は初心者にとって欠かせない基本的なステップと言えるでしょう。

スイッチからの入力は、多くの電子デバイスや回路での操作に必要な要素です。

この節では、VHDLを使ってスイッチ入力をどのように取得するのか、具体的なサンプルコードを通して説明します。

このコードでは、外部のスイッチからの入力をVHDLで取得するコードを表しています。

この例では、スイッチがオンのときに特定の動作を行い、オフのときには異なる動作を行うというシンプルな動作を実現しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity SwitchInput is
    Port ( switch : in STD_LOGIC;
           LED : out STD_LOGIC);
end SwitchInput;

architecture Behavioral of SwitchInput is
begin
process(switch)
begin
  if switch = '1' then
    -- スイッチがオンのときの動作
    LED <= '1'; -- LEDを点灯
  else
    -- スイッチがオフのときの動作
    LED <= '0'; -- LEDを消灯
  end if;
end process;
end Behavioral;

このコードの主な動作は、スイッチの入力に応じてLEDを制御するものです。

具体的には、スイッチがオン(’1’)の場合にはLEDを点灯し、オフ(’0’)の場合にはLEDを消灯するという動作をしています。

このサンプルコードを実行した際、スイッチがオンになるとLEDが点灯し、オフになるとLEDが消灯するという動きを観察することができます。

この基本的なサンプルは、さまざまな応用例へと発展させることができます。

例えば、複数のスイッチを使用して、それぞれ異なるLEDやデバイスを制御するというような応用も考えられます。

また、スイッチの入力状態に応じて異なる動作を行う複雑なシミュレーションも、この基本的なコードをベースとして作成することが可能です。

次に、このサンプルコードの注意点として、物理的なスイッチのチャタリング現象について触れておきます。

チャタリングとは、スイッチをオンやオフにした際に、短時間で複数回のオンオフが繰り返される現象のことを指します。

この現象が発生すると、意図しない動作や誤動作の原因となることがあるため、デバウンス回路やソフトウェアでのチャタリング対策が必要となります。

VHDLシミュレーションでのスイッチ入力の取得は、基本的な操作の一部として重要な役割を果たしています。

○サンプルコード3:ディスプレイ表示の制御

ディスプレイ表示の制御は、電子工学や情報工学の分野において、非常に基本的かつ重要なスキルとなります。

特にVHDLを利用したシミュレーション環境では、複雑な動作をシンプルに示すためにディスプレイ表示の実装は必須となります。

ここでは、ディスプレイに数字や文字を表示する基本的なサンプルコードを解説します。

-- ディスプレイ表示のサンプルコード
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity displayControl is
    Port ( clk : in STD_LOGIC;
           rst : in STD_LOGIC;
           dataIn : in STD_LOGIC_VECTOR(7 downto 0);
           displayOut : out STD_LOGIC_VECTOR(7 downto 0));
end displayControl;

architecture behavior of displayControl is
begin
    process(clk, rst)
    begin
        if rst = '1' then
            -- リセット時にディスプレイをクリア
            displayOut <= (others => '0');
        elsif rising_edge(clk) then
            -- データ入力をディスプレイ出力に転送
            displayOut <= dataIn;
        end if;
    end process;
end behavior;

このコードでは、外部からの8ビットのデータ入力をディスプレイに表示する動作を実現しています。

具体的には、クロックの立ち上がりエッジごとに、データ入力dataInの内容をディスプレイ出力displayOutに転送しています。

リセット信号rstがアクティブになったときは、ディスプレイをすぐにクリアしています。

この例で行われていることは、クロックの動作に同期して、ディスプレイに情報を表示するためのデータ転送を行っています。

これにより、外部からの任意のデータをリアルタイムにディスプレイに反映させることができます。

このコードを実際にVHDLシミュレーションツールで実行すると、入力データがディスプレイに正確に反映されることを確認できます。

リセット時にはディスプレイがクリアされ、それ以外のときは入力データがそのまま表示されます。

このディスプレイ表示の制御を応用することで、さまざまな情報をディスプレイに表示することができます。

例えば、センサからのデータをリアルタイムで表示したり、計算結果やログ情報をユーザに提示したりすることができます。

この基本的な制御をマスターすることで、より高度なディスプレイ制御やユーザインターフェースの実装もスムーズに進めることができるでしょう。

○サンプルコード4:タイマー機能の実装

VHDLを用いたシミュレーションでのタイマー機能の実装は、多くの電子回路設計で必要とされる基本的な機能です。

ここでは、タイマーを実装するシンプルなサンプルコードとその解説を行います。

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

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

architecture Behavior of timer is
    signal cnt : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
begin
    process(clk, rst)
    begin
        -- クロックの立ち上がりエッジで動作
        if rising_edge(clk) then
            -- リセットがアクティブならカウントを初期化
            if rst = '1' then
                cnt <= "00000000";
            -- スタートがアクティブならカウントアップ
            elsif start = '1' then
                cnt <= cnt + 1;
            end if;
        end if;
    end process;

    -- カウント値を外部に出力
    count <= cnt;

    -- カウント値が最大になったらdoneをアクティブに
    done <= '1' when cnt = "11111111" else '0';

end Behavior;

このコードでは、外部からのクロック入力clkに同期して、タイマーをカウントアップする機能を実装しています。

rst入力を用いてタイマーをリセットし、start入力を用いてタイマーをスタートします。カウント値が最大になった場合、done出力がアクティブになります。

具体的な動作は次の通りです。

  • rstがアクティブ(1)の場合、カウントは”00000000″に初期化されます。
  • startがアクティブの間、毎クロックでカウントが1増加します。
  • カウントが”11111111″になると、doneがアクティブになります。

この例では、8ビットのカウントアップタイマーを作成していますので、最大で256クロックサイクルまでカウントすることができます。

このタイマー機能は、特定の期間待機したり、特定の期間操作を続けたりする際に役立ちます。

例えば、LEDを一定時間点滅させたり、モータを一定時間動作させるなどの応用が考えられます。

次に、このタイマーを使った実際の動作の様子を説明します。

仮に、clkが1MHzの周波数で動作しているとすると、カウントが最大値になるまでの時間は約256μsとなります。

そのため、startをアクティブにしてから約256μs後に、doneがアクティブになることが確認できます。

注意点として、このサンプルコードはシンプルな例を示すためのもので、実際の回路設計においては、オーバーフロー対策や周波数設定などの追加的な機能が必要になることも考えられます。

○サンプルコード5:カウンター機能の実装

VHDLでは、電子回路のシミュレーションが容易に行える特性を持っており、今回のサンプルコードでは、基本的なカウンター機能の実装について紹介します。

この例では、外部からのクロック信号に同期して、カウント値を増加させるシンプルなカウンターを構築しています。

-- カウンターモジュールの定義
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

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

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

    count <= count_internal;
end Behavioral;

このコードでは、クロック信号clkが立ち上がりエッジを検出するたびに、内部のカウント値count_internalが1増加します。

また、リセット信号resetがアクティブ(=’1′)の場合、カウンターは初期値”00000000″にリセットされます。

このカウンターを使用すると、例えばLEDを点滅させるタイミングや、特定の間隔での動作を制御する際に役立ちます。

カウンター値が最大値に達したら何らかのアクションを起こす、といった利用方法が考えられます。

このシミュレーションを行うと、クロックの立ち上がり毎にカウンター値が増加していく様子を確認できます。

リセット信号をアクティブにすると、カウンター値は0に戻ります。

応用例としては、このカウンターの出力をディスプレイやLEDに接続して、動作を視覚的に確認することができます。

また、カウンターの桁数や最大値を変更して、さまざまな用途に適用することも可能です。

○サンプルコード6:乗算回路の実装

VHDLでのデジタル回路の設計は非常に強力で、あらゆる複雑な計算や操作が可能です。

今回は乗算の基本的な操作に焦点を当て、VHDLでの乗算回路の実装方法を紹介します。

このコードでは乗算を行う簡単な回路を設計しています。

この例では、2つの入力信号を取り、それらの乗算結果を出力として提供します。

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

entity multiplier is
    Port ( A : in  STD_LOGIC_VECTOR(3 downto 0); -- 4bit入力A
           B : in  STD_LOGIC_VECTOR(3 downto 0); -- 4bit入力B
           P : out STD_LOGIC_VECTOR(7 downto 0)); -- 8bitの出力結果
end multiplier;

architecture Behavioral of multiplier is
begin
    P <= A * B; -- AとBの乗算
end Behavioral;

このコードの中心的な部分は、P <= A * B;の行です。

ここで、2つの4ビット入力AとBの乗算を行い、8ビットの結果をPに格納しています。

VHDLの強力な機能の1つは、このように複雑な操作もシンプルな記述で実現できる点です。

シミュレーションを実行すると、例えばAに"0010"(=2)、Bに"0011"(=3)を入力として与えると、Pの出力結果は"0110"(=6)となります。

これは、2乗する3は6という計算結果と一致しています。

○サンプルコード7:メモリ操作のシミュレーション

VHDLシミュレーションにおいて、メモリ操作は中級者向けのトピックとして取り上げられることが多いです。

デジタル回路におけるデータの一時保存やデータの取得、書き換えを行う際に、メモリは非常に重要な役割を果たします。

ここでは、VHDLを使ったメモリ操作のシミュレーションについて学びます。

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

entity MemoryOperation is
    Port ( clk : in STD_LOGIC;
           wr : in STD_LOGIC;
           addr : in STD_LOGIC_VECTOR(3 downto 0);
           data_in : in STD_LOGIC_VECTOR(7 downto 0);
           data_out : out STD_LOGIC_VECTOR(7 downto 0));
end MemoryOperation;

architecture Behavioral of MemoryOperation is
    type memory_array is array (0 to 15) of STD_LOGIC_VECTOR(7 downto 0);
    signal memory : memory_array := (others => "00000000");
begin
    process(clk)
    begin
        if rising_edge(clk) then
            if wr = '1' then
                memory(conv_integer(addr)) <= data_in;
            end if;
            data_out <= memory(conv_integer(addr));
        end if;
    end process;
end Behavioral;

このコードでは、8ビット幅のデータを持つ16個のメモリセルを操作することができます。

具体的には、wr信号が’1’のとき、data_inの値がaddrで指定されたメモリアドレスに書き込まれます。

そして、どのアドレスが指定されていても、そのアドレスのメモリセルの内容がdata_outに出力されます。

この例では、メモリアドレスは4ビット幅のaddrで指定し、対応する8ビットのデータをdata_inから取得、もしくはdata_outとして出力するシステムを作成しています。

こうしたメモリ操作は、例えばデジタル時計の内部で現在の時間を保存したり、簡単なCPUで指示を保存したりするのに使用されます。

このシンプルなメモリ操作のシミュレーションは、VHDL入門者にとって非常に有用なスキルとなります。

このコードを正常に実行すると、wrが’1’の時に指定したアドレスにデータが書き込まれ、そのアドレスの内容がdata_outに正確に出力されることが期待されます。

ですので、例えばaddrが”0010″、data_inが”11001010″でwrが’1’の時にそのデータを書き込み、後でaddrを”0010″に設定すると、data_outとして”11001010″が得られることになります。

次に、このコードのカスタマイズ例として、メモリサイズやデータ幅を変更する方法を見てみましょう。

-- メモリサイズを32、データ幅を16ビットに変更
type memory_array is array (0 to 31) of STD_LOGIC_VECTOR(15 downto 0);

上記のコードの変更により、メモリのサイズやデータの幅を容易にカスタマイズすることができます。

このようにVHDLでは、一度基本的な構造を理解してしまえば、様々なカスタマイズが可能です。

○サンプルコード8:モジュラー構造の利用

VHDL設計において、モジュラー構造の活用は非常に重要です。

モジュラー設計は、大きな設計を小さな部品に分割するアプローチで、各部品をモジュールと呼びます。

これにより、再利用やテスト、デバッグが容易になり、全体の設計が明確かつ効率的に行えるようになります。

下記のコードは、モジュラー構造を活用したシンプルな加算器と乗算器の組み合わせの例を表しています。

-- 加算器モジュール
module adder(a, b, sum);
  input a, b;
  output sum;
  -- 加算のロジック
  assign sum = a + b;
endmodule

-- 乗算器モジュール
module multiplier(a, b, product);
  input a, b;
  output product;
  -- 乗算のロジック
  assign product = a * b;
endmodule

-- 上記の加算器と乗算器を利用した計算モジュール
module calculator(input1, input2, outputSum, outputProduct);
  input input1, input2;
  output outputSum, outputProduct;

  adder add(input1, input2, outputSum);          -- 加算器モジュールのインスタンス化
  multiplier mult(input1, input2, outputProduct); -- 乗算器モジュールのインスタンス化

endmodule

このコードでは、加算器と乗算器の2つの独立したモジュールを定義しています。

そして、これらを組み合わせて全体のcalculatorモジュール内でインスタンス化して利用しています。

このように部分的なロジックをモジュールとして分割し、それらを組み合わせることで、大きなシステムの設計もスムーズに行えます。

このモジュラー設計を採用すると、次のような利点が得られます。

  1. 各モジュールは独立してテストや再利用が可能になる。
  2. 大規模な設計でも、各モジュールを独立して考えることで、全体の流れや機能が把握しやすくなる。
  3. 同じ機能を持つモジュールは再利用することができ、コードの量を削減できる。

実際に上記のコードをシミュレーションすると、入力値input1input2に基づいて、outputSumにはその和、outputProductにはその積が出力される動作を確認することができます。

例えば、input1が3、input2が4の場合、outputSumは7、outputProductは12として出力されるでしょう。

○サンプルコード9:信号処理の実装例

信号処理は、VHDLの設計で頻繁に取り扱われるテーマの一つです。

特にデジタルフィルターやFFT(高速フーリエ変換)のような高度なアルゴリズムを実装する際には、この知識が不可欠となります。

ここでは、VHDLを使ってシンプルな信号処理の例を取り上げ、それに基づいて実用的なサンプルコードの解説を行います。

このコードでは、信号を受け取り、移動平均を計算するシンプルな例を表しています。

この例では、4つの信号を順次受け取り、その平均値を出力するシンプルな移動平均フィルタを実装しています。

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

entity MovingAverage is
    Port ( clk : in  STD_LOGIC;
           input_signal : in  STD_LOGIC_VECTOR(7 downto 0);
           avg_signal : out  STD_LOGIC_VECTOR(7 downto 0));
end MovingAverage;

architecture Behavioral of MovingAverage is
    signal sum : STD_LOGIC_VECTOR(8 downto 0) := (others => '0');
    signal buffer : array (0 to 3) of STD_LOGIC_VECTOR(7 downto 0) := (others => (others => '0'));
begin
    process(clk)
    variable index : integer := 0;
    begin
        if rising_edge(clk) then
            sum <= sum + input_signal - buffer(index);
            buffer(index) <= input_signal;
            index := (index + 1) mod 4;
        end if;
    end process;
    avg_signal <= sum(8 downto 1);
end Behavioral;

上記のコードは、入力信号をbufferという配列に保存しています。

そして、新しい信号が入ってくるたびに、古い信号をsumから引き、新しい信号をsumに加えて、平均値を計算しています。

出力としては、sumの上位8ビットをavg_signalとして出力しています。

このコードの実行により、信号の急激な変動を滑らかにする効果が期待できます。

たとえば、センサからのノイジーな信号を受け取る場合や、ADC(アナログ-デジタル変換器)からのデジタル値を滑らかにしたい場合にこのような移動平均フィルタを利用すると効果的です。

次に、実際にこのコードをシミュレーションして動作を確認すると、急激な信号変動が入力された際にも、出力されるavg_signalは滑らかに変動することが観察できます。

これにより、デジタルシステム内での信号処理が効果的に行われることが確認できます。

○サンプルコード10:高度なテストベンチの利用

VHDLシミュレーションで重要なのは、デザインした回路が想定通りに動作するかを確認するためのテストベンチの利用です。

テストベンチはVHDLで記述されたシミュレーション専用のコードで、実際のハードウェア環境をエミュレートします。

ここでは、高度なテストベンチの実例とその詳細な解説を行います。

このコードでは、テストベンチを使って複雑なシミュレーション環境を構築し、特定の入力条件下での回路の動作をチェックするコードを表しています。

この例では、ランダムな入力パターンを生成して回路のロバスト性をテストしています。

-- 高度なテストベンチの例
LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;

ENTITY testBench IS
END testBench;

ARCHITECTURE behavior OF testBench IS 
    SIGNAL testSignal : std_logic_vector(7 DOWNTO 0);
BEGIN
    -- ランダムなテストパターンを生成
    PROCESS
    BEGIN
        testSignal <= std_logic_vector(TO_UNSIGNED(RANDOM mod 256, 8));
        WAIT FOR 10 ns;
    END PROCESS;

    -- ここに回路のインスタンスとシミュレーションのコードを追加
    -- ...

END behavior;

テストベンチの主要な部分は、ランダムな8ビットの値を生成するプロセスです。

このプロセスは、10nsごとに新しいランダムな値をtestSignalに割り当てます。

このようにして、連続したランダムな入力値を回路に供給することで、さまざまなシチュエーションでの動作を確認できます。

このテストベンチを使用すると、回路が異なる入力パターンで正確に動作するかを網羅的にテストすることができます。

特に、エッジケースや予期しない入力パターンでの回路の動作を確認するのに役立ちます。

実際にシミュレーションを実行すると、ランダムに生成された入力値に対して回路が正常に動作するかを確認できます。

この方法は、手動でテストパターンを作成するよりも効率的に多くのシナリオをカバーすることができます。

●注意点と対処法

高度なテストベンチを使用する際の注意点は、ランダムな入力値が必ずしも実際の使用シーンを反映していない場合があることです。

そのため、ランダムテストだけでなく、特定のシナリオを再現するテストケースも組み合わせることが推奨されます。

また、ランダムなテストパターンを大量に生成する場合、シミュレーションの実行時間が長くなる可能性があるため、その点も考慮が必要です。

●カスタマイズ方法:VHDLシミュレーションの拡張手法

VHDLシミュレーションはカスタマイズが容易であり、さまざまな拡張手法が考えられます。

例えば、外部ファイルからテストパターンを読み込んでシミュレーションする方法、特定の条件下でのみテストを実行する条件分岐の追加など、ニーズに応じたカスタマイズが可能です。

このようなカスタマイズを行うことで、更に効率的なテストや、特定のシチュエーションに特化したテストが実現可能となります。

まとめ

VHDLシミュレーションは、回路設計の正確性を確認するための非常に重要なツールです。

本ガイドでは、VHDLシミュレーションの基本から高度なテストベンチの作成方法までを詳細に解説しました。

これらの知識を活用して、効率的かつ正確な回路設計を目指しましょう。