VHDLアーキテクチャ完全解説!初心者向け10選サンプルコード

VHDLアーキテクチャの初心者向け解説とサンプルコードイメージVHDL
この記事は約22分で読めます。

 

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

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

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

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

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

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

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

はじめに

VHDLはデジタルシステムを設計・シミュレートするためのハードウェア記述言語です。

この記事では、VHDLの「アーキテクチャ」に焦点を当て、初心者でも理解できるように10のサンプルコードを用いて詳しく解説します。

VHDLのアーキテクチャを通じて、デジタルシステムの設計における基本から応用までの知識を深めることができます。

●VHDLアーキテクチャの基礎

○アーキテクチャの概要

VHDLにおけるアーキテクチャは、エンティティの振る舞いを記述する部分です。

エンティティは「何をするのか」を定義し、アーキテクチャは「どのようにそれを実現するのか」を記述します。

○基本的な記述方法

アーキテクチャは次のように記述されます。

architecture アーキテクチャ名 of エンティティ名 is
-- 宣言部
begin
-- 本体部
end architecture アーキテクチャ名;

●VHDLアーキテクチャのサンプルコード

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

このコードでは、単純なアーキテクチャの基本形を紹介しています。

この例では、エンティティとしてmy_entityを定義し、そのアーキテクチャをmy_archとして記述しています。

entity my_entity is
end entity my_entity;

architecture my_arch of my_entity is
begin
end architecture my_arch;

このコードは特定の動作を持たないため、具体的な実行結果は出力されません。

主に構造の理解のためのサンプルです。

○サンプルコード2:信号の使用例

このコードでは、信号を使って入力を出力に接続するコードを紹介しています。

この例では、input_signaloutput_signalに接続しています。

entity signal_entity is
    port(
        input_signal : in bit;
        output_signal : out bit
    );
end entity signal_entity;

architecture signal_arch of signal_entity is
begin
    output_signal <= input_signal;
end architecture signal_arch;

input_signalに与えられた値がoutput_signalにそのまま出力されます。

○サンプルコード3:プロセスの利用

VHDLにおけるプロセスは、一連の命令を順番に実行するための仕組みです。

通常のプログラム言語のサブルーチンや関数に似た概念と言えるでしょう。

このセクションでは、VHDLのプロセスの基本的な利用方法とその特徴を紹介します。

このコードではプロセスを使って簡単なフリップフロップ回路を実装する例を紹介しています。

この例では、クロック信号が立ち上がるタイミングで、D入力がQ出力に転送される機能を持つフリップフロップを設計しています。

entity flipflop is
    Port ( clk : in  STD_LOGIC;
           D   : in  STD_LOGIC;
           Q   : out STD_LOGIC);
end flipflop;

architecture Behavioral of flipflop is
begin
    process(clk) -- クロック信号でトリガーされるプロセス
    begin
        if rising_edge(clk) then -- クロックの立ち上がりエッジで
            Q <= D; -- Dの値をQに転送
        end if;
    end process;
end Behavioral;

このコードの解説をします。まず、entity部ではフリップフロップの入出力ポートを定義しています。

次に、architecture部で実際の動作を定義しています。

ここで重要なのは、process文です。

このprocess文内で、clk信号の立ち上がりエッジを検出し、その時点のDの値をQに転送する動作を記述しています。

当然ながら、実際のハードウェアで動作させる場合、このフリップフロップは非常に高速に動作します。

そのため、例えば1秒間に数百万回、Dの値がQに転送されることになります。

VHDLにおけるプロセスは、シーケンシャルな動作の記述に適しています。

例として挙げたフリップフロップの動作も、実際のハードウェアの動作をシミュレートする上で、非常に効果的に表現することができます。

一方、並列的な動作を記述する場合は、プロセス外での信号代入などが用いられます。

このフリップフロップを実際に動作させた場合、クロック信号の立ち上がりごとにDの現在の値がQに出力されます。

例えば、Dが1であれば、Qも1となり、Dが0であれば、Qも0となります。

○サンプルコード4:条件分岐の例

VHDLのアーキテクチャで条件分岐を行う場合、多くの状況でif文を利用します。

if文を使うことで、特定の条件が満たされたときにのみ、特定の処理を行うことができるようになります。

下記のサンプルコードでは、信号input_signalの値が1のとき、output_signalを1に、それ以外の場合は0に設定するという簡単な条件分岐を示しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity condition_example is
    Port ( input_signal : in STD_LOGIC;
           output_signal : out STD_LOGIC);
end condition_example;

architecture Behavioral of condition_example is
begin
    process(input_signal)
    begin
        if input_signal = '1' then
            output_signal <= '1';
        else
            output_signal <= '0';
        end if;
    end process;
end Behavioral;

このコードではinput_signalを使って条件分岐を行っています。

具体的には、input_signalの値が1の場合、output_signalに1を割り当て、それ以外の場合は0を割り当てています。

この例を実際にVHDLのシミュレーション環境で動かすと、input_signalが1の場合、output_signalが1になることを確認できます。

一方、input_signalが0の場合、output_signalは0となります。

○サンプルコード5:ループの使用

VHDLにおけるループは、同じ処理を繰り返し実行したいときに使用します。

forループやwhileループなどがありますが、ここではforループの基本的な使用例を紹介します。

下記のサンプルコードでは、8ビットの入力信号input_vectorの各ビットを反転して、出力信号output_vectorに割り当てる処理を行っています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity loop_example is
    Port ( input_vector : in STD_LOGIC_VECTOR(7 downto 0);
           output_vector : out STD_LOGIC_VECTOR(7 downto 0));
end loop_example;

architecture Behavioral of loop_example is
begin
    process(input_vector)
    begin
        for i in 0 to 7 loop
            output_vector(i) <= not input_vector(i);
        end loop;
    end process;
end Behavioral;

このコードでは、0から7までのインデックスでinput_vectorの各ビットを反転し、その結果をoutput_vectorに割り当てています。

この例を実際に動かすと、例えばinput_vectorが”10101010″の場合、output_vectorは”01010101″になります。

また、”11110000″の場合、”00001111″となります。

VHDLを使用してデジタル回路の設計を行う際、これらの基本的な構文やサンプルコードは非常に役立ちます。

○サンプルコード6:コンポーネントの利用

VHDLでは、設計を再利用するために、既存のエンティティをコンポーネントとして別のエンティティ内で使用できます。

この方法で、一度定義した回路を、他の部分でも何度も利用することができます。

既存のエンティティを新しいアーキテクチャ内でコンポーネントとして使うための基本的な手順を表すサンプルコードを紹介します。

このコードでは、my_componentというエンティティを使って、新しいエンティティの中でその動作を再現する例を紹介しています。

-- まず、再利用したいエンティティを定義します。
entity my_component is
    Port ( a : in std_logic;
           b : out std_logic);
end entity my_component;

architecture behavior of my_component is
begin
    b <= a;
end architecture behavior;

-- 新しいエンティティ内で上記のエンティティをコンポーネントとして利用します。
entity main is
end entity main;

architecture structure of main is
    -- コンポーネントの宣言
    component my_component
        Port ( a : in std_logic;
               b : out std_logic);
    end component;
    signal test_a : std_logic;
    signal test_b : std_logic;
begin
    -- コンポーネントのインスタンス化
    U1: my_component port map (a => test_a, b => test_b);
end architecture structure;

このサンプルコードでは、まずmy_componentというエンティティを定義しています。

次に、新しいエンティティmainの中で、このmy_componentをコンポーネントとして再利用しています。

この例で、test_aがHIGHの場合、test_bもHIGHになります。

LOWの場合も、test_bがLOWになります。これはmy_componentの動作に従っています。

注意点として、コンポーネントを使用する前にその宣言が必要です。

また、エンティティとコンポーネントの間でポートの名前や順序が異なる場合は、ポートマッピングに注意が必要です。

応用例として、このコンポーネント化された設計を使って、複数の同じ機能を持つ回路を一つのアーキテクチャ内で繰り返し利用することも可能です。

例えば、4ビットのデータバスを持つ回路で、各ビットごとに同じ処理を行いたい場合、その処理をコンポーネントとして定義し、4回インスタンス化することで、効率的に設計を進めることができます。

カスタマイズ例として、コンポーネントの内部に新しい信号や動作を追加して、元のエンティティには影響を与えずに、新しい機能を持たせることもできます。

これにより、モジュラーな設計が可能となり、大規模な回路設計でも管理やデバッグが容易になります。

○サンプルコード7:ジェネリックの使用

ジェネリックとは、VHDLにおいて、エンティティの振る舞いや構造を外部から変更するためのメカニズムの一つです。

これにより、一つのエンティティやアーキテクチャの中で、異なるパラメータや特性を持つ複数のモジュールを生成することができます。

この機能を使うことで、再利用性や柔軟性が向上し、設計の効率が大きく上がります。

例えば、異なるビット幅の加算器を設計する場合、ジェネリックを利用することでビット幅を動的に変更することが可能となります。

このコードではジェネリックを使ってビット幅を可変にした加算器のエンティティを表しています。

この例ではビット幅を指定して、そのビット幅に応じた加算器を生成しています。

entity adder is
    generic (
        WIDTH : integer := 8
    );
    port (
        A : in  std_logic_vector(WIDTH-1 downto 0);
        B : in  std_logic_vector(WIDTH-1 downto 0);
        SUM : out std_logic_vector(WIDTH-1 downto 0)
    );
end entity adder;

architecture behavior of adder is
begin
    -- 加算処理
    SUM <= A + B;
end architecture behavior;

このコードでは、ジェネリックを用いてWIDTHという名前の整数型のパラメータを定義しています。

デフォルトのビット幅は8に設定されていますが、このエンティティを使用する際に異なるビット幅を指定することで、そのビット幅に応じた加算器を簡単に生成できます。

例えば、ビット幅16の加算器をインスタンス化する場合、次のように記述します。

U1 : adder generic map (WIDTH => 16)
     port map (A => A16, B => B16, SUM => SUM16);

このようにジェネリックを利用することで、同じエンティティを基にした異なるビット幅の加算器を作成することができ、コードの再利用性が高まります。

ジェネリックの使用はVHDL設計において非常に強力なツールとなります。

設計時に異なるパラメータや設定を持つモジュールを簡単に生成することが可能となるため、効率的なコードの作成が可能となります。

次に、サンプルコード8に進む前に、ジェネリックの注意点をいくつか挙げます。

まず、ジェネリックを使用する際は、適切なデフォルト値を設定しておくと良いでしょう。

また、ジェネリックの値によっては回路の動作が不安定になる可能性もあるため、注意が必要です。

○サンプルコード8:外部ファイルの読み込み

VHDLでは、シミュレーションや実際の動作をテストする際に外部ファイルを読み込むことがよく行われます。

例えば、ある特定の入力データを持ったテストベンチを実行するときや、FPGAの動作をシミュレーションする場合には、外部のテキストファイルやCSVファイルを読み込んでそのデータを利用することがあります。

ここでは、外部ファイルの読み込み方法を紹介します。

このコードでは、外部のテキストファイルを使ってデータを読み込むコードを紹介しています。

この例では、指定されたパスのテキストファイルを開き、その内容を一行ずつ読み取っています。

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

entity FileRead is
end FileRead;

architecture Behavior of FileRead is
begin
  process
    variable linebuf: line;
    variable txtbuf: string(1 to 256);
    variable len: natural;
    file file_pointer: text open read_mode is "path/to/your/file.txt";
  begin
    while not endfile(file_pointer) loop
      readline(file_pointer, linebuf);
      read(linebuf, txtbuf, len);
      -- ここでtxtbufを使用して何らかの処理を行う
    end loop;
    wait;
  end process;
end Behavior;

このコードを実行すると、指定したテキストファイルの内容が一行ずつtxtbufに読み込まれます。

それを基に、さまざまな処理をコード内で行うことができます。

例えば、読み込んだデータに基づいてシミュレーションを行ったり、FPGAの動作をテストしたりすることが考えられます。

注意点としては、ファイルのパスは正確に指定する必要があります。

また、読み込むテキストファイルの内容によっては、適切なデータ型や変数のサイズを変更する必要があるかもしれません。

例えば、非常に長い行が含まれている場合や、特定のデータ形式を持ったファイルを読み込む場合などです。

応用例としては、外部ファイルを読み込むだけでなく、VHDL内で生成されたデータを外部ファイルに書き出すことも可能です。

これには、writeやwritelineの関数を使用します。

また、CSV形式や特定の区切り文字でデータが整形されている場合には、そのフォーマットに合わせてデータの読み取りや分解を行うコードを追加することが考えられます。

このように、VHDLを用いて外部ファイルの読み込みや書き出しを行うことで、より実用的で柔軟なシミュレーションやテストが可能となります。

初心者の方も、上記のサンプルコードを参考にしながら、手元の環境で試してみると良いでしょう。

○サンプルコード9:関数の定義と利用

VHDLの設計フローでは、しばしば繰り返し使用するロジックや計算を効率的に記述するために関数を用いることが求められます。

関数を使用することで、コードの再利用性が向上し、複雑な設計でも読みやすく保つことが可能になります。

ここでは、VHDLでの関数の定義とその利用方法に焦点を当てて解説していきます。

関数は特定のタスクを実行し、その結果を返す役割を持ちます。

下記のサンプルコードは、2つの整数を受け取り、それらの和を返す簡単な関数を示しています。

-- 関数の定義
function adder(a : integer; b : integer) return integer is
begin
    return a + b;
end function adder;

このコードでは、adderという名前の関数を定義しています。

この例では、整数を2つ引数として受け取り、それらの和を返す機能を持ちます。

関数を定義した後、それを利用して実際の計算やロジックの記述を行うことができます。

下記のサンプルコードは、上記で定義したadder関数を使って、2つの数値の和を計算する例を表しています。

signal result : integer;
...
begin
    result <= adder(3, 5);
end;

この例では、3と5をadder関数に渡し、その結果をresultという名前の信号に割り当てています。

そのため、resultは8という値を持つことになります。

このようにして、関数を使うことで複雑な計算やロジックを簡潔に表現することができます。

また、関数の中身が変更された場合でも、関数を使用している他の部分のコードを変更する必要はありません。これは再利用性とメンテナンス性の向上に寄与します。

また、関数はさまざまな用途に応用することができます。

例えば、複数の信号の平均を計算する関数や、特定の条件下での信号の値を計算する関数など、必要に応じて様々な関数を定義し利用することができます。

また、関数をモジュール化してライブラリとして提供することで、他のVHDLプロジェクトでの再利用を容易にすることも可能です。

このような方法を採用することで、一度定義した関数を何度も使用することができ、効率的な設計が進められます。

総じて、関数はVHDLの設計において非常に重要な要素の一つです。

適切に関数を定義し、その機能を最大限に活用することで、効率的で高品質な設計を実現することができます。

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

VHDLのデザインを正しく動作しているか確認するためには、テストベンチを作成してシミュレーションを行う必要があります。

テストベンチは、実際のハードウェア環境を模倣したもので、VHDLコードの動作を検証するためのものです。

ここでは、テストベンチの基本的な作成方法を学びます。

まず、簡単なANDゲートのVHDLコードを想定して、その動作を検証するテストベンチを作成してみましょう。

-- ANDゲートのVHDLコード
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity AND_gate is
    Port ( A : in STD_LOGIC;
           B : in STD_LOGIC;
           Y : out STD_LOGIC);
end AND_gate;

architecture Behavioral of AND_gate is
begin
    Y <= A and B;
end Behavioral;

上記のコードは、2つの入力A、Bを持ち、出力YにAとBの論理積を出力するANDゲートを定義しています。

次に、このANDゲートの動作を検証するためのテストベンチを作成します。

-- ANDゲートのテストベンチ
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity tb_AND_gate is
end tb_AND_gate;

architecture sim of tb_AND_gate is
    signal A, B : STD_LOGIC;
    signal Y    : STD_LOGIC;

    component AND_gate
        Port ( A : in STD_LOGIC;
               B : in STD_LOGIC;
               Y : out STD_LOGIC);
    end component;

begin
    DUT: AND_gate port map (A, B, Y);

    process
    begin
        A <= '0'; B <= '0'; wait for 10 ns;
        A <= '0'; B <= '1'; wait for 10 ns;
        A <= '1'; B <= '0'; wait for 10 ns;
        A <= '1'; B <= '1'; wait for 10 ns;
        wait;
    end process;
end sim;

このテストベンチのコードでは、まずANDゲートのインスタンスをDUT(Device Under Test)として配置します。

次に、process文を使用して、AとBの入力の組み合わせを変更し、それぞれの状態でのYの出力を観察することができます。

この例を参考に、テストベンチの作成方法を理解した上で、異なるVHDLデザインの動作検証にも応用できることを意識してください。

このテストベンチをシミュレーションすると、AとBの入力に対してYが正確に論理積を出力することが確認できます。

具体的には、AとBが共に’1’の場合のみ、Yが’1’となり、それ以外の入力の組み合わせでは、Yが’0’となることが観察できます。

●VHDLアーキテクチャの注意点

○信号の扱い方

VHDLでの信号の扱い方は、非常に重要です。

信号はハードウェアの物理的な配線やレジスタを表現するためのものであり、正しく扱うことでデザインの意図通りの動作を得ることができます。

信号を使用する際の基本的な注意点として、信号には直接値を代入するのではなく、'<=(シグナル代入演算子)’を使用して値を代入します。

また、信号の更新は非同期的に行われ、プロセスの中で信号の値が変更された場合、その変更は即座には反映されず、プロセスの実行が終了するまで待機されます。

この特性を理解し、意図しない動作やバグを引き起こすことなく、信号を効果的に使用することが重要です。

○変数と信号の違い

VHDLには、変数と信号の2つの主要なデータ型が存在します。

これらは表面上似ているように見えますが、動作や使用方法には重要な違いがあります。

変数は、プロセス内でのみ使用されるもので、プロセスが実行される間にその値が変更される可能性があります。

一方、信号はプロセスの外部でも使用され、その値はプロセスの実行が終了するまで更新されません。

この違いを理解し、適切なデータ型を選択することで、意図した動作を得ることができます。

○構文の注意点

VHDLの構文には、他のプログラミング言語とは異なる特徴やルールがいくつか存在します。

これらのルールを遵守することで、コードのバグを防ぎ、意図した動作を確実に得ることができます。

具体的な注意点としては、VHDLは大文字と小文字を区別しないため、変数や信号の名前において、大文字と小文字を混在させることを避けることが推奨されます。

また、コメントは”–“で始まる行として記述され、その行の終わりまでがコメントとして扱われます。

VHDLの構文やルールを理解することで、効率的かつバグの少ないコードを書くことができます。

●VHDLアーキテクチャのカスタマイズ方法

VHDLは非常に柔軟性が高く、独自の機能やデータ型を追加することで、ユーザーのニーズに合わせたカスタマイズが可能です。

ここでは、オリジナルの関数の作成方法や独自のデータ型の定義方法を解説します。

○オリジナルの関数の作成

VHDLにおける関数は、特定の処理を行った結果を返すためのものであり、複雑な計算や特定の処理を繰り返し使用する場合に非常に役立ちます。

2つの整数の最大値を返す関数の例を紹介します。

function max_val(a: integer; b: integer) return integer is
begin
  if a > b then
    return a;
  else
    return b;
  end if;
end function max_val;

このコードでは、max_valという名前の関数を定義しています。

この例では、2つの整数abを引数として受け取り、大きい方の値を返します。

利用する場合は次のようになります。

signal result: integer;
...
result <= max_val(5, 3);

このコードを使用すると、resultという信号には5という値が代入されます。

○独自のデータ型の定義

VHDLでは、標準のデータ型だけでなく、ユーザー独自のデータ型を定義することもできます。

例えば、3つの整数を持つデータ型を定義する場合の例を紹介します。

type int3 is array(0 to 2) of integer;

このコードでは、int3という名前の新しいデータ型を定義しています。

この例では、3つの整数を持つ配列型としています。

利用する場合は以下のようになります。

signal my_values: int3 := (1, 2, 3);

このコードを使用すると、my_valuesという信号には1, 2, 3という3つの整数が代入されます。

まとめ

VHDLアーキテクチャは、その柔軟性と拡張性により、多岐にわたるデザインや実装が可能となっています。

本記事では、基本的な概念から応用的なサンプルコード、カスタマイズ方法に至るまで、幅広くVHDLアーキテクチャについて解説しました。

この知識を基に、さらに高度なデザインや実装に挑戦することができるでしょう。