VHDL-Tutorium
Dieses Buch steht im Regal Elektrotechnik.
Einführung
[Bearbeiten]VHDL - ausgeschrieben "Very High Speed Integrated Circuit Hardware Description Language" ist eine Hardwarebeschreibungssprache. In VHDL können digitale Schaltungen und ihr Verhalten entworfen, beschrieben und simuliert werden. Aus den Verhaltensbeschreibungen können Netzlisten für ASICs oder auch Bitstreams für FPGAs erzeugt werden. Dieser Vorgang wird auch als Synthese bezeichnet. Hierbei ist zu beachten, dass nicht alles, was sich in VHDL beschreiben lässt, auch synthetisierbar ist.
Das kommt daher, dass VHDL auch als Simulationssprache für digitale Schaltungen entworfen ist. Man beschreibt also eine Schaltung in VHDL, bettet sie in eine ebenfalls in VHDL geschriebene Testumgebung, die Testbench, ein und lässt sich das Ganze von einem VHDL-Simulator durchrechnen. So können Fehler der Schaltungen gefunden werden, bevor diese in Produktion geht und möglicherweise auf Millionen von Chips implementiert wird. Solche Simulatoren gibt es von verschiedenen Herstellern, ein freier ist der GHDL.
Das Verhältnis von VHDL zu Programmiersprachen
[Bearbeiten]VHDL ins Verhältnis zu Programmiersprachen zu setzen, ist nicht ganz einfach, weil es auf einen Vergleich von Äpfeln mit Birnen hinausläuft. Dummerweise sehen diese Äpfel den Birnen auf den ersten Blick sehr ähnlich. Versuchen wir es trotzdem einmal.
Ein Programm, geschrieben in einer Programmiersprache, wird von einem Compiler in Maschinencode für ein Prozessorsystem übersetzt und anschließend von diesem System ausgeführt. Das Prozessorsystem besteht seinerseits aus verschiedenen Hardwarekomponenten, wie Rechenwerken, Adresserzeugungseinheiten, Speichern und Registern.
Der Prozessor selbst ist ein Automat, der Datenworte aus einem Speicher liest und interpretiert. Je nachdem, welchen Befehlscode das Datenwort enthalten hat, kann der Prozessor nun seine Hardwareeinheiten auf eine festgelegte Weise ansteuern, um die codierte Funktion auszuführen (z.B. "Addiere zwei Zahlen miteinander").
Der Hauptunterschied zwischen Programmierung und Hardwarebeschreibung ist, dass ein Prozessorsystem bereits eine spezielle Art von Hardwareschaltung ist, die durch ein Programm im Rahmen ihrer Möglichkeiten gesteuert werden kann. Diese Möglichkeiten sind in Form der Maschinensprache vom Hersteller bereits vorgegeben und können (normalerweise) im Nachhinein nicht mehr verändert oder erweitert werden. Ein Prozessor ist bereits eine Hardwarebeschreibung, umgesetzt auf echte Elektronik.
Hardwarebeschreibungssprachen wie VHDL setzen also eine ganze Ebene tiefer an, als Programmiersprachen. Es ist kein Problem, ein Prozessorsystem in VHDL zu beschreiben, zu simulieren (um die Korrektheit der Beschreibung zu prüfen) und per Synthese die Daten zu erzeugen, die es einem Fertiger ermöglichen, genau diese Schaltung als Chip zu bauen.
Die Synthese erzeugt dabei eine Art Schaltplan (Netzliste) aus digitalelektronischen Standardkomponenten, wie Flip-Flops und Logikgattern. In einem zweiten Schritt kann diese Netzliste dann auf die Standardzellbibliothek eines Fertigers für einen Mikrochip oder auf ein FPGA abgebildet werden. Im Falle, dass das Syntheseziel ein FPGA ist, kann jetzt noch der Bitstrom zur Konfiguration erzeugt werden. Wird dieser in das FPGA geladen, verhält es sich anschließend so, wie es die Beschreibung vorgibt.
Achtung: Auch wenn das Einspielen des Bitstroms in ein FPGA an das Flashen eines Mikrocontrollers erinnert, bewirkt es etwas völlig anderes! Der Mikrocontroller wird, wie eben ein Prozessorsystem, die Befehle schrittweise verarbeiten, sobald das Flashen abgeschlossen ist und das System anläuft. Der Bitstrom schaltet dagegen direkt elektrische Verbindungen zwischen den Grundbausteinen des FPGAs auf dem Chip.
Klassen digitaler Schaltungen
[Bearbeiten]Digitale Schaltungen kann man in zwei Klassen einteilen: speicherfreie oder auch kombinatorische Logik und sequenzielle Schaltungen.
Unter kombinatorischer Logik versteht man Schaltungen, die nur aus miteinander kombinierten logischen Verknüpfungen, wie NOT-, AND-, OR-, oder XOR-Gattern, bestehen und keine speichernden Elemente beinhalten. Änderungen von Eingangssignalen bewirken, idealisiert betrachtet, eine sofortige Reaktion am Ausgang der Schaltung. Ein Taktsignal wird nicht benötigt.
Sequenzielle Schaltungen dagegen enthalten speichernde Elemente wie Latches oder Flip-Flops. Latches arbeiten ohne Takt, meist gibt es ein Enable-Signal, bei dessen Aktivierung der Ausgang des Latches dem Eingang folgt, solange es aktiv ist. Wird das Enable-Signal deaktiviert, behält der Ausgang des Latches den letzten Zustand bei, speichert also. Dieses Verhalten wird auch als Level-Triggered bezeichnet. Flip-Flops sind dagegen getaktet, sie schalten den am Eingang anliegenden Pegel nur während einer Taktflanke an den Ausgang durch. Dieses Verhalten nennt man Edge-Triggered oder Flankengetriggert.
In den üblichen digitalen, getakteten Systemen werden praktisch ausschließlich Flip-Flops verwendet. Entsteht während der Synthese ein Latch, weist das meist auf einen Beschreibungsfehler hin! Mehrere zu einer Einheit zusammengefasste Flip-Flops nennt man auch ein Register.
Eine digitale Schaltung wird meist so aufgebaut, dass sich zwischen zwei Registern oder Flip-Flops ein Block aus kombinatorischer Logik befindet. Häufig wird auch die VHDL-Beschreibung so strukturiert, dass es einen Prozess gibt, der den speicherfreien Teil der Schaltung beschreibt, sowie einen zweiten Prozess, der nur die Register beschreibt ("Zwei-Prozess-Schreibweise"). Prozesse werden später detailierter behandelt.
Grundfunktionen digitaler Logik-ICs
[Bearbeiten]An rekonfigurierbaren Logik-ICs sind heute vor allem CPLDs und FPGAs verbreitet. CPLDs sind einfacher aufgebaut, bieten dafür aber auch nicht die Logikdichte von FPGAs. Dafür sind sie billiger, benötigen weniger externe Bauteile und sind nach dem Einschalten schneller betriebsbereit. Für die Beschreibung in VHDL sind die Unterschiede jedoch irrelevant.
Ein modernes FPGA besteht hauptsächlich aus Lookup-Tables (LUTs), mit denen der kombinatorische Schaltungsteil realisiert wird, sowie Flip-Flops. Meist werden mehrere LUTs mit der gleichen Anzahl Flip-Flops zu Blöcken zusammengefasst und z.B. als ALM (Altera: Adaptive Logic Module) oder CLB (Xilinx: Configurable Logic Block) bezeichnet. Die Größe eines Designs drückt sich darin aus, wie viele dieser Logikblöcke benötigt werden. Diese Blöcke sind so flexibel, dass sie jede synthetisierbare Funktion abbilden können.
Neben diesen Alleskönnern sind auf einem FPGA meist noch weitere, spezielle Blöcke vorhanden. Gängig sind SRAM-Speicherblöcke, Multiplizierer und Clock-Management-Blöcke. Es können jedoch auch ganze Mehrkern-Prozessoren integriert werden. Diese auch Hard-Macro genannten Blöcke stellen häufig benötigte Teilschaltungen in optimierter Form zur Verfügung. Speicher oder Multiplizierer, aus Standard-Logikblöcken realisiert, belegen verhältnismäßig viele Ressourcen und können die erreichbare Geschwindigkeit des Designs negativ beeinflussen.
VHDL Grundlagen
[Bearbeiten]Struktur einer VHDL-Beschreibung
[Bearbeiten]In VHDL werden große Schaltungssysteme in der Regel hierarchisch aus kleineren Teilschaltungen zusammen gesetzt, die in ihren eigenen Modulen beschrieben sind. Ähnlich wie eine reale Schaltung auf einer Platine auch aus unterschiedlichen ICs besteht, die miteinander verbunden sind. Der Aufbau einer VHDL-Beschreibung ähnelt von der Struktur her dann auch sehr einem Datenblatt für ein IC.
Das Pinout im Datenblatt wird in VHDL durch eine Entity ersetzt, wo in Form einer Port-Definition die Schnittstelle des Moduls beschrieben wird. Das Verhalten bzw. die Funktionsweise des Bausteins wird in VHDL in (mindestens) einer Architecture untergebracht. Zu einer Entity können mehrere Architectures existieren. Bei der Instanziierung des Moduls kann dann angegeben werden, welche davon zum Einsatz kommen soll. Das kann praktisch sein, wenn verschiedene Architectures auf unterschiedliche Ziele optimiert sind (z.B. schnell und groß vs. klein und langsam).
Vorgehen bei der Formulierung
[Bearbeiten]Um diese beiden Arten der realen Hardware nachzubilden, ist die grundsätzliche Denkweise für ein VHDL-Programm deutlich anders, als es für den seriellen Ablauf beispielsweise eines C-Programms nötig ist. VHDL ist im Grunde eine parallele Aneinanderreihung von Prozessen, die quasi simultan abgearbeitet werden. Der VHDL-Simulator wird zwar den Code in irgendeiner Weise in eine serielle Software umwandeln. Die Wirkungsweise der Prozesse ist jedoch so, als würden sie wirklich völlig gleichzeitig bearbeitet.
Zu welchem Zeitpunkt ein Prozess abgearbeitet wird, bestimmt die Sensitivity-List. Dies ist im Prozess-Header eine Liste von vereinbarten Signalen. Ändert eines dieser Signale den Wert, so wird der Prozess angestoßen. Führt dieses zu einer Änderung eines Ausgangs und ist dieser wiederum in der Sensitivity-List eines anderen Prozesses, so wird auch dieser mit dem neuen Wert des Signals angestoßen. Wichtig ist hierbei wieder das Wissen um die Tatsache, dass die sequentielle Anordnung der Prozesse und auch der Abläufe lediglich in der Softwaresimulation relevant ist. Damit ist auch die Reaktion derselben auf die S-Liste für die letzliche Struktur der Hardware irrelevant.
Man unterscheidet zwei Typen von Prozessen: Kombinatorische und synchron getaktete Prozesse, analog zu der eingangs besprochenen realen Hardware.
Kombinatorische Prozesse haben in der Sensitivity-List alle Eingangssignale und beschreiben im Inneren deren Verknüpfung. Synchron getaktete Prozesse haben in der Sensitivity-List nur „reset“ und „clock“. Im Inneren wird beschrieben, welches Signal oder auch welche Verknüpfung von Signalen zur Taktflanke am Ausgang übernommen werden soll.
Beispiel für einen kombinatorischen Prozess
[Bearbeiten] procname: process(a,b,c)
begin
x <= (a and b) or c;
end process;
- Ausgang "x" ist eine Verknüpfung von "a","b","c"
Beispiel für einen synchron getakteten Prozess (incl. asynchronem, low-aktivem Reset)
[Bearbeiten] procname: process(nres,clk)
begin
if (nres='0') then
q <= '0';
elsif (clk'event and clk='1') then
q <= x;
end if;
end process;
- FlipFlop mit Ausgang "q" schaltet mit steigender "clk"-Flanke und übernimmt den Wert von "x"
- x stammt aus kombinatorischem Prozess!!
Beispiele zu den aus verschiedenen Sprachen bekannten Konstrukten
[Bearbeiten] n: process(a,b)
begin
x <= a and b; -- Und-Verknüpfung von "a" und "b"
end;
dazu gleichwertig:
n: process(a,b)
begin
if (a='1') and (b='1') then
x <= '1';
else
x <= '0';
end if;
end;
dazu gleichwertig:
n: process(a,b)
variable ab : std_logic_vector(1 downto 0);
begin
ab(0) := a;
ab(1) := b;
case ab is
when "00" => x <= '0';
when "01" => x <= '0';
when "10" => x <= '0';
when "11" => x <= '1';
when others => null;
end case;
end;
Entitys und Architectures
[Bearbeiten]Im Weiteren baut VHDL einen begrenzten Rahmen von logischen Elementen als ein Bauelement zusammen, das wiederum mit der diskreten Schaltungstechnik vergleichbar ist. So ist es ähnlich wie in der altbekannten TTL-Schaltungstechnik, dass ein solches Bauteil Eingänge, Ausgänge und ein Innenleben hat. VHDL definiert dieses Element oder diesen Block mit seinen "Pins" in der "Entity". Die "Architecture" beschreibt dann mit den oben gezeigten Prozessen das Innenleben.
Formal
[Bearbeiten]entity 74LS00 is
port (i0,i1 : in bit;
i2,i3 : in bit; -- Eingänge
o0 : out bit; -- Ausgang
o1 : out bit);
end 74LS00;
Verwendung der Entity:
architecture behv of 74LS00 is
signal z : bit; -- Vereinbarung internen Signale
begin
comb_1: process(i0,i1,i2,i3)
begin
o0 <= not(i0 and i1); -- nand nr.1
z <= not(i2 and i3); -- nand nr.2
end comb_1;
comb_2: process(z)
begin
o1 <= z;
end comb_2;
end behv;
Erläuterung
[Bearbeiten]Man sieht: In der "entity" wird eine Portliste vereinbart, die alle nach außen führenden Signale beinhalten muss. Interne Signale werden wie oben gezeigt vereinbart. Üblicherweise führen diese nach der Synthese zu realen "Drähten" in der Schaltung, können aber auch, wie in diesem Fall "z", wegoptimiert werden.
Besonderheit: Signale, die den Block verlassen, können nicht in der "architecture" verschaltet werden. Das heißt, alle in der entity als "out" deklarierten Signale können nirgends in der "sensitivity list" eines Prozesses oder als Zuweisungswert erscheinen. Sie sollen ebenfalls nur in einem einzigen Prozess des VHDL-Files zugewiesen werden, so wie ein "Draht" ebenfalls zu einem Zeitpunkt immer nur einen Pegel führen kann.
Ein File mit einer Entity und einer Architecture ist bereits eine kompilierbare, vollständige VHDL-Komponente. Ähnlich wie auf einer Platine können auch mehrere VHDL-Bauteile miteinander quasi verdrahtet werden. In diesem Fall wird in einem anderen oder auch gleichen VHDL-File diese Komponente als "component" eingebunden.
Beispiel einer "Component"-Deklaration:
component 74LS00
port (i0,i1,i2,i3 : in bit;
o0,o1 : out bit);
end component;
Beispiel für eine "Component"-Instanziierung:
architecture behv of test is
-- Signal-Deklarierungen:
signal a,b,c,d : bit;
-- Componenten-Deklarierungen:
component adder
port(a,b : in bit;
sum,carry : out bit);
end component;
begin
teil_a: adder port map (a,b,c,d); -- Komponente "adder" hat den Namen "teil_a"
-- a,b,c,d ist angeschlossen
end behv;
Basis-Konstrukte
[Bearbeiten]Nach dieser kurzen Einführung über die grundsätzlichen Gedanken von VHDL sollen im Folgenden in alphabetischer Reihenfolge die sprachlichen Basis-Konstrukte beschrieben werden:
aggregates
[Bearbeiten]Ein Aggregat ist ein Klammerausdruck, der mehrere Einzelelemente zu einem Vektor zusammenfasst, wobei die Elemente durch Kommata getrennt werden.
(wert_1,wert_2,...) (element_1 => wert_1,element_2 => wert_2,...)
Beispiele:
signal databus : bit_vector(3 downto 0);
signal d1,d2,d3,d4 : bit;
...
databus <= (d1,d2,d3,d4);
identisch mit:
databus(3) <= d1;
databus(2) <= d2;
databus(1) <= d3;
databus(0) <= d4;
Weiteres Beispiel:
type zustand is (idle,run,warte,aktion); -- enumerated type
signal state : zustand;
type packet is record
flag : std_logic;
nummer : integer range 0 to 7;
daten : std_logic_vector(3 downto 0);
end record;
signal x : packet;
x <= ('1',3,"0011");
type vierbit is array(3 downto 0) of std_ulogic;
type speicher is array(0 to 7) of vierbit;
variable xmem : speicher := (others=>'0'); -- mit '0' vorbelegen
variable dbus : std_logic_vector(15 downto 0) := (others=>'Z');
alias
[Bearbeiten]Ein Alias bezeichnet einen Teil eines Signals.
alias ''alias_name'' : ''alias_type''(range) is ''signal_name''(range);
Beispiel:
signal daten_in : bit_vector(11 downto 0);
alias opcode : bit_vector(3 downto 0) is daten_in(3 downto 0);
alias daten : bit_vector(7 downto 0) is daten_in(11 downto 4);
architecture
[Bearbeiten]Eine Designeinheit in VHDL, die das Verhalten oder die Struktur einer Entity beschreibt.
architecture ''architecture_name'' of ''entity_name'' is
declarations
begin
concurrent statements
end architecture;
arrays
[Bearbeiten]type ''type_name'' is array (range) of ''element_type'';
Beispiele:
type speicher is array (0 to 1023) of integer range 0 to 255;
signal sram : speicher;
type mem8 is array (natural range <>) of std_logic_vector(7 downto 0);
signal sram8_1024 : mem8(1023 downto 0);
signal sram8_8 : mem8(7 downto 0);
Zugriff/Initialisierung:
-- beide gleichwertig, Elemente 1 und 2 werden mit "5" initialisiert:
sram8_8 <= ("0", "0", "0", "0", "0", "5", "5", "0" );
sram8_8 <= ( 2 | 1 => "5", others => "0");
-- oder einzelnes Element mit 0xB initialisieren
sram8_8(2) <= x"B"
assert
[Bearbeiten]assert ''bedingung'' report ''string'' severity ''severity_level'';
Überwache, dass bedingung erfüllt ist, wenn NICHT, dann report string mit severity severity_level
severity_level ist "error" (default), "note", "warning" oder "failure"
Beispiel:
assert (a > c) report "a muss grösser c sein" severity note;
assert (true) report "diese Meldung wird nie ausgegeben" severity note;
assert (false) report "diese Meldung wird immer ausgegeben" severity note;
attributes
[Bearbeiten]T repräsentiert einen beliebigen Typ, A repräsentiert ein Array, S repräsentiert ein beliebiges Signal und E repräsentiert eine Entity.
Attribut | Beschreibung |
---|---|
T'BASE | Basistyp von T |
T'LEFT | Wert am weitesten links in T. (Grösster wenn downto) |
T'RIGHT | Wert am weitesten rechts in T. (Kleinster wenn downto) |
T'HIGH | Grösster Wert in T. |
T'LOW | Kleinster Wert in T. |
T'ASCENDING | Boolean, TRUE wenn range definiert mit "to". |
T'IMAGE(X) | String der den Wert X repräsentiert. |
T'VALUE(X) | Wert vom Typ T, konvertiert vom String X. |
T'POS(X) | Integer Position von X im diskreten Typ T. |
T'VAL(X) | Wert vom diskreten Typ an integer Position X. |
T'SUCC(X) | Wert vom diskreten Typ der auf X folgt |
T'PRED(X) | Wert vom diskreten Typ der vor X liegt |
T'LEFTOF(X) | Wert vom diskreten Typ links von X |
T'RIGHTOF(X) | Wert vom diskreten Typ rechts von X |
A'LEFT | Eintrag ganz links in A |
A'LEFT(N) | Eintrag ganz links in Dimension N von A |
A'RIGHT | Eintrag ganz rechts in A |
A'RIGHT(N) | Eintrag ganz rechts in Dimension N von A |
A'HIGH | Höchster Eintrag in A |
A'HIGH(N) | Höchster Eintrag in Dimension N von A |
A'LOW | Tiefster Eintrag in A |
A'LOW(N) | Tiefster Eintrag in Dimension N von A |
A'RANGE | Range von A'LEFT to A'RIGHT oder A'LEFT downto A'RIGHT |
A'RANGE(N) | Range von Dimension N in A |
A'REVERSE_RANGE | Range in A to und downto umgekehrt |
A'REVERSE_RANGE(N) | REVERSE_RANGE von Dimension N in A |
A'LENGTH | Integer Wert der Anzahl Elemte in A |
A'LENGTH(N) | Anzahl Werte in Dimension N von A |
A'ASCENDING | Boolean, TRUE wenn range definiert mit "to" |
A'ASCENDING(N) | Boolean, TRUE wenn Dimension N in A definiert mit "to" |
S'DELAYED(t) | Signalwert zur Zeit NOW -t |
S'STABLE | TRUE, wenn kein Event in S |
S'STABLE(t) | TRUE, wenn kein Event in S für Zeit t |
S'QUIET | TRUE, wenn kein Event in diesem Simulations Zyklus |
S'QUIET(t) | TRUE, wenn kein Event in S für Zeit t |
S'TRANSACTION | Bit Signal, invertiert immer wenn Signal S ändert |
S'EVENT | TRUE, wenn Signal S Event in diesem Simulationszyklus hatte |
S'ACTIVE | TRUE, wenn Signal S aktiv in diesem Simulationszyklus |
S'LAST_EVENT | Zeit seit letztem Event auf Signal S |
S'LAST_ACTIVE | Zeit seit Singal S zuletzt aktiv |
S'LAST_VALUE | Vorhergehender Wert von Signal S |
S'DRIVING | |
S'DRIVING_VALUE | |
E'SIMPLE_NAME | String mit Name der Entitiy E |
E'INSTANCE_NAME | String mit Designs Hirarchie inkl. Entity E |
E'PATH_NAME | String zu Design Wurzel von E |
Beispiele:
signal'LEFT 7 bei std_logic_vector(7 downto 0); 0 bei std_logic_vector(0 to 7); signal'RIGHT 0 bei std_logic_vector(7 downto 0); 7 bei std_logic_vector(0 to 7); signal'HIGH 7 bei std_logic_vector(7 downto 0); 7 bei std_logic_vector(0 to 7); signal'LOW 0 bei std_logic_vector(7 downto 0); 0 bei std_logic_vector(0 to 7); signal'RANGE 7 downto 0 bei std_logic_vector(7 downto 0); 0 to 7 bei std_logic_vector(0 to 7); signal'REVERSE_RANGE 0 to 7 bei std_logic_vector(7 downto 0); 7 downto 0 bei std_logic_vector(0 to 7); signal'LENGTH 8 bei std_logic_vector(7 downto 0); 8 bei std_logic_vector(0 to 7); signal'EVENT if (clk'event and clk='1') then
block statements
[Bearbeiten]
''block_name'': block
declarations
begin
concurrent statements
end block;
case
[Bearbeiten]case ''expression'' is
when ''fall_1'' => ''sequential statement''
when ''fall_2'' => ''sequential statement''
when others => ''sequential statement''
end case;
Erstes Beispiel:
case wert is
when 0 => w <= '1';
when 1 => w <= '0';
when 2 | 3 => w <= a;
when 4 to 7 => w <= b;
when others => w <= 'X';
end case;
Zweites Beispiel:
wert <= '0';
case din is
when "00" => wert <= '1';
when others => null; -- "when others" soll immer vorhanden sein
end case; -- durch default-Zuweisung is "null"-statement möglich!
Beispiel 3:
type state_type is ( IDLE, DO_SOMETHING );
signal s_state : state_type :=IDLE;
...
case s_state is
when IDLE => tx_line <= '0';
when DO_SOMETHING => tx_line <= '1';
when others =>
assert false report "case defaulted!" severity failure;
end case;
component declaration
[Bearbeiten]Deklaration zur Festlegung des Namens und der Schnittstelle einer Komponente, die einer Entitydeklaration und Architecture zugeordnet sein muss.
component ''component_name''
generic (''generic_liste'');
port (''port_liste'');
end component;
component instantiation
[Bearbeiten]label: ''component_name''
generic map ( ''generic1'' => '' generic1_entity'' )
port map (
''component_port1'' => ''entity_port1'',
''component_port2'' => ''entity_port2'',
...
''component_portx'' => ''entity_portx''
);
concatenation
[Bearbeiten]Verkettung von Vektoren. Kann dort benutzt werden, wo keine aggregates erlaubt sind, z.b. innerhalb von Funktionen.
Beispiel
signal a : STD_LOGIC_VECTOR(3 downto 0) := "1111";
signal b : STD_LOGIC_VECTOR(3 downto 0) := "0000";
signal c : STD_LOGIC_VECTOR(7 downto 0);
c <= a & b; --Dem Vektor ''c'' werden die 8 Bits aus ''a'' und ''b'' zugewiesen ("11110000")
constant
[Bearbeiten]constant ''constant_name'' : type := value;
Beispiel:
constant festwert : std_logic_vector(7 downto 0) := "10101100";
constant zeitwert : time := 50 ns;
type rdatum is array (0 to 3) of bit_vector(7 downto 0);
constant rom : rdatum :=
("00000001",
"00000010",
"00000011",
"00000100");
entity
[Bearbeiten]Eine Struktureinheit in einem VHDL- Entwurfssystem. Beschreibt die Schnittstellen eines VHDL-Funktionsblocks nach außen. Mit Hilfe von Port-Anweisungen erfolgt die Deklaration der Anschlüsse innerhalb der Entity. Zu jeder Entity gehört eine Architecture.
entity ''entity_name'' is
generic (generic_list);
port (port_list);
end ''entity_name'';
exit-Anweisung
[Bearbeiten]Mit der exit-Anweisung wird die "innerste" Schleife verlassen und mit der Anweisung, die direkt auf die Schleife folgt, fortgefahren.
Beispiel: Bestimmen der Anzahl der führenden Nullen
for i in signal'range loop
exit when signal(i)='1';
null_v := null_v + 1;
end loop;
file declaration
[Bearbeiten]Beispiel der Benutzung von Dateien:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_textio.all;
library std;
use std.textio.all;
architecture sim of dut is
file my_file : text open write_mode is "my_file.dat";
begin
proc: process(clk)
variable outline : line;
variable counter : integer := 0;
begin
if rising_edge(clk) then
write(outline, string'("Takt: "));
write(outline, counter);
writeline(my_file, outline);
counter := counter + 1;
end if;
end process proc;
end sim;
for loop
[Bearbeiten]synthesefähig: ja. Bei der Synthese einer for loop wird eine Kette aus Logikblöcken (z.B. einfache Bausteine wie LUTs, Flipflops oder komplexere Kombinationen von solchen) auf dem Chip erzeugt. Für jeden Schleifendurchlauf wird in der Kette der gleiche Block hardwaremäßig angehängt.
Bei der Simulation einer for loop wird die Schleife zeitlich sequentiell durchlaufen.
''eventuell_label'': for ''parameter'' in ''range'' loop
sequential statements
end loop ''eventuell_label'';
- Parameter muss nicht deklariert werden.
- Parameter darf im loop nicht verändert werden
Muss loop synthetisierbar sein:
- range ist statisch
- keine wait statements im loop
Beispiel:
for i in 0 to 7 loop w(i) <= a(i) and b; -- 8bit bus "a" mit Einzelsignal "b" verunden end loop;
Beispiel 2:
for i in 1 to 10 loop
if (REPEAT = '1') then
i := i-1; -- Error
end if;
end loop;
functions
[Bearbeiten]Eine der beiden Möglichkeiten in VHDL, Code mittels eines einfachen Aufrufmechanismus wiederverwertbar zu machen. Functions werden gewöhnlich mit ihrem Namen und einer in Klammern stehenden Liste der Eingangsparameter aufgerufen und können nur ein Ausgangsargument liefern- vgl. auch Procedure.
function ''funktion_name'' (parameter_list) return ''type'' is
declarations
begin
sequential statements
end function_name;
Beispiel:
function parity_generator (din : std_ulogic_vector)
return std_ulogic is
variable t : std_ulogic := '0'; -- variable mit default Zuweisung
begin
for i in din'range loop -- ganze Busbreite
t := t xor din(i);
end loop;
return t;
end parity_generator;
Aufruf der Funktion als "concurrent" oder "sequential statement":
sig_pary <= parity_generator(data_bus);
Achtung: keine "signal assignments" oder "wait"
generate
[Bearbeiten]''label'': for ''parameters'' in ''range'' generate
concurrent statements
end generate ''label'';
oder
''label'': if ''condition'' generate
concurrent statements
end generate ''label'';
Beispiel:
architecture gen of test is
component volladdierer
port (x,y,ci : in bit;
s,co : out bit);
end component;
component halbaddierer
port (x,y : in bit;
s,co : out bit;
end component;
signal carry : bit_vector(0 to 7);
begin
gen_addierer: for i in 0 to 7 generate
niedrigstes_bit: if i=0 generate
w0: entity halbaddierer port map
(x(i),y(i),s(i),carry(i));
end generate niedrigstes_bit;
hoeheres_bit: if i>0 generate
wi: entity volladdierer port map
(x(i),y(i),carry(i-1),s(i),carry(i));
end generate hoeheres_bit;
end generate gen_addierer;
co <= carry(7);
end gen;
Beispiel2:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
...
dft_mem_in : in std_logic_vector(const1 downto 0);
dft_mem_out : out std_logic_vector(const2 downto 0);
pwr_mem_ctrl_in : in std_logic_vector(3 downto 0)
...
gen0: for i in dft_mem_out'range generate
signal tmp : std_logic_vector(dft_mem_out'range);
begin
tmp <= std_logic_vector(resize(unsigned(dft_mem_in), dft_mem_out'length));
dft_mem_out(i) <= tmp(i) xor (pwr_mem_ctrl_in(0) xor pwr_mem_ctrl_in(1) xor pwr_mem_ctrl_in(2) xor pwr_mem_ctrl_in(3));
end generate gen0;
generic
[Bearbeiten]Mit dem generic Konstrukt können Parameter festgelegt werden, die im Quelltext bei der Verwendung der component oder der entity änderbar sind, die sich aber während der Simulation bzw. nach der Synthese nicht mehr ändern.
Verwendung in den Konstrukten component declaration oder entity
synthesefähig: ja
entity ''entity_name'' is
generic (generic_list);
port (port_list);
end ''entity_name'';
Beispiel: (wichtig für skalierbare Blöcke!!)
entity test is
generic (n : integer := 15); -- hierbei ist 15 der Default-Wert, falls kein Generic
-- bei der Initialisierung angegeben wird
port (a : in std_ulogic_vector(n-1 downto 0));
end test;
if
[Bearbeiten]Die if-Anweisung ist nur innerhalb von Prozessen erlaubt, da sie ein sequentielles Konstrukt ist.
-- Allgemeine Syntax
if ''condition_a'' then
sequential statements
elsif ''condition_b'' then
sequential statements
else
sequential statements
end if;
-- Beispiel
if nreset='0' then
count <= 0;
elsif clk'event and clk='1' then
if count=9 then
count <= 0;
else
count <= count+1;
end if;
end if;
next-Anweisung
[Bearbeiten]Die next-Anweisung beendet den aktuellen Schleifendurchlauf vorzeitig; das bedeutet, dass die Anweisungen bis zur end-loop-Anweisung übersprungen werden und mit dem nächsten Schleifendurchlauf fortgefahren wird.
Beispiel: Bestimmen der Anzahl der Nullen in einem Vektor
for i in signal'range loop
next when signal(i)='1';
null_v := null_v + 1;
end loop;
notations
[Bearbeiten]hex_var := 16#8001#;
binary_s <= b"000_111_010";
octal_s <= o"207";
hex_s <= x"01_FB";
null statement
[Bearbeiten]Falls durch die Syntax ein Statement erforderlich ist, kann das "Null"-Statement verwendet werden, um anzuzeigen, dass nichts zu tun ist. Vgl. hierzu beispielsweise 'when others => null;' einer case-Anweisung.
proc: process(clk)
begin
if rising_edge(clk) then
case select is
when "00" => reg <= '1';
when "11" => reg <= '0';
when others => null;
end case;
end if;
end process proc;
operators
[Bearbeiten]Logische Operatoren für Typen: bit,boolean,bit_vector,std_logic,std_logic_vector
and -- und
or -- oder
nand -- nicht und
nor -- nicht oder
xor -- exclusive oder
xnor -- exclusives nicht oder
Vergleichs Operatoren Ergebnis: boolean
= -- Gleichheit
/= -- Ungleichheit
< -- kleiner
> -- groesser
<= -- kleiner gleich (Achtung bei Type int: Speichern)
>= -- grösser gleich
Arithmetische Operatoren für Typen: integer,real
a <= a + 7; -- Addition
r1 <= r2 - 3.1415 -- Subtraktion (real)
m <= x * y -- Multiplikation
d <= m / 2 -- Division
VHDL93:
sll -- shift left logical
srl -- shift right logical
sla -- shift left arith.
sra -- shift right arith.
rol -- rotate left
ror -- rotate right
package
[Bearbeiten]package ''package_name'' is
declarations
end package;
Beispiele:
package demo is
constant nullwert : bit_vector := "00000000";
function foo ( v : std_ulogic ) return std_ulogic;
component adder -- Dessen Implementierung ist vielleicht in irgendeiner Library vorcompiliert,
port(x,y,ci : in bit; -- da ein Component niemals in der package body definiert werden kann.
s,co : out bit);
end component;
end demo;
package body demo is
function foo ( v : std_ulogic ) return std_ulogic is
begin
return v;
end function;
end package body;
Package-Aufruf lautet dann:
use work.demo.all;
entity xx is
port
( wert : out bit_vector(7 downto 0));
end xx;
architecture behv of xx is
begin
wert <= nullwert;
end behv;
port
[Bearbeiten]Mit port können die Ein- und Ausgänge festgelegt werden, mit der die entity mit der Umgebung kommuniziert, in der sie eingebunden wird.
Verwendung in den Konstrukten component declaration oder entity.
synthesefähig: ja
entity ''entity_name'' is
generic (generic_list);
port (port_list);
end ''entity_name'';
Beispiel: (wichtig für skalierbare Blöcke!!)
entity test is
generic (n : integer := 15); -- hierbei ist 15 der Default-Wert, falls kein Generic
-- bei der Initialisierung angegeben wird
port (a : in std_ulogic_vector(n-1 downto 0));
end test;
procedures
[Bearbeiten]synthesefähig: ja, falls die Prozedur kein wait enthält. Bei der Synthese einer Prozedur werden die aufeinanderfolgenden Zeilen in eine Kette von Logikbausteinen (z.B. LUTs, Flipflops) umgesetzt.
Parameter können constant, variable oder Signale sein. Auf Signale kann gelesen (in) oder geschrieben (out) werden.
procedure ''procedure_name'' (paramter_list) is
declarations
begin
sequential statements
end ''procedure_name'';
Beispiel:
procedure parity_generator
(signal din : in std_ulogic_vector;
signal par : out std_ulogic) is
variable t : std_ulogic := '0';
begin
for i in 0 to din'range loop
t := t xor din(i);
end loop;
par <= t;
end parity_generator;
Proceduren in Packages:
package my_package is
procedure parity_generator
(signal din : in std_ulogic_vector;
signal par : out std_ulogic);
end my_package;
package body my_package is
procedure parity_generator
(signal din : in std_ulogic_vector;
signal par : out std_ulogic) is
variable t : std_ulogic := '0';
begin
for i in 0 to din'range loop
t := t xor din(i);
end loop;
par <= t;
end parity_generator;
end my_package;
Aufruf:
...
parity_generator(databus,par_bit);
...
process
[Bearbeiten]''optionales_label'': process (optionale sensitivity liste)
declarations
begin
sequential statements;
end process optionales_label;
records
[Bearbeiten] type my_record_t is record
element1 : std_logic;
element2 : std_logic;
element3 : std_logic_vector(1 downto 0);
element4 : std_logic_vector(4 downto 0);
end record;
type my_array_t is array (3 downto 0) of my_record_t;
Benutzung:
signal my_signal_s : my_array_t;
my_signal_s <= (others => ('0','0',(others => '0'),(others => '0')));
type definiert in VHDL
[Bearbeiten] type Time is range MIN_VALUE to MAX_VALUE --implementation defined--
units
fs; -- femtosecond
ps = 1000 fs; -- picosecond
ns = 1000 ps; -- nanosecond
us = 1000 ns; -- microsecond
ms = 1000 us; -- millisecond
sec = 1000 ms; -- second
min = 60 sec; -- minute
hr = 60 min; -- hour
end units Time;
type struktur
[Bearbeiten]types-+-scalar----+-discrete-------+-integer-------+-integer | | | +-natural | | | +-positive | | | | | +-enumeration---+-boolean | | +-bit | | +-character | | +-file_open_kind | | +-file_open_status | | +-severity_level | | | +-floating point-+-----------------real | | | +-physical-------+-----------------delay_length | +-----------------time | +-composite-+-array----------+-constrained- | | | | | +-unconstrained-+-bit_vector | | +-string | | | +-record- | +-access- | +-file-
Typumwandlung (type conversion)
[Bearbeiten]library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
std_logic_vector ? unsigned ¦ unsigned(arg) std_logic_vector ? signed ¦ signed(arg) std_logic_vector ? integer ¦ std_logic_vector->signed/unsigned->integer natural ? unsigned ¦ to_unsigned(arg,size) integer ? signed ¦ to_signed(arg,size) integer ? std_logic_vector ¦ integer->signed->std_logic_vector unsigned ? std_logic_vector ¦ std_logic_vector(arg) unsigned ? integer ¦ to_integer(arg) signed ? std_logic_vector ¦ std_logic_vector(arg) signed ? integer ¦ to_integer(arg)
unsigned ? unsigned ¦ resize(arg,size) signed ? signed ¦ resize(arg,size)
std_ulogic ? bit ¦ to_bit(arg) std_ulogic_vector ? bit_vector ¦ to_bitvector(arg) std_ulogic_vector ? std_logic_vector ¦ to_stdlogicvector(arg) bit ? std_ulogic ¦ to_stdulogic(arg) bit_vector ? std_logic_vector ¦ to_stdlogicvector(arg) bit_vector ? std_ulogic_vector ¦ to_stdulogicvector(arg) std_logic_vector ? std_ulogic_vector ¦ to_stdulogicvector(arg) std_logic_vector ? bit_vector ¦ to_bitvector(arg)
Beispiele:
signal value_i : integer range 0 to 15;
signal value_u : unsigned(3 downto 0);
signal value_slv : std_logic_vector(3 downto 0);
…
value_i <= 14;
value_u <= to_unsigned(value_i, value_u'length); -- Typumwandlung
value_slv <= std_logic_vector(value_u); -- cast (Typen sind eng verwandt)
type declaration
[Bearbeiten] type <memory_type> is array(0 to 9) of std_logic_vector(7 downto 0);
type <fsm_type> is (idle, run, ready);
type <hour_range_type> is range 1 to 12;
subtype <vector_type> is std_logic_vector(5 downto 0);
use
[Bearbeiten]Verwendung von Bibliotheken, oder Teilen daraus.
Beispiel:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
variable declaration
[Bearbeiten]variable ''Variablenname'' : ''Typ'' := ''Initialisierungswert''
Beispiel:
variable MeinBool : boolean := false;
variable assignment
[Bearbeiten]''Variablenname'' := ''Wert'';
wait
[Bearbeiten]- wait until condition
- wait on signal list
- wait for time -- nicht synthetisierbar
- wait;
Beispiel:
wait until din="0010";
oder:
stimulus: process
begin
loop
clk <= '0';
wait for 50 ns;
clk <= '1';
wait for 50 ns;
end loop;
end process;
when
[Bearbeiten]signal_name <= expression when condition else expression when condition else expression;
Beispiel (Tristate Port):
data <= data_out when data_enable = '1' else 'Z';
while
[Bearbeiten]while ''condition'' loop
sequential statements
end loop;
Beispiel (Register rechts schieben):
if clk'event and clk='1' then
i:=0;
while i<7 loop
buswert(i) <= buswert(i+1);
i:=i+1;
end loop;
buswert(7) <= din;
end if;
with select
[Bearbeiten]with select_signal select dst_signal <= src_signal when select_value1, src_signal when select_value2, src_signal when others;
Selektive Signalzuweisung außerhalb eines Prozesses, als Alternative zu case:
signal sel_s : std_logic_vector(1 downto 0); signal monitor_s,one_s,two_s : std_logic_vector(3 downto 0);
with sel_s select monitor_s <= one_s when "00", two_s when "11", "0000" when others;
Operator Vorrangliste
[Bearbeiten]VHDL-Operatoren in der Reihenfolge des Vorrangs
Note: Der Concatenate Operator "&" hat eine geringere Priorität als einige arithmetische Operatoren +/-
**
abs
not
*
/
mod
rem
+
-
+
-
&
sll
srl
sla
sra
rol
ror
=
/=
<
<=
>
>=
and
or
nand
nor
xor
xnor
Fehlervermeidung
[Bearbeiten]Unbeabsichtiges Erzeugen eines "Latch"
[Bearbeiten]In der Synthese wird unbeabsichtigt ein "Latch" implementiert. Die Ursache ist meist, dass in einem kombinatorischen Prozess die Zuweisungen auf ein Signal nicht vollständig auscodiert wurden:
Beispiel:
if (a='1') and (b='0') then
x <= '1';
elsif (a='0') and (b='1') then
x <= '0';
end if;
Die Fälle a=1 und b=1 ebenso wie a=0 und b=0 wurden nicht definiert. Die Folge ist, dass die Synthese versucht in diesen Fällen den aktuellen Zustand beizubehalten. Dies erfolgt durch die Implementierung eines "Latch".
Vergessen von Signalen in der "sensitivity list" eines kombinatorischen Prozesses
[Bearbeiten]Werden Signale in der Liste nicht absichtlich weggelassen, um ein bestimmtes Verhalten der Schaltung zu erzeugen, sondern aus Nachlässigkeit nicht hinzugefügt, wird als Folge das Verhalten der Simulation von dem der realen Gatterschaltung abweichen. Es gibt vhdl-Editoren, die die "sensitivity list" prüfen. Auch in der Synthese erscheinen meistens Warnmeldung.
Verwenden von Reset-Signalen in einem Design
[Bearbeiten]Asynchrone Reset-Signale gehören bei VHDL-Designs unbedingt synchronisiert:
library ieee;
use ieee.std_logic_1164.all;
entity myEnt is
port(
rst_an : in std_logic;
clk: in std_logic;
rst: in std_logic;
sigIn: in std_logic_vector(3 downto 0);
sigOut: out std_logic_vector(3 downto 0));
end entity myEnt;
architecture myArch of myEnt is
signal sync_rst_r : std_logic_vector(1 downto 0);
signal mySig: std_logic_vector(sigIn'range);
begin
process(clk)
begin
if (rising_edge(clk)) then
sync_rst_r <= sync_rst_r(0) & rst_an;
end if;
end process;
process(clk, sync_rst_r)
begin
if sync_rst_r(1) = '0' then
mySig <= (others => '0');
elsif (rising_edge(clk)) then
if (rst = '1') then
mySig <= (others => '0');
else
mySig <= sigIn;
end if;
end if;
end process;
sigOut <= mySig;
end architecture;
Für detailierte Information zum Thema Reset im FPGAs siehe das Whitepaper von Xilinx "Get Smart About Reset: Think Local, Not Global" (englischsprachig).
Generieren von "Clock"-Signalen in einem Design
[Bearbeiten]Die "Clock"-Signale werden mit Hilfe einer speziellen Verdrahtung auf dem FPGA verteilt. Ein "Clock"-Signal darf nie durch Logik erzeugt werden. Im Folgenden wird ein schlechtes Beispiel gezeigt, in welchem ein neues Clock Signal erzeugt wird:
library ieee;
use ieee.std_logic_1164.all;
entity clock_divider_ent is
port (
clk : in std_logic;
clkDiv : out std_logic);
end clock_divider_ent;
architecture synth of clock_divider_ent is
signal counter: integer range 1023 downto 0;
signal clkDivInt: std_logic := '0';
begin
process(clk)
begin
if (rising_edge(clk)) then
if (counter = 0) then
counter <= 1023;
clkDivInt <= not clkDivInt;
else
counter <= counter - 1;
end if;
end if;
end process;
end synth;
In diesem Beispiel wird eine bessere Implementierung für den oben gezeigten Block dargestellt. Das Signal clkDivInt wird hier zu einem kurzen Puls: jedes Mal wenn der Zähler den Wert null erreicht, bleibt dieser Puls hoch nur während eines einzigen Taktzyklus vom clk.
library ieee;
use ieee.std_logic_1164.all;
architecture synth of clock_divider_ent is
signal counter: integer range 2047 downto 0;
signal clkDivInt: std_logic := '0';
begin
process(clk)
begin
if (rising_edge(clk)) then
counter <= counter - 1;
clkDivInt <= '0';
if (counter = 0) then
counter <= 2047;
clkDivInt <= '1';
end if;
end if;
end process;
end synth;
...
process(clk)
begin
if(rising_edge(clk)) then
if (clkDiv = '1') then
...
end if;
end if;
end process;
...
Vergleich von Zahlen
[Bearbeiten]Nach Möglichkeit sollte immer auf "=" und nicht auf "<", ">", "<=" oder ">=" verglichen werden, da diese aufwendiger in Hardware zu implementieren sind.
std_logic_arith vs numeric_std
[Bearbeiten]Die Bibliothek std_logic_arith wurde nicht standardisiert und ist daher nicht überall in gleicher Weise implementiert. Sie sollte daher nur in Ausnahmefällen Verwendung finden. Stattdessen sollte die gut ausgebaute und flexible numeric_std verwendet werden.
library ieee;
use ieee.std_logic_arith.all; -- Vermeiden, nicht standardisiert.
use ieee.numeric_std.all; -- Achtung: Beide zusammen ergibt Probleme!.