VHDLプロシージャ活用:再利用可能なコードで設計をシンプルに
VHDLにおいて、プロシージャは設計者が繰り返しコードブロックを避けるためのサブプログラムです。同じ操作が複数箇所で必要な場合、完全なモジュールを作成するのは過剰です。プロシージャは軽量で再利用可能な解決策を提供します。
プロシージャはアーキテクチャ、パッケージ、プロセスなどあらゆる宣言領域で宣言でき、スコープはその領域に限定されます。呼び出されると、プロシージャ本体がその場に書かれたかのように実行され、別コンポーネントのオーバーヘッドなしでモジュール性の利点を得られます。
関数とは異なり、プロシージャは直接値を返しません。しかし、パラメータリストでoutやinoutシグナルを宣言することで値を呼び出し元に戻せます。これにより、プロシージャを入出力を持つ小さなモジュールのように振る舞わせられます。
この投稿はBasic VHDL Tutorialsシリーズの一部です。
プロシージャ定義の標準構文は以下の通り:
procedure <procedure_name> (signal|variable|constant <name1> : in|out|inout <type>; ... ) is <declarations_for_use_within_the_procedure> begin <code_performed_by_the_procedure_here> end procedure;
パラメータリストはプロシージャの入出力を定義します(ミニモジュールのよう)。パラメータはシグナル、変数、定数のいずれでも可。プロシージャ内では追加オブジェクト(定数、変数、型、サブタイプ、エイリアス)を宣言可能。シグナルはローカル宣言不可。
プロシージャはwait文を含められるため、テストベンチで特に有用です。例えばシンプルなBFMドライバやDUT出力チェックなど。
演習
前回のチュートリアルでは、深くネストしたif-then-else文でタイマーモジュールを構築しました。各ネストレベルが複雑さを増し可読性を低下させましたが、異なる信号セットに対して同じ操作を行っていました。より良いアプローチは、繰り返しロジックをプロシージャにカプセル化することです。
以下のビデオチュートリアルでVHDLプロシージャの作成を解説:
例:プロシージャベースのタイマー
テストベンチコードは、DUTを駆動するプロシージャ呼び出し追加以外は変更なし:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity T19_ProcedureTb is
end entity;
architecture sim of T19_ProcedureTb is
constant ClockFrequencyHz : integer := 10; -- 10 Hz
constant ClockPeriod : time := 1000 ms / ClockFrequencyHz;
signal Clk : std_logic := '1';
signal nRst : std_logic := '0';
signal Seconds : integer;
signal Minutes : integer;
signal Hours : integer;
begin
i_Timer : entity work.T19_Timer(rtl)
generic map(ClockFrequencyHz => ClockFrequencyHz)
port map (
Clk => Clk,
nRst => nRst,
Seconds => Seconds,
Minutes => Minutes,
Hours => Hours
);
Clk <= not Clk after ClockPeriod / 2;
process is
begin
wait until rising_edge(Clk);
wait until rising_edge(Clk);
nRst <= '1';
wait;
end process;
end architecture;
タイマーモジュール自体はカウンタインクリメント用プロシージャを活用:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity T19_Timer is
generic(ClockFrequencyHz : integer);
port(
Clk : in std_logic;
nRst : in std_logic; -- 負論理リセット
Seconds : inout integer;
Minutes : inout integer;
Hours : inout integer
);
end entity;
architecture rtl of T19_Timer is
signal Ticks : integer;
procedure IncrementWrap(
signal Counter : inout integer;
constant WrapValue : in integer;
constant Enable : in boolean;
variable Wrapped : out boolean
) is
begin
Wrapped := false;
if Enable then
if Counter = WrapValue - 1 then
Wrapped := true;
Counter <= 0;
else
Counter <= Counter + 1;
end if;
end if;
end procedure;
begin
process(Clk) is
variable Wrap : boolean;
begin
if rising_edge(Clk) then
if nRst = '0' then
Ticks <= 0; Seconds <= 0; Minutes <= 0; Hours <= 0;
else
IncrementWrap(Ticks, ClockFrequencyHz, true, Wrap);
IncrementWrap(Seconds, 60, Wrap, Wrap);
IncrementWrap(Minutes, 60, Wrap, Wrap);
IncrementWrap(Hours, 24, Wrap, Wrap);
end if;
end if;
end process;
end architecture;
以下の波形は、タイマーが60分に達したときに分カウンタが正しくラップすることを確認:

分析
波形から、ラップ機能が元のネストif実装と同一に振る舞うことがわかります。変更は構造的なもので、基礎ロジックは変わっていません。
第1パラメータCounterはinoutシグナルで、プロシージャが値を読み書き可能。第2・第3パラメータWrapValueとEnableは定数で、プロシージャ内ではコンパイル時定数として扱われます。
最後のパラメータWrappedはoutモードのvariableで、疑似戻り値としてカウンタがラップしたかを示します。
メインプロセスではIncrementWrapを4回連続呼び出し、カウンタをカスケード接続。前回呼び出しのWrap変数が次回呼び出しのEnable入力になります。ここで変数を使うのが必須です。シグナル更新はプロセスがサスペンドするまで可視化されないため、変数が即時手続き的データフローを提供します。

まとめ
- プロシージャは再利用可能なサブプログラムとして重複コードを排除。
- パラメータはシグナル、変数、定数のいずれでも可、柔軟性を提供。
- 関数と異なり
wait文を含められるため、テストベンチドライバに最適。
次のチュートリアルへ »
VHDL
- カッターグラインダー技術の習得:実践ガイド
- モリブデンが現代産業を形作る仕組み:用途、利点、需給動向
- VHDLプロシージャ例:std_logic_vectorのインクリメント
- VHDLで文字列リストを作成する: ベスト・プラクティスと実装例
- VHDLテストベンチ・シミュレーションを優雅に終了させる方法
- VHDLでPWMコントローラを実装: 設計、シミュレーション、FPGAデモ
- VHDLで乱数生成: 一様分布からOSVVMまで
- プロセス内プロシージャでVHDLステートマシンをクリーンに設計
- VHDLにおける不純関数の活用:FSMの可読性と保守性向上
- VHDL関数マスター:効率的な設計のための実践ガイド
- VHDLで有限ステートマシンを設計:信号機の実践例