読み込み中...

VHDLノンブロッキング代入の完全ガイド!11選の実用コード

初心者が理解しやすいVHDLのノンブロッキング代入の教材イメージ VHDL
この記事は約25分で読めます。

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

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

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

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

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

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

はじめに

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

その中でも、「ノンブロッキング代入」という概念は非常に重要です。

この記事では、VHDLでのノンブロッキング代入を初心者でも理解できるように、11選の実用コードとその詳細な説明を通じて、しっかりと学ぶことができます。

●VHDLノンブロッキング代入の基本

○ノンブロッキング代入とは?

VHDLにおけるノンブロッキング代入は、主に<=オペレータを使用して行います。

この代入方法は、すぐには値が変わらず、すべての代入が終わった後にまとめて更新される特徴があります。

○ブロッキング代入との違い

対照的に、ブロッキング代入は:=オペレータを使用して行います。

この方法での代入は、コードの順番に従って即座に値が更新される特徴があります。

ノンブロッキング代入は同時に多くの信号を更新する際に役立ちますが、ブロッキング代入は逐次的な処理が求められる場面での使用が適しています。

●ノンブロッキング代入の使い方

○サンプルコード1:基本的なノンブロッキング代入

このコードでは基本的なノンブロッキング代入を使って信号の更新を行う方法を表しています。

この例では、信号abの値を交換しています。

signal a, b : std_logic_vector(7 downto 0);
...
process
begin
    a <= b;
    b <= a;
end process;

このコードを実行すると、信号abの値が交換されます。

○サンプルコード2:複数の信号の代入

このコードでは、複数の信号をノンブロッキング代入を使って同時に更新する方法を表しています。

この例では、三つの信号x, y, zの値を順番にシフトしています。

signal x, y, z : std_logic_vector(7 downto 0);
...
process
begin
    x <= y;
    y <= z;
    z <= x;
end process;

このコードを実行すると、信号xyの値に、yzの値に、そしてzは元々のxの値に更新されます。

○サンプルコード3:条件付きノンブロッキング代入

VHDLでのノンブロッキング代入は、特定の条件が満たされた場合にのみ実行される「条件付きノンブロッキング代入」を利用することができます。

これにより、特定の条件下でのみ信号値を更新することが可能となります。

この部分では、条件付きノンブロッキング代入の使用方法について学んでいきます。

条件付きノンブロッキング代入を使用したサンプルコードの一例を紹介します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity condition_example is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           data_in : in STD_LOGIC_VECTOR(7 downto 0);
           enable : in STD_LOGIC;
           data_out : out STD_LOGIC_VECTOR(7 downto 0));
end condition_example;

architecture Behavioral of condition_example is
signal temp_data : STD_LOGIC_VECTOR(7 downto 0);
begin
process(clk, reset)
begin
    -- リセットがアクティブなら
    if reset = '1' then
        temp_data <= "00000000"; 
    elsif rising_edge(clk) then
        if enable = '1' then
            temp_data <= data_in;
        end if;
    end if;
end process;

data_out <= temp_data;

end Behavioral;

このコードでは、enable信号が'1'のときのみ、data_inの値をtemp_dataにノンブロッキング代入しています。

enable'0'の場合、前回のtemp_dataの値が維持されます。

また、reset信号が'1'の場合には、temp_dataが0にリセットされる機能も持っています。

このサンプルコードを利用すると、例えば外部デバイスからのデータを一時的にバッファとして保存する場合などに、enable信号を使ってデータの更新タイミングを制御することができます。

このコードを実行すると、enable信号が'1'のときのみdata_inの値がdata_outに出力されることを確認できます。

逆に、enable信号が'0'の場合、前回のdata_outの値が維持される動作を確認することができます。

○サンプルコード4:ループ内での使用例

VHDLプログラミングにおいて、ループ内でのノンブロッキング代入の使用は非常に一般的です。

特に、配列やベクタといった信号群に対して一括で値を代入したい場合、ループ構造と組み合わせることで、効率的かつ簡潔に記述することができます。

このコードでは、ノンブロッキング代入を使ってループ内で配列の要素に値を代入するコードを表しています。

この例では、10ビットのベクタdata_arrayに、インデックスに対応する値を代入しています。

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

entity loop_example is
    Port ( clk : in STD_LOGIC;
           rst : in STD_LOGIC;
           data_array : out STD_LOGIC_VECTOR (9 downto 0));
end loop_example;

architecture Behavioral of loop_example is
begin
    process(clk, rst)
    begin
        if rst = '1' then
            data_array <= (others => '0');
        elsif rising_edge(clk) then
            for i in data_array'range loop
                data_array(i) <= '1' when i mod 2 = 0 else '0'; -- 偶数インデックスに1を代入
            end loop;
        end if;
    end process;
end Behavioral;

このサンプルコードでは、クロックの立ち上がりエッジが検出されるたびに、ノンブロッキング代入を使用してdata_arrayの各要素に値を代入しています。

特に、偶数インデックスの要素には’1’を、奇数インデックスの要素には’0’を代入するようにしています。

このコードをFPGAやASICに実装し、実際に動作させると、data_arrayの信号は、”1010101010″というパターンを示すでしょう。

この動作は、クロック信号が供給されるたびに繰り返されます。

また、VHDLでは、ノンブロッキング代入を使用すると、全ての代入が同時に行われるため、先に代入された信号の値が後の処理で変更されるというような問題に遭遇することはありません。

これは特に、ループ処理の中で複数の信号に同時に値を代入する際などに有用です。

次に、この方法の応用例として、data_arrayの要素に0から9までの数値を代入する方法を表します。

ループ構造を使用することで、各要素に対応する数値を効率的に代入することが可能となります。

-- 応用例:data_arrayの各要素に0から9までの数値を代入
process(clk, rst)
begin
    if rst = '1' then
        data_array <= (others => '0');
    elsif rising_edge(clk) then
        for i in data_array'range loop
            data_array(i) <= std_logic_vector(to_unsigned(i, data_array'length)); 
        end loop;
    end if;
end process;

この応用例では、to_unsigned関数を使用して、ループのインデックスistd_logic_vectorに変換して、data_arrayの対応する要素に代入しています。

●ノンブロッキング代入の応用例

VHDLのノンブロッキング代入を理解すると、より複雑で実用的なデジタル回路の設計が可能となります。

ここでは、ノンブロッキング代入を活用した具体的な応用例としてのサンプルコードを紹介します。

○サンプルコード5:状態マシンでの使用例

このコードでは、ノンブロッキング代入を使って状態マシンを実装する方法を表しています。

この例では、ノンブロッキング代入を使って状態の遷移を制御しています。

entity state_machine is
-- 必要な入出力ポートを定義
end entity state_machine;

architecture behavior of state_machine is
    type state_type is (s1, s2, s3);
    signal current_state, next_state: state_type;
begin
    process(clk)
    begin
        if rising_edge(clk) then
            current_state <= next_state;
        end if;
    end process;

    process(current_state)
    begin
        case current_state is
            when s1 =>
                -- 条件や動作を記述
                next_state <= s2; -- ノンブロッキング代入で次の状態を設定
            when s2 =>
                -- 条件や動作を記述
                next_state <= s3;
            when others =>
                -- 条件や動作を記述
                next_state <= s1;
        end case;
    end process;
end architecture behavior;

このコードを実行すると、指定した条件に基づいて状態マシンが状態遷移を行います。

具体的には、現在の状態(current_state)に応じて次の状態(next_state)が設定され、クロックの立ち上がりエッジで現在の状態が更新されるという動作をします。

○サンプルコード6:カウンタ回路の設計

このコードでは、ノンブロッキング代入を使用してカウンタ回路を設計する方法を表しています。

この例では、指定したビット数のカウンタがアップカウントする動作をしています。

entity counter is
    port(
        clk : in std_logic;
        rst : in std_logic;
        count : out std_logic_vector(7 downto 0)
    );
end entity counter;

architecture behavior of counter is
    signal count_internal : std_logic_vector(7 downto 0);
begin
    process(clk, rst)
    begin
        if rst = '1' then
            count_internal <= (others => '0');
        elsif rising_edge(clk) then
            count_internal <= count_internal + 1;
        end if;
    end process;

    count <= count_internal;
end architecture behavior;

カウンタの値は、count_internalという内部信号に保持されています。

リセット信号rstがアクティブの場合、カウンタは0に初期化され、それ以外の場合、クロックの立ち上がりエッジごとにカウンタの値が1増加します。

○サンプルコード7:FIFOの設計

FIFO(First In, First Out)は、データを一時的に格納する際に用いられるデータ構造の一つであり、最初に入れられたデータが最初に出てくる特性を持っています。

ここでは、VHDLを用いてノンブロッキング代入を活用したFIFOの設計方法を詳しく紹介します。

ノンブロッキング代入を用いてFIFOを実装する基本的なコードを紹介します。

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

entity FIFO is
    Port ( clk : in STD_LOGIC;
           rst : in STD_LOGIC;
           en : in STD_LOGIC;
           din : in STD_LOGIC_VECTOR(7 downto 0);
           dout : out STD_LOGIC_VECTOR(7 downto 0);
           full : out STD_LOGIC;
           empty : out STD_LOGIC);
end FIFO;

architecture Behavioral of FIFO is
    type fifo_type is array (0 to 255) of STD_LOGIC_VECTOR(7 downto 0);
    signal fifo : fifo_type;
    signal wr_ptr : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
    signal rd_ptr : STD_LOGIC_VECTOR(7 downto 0) := "00000000";
begin
    process(clk, rst)
    begin
        if rst = '1' then
            wr_ptr <= "00000000";
            rd_ptr <= "00000000";
        elsif rising_edge(clk) then
            if en = '1' then
                fifo(conv_integer(wr_ptr)) <= din;  -- ノンブロッキング代入を使用
                wr_ptr <= wr_ptr + 1;
                rd_ptr <= rd_ptr + 1;
            end if;
        end if;
    end process;

    dout <= fifo(conv_integer(rd_ptr));
    full <= '1' when wr_ptr = "11111111" else '0';
    empty <= '1' when rd_ptr = wr_ptr else '0';
end Behavioral;

このコードでは、256個の8ビットデータを保存するFIFOを設計しています。

ノンブロッキング代入を使用して、fifo配列にデータを代入しています。

wr_ptrrd_ptrは、それぞれ書き込みと読み出しの位置を指示するポインタとして機能します。

このFIFOの動作は、en信号が'1'のときにデータが読み込まれ、次のクロックサイクルで読み出されます。

fullおよびempty信号は、FIFOが満杯または空であるかどうかを表すために提供されています。

このFIFOの実行時、en'1'で、データが入力されると、そのデータはfifo配列に格納され、次のクロックサイクルでdoutから出力されます。

また、FIFOが満杯の場合、full信号が'1'になり、空の場合はempty信号が'1'になります。

○サンプルコード8:メモリマッピング

デジタル回路の設計において、メモリマッピングは非常に重要なテクニックの1つと言えます。

VHDLを使用したノンブロッキング代入を駆使して、効率的なメモリマッピングの設計を行うことができます。

このコードでは、VHDLを使ってメモリマッピングを行う方法を表しています。

この例では、4ビットのアドレス空間を持つ簡単なメモリマッピングを設計しています。

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

entity MemoryMapping is
    Port ( address : in  STD_LOGIC_VECTOR(3 downto 0);
           data_in : in  STD_LOGIC_VECTOR(7 downto 0);
           write_enable : in STD_LOGIC;
           data_out : out STD_LOGIC_VECTOR(7 downto 0));
end MemoryMapping;

architecture Behavioral of MemoryMapping is
    type memory_array is array (0 to 15) of STD_LOGIC_VECTOR(7 downto 0);
    signal memory : memory_array;
begin
    process(address, data_in, write_enable)
    begin
        -- メモリへの書き込み処理
        if write_enable = '1' then
            memory(to_integer(address)) <= data_in;
        end if;
        -- メモリからの読み出し処理
        data_out <= memory(to_integer(address));
    end process;
end Behavioral;

このコードでは、4ビットのアドレスを持つメモリアレイを使用しています。

このメモリアレイは、8ビットのデータを持つことができ、合計16個のアドレス位置にデータを格納することができます。

また、書き込みを制御するwrite_enable信号を使って、データの書き込みと読み出しを制御しています。

この設計を行った場合、メモリへのアクセスはノンブロッキング代入を使用して行われ、アドレスが指定された場所にデータが迅速に書き込まれます。

そのため、高速なメモリアクセスが可能となります。

このサンプルコードを使用すると、例えばaddress"0010"data_in"00001111"を入力し、write_enable'1'にすると、メモリの2番地に"00001111"が書き込まれます。

その後、write_enable'0'にしてaddress"0010"を入力すると、data_outから"00001111"が出力されます。

このようにして、VHDLを使用してノンブロッキング代入を駆使したメモリマッピングの設計が行えます。

この技術は、大規模なデジタル回路の設計やFPGAの設計においても非常に役立ちます。

○サンプルコード9:同期回路の設計

デジタル回路の設計において、同期回路は非常に重要な役割を果たします。

VHDLを用いて、ノンブロッキング代入を活用して同期回路を設計する方法を学ぶことで、より効率的なデジタルシステムを構築するための基盤を築くことができます。

このコードではVHDLを使ってノンブロッキング代入を活用し、同期回路の設計を表しています。

この例ではクロック信号に同期してデータを更新する同期回路を設計しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity sync_circuit is
    Port ( clk : in STD_LOGIC;
           input : in STD_LOGIC_VECTOR(7 downto 0);
           output : out STD_LOGIC_VECTOR(7 downto 0));
end sync_circuit;

architecture Behavior of sync_circuit is
begin
    process(clk)
    begin
        if rising_edge(clk) then
            output <= input;  -- ノンブロッキング代入を用いた同期
        end if;
    end process;
end Behavior;

このコードでは、クロック信号clkの立ち上がりエッジで、入力inputのデータを出力outputに同期的に更新しています。

このとき、ノンブロッキング代入の特性を活用して、outputの更新が行われるタイミングを制御しています。

この同期回路を使用することで、データの伝播遅延や競合などの問題を防ぐことができます。

特に大規模なデジタル回路の設計では、このような同期技術は不可欠です。

このコードを実際にFPGAやASICのツールに取り込んでシミュレーションを実行すると、inputの変更がclkの立ち上がりエッジと同期してoutputに反映されることを確認できます。

注意点として、rising_edge(clk)関数を使用することで、クロック信号の立ち上がりエッジを検出しています。

この関数を使用しないと、クロックのエッジを正確に検出することが難しくなるので注意が必要です。

応用例として、この同期回路を基に、さまざまなデジタルロジックを組み合わせることで、より高度な機能を持つ回路を設計することができます。

たとえば、入力データを一定の条件下で処理するフィルタ回路や、特定のパターンを検出するロジックなど、さまざまな応用が考えられます。

カスタマイズ例として、出力信号に遅延を持たせるためのレジスタを追加することで、遅延同期回路を実装することも可能です。

これにより、特定の条件下での出力信号の挙動を制御することができます。

○サンプルコード10:非同期回路の設計

VHDLにおける非同期回路の設計は、デジタル回路の中で特に難易度が高いとされる部分です。

非同期回路は、同期回路とは異なり、クロック信号に依存せずに動作します。

今回は、ノンブロッキング代入を用いた非同期回路の基本的な設計に関して詳しく解説します。

このコードでは、VHDLを使って非同期回路の基本形を表しています。

この例では、非同期入力信号に対応するためのラッチを実装しています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity async_latch is
    Port ( D : in  STD_LOGIC;
           EN : in  STD_LOGIC;
           Q : out  STD_LOGIC);
end async_latch;

architecture Behavioral of async_latch is
begin
process(D, EN)
begin
    -- ラッチの動作を定義
    if EN = '1' then
        Q <= D; -- ノンブロッキング代入
    end if;
end process;
end Behavioral;

上記のコードにおいて、非同期入力信号Dが有効化信号ENによってQに代入されるラッチが実装されています。具体的には、ENが’1’のとき、Dの値がQに代入されます。

ここで、ノンブロッキング代入が用いられていることに注目してください。

このラッチの動作は次の通りです。

ENが’1’になった際、Dの値が即座にQへと反映される。

この動作は、非同期回路の特徴としてクロックに依存しないため、クロックのタイミングに影響されることなく動作します。

注意点として、非同期回路の設計時には、タイミングの問題やラッチの振る舞いに特に注意が必要です。

特に、非同期信号が同期信号と交差する場面では、メタスタビリティ(不安定状態)が生じるリスクがあります。

○サンプルコード11:非同期リセットを持つカウンタ

このコードでは、非同期リセット機能を持つカウンタの実装を表しています。

この例では、クロックに同期してカウントアップする動作と、非同期でリセットする動作を持つカウンタを実装しています。

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

entity async_reset_counter is
    Port ( CLK : in STD_LOGIC;
           RST : in STD_LOGIC;
           Q : out STD_LOGIC_VECTOR(3 downto 0));
end async_reset_counter;

architecture Behavioral of async_reset_counter is
signal count : STD_LOGIC_VECTOR(3 downto 0) := "0000";
begin
process(CLK, RST)
begin
    -- 非同期リセットの動作
    if RST = '1' then
        count <= "0000"; -- ノンブロッキング代入
    elsif rising_edge(CLK) then
        count <= count + 1; -- カウントアップ
    end if;
end process;

Q <= count;
end Behavioral;

このコードでは、非同期リセット信号RSTが’1’になった場合、カウンタcountが即座に0にリセットされます。

それ以外の場合、クロックの立ち上がりエッジでカウンタが1ずつ増加します。

ここでも、非同期リセット動作のためにノンブロッキング代入が用いられています。

非同期リセットの動作により、任意のタイミングでカウンタをリセットすることができます。

しかし、前述の通り、非同期信号の取り扱いには注意が必要であり、特に同期信号との交差や近接する場合には、設計に細心の注意が求められます。

●注意点と対処法

VHDLでのノンブロッキング代入を使用する際、初心者を含む多くのエンジニアが陥る可能性があるトラブルや疑問点について説明します。

また、それらの問題を効果的に回避するための具体的な対処法も合わせて紹介します。

○ノンブロッキング代入のタイミング問題

このコードではノンブロッキング代入のタイミングを誤解して、予期しない結果になる可能性があるシチュエーションを表しています。

この例では、clkの立ち上がりエッジでaの値がbにノンブロッキング代入されますが、その後すぐにaの値が変更されてしまう場面を取り上げています。

process(clk)
begin
    if rising_edge(clk) then
        b <= a;  -- ノンブロッキング代入
        a <= a + 1;  -- aの値を更新
    end if;
end process;

このようなコードでは、bに代入されるaの値が予期しないものになる恐れがあります。

この問題を回避するためには、aの更新を別のプロセスで行う、またはbへの代入を最後に移動するなどの工夫が必要です。

○複数のソースからの代入

ノンブロッキング代入では、同じ信号に対して複数の場所から代入を行うことは避ける必要があります。

process(clk)
begin
    if rising_edge(clk) then
        if en = '1' then
            data_out <= data_in1;  -- ノンブロッキング代入
        else
            data_out <= data_in2;  -- ノンブロッキング代入
        end if;
    end if;
end process;

このようなコードでは、enの信号に応じてdata_outdata_in1data_in2が代入されるように見えますが、両方の代入が同時に評価されるため、結果は不定となってしまいます。

これを回避するためには、代入元の信号を一意にするか、異なる条件下での代入を明確に区別する必要があります。

●カスタマイズのコツ

VHDLのノンブロッキング代入を更に効果的に活用するためのカスタマイズのコツについて紹介します。

○ノンブロッキング代入と関数の組み合わせ

ノンブロッキング代入とVHDLの関数を組み合わせることで、より複雑な処理や再利用性の高いコードを作成することができます。

下記のサンプルコードは、関数calculateを用いて、ノンブロッキング代入での計算を行っています。

function calculate(x : integer) return integer is
begin
    return x * 2 + 1;
end function calculate;

process(clk)
begin
    if rising_edge(clk) then
        result <= calculate(input_data);  -- ノンブロッキング代入を使用した関数の呼び出し
    end if;
end process;

このコードでは、関数calculateを使ってinput_dataの2倍に1を足した結果をresultに代入しています。

関数を用いることで、複雑な計算や処理をモジュール化して再利用することが容易になります。

まとめ

VHDLのノンブロッキング代入は、デジタル回路設計において非常に強力なツールです。

しかし、その特性を正しく理解し、適切に使用しないと予期しない動作やバグを生じる可能性があります。

本記事では、その基本的な使用方法や応用例、注意点と対処法を紹介しました。

これを参考に、より高品質なデジタル回路の設計を行ってください。