VHDL

Aus Wikibooks

Wechseln zu: Navigation, Suche
Dieses Buch steht im Regal Elektrotechnik

Inhaltsverzeichnis

[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.

Persönliche Werkzeuge