courses:system_design:simulation:notizen

Deutsche Notizen

Zur Verifikation der spezifizierten Funktionalität des entwickelten Bausteins verwendet man eine Testbench, die das DUT (Device Under Test) mit Stimuli versorgt und die erhaltenen Antworten analysieren oder abspeichern kann. Die Information für die Stimuligenerierung kann entweder aus einem externen File erfolgen oder fest in die Testbench integriert werden. Ein Simulator stellt die Signale in Waveforms dar, die der Designer mit den zu erwarteten Ergebnissen manuell=visuell vergleicht und bei auftretenden Fehlern den Quellcode modifiziert. Jedoch wird diese Vorgehensweise bei größeren Design unpraktikabel und fehlerträchtig, so daß eine automatische Verifikation wünschenswert ist. Beispielsweise speichert die Testbench den Speicherbereich eines integrierten RAMs nach der Simulation ab und dieses File wird mit einem 'Golden File' verglichen. Ein weiteres Anwendungsgebiet für die Abspeicherung von Simulationsdaten ist die Erzeugung von Testvektoren für einen bestimmten Tester.

Auf welche Art und Weise werden Stimuli erzeugt und wie ist eine Testbench aufgebaut?

Der Deklarationsteil der Architecture enthält zum einen die Component Deklaration des DUTs (Device Under Test), zum anderen die Definition der internen Signale und beispielsweise die Definition der Taktperiode. In einem Stimuliprozeß werden die Eingangssignale des DUTs mit Werten versehen. Da man die Eingänge des DUTs nicht direkt ansteuern kann, müssen interne Zwischensignale definiert werden. Um die Portnamen und die internen Signale besser auseinanderzuhalten, werden diese meistens mit einem Präfix (z. B.: “W_” Wire, “N_” Net) versehen. Die explizite Auswahl der einzelnen Unterkomponenten des DUTs bei der Simulation erfolgt mit Hilfe der Configuration. Auf der folgenden Seite ist ein möglicher Aufbau der Architecture dargestellt.

Das Beispiel zeigt eine VHDL Testbench für das Modul TEST. Dieses Modul wird in der Architektur der Testbench als Komponente deklariert. Desweitern werden eine Konstante namens PERIOD für die Takterzeugung deklariert, sowie interne Signale zum verknüpfen mit dem DUT (hier die Komponente TEST). Für die Taktgenerierung ist es wichtig, daß das Taktsignal auf '0' oder '1' initialisiert ist, wie dies im Beispiel gemacht wurde.

Die Taktgenerierung ist sehr wichtig im synchronen Entwirf. In der Testbench kann der Takt für die Simulation durch eine einfache nebenläufige Anweisung oder in einem Prozeß erzeugt werden. Für einen (symmetrischen) Standardtakt wird die nebenläufige Variante empfohlen.

Im einfachsten Fall ist der Takt also symmetrisch und läuft während der gesamten Simulation. Da der Wert des Taktsignals alle halbe Periode invertiert wird, muß der Anfangswert entweder '0' oder '1' sein. Der Startwert muß also z.B. durch eine Initialisierungsanweisung in der Signaldeklaration vorgegeben werden. Die aufwendigere Variante in der bedingten Signalzuweisung erzeugt einen Takt, der nur ein Viertel der Periode auf '1' ist. Hier muß das Taktsignal nicht initialisiert werden, da die bedingte Signalzuweisung einen unbedingten else Zweig enthält.

Die komplette Simulation wird durch die ASSERT Anweisung nach 100 Taktzyklen gestoppt. Die Zeitabfrage könnte auch in die bedingte Signalzuweisung aufgenommen werden.

Takt die ine festes Phasenverhältnis habe, werden am besten mit dem 'delayed' Attribut modelliert, wie z.B. die Anweisung: “CLK_DELAYED ⇐ W_CLK'delayed(5 ns);”.

Die Erzeugung des Reset Signals ist hier als einfache Treiberliste realisiert: Das Signal wird mit '0' vorbelegt, nach 20 ns auf '1' gesetzt und dann nach weiteren 20 ns, also zur Zeit 40 ns, wiederum auf '0'.

Alle anderen Eingänge des DUT können in gleicher Weise wie der Takt oder das Reset Signal stimuliert werden. Jedoch wird eine eigener Stimuli Prozeß bevorzugt. Hier können alle zur einer Simulationszeit aktiven Stimulis gruppiert werden, was die Lesbarkeit steigert. Man beachte, daß jetzt Wait Anweisunge zum Anhalten des Prozesses notwendig sind. Etwas komfortablere Testbenches verwerten zur Erzeugung der Stimuli auch die Antwort des DUT. Je genauer die später Arbeitsumgebung des DUT modelliert werden soll, desto komplexer wird die Testbench.

Die Simulation eines VHDL-Modells läuft in drei Schritten ab. In der Elaboration-Phase wird der VHDL-Text analysiert. In der Initialisation-Phase erhalten alle Signale ihre Anfangswerte und in der Execution-Phase wird das Modell ausgeführt, d.h. simuliert.

In der Elaboration werden alle beschriebenen Module in ein Kommunikationsmodell zusammengefaßt. Daraus geht dann hervor, welcher Prozeß durch welchen anderen aktiviert werden kann, oder anders ausgedrückt, die einzelnen Prozesse werden durch eine Netzliste miteinander verbunden. Alle Module werden analysiert und in eine ausführbare Form gebracht. Nur Schleifenvariablen und Unterporgramme werden während der Ausführung dynamisch elaboriert.

In der Initialisation erhalten alle Signale ihren Startwert. Entweder ist der Startwert in der Signaldeklaration angegeben, oder es wird der erste Wert in der Typdefinition (= type`left) verwendet. Im Fall des Typs STD_ULOGIC ist dies das `u` für uninitialisiert.

Zum Abschluß der Initialisation wird jeder Prozeß einmal aufgerufen und abgearbeitet bis er zum Stillstand gekommen ist, ohne jedoch danach ein Signal-Update durchzuführen.

Die Execution bildet die eigentliche Simulation. Durch Prozesse der Testbench wird das VHDL-Modell mit Stimuli versorgt. Im Waveformfenster können dann die einzelnen Signale des Modells betrachtet werden (Stimuli und Antworten des Modells). Die Antworten können auch in der Testbench mit den erwarteten Antworten verglichen werden.

Das Beispieldesign DUT (Device Under Test), das verifiziert werden soll, besteht aus verschiedenen Komponenten (Module A, B, C, D). Zusätzlich werden in den Modulen A und B die Packages PKG1 bzw. PKG2 verwendet. Bei Package PKG2 wurde eine Aufteilung vorgenommen. Im Package befinden sich beispielsweise die Deklarationen der Unterprogramme und im Package Body die eigentliche Definition.

In VHDL gibt es zwei verschiedene Verzögerungsmodelle: Transport und Inertial(default). Beim Transport Delay Modell wird alles übertragen, wie im oberen Beispiel, wo das Signal A 2 ns verzögert auf S geschrieben wird (Nachbildung einer Signalübertragung durch einen Draht);. Beim Inertial Delay Modell werden Signale nur dann übertragen, wenn sie für eine bestimmte Zeit aktiv sind; spikes werden herausmodelliert, wie im unteren Beispiel, wo das Signal A einmal für weniger als 2 ns aktiv war und dieser spike dann nicht übertragen wurde.

Eine Signalzuweisung hat die allgemeine Form: T ⇐ reject r_time inertial wert_1 after time_1 . Äquivalent sind dann: T ⇐ wert_1 after time_1 (default inertial delay) T ⇐ inertial wert_1 after time_1 T ⇐ reject time_1 inertial wert_1 after time_1 und auch: T ⇐ transport wert_1 after time_1 . T ⇐ reject 0 ns inertial wert_1 after time_1 Ferner kann T ⇐ wert_1 ; für T ⇐ wert_1 after 0 ns; verwendet werden. Der aktuelle Treiberwert kann nicht geändert werden.

Ein Signaltreiber verwaltet eine Liste von Wert/Zeit Paaren, d.h. zu welcher Zeit welcher Wert getrieben werden soll. Im einfachsten Beispiel befindet sich nur ein Eintrag in der Liste, z.B. (2,2 ns). Kommt dann ein neues Paar, so muß unterschieden werden, ob die Zeit im Wert/Zeit Paar chronologisch nach der Zeit des letzten Listeneintrags liegt oder nicht. Liegt sie danach, wird das Paar an die Liste angehängt (linkes Beispiel); sonst wird das neue Wert/Zeit Paar chronologisch einsortiert (vor eventuell vorhandene Paare mit gleichem Zeiteintrag)und alle Paare danach werden gelöscht (rechtes Beispiel).

Nach der ersten Signalzuweisung enthält die Liste für den Treiber von S drei Wert/Zeit Paare. Im Fall mit S ⇐ transport 4 after 4 ns; wird der letzte Listeneintrag überschrieben. Wäre die Liste schon länger gewesen, so wären auch alle nachfolgenden Listeneinträge gelöscht worden. Im Fall mit S ⇐ transport 4 after 6 ns; wird das Paar an die Liste angehängt, weil der Zeiteintrag dieses Paares chronologisch nach dem des letzten Listeneintrags folgt. Der Zeiteintrag t_i des hinzukommenden Wert/Zeit Paares entscheidet darüber, ob die Liste überschrieben und nachfolgende Einträge gelöscht werden (t_i-1 >= t_i), oder ob das Paar an die Liste angehängt wird (t_i-1 < t_i).

Bei den rechten drei Beispielen ist im neuen Wert/Zeit Paar die Zeit jeweils höchstens so groß wie die Zeit des Paares in der Liste, weshalb der alte Eintrag durch den neuen überschrieben wird. Im linken Beispiel ist das pulse rejection limit der Signalzuweisung S ⇐ 2 after 2 ns gleich 2 ns. Also werden alle Transaktionen markiert, deren Auftrittszeit kleiner (2 ns minus pulse rejection time =) 0 ns ist. Demnach wird (1, 1 ns) nicht markiert und im letzten Schritt gelöscht.

Nach der ersten Signalzuweisung S ⇐ 1 after 1 ns, 3 after 3 ns, 5 after 5 ns; enthält die Liste 3 Wert/Zeit Paare. Die zweite Signalzuweisung S ⇐ 3 after 4 ns, 4 after 5 ns; löscht aus der Liste den letzten Eintrag (4 ns ⇐ 5 ns) und hängt 2 Einträge an. Dann werden alle neuen Transaktionen und auch (3, 3 ns) markiert, weil diese Transaktion direkter Vorgänger einer markierten Transaktion ist und densselben Wert hat wie diese markierte Transaktion (nämlich 3). Die unmarkierten Einträge werden gelöscht (das ist das Paar (1, 1 ns)) .

Die Signalzuweisung S ⇐ 2 after 3 ns, 2 after 12 ns, 12 after 13 ns, 5 after 20 ns, 8 after 42 ns; bildet die erste Liste. Die zweite Signalzuweisung S ⇐ reject 15 ns inertial 12 after 20 ns, 18 after 41 ns; modifiziert diese Liste in folgender Weise:

  • Schritt 1: alle Paare mit Zeitwerten größer gleich 20 ns werden entfernt
  • Schritt 2: die neuen Paare werden angehängt
  • Schritt 3: alle neuen Transaktionen werden markiert (gelb)
  • Schrott 4: Transzaktionen mit einem Zeitwert kleiner als dem der ersten neuen Transaktion (20 ns) minus dem reject limit (15 ns). also 5 ns, werden markiert (rot)
  • Schritt 5: von den unmarkierten werden noch diejenigen markiert, welche unmittelbare Vorgänger einer markierten Transaktion sind und denselben Wert beinhalten wie die bereits markierte Transaktion (grün)
  • Schritt 6: das aktuell treibende Paar wird markiert; das ist hier nicht nötig, da dieses schon markiert ist
  • Schritt 7: alle unmarkierten Transaktionen werden gelöscht

Chapters of System Design > Simulation