Looking for vhdl Keywords? Try Ask4Keywords

vhdlErste Schritte mit vhdl


Bemerkungen

VHDL ist eine zusammengesetzte Abkürzung für VHSIC (Very High Speed ​​Integrated Circuit) - HDL (Hardware Description Language). Als Hardwarebeschreibungssprache wird sie hauptsächlich zur Beschreibung oder zum Modellieren von Schaltkreisen verwendet. VHDL ist eine ideale Sprache zum Beschreiben von Schaltkreisen, da es Sprachkonstrukte bietet, die sowohl gleichzeitiges als auch sequentielles Verhalten zusammen mit einem Ausführungsmodell beschreiben, das Mehrdeutigkeiten entfernt, die beim Modellieren von gleichzeitigem Verhalten auftreten.

VHDL wird typischerweise in zwei verschiedenen Zusammenhängen interpretiert: für die Simulation und für die Synthese. Bei der Interpretation für die Synthese wird Code in die entsprechenden modellierten Hardwareelemente konvertiert (synthetisiert). Normalerweise steht nur eine Teilmenge der VHDL für die Verwendung während der Synthese zur Verfügung. Unterstützte Sprachkonstrukte sind nicht standardisiert. es ist eine Funktion der verwendeten Synthesemaschine und der Zielhardware. Wenn VHDL für die Simulation interpretiert wird, stehen alle Sprachkonstrukte für die Modellierung des Verhaltens von Hardware zur Verfügung.

Versionen

Ausführung Veröffentlichungsdatum
IEEE 1076-1987 1988-03-31
IEEE 1076-1993 1994-06-06
IEEE 1076-2000 2000-01-30
IEEE 1076-2002 2002-05-17
IEEE 1076c-2007 2007-09-05
IEEE 1076-2008 2009-01-26

Eine Simulationsumgebung für den synchronen Zähler

Simulationsumgebungen

Eine Simulationsumgebung für ein VHDL-Design (das Design Under Test oder DUT) ist ein anderes VHDL-Design, das mindestens Folgendes umfasst:

  • Deklariert Signale, die den Eingangs- und Ausgangsanschlüssen des DUT entsprechen.
  • Instantiiert den Prüfling und verbindet seine Ports mit den deklarierten Signalen.
  • Instanziiert die Prozesse, die die Signale steuern, die an die Eingangsanschlüsse des DUT angeschlossen sind.

Optional kann eine Simulationsumgebung andere Designs als das DUT instanziieren, wie z. B. Verkehrsgeneratoren an Schnittstellen, Monitore zur Überprüfung der Kommunikationsprotokolle, automatische Überprüfung der DUT-Ausgänge ...

Die Simulationsumgebung wird analysiert, ausgearbeitet und ausgeführt. Die meisten Simulatoren bieten die Möglichkeit, eine Reihe von Signalen auszuwählen, die beobachtet werden sollen, deren grafische Wellenformen aufzuzeichnen, Haltepunkte in den Quellcode zu setzen, den Quellcode aufzurufen ...

Idealerweise sollte eine Simulationsumgebung als robuster Nicht-Regressionstest verwendet werden. Das heißt, sie sollte automatisch Verstöße gegen die DUT-Spezifikationen erkennen, nützliche Fehlermeldungen melden und eine angemessene Abdeckung der DUT-Funktionen gewährleisten. Wenn solche Simulationsumgebungen verfügbar sind, können sie bei jeder Änderung des Prüflings erneut ausgeführt werden, um zu überprüfen, ob er noch funktionsfähig ist, ohne dass langwierige und fehleranfällige visuelle Inspektionen der Simulationsspuren erforderlich sind.

In der Praxis ist das Entwerfen idealer oder sogar guter Simulationsumgebungen eine Herausforderung. Es ist häufig genauso schwierig oder sogar schwieriger, als den Prüfling selbst zu entwerfen.

In diesem Beispiel stellen wir eine Simulationsumgebung für das Beispiel des synchronen Zählers vor . Wir zeigen, wie man es mit GHDL und Modelsim ausführt und wie man grafische Wellenformen mit GTKWave mit GHDL und den eingebauten Wellenform-Viewer mit Modelsim beobachtet. Wir besprechen dann einen interessanten Aspekt der Simulation: Wie kann man sie stoppen?

Eine erste Simulationsumgebung für den synchronen Zähler

Der synchrone Zähler hat zwei Eingangsports und einen Ausgangsport. Eine sehr einfache Simulationsumgebung könnte sein:

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

Simulieren mit GHDL

Lassen Sie uns dies mit GHDL kompilieren und simulieren:

$ 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"
 

Dann sagen uns Fehlermeldungen zwei wichtige Dinge:

  • Der GHDL Analyzer entdeckt , dass unser Design eine Entität namens instanziiert counter aber diese Einheit nicht in der Bibliothek gefunden work . Dies liegt daran, dass wir counter vor counter_sim nicht kompiliert counter_sim . Beim Kompilieren von VHDL-Entwürfen, die Entitäten instanziieren, müssen die unteren Ebenen immer vor den oberen Ebenen kompiliert werden (hierarchische Entwürfe können auch von oben nach unten kompiliert werden, jedoch nur, wenn sie component und keine Entitäten instanziieren).
  • Die von unserem Design verwendete rising_edge Funktion ist nicht definiert. Dies ist darauf zurückzuführen, dass diese Funktion in VHDL 2008 eingeführt wurde und wir GHDL nicht angewiesen haben, diese Version der Sprache zu verwenden (standardmäßig wird VHDL 1993 mit Toleranz der VHDL 1987-Syntax verwendet).

Lassen Sie uns die beiden Fehler beheben und die Simulation starten:

$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C
 

Beachten Sie, dass die Option --std=08 für die Analyse und Simulation erforderlich ist. Beachten Sie auch, dass wir die Simulation mit der Entität counter_sim , der Architektur sim , und nicht mit einer Quelldatei gestartet haben.

Da unsere Simulationsumgebung einen nie endenden Prozess hat (der Prozess, der die Uhr generiert), stoppt die Simulation nicht und wir müssen sie manuell unterbrechen. Stattdessen können Sie mit der Option --stop-time eine Stoppzeit angeben:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time
 

Die Simulation sagt uns nicht viel über das Verhalten unseres DUTs aus. Lassen Sie uns die Wertänderungen der Signale in einer Datei sichern:

$ 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
 

(Ignorieren Sie die Fehlermeldung, dies muss in GHDL behoben werden und hat keine Auswirkungen). Eine counter_sim.vcd Datei wurde erstellt. Sie enthält im VCD-Format (ASCII) alle Signaländerungen während der Simulation. GTKWave kann uns die entsprechenden grafischen Kurven zeigen:

$ gtkwave counter_sim.vcd
 

wo wir sehen können, dass der Zähler wie erwartet funktioniert.

Eine GTKWave-Wellenform

Simulieren mit Modelsim

Das Prinzip ist bei Modelsim genau das gleiche:

$ 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'
 

Geben Sie hier die Bildbeschreibung ein

Beachten Sie die -voptargs="+acc" Option bestanden vsim : es verhindert , dass der Simulator von der Optimierung des aus data und ermöglicht es uns , auf die Wellenformen zu sehen.

Simulationen mit gutem Ende beenden

Bei beiden Simulatoren mussten wir die nie endende Simulation unterbrechen oder eine Stoppzeit mit einer dedizierten Option angeben. Das ist nicht sehr bequem. In vielen Fällen ist die Endzeit einer Simulation schwer vorherzusehen. Es ist viel besser, die Simulation aus dem VHDL-Code der Simulationsumgebung heraus zu stoppen, wenn eine bestimmte Bedingung erreicht ist, beispielsweise wenn der aktuelle Wert des Zählers 20 erreicht. Dies kann mit einer Bestätigung im erreicht werden Prozess, der den Reset übernimmt:

  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;
 

Solange die data von 20 abweichen, wird die Simulation fortgesetzt. Wenn die data 20 erreichen, stürzt die Simulation mit einer Fehlermeldung ab:

$ 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
 

Beachten Sie, dass wir nur die Simulationsumgebung neu kompiliert haben: Es ist das einzige Design, das geändert wurde, und es ist die oberste Ebene. Hätten wir nur counter.vhd geändert, hätten wir beide neu kompilieren müssen: counter.vhd weil sich das geändert hat, und counter_sim.vhd weil es auf counter.vhd .

Das Absturz der Simulation mit einer Fehlermeldung ist nicht sehr elegant. Es kann sogar ein Problem sein, wenn die Simulationsnachrichten automatisch analysiert werden, um zu entscheiden, ob ein automatischer Nicht-Regressionstest erfolgreich war oder nicht. Eine bessere und viel elegantere Lösung ist, alle Prozesse zu stoppen, wenn eine Bedingung erreicht ist. Dies kann beispielsweise durch Hinzufügen eines boolean End Of Simulation ( eof ) -Signals erfolgen. Standardmäßig wird es zu Beginn der Simulation auf false initialisiert. Einer unserer Prozesse wird es auf true wenn der Zeitpunkt gekommen ist, um die Simulation zu beenden. Alle anderen Prozesse werden dieses Signal überwachen und mit ewigem wait aufhören wait wenn es 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
 

Nicht zuletzt gibt es in VHDL 2008 eine noch bessere Lösung mit dem Standardpaket env und den darin enthaltenen stop and- finish Verfahren:

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
 

Hallo Welt

Es gibt viele Möglichkeiten, die klassische "Hallo Welt!" Zu drucken. Nachricht in VHDL. Die einfachste von allen ist wahrscheinlich so etwas wie:

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

Installation oder Setup

Ein VHDL-Programm kann simuliert oder synthetisiert werden. Simulation ähnelt am ehesten der Ausführung in anderen Programmiersprachen. Synthesis übersetzt ein VHDL-Programm in ein Netzwerk von Logikgattern. Viele VHDL-Simulations- und Synthesewerkzeuge sind Bestandteil kommerzieller EDA-Suites (Electronic Design Automation). Sie behandeln häufig auch andere Hardwarebeschreibungssprachen (HDL) wie Verilog, SystemVerilog oder SystemC. Es gibt einige freie und Open Source-Anwendungen.

VHDL-Simulation

GHDL ist wahrscheinlich der ausgereifteste freie und Open Source-VHDL-Simulator. Je nach verwendetem Backend gibt es drei verschiedene Geschmacksrichtungen: gcc , llvm oder mcode . Die folgenden Beispiele zeigen die Verwendung von GHDL ( mcode version) und Modelsim, dem kommerziellen HDL-Simulator von Mentor Graphics, unter einem GNU / Linux-Betriebssystem. Bei anderen Tools und anderen Betriebssystemen wäre das sehr ähnlich.

Hallo Welt

Erstellen Sie eine Datei hello_world.vhd die hello_world.vhd enthält:

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

Eine VHDL-Kompilierungseinheit ist ein vollständiges VHDL-Programm, das alleine kompiliert werden kann. Entitäten sind VHDL-Kompilierungseinheiten, die zur Beschreibung der externen Schnittstelle einer digitalen Schaltung, dh ihrer Eingangs- und Ausgangsanschlüsse, verwendet werden. In unserem Beispiel heißt die entity hello_world und ist leer. Die Schaltung, die wir modellieren, ist eine Blackbox, sie hat keine Eingänge und keine Ausgänge. Architekturen sind eine andere Art von Kompilierungseinheit. Sie sind immer einer entity und werden verwendet, um das Verhalten der digitalen Schaltung zu beschreiben. Eine Entität kann eine oder mehrere Architekturen aufweisen , um das Verhalten der Entität zu beschreiben. In unserem Beispiel ist die Entität nur einer Architektur namens arc , die nur eine VHDL-Anweisung enthält:

  assert false report "Hello world!" severity note;
 

Die Anweisung wird zu Beginn der Simulation ausgeführt und die Hello world! gedruckt Hello world! Nachricht auf der Standardausgabe. Die Simulation endet dann, weil nichts mehr zu tun ist. Die VHDL-Quelldatei, die wir geschrieben haben, enthält zwei Übersetzungseinheiten. Wir hätten sie in zwei verschiedene Dateien trennen können, aber wir hätten keine davon in verschiedene Dateien aufteilen können: Eine Übersetzungseinheit muss vollständig in einer Quelldatei enthalten sein. Beachten Sie, dass diese Architektur nicht synthetisiert werden kann, da sie keine Funktion beschreibt, die direkt in Logikgatter übersetzt werden kann.

Analysieren Sie das Programm und führen Sie es mit GHDL aus:

$ 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!
 

Im gh_work Verzeichnis werden die von GHDL erzeugten Dateien gespeichert. Das sagt die Option --workdir=gh_work . Die Analysephase überprüft die Richtigkeit der Syntax und erstellt eine Textdatei, die die in der Quelldatei gefundenen Übersetzungseinheiten beschreibt. Die Laufphase kompiliert, verknüpft und führt das Programm tatsächlich aus. Beachten Sie, dass in der mcode Version von GHDL keine Binärdateien generiert werden. Das Programm wird bei jeder Simulation neu kompiliert. Die gcc oder llvm Versionen verhalten sich unterschiedlich. Beachten Sie auch, dass ghdl -r nicht den Namen einer VHDL-Quelldatei wie ghdl -a verwendet, sondern den Namen einer Kompilierungseinheit. In unserem Fall übergeben wir den Namen der entity . Da nur eine architecture zugeordnet ist, muss nicht angegeben werden, welche architecture simuliert werden soll.

Mit 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 und vsim sind vier Befehle, die Modelsim bietet. vlib erstellt ein Verzeichnis ( ms_work ), in dem die generierten Dateien gespeichert werden. vmap einem von vlib erstellten Verzeichnis einen logischen Namen zu ( work ). vcom kompiliert eine VHDL-Quelldatei und speichert das Ergebnis standardmäßig in dem Verzeichnis, das dem logischen work . Schließlich simuliert vsim das Programm und erzeugt dieselbe Ausgabe wie GHDL. Beachten Sie erneut, dass, was vsim verlangt, keine Quelldatei ist, sondern der Name einer bereits kompilierten vsim . Die Option -c weist den Simulator an, im Befehlszeilenmodus anstelle des Standardmodus der grafischen Benutzeroberfläche (GUI) auszuführen. Mit der Option -do wird ein TCL-Skript übergeben, das nach dem Laden des Entwurfs ausgeführt wird. TCL ist eine Skriptsprache, die sehr häufig in EDA-Tools verwendet wird. Der Wert der Option -do kann der Name einer Datei oder, wie in unserem Beispiel, eine Zeichenfolge von TCL-Befehlen sein. run -all; quit weist den Simulator an, die Simulation auszuführen, bis sie auf natürliche Weise endet - oder für immer, wenn sie für immer andauert - und dann zu beenden.

Signale vs. Variablen, ein kurzer Überblick über die Simulationssemantik von VHDL

Dieses Beispiel behandelt einen der grundlegendsten Aspekte der VHDL-Sprache: die Simulationssemantik. Es ist für VHDL-Anfänger gedacht und bietet eine vereinfachte Ansicht, in der viele Details weggelassen wurden (verschobene Prozesse, VHDL Procedural Interface, gemeinsame Variablen ...). Leser, die an der echten vollständigen Semantik interessiert sind, beziehen sich auf das Language Reference Manual (LRM).

Signale und Variablen

Die meisten klassischen imperativen Programmiersprachen verwenden Variablen. Sie sind Wertcontainer. Ein Zuweisungsoperator wird verwendet, um einen Wert in einer Variablen zu speichern:

a = 15;
 

und der aktuell in einer Variablen gespeicherte Wert kann gelesen und in anderen Anweisungen verwendet werden:

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

VHDL verwendet auch Variablen und sie haben genau dieselbe Rolle wie in den meisten zwingenden Sprachen. VHDL bietet jedoch auch eine andere Art von Wertecontainer an: das Signal. Signale speichern auch Werte, können auch zugewiesen und gelesen werden. Die Art der Werte, die in Signalen gespeichert werden können, ist (fast) die gleiche wie in Variablen.

Warum also zwei Arten von Wertcontainern? Die Antwort auf diese Frage ist wesentlich und im Herzen der Sprache. Das Verständnis der Unterschiede zwischen Variablen und Signalen ist das allererste, was Sie tun müssen, bevor Sie etwas in VHDL programmieren.

Lassen Sie uns diesen Unterschied an einem konkreten Beispiel veranschaulichen: dem Tauschen.

Hinweis: Alle folgenden Codeausschnitte sind Teil von Prozessen. Wir werden später sehen, was Prozesse sind.

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

tauscht die Variablen a und b . Nach Ausführung dieser 3 Anweisungen ist der neue Inhalt von a der alte Inhalt von b und umgekehrt. Wie in den meisten Programmiersprachen wird eine dritte temporäre Variable ( tmp ) benötigt. Wenn wir anstelle von Variablen Signale austauschen wollten, würden wir schreiben:

    r <= s;
    s <= r;
 

oder:

    s <= r;
    r <= s;
 

mit dem gleichen Ergebnis und ohne die Notwendigkeit eines dritten temporären Signals!

Hinweis: Der VHDL-Signalzuweisungsoperator <= unterscheidet sich vom Variablenzuweisungsoperator := .

Betrachten wir ein zweites Beispiel, in dem wir davon ausgehen, dass das print Unterprogramm die dezimale Darstellung seines Parameters druckt. Wenn a eine Ganzzahlvariable ist und der aktuelle Wert 15 ist, führen Sie Folgendes aus:

    a := 2 * a;
    a := a - 5;
    a := a / 5;
    print(a);
 

wird drucken:

5
 

Wenn wir dies Schritt für Schritt in einem Debugger ausführen, können wir den Wert a Änderung von den ursprünglichen 15 auf 30, 25 und schließlich 5 sehen.

Wenn s jedoch ein ganzzahliges Signal ist und der aktuelle Wert 15 ist, wird Folgendes ausgeführt:

    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
 

wird drucken:

15
3
 

Wenn wir dies Schritt für Schritt in einem Debugger ausführen, sehen wir erst nach der wait Anweisung eine Wertänderung von s . Außerdem ist der Endwert von s nicht 15, 30, 25 oder 5, sondern 3!

Dieses anscheinend merkwürdige Verhalten beruht auf der grundsätzlich parallelen Natur digitaler Hardware, wie wir in den folgenden Abschnitten sehen werden.

Parallelität

Da VHDL eine Hardwarebeschreibungssprache (Hardware Description Language, HDL) ist, ist sie von Natur aus parallel. Ein VHDL-Programm ist eine Sammlung von sequentiellen Programmen, die parallel ausgeführt werden. Diese sequentiellen Programme werden als Prozesse bezeichnet:

P1: process
begin
  instruction1;
  instruction2;
  ...
  instructionN;
end process P1;

P2: process
begin
  ...
end process P2;
 

Die Prozesse enden ebenso wie die Hardware, die sie modellieren, nie auf: Sie sind endlose Schleifen. Nach der Ausführung der letzten Anweisung wird die Ausführung mit der ersten fortgesetzt.

Wie bei jeder Programmiersprache, die die eine oder andere Form der Parallelität unterstützt, ist ein Scheduler für die Entscheidung verantwortlich, welcher Prozess (und wann) während einer VHDL-Simulation ausgeführt wird. Darüber hinaus bietet die Sprache spezifische Konstrukte für die Kommunikation und Synchronisation zwischen Prozessen.

Terminplanung

Der Scheduler verwaltet eine Liste aller Prozesse und für jeden von ihnen, seinen aktuellen Zustand erfasst werden kann , die running , run-able oder suspended . Es gibt höchstens einen running Prozess: den gerade ausgeführten Prozess. Solange der momentan ausgeführte Prozess keine wait Anweisung ausführt, wird er weiter ausgeführt und verhindert, dass andere Prozesse ausgeführt werden. Der VHDL-Scheduler ist nicht präventiv: Es ist für jeden Prozess verantwortlich, sich selbst zu suspendieren und andere Prozesse laufen zu lassen. Dies ist eines der Probleme, auf das VHDL-Anfänger häufig stoßen: der freilaufende Prozess.

  P3: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    r <= a;
  end process P3;
 

Hinweis: Die Variable a wird lokal deklariert, während die Signale s und r anderer Stelle auf einer höheren Ebene deklariert werden. VHDL-Variablen sind lokal für den Prozess, der sie deklariert, und für andere Prozesse nicht sichtbar. Ein anderer Prozess könnte auch eine Variable mit dem Namen a deklarieren. Sie wäre nicht dieselbe Variable wie die des Prozesses P3 .

Sobald der Scheduler den P3 Prozess wieder P3 bleibt die Simulation stecken, die aktuelle Simulationszeit wird nicht mehr fortgeführt, und die einzige Möglichkeit, dies zu stoppen, besteht darin, die Simulation abzubrechen oder zu unterbrechen. Der Grund ist, dass P3 keine wait Anweisung hat und daher für immer im running Zustand bleibt, wobei er seine 3 Anweisungen durchläuft. Kein anderer Prozess wird jemals eine Chance erhalten, selbst wenn er run-able .

Sogar Prozesse, die eine wait Anweisung enthalten, können das gleiche Problem verursachen:

  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;
 

Hinweis: Der VHDL-Gleichheitsoperator ist = .

Wenn Prozess P4 wieder aufgenommen wird, während der Wert von Signal s 3 ist, wird er für immer ausgeführt, da die Bedingung a = 16 niemals wahr ist.

Nehmen wir an, dass unser VHDL-Programm solche pathologischen Prozesse nicht enthält. Wenn der laufende Prozess einen wait Befehl ausführt, wird er sofort angehalten und vom Scheduler in den suspended Status versetzt. Der wait Befehl enthält auch die Bedingung, dass der Prozess wieder run-able wird. Beispiel:

    wait on s;
 

bedeutet mir , bis der Wert des Signals aussetzen s ändert. Diese Bedingung wird vom Scheduler aufgezeichnet. Der Scheduler wählt dann einen anderen Prozess aus der run-able , versetzt ihn in den running Status und führt ihn aus. Und die gleichen Wiederholungen , bis alle run-able Prozesse ausgeführt und wurden ausgesetzt.

Wichtiger Hinweis: wenn mehrere Prozesse run-able , der VHDL - Standard spezifiziert nicht , wie der Planer soll auswählen , welches zu laufen. Dies hat zur Folge, dass je nach Simulator, Simulatorversion, Betriebssystem oder etwas anderem zwei Simulationen desselben VHDL-Modells an einem Punkt unterschiedliche Entscheidungen treffen und einen anderen Prozess zur Ausführung auswählen können. Wenn diese Auswahl Auswirkungen auf die Simulationsergebnisse hätte, könnte man sagen, dass VHDL nicht deterministisch ist. Da Nicht-Determinismus normalerweise unerwünscht ist, sollten die Programmierer nicht deterministische Situationen vermeiden. Glücklicherweise kümmert sich VHDL darum, und hier kommen Signale ins Bild.

Signale und Kommunikation zwischen Prozessen

VHDL vermeidet Nichtdeterminismus mit zwei spezifischen Merkmalen:

  1. Prozesse können Informationen nur über Signale austauschen
  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;
 

Hinweis: VHDL-Kommentare erstrecken sich von -- bis zum Ende der Zeile.

  1. Der Wert eines VHDL-Signals ändert sich während der Ausführung von Prozessen nicht

Bei jeder Zuweisung eines Signals wird der zugewiesene Wert vom Scheduler aufgezeichnet, der aktuelle Wert des Signals bleibt jedoch unverändert. Dies ist ein weiterer wesentlicher Unterschied bei Variablen, die ihren neuen Wert unmittelbar nach der Zuweisung annehmen.

Betrachten wir eine Ausführung des obigen Prozesses P5 und nehmen an, dass a=5 , s=1 und r=0 wenn der Scheduler ihn wiederaufnimmt. Nach Ausführung des Befehls a := s + 1; ändert sich der Wert der Variablen a und wird zu 2 (1 + 1). Beim Ausführen des nächsten Befehls r <= a; es ist der neue Wert von a (2), der r zugewiesen wird. Da r jedoch ein Signal ist, ist der aktuelle Wert von r immer noch 0. Wenn a ausgeführt wird, gilt Folgendes a := r + 1; Die Variable a nimmt (sofort) den Wert 1 (0 + 1) an, nicht 3 (2 + 1), wie die Intuition sagen würde.

Wann nimmt das Signal r wirklich seinen neuen Wert an? Wenn der Scheduler alle ausführbaren Prozesse ausgeführt hat, werden sie alle angehalten. Dies wird auch als: nach einem Deltazyklus bezeichnet . Nur dann prüft der Scheduler alle Werte, die den Signalen zugewiesen wurden, und aktualisiert die Werte der Signale tatsächlich. Eine VHDL-Simulation ist ein Wechsel von Ausführungsphasen und Signalaktualisierungsphasen. Während der Ausführungsphasen wird der Wert der Signale eingefroren. Symbolisch sagen wir, dass zwischen einer Ausführungsphase und der folgenden Signalaktualisierungsphase ein Zeitdelta verstrichen ist. Dies ist keine Echtzeit. Ein Deltazyklus hat keine physische Dauer.

Dank dieses verzögerten Signalaktualisierungsmechanismus ist VHDL deterministisch. Prozesse können nur mit Signalen kommunizieren und Signale ändern sich während der Ausführung der Prozesse nicht. Die Reihenfolge der Ausführung spielt dabei keine Rolle: Ihre externe Umgebung (die Signale) ändert sich während der Ausführung nicht. Lassen Sie uns dies am vorherigen Beispiel mit den Prozessen P5 und P6 , wobei der Anfangszustand P5.a=5 , P6.a=10 , s=17 , r=0 und der Scheduler beschließt, zuerst P5 und dann P6 auszuführen . Die folgende Tabelle zeigt den Wert der beiden Variablen, die aktuellen und nächsten Werte der Signale nach Ausführung jeder Anweisung jedes Prozesses:

Prozess / Anweisung P5.a P6.a s.current s.next r.current r.next
Ausgangszustand 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
Nach Signalaktualisierung 1 1 1 18

Wenn der Scheduler sich bei den gleichen Anfangsbedingungen dafür entscheidet, zuerst P6 und dann P5 auszuführen:

Prozess / Anweisung P5.a P6.a s.current s.next r.current r.next
Ausgangszustand 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
Nach Signalaktualisierung 1 1 1 18

Wie wir sehen können, ist das Ergebnis nach Ausführung unserer zwei Prozesse in jeder Reihenfolge der Ausführung dasselbe.

Diese gegen-intuitive Signalzuweisungssemantik ist der Grund für eine zweite Art von Problemen, auf die VHDL-Anfänger häufig stoßen: Die Zuweisung, die anscheinend nicht funktioniert, weil sie um einen Deltazyklus verzögert ist. Wenn der Prozess P5 schrittweise in einem Debugger ausgeführt wird, nachdem r zugewiesen wurde und a mit r + 1 zugewiesen wurde, kann man erwarten, dass der Wert von a 19 ist, aber der Debugger sagt hartnäckig, dass r=0 und a=1 ...

Hinweis: Das gleiche Signal kann während derselben Ausführungsphase mehrmals zugewiesen werden. In diesem Fall entscheidet die letzte Zuweisung über den nächsten Wert des Signals. Die anderen Zuweisungen haben keinerlei Auswirkungen, so als ob sie niemals ausgeführt worden wären.

Es ist an der Zeit, unser Verständnis zu überprüfen: Bitte gehen Sie zu unserem ersten Austauschbeispiel zurück und versuchen Sie zu verstehen, warum:

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

tauscht die Signale r und s tatsächlich aus, ohne dass ein drittes temporäres Signal erforderlich ist.

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

wäre absolut gleichwertig. Versuchen Sie auch zu verstehen, warum, wenn s ein ganzzahliges Signal ist und der aktuelle Wert 15 ist, und wir ausführen:

  process
  begin
    ---
    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
    ---
  end process;
 

Die beiden ersten Zuweisungen des Signals s haben keine Auswirkung, warum s schließlich 3 zugewiesen wird und warum die beiden gedruckten Werte 15 und 3 sind.

Körperliche Zeit

Um Hardware zu modellieren, ist es sehr nützlich, die physikalische Zeit modellieren zu können, die einige Operationen benötigen. Hier ist ein Beispiel, wie dies in VHDL möglich ist. Das Beispiel modelliert einen synchronen Zähler und es ist ein vollständiger, in sich geschlossener VHDL-Code, der kompiliert und simuliert werden kann:

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

In Prozess P1 der wait nicht gewartet, bis sich der Wert eines Signals ändert, wie wir bisher gesehen haben, sondern um eine bestimmte Zeit abzuwarten. Dieser Prozess modelliert einen Taktgenerator. Signal clk ist der Takt unseres Systems, es ist periodisch mit einer Periode von 20 ns (50 MHz) und hat einen Arbeitszyklus.

Der Prozess P2 modelliert ein Register, das, wenn gerade eine steigende Flanke von clk aufgetreten ist, den Wert seines Eingangs nc seinem Ausgang c zuordnet und dann auf die nächste Wertänderung von clk wartet.

Der Prozess P3 modelliert einen Inkrementierer, der den c inkrementierten Wert seines Eingangs c seinem Ausgang nc ... mit einer physikalischen Verzögerung von 5 ns zuordnet. Es wartet dann, bis sich der Wert seines Eingangs c ändert. Das ist auch neu. Bisher haben wir Signale immer mit zugewiesen:

  s <= value;
 

was wir aus den in den vorangegangenen Abschnitten erläuterten Gründen implizit übersetzen können in:

  s <= value; -- after delta
 

Dieses kleine digitale Hardwaresystem könnte durch die folgende Abbildung dargestellt werden:

Ein synchroner Zähler

Mit der Einführung der physikalischen Zeit und dem Wissen, dass wir auch eine symbolische Zeit haben, die in Delta gemessen wird, haben wir nun eine zweidimensionale Zeit, die wir T+D wobei T eine physikalische Zeit ist, die in Nanosekunden gemessen wird, und D eine Zahl ist von Deltas (ohne körperliche Dauer).

Das komplette Bild

Es gibt einen wichtigen Aspekt der VHDL-Simulation, den wir noch nicht besprochen haben: Nach einer Ausführungsphase befinden sich alle Prozesse im suspended Zustand. Wir haben informell erklärt, dass der Scheduler dann die Werte der zugewiesenen Signale aktualisiert. Aber in unserem Beispiel eines synchronen Zählers sollten die Signale clk , c und nc gleichzeitig aktualisiert werden? Was ist mit den körperlichen Verspätungen? Und was passiert als Nächstes mit allen Prozessen im suspended Zustand und keinem im run-able Zustand?

Der vollständige (aber vereinfachte) Simulationsalgorithmus ist der folgende:

  1. Initialisierung
    • Aktuelle Zeit Tc auf 0 + 0 setzen (0 ns, 0 Delta-Zyklus)
    • Alle Signale initialisieren.
    • Führen Sie jeden Prozess aus, bis er in einer wait Anweisung angehalten wird.
      • Notieren Sie die Werte und Verzögerungen der Signalzuweisungen.
      • Notieren Sie die Bedingungen für die Wiederaufnahme des Prozesses (Verzögerung oder Signaländerung).
    • Berechne das nächste Mal Tn als das früheste von:
      • Die Wiederaufnahmezeit von Prozessen, die durch ein wait for <delay> angehalten wurden.
      • Das nächste Mal, zu dem sich ein Signalwert ändern soll.
  1. Simulationszyklus
    • Tc=Tn .
    • Signale aktualisieren, die sein müssen.
    • Versetzen Sie alle Prozesse in einen run-able Zustand, die auf eine Wertänderung eines der aktualisierten Signale gewartet haben.
    • Versetzen Sie in einen run-able Zustand alle Prozesse, die von einer wait for <delay> -Anweisung angehalten wurden und für die die Wiederaufnahmezeit Tc .
    • Führen Sie alle ausführbaren Prozesse aus, bis sie angehalten werden.
      • Notieren Sie die Werte und Verzögerungen der Signalzuweisungen.
      • Notieren Sie die Bedingungen für die Wiederaufnahme des Prozesses (Verzögerung oder Signaländerung).
    • Berechne das nächste Mal Tn als das früheste von:
      • Die Wiederaufnahmezeit von Prozessen, die durch ein wait for <delay> angehalten wurden.
      • Das nächste Mal, zu dem sich ein Signalwert ändern soll.
    • Wenn Tn unendlich ist, stoppen Sie die Simulation. Andernfalls starten Sie einen neuen Simulationszyklus.

Manuelle Simulation

Lassen Sie uns abschließend den vereinfachten Simulationsalgorithmus auf dem oben dargestellten synchronen Zähler manuell ausführen. Wir können beliebig entscheiden, dass, wenn mehrere Prozesse lauffähig sind, die Reihenfolge P3 > P2 > P1 . Die folgenden Tabellen zeigen die Entwicklung des Systemstatus während der Initialisierung und der ersten Simulationszyklen. Jedes Signal hat eine eigene Spalte, in der der aktuelle Wert angezeigt wird. Wenn eine Signalzuweisung ausgeführt wird, wird der geplante Wert auf den aktuellen Wert angehängt, zum Beispiel a/b@T+D , wenn der Stromwert a wird und der nächste Wert b zum Zeitpunkt T+D (physical Zeit plus Delta - Zyklen) . Die letzten drei Spalten geben die Bedingung für die Wiederaufnahme der ausgesetzten Prozesse an (Name der Signale, die geändert werden müssen, oder Zeitpunkt, zu dem der Prozess wieder aufgenommen werden soll).

Initialisierungsphase:

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 0 + 0
Alle Signale initialisieren 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
Nächstes Mal berechnen 0 + 0 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c

Simulationszyklus # 1

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Signale aktualisieren 0 + 1 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Nächstes Mal berechnen 0 + 1 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c

Hinweis: Während des ersten Simulationszyklus gibt es keine Ausführungsphase, da bei keinem unserer drei Prozesse die Wiederherstellungsbedingung erfüllt ist. P2 ist für eine Wertänderung des Wartens clk und dort eine Transaktion auf war clk , aber wie die alten und neuen Werte gleich sind, ist dies kein Wertänderung.

Simulationszyklus Nr. 2

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Signale aktualisieren 5 + 0 '0' 0 1 10 + 0 clk c
Nächstes Mal berechnen 5 + 0 10 + 0 '0' 0 1 10 + 0 clk c

Hinweis: Wieder gibt es keine Ausführungsphase. nc geändert, aber kein Prozess wartet auf nc .

Simulationszyklus Nr. 3

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 10 + 0 '0' 0 1 10 + 0 clk c
Signale aktualisieren 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
Nächstes Mal berechnen 10 + 0 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c

Simulationszyklus Nr. 4

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Signale aktualisieren 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/1 @ 10 + 2 1 20 + 0 c
P2/end if 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/wait on clk 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Nächstes Mal berechnen 10 + 1 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c

Simulationszyklus Nr. 5

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Signale aktualisieren 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
Nächstes Mal berechnen 10 + 2 15 + 0 '1' 1 1/2 @ 15 + 0 20 + 0 clk c

Hinweis: Man könnte meinen, dass das nc Update auf 15+2 , während wir es auf 15+0 . Wenn eine physikalische Verzögerung (hier 5 ns ) zu einer aktuellen Zeit ( 10+2 ) hinzugefügt wird, verschwinden die Delta-Zyklen. Delta-Zyklen sind tatsächlich nur nützlich, um verschiedene Simulationszeiten T+0 , T+1 ... mit derselben physikalischen Zeit T . Sobald sich die physikalische Zeit ändert, können die Deltazyklen zurückgesetzt werden.

Simulationszyklus # 6

Operationen Tc Tn clk c nc P1 P2 P3
Aktuelle Uhrzeit einstellen 15 + 0 '1' 1 1/2 @ 15 + 0 20 + 0 clk c
Signale aktualisieren 15 + 0 '1' 1 2 20 + 0 clk c
Nächstes Mal berechnen 15 + 0 20 + 0 '1' 1 2 20 + 0 clk c

Hinweis: Wieder gibt es keine Ausführungsphase. nc geändert, aber kein Prozess wartet auf nc .

Simulation ... wechseln Sie auf Englisch, um weiterlesen zu können

Synchronzähler

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