www.mikrocontroller.net

VHDL

Inhaltsverzeichnis

[bearbeiten] VHDL als Programmiersprache

VHDL ist im Grunde eine ganz normale Programmiersprache die kompiliert und ausgeführt werden kann. In dieser Funktion wird VHDL haupsächlich zum Schreiben von Simulationen verwendet.

[bearbeiten] VHDL als Hardwarebeschreibungssprache

In VHDL lassen sich auch digitale Schaltungen beschreiben. Die Beschreibung wird von einer Synthesesoftware in eine Netzliste umgesetzt. Der grundlegende Unterschied zur Verwendung von VHDL als Programmiersprache ist, dass man nicht beliebigen Code schreiben kann, sondern sich an bestimmte Strukturen halten muss die der Synthesizer kennt und in Hardware umsetzen kann.

Wie diese Strukturen im allgemeinen aussehen ist weiter unten beschrieben, Details erfährt man in der Dokumentation der Software, z.B. dem XST User Guide.

[bearbeiten] Grundregeln für synthetisierbaren VHDL-Code

Folgende Grundregeln sollten vor allem Anfänger auf jeden Fall einhalten:

  • Es darf nur zwei Arten von Prozessen geben:
    1. Rein kombinatorische Prozesse:
      • Alle Signale die im Prozess gelesen werden in die sensitivity list eintragen
      • Jedes Ausgangssignal muss in jedem if-Zweig einen von sich selbst verschiedenen Wert zugewiesen bekommen, damit keine Latches entstehen (mein_signal <= mein_signal; ist unzulässig!)
      • Keine Taktflankenabfrage
    2. Rein getaktete Prozesse:
      • Nur Clk und asynchroner Reset in die sensitivity list
      • Maximal ein Reset
      • Nur eine Taktflankenabfrage (rising_edge)
  • Nur ein einziger Takt im gesamten Design, nur steigende ODER fallende Taktflanke auswerten. Um eine Flanke eines anderen Signals zu erkennen, siehe VHDL Flankenerkennung.
  • Keine Takte runterteilen, stattdessen Clock Enable verwenden.
  • Kein "after", "wait for" o.ä. verwenden, das ist nicht synthetisierbar
  • Keine shared variables verwenden
  • Variablen nur dann verwenden, wenn man genau verstanden hat was der Unterschied zu einem Signal ist, und sich das Problem nicht auch mit Signalen lösen lässt
  • Kein IEEE.STD_LOGIC_(UN)SIGNED verwenden, sondern IEEE.NUMERIC_STD (siehe Rechnen in VHDL)

[bearbeiten] Beispiel: Kombinatorischer Prozess

process(a,b,c)
begin
  y <= a + b + c;
end process;

[bearbeiten] Beispiel: Getakteter Prozess ohne Reset

process(clk)
begin
  if rising_edge(clk) then
    x <= y;
  end if;
end process;

[bearbeiten] FAQ

[bearbeiten] CLK='1' and CLK'event oder rising_edge()?

Frage: Bei der Beschreibung von FlipFlops werden je nach Buch/Programmierer zwei unterschiedliche Konstrukte benutzt:

Variante 1 (klassisch):

process(clk)
begin
  if clk = '1' and clk'event then
    --snip
  end if;
end process;

und Variante 2:

process(clk)
begin
  if rising_edge(clk) then
    --snip
  end if;
end process;

Welche ist zu empfehlen?

Antwort: Kurz gesagt bei der Synthese gibt es keine Unterschiede, in der Simulation kleine. Verwende die besser lesbare Variante. Die mit rising_edge() (bzw. falling_edge()) gilt allgemein als die bessere (Lesbarkeit und Simulationsgenauigkeit).

Wer es genau wissen will: Die klassische Variante ist nur korrekt für Signale die nur die Werte '1' und '0' annehmen können. Bei Signalen vom Typ std_logic (der Standardtyp für Signale) werden Flanken erkannt, die in echt keine sind. Zum Beispiel beim Treiben eines PullUps ('H' -> '1' keine Flanke in Echt aber für clk = '1' and clk'event) oder beim Simulationsstart ohne Initialisierungswert für die Signale ('U' -> '1').(Signale mit Initialisierungswert werden so deklariert: signal a: std_logic := '0'; ). Die Funktionen rising_edge() und falling_edge() konvertieren den std_logic Wert vor dem Vergleich auf '1' bzw. '0' und simulieren so keine Flanke wo in der echten Hardware auch keine ist.

[bearbeiten] Wann und warum verwendet man Variablen?

  • Variablen erhalten immer sofort ihren neuen Wert, Signale erst nach Delta-Delay (oder nach einer spezifizierten Zeit). Variablen können daher in einem Prozess gleich weiterverwendet werden für die nachfolgenden Berechnungen, Signale sind dabei erst mit dem nächsten Takt gültig (das Delta-Delay führt dazu, dass der Wert für diesen Durchlauf noch nicht gilt, der nächste Durchlauf des getakteten Prozesses passiert dann erst einen Clockcycle später. Bei asynchronen Prozessen wird der Prozess bei der Verwendung von Signalen dann eben nochmals gestartet ... (oder so oft, so viele voneinander abhängige Signalzuweisungen drin sind), bei Variablen wird er in einem Durchlauf berechnet (Simulationszeit!).
  • Es kann recht unübersichtlich werden, wenn man eine komplexe Berechnung hat und diese mit vielen Klammerebenen verschachtelt direkt in der Signalzuweisung beschreibt. Hier kann es besser sein, den Wert "nach und nach" über Variablen "zusammenzubauen" und erst am Schluss auf ein Signal zuzuweisen.
  • Manchmal möchte man gemeinsame Teilausdrücke vorab berechnen, z.B.
  Sig1 := A + B;
  Sig2 := C + D;
  Sig3 := E + F;
  Result1 <= Sig1 - Sig2;
  Result2 <= Sig1 - Sig3;

ist i.d.R. besser als

  Result1 <= (A + B) - (C + D);
  Result2 <= (A + B) - (E + F);

Wenns komplexer wird, ist es auch einfacher, daran was zu ändern.

  • Verschiedenen Synthesetools können manchmal mehr, manchmal weniger gut optimieren. So kann man z.B. durch das Zusammenfassen eines Teilausdruckes eine bessere Optimierung erreichen (Stichwort Resource-Sharing). Z.B.:
  if (opcode = add) then
    res <= a+b;
  else -- opcode = sub
    res <= a-b;
  end if;

oder

  if (opcode = add) then
    var1 := b;
  else
    var1 := -b;
  end if;
  res <= a+var1;

Im ersten Fall wird ggf. ein Addierer und ein Subtrahierer (noch'n Addierer) eingebaut und das Ergebniss gemultiplext, im zweiten Fall wird eventuell nur ein Addierer eingebaut und der b-Eingang des Addierers gemultiplext. Sind a und b beispielsweise 32-Bit-Vektoren, dann macht das HW-mäßig schon was aus. Gute Synthesetools sollten dies aber mittlerweile automatisch machen, so das in beiden Fällen dasselbe rauskommt (war nicht immer so). (BTW, ich selber tendiere normalerweise zur ersten Variante, da besser lesbar - und man die HW-Implementierung nicht notwendigerweise vorwegnehmen soll, aber da hat jeder seine eigene Meinung zu ...)

  • Simulatoren sind i.d.R. etwas schneller mit Variablen als mit Signalen. Hat man also viele Prozesse mit Signalen vs. Variablen in einem großen Design, dann kann das schon was ausmachen ... Siehe auch oben bei asynchronen Prozessen, die mit Signalen ggf. wesentlich öfters durchlaufen werden.

[bearbeiten] Kodierschaltungen

Kodierschaltungen (Coder) sind Schaltungen mit einem mehrstelligen Ein- und Ausgang. In der Schaltung werden keine FF oder andere Speicher benutzt. Ein typisches Beispiel ist die Wandlung einer Binärzahl in eine Binär Codierte Dezimalzahl. Eine sehr übersichtliche Schreibweise benutzt ein Konstanten-Feld.

Vor- und Nachteile verschiedener VHDL-Varianten einen Coder zu beschreiben werden hier besprochen.

[bearbeiten] If ausserhalb von einem Process?

If Bedinungen sind auserhalb eines prozesses nicht möglich. Lösung:

 vector_or <= '0' when oder_vector = X"0000" else '1';

Dies nennt der Fachman bedingte Zuweisung (conditional assignment).

[bearbeiten] Alle Leitungen auf "0000.." bzw "1111..." setzen?

Auf null setzten:

count <= (others => '0');

Auf eins setzten:

count <= (others => '1');

[bearbeiten] Report von std_logic_vector

Report kann nur Strings verarbeiten, deswegen muss ein std_logic_vector in einen String verwandelt werden:

report integer'image(to_integer(unsigned(rdata)));

[bearbeiten] parallel -> seriell: Schieberegister oder Multiplexer?

* http://www.mikrocontroller.net/topic/78556

[bearbeiten] Links

[bearbeiten] Kurzreferenzen

[bearbeiten] Online-Bücher

http://de.wikibooks.org/wiki/VHDL

[bearbeiten] Deutsch

[bearbeiten] English

webmaster@mikrocontroller.netImpressumWerbung auf Mikrocontroller.net