6. Architecture

Eine Architecture enthält die Beschreibung des Inhalts eines Modells. Diese Beschreibung ist sowohl auf funktionaler als auch auf strukturaler Ebene möglich; ein bestimmtes Modell kann also entweder durch sein Verhalten oder durch seinen Aufbau (Netzliste aus verdrahteten Submodulen) beschrieben werden. Es ist möglich, einer Entity mehrere Architectures mit Beschreibungen auf verschiedenen Ebenen zuzuordnen.

Auch die Architecture besteht aus einem deklarativen - und einem Anweisungsteil (Architecture-Body). Im Kopf stehen neben Datendeklarationen (vorhergehende Kapitel), Komponentende-klarationen (siehe unten) oder Funktions- bzw. Prozedurdeklarationen (nachfolgende Kapitel).

Der Architecture Body enthält die eigentliche Modellbeschreibung. Beachtet werden muß, daß sämtliche Anweisungen auf der Architekturebene nebenläufig (quasi parallel) ablaufen. Eine der möglichen nebenläufigen ("concurrent") Anweisungen ist der Aufruf sog. Prozesse, welche wie die Unterprogramme programmiersprachenähnliche Anweisungen in sequentieller Reihenfolge abarbeiten können.

Der prinzipielle Aufbau einer Architecture ist wie folgt:

    ARCHITECTURE arch_name OF entity_name IS

data declarations

component declarations

function or procedure declarations


BEGIN

concurrent statements


END [arch_name];

6.1 Strukturale Modellierung

Strukturale Modellierung bedeutet Instantiierung und Verdrahtung von einzelnen Submodulen. Das Vorgehen bei der strukturalen Modellierung erfolgt in VHDL über drei Stufen: Komponentendeklaration, Komponenteninstantiierung und Komponentenkonfiguration. Dieses nicht ganz einfach zu verstehende Konzept kann mit den von der Hardware her bekannten Begriffen IC-Sockel, Einsetzen und Verdrahten von IC-Sockeln und Einsetzen von ICs in diese Sockel verglichen werden.

Komponentendeklaration

Als erste Stufe muß, ähnlich wie bei einer Typdeklaration, der Prototyp der zu instantiierenden Sockel beschrieben werden. Dies geschieht im Architecture-Deklarationsteil mit Hilfe der COMPONENT -Anweisung:

    COMPONENT socket_type_name

GENERIC ( gen_name1, gen_name2, ... : type_name; ...);
PORT ( port_name1, port_name2, ...: IN type_name;
port_name3, port_name4, ...: OUT type_name;
port_name5, port_name6, ...: INOUT type_name);

END COMPONENT;

Komponenteninstantiierung

Instanzen der Sockeltypen werden in strukturalen Architekturen unter Angabe eines Instanzennamens und der Portzuordnungen miteinander verdrahtet.

Werden die lokalen Signale und Generics (der Architektur) in gleicher Reihenfolge angegeben wie bei der Komponentendeklaration, so gilt folgende Syntax:

    instance_identifier : socket_type_name
GENERIC MAP ( value1, value2, ...)
PORT MAP ( signal_name1, signal_name2, ...);

Soll in anderer Reihenfolge verdrahtet werden, müssen die Ports explizit "gemappt" werden ("=>"-Pfeil). Entsprechendes gilt für die Generics:

    instance_identifier : socket_type_name
GENERIC MAP ( argument1 => value2,
argument2 => value1, ...)
PORT MAP ( port_name2 => signal_name1,
port_name1 => signal_name2, ...);

Es können mehrere gleichartige Komponenten (socket_type_name ) instantiiert werden, allerdings müssen diesen dann unterschiedliche Identifier (instance_identifier ) zugeordnet werden.

Ein Beispiel (D-Flip-Flop):

    ARCHITECTURE structural OF dff IS

COMPONENT dlatch
PORT ( c, cb, d, r : IN std_ulogic;
q, qb : OUT std_ulogic);
END COMPONENT;
SIGNAL q_int, qb_int : std_ulogic;

BEGIN

master: dlatch PORT MAP ( c, d, r, q_int, qb_int);
slave: dlatch PORT MAP ( cb, q_int, r, q, qb);

END structural;

Hinweis:

Die Instantiierung der einzelnen Komponenten legt lediglich fest, welcher Typ "Sockel" in das strukturale Modell eingesetzt und verdrahtet wird. Das Modell, welches letztlich in diesen Sockel eingesetzt wird, legt man erst in der "Configuration" fest.

Block-Anweisung

Mit Hilfe der BLOCK -Anweisung kann innerhalb eines VHDL-Modells eine Hierarchie- oder Blockstruktur eingeführt werden. Die BLOCK -Anweisung besteht aus deklarativem Kopf und dem Body. Ein Block kann die gleiche Funktionalität enthalten wie die Architecture selbst.

   block_name : BLOCK

block declarations


BEGIN

concurrent statements


END BLOCK [block_name];

Generate-Anweisung

Die Instantiierung mehrerer gleichartiger Komponenten kann mit der GENERATE -Anweisung automatisiert werden. Innerhalb der GENERATE -Anweisung können wiederum nebenläufige Anweisungen stehen. Die GENERATE -Anweisung kann nicht nur mehrmals als Schleife durchlaufen, sondern auch durch eine Bedingung gesteuert werden:

    generate_name : FOR identifier IN range
GENERATE

concurrent statements


END GENERATE [generate_name];


generate_name : IF condition
GENERATE

concurrent statements


END GENERATE [generate_name];

Am Beispiel eines 8-Bit Registers soll dies verdeutlicht werden:

    ENTITY n_bit_latch IS

GENERIC ( n : positive := 8);
PORT ( clk : IN bit;
d : IN bit_vector (n-1 DOWNTO 0);
q : OUT bit_vector (n-1 DOWNTO 0));

END n_bit_latch;


ARCHITECTURE structural OF n_bit_latch IS

COMPONENT latch
PORT (gate, data : IN bit; q :OUT bit);
END COMPONENT;

BEGIN

generate_loop : FOR i IN 1 TO n GENERATE
instance : latch
PORT MAP (clk, d(i), q(i));
END GENERATE generate_loop;

END structural;

6.2 Verhaltensmodellierung

6.2.1 Attribute

Mit Hilfe von sogenannten Attributen können bestimmte Eigenschaften von Objekten abgefragt werden. Die Verwendung solcher Attribute kann eine VHDL-Beschreibung wesentlich kürzer und eleganter gestalten. Attribute werden folgendermaßen auf Signale, Blöcke, Typen und Felder angewendet:

object_name'attribute_name

Neben den im folgenden aufgeführten, vordefinierten Attributen gibt es noch die Möglichkeit, benutzerdefinierte Attribute zu deklarieren und zu verwenden.

Typbezogene Attribute:

  • t'BASE : Basistyp des Typs oder Subtyps
  • t'LEFT : linke Grenze eines skalaren Typs oder Subtyps
  • t'RIGHT : rechte Grenze eines skalaren Typs oder Subtyps
  • t'HIGH : obere Grenze eines skalaren Typs oder Subtyps
  • t'LOW : untere Grenze eines skalaren Typs oder Subtyps
  • t'POS(X) : Position des Elementes X in einem diskreten, skalaren Typ oder Subtyp
  • t'VAL(X) : Wert des Elementes an Position X in einem diskr., skal. Typ oder Subtyp
  • t'SUCC(X) : Vorgänger des Elementes X in einem diskr., skal. Typ oder Subtyp
  • t'PRED(X) : Nachfolger des Elementes X in einem diskr., skal. Typ oder Subtyp
  • t'LEFTOF(X) : Links von X stehendes Element in einem diskr., skal. Typ oder Subtyp
  • t'RIGHTOF(X) : Rechts von X stehendes Element in einem diskr., skal. Typ oder Subtyp

Feldbezogene Attribute:

  • a'LEFT : Linke Grenze eines Arrays, Alias, Typs oder Subtyps
  • a'RIGHT : Rechte Grenze eines Arrays, Alias, Typs oder Subtyps
  • a'HIGH : Obere Grenze eines Arrays, Alias, Typs oder Subtyps
  • a'LOW : Untere Grenze eines Arrays, Alias, Typs oder Subtyps
  • a'LENGTH : Bereichslänge eines Arrays, Alias, Typs oder Subtyps
  • a'RANGE : Bereich eines Arrays, Alias, Typs oder Subtyps
  • a'REVERSE_RANGE : Bereich in umgekehrter Reihenfolge

Blockbezogene Attribute:

  • b'BEHAVIOR : TRUE, falls der Block keine Component-Instatiierung enthält
  • b'STRUCTURE : TRUE, falls der Block keinen nicht-passiven Prozeß enthält

Signalbezogene Attribute:

  • s'DELAYED [t] : Um eine Zeit t (default: 1 delta) verzögertes Signal
  • s'STABLE [t] : Gibt an, ob ein Signal eine Zeit t (def.: 1 delta) stabil war (TRUE, FALSE)
  • s'QUIET [t] : Gibt an, ob ein Signal eine Zeit t (def.: 1 delta) ruhte (TRUE, FALSE)
  • s'TRANSACTION : Bit, welches bei jedem Event des Signals toggelt
  • s'EVENT : TRUE, falls sich das Signal während des mom. Simulationszyklus ändert
  • s'ACTIVE : TRUE, wenn das Signal während des mom. Simulationszyklus aktiv ist
  • s'LAST_EVENT : Zeitdifferenz zum letzten Ereignis des Signals s'LAST_ACTIVE : Zeitdifferenz zum letzten aktiven Zeitpunkt des Signals
  • s'LAST_VALUE : Wert des Signals vor dem letzten Ereignis

Anwendungen finden sich in den zahlreichen Beispielen der folgenden Kapitel.

6.2.2 Nebenläufige Anweisungen

Für die Verhaltensbeschreibung in VHDL stehen nicht nur Anweisungen zur Verfügung, die denen einer Programmiersprache wie C oder PASCAL ähnlich sind und sequentiell abgearbeitet werden, sondern auch Anweisungen, die spezielle Eigenschaften von Hardware (parallel arbeitende Funktionseinheiten) nachbilden. Diese nennt man "nebenläufig".

Alle Anweisungen werden innerhalb einer Architecture "nebenläufig" (quasi parallel) ausgeführt, innerhalb von Prozessen und Unterprogrammen jedoch sequentiell (nacheinander).

Die wichtigsten nebenläufigen Anweisungen sind neben den Elementen der strukturalen Beschreibung (siehe vorhergehendes Kapitel) Signalzuweisungen, Assertions und PROCESS -Anweisungen.

Signalzuweisungen

Folgende Arten nebenläufiger Signalzuweisungen sind möglich: ein Signal oder einen Wert (allgemein: value_expression), evtl. verzögert auf ein anderes Signal zuweisen, einem Signal bestimmte Werte (zeitverzögert) zuweisen oder zwei Alternativen von bedingten Signalzuweisungen:

   [label :] signal_name  <= [TRANSPORT]
value_expr1
[AFTER time_expr1
],
value_expr2
AFTER time_expr2
,
...
value_exprn
AFTER time_exprn
;

[label :] signal_name <= [TRANSPORT]
value_expr1
[AFTER time_expr1
] WHEN condition1
ELSE
value_expr2
[AFTER time_expr2
] WHEN condition2
ELSE
...
value_exprn
[AFTER time_exprn
];

[label :] WITH object_name SELECT signal_name <= [TRANSPORT]
value_expr1
[AFTER time_expr1
] WHEN choice1
,
value_expr2
[AFTER time_expr2
] WHEN choice2
,
...
value_exprn
[AFTER time_exprn
] WHEN OTHERS;

Das Schlüsselwort TRANSPORT bedeutet die Anwendung eines bestimmten zeitlichen Zuweisungsmechanismus (mehr dazu in einem späteren Kapitel).

Beispiele:

  a <= TRANSPORT NOT b AFTER 5 ns;

ass1: b <= '0' AFTER 1 ns, '1' AFTER 4 ns;

choice: WITH mux_val SELECT
c <= "001" AFTER 1 ns WHEN 1,
"010" AFTER 5 ns WHEN 2,
"011" AFTER 3 ns WHEN 3,
"111" AFTER 5 ns WHEN OTHERS;

Assertions

Assertions dienen zur Überprüfung von Fehlerbedingung und zur Ausgabe entsprechender Meldungen. Eine ASSERTION -Anweisung muß folgendermaßen gelesen werden: "Stelle sicher, daß die angegebene Bedingung (condition ) erfüllt ist, ansonsten bringe die nachfolgende Meldung zur Anzeige.

  [label :] ASSERT condition

REPORT "message_string"
SEVERITY severity_level;

Die Angabe des Fehlergrades (severity_level ) weist den Simulator (je nach Einstellung) an, ob beim Auftreten des Fehlerfalles die Simulation abgebrochen wird (z.B. bei failure , error ) oder nicht (z.B. bei note , warning ).

   ASSERT reset = '1'
REPORT "Reset is active '0'"
SEVERITY warning;

check: ASSERT (time = 0 ns) OR (set /= 'U')
REPORT "Set is uninitialized"
SEVERITY failure;

Prozeßanweisung

Prozesse dienen als Umgebung für sequentielle, d.h. nacheinander ablaufende Befehle zur Modellierung von prozeduralen Vorgängen. Zur Aktivierung eines Prozesses gibt es prinzipiell zwei Möglichkeiten, die sich gegenseitig ausschließen:

  • Angabe einer Liste von Signalen in der Prozeßanweisung selbst ("sensitivity list"). Der Prozeß wird immer dann aktiviert, wenn ein Ereignis auf einem dieser Signale auftritt.
  • Ohne "sensitivity list" kann innerhalb eines Prozesses mit Hilfe von WAIT -Anweisungen auf bestimmte Ereignisse gewartet werden. Bei der Initialisierung der Simulation werden Prozesse immer aktiviert und bis zur ersten WAIT-Anweisung bzw. im Fall einer "sensitivity list" bis zum Ende abgearbeitet.

Existieren mehrere Prozesse innerhalb einer Architektur, so sind sie gleichzeitig aktiv.

Die zwei Prozeßvarianten besitzen folgende Syntax:

   [process_name :] PROCESS (signal_name1, signal_name2, ...)

process declarations


BEGIN

sequential statements


END PROCESS [process_name];



[process_name :] PROCESS

process declarations


BEGIN

sequential statements

WAIT-statement


END PROCESS [process_name];

6.2.3 Sequentielle Anweisungen

Sequentielle Anweisungen dürfen nur innerhalb von Prozessen und Unterprogrammen auftreten.

Innerhalb dieser Umgebungen, und nur dort, sind Variablen erlaubt. Die wichtigsten sequentiellen Anweisungen sind Variablen-Zuweisungen, Schleifen, bedingte Verzweigungen, WAIT- und CASE-Anweisungen. Daneben sind in sequentiellen Umgebungen auch Signalzu-weisungen, Assertions (siehe oben), Prozedur- und Funktionsaufrufe (siehe nachfolgende Kapitel) möglich.

Bis auf die LOOP -Anweisung sind bei den sequentiellen Anweisungen keine Labels erlaubt.

Assertions

Siehe Kapitel "Nebenläufige Befehle".

Signalzuweisungen

Im sequentiellen Fall existieren keine bedingten Signalzuweisungen. Diese müssen hier mit Hilfe von IF-THEN-ELSE - und CASE - Anweisungen realisiert werden. Für nicht-bedingte Signalzuweisungen siehe Kapitel "Nebenläufige Befehle".

Variablenzuweisung

Es gilt hierbei folgende Syntax unter der Verwendung des Variablenzuweisungsoperators (":= "):

  variable_name := value_expression
;

Beispiele für Variablenzuweisungen:

   int_var_a   := 0;

string_var := "Value of string 'x' is " & x;

xyz := my_function (data_bus, 4);

WAIT-Anweisung

WAIT -Anweisungen dürfen nicht in Funktionen verwendet werden. Auch Prozesse mit sensitivity-list dürfen keine WAIT- Anweisungen enthalten.

Für die WAIT -Anweisung gibt es verschiedene Möglichkeiten, anzugeben, wie lange bzw. worauf gewartet werden soll. Kombinationen sind möglich:

   WAIT [ON    signal_list
]
[UNTIL condition
]
[FOR time_expression
];

Die Angabe einer Liste von Signalen und einer Bedingung bewirkt, daß ein Ereignis auf einem der Signale auftreten und gleichzeitig die Bedingung erfüllt sein muß. Die Angabe einer Zeitangabe bewirkt, daß maximal für die spezifizierte Dauer gewartet wird. Die WAIT -Anweisung ohne weiteres Argument wartet "unendlich lange" bis zum Ende der Simulation; sie steht z.B. am Ende eines Prozesses, der nur einmal ausgeführt werden soll.

IF-Anweisung

Mit dieser Anweisung sind bedingte Verzweigungen realisierbar:

   IF condition1
THEN
sequential statements


[ELSIF condition2
THEN
sequential statements


... ]

[ELSE
sequential statements
]

END IF;

Folgendes Beispiel beschreibt das Verhalten eines D-FlipFlops:

   IF reset = '0' THEN
q <= '0' AFTER 1 ns;
qb <= '1' AFTER 2 ns;
ELSIF (clk'EVENT) AND (clk = '1') THEN
q <= d AFTER 5 ns;
qb <= NOT d AFTER 7 ns;
END IF;

CASE-Anweisung

Mit der CASE -Anweisung sind ebenfalls bedingte Verzweigungen realisierbar. Einschränkend zur IF -Anweisung kann nur in Abhängigkeit eines Objektes oder eines Ausdrucks verzweigt werden und es müssen alle möglichen Fälle abgedeckt werden (notfalls mit "OTHERS "):

   CASE expression
IS
WHEN cases1
=>
sequential statements


[WHEN cases2
=>
sequential statements


... ]

WHEN OTHERS =>
sequential statements


END CASE;

Ein Beispiel für die CASE -Anweisung:

   CASE int_value IS
WHEN 0 => int := 5;
WHEN 1|2|8 => int := int_value;
WHEN 3 to 7 => int := int_value + 1;
WHEN OTHERS => int := 0;
END CASE;

NULL-Anweisung

Die NULL-Anweisung führt keine Aktion aus. Sie dient z.B. zur expliziten Kennzeichnung von aktionslosen Fällen in IF- oder CASE-Anweisungen:

   NULL;

LOOP-Anweisung

Mit Hilfe der LOOP-Anweisung können Iterationsschleifen realisiert werden. Dies kann durch Angabe eines diskreten Bereiches erfolgen oder mit einer Bedingung gesteuert werden. Im ersteren Fall wird implizit eine Laufvariable (var_name ) deklariert, die innerhalb der Schleife verwendet werden kann.

   [loop_label :] FOR var_name IN range
LOOP

sequential statements


END LOOP [loop_label];



[loop_label :] WHILE condition
LOOP

sequential statements


END LOOP [loop_label];

Zwei Beispiele für Schleifenkonstruktionen:

    loop_1 : FOR i IN 5 downto 0 LOOP
table(i) := table(i+1) ** i;
END LOOP;

loop_x : WHILE int_value < 10 LOOP
FOR j IN table'RANGE LOOP
table(j) := table(j-1);
tab_value := table(j) + j;
END LOOP;
int_value := int_value + tab_value;
END LOOP loop_x;

EXIT- und NEXT-Anweisung

EXIT - und NEXT -Anweisungen dienen zum vorzeitigen Ausstieg aus Schleifenanweisungen. Mit NEXT wird zum nächsten Schleifendurchlauf gesprungen, mit EXIT die Schleife komplett verlassen. Bei verschachtelten Schleifen beziehen sich die Anweisungen immer auf die innerste Schleife, in der sie auftreten. Durch Angabe eines LOOP -Labels können auch Ausstiege aus hierarchisch höher liegenden Schleifen realisiert werden:

   NEXT [loop_label] [WHEN condition
];
EXIT [loop_label] [WHEN condition
];

loop_x : WHILE int_value < 10 LOOP
FOR j IN table'RANGE LOO
table(j) := table(j-1);
EXIT loop_x WHEN table(j) > 20;
value_2 := table(j) + j;
END LOOP;
NEXT WHEN j = 5;
int_value := j + 3;
END LOOP loop_x;

6.2.4 Funktionen und Prozeduren

In VHDL gibt es, ähnlich zu Programmiersprachen wie C oder Pascal, die Möglichkeit der Realisierung von Unterprogrammen mittels Prozeduren und Funktionen.

  • Funktionen werden mit verschiedenen Argumenten aufgerufen und liefern einen Ergebniswert zurück. Der Funktionsaufruf (mit Eingabeargumenten) kann an der gleichen Stelle in Ausdrücken stehen, an der der Typ des Ergebniswertes erlaubt ist.
  • Prozeduren hingegen werden mit einer Liste von Argumenten aufgerufen, die nicht nur Eingänge, sondern auch Ausgänge oder bidirektional sein können. Der Aufruf einer Prozedur ist ein eigenständiger Befehl, er kann nebenläufig oder sequentiell erfolgen.

Die Definitionen von Unterprogrammen sollten je nach Verwendungsgebiet in Packages, Entities oder den Deklarationsteilen von Architecture, Process oder Block erfolgen:

   PROCEDURE proc_name ( obj_class data_name: IN  data_type;
obj_class data_name: OUT data_type;
obj_class data_name: INOUT data_type) IS

declarations


BEGIN

sequential statements


END proc_name;

Bei Prozeduren kann mit obj_class die Objektklasse (signal, variable, constant) angegeben werden.

Im Falle einer Funktion ist die RETURN -Anweisung zwingend:

 FUNCTION func_name ( data_name1, data_name2 : data_type, ... ) RETURN result_data_type IS

declarations


BEGIN

sequential statements

RETURN statement


END func_name;

Für eine höhere Flexibilität kann die Unterprogrammdeklaration auch aufgespalten werden in Schnittstellenvereinbarung und eigentliche Unterprogrammdeklaration. Die Schnittstelle wird im Package bekanntgegeben, während die Deklaration des Unterprogramms im gleichnamigen Package Body steht. Dieses Vorgehen hat unter Ausnutzung der Abhängigkeiten beim Compilieren der Design-Einheiten den Vorteil, daß das Unterprogramm in seiner Funktion verändert werden kann, ohne das Package und damit alle abhängigen Design-Einheiten neu compilieren zu müssen. Schnittstellenvereinbarungen haben folgendes Aussehen:

   PROCEDURE proc_name ( obj_class data_name: IN  data_type;
obj_class data_name: OUT data_type;
obj_class data_name: INOUT data_type);

FUNCTION func_name ( data_name1, data_name2 : data_type, ... ) RETURN result_data_type;

Weiterhin existiert bei Funktionen und Prozeduren in VHDL die Möglichkeit des Überladens: Es können mehrere Unterprogramme gleichen Namens verfaßt werden, die jedoch unterschiedliche Anzahl, Typen oder Namen von Argumenten verarbeiten bzw. unterschiedliche Ergebnistypen liefern. Der Simulator entscheidet anhand des Kontextes beim Aufruf des Unterprogramms, welche der definierten Alternativen gleichen Namens letztendlich auszuführen ist.

Einige Beispiele für Prozeduren (das erste produziert lediglich die Ausgabemeldung "Thank you", letzteres ersetzt, aufgerufen als concurrent statement, ein einfaches D-FlipFlop):

   PROCEDURE thank_you IS
BEGIN
ASSERT false
REPORT "Thank you!"
SEVERITY note;
END thank_you;


PROCEDURE regist ( SIGNAL c, d : IN bit; SIGNAL q : OUT bit) IS
VARIABLE t : TIME := 2.2 ns;
BEGIN
LOOP
WAIT ON c;
IF c = '1' THEN
q <= d AFTER t;
END IF;
END LOOP;
END regist;

Folgende Beispiele zeigen eine überladene "AND" Funktion und eine einfache Bit-zu-Integer-Konvertierung:

 FUNCTION "AND" (a,b : bit_vector) RETURN bit_vector IS
ALIAS c : bit_vector (1 TO a'LENGTH) IS a;
ALIAS d : bit_vector (1 TO b'LENGTH) IS b;
VARIABLE result : bit_vector (1 TO c'length);

BEGIN
ASSERT a'LENGTH = b'LENGTH
REPORT "Different length!"
SEVERITY failure;
FOR i in 1 TO c'LENGTH LOOP
IF (((c(i) = '1') AND (d(i) = '1')) THEN
result(i) := '1';
ELSE result(i) := '0'
END IF;
END LOOP;
RETURN result;
END "AND";


FUNCTION convert (a : bit) RETURN integer IS
CONSTANT voltage : integer;
BEGIN
IF a = '1' THEN voltage := 5;
ELSE voltage := 0;
END IF;
RETURN voltage;
END convert;