VHDL
Aus Wikibooks
[Bearbeiten] Einführung
VHDL (VHSIC (Very High Speed Integrated Circuits) Hardware Description Language) ist in Europa die verbreiteste Hardware-Beschreibungssprache. Daneben gibt es VERILOG. Ursprünglich wurde sie entwickelt, um Testumgebungen für die Simulation integrierter Schaltungen zu entwickeln. Daher ist VHDL eine relativ komplexe Programmiersprache, deren Konstrukte nicht zwangsläufig synthetisierbar sind. Das führt bereits zum üblichen Ablauf der Hardwareentwicklung: Nach der Festlegung der Funktionalität wird diese mittels VHDL beschrieben. Der entstandene "Source-Code" wird compiliert und anschließend simuliert. Nachdem die einwandfreie Funktion sichergestellt ist wird der VHDL-Code direkt mit Hilfe eines Syntheseprogramms in eine Gatternetzliste umgesetzt. Dazu benötigt das Synthesewerkzeug eine von der Zielhardware abhängige Elementebibliothek, die der Chiphersteller zur Verfügung stellt.
[Bearbeiten] Zum Wesen von VHDL
Im Innersten besteht jede Hardwareschaltung aus kombinatorischer Logik und speichernden Elementen.
Unter Kombinatorik versteht man "NICHT/UND/ODER/Exclusiv-ODER"-Gatter und deren Kombinationen. Also jede Art von Verknüpfungen von einem oder mehreren Eingängen zu einem oder mehreren Ausgängen. Eine Änderung eines Eingangs bewirkt eine unmittelbare Wirkung auf den Ausgang. Eine wirkliche Hardwareschaltung benötigt dazu jedoch geringe Laufzeiten. Diese Laufzeiten kombinatorischer Logik werden aber in einer reinen "RTL"- Beschreibung nicht im VHDL-Code modelliert, obwohl dies an sich möglich wäre.
Speichernde Elemente sind "Flipflops" oder "Latches". Latches sollte man grundsätzlich vermeiden. Flipflops sind Grundelemente die einen Dateneingang und einen Takteingang haben. Der Ausgang übernimmt üblicherweise den Zustand des Eingangs mit der steigenden Taktflanke. Es gibt auch Flipflops die zur fallenden Flanke schalten. Zusätzlich können Flipflops auch einen asynchronen Reset-Eingang haben. Dieser setzt im Normalfall zu Beginn, nach Anlegen der Versorgungsspannung, das Flipflop in den Grundzustand (Ausgang hat den Pegel "0").
Um diese zwei 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 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 welchen 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.
Man unterscheidet zwei Typen von Prozessen: Kombinatorische und synchron getaktet Prozesse, analog zu der eingangs besprochenen realen Hardware. Kombinatorische Prozesse haben in der Sensitivity-List alle Eingangs-Signale und beschreiben im Inneren deren Verknüpfung. Synchron getaktet 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:
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:
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!!
Zur Beschreibung kombinatorischer Vorgänge gibt es die aus vielen Sprachen bekannte Konstrukte:
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;
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:
entity 74LS00 is
port (i0,i1 : in bit;
i2,i3 : in bit; -- Eingänge
o0 : out bit; -- Ausgang
o1 : out bit);
end 74LS00;
architecture behv of 74LS00 is
begin
signal z : bit; -- Vereinbarung internen Signale
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;
Man sieht in der "entity" wird eine Portliste vereinbart, die alle nach außen führenden Signale beinhalten muss. Interne Signale werden wie oben 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" verschalten 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, da ein "Draht" ebenfalls zu einem Zeitpunkt immer nur einen Pegel besitzen kann.
Ein File mit einer Entity und einer Architecture ist bereits eine compilierbare, 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"-Dekaration:
component 74LS00
port (i0,i1,i2,i3 : in bit;
o0,o1 : out bit);
end component;
Beispiel für eine "Component"-Instanzierung:
architecture behv of test is
-- signal deklarierungen:
signal a,b,c,d : bit;
-- componenten declarierungen:
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;
[Bearbeiten] Basis-Konstrukte
Nach dieser kurzen Einführung über die grundsätzlichen Gedanken von VHDL sollen im Folgenden in alphabetischer Reihenfolge die sprachlichen Basis-Konstrukte beschrieben werden:
[Bearbeiten] aggregates
(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;
--
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');
[Bearbeiten] alias
Ein Alias bezeichet einen Teil eines Signales.
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);
[Bearbeiten] architecture
architecture ''architecture_name'' of ''entity_name'' is declarations begin concurrent statements end architecture;
[Bearbeiten] arrays
type ''type_name'' is array (range) of ''element_type'';
Beispiel:
type Speicher is array (0 to 1023) of integer range 0 to 255; signal sram : speicher;
[Bearbeiten] assert
assert ''bedingung'' report ''string'' severity ''severity_level''; default severity_level ist "error" Beispiel: assert (a > c) report "a zu gross" severity note;
[Bearbeiten] attributes
in_signal'event out_array'range
[Bearbeiten] block statements
[Bearbeiten] case
case ''expression'' is when ''fall_1'' => ''sequential statement'' when ''fall_2'' => ''sequential statement'' when others => ''sequential statement'' end case;
Beispiel1:
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;
Beispiel2:
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!
[Bearbeiten] component declaration
component ''component_name''
generic (''generic_liste'');
port (''port_liste'');
end component;
[Bearbeiten] component instantiation
[Bearbeiten] constant
constant ''constant_name'' : type := value;
Beispiel:
constant festwert : std_logic_vector(7 downto 0) := "10101100";
constant zeitwert : time := 50ns;
type rdatum is array (0 to 3) of bit_vector(7 downto 0);
constant rom : rdatum :=
("00000001",
"00000010",
"00000011",
"00000100");
[Bearbeiten] entity
entity ''entity_name'' is generic (generic_list); port (port_list); end ''entity_name'';
[Bearbeiten] exit-Anweisung
mit der exit-Anweisung wird die "innerste" Schleife verlassen und mit der Anweisung, die direkt auf die Schleife folgt, fortgefahren.
[Bearbeiten] file declaration
[Bearbeiten] for loop
''eventuell_label'': for ''parameter'' in ''range'' loop sequential statements end loop ''eventuell_label'';
Beispiel:
for i in 0 to 7 loop w(i) <= a(i) and b; -- 8bit bus "a" mit Einzelsignal "b" verunden end loop
[Bearbeiten] functions
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 <= paritiy_generator(data_bus);
Achtung: keine "signal assignments" oder "wait"
[Bearbeiten] generate
''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: halbaddierer port map
(x(i),y(i),s(i),carry(i));
end generate niedrigstes bit;
hoehers_bits: if i>0 generate
wi: 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;
[Bearbeiten] generic
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); port (a : in std_ulogic_vector(n-1 downto 0); end test;
[Bearbeiten] if
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;
[Bearbeiten] library
[Bearbeiten] names
[Bearbeiten] next
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.
[Bearbeiten] null statement
[Bearbeiten] operators
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 not -- nicht (invertierung)
Vergleichs Operatoren Ergebnis: boolean
= -- Gleichheit /= -- Ungleichheit < -- kleiner > -- groesser <= -- kleiner gleich >= -- grösser gleich
Arithmetische Operatoren für Typen: integer,real
a <= a + 7; -- Addition r1 <= r2 - 3,1414 -- 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
[Bearbeiten] package
package ''package_name'' is declarations end package
Beispiele:
package demo is
constant nullwert : bit_vector := "00000000";
component adder
port(x,y,ci : in bit;
s,co : out bit);
end component;
end demo;
Package-Aufruf lautet dann:
use work.demo.all; entitiy xx is port ( wert : out bit_vector(7 downto 0)); end xx; architecture behv of xx is begin wert <= nullwert; end behv;
[Bearbeiten] procedures
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_procedures is
procedure parity_generator
(signal din : in std_ulogic_vector;
signal par : out std_ulogic);
end my_procedures;
package body my_procedures is
procedure parity... (procedure code siehe oben!)
end my_procedures;
Aufruf:
... parity_generator(databus,par_bit); ...
[Bearbeiten] process
''optionales_label'': process (optionale sensitivity liste) declarations begin sequential statements; end process optionales_label;
[Bearbeiten] type conversion
[Bearbeiten] type declaration
[Bearbeiten] use
Verwendung von Bibliotheken, oder Teilen daraus.
Beispiel:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
[Bearbeiten] variable declaration
variable ''Variablenname'' : ''Typ'' := ''Initialisierungswert''
Beispiel:
variable MeinBool : boolean := false;
[Bearbeiten] variable assignment
''Variablenname'' := ''Wert'';
[Bearbeiten] wait
- wait until condition
- wait on signal list
- wait for time
- 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;
[Bearbeiten] while
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;
[Bearbeiten] Beispielprogramme
[Bearbeiten] Zustandsmaschinen
Die im folgende beschriebene Codierung einer Zustandsmaschine erhebt nicht den Anspruch die eleganteste Lösung darzustellen, sie soll vielmehr einen Eindruck der Sprache und der grundsätzlichen Abläufe vermitteln.
architecture demo of zustandsmaschine is
constant vectorbreite : integer := 3;
type zustandsvector is std_logic_vector(vectorbreite downto 0);
signal zustand : zustandsvector;
-- "hot one" Codierung !!
constant warte_zustand : zustandsvector := "0001";
constant a_zustand : zustandsvector := "0010";
constant b_zustand : zustandsvector := "0100";
constant c_zustand : zustandsvector := "1000";
signal gehe_zu_warte_zustand : std_logic;
signal gehe_zu_a_zustand : std_logic;
signal gehe_zu_b_zustand : std_logic;
signal gehe_zu_c_zustand : std_logic;
signal timer : std_logic_vector(3 downto 0);
begin
--------------------------------------------
zustaende : process(nres,clk)
begin
if (nres='0') then
zustand <= warte_zustand;
elsif (clk'event and clk='1') then
if (gehe_zu_warte_zustand='1') then
zustand <= warte_zustand;
elsif (gehe_zu_a_zustand='1') then
zustand <= a_zustand;
elsif (gehe_zu_b_zustand='1') then
zustand <= b_zustand;
elsif (gehe_zu_c_zustand='1') then
zustand <= c_zustand;
else -- der "else" Pfad ist optional!
zustand <= zustand; -- ohne diese Zeilen identische Funktion!!
end if;
end process zustaende;
--------------------------------------------
zustandsweiterschaltung : process(eingang1,eingang2,zustand,timer)
begin
gehe_zu_warte_zustand <= '0'; -- default assignments
gehe_zu_a_zustand <= '0';
gehe_zu_b_zustand <= '0';
gehe_zu_c_zustand <= '0';
case zustand is
when warte_zustand =>
if (eingang1='1') then -- Priorisierung von
gehe_zu_a_zustand <= '1'; -- "eingang1" und "eingang2"
elsif (eingang2='1') then
gehe_zu_c_zustand <= '1';
end if;
when a_zustand =>
if (timer="0000") then -- Maschine bleibt im "a_zustand"
gehe_zu_b_zustand <= '1'; -- bis Timer abgelaufen
end if;
when b_zustand =>
gehe_zu_warte_zustand <= '1';
when c_zustand =>
gehe_zu_a_zustand <= '1';
when others => -- diese Zeile sichert Vollständigkeit
gehe_zu_warte_zustand <= '1'; -- der Auscodierung!!!
end case;
end process zustandsweiterschaltung;
--------------------------------------------
wartezeit : process (nres,clk)
begin
if (nres='0') then
timer <= "1010";
elsif (clk'event and clk='1') then
if (zustand=a_zustand) then -- Zahler steht auf "1010"
timer <= timer-1; -- nur im "a_zustand" läuft er rückwärts
else
timer <= "1010";
end if;
end if;
end process wartezeit;
--------------------------------------------
--------------------------------------------
getakteter_ausgang : process (nres,clk)
begin
if (nres='0') then
ausgang1 <= '0';
elsif (clk'event and clk='1') then
if (zustand=b_zustand) then
ausgang1 <= '1'; -- eine "clk"-Periode langer Pulse
else -- Achtung: erscheint einen Takt nach "b_zustand"!!
ausgang1 <= '0';
end if;
end if;
end process getakteter_ausgang;
--------------------------------------------
--------------------------------------------
asynchroner_ausgang : process (gehe_zu_b_zustand)
begin
ausgang2 <= gehe_zu_b_zustand; -- eine "clk"-Periode langer Pulse (könnte spiken!!)
end process asynchroner_ausgang;
--------------------------------------------
--------------------------------------------
[Bearbeiten] Fehlervermeidung
1) 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 wurde:
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".
2) In der "sensitivity list" eines kombinatorischen Prozesses wird ein Signal nicht aufgeführt.
Die Folge ist ein Verhalten in der Simulaton das von der realen Gatterschaltung abweicht! Es gibt vhdl-Editoren die die "sensitivity list" prüfen. Spätesten in der Synthese erscheint eine Warnmeldung.

