Looking for vhdl Answers? Try Ask4KnowledgeBase
Looking for vhdl Keywords? Try Ask4Keywords

vhdlvhdlを使い始める


備考

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デザイン(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
 

カウンタが期待どおりに動作することがわかります。

GTKウェーブ波形

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 とそれが宣言したstopfinish 手順を使って、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
 

こんにちは世界

古典的な "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プログラムはシミュレーションまたは合成することができます。シミュレーションは、他のプログラミング言語のほとんどの実行に似ています。合成は、VHDLプログラムを論理ゲートのネットワークに変換します。多くのVHDLシミュレーションおよび合成ツールは、商用Electronic Design Automation(EDA)スイートの一部です。 Verilog、SystemVerilog、SystemCなどの他のハードウェア記述言語(HDL)も頻繁に処理します。いくつかのフリーでオープンソースのアプリケーションが存在します。

VHDLシミュレーション

GHDLは、おそらく最も成熟したフリーでオープンソースのVHDLシミュレータです。使用されるバックエンドに応じて、 gccllvm または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
...
 

vlibvmapvcomvsim は、 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 または永遠にそれが永遠に続く場合- -その後、終了することが自然に終了するまでシミュレーションを実行するシミュレータを指示します。

信号対変数、VHDLのシミュレーションセマンティクスの概要

この例では、VHDL言語の最も基本的な側面の1つ、すなわちシミュレーションのセマンティクスを扱います。 VHDLの初心者向けであり、多くの詳細が省略された簡略化されたビューを提示します(プロセスの延期、VHDL手続きインタフェース、シェア変数...)。完全なセマンティクスに興味のある読者は、LRMを参照する必要があります。

信号と変数

ほとんどの古典的な命令型プログラミング言語は変数を使用します。それらはバリューコンテナです。代入演算子を使用して、変数に値を格納します。

a = 15;
 

変数に現在格納されている値を読み込んで他のステートメントで使用することができます。

if(a == 15) { print "Fifteen" }
 

VHDLは変数も使用し、ほとんどの命令型言語とまったく同じ役割を持っています。しかし、VHDLはまた、別の種類のバリューコンテナを提供しています。信号はまた、値を記憶し、割り当てられ、読み出すこともできる。シグナルに格納できる値のタイプは、変数とほぼ同じです。

だから、2種類のバリューコンテナを持つ理由は何ですか?この質問への答えは不可欠であり、言葉の中心です。変数と信号の違いを理解することは、VHDLで何かをプログラムしようとする前にまず行うべきことです。

具体的な例として、この違いを説明しましょう。

注:次のすべてのコードスニペットはプロセスの一部です。私たちはプロセスが何であるかを後で見ていきます。

    tmp := a;
    a   := b;
    b   := tmp;
 

変数ab 入れ替えます。これらの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シミュレーション中に実行するプロセス(およびいつ)を決定する責任があります。さらに、この言語は、プロセス間通信および同期のための特定の構成を提供する。

スケジューリング

スケジューラは、すべてのプロセスのリストを保持し、それらのそれぞれのために、することができ、現在の状態を記録runningrun-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 はローカルで宣言され、シグナルsr はより高いレベルで宣言されます。 VHDL変数は、それらを宣言するプロセスにとってローカルであり、他のプロセスでは見られません。別のプロセスはまた、名前の変数を宣言でき、それは、プロセスの一つとして同じ変数ではありませんa P3

スケジューラがP3 プロセスを再開するとすぐに、シミュレーションが停止し、シミュレーションの現在の時間がもう進行しなくなり、これを停止する唯一の方法はシミュレーションを強制終了または中断することです。その理由は、 P3wait ステートメントを持っておらず、そのために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つの特定の特性を使用して非決定論を回避します。

  1. プロセスは信号のみで情報を交換できます
  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のコメントから延びる-- ラインの終わりに。

  1. プロセスの実行中にVHDL信号の値は変化しません

信号が割り当てられるたびに、割り当てられた値がスケジューラによって記録されますが、信号の現在の値は変更されません。これは、割り当てられた直後に新しい値をとる変数とのもう1つの大きな違いです。

上記のプロセスP5 実行を見て、それがスケジューラによって再開されたときにa=5s=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=5P6.a=10s=17r=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に割り当てられ、 ar + 1 に割り当てられa 後、 a の値は19でa と予想されますが、デバッガは執拗にr=0 かつa=1 ...

注:同じ実行フェーズで同じ信号を複数回割り当てることができます。この場合、信号の次の値を決定するのは最後の割り当てです。他の課題は、まったく実行されていない場合と全く同じように、まったく効果がありません。

私たちの理解を確認する時間です:最初のスワッピングの例に戻って、理由を理解してみてください:

  process
  begin
    ---
    s <= r;
    r <= s;
    ---
  end process;
 

実際に信号rs を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 状態になります。私たちは、スケジューラが割り当てられた信号の値を更新することを非公式に述べました。しかし、我々の同期カウンタの例では、信号clkcnc を同時に更新するか?物理的遅延はどうですか?次に、 suspended 状態のプロセスとrun-able 状態のプロセスでは何が起こりますか?

完全な(ただし単純化された)シミュレーションアルゴリズムは、次のとおりです。

  1. 初期化
    • 現在時刻Tc を0 + 0(0ns、0デルタ - サイクル)に設定する
    • すべての信号を初期化する。
    • wait 文で中断するまで、各プロセスを実行します。
      • 信号割り当ての値と遅延を記録します。
      • プロセスが再開するための条件を記録します(遅延または信号変更)。
    • 次の時間Tn を次の中で最も早く計算します。
      • wait for <delay> によって中断されたプロセスの再開時間。
      • 次回信号値が変化する時。
  1. シミュレーションサイクル
    • 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つのプロセスのいずれもレジューム条件が満たされていないため、実行フェーズはありません。 P2clk 値変更を待っていて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+0T+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 で待機していません。

シミュレーション...読み込みを続けるために英語に切り替えます

同期カウンタ

-- 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;