vhdl チュートリアル
vhdlを使い始める
サーチ…
備考
VHDLは、VHSIC(超高速集積回路)HDL(ハードウェア記述言語)の複合頭字語です。ハードウェア記述言語として、主に回路を記述またはモデル化するために使用されます。 VHDLは、並行動作のモデル化時に導入されるあいまい性を排除する実行モデルとともに、並行動作と順次動作の両方を容易に記述する言語構成を提供するので、回路を記述するのに理想的な言語です。
VHDLは、通常、シミュレーションと合成の2つの異なるコンテキストで解釈されます。合成のために解釈されると、コードは、モデル化された同等のハードウェア要素に変換(合成)されます。 VHDLのサブセットのみが合成中に使用でき、サポートされている言語構造は標準化されていません。これは、使用される合成エンジンおよびターゲットハードウェアデバイスの機能である。 VHDLをシミュレーション用に解釈すると、ハードウェアの動作をモデリングするためにすべての言語構成が利用できます。
バージョン
バージョン | 発売日 |
---|---|
IEEE 1076-1987 | 1988-03-31 |
IEEE 1076-1993 | 1994年6月6日 |
IEEE 1076-2000 | 2000-01-30 |
IEEE 1076-2002 | 2002-05-17 |
IEEE 1076c-2007 | 2007-09-05 |
IEEE 1076-2008 | 2009-01-26 |
インストールまたはセットアップ
VHDLプログラムはシミュレーションまたは合成することができます。シミュレーションは、他のプログラミング言語のほとんどの実行に似ています。合成は、VHDLプログラムを論理ゲートのネットワークに変換します。多くのVHDLシミュレーションおよび合成ツールは、商用Electronic Design Automation(EDA)スイートの一部です。 Verilog、SystemVerilog、SystemCなどの他のハードウェア記述言語(HDL)も頻繁に処理します。いくつかのフリーでオープンソースのアプリケーションが存在します。
VHDLシミュレーション
GHDLは、おそらく最も成熟したフリーでオープンソースのVHDLシミュレータです。使用されるバックエンドに応じて、 gcc
、 llvm
またはmcode
3種類のフレーバーが提供されます。次の例は、GHDL( mcode
バージョン)と、メンターグラフィックスの市販のHDLシミュレータであるmcode
GNU / Linuxオペレーティングシステムで使用する方法を示しています。他のツールや他のオペレーティングシステムと非常によく似ています。
こんにちは世界
以下を含むhello_world.vhd
ファイルを作成します。
-- File hello_world.vhd
entity hello_world is
end entity hello_world;
architecture arc of hello_world is
begin
assert false report "Hello world!" severity note;
end architecture arc;
VHDLコンパイルユニットは、単独でコンパイルできる完全なVHDLプログラムです。 エンティティは、デジタル回路の外部インタフェース、すなわちその入力および出力ポートを記述するために使用されるVHDLコンパイル単位です。この例では、 entity
名前はhello_world
、空です。私たちがモデリングしている回路はブラックボックスです。入力も出力もありません。 アーキテクチャーは別のタイプのコンパイル単位です。それらは常にentity
関連付けられ、デジタル回路の動作を記述するために使用されます。 1つのエンティティは、エンティティの動作を記述する1つ以上のアーキテクチャを持つことができます。この例では、 エンティティは1つのVHDLステートメントだけを含むarc
というアーキテクチャに関連付けられています。
assert false report "Hello world!" severity note;
ステートメントは、シミュレーションの開始時に実行され、 Hello world!
を印刷しHello world!
メッセージを標準出力に出力します。これ以上何もする必要がないため、シミュレーションは終了します。私たちが書いたVHDLソースファイルには2つのコンパイルユニットが含まれています。それらを2つの異なるファイルに分割することはできましたが、それらのファイルを異なるファイルに分割することはできませんでした。コンパイル単位は完全に1つのソースファイルに含まれている必要があります。このアーキテクチャは論理ゲートに直接変換できる機能を記述していないため合成できないことに注意してください。
分析し、GHDLでプログラムを実行する:
$ mkdir gh_work
$ ghdl -a --workdir=gh_work hello_world.vhd
$ ghdl -r --workdir=gh_work hello_world
hello_world.vhd:6:8:@0ms:(assertion note): Hello world!
gh_work
ディレクトリは、GHDLが生成するファイルを格納するディレクトリです。これは、-- --workdir=gh_work
オプションの--workdir=gh_work
です。分析フェーズでは、構文の正しさをチェックし、ソースファイルにあるコンパイル単位を説明するテキストファイルを作成します。実行フェーズは実際にプログラムをコンパイル、リンク、実行します。 GHDLのmcode
版では、バイナリファイルは生成されないことに注意してください。プログラムをシミュレートするたびに、プログラムが再コンパイルされます。 gcc
またはllvm
バージョンは異なる動作をします。また、 ghdl -r
は、 ghdl -a
と同様にVHDLソースファイルの名前ではなく、コンパイル単位の名前をとることに注意してください。私たちの場合、 entity
名前を渡しentity
。 1つのarchitecture
しか関連付けられていないため、シミュレートするarchitecture
を指定する必要はありません。
Modelsimを使って:
$ vlib ms_work
$ vmap work ms_work
$ vcom hello_world.vhd
$ vsim -c hello_world -do 'run -all; quit'
...
# ** Note: Hello world!
# Time: 0 ns Iteration: 0 Instance: /hello_world
...
vlib
、 vmap
、 vcom
、 vsim
は、 vcom
が提供する4つのコマンドです。 vlib
は、生成されたファイルが格納されるディレクトリ( ms_work
)を作成します。 vmap
は、 vlib
で作成されたディレクトリを論理名( work
)に関連付けます。 vcom
はVHDLソースファイルをコンパイルし、デフォルトでは結果をwork
論理名に関連付けられたディレクトリに格納します。最後に、 vsim
はプログラムをシミュレートし、GHDLと同じ種類の出力を生成します。 vsim
が求めているのは、ソースファイルではなく、すでにコンパイルされたコンパイルユニットの名前です。 -c
オプションを指定すると、シミュレータはデフォルトのグラフィカルユーザーインターフェイス(GUI)モードではなくコマンドラインモードで実行されます。 -do
オプションは、デザインをロードした後に実行するTCLスクリプトを渡すために使用されます。 TCLは、EDAツールで頻繁に使用されるスクリプト言語です。 -do
オプションの値は、ファイルの名前でも、この例のように、TCLコマンドの文字列でも-do
ません。 run -all; quit
または永遠にそれが永遠に続く場合- -その後、終了することが自然に終了するまでシミュレーションを実行するシミュレータを指示します。
同期カウンタ
-- File counter.vhd
-- The entity is the interface part. It has a name and a set of input / output
-- ports. Ports have a name, a direction and a type. The bit type has only two
-- values: '0' and '1'. It is one of the standard types.
entity counter is
port(
clock: in bit; -- We are using the rising edge of CLOCK
reset: in bit; -- Synchronous and active HIGH
data: out natural -- The current value of the counter
);
end entity counter;
-- The architecture describes the internals. It is always associated
-- to an entity.
architecture sync of counter is
-- The internal signals we use to count. Natural is another standard
-- type. VHDL is not case sensitive.
signal current_value: natural;
signal NEXT_VALUE: natural;
begin
-- A process is a concurrent statement. It is an infinite loop.
process
begin
-- The wait statement is a synchronization instruction. We wait
-- until clock changes and its new value is '1' (a rising edge).
wait until clock = '1';
-- Our reset is synchronous: we consider it only at rising edges
-- of our clock.
if reset = '1' then
-- <= is the signal assignment operator.
current_value <= 0;
else
current_value <= next_value;
end if;
end process;
-- Another process. The sensitivity list is another way to express
-- synchronization constraints. It (approximately) means: wait until
-- one of the signals in the list changes and then execute the process
-- body. Sensitivity list and wait statements cannot be used together
-- in the same process.
process(current_value)
begin
next_value <= current_value + 1;
end process;
-- A concurrent signal assignment, which is just a shorthand for the
-- (trivial) equivalent process.
data <= current_value;
end architecture sync;
こんにちは世界
古典的な "Hello world!"を印刷する方法はたくさんあります。 VHDLのメッセージ。おそらく最も単純なものは次のようなものです:
-- File hello_world.vhd
entity hello_world is
end entity hello_world;
architecture arc of hello_world is
begin
assert false report "Hello world!" severity note;
end architecture arc;
同期カウンタのシミュレーション環境
シミュレーション環境
VHDLデザイン(Design Under TestまたはDUT)のシミュレーション環境は、少なくとも以下のVHDLデザインです。
- DUTの入力ポートと出力ポートに対応する信号を宣言します。
- DUTをインスタンス化し、そのポートを宣言された信号に接続します。
- DUTの入力ポートに接続された信号を駆動するプロセスをインスタンス化します。
オプションで、シミュレーション環境では、インターフェイス上のトラフィックジェネレータ、通信プロトコルをチェックするモニタ、DUT出力の自動ベリファイアなど、DUT以外のデザインをインスタンス化できます。
シミュレーション環境が分析され、精緻化され実行されます。ほとんどのシミュレータは、観測する信号のセットを選択し、グラフィカルな波形をプロットし、ソースコードにブレークポイントを入れ、ソースコード内でステップする可能性を提供します...
理想的には、シミュレーション環境は堅牢な非回帰テストとして使用可能である必要があります。つまり、DUT仕様の違反を自動的に検出し、有用なエラーメッセージを報告し、DUT機能の合理的な範囲を保証する必要があります。このようなシミュレーション環境が利用可能な場合、DUTのあらゆる変更時に再実行して、シミュレーショントレースの煩雑でエラーが発生しやすい視覚検査を必要とせずに、機能的に正しいことを確認することができます。
実際には、理想的なシミュレーション環境を設計することは困難です。 DUTそのものを設計するよりも、しばしば困難である。
この例では、 同期カウンタのシミュレーション環境を示します。 GHDLとModelSimを使用して実行する方法と、GHDLでGTKWaveを使用し、 Modelsimで組み込みの波形ビューアを使用してグラフィカル波形を観察する方法を示します。次に、シミュレーションの興味深い点について議論します:それらを止める方法は?
同期カウンタの第1シミュレーション環境
同期カウンタには2つの入力ポートと1つの出力ポートがあります。非常に単純なシミュレーション環境は次のようなものです。
-- File counter_sim.vhd
-- Entities of simulation environments are frequently black boxes without
-- ports.
entity counter_sim is
end entity counter_sim;
architecture sim of counter_sim is
-- One signal per port of the DUT. Signals can have the same name as
-- the corresponding port but they do not need to.
signal clk: bit;
signal rst: bit;
signal data: natural;
begin
-- Instantiation of the DUT
u0: entity work.counter(sync)
port map(
clock => clk,
reset => rst,
data => data
);
-- A clock generating process with a 2ns clock period. The process
-- being an infinite loop, the clock will never stop toggling.
process
begin
clk <= '0';
wait for 1 ns;
clk <= '1';
wait for 1 ns;
end process;
-- The process that handles the reset: active from beginning of
-- simulation until the 5th rising edge of the clock.
process
begin
rst <= '1';
for i in 1 to 5 loop
wait until rising_edge(clk);
end loop;
rst <= '0';
wait; -- Eternal wait. Stops the process forever.
end process;
end architecture sim;
GHDLによるシミュレーション
GHDLでこれをコンパイルしてシミュレートしましょう:
$ mkdir gh_work
$ ghdl -a --workdir=gh_work counter_sim.vhd
counter_sim.vhd:27:24: unit "counter" not found in 'library "work"'
counter_sim.vhd:50:35: no declaration for "rising_edge"
次に、エラーメッセージが2つの重要なことを教えてくれます。
- GHDLアナライザーは、私たちのデザインが
counter
という名前のエンティティをインスタンス化していますが、このエンティティはライブラリのwork
見つからないことを発見しました。これは、counter_sim
前にcounter
コンパイルしなかったためです。エンティティをインスタンス化するVHDLデザインをコンパイルする場合、ボトムレベルは常にトップレベルより前にコンパイルする必要があります(階層デザインもトップダウンでコンパイルできcomponent
、エンティティではなくcomponent
をインスタンス化する場合のみ)。 - 私たちのデザインで使用されている
rising_edge
関数は定義されていません。これは、この機能がVHDL 2008で導入されたことと、このバージョンの言語を使用するようにGHDLに指示していないためです(デフォルトでVHDL 1993とVHDL 1987の構文を許容します)。
2つのエラーを修正し、シミュレーションを開始しましょう:
$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C
解析とシミュレーションには--std=08
オプションが必要であることに注意してください。また、ソースファイルではなく、エンティティcounter_sim
、architecture sim
でシミュレーションを開始したことにも注意してください。
私たちのシミュレーション環境では決してプロセスが終了しないため(クロックを生成するプロセス)、シミュレーションは停止せず、手動で中断する必要があります。代わりに、停止時間を--stop-time
オプションで指定することができます:
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time
つまり、シミュレーションではDUTの動作についてはほとんど教えてくれません。シグナルの値の変化をファイルにダンプしましょう:
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns --vcd=counter_sim.vcd
Vcd.Avhpi_Error!
ghdl:info: simulation stopped by --stop-time
(エラーメッセージを無視してください、これはGHDLで修正する必要があり、結果はありません)。 counter_sim.vcd
ファイルが作成されました。これは、シミュレーション中にすべての信号変化をVCD(ASCII)形式で含んでいます。 GTKWaveは、対応するグラフィカルな波形を表示することができます:
$ gtkwave counter_sim.vcd
カウンタが期待どおりに動作することがわかります。
Modelsimでシミュレーションする
原則はModelsimとまったく同じです:
$ vlib ms_work
...
$ vmap work ms_work
...
$ vcom -nologo -quiet -2008 counter.vhd counter_sim.vhd
$ vsim -voptargs="+acc" 'counter_sim(sim)' -do 'add wave /*; run 60ns'
vsim
渡される-voptargs="+acc"
オプションに注意してください:シミュレータがdata
信号を最適化するのを防ぎ、波形上でそれを見ることができます。
正常に終了するシミュレーション
どちらのシミュレータでも、終わりのないシミュレーションを中断したり、専用のオプションで停止時間を指定する必要がありました。これはあまり便利ではありません。多くの場合、シミュレーションの終了時間を予測することは困難です。特定の条件に達すると、たとえばカウンタの現在の値が20に達すると、シミュレーション環境のVHDLコードの内部からシミュレーションを停止するほうがずっと良いでしょう。これは、シミュレーション環境のアサーションで達成できます。リセットを処理するプロセス:
process
begin
rst <= '1';
for i in 1 to 5 loop
wait until rising_edge(clk);
end loop;
rst <= '0';
loop
wait until rising_edge(clk);
assert data /= 20 report "End of simulation" severity failure;
end loop;
end process;
data
が20と異なる限り、シミュレーションは継続されます。 data
が20に達すると、シミュレーションがクラッシュし、エラーメッセージが表示されます。
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:90:24:@51ns:(assertion failure): End of simulation
ghdl:error: assertion failed
from: process work.counter_sim(sim2).P1 at counter_sim.vhd:90
ghdl:error: simulation failed
シミュレーション環境のみを再コンパイルしたことに注意してください。これは、変更された唯一のデザインであり、トップレベルです。我々は唯一の修正だったcounter.vhd
:我々は両方の再コンパイルしなければならなかっただろう、 counter.vhd
、それが変更されたためとcounter_sim.vhd
それはに依存しているためcounter.vhd
。
エラーメッセージでシミュレーションをクラッシュさせることはあまり優雅ではありません。シミュレーションメッセージを自動的に解析して、自動非回帰テストが合格したかどうかを判断する際にも問題になる可能性があります。より良い、より洗練されたソリューションは、条件に達するとすべてのプロセスを停止することです。これは、例えば、 boolean
エンド・オブ・シミュレーション( eof
)信号を加えることによって行うことができる。デフォルトでは、シミュレーションの開始時にfalse
に初期化されfalse
。私たちのプロセスの1つは、シミュレーションが終了する時が来たときにそれをtrue
設定しtrue
。他のすべてのプロセスはこのシグナルを監視し、それがtrue
になると永遠のwait
停止しtrue
:
signal eos: boolean;
...
process
begin
clk <= '0';
wait for 1 ns;
clk <= '1';
wait for 1 ns;
if eos then
report "End of simulation";
wait;
end if;
end process;
process
begin
rst <= '1';
for i in 1 to 5 loop
wait until rising_edge(clk);
end loop;
rst <= '0';
for i in 1 to 20 loop
wait until rising_edge(clk);
end loop;
eos <= true;
wait;
end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:120:24:@50ns:(report note): End of simulation
最後に、標準パッケージenv
とそれが宣言したstop
とfinish
手順を使って、VHDL 2008に導入されたさらに優れたソリューションがあります。
use std.env.all;
...
process
begin
rst <= '1';
for i in 1 to 5 loop
wait until rising_edge(clk);
end loop;
rst <= '0';
for i in 1 to 20 loop
wait until rising_edge(clk);
end loop;
finish;
end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
simulation finished @49ns
信号対変数、VHDLのシミュレーションセマンティクスの概要
この例では、VHDL言語の最も基本的な側面の1つ、すなわちシミュレーションのセマンティクスを扱います。 VHDLの初心者向けであり、多くの詳細が省略された簡略化されたビューを提示します(プロセスの延期、VHDL手続きインタフェース、シェア変数...)。完全なセマンティクスに興味のある読者は、LRMを参照する必要があります。
信号と変数
ほとんどの古典的な命令型プログラミング言語は変数を使用します。それらはバリューコンテナです。代入演算子を使用して、変数に値を格納します。
a = 15;
変数に現在格納されている値を読み込んで他のステートメントで使用することができます。
if(a == 15) { print "Fifteen" }
VHDLは変数も使用し、ほとんどの命令型言語とまったく同じ役割を持っています。しかし、VHDLはまた、別の種類のバリューコンテナを提供しています。信号はまた、値を記憶し、割り当てられ、読み出すこともできる。シグナルに格納できる値のタイプは、変数とほぼ同じです。
だから、2種類のバリューコンテナを持つ理由は何ですか?この質問への答えは不可欠であり、言葉の中心です。変数と信号の違いを理解することは、VHDLで何かをプログラムしようとする前にまず行うべきことです。
具体的な例として、この違いを説明しましょう。
注:次のすべてのコードスニペットはプロセスの一部です。私たちはプロセスが何であるかを後で見ていきます。
tmp := a;
a := b;
b := tmp;
変数a
とb
入れ替えます。これらの3つの命令を実行した後、 a
の新しい内容はb
の古い内容であり、逆にその内容である。ほとんどのプログラミング言語と同様に、第3の一時変数( tmp
)が必要です。変数の代わりにシグナルを入れ替える場合は、次のように記述します。
r <= s;
s <= r;
または:
s <= r;
r <= s;
同じ結果で、第3の一時的な信号を必要とせずに!
注:VHDL信号代入演算子
<=
は変数代入演算子:=
とは異なります。
2番目の例を見てみましょう。ここでは、 print
サブプログラムがそのパラメータの10進表現を出力すると仮定しています。 a
が整数変数で、その現在の値が15の場合は、次を実行します。
a := 2 * a;
a := a - 5;
a := a / 5;
print(a);
印刷されます:
5
私たちは、デバッガのステップでこのステップを実行すると、私たちは、の値を参照することができ、最初の15〜30、25、最終的に5から変更を。 a
しかし、 s
が整数信号で、現在の値が15の場合は、次のように実行します。
s <= 2 * s;
s <= s - 5;
s <= s / 5;
print(s);
wait on s;
print(s);
印刷されます:
15
3
デバッガでこのステップをステップごとに実行すると、 wait
命令の後までs
値の変更は表示されません。さらに、 s
最終値は15,30,25、または5ではなく3になります。
このような奇妙な動作は、次のセクションで説明するように、デジタルハードウェアの基本的な並列性に起因します。
平行
VHDLはハードウェア記述言語(HDL)であり、本質的には並列である。 VHDLプログラムは、並行して実行される順次プログラムの集合です。これらの逐次プログラムはプロセスと呼ばれます。
P1: process
begin
instruction1;
instruction2;
...
instructionN;
end process P1;
P2: process
begin
...
end process P2;
プロセスは、モデリングしているハードウェアと同様、決して終わらず、無限ループです。最後の命令を実行した後、実行は最初の命令から続行されます。
1つの形式または別の並列処理をサポートするプログラミング言語と同様に、スケジューラは、VHDLシミュレーション中に実行するプロセス(およびいつ)を決定する責任があります。さらに、この言語は、プロセス間通信および同期のための特定の構成を提供する。
スケジューリング
スケジューラは、すべてのプロセスのリストを保持し、それらのそれぞれのために、することができ、現在の状態を記録running
、 run-able
またはsuspended
。多くの場合、 running
プロセスは1つで、現在実行中のプロセスです。現在実行中のプロセスがwait
命令を実行しない限り、プロセスは実行を継続し、他のプロセスが実行されるのを防ぎます。 VHDLスケジューラはプリエンプティブではありません。自分自身を中断し、他のプロセスを実行させるのはそれぞれのプロセスの責任です。これはVHDLの初心者が頻繁に遭遇する問題の1つです。フリーランニングプロセスです。
P3: process
variable a: integer;
begin
a := s;
a := 2 * a;
r <= a;
end process P3;
注:変数
a
はローカルで宣言され、シグナルs
とr
はより高いレベルで宣言されます。 VHDL変数は、それらを宣言するプロセスにとってローカルであり、他のプロセスでは見られません。別のプロセスはまた、名前の変数を宣言でき、それは、プロセスの一つとして同じ変数ではありませんa
P3
。
スケジューラがP3
プロセスを再開するとすぐに、シミュレーションが停止し、シミュレーションの現在の時間がもう進行しなくなり、これを停止する唯一の方法はシミュレーションを強制終了または中断することです。その理由は、 P3
がwait
ステートメントを持っておらず、そのために3つの命令をループして永久にrunning
状態にとどまるためです。それがrun-able
可能であっても、実行する機会は他のどのプロセスにも与えられません。
wait
文を含むプロセスでも同じ問題が発生する可能性があります。
P4: process
variable a: integer;
begin
a := s;
a := 2 * a;
if a = 16 then
wait on s;
end if;
r <= a;
end process P4;
注:VHDL等価演算子は
=
です。
信号s
値が3である間にプロセスP4
が再開されると、 a = 16
状態は決して真でないので、それは永久に実行される。
私たちのVHDLプログラムにこのような病理学的プロセスが含まれていないと仮定しよう。実行中のプロセスがwait
命令を実行すると、実行中のプロセスはすぐに中断され、スケジューラはsuspended
状態にします。 wait
命令は、プロセスrun-able
再びrun-able
になるための条件も保持します。例:
wait on s;
信号s
値が変化するまで私を中断することを意味します 。この状態はスケジューラによって記録されます。スケジューラはrun-able
中から別のプロセスを選択し、 running
状態にして実行します。 run-able
すべてのプロセスが実行され、一時停止されるまで、同じことが繰り返されます。
重要な注意:複数のプロセスが
run-able
場合、VHDL標準ではスケジューラがrun-able
プロセスをどのように選択するかは指定されていません。その結果、シミュレータ、シミュレータのバージョン、オペレーティングシステムなどに応じて、同じVHDLモデルの2つのシミュレーションが異なる選択を行い、実行する別のプロセスを選択する可能性があります。この選択がシミュレーション結果に影響を与える場合、VHDLは非決定論的であると言えるでしょう。非決定論は通常望ましくないので、非決定論的な状況を避けることはプログラマーの責任です。幸いにも、VHDLがこれを処理し、これが信号が画像に入る場所です。
信号とプロセス間通信
VHDLは、2つの特定の特性を使用して非決定論を回避します。
- プロセスは信号のみで情報を交換できます
signal r, s: integer; -- Common to all processes
...
P5: process
variable a: integer; -- Different from variable a of process P6
begin
a := s + 1;
r <= a;
a := r + 1;
wait on s;
end process P5;
P6: process
variable a: integer; -- Different from variable a of process P5
begin
a := r + 1;
s <= a;
wait on r;
end process P6;
注意:VHDLのコメントから延びる
--
ラインの終わりに。
- プロセスの実行中にVHDL信号の値は変化しません
信号が割り当てられるたびに、割り当てられた値がスケジューラによって記録されますが、信号の現在の値は変更されません。これは、割り当てられた直後に新しい値をとる変数とのもう1つの大きな違いです。
上記のプロセスP5
実行を見て、それがスケジューラによって再開されたときにa=5
、 s=1
およびr=0
と仮定する。命令a := s + 1;
実行しa := s + 1;
後a := s + 1;
変数a
の値が変化し、2(1 + 1)となる。次の命令r <= a;
実行するときr <= a;
r
代入されるのはa
(2)の新しい値です。しかし、 r
は信号であり、 r
の現在の値はまだ0です。したがって、 a := r + 1;
変数a
は、直感で言うように(直ちに)値1(0 + 1)であり、3(2 + 1)ではありません。
信号r
本当に新しい価値を取るでしょうか?スケジューラーが実行可能なすべてのプロセスを実行し、スケジューラーがすべて中断された場合。これは、1 デルタサイクル後とも呼ばれます 。スケジューラは、信号に割り当てられたすべての値を調べ、実際に信号の値を更新するだけです。 VHDLシミュレーションは、実行フェーズと信号更新フェーズの交互に行われます。実行段階では、信号の値は固定されます。象徴的には、実行フェーズと次の信号更新フェーズとの間に、時間のデルタが経過したとする。これはリアルタイムではありません。 デルタサイクルには物理的な持続時間はありません。
この遅延信号更新メカニズムにより、VHDLは確定的です。プロセスは信号とのみ通信でき、プロセスの実行中は信号は変化しません。したがって、プロセスの実行順序は重要ではありません。実行中に外部環境(シグナル)は変化しません。初期状態がP5.a=5
、 P6.a=10
、 s=17
、 r=0
であり、スケジューラがP5
最初に実行しP6
次に実行することを決定したプロセスP5
およびP6
。次の表は、2つの変数の値、各プロセスの各命令を実行した後の信号の現在値と次の値を示しています。
プロセス/命令 | P5.a | P6.a | s.current | s.next | r.current | r.next |
---|---|---|---|---|---|---|
初期状態 | 5 | 10 | 17 | 0 | ||
P5 / a := s + 1 | 18 | 10 | 17 | 0 | ||
P5 / r <= a | 18 | 10 | 17 | 0 | 18 | |
P5 / a := r + 1 | 1 | 10 | 17 | 0 | 18 | |
P5 / wait on s | 1 | 10 | 17 | 0 | 18 | |
P6 / a := r + 1 | 1 | 1 | 17 | 0 | 18 | |
P6 / s <= a | 1 | 1 | 17 | 1 | 0 | 18 |
P6 / wait on r | 1 | 1 | 17 | 1 | 0 | 18 |
信号更新後 | 1 | 1 | 1 | 18 |
同じ初期条件で、スケジューラがP6
最初に実行しP5
次に実行することを決定した場合、
プロセス/命令 | P5.a | P6.a | s.current | s.next | r.current | r.next |
---|---|---|---|---|---|---|
初期状態 | 5 | 10 | 17 | 0 | ||
P6 / a := r + 1 | 5 | 1 | 17 | 0 | ||
P6 / s <= a | 5 | 1 | 17 | 1 | 0 | |
P6 / wait on r | 5 | 1 | 17 | 1 | 0 | |
P5 / a := s + 1 | 18 | 1 | 17 | 1 | 0 | |
P5 / r <= a | 18 | 1 | 17 | 1 | 0 | 18 |
P5 / a := r + 1 | 1 | 1 | 17 | 1 | 0 | 18 |
P5 / wait on s | 1 | 1 | 17 | 1 | 0 | 18 |
信号更新後 | 1 | 1 | 1 | 18 |
わかるように、2つのプロセスを実行した後の結果は、実行順序が同じでも同じです。
この直感的な信号割り当てのセマンティクスは、VHDLの初心者が頻繁に遭遇する第2のタイプの問題の1つです。つまり、1つのデルタサイクルだけ遅延しているために割り当てが明らかに機能しません。デバッガでプロセスP5
段階的に実行すると、 r
が18に割り当てられ、 a
がr + 1
に割り当てられa
後、 a
の値は19でa
と予想されますが、デバッガは執拗にr=0
かつa=1
...
注:同じ実行フェーズで同じ信号を複数回割り当てることができます。この場合、信号の次の値を決定するのは最後の割り当てです。他の課題は、まったく実行されていない場合と全く同じように、まったく効果がありません。
私たちの理解を確認する時間です:最初のスワッピングの例に戻って、理由を理解してみてください:
process
begin
---
s <= r;
r <= s;
---
end process;
実際に信号r
とs
を3番目の一時的な信号を必要とせずに入れ替える理由は次のとおりです。
process
begin
---
r <= s;
s <= r;
---
end process;
厳密に等価であろう。また、なぜs
が整数信号で現在の値が15であるのかを理解してください。そして、次のように実行します。
process
begin
---
s <= 2 * s;
s <= s - 5;
s <= s / 5;
print(s);
wait on s;
print(s);
---
end process;
信号s
最初の2つの割り当ては効果がなく、なぜs
が最終的に3に割り当てられ、なぜ2つの印刷された値が15と3であるのか。
物理的な時間
ハードウェアをモデル化するには、ある操作で取られた物理的な時間をモデル化することが非常に有用です。これはVHDLでどのように行うことができるかの例です。この例では、同期カウンタをモデル化しています。これは、コンパイルおよびシミュレートできる完全な自己完結型のVHDLコードです。
-- File counter.vhd
entity counter is
end entity counter;
architecture arc of counter is
signal clk: bit; -- Type bit has two values: '0' and '1'
signal c, nc: natural; -- Natural (non-negative) integers
begin
P1: process
begin
clk <= '0';
wait for 10 ns; -- Ten nano-seconds delay
clk <= '1';
wait for 10 ns; -- Ten nano-seconds delay
end process P1;
P2: process
begin
if clk = '1' and clk'event then
c <= nc;
end if;
wait on clk;
end process P2;
P3: process
begin
nc <= c + 1 after 5 ns; -- Five nano-seconds delay
wait on c;
end process P3;
end architecture arc;
プロセスP1
では、 wait
命令は、今まで見たように信号の値が変化するまでwait
するのではなく、一定の時間待機するために使用されます。このプロセスは、クロックジェネレータをモデル化します。信号clk
はシステムのクロックで、周期20 ns(50 MHz)で周期的であり、デューティ・サイクルを持っています。
プロセスP2
は、 clk
立ち上がりエッジがclk
発生した場合、その入力nc
値を出力c
に割り当て、次にclk
次の値変化を待つレジスタをモデル化する。
プロセスP3
は、その入力c
値を1だけインクリメントしてその出力nc
...に5nsの物理的遅延で割り当てるインクリメンタをモデル化する。次に、入力c
値が変わるまで待ちます。これも新しいものです。これまでは常に次のように信号を割り当てました。
s <= value;
前のセクションで説明した理由から、暗黙のうちに次のものに変換できます。
s <= value; -- after delta
この小型デジタルハードウェアシステムは、次の図で表すことができます。
物理的時間の導入と、 デルタで測定された象徴的な時間があることを知ると、 T
はナノ秒単位で測定された物理的時間であり、 D
は数字であるT+D
を表す2次元時間を有するデルタの(物理的な持続時間なしで)。
完全な画像
VHDLシミュレーションの重要な側面の1つは、まだ検討していないことです。実行フェーズ後、すべてのプロセスがsuspended
状態になります。私たちは、スケジューラが割り当てられた信号の値を更新することを非公式に述べました。しかし、我々の同期カウンタの例では、信号clk
、 c
、 nc
を同時に更新するか?物理的遅延はどうですか?次に、 suspended
状態のプロセスとrun-able
状態のプロセスでは何が起こりますか?
完全な(ただし単純化された)シミュレーションアルゴリズムは、次のとおりです。
- 初期化
- 現在時刻
Tc
を0 + 0(0ns、0デルタ - サイクル)に設定する - すべての信号を初期化する。
-
wait
文で中断するまで、各プロセスを実行します。- 信号割り当ての値と遅延を記録します。
- プロセスが再開するための条件を記録します(遅延または信号変更)。
- 次の時間
Tn
を次の中で最も早く計算します。-
wait for <delay>
によって中断されたプロセスの再開時間。 - 次回信号値が変化する時。
-
- 現在時刻
- シミュレーションサイクル
-
Tc=Tn
。 - 必要な信号を更新する。
- 更新された信号の1つの値の変更を待っていたすべてのプロセスを
run-able
状態にします。 -
wait for <delay>
ステートメントのwait for <delay>
によって中断され、再開時間がTc
すべてのプロセスをrun-able
状態にします。 - 実行可能なすべてのプロセスを中断するまで実行します。
- 信号割り当ての値と遅延を記録します。
- プロセスが再開するための条件を記録します(遅延または信号変更)。
- 次の時間
Tn
を次の中で最も早く計算します。-
wait for <delay>
によって中断されたプロセスの再開時間。 - 次回信号値が変化する時。
-
-
Tn
が無限大の場合は、シミュレーションを停止します。さもなければ、新しいシミュレーションサイクルを開始してください。
-
マニュアルシミュレーション
結論として、上記の同期カウンタで簡略化されたシミュレーションアルゴリズムを手動で実行しましょう。我々は、いくつかのプロセスが実行可能である場合、順序はP3
> P2
> P1
となるように任意に決定します。以下の表は、初期化および最初のシミュレーションサイクル中のシステムの状態の進化を表しています。各信号には、現在の値が表示される独自の列があります。信号割当てが実行されると、現在の値がa
あり、次の値が時間T+D
(物理時間+デルタ・サイクル)でb
になる場合、 a/b@T+D
ように、 。 3つの最後の列は、中断されたプロセスを再開する条件を示します(変更する必要がある信号の名前またはプロセスを再開する時刻)。
初期化フェーズ:
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 0 + 0 | |||||||
すべての信号を初期化する | 0 + 0 | '0' | 0 | 0 | ||||
P3/nc<=c+1 after 5 ns | 0 + 0 | '0' | 0 | 0/1 + 5 + 0 | ||||
P3/wait on c | 0 + 0 | '0' | 0 | 0/1 + 5 + 0 | c | |||
P2/if clk='1'... | 0 + 0 | '0' | 0 | 0/1 + 5 + 0 | c | |||
P2/end if | 0 + 0 | '0' | 0 | 0/1 + 5 + 0 | c | |||
P2/wait on clk | 0 + 0 | '0' | 0 | 0/1 + 5 + 0 | clk | c | ||
P1/clk<='0' | 0 + 0 | '0' / '0' @ 0 + 1 | 0 | 0/1 + 5 + 0 | clk | c | ||
P1/wait for 10 ns | 0 + 0 | '0' / '0' @ 0 + 1 | 0 | 0/1 + 5 + 0 | 10 + 0 | clk | c | |
次回に計算する | 0 + 0 | 0 + 1 | '0' / '0' @ 0 + 1 | 0 | 0/1 + 5 + 0 | 10 + 0 | clk | c |
シミュレーションサイクル#1
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 0 + 1 | '0' / '0' @ 0 + 1 | 0 | 0/1 + 5 + 0 | 10 + 0 | clk | c | |
信号を更新する | 0 + 1 | '0' | 0 | 0/1 + 5 + 0 | 10 + 0 | clk | c | |
次回に計算する | 0 + 1 | 5 + 0 | '0' | 0 | 0/1 + 5 + 0 | 10 + 0 | clk | c |
注:最初のシミュレーションサイクルでは、3つのプロセスのいずれもレジューム条件が満たされていないため、実行フェーズはありません。
P2
はclk
値変更を待っていてclk
にトランザクションがありますが、古い値と新しい値が同じであるため、これは値の変更ではありません。
シミュレーションサイクル#2
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 5 + 0 | '0' | 0 | 0/1 + 5 + 0 | 10 + 0 | clk | c | |
信号を更新する | 5 + 0 | '0' | 0 | 1 | 10 + 0 | clk | c | |
次回に計算する | 5 + 0 | 10 + 0 | '0' | 0 | 1 | 10 + 0 | clk | c |
注:再び、実行フェーズはありません。
nc
変更されましたが、プロセスはnc
で待機していません。
シミュレーションサイクル#3
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 10 + 0 | '0' | 0 | 1 | 10 + 0 | clk | c | |
信号を更新する | 10 + 0 | '0' | 0 | 1 | 10 + 0 | clk | c | |
P1/clk<='1' | 10 + 0 | '0' / '1' @ 10 + 1 | 0 | 1 | clk | c | ||
P1/wait for 10 ns | 10 + 0 | '0' / '1' @ 10 + 1 | 0 | 1 | 20 + 0 | clk | c | |
次回に計算する | 10 + 0 | 10 + 1 | '0' / '1' @ 10 + 1 | 0 | 1 | 20 + 0 | clk | c |
シミュレーションサイクル#4
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 10 + 1 | '0' / '1' @ 10 + 1 | 0 | 1 | 20 + 0 | clk | c | |
信号を更新する | 10 + 1 | '1' | 0 | 1 | 20 + 0 | clk | c | |
P2/if clk='1'... | 10 + 1 | '1' | 0 | 1 | 20 + 0 | c | ||
P2/c<=nc | 10 + 1 | '1' | 0/110 + 2 | 1 | 20 + 0 | c | ||
P2/end if | 10 + 1 | '1' | 0/110 + 2 | 1 | 20 + 0 | c | ||
P2/wait on clk | 10 + 1 | '1' | 0/110 + 2 | 1 | 20 + 0 | clk | c | |
次回に計算する | 10 + 1 | 10 + 2 | '1' | 0/110 + 2 | 1 | 20 + 0 | clk | c |
シミュレーションサイクル#5
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 10 + 2 | '1' | 0/110 + 2 | 1 | 20 + 0 | clk | c | |
信号を更新する | 10 + 2 | '1' | 1 | 1 | 20 + 0 | clk | c | |
P3/nc<=c+1 after 5 ns | 10 + 2 | '1' | 1 | 1/2 @ 15 + 0 | 20 + 0 | clk | ||
P3/wait on c | 10 + 2 | '1' | 1 | 1/2 @ 15 + 0 | 20 + 0 | clk | c | |
次回に計算する | 10 + 2 | 15 + 0 | '1' | 1 | 1/2 @ 15 + 0 | 20 + 0 | clk | c |
注意:1はそれを考えることが
nc
更新がでスケジュールされるだろう15+2
我々がそれをスケジュールしながら、15+0
。現在の時間(10+2
)に非ゼロの物理的遅延(ここで5 ns
)を加えると、デルタサイクルは消滅する。実際に、デルタサイクルは、同じ物理的時間T
有する異なるシミュレーション時間T+0
、T+1
...を区別するためにのみ有用である。物理的な時間が変化するとすぐに、デルタサイクルをリセットすることができます。
シミュレーションサイクル#6
オペレーション | Tc | Tn | clk | c | nc | P1 | P2 | P3 |
---|---|---|---|---|---|---|---|---|
現在の時刻を設定する | 15 + 0 | '1' | 1 | 1/2 @ 15 + 0 | 20 + 0 | clk | c | |
信号を更新する | 15 + 0 | '1' | 1 | 2 | 20 + 0 | clk | c | |
次回に計算する | 15 + 0 | 20 + 0 | '1' | 1 | 2 | 20 + 0 | clk | c |
注:再び、実行フェーズはありません。
nc
変更されましたが、プロセスはnc
で待機していません。