読み込み中...

VHDLで遅延属性を活用する12の方法

遅延属性 徹底解説 VHDL
この記事は約40分で読めます。

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

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

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

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

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

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

●VHDLの遅延属性とは?

デジタル回路設計において、信号の伝搬遅延は避けられない現実です。

VHDLという強力なハードウェア記述言語では、この現実に対応するための機能として遅延属性が用意されています。

遅延属性は、回路内の信号伝搬に要する時間を模擬し、より現実的な回路動作をシミュレーションや合成時に反映させる重要な要素です。

初めてVHDLに触れる方にとって、遅延属性は少し難解に感じるかもしれません。

しかし、適切に使用すれば、信頼性の高い回路設計が可能になります。

本記事では、VHDLの遅延属性について、基礎から応用まで詳しく解説していきます。

○遅延属性の基礎知識

遅延属性は、信号の変化が実際に反映されるまでの時間を表現します。

デジタル回路において、スイッチングやゲート通過に要する時間は無視できません。

特に高速動作や精密なタイミング制御が求められる場合、遅延を考慮した設計は不可欠です。

VHDLでは、主に次の遅延属性が使用されます。

  1. トランスポート遅延(transport delay)
  2. イナーシャル遅延(inertial delay)

トランスポート遅延は、信号の変化をそのまま指定時間後に伝播させます。

一方、イナーシャル遅延は、短時間のパルスや連続した変化を除去する効果があります。

実際の回路では、配線やゲートによる遅延が存在します。

遅延属性を適切に使用することで、シミュレーション時により現実に近い動作を再現できます。

また、合成ツールに対して遅延に関する制約を与えることもできます。

○遅延属性の設定方法

VHDLで遅延属性を設定する方法を見ていきましょう。

基本的な構文は次のようになります。

signal_name <= value after delay_time;

ここで、signal_nameは対象の信号名、valueは新しい値、delay_timeは遅延時間を表します。

遅延時間の単位には、ns(ナノ秒)、ps(ピコ秒)などが使用できます。

例えば、10ns後にHIGHになる信号を定義する場合

signal slow_signal : std_logic;
...
slow_signal <= '1' after 10 ns;

トランスポート遅延とイナーシャル遅延の違いは、キーワードを用いて指定します。

signal_name <= transport value after delay_time;  -- トランスポート遅延
signal_name <= value after delay_time;            -- デフォルトはイナーシャル遅延

より複雑な遅延パターンを表現したい場合は、波形指定を使用できます。

signal complex_signal : std_logic;
...
complex_signal <= '0', '1' after 5 ns, '0' after 10 ns, '1' after 15 ns;

遅延属性の設定は、シミュレーション時の動作に影響を与えますが、実際の合成結果には直接反映されないことに注意が必要です。

合成ツールに遅延情報を伝えるには、別途制約ファイルなどを用いる必要があります。

○クロックと遅延の関係性

デジタル回路設計において、クロック信号と遅延の関係は非常に重要です。

クロック信号は回路全体の動作タイミングを制御する要となるため、クロックに関連する遅延を適切に管理する必要があります。

クロックスキューと呼ばれる現象は、クロック信号が回路の異なる部分に到達する時間差を指します。

大規模な回路では、このスキューが無視できないレベルになることがあります。

VHDLの遅延属性を使用することで、クロックスキューの影響をシミュレーションで確認できます。

例えば、クロックツリーの遅延を模擬する場合

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity clock_tree is
    Port ( clk_in : in STD_LOGIC;
           clk_out1, clk_out2 : out STD_LOGIC);
end clock_tree;

architecture Behavioral of clock_tree is
begin
    clk_out1 <= clk_in after 2 ns;  -- 2nsの遅延
    clk_out2 <= clk_in after 3 ns;  -- 3nsの遅延
end Behavioral;

このような表現により、クロック信号の伝搬遅延の違いをシミュレーションで確認できます。

実際の設計では、クロックスキューを最小限に抑えるために、専用のクロック分配ネットワークを使用したり、位相同期ループ(PLL)を用いてクロックを調整したりします。

遅延属性はまた、セットアップタイムとホールドタイムの検証にも役立ちます。

フリップフロップのデータ入力に遅延を加えることで、タイミング違反を意図的に発生させ、回路の動作限界をテストできます。

process(clk)
begin
    if rising_edge(clk) then
        q <= d after 1 ns;  -- データ遅延を加える
    end if;
end process;

このように、VHDLの遅延属性を活用することで、クロックに関連する様々なタイミング問題をシミュレーションレベルで検証できます。

ただし、実際の回路動作では、配線遅延やデバイスの特性によって予期せぬ遅延が生じる可能性があります。

そのため、最終的な検証は実機テストや専用のタイミング解析ツールを用いて行う必要があります。

●遅延属性を使った実践的な回路設計

遅延属性の基本を理解したところで、具体的な回路設計における活用例を見ていきましょう。

実際の設計では、単純な遅延だけでなく、複数の信号間の相互作用や、様々な条件下での動作を考慮する必要があります。

○サンプルコード1:シンプルな遅延回路の実装

まずは、最も基本的な遅延回路の例から始めます。

この回路は入力信号を指定した時間だけ遅延させて出力します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity delay_circuit is
    Port ( input : in STD_LOGIC;
           output : out STD_LOGIC);
end delay_circuit;

architecture Behavioral of delay_circuit is
begin
    output <= input after 10 ns;
end Behavioral;

この回路では、入力信号がそのまま10ナノ秒遅れて出力されます。

シンプルですが、この基本的な構造は、より複雑な遅延処理の基礎となります。

実行すると、入力信号が変化してから10ナノ秒後に出力信号が変化します。

例えば、入力が0から1に変わった場合、出力は10ナノ秒後に0から1に変化します。

同様に、入力が1から0に変わった場合も、10ナノ秒後に出力が1から0に変化します。

○サンプルコード2:遅延属性を使ったクロック同期

次に、クロック信号を用いた同期回路に遅延を組み込む例を見てみましょう。

この回路では、入力信号をクロックに同期させつつ、一定の遅延を加えます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity sync_delay_circuit is
    Port ( clk : in STD_LOGIC;
           input : in STD_LOGIC;
           output : out STD_LOGIC);
end sync_delay_circuit;

architecture Behavioral of sync_delay_circuit is
    signal internal_signal : STD_LOGIC;
begin
    process(clk)
    begin
        if rising_edge(clk) then
            internal_signal <= input;
        end if;
    end process;

    output <= internal_signal after 5 ns;
end Behavioral;

この回路では、入力信号をクロックの立ち上がりエッジで内部信号に取り込み、その内部信号を5ナノ秒遅延させて出力しています。

クロック同期と遅延の組み合わせにより、タイミング制御がより精密になります。

実行すると、クロックの立ち上がりエッジで内部信号が更新され、その5ナノ秒後に出力信号が変化します。

例えば、クロックの立ち上がりで入力が1になった場合、内部信号はすぐに1になりますが、出力信号は5ナノ秒後に1になります。

○サンプルコード3:リセット信号の遅延管理

最後に、リセット信号の遅延管理を行う回路の例を見てみましょう。

リセット信号は回路全体に影響を与えるため、適切な遅延管理が重要です。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity reset_delay_circuit is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           input : in STD_LOGIC;
           output : out STD_LOGIC);
end reset_delay_circuit;

architecture Behavioral of reset_delay_circuit is
    signal delayed_reset : STD_LOGIC;
begin
    delayed_reset <= reset after 2 ns;

    process(clk, delayed_reset)
    begin
        if delayed_reset = '1' then
            output <= '0';
        elsif rising_edge(clk) then
            output <= input;
        end if;
    end process;
end Behavioral;

この回路では、リセット信号に2ナノ秒の遅延を加えています。

遅延されたリセット信号を用いることで、リセットのタイミングをより細かく制御できます。

実行すると、リセット信号が’1’になってから2ナノ秒後に、出力信号が’0’にリセットされます。

リセットが解除された後は、クロックの立ち上がりエッジで入力信号が出力に反映されます。

この遅延により、リセット信号の伝搬遅延を考慮した、より安定した回路動作が期待できます。

○サンプルコード4:カウンタのディレイ設定

カウンタは多くのデジタル回路で使用される基本的な構成要素です。

遅延属性を活用することで、より現実的なカウンタの動作をシミュレーションできます。

ここでは、遅延を組み込んだ4ビットカウンタの例を紹介します。

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

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

architecture Behavioral of delayed_counter is
    signal internal_count : unsigned(3 downto 0);
    signal delayed_count : STD_LOGIC_VECTOR(3 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            internal_count <= (others => '0');
        elsif rising_edge(clk) then
            if enable = '1' then
                internal_count <= internal_count + 1;
            end if;
        end if;
    end process;

    -- 各ビットに異なる遅延を設定
    delayed_count(0) <= std_logic_vector(internal_count)(0) after 1 ns;
    delayed_count(1) <= std_logic_vector(internal_count)(1) after 2 ns;
    delayed_count(2) <= std_logic_vector(internal_count)(2) after 3 ns;
    delayed_count(3) <= std_logic_vector(internal_count)(3) after 4 ns;

    count <= delayed_count;
end Behavioral;

この回路では、内部カウンタの値を更新した後、各ビットに異なる遅延を設定しています。

最下位ビットから順に1ns、2ns、3ns、4nsの遅延を加えています。

実行すると、カウンタの値が変化すると、各ビットが異なるタイミングで更新されます。

例えば、カウンタが3(0011)から4(0100)に変わる場合

  1. 最初に、内部カウンタが4に更新されます。
  2. 1ns後に、最下位ビット(bit 0)が0に変化します。
  3. 2ns後に、2番目のビット(bit 1)が0に変化します。
  4. 3ns後に、3番目のビット(bit 2)が1に変化します。
  5. 4ns後に、最上位ビット(bit 3)が0のまま変化しません。

この遅延設定により、実際の回路で発生する可能性があるビット間の遅延差を模擬しています。

特に高速動作時や、カウンタ出力を使用する回路の設計時に、この遅延差を考慮することが重要です。

●遅延属性のシミュレーションと実機適用

VHDLにおける遅延属性の理解を深めたところで、実際のシミュレーションと実機適用について掘り下げていきましょう。

理論と実践の橋渡しとなる重要な段階です。

シミュレーションでは、回路の動作を事前に確認し、潜在的な問題を洗い出します。

一方、実機適用では、シミュレーションでは予測できなかった現実世界の制約に直面することがあります。

○サンプルコード5:遅延回路のシミュレーション方法

遅延回路のシミュレーションは、設計した回路が意図通りに動作するか確認する重要なステップです。

VHDLのテストベンチを使用して、遅延を含む回路の挙動を検証できます。

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

entity delay_circuit_tb is
end delay_circuit_tb;

architecture Behavioral of delay_circuit_tb is
    signal input, output : STD_LOGIC;
    constant DELAY_TIME : time := 10 ns;

    component delay_circuit
        Port ( input : in STD_LOGIC;
               output : out STD_LOGIC);
    end component;

begin
    UUT: delay_circuit port map (input => input, output => output);

    stimulus: process
    begin
        input <= '0';
        wait for 20 ns;
        input <= '1';
        wait for 20 ns;
        input <= '0';
        wait for 20 ns;
        input <= '1';
        wait;
    end process;

end Behavioral;

テストベンチでは、入力信号を変化させ、出力信号の遅延を観察します。

シミュレーション結果を波形ビューアで確認することで、遅延が正しく実装されているか視覚的に判断できます。

実行すると、波形ビューアでは、入力信号の変化から10ns後に出力信号が変化することが確認できます。

例えば、20ns時点で入力が0から1に変化すると、30ns時点で出力が0から1に変化します。

同様に、40ns時点での入力変化は50ns時点で出力に反映されます。

○サンプルコード6:FPGAでの遅延属性の適用例

FPGAでVHDLの遅延属性を適用する際は、合成ツールの制約や実際のハードウェア特性を考慮する必要があります。

ここでは、FPGAでの遅延制御を意識した回路例をみてみましょう。

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

entity fpga_delay_control is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           input : in STD_LOGIC;
           output : out STD_LOGIC);
end fpga_delay_control;

architecture Behavioral of fpga_delay_control is
    signal delay_chain : STD_LOGIC_VECTOR(3 downto 0);
    attribute keep : string;
    attribute keep of delay_chain : signal is "true";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            delay_chain <= (others => '0');
        elsif rising_edge(clk) then
            delay_chain <= delay_chain(2 downto 0) & input;
        end if;
    end process;

    output <= delay_chain(3);
end Behavioral;

実行すると、FPGAに実装すると、入力信号がクロック4サイクル分遅延して出力に現れます。

例えば、入力が1になってから4クロックサイクル後に出力が1になります。

ただし、実際の遅延時間はFPGAのクロック周波数に依存します。

○サンプルコード7:複数信号の遅延調整テクニック

複数の信号を同時に扱う場合、各信号の遅延を個別に制御することが求められます。

ここでは、異なる遅延を持つ複数信号を扱う例を紹介します。

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

entity multi_delay_circuit is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           input_a : in STD_LOGIC;
           input_b : in STD_LOGIC;
           output_a : out STD_LOGIC;
           output_b : out STD_LOGIC);
end multi_delay_circuit;

architecture Behavioral of multi_delay_circuit is
    signal delay_a : STD_LOGIC_VECTOR(2 downto 0);
    signal delay_b : STD_LOGIC_VECTOR(4 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            delay_a <= (others => '0');
            delay_b <= (others => '0');
        elsif rising_edge(clk) then
            delay_a <= delay_a(1 downto 0) & input_a;
            delay_b <= delay_b(3 downto 0) & input_b;
        end if;
    end process;

    output_a <= delay_a(2);
    output_b <= delay_b(4);
end Behavioral;

実行すると、クロック信号に同期して、input_aは3クロックサイクル後にoutput_aに、input_bは5クロックサイクル後にoutput_bに反映されます。

例えば、input_aが1になってから3クロックサイクル後にoutput_aが1になり、input_bが1になってから5クロックサイクル後にoutput_bが1になります。

○サンプルコード8:階層型設計における遅延の実装

大規模な設計では、階層構造を用いて回路を管理します。

階層型設計における遅延の実装例を見てみましょう。

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

entity top_level_delay is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           input : in STD_LOGIC;
           output : out STD_LOGIC);
end top_level_delay;

architecture Behavioral of top_level_delay is
    signal intermediate : STD_LOGIC;

    component delay_stage
        Port ( clk : in STD_LOGIC;
               reset : in STD_LOGIC;
               input : in STD_LOGIC;
               output : out STD_LOGIC);
    end component;

begin
    stage1: delay_stage port map (clk => clk, reset => reset, input => input, output => intermediate);
    stage2: delay_stage port map (clk => clk, reset => reset, input => intermediate, output => output);
end Behavioral;

-- Delay stage component
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity delay_stage is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           input : in STD_LOGIC;
           output : out STD_LOGIC);
end delay_stage;

architecture Behavioral of delay_stage is
    signal delay_reg : STD_LOGIC_VECTOR(1 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            delay_reg <= (others => '0');
        elsif rising_edge(clk) then
            delay_reg <= delay_reg(0) & input;
        end if;
    end process;

    output <= delay_reg(1);
end Behavioral;

実行すると、トップレベルの回路では、入力信号が2つの遅延ステージを経由して出力に到達します。

各ステージで2クロックサイクルの遅延があるため、合計で4クロックサイクルの遅延が発生します。

例えば、入力が1になってから4クロックサイクル後に出力が1になります。

階層型設計を用いることで、複雑な遅延パターンを管理しやすくなり、再利用性も向上します。

各階層で適切な遅延を設定することで、全体としての遅延特性を細かく制御できます。

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

遅延属性の使用には様々な落とし穴があります。

経験豊富なエンジニアでも、思わぬエラーに遭遇することがあります。

代表的なエラーとその対処法を見ていきましょう。

○遅延に起因する動作不良

遅延に起因する動作不良は、設計者を悩ませる厄介な問題です。

主な原因として、タイミング違反、グリッチ、メタステーブル状態などが挙げられます。

タイミング違反は、信号が期待されるタイミングで到達しない場合に発生します。

例えば、フリップフロップのセットアップ時間やホールド時間を満たさない場合、不定な値が取り込まれる可能性があります。

対策として、クリティカルパスの最適化が効果的です。

次のコードは、パイプライン化によりクリティカルパスを分割する例です。

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

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

architecture Behavioral of pipeline_example is
    signal stage1, stage2 : STD_LOGIC_VECTOR(7 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            stage1 <= (others => '0');
            stage2 <= (others => '0');
            output <= (others => '0');
        elsif rising_edge(clk) then
            stage1 <= input;
            stage2 <= stage1;
            output <= stage2;
        end if;
    end process;
end Behavioral;

実行すると、入力信号は3つのステージを経て出力に到達します。

各ステージ間でレジスタを挿入することで、クリティカルパスが分割され、より高速な動作が可能になります。

例えば、100MHzで動作していた回路が、パイプライン化により300MHzで動作可能になる場合があります。

○シミュレーションと実機動作の差異解消法

シミュレーションでは問題なく動作する回路が、実機では予期せぬ動作をすることがあります。

主な原因は、シミュレーションモデルと実際のハードウェアの違いです。

例えば、シミュレーションでは無視されていた配線遅延が、実機では無視できない影響を及ぼす場合があります。

また、温度や電圧の変動、製造ばらつきなども実機特有の要因です。

差異を解消するためには、より現実的なシミュレーションモデルの使用や、実機でのタイミング測定が有効です。

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

entity timing_measurement is
    Port ( clk : in STD_LOGIC;
           reset : in STD_LOGIC;
           trigger : in STD_LOGIC;
           measured_signal : in STD_LOGIC;
           result : out STD_LOGIC_VECTOR(15 downto 0));
end timing_measurement;

architecture Behavioral of timing_measurement is
    signal counter : unsigned(15 downto 0);
    signal measurement_active : STD_LOGIC;
begin
    process(clk, reset)
    begin
        if reset = '1' then
            counter <= (others => '0');
            measurement_active <= '0';
            result <= (others => '0');
        elsif rising_edge(clk) then
            if trigger = '1' and measurement_active = '0' then
                measurement_active <= '1';
                counter <= (others => '0');
            elsif measurement_active = '1' then
                if measured_signal = '1' then
                    result <= std_logic_vector(counter);
                    measurement_active <= '0';
                else
                    counter <= counter + 1;
                end if;
            end if;
        end if;
    end process;
end Behavioral;

実行すると、トリガー信号が入力されてから測定対象の信号が’1’になるまでの時間を、クロックサイクル数でカウントします。

例えば、トリガーから100クロックサイクル後に測定信号が’1’になった場合、result出力は”0000000001100100″(100の16ビット2進表現)となります。

○タイミング違反の診断と修正テクニック

タイミング違反は、信号が期待されるタイミングで到達しない状況を指します。

セットアップ時間違反とホールド時間違反が代表的です。

診断には、静的タイミング解析(STA)ツールが有効です。

修正テクニックとしては、クリティカルパスの最適化、パイプライン化、リタイミングなどがあります。

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

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

architecture Behavioral of retiming_example is
    signal stage1, stage2, stage3 : STD_LOGIC_VECTOR(7 downto 0);
begin
    process(clk, reset)
    begin
        if reset = '1' then
            stage1 <= (others => '0');
            stage2 <= (others => '0');
            stage3 <= (others => '0');
            output <= (others => '0');
        elsif rising_edge(clk) then
            stage1 <= input;
            stage2 <= stage1(7 downto 1) & stage1(0);
            stage3 <= stage2(7 downto 2) & stage2(1 downto 0);
            output <= stage3(7 downto 3) & stage3(2 downto 0);
        end if;
    end process;
end Behavioral;

実行すると、入力信号は3つのステージを経て出力に到達します。

各ステージで演算負荷が分散されているため、クリティカルパスが短縮されます。

例えば、元の回路が100MHzで動作限界だった場合、リタイミング後は150MHzでの動作が可能になるかもしれません。

タイミング違反の修正は試行錯誤的なプロセスです。

STAツールの結果を参考に、クリティカルパスを特定し、適切な箇所にレジスタを挿入したり、ロジックを再配置したりすることで、段階的に改善を図ります。

●遅延属性の応用例と最適化

VHDLの遅延属性を使いこなすことで、回路設計の可能性が大きく広がります。

ただし、単に遅延を追加するだけでは十分ではありません。

パフォーマンスの向上や最適化を考慮しながら、遅延属性を巧みに活用する必要があります。

ここでは、実践的な応用例と最適化テクニックを紹介します。

○サンプルコード9:パフォーマンス向上のための遅延調整

高速な回路設計において、遅延を適切に調整することでパフォーマンスを向上させられる場合があります。

例えば、パイプライン処理を導入し、各ステージの遅延をバランス良く分散させることで、全体のスループットを上げることができます。

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

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

architecture Behavioral of pipeline_performance is
    signal stage1, stage2, stage3 : STD_LOGIC_VECTOR(7 downto 0);

    -- 遅延調整用の属性
    attribute keep : string;
    attribute keep of stage1, stage2, stage3 : signal is "true";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            stage1 <= (others => '0');
            stage2 <= (others => '0');
            stage3 <= (others => '0');
            output <= (others => '0');
        elsif rising_edge(clk) then
            stage1 <= input;
            stage2 <= stage1 xor "10101010";  -- 簡単な演算を追加
            stage3 <= stage2 and "11001100";  -- さらに演算を追加
            output <= stage3 or "00110011";   -- 最終段階の演算
        end if;
    end process;
end Behavioral;

このコードでは、3段階のパイプラインを実装しています。

各ステージで簡単な演算を行い、遅延を分散させています。

keep属性を使用することで、合成ツールによる最適化を制限し、意図した遅延構造を維持します。

実行すると、入力信号が3クロックサイクルを経て出力に現れます。

例えば、1サイクル目に入力が”11110000″の場合、2サイクル目にstage2が”01011010″、3サイクル目にstage3が”01001000″となり、4サイクル目に出力が”01111011″になります。

各ステージの演算が分散されているため、クロック周波数を上げやすくなります。

○サンプルコード10:合成ツールを活用した遅延最適化

現代の FPGA 設計では、合成ツールの高度な最適化機能を活用することが不可欠です。

ツールに適切な制約を与えることで、遅延を最適化しつつ、所望の動作を実現できます。

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

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

architecture Behavioral of synthesis_optimized_delay is
    signal intermediate : STD_LOGIC_VECTOR(7 downto 0);

    -- 合成ツール向けの属性
    attribute max_fanout : integer;
    attribute max_fanout of intermediate : signal is 10;

    attribute keep : string;
    attribute keep of intermediate : signal is "true";
begin
    process(clk, reset)
    begin
        if reset = '1' then
            intermediate <= (others => '0');
            output <= (others => '0');
        elsif rising_edge(clk) then
            intermediate <= input;
            output <= intermediate xor "10101010";
        end if;
    end process;
end Behavioral;

このコードでは、max_fanout属性を使用して中間信号のファンアウトを制限しています。

また、keep属性で中間信号を保持するよう指示しています。

これらの属性は合成ツールに対するヒントとして機能し、最適な遅延バランスを実現するためのガイドラインとなります。

実行すると、入力信号が1クロックサイクル遅延して中間信号に保存され、さらに1サイクル後に演算結果が出力されます。

例えば、1サイクル目に入力が”11110000″の場合、2サイクル目に中間信号が”11110000″となり、3サイクル目に出力が”01011010″になります。

ファンアウト制限により、中間信号の負荷が分散され、タイミング性能が向上する可能性があります。

○サンプルコード11:モジュール間の遅延バランシング

大規模な設計では、複数のモジュール間で遅延をバランス良く分配することが重要です。

モジュール間の通信遅延を考慮しながら、全体のパフォーマンスを最適化する必要があります。

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

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

architecture Behavioral of delay_balanced_system is
    signal intermediate1, intermediate2 : STD_LOGIC_VECTOR(7 downto 0);

    component module_a
        Port ( clk : in STD_LOGIC;
               reset : in STD_LOGIC;
               input : in STD_LOGIC_VECTOR(7 downto 0);
               output : out STD_LOGIC_VECTOR(7 downto 0));
    end component;

    component module_b
        Port ( clk : in STD_LOGIC;
               reset : in STD_LOGIC;
               input : in STD_LOGIC_VECTOR(7 downto 0);
               output : out STD_LOGIC_VECTOR(7 downto 0));
    end component;
begin
    mod_a: module_a port map (clk => clk, reset => reset, input => input, output => intermediate1);
    mod_b: module_b port map (clk => clk, reset => reset, input => intermediate1, output => intermediate2);

    process(clk, reset)
    begin
        if reset = '1' then
            output <= (others => '0');
        elsif rising_edge(clk) then
            output <= intermediate2;
        end if;
    end process;
end Behavioral;

-- Module A
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

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

architecture Behavioral of module_a is
begin
    process(clk, reset)
    begin
        if reset = '1' then
            output <= (others => '0');
        elsif rising_edge(clk) then
            output <= input xor "10101010";
        end if;
    end process;
end Behavioral;

-- Module B
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

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

architecture Behavioral of module_b is
begin
    process(clk, reset)
    begin
        if reset = '1' then
            output <= (others => '0');
        elsif rising_edge(clk) then
            output <= input and "11001100";
        end if;
    end process;
end Behavioral;

このコードでは、2つのモジュール(module_aとmodule_b)を直列に接続し、各モジュールで1クロックサイクルの遅延を導入しています。

全体として3段階のパイプラインとなり、遅延が均等に分散されます。

実行すると、入力信号が3クロックサイクルを経て出力に現れます。

例えば、1サイクル目に入力が”11110000″の場合、2サイクル目にintermediate1が”01011010″、3サイクル目にintermediate2が”01001000″となり、4サイクル目に最終出力が”01001000″になります。

各モジュールの処理が分散されているため、全体としてバランスの取れた遅延特性が実現されます。

○サンプルコード12:非同期回路での遅延属性活用

最後に、非同期回路における遅延属性の活用例を見てみましょう。

非同期回路では、信号の遷移タイミングが重要であり、適切な遅延を挿入することで、ハザードやグリッチを防ぐことができます。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity async_delay_circuit is
    Port ( input_a : in STD_LOGIC;
           input_b : in STD_LOGIC;
           output : out STD_LOGIC);
end async_delay_circuit;

architecture Behavioral of async_delay_circuit is
    signal delayed_a, delayed_b : STD_LOGIC;
begin
    delayed_a <= input_a after 2 ns;
    delayed_b <= input_b after 3 ns;

    output <= delayed_a and delayed_b;
end Behavioral;

このコードでは、2つの入力信号に異なる遅延を与えています。

AND演算を行う前に信号を遅延させることで、入力信号の遷移タイミングのばらつきを吸収し、出力のグリッチを抑制します。

実行すると、input_aの変化から2ns後、input_bの変化から3ns後に、それぞれdelayed_aとdelayed_bが更新されます。

output信号は、delayed_aとdelayed_bの論理積となります。

例えば、input_aが0から1に変化してから2ns後にdelayed_aが1になり、input_bが既に1だった場合、その時点でoutputが1になります。

この遅延により、入力信号の微小なタイミング差による誤動作を防ぐことができます。

まとめ

VHDLにおける遅延属性の活用方法について、基礎から応用まで幅広く解説してきました。

遅延属性は、単なる信号の遅延だけでなく、回路全体の性能や信頼性に大きな影響を与える重要な要素です。

遅延属性の正しい理解と適切な使用は、FPGAプロジェクトにおいて重要な役割を果たします。

本記事で学んだ技術を実践し、経験を積むことで、遅延に起因する問題を自力で解決し、高品質な設計を行う能力が身につくはずです。

VHDLの遅延属性をマスターし、効率的で信頼性の高い回路設計に挑戦してください。