courses:system_design:synthesis:notizen

Deutsche Notizen

RTL - Register-Transfer-Level. Im Unterschied zur Modellierung auf Verhaltensebene, wo VHDL wie jede andere Programmiersprache verwendet werden kann, liegt die RT-Ebene eine Stufe näher and er Hardware. Dabei wird der Algorithmus aufgeteilt in rein kombinatorische Blöcke und in getaktete Blöcke mit speicherndem Charakter d.h. mit Flip-Flops. Im rein kombinatorischen Block soll kein speicherndes Element beinhaltet sein. Dafür müssen IF-Anweisungen vollständig beschrieben werden, d.h. der ELSE-Zweig darf nicht vergessen werden, da ansonsten durch das Synthesetool unbeabsichtigte Latches erzeugt werden können. Eine andere Möglichkeit ist, vor der IF-Bedingung Default-Werte zuzuweisen, die dann gegebenenfalls im IF-Zweig geändert werden können. Ein getakteter Prozeß mit asynchronem Reset hat grob die folgende Gestalt:

process(clock, reset)
begin
   if reset = '1' then
      ...(Reset-Anweisungen)
   elsif clock'event and clock = '1' then
      ...(Zuweisungen an FFs) ;
   end if ;
end process;

Eine IF-Anweisung wird bei der Synthese immer als ein oder mehrere Multiplexer realisiert. Dabei entspricht jede Bedingung (IF oder ELSIF-Zweig) einem Multiplexer. Zu beachten ist die Priorität der einzelnen Zweige. Bei der Simulation einer IF-Anweisung werden beim Eintreten einer Bedingung die anderen (folgenden) nicht mehr abgearbeitet. Diese Reihenfolge der Zweige muß bei der Synthese ebenfalls bestehen bleiben. Dies ist aber nur durch hintereinander geschaltete Multiplexer möglich, wobei die ERSTE Bedingung am SELECT-Eingang des LETZTEN Multiplexers anliegt.

Ein Prozess wird bei der Simulation dann aktiviert, wenn an einem der Signale seiner Sensitvitätsliste ein Ereignis eintritt.

Wenn, wie in obiger Frage, das SEL Signal nicht in der Liste enthalten wäre, würde der Prozess nur durch Ereignisse an A oder B aktiviert werden. Dies entspricht zum einen nicht dem gewünschten Verhalten des beschriebenen Multiplexers. Zum anderen reagieren Synthesewerkzeuge nicht auf Sensitivitätslisten, sondern verarbeiten den im Prozess enthaltenen VHDL-Code. In unserem Fall eine IF-Anweisung. Diese wird immer als Multiplexer realisiert. Ergebnis: Wenn SEL nicht in der Snsitivitätsliste enthalten ist, simuliert man nicht das Bauteil, welches eigentlich synthetisiert wird. Fazit: Für die Beschreibung von reiner Kombinatorik sollten immer alle Eingänge (= Signale die gelesen werden) in der Sensitivitätsliste stehen.

Die Sensitivity List kann durch eine WAIT ON Anweisung am Ende des Prozesses ersetzt werden. Dabei ändert sich das Verhalten nicht.

In diesem Beispiel wurde der else Zweig weggelassen. Bei der Simulation wird nun, falls SEL = '0' ist, der alte Wert von Z beibehalten, d.h. an Z wird keine Änderung durchgeführt.

In der entsprechenden Hardware muß hierfür auf ein speicherndes Element zurückgegriffen werden. Die Synthesewerkzeuge erzeugen darum ein Latch, bei welchem das SEL Signal am Takteingang anliegt. Dies ist im synchronen Design ein sehr schwer testbares Element und sollte daher nicht verwendet werden. I.A. werden nur flankengetriggerte FF verwendet, die ALLE an ein und demselben Takt angeschlossen sind. Dann besteht die Möglichkeit, alle FF durch spez. Scan-FF zu einem sog. Scan-Pfad zusammenzuschließen und mit einem zusätzlichen Eingangspin den Chip im Scan-Test-Modus gezielt in einen Zustand zu setzen oder interne Werte auszulesen.)

Hier wird ein taktflankengesteuertes D-FlipFlop beschrieben. Tritt ein Ereignis am Takt ein (“event) und hat dieses Ereignis den Wert EINS wird der Wert des Pins D an den Pin Q weitergegeben. (Man könnte auch auf die negative Taktflanke warten, dann muß eben CLK='0' sein).

Alle diese Beispiele würden bei der Simulation das gleiche Verhalten zeigen, doch haben sich in der Praxis zwei Varianten durchgesetzt. Diese sind die if-Anweisung und die wait until Anweisung mit dem Bedingungsausdruck clock_signal_name'EVENT and clock_signal_name='1'. Synthesetools, die den Standard IEEE 1076.6 unterstützen müssen aber ebenfalls die anderen Varianten in gleicher Weise synthetisieren.

Die If Variante ist insofern geeigneter, als daß man hier auch asynchrone Signale, wie ein Reset oder Set Signal, berücksichigen kann. Somit würde im ersten If-Zweig das asynchrone Signal abgepüft werden und im nachfolgenden “else if” Zweig das Taktsignal. (Siehe See Asynchronous Set/Reset)

Die Funktion rising_edge wird hier nur der Vollständigkeit halber erwähnt. Sie wird von den meisten Synthesewerkzeugen nicht akzeptiert (wg. 'last_value), kann aber in der Simulation durchaus Anwendung finden.

Dies ist die beschreibung eines Zählers ohne Rücksetzleitung. Es ist auf einige Besonderheiten hinzuweisen: Zuerst die range-Angabe bei der Portdeklaraion des Ausgangs. Hier werden nur die Zahlen von 0 bis 15 erlaubt, d.h. es genügen 4 bit zur binären Darstellung. Das Portsignal Q wird also durch das Synthesewerkzeug durch ein 4 bit breites Signal ersetzt (letztendlich werden durch die Synthesewerkzeuge alle Typen in std_logic Typen umgewandelt). Also wird ein 4 bit Zähler realisiert. Die nächste Besonderheit ist, daß der Portmodus out (des Signals Q) nur beschrieben und nicht gelesen werden kann. Daher muß ein (temporäres) Signal COUNT innerhalb der Architcture deklariert werden, um die Abfrage COUNT >= 9 realisieren zu können. Das Ergenis des Zählvorgangs wird dann in einer concurrent-signal-Anweisung (Q ⇐ COUNT) als extra Prozess an Q übergeben (d.h. jedes Ereignis an COUNT löst die Anweisung Q ⇐ COUNT aus. Die innere IF-Anweisung beschreibt somit die Kombinatorik vor den FF. Die Zahl der FF ergibt sich aus der Breite der Signale, welche innerhalb der äußeren IF-Anweisung ('event usw.) eine Zuweisung erhalten. In unserem Fall ist dies nur das Signal COUNT, der Breite 4 bit (wg. dem range 0 bis 15). Wichtig : In der Sensitivitätsliste steht nur das Signal CLK !!!

Um einen RESET zu beschreiben geht man wie folgt vor: Das RESET-Signal steht auch in der Sensitivitätsliste. Die RESET Abfrage kommt als erstes (asyn. RESET), in der nachfolgenden elsif-Anweisung steht die CLK Abfrage. Es gibt keinen else Zweig !!! Dies würde bei der Synthese zu einer Fehlermeldung führen. Die Kombinatorik für die Berechnung des nächsten Zustandes steht wie gehabt innerhalb des CLK-Zweiges (hier der elsif-Zweig). Ganz Allgemein gilt: die letzte elsif-Bedingung enthält die CLK-Abfrage (und zwar genau NAME'event and NAME='wert', wobei wert 0 oder 1 sein kann). Alle vorherigen if und elsif Bedingungssignale müssen auch in die Sensitivitätsliste, da ihre Abfragen asynchron zum Takt erfolgen sollen. Wichtig : Sind diese Signale nicht in der Senitivitätsliste enthalten, simuliert man etwas anderes als man synthetisiert, da den Synthesewerkzeugen die Sensitivitätsliste prinzipiell egal ist. Sie richten sich nur nach der Struktur des VHDL Codes innerhalb des Prozesses.

In diesem Kapitel wird gezeigt, welche Typen von endliche Automaten (FSM = Finite State Maschines) existieren und wie man sie in VHDL beziehungsweise auch grafisch modellieren kann. Im weiteren wird immer von synchronen Automaten ausgegangen. Prinzipiell kann ein endlicher Automat in VHDL entweder durch einen einzelnen Prozeß oder durch zwei getrennte Prozesse dargestellt werden. Wie dies gemacht wird und welche Vor- bzw. Nachteile die jeweilige Variante besitzt wird gezeigt. Für die Darstellung des aktuellen Zustandes werden im allgemeinen sprechende Namen verwendet. Man benötigt also eine Aufzählungstypen, der als Werte diese sprechenden Namen enthält. Später während der Synthese müßen diese Werte in eine binäre Darstellung umgewandelt werden. Dieser Vorgang nennt man Zustandskodierung (state encoding). Es gibt unterschiedlichste Varianten von endlichen Automaten. Die Standardvarianten aus der Theorie sind die Medvedev-, Moore- und Mealy-Automaten. Man unterscheidet aber weit mehr als dies drei Varianten. Z.B. ist es aus verschieden Gründen ratsam, die Ausgänge eines Moduls immer mit speichernden Elementen (Registern) zu versehen. Hierdurch ergeben sich für die Automaten weitere Darstellungsvarianten, die ebenfalls vorgestellt werden.

Im Bild ist ein einfacher Automat in drei Sichtweisen dargestellt. Die obere Grafik zeigt den Automaten als abstraktes Blockschaltbild, in dem die relevanten Signale bezeichnet sind. Der erste Block (Oval) repräsentiert die Logik des Automaten, während der zweite Block (Rechteck) die speichernden Elemente repräsentiert. In der unteren rechten Grafik ist der Automat als sogenanntes “Bubblediagram” dargestellt. Die Kreise markieren die verschiedenen Zustände des Automaten. Der Automat wechselt seinen Zustand, wenn die Bedingung entlang einer Transition (Pfeil) zum Zeitpunkt der aktiven Taktflanke mit Wahr berechnet wird. Dies entspricht einem synchronen Verhalten. Ausnahme ist das asynchrone Reset-Verhalten. Wird das Reset Signal aktiv, so wechselt der Automat unverzüglich in den Reset Zustand START. In der unteren linken Grafik ist der entsprechende Auszug aus dem VHDL Quelltext gezeigt. Der Automat wird in einem einzigen getakteten Prozeß beschrieben. Im ersten IF Zweig steht die Reset Sequenz drinnen, in dem zweiten, dem ELSIF Zweig, wird der restliche Automat beschrieben. In der den Automaten beschreibenden CASE Anweisung wird untersucht, welcher Zustand gerade eingenommen ist und ob gerade der entsprechende Eingangswert für einen Zustandswechsel anliegt.

Auf obiger Folie ist der gleiche Automat, wie eine Folie vorher dargestellt. Nun wird aber der Folgezustand NEXTSTATE explizit betrachtet. In der Blockgrafik ist dieses Signal also zusätzlich eingetragen. Im Bubblediagram ergeben sich hier zunächst keine Änderungen. Im VHDL Quelltext wird nun aber die Logik, welche den Folgezustand berechnet, in einem eigenen Prozeß beschrieben. Es existiert nun also ein getakteter Prozeß, der nur die Speicher beschreibt, und ein rein kombinatorischer Prozeß, der die Logik des Automaten beschreibt. In der entsprechenden CASE Anweisung wird wiederum der aktuelle Zustand abgefragt und die entsprechende Eingangsbelegung untersucht. Soll der aktuelle Zustand geändert werden, so unterscheidet sich NEXTSTATE von STATE. Mit der nächsten aktiven Taktflanke wird dieser neue Zustandswert als aktueller Wert übernommen.

Die Folien vorher haben gezeigt, daß man einen endlichen Automaten in VHDL durch einen oder auch durch zwei Prozesse beschreiben kann. Je nach Geschmack und eigener Erfahrung wird eine von beiden Darstellungsmöglichkeiten von den Entwicklern bevorzugt.

Allgemein gibt es folgende Vor- und Nachteile:

Struktur und Lesbarkeit Entsprechend der Struktur der später erzeugten Hardware sollte man auch in VHDL diese Struktur widerspiegeln. Da reine Kombinatorik und speichernde Elemente unterschiedliche Strukturelemente sind, sollten sie im VHDL Quelltext in zwei getrennte Prozesse beschreiben werden. Andererseits interessiert man sich i.a. nur für den tatsächlich stattfindenen Zustandswechsel, welcher dann auch nach außen sichtbar ist (STATE). Diesem wird eine Darstellung mit einem Prozeß gerechter. Die grafische Darstellung, welche oft als Spezifikation eines Automaten verwendet wird, gleicht mehr einer Darstellung mit einem Prozeß als einer mit zwei Prozessen. Ein Ein-Prozeß Darstellung bleibt hier also näher an der Spezifikation.

Simulation Treten bei der Simulation Fehler auf, so sind diese oft leichter zu finden, wenn man auf das Zwischensignal NEXTSTATE zugreifen kann. Man kann genau erkennen zu welchem Zeitpunkt und bei welchen Werten der Fehler zuerst auftritt und somit die Ursache des Fehlers genau lokalisieren. Somit ist eine Darstellung mit zwei Prozessen für die Simulation zu bevorzugen.

Synthese Aufgrund der Heuristik der Synthesealgorithmen kann man hier keine feste Aussage machen. Es hat sich aber bei ein paar Synthesewerkzeugen gezeigt, daß man bessere (weniger Gatterequivalente) Syntheseergebnisse erhält (bei schnellen, nicht ausgefeilten Syntheseläufen), wenn man zwei Prozesse verwendet. Grund hierfür ist die Nähe zur Hardwarestruktur.

Ein endlicher Automat ist eine abstrakte Beschreibungsform von digitaler Hardware. Für die Synthese müssen die Zustandswerte in passende binäre Darstellungen zur Verfügung stehen oder sie werden entsprechend während der Synthese in eine binäre Darstellung umgeformt. Diese Umformung wird als Zustandskodierung (state encoding) bezeichnet. Die meisten Synthesewerkzeuge wählen standardmäßig eine Binärkodierung, falls vom Entwickler nicht explizit eine andere Kodierung vorgegeben wird. Die Zustände des obigen Beipiels könnten also mit den Werten “00”, “01” und “10” kodiert werden. Es gibt aber weitere Kodierungsmöglichkeiten. Für eine geschwindigkeitsoptimale Schaltung wird z.B. standardmäßig eine “1-aus-n” Kodierung gewählt, die sich im Englischen one hot Kodierung nennt. Für jeden Zustand des Automaten wird ein eigenes Zustandsbit eingeführt. Ist ein Bit auf '1' gesetzt, so ist der entsprechende Zustand vom Automaten eingenommen. Bei der Kodierung der Zustände taucht aber ein nicht zu vernachlässigendes Problem auf: Besitzt ein Automat n Zustände so benötigt man z.B. für eine Binärkodierung ENTIER[ld(n)] FlipFlops, allso die nächst größere ganze Zahl des Zweierlogarithmusses von n. Im obigen Beispiel benötigt man also zwei FFs für eine binäre Kodierung des Automaten. Da dieser aber nur drei Zustände besitzt, mit 2 FFs aber vier Zustände (“00”, “01”, “10”, “11”) kodiert werden können, erhält man einen ungültigen Zustand, der zu einem unsicheren Automaten führt. Im Normalfall muß ein Abfangmechanismus vorgesehen werden, der ein fehlerhaftes Einnehmen dieses ungültigen Zustands korrigiert. Wie dies gemacht wird, wird in den nächsten Folien gezeigt.

Die naheliegendste Möglichkeit, ungültige Zustände abzufangen, wäre es, eine “when others” Abfrage in die CASE Anweisung aufzunehmen. Diese ist gedacht, um alle bisher in der CASE Anweisung nicht berücksichtigten möglichen Werte des überprüften Ausdrucks (hier: STATE) auszuwerten. Also sollten hiermit also alle ungültigen Zustände des Automaten sicher abgefangen werden können, in dem man z.B. in gegebenenfalls bei Auftreten eines ungültigen Zustandes in den RESET Zustand (hier: START) wechselt. VHDL ist aber eine sehr strikte Sprache. So stehen z.B. während der Simulation auch nur die Werte zur Verfügung, ie durch den Typ vorgegeben sind. In der Simulation existieren im oben gezeigten Beispiel also noch keine ungültigen Zustände, die dementsprechen auch nicht simuliert werden können. Desweiteren ist die synthetisierte Schaltung ebenfalls nicht zwingend sicher. Widerum kann es passieren, daß der Compiler des Synthesewerkzeugs den “others”-Zweig verwirft, weil dieser (noch) nicht vorhandene Werte abprüft und somit aus Sicht des Compilers überflüssig ist. Das reine Einfügen eines “when others” Zweige in die CASE Anweisung ist also keine ratsame Lösung.

Die zweite Möglichkeit ist es, zusätzliche Werte in die Typendeklaration aufzunehmen. Man muß hier soviel Werte in den entsprechenden Aufzählungstyp aufnehmen, so daß nach der Zustandskodierung keine ungültigen Werte auftreten können. Besitzt z.B. ein Automat 20 Zustände, so benötigt man bei einer binären Kodierung 5 FlipFlops. Mit 5 FFs kann man aber 32 Werte darstellen; es müssen also noch zusätzlich 12 Werte in den Aufzählungstyp des Automaten aufgenommen werden. Mit dem Auffüllen des Aufzählungstypen erhält man eine Beschreibung, mit der man nun auch in der Simulation das Verhalten des Automaten im Fehlerfall überprüfen kann. Man erhält auch nach der Synthese eine fehlersichere Schaltung für den Automaten. Diese Methode ist allerdings etwas umständlich, weil man unter Umständen gezwungen ist, sehr viel sogenannte Dummy Zustände aufzunehmen. Außerdem ist diese Methode nur dann anwendbar, wenn man eine feste Binärkodierung des Automaten vorraussetzt. Hat man einerseits z.B. 12 zusätzliche Werte aufgenommen, um einen binär kodierten Automaten sicher zu machen, so erhält man bei einem Wechsel zur one hot Kodierung 12 unnötige FFs (jeder Wert des Aufzählungstypen wird durchein Bit repräsentiert). Andererseits ist es z.B. bei einer one hot Kodierung nicht möglich, den Automaten durch Einfügen von zusätzlichen Werten, sicher zu machen. Jeder neue Wert würde nur zu einem zusätzlichen FF führen und damit die Anzahl der ungültigen Zustände erheblich erhöhen. Somit ist auch diese Methode nicht empfehlenswert.

Die beste Variante ist die der Handkodierung. Das heißt, man legt die Kodierung als Entwickler selbst fest. Dies erfolgt, in dem man statt eines Aufzählungstyps einen Vektortyp basierend z.B. auf dem std_ulogic_vector Typ festlegt. Die Breite dieses Vektors richtet sich nun nach der selber zu wählenden Kodierung. Das Zustandssignal erhält nun diesen Vektortyp (Man spricht auch vom “Zustandsvektor”). Als nächstes definiert man Konstanten, die die entsprechenden Zustände des Autmaten repräsentieren sollen. Diese Konstanten erhalten entsprechende Vektorwerte gemäß der selbstgewählten Kodierung. Mit diesen Konstanten kann also die Kodierung exakt festgelegt werden und kann nicht mehr durch das Synthesewerkzeug verändert werden. Somit ist das VHDL Modell auch 100%'tig portierbar. Desweiteren kann nun auch der Fehlerfall in der Simulation überprüft werden, da der Zustandsvektor die spätere Hardware widerspiegelt und alle notwendigen Werte annehmen kann. Als einziger Nachteil kann angeführt werden, daß bei der Handkodierung etwas mehr Schreibarbeit geleistet werden muß; dies gilt vor allem, wenn sich die Kodierung ändern soll. Die Methode der Handkodierung ist also die beste Variante einen Automaten fehlersicher zu entwerfen und zudem garantiert portierbar zu halten.

Die grundlegenden drei verschiedenen Automaten aus der Theorie (Medvedev, Moore und Mealy Automaten) unterscheiden sich in der Berechnung der Ausgangswerte. Beim Medvedev Automat ist der Wert des Ausgangs immer gleich dem Wert des Zustandvektors. Das heißt die Logik für den Ausgang besteht aus einer reinen Verdrahtung; nämlich der Verdrahtung des Zustandvektors mit dem Ausgangsvektor. Diese Verdrahtung wird in VHDL mit einer einfachen Signalzuweisung gemacht. Diese ist im obigen Beispiel als nebenläufige Anweisung hingeschrieben worden.

Hier ist ein Beispiel für einen Medvedev Automaten gezeigt. Im Bubblediagram sind die Zustände des Automaten (START, MIDLLE, STOP), deren Kodierungen (“00”, “11”, “01”; siehe Konstantendeklaration) und die Zustandsübergänge aufgezeichnet. Die sogennanten Gewichte (Label) der Transitionen (Pfeile) bestimmen die Werte des Eingangsvektors (hier die beiden Signale A und B) für die der entsprechende Zustandsübergang durchgeführt wird. Bei 10 | 01 wird der Zustandsübergang durchgeführt, wenn der Eingangsvektor entweder den Wert “10” oder den Wert “01” annimmt. Die Funktionalität des Automaten ist entsprechend im VHDL Quelltext auf der linken Seite beschrieben. Es wurde eine Darstellung mit zwei Prozessen gewählt. Man erkennt, daß hier der Ausgangsvektor in einer nebenläufigen Anweisung mit dem Zustandsvektor verdrahtet ist.

In der Waveform sieht man die Signalverläufe der beschriebenen Schaltung während einer Simulation. Man erkennt deutlich, daß es sich um einen Medvedev Automaten handelt, da die Werte des Ausgangsvektors (repräsentiert durch die beiden Signale Y und Z) immer mit den Werten des Zustandvektors übereinstimmen.

Hier wird ein Moore Automat dargestellt. Der Wert des Ausgangsvektors ergibt sich als Funktion des aktuellen Zustandswertes. Dementsprechend taucht in der Blockdarstellung ein zweiter Logikblock auf, der den speichernden Elementen nachgeschaltet ist. Dieser Logikblock beinhaltet die Hardware, welche den Ausgangswert aus dem Zustand des Automaten ableitet. Im VHDL Quelltext wird diese Logik in einem eigenen Prozeß beschrieben. Da der Wert des Ausgangsvektor nur von dem Wert des Zustands abhängt, wird dementsprechend nur der Zustandsvektor in der Sensitivity Liste des Prozesses aufgenommen.

Das Bild zeigt ein Beispiel für einen Moore Automaten. Der Unterschied zum Medvedev Automaten ist daraus ersichtlich, daß sich der Kodierungswert der Zustände von den entsprechenden Ausganswerten unterscheiden. Beide sind in dem jeweiligen Kreis eingezeichnet. Die Werte des Ausgansvektors (Y,Z) sind die gleichen, die beim Beispiel des Medvedev Automaten gewählt wurden. Die Zustandskodierung ist aber nun binär aufwärtszählend kodiert. Im VHDL Quelltext ist die Logik für die Ausgangssignale aus Platzgründen nicht in einem Prozeß dargestellt, sondern in einzelnen nebenläufigen Anweisungen. Man erkennt deutlich, wie die Ausgangswerte aus den Zustandswerten berechnet werden.

In der Waveform erkennt man wiederum sehr deutlich die Charakteristika eines Moore Automaten. Die Werte des Ausgangsvektors ändern sich synchron mit dem Zustandswert des Automaten. Diesesmal unterscheiden sich aber die Werte des Ausgangsvektors von denen des Zustandvektors.

Es wird ein Mealy Automat dargestellt. Der Wert des Ausgangsvektors ergibt sich als Funktion des aktuellen Zustandswertes sowie des Wertes des Eingangsvektors. Deswegen wird auch in der Blockdarstellung der Eingangsvektor mit in den Logikblock der Ausgangsfunktion hieneingeführt. Im VHDL Quelltext taucht nun auch der Eingangsvektor in der Sensitivity Liste des Prozesses zur Berechnung der Ausgangswerte auf.

Im Bild ist ein Beispiel für einen Mealy Automaten dargestellt. Im Gegensatz zu den beiden anderen vorher vorgestellten Automatentypen kann man den Wert des Ausgangsvektors nicht direkt in den entsprechenden Kreis zeichen. Vielmehr ist es ja jeweils eine Funktion, die sich von Zustand zu Zustand unterscheidet. Diese Funktionen werden in einer grafischen Darstellung oftmals nicht explizit angezeigt, sondern hinter den Zuständen “versteckt”. Im VHDL Quelltext ist die Berechnung der Ausgangswerte wieder durch nebenläufige Anweisungen dargestellt. Man erkennt, daß nun auch die Eingangssignale auf der rechten Seite der Zuweisungen auftauchen und somit in die Berechnung einfließen.

In der Waveform sind die Charakteristika eines Mealy Automaten wieder gut zu erkennen. Am auffallendsten ist, daß sich die Werte der Ausgangssignale ab und zu auch mit den Werten der Eingangssignale ändern. Zudem ändern sie sich natürlich auch bei einem entsprechenden Zustandswechsel. Wie man erkennt, kann dies dazu führen, daß man sogenannte Spikes erhält, also Signalpulse mit einer sehr geringen Pulsbreite. Dies kann zu Fehlverhalten in nachgeschalteten Blöcken führen, was natürlich zu vermeiden ist. Somit muß man bei der Modellierung eines Mealy Automaten, wie er hier beschrieben wurde, sehr sorgfältig vorgehen.

Es gibt unterschiedliche Gründe, welche Version von Automat ein Eintwickler verwendet. Für den Medvedev Automat spricht die geringere Menge an Hardware, die für den Automaten und Ausgangsberechnung benötigt wird. Da die Ausgänge direkt aus dem Zustandsvektor hervorgehen, wird für diese keine zusätzliche Kombinatorik (Logik) benötigt und neu Werte der Ausgänge liegen automatisch synchron zum Takt an. Allerdings ist der Entwickler gezwungen die Kodierung des Automaten starr vorzugeben. Dies bedeutet etwas mehr Aufwand und eine geringe Flexibilität, die sich bei eventuellen Änderungen des Automaten negativ auswirken. Der Moore Automat wird bevorzugt, da er flexibler als der Medvedev Automat ist und die Ausgangsberechnung nur von dem Wert des Zustandsvektors abhängt. Durch diese reine Abhängigkeit vom Zustandsvektor werden die Ausgangswerte relativ sicher berechnet, d.h. die Werte liegen sicher vor der nächsten Taktflanke an und es treten keine Spikes auf. Ein kleiner Nachteil, der manchmal aber relevant wird, ist, daß eine Eingangsänderung sich erst einen Taktzykklus später auf den Ausgang auswirkt (es muß sich ja vorher erst der Zustandsvektor ändern). Es kann vorkommen, daß man sich diese Zeitverzögerung nicht leisten kann und somit keinen Moore Automaten verwenden kann. Der Mealy Automat ist der flexibelste der vorgestellten Automaten. Durch die Abhängigkeit vom Zustandsvektor und vom Eingangsvektor kann der Ausgangsvektor auf jede Änderung reagieren. Er besitzt aber einige Nachteile. So können z.B. Spikes auftreten, wie dies in der vorherigen Folie gezeigt wurde. Schaltet man zwei Mealy Automaten hintereinander, so läuft man Gefahr, daß man eine kombinatorische Rückführungsschleife erzeugt, wie dies im obigen Bild hervorgehoben ist. Zudem entstehen hier unnötig lange Pfade. Es werden zwei Logikblöcke in Reihe geschaltet, so daß eine Datenänderung relativ lange braucht, um zu den nächsten FFs zu gelangen. Dies wirkt sich negativ auf die zu wählende Taktfrequenz aus.

Auf der vorherigen Folie wurde gezeigt, daß bei der Verkettung zweier Mealy Automaten kombinatorische Rückführungsschleifen entstehen können. Diese können vermieden werden, in dem man die Ausgänge “abtaktet”, d.h. mit FFs versieht. Somit wird die Rückführungsschleife unterbrochen. Die allgemeinen Grunde für das Abtakten von Ausgängen sind das verhindern von langen Pfaden zwischen FFs und das erzwingen von sicheren Zeitverhalten. In dem man prinzipiell alle Ausgängen abtaktet trennt man die Ausgangslogik von dem nächsten Logikblock ds nachgeschalteten Moduls und verkürzt somit den Pfad. Hierdurch sind wiederum höhere Taktfrequenzen möglich. Desweiteren haben Synthesewerkzeuge oft Probleme, wenn sie Pfade über Modulgrenzen hinweg auf die Laufzeit hin optimieren sollen. Durch das Abtakten kann dieses Problem umgangen werden. Ein weiterer Vorteil ist, daß das nachgeschaltete Modul mit sicheren Eingangsdaten arbeiten kann, d.h. die Daten ändern sich mit der aktiven Taktflanke und es treten keine Spikes auf. Dies läßt dem Entwickler des nachgeschlateten Moduls mehr Freiheiten beim Entwurf.

Bei derAbtaktung von Automaten unterscheidet man zwei Varianten. Bei der ersten Variante erhält man einen Takt Verzögerung. Es werden hier einfach FFs an die Ausgänge angeschlossen. Somit liegen die Ausgänge erst nach der nächsten aktiven Taktflanke an. Dieser Taktverzug kann manchmal nicht akzeptiert werden. Bei der zweiten Variante werden nicht nur FFs an die Ausgänge angeschlossen, sondern das NEXTSTATE Signal um die Zustands-FFs herum in die Ausgangslogik geführt. Bei größeren Automaten kann dies zu unübersichtlichen Abhängigkeiten führen und relativ lange Pfade erzeugen (es sind ja nun wieder zwei Logikblöcke hintereinander geschaltet). Allerdings stellt dies Version von Automat die flexibelste und schnellste (Anzahl der Taktperioden) Variante mit sicheren, sprich abgetakteten, Ausgangssignalen dar.

Im Bild ist wiederum das Beispiel des Mealy Automaten dargestellt. Im Bubblediagram sind aber die Anweisungen, die vorher im jeweiligen Zustand versteckt waren, nun explizit an Selbstschleifen angetragen. Außerdem erhalten die Transitionen nun ebenfalls Anweisungen an die Ausgangssignale. Somit ist ersichtlich, daß mit jeder aktiven Taktflanke ein Wert an die Ausgänge zugewiesen wird, d.h. die Ausgänge müssen mit FFs versehen werden. Da an den Transitionen die Zuweisungen des alten Zustandes (der gerade verlassen wird) stehen, sind die Ausgänge nur vom aktuellen (alten) Zustand (STATE) abhängig aber nicht vom zukünftigen Zustand (NEXTSTATE). Prinzipiell lassen sich die Ausgangsanweisungen wieder in der Grafik in den Zuständen verstecken. Hierfür werden dann sogenannte in-state actions für die Selbstschleifen und exit-state actions für die Transitionen verwendet. Im VHDL Quelltext sehen wir, daß für die Ausgänge zunächst Zwischensignale (Y_I, Z_I) berechnet werden, die von den Eingangswerten und dem aktuellen Zustand abhängen. Diese Zwischensignale werden dann in dem nachfolgenden getaktetem Prozeß mit FFs versehen.

Die Wavefrom zeigt die Simulationsergebnisse des abgetakteten Mealy Automaten. Man erkennt deutlich, daß sich die Ausgangnswerte erst einen Takt nach der entsprechenden Zustandsänderung ändern. Man erkennt außerdem, daß Eingangsänderungen erst mit der nächsten aktiven Taktflanke zu Änderungen der Ausgangswerte führen können. Alle Signalwechsel interner Signale erfolgen also synchron mit dem Takt, so daß man ein wohl definiertes Verhalten des Automaten erhält. Dennoch muß man bei der Modellierung sorgsam vorgehen. Gerade der Taktverzug zwischen Zustandsänderung und Ausgangsänderung muß berücksichtigt werden. Wenn man hier nicht aufpasst, können die Werte die in der Waveform eingekreist sind zu einem unerwünschten Verhalten von nachgeschalteten Modulen führen.

Im Bild ist wiederum das Beispiel des Mealy Automaten gezeigt. Neben denn vorher schon gezeigten Selbstschleifen sind auch hier Ausgangsanweisungen an die Transitionen angetragen. Somit stehen Anweisungen an Ausgänge nur an getakteten Übergängen und es müssen dementsprechend FFs für diese Ausgänge vorgesehen werden. Allerdings stehen nun an den Transitionen die Ausgangsanweisungen, die ebenfalls im Zielzustand gelten. Somit hängen die Ausgänge einerseits von den aktuellen Eingangswerten und andererseits von dem zukünftigen Zustand (NEXTSTATE) ab. Dies spiegelt sich also nun auch im VHDL Quelltext wieder. Diesesmal werden die Werte der Zwischensignale aber aus den Werten des momentanen Folgezustands und der Eingänge gebildet. Die Zwischensignale werden wiederum in einem getakteten Prozeß mit FFs versehen.

Die Waveform zeigt die Ergebnisse der Simulation des abgetakteten Mealy Automaten. Man erkennt daß sich nun die Ausgänge wiederum synchron mit den Zustandsänderungen ändern und keine unerwünschten temporären Signalwert anliegen.

Wird ein Prozeß wie oben beschrieben, so entsteht daraus die gezeigte Hardware. Unerwünscht ist natürlich die Rückkopplung vom Ausgang zum Eingang (feedback loop), wodurch der Prozeß nie zur Ruhe kommt.

Eine IF Anweisung wird grundsätzlich als Multiplexer und zusätzlicher vorgeschalteter Logik realisiert. Aus diesem Grund werden im Beispiel (1) zwei Addierer implementiert, so wie der Designer es beschrieben hat. Jedoch ist leicht zu erkennen, daß in diesem Fall ein Addierer ausreicht, um die Funktionalität abzubilden. Der Beispiel Code 2 zeigt die alternative Beschreibungsvariante, bei der auf jeden Fall nur ein Addierer realisiert wird. Zur Beruhigung kann gesagt werden, daß viele Synthesewerkzeuge auch im Fall 1 die Optimierungsmöglichkeit erkennen und nur einen Addierer implementieren.

Die Struktur der im ersten Syntheseschritt erzeugten Hardware wird sehr stark durch den VHDL Code selbst beinflußt. Konsequenterweise hat der Kodierstil einen großen Einfluß auf die Optimierungsalgorithmen. Da nicht alle Optimierungswerkzeuge in der Lage sind die Struktur eines Entwurfes zu optimieren (z.B. resource sharing, kurze Pfade für “langsame” Signale) empfiehlt es sich, die passende Struktur durch entsprechende Kodierung vorzugeben.

Unterschiedliche VHDL Kodierungsstile werden anhand eines Multiplizierers zweier 2-bit Nummern gezeigt. Der größte Eingangswert ist jeweils 3, womit sich ein maximaler Ausgabewert von 9 ergibt. Es werden also insgesamt vier Eingangs- und vier Ausgangsbit benötigt. Die Entity des VHDL Entwurfs soll für alle Realisierungen gleich sein.

Der direkteste Ansatz ist eine Funktionstabelle des Multiplizierers. Die Tabelle listet alle möglichen Eingabewerte und entsprechende Ausgabewerte auf. Aus dieser Tabelle können direkt die Karnaugh Diagramme für die Ausgangssignale abgeleitet werden.

Die EINS-Stellen der Funktionstabelle werden in die Karnaugh-Diagramme eingetragen. Durch zusammenfassen benachbarter Bereiche lassen sich die minimalen Ausgangsfunktionen ermitteln.

Hier wird einfach die Funktionstabelle als CASE-Anweisung in VHDL realisiert. Um die Eingänge in der CASE-Anweisung auf einmal abfragen zu können, werden sie dem Zwischensignal A_B mit einer concurrent-signal Anweisung und dem String-Operator ”&“ zugewiesen. Der kombinatorische Prozess muß dann natürlich sensitv auf A_B sein. Die Signalzuweisung an A_B ist auch ein Prozess, der sensitiv auf alle rechts stehenden Signale (also unser Eingänge) ist. Letztendlich ist A_B bei der Synthese rein temporär.

Hier werden nur die Ausgangsfunktionen in VHDL wiedergegeben. Letzendlich haben wir die Synthese selbst, ohne Tooleinsatz durchgeführt. Wichtig ist hier der rein kombinatorische Prozess, der sensitiv auf alle Eingänge ist.

Hier ist nun der elegante Einsatz des Datentyps INTEGER zu sehen. Es genügt nur die Multiplikation mit dem entsprechenden Operator zu beschreiben. Wesentlich ist die Beschränkung des INTEGER-ranges, da ohne diese Angabe automatisch der größtmögliche Wert gewählt wird. Dies würde jeweils ein 32-bit breites Signal ergeben. Für die Synthese ist alles bekannt: 1: die Breite der Ein- und Ausgangssignale 2: die Operation “MAL” Mit diesen Angaben kann das Synthesewerkzeug eine Funktionstabelle aufstellen und eine Minimierung durchführen.

Das Synthesewerkzeug erkennt über den Operator, welche Funktion abgebildet werden soll. Je nach Zieltechnology realisiert das Tool den Operator als “Gattergrab”, d.h. es baut die Funktionalität mit einzelnen Standardzellen nach, oder verwendet eine in der Bibliothek vorhandene Komponente. Der Halbleiterhersteller hat diese flächen- und laufzeitoptimiert. Bei der Synthese kann man auch gezielt Angaben über die spätere Architektur (ripple-carry oder carry-look-ahead) des Operators machen. Diese Informationen werden dem Synthesewerkzeug entweder über Kommentare im VHDL Code mitgeteilt oder direkt im Tool ausgewählt.

Genauso wie bei der Multiplikation, wird auch bei der Addition verfahren. Die Bitbreiten der Portsignale sind über die INTEGER-Range Beschränkung bekannt. Der Operator “PLUS” kann in die entsprechende Funktionstabelle umgesetz werden. Eine Synthese ist also möglich. Anders ist es bei der Verwendung von eigenen Datentypen (nicht INTEGER). Durch die Sprache (das LRM=Language Reference Manual) werden per default die arithmetischen Operatoren nur für INTGER (und REAL) definiert. Bei eigenen Datentypen muß man eigentlich nur eine Möglichkeit besitzten, dem Synthesewerkzeug (und dem Simulator) mitzuteilen, daß z.B. eine Addition durchgeführt wird; das Synthesetool also eine Funktionstabelle für die Addition aufzustellen hat. Aus den Deklarationen der Signale (hier als ARRAY) ist die Bitbreite ablesbar, nötig ist also nur noch die Information, was zu tun ist. Da alle Operatoren in VHDL als Funktionen realisiert sind, die auch überladen (siehe auch OVERLOADING) werden können, muß ein “neuer” Operator für eben diese Datentypen geschrieben werden. Diese neue Funktion (mit dem gleichen Namen ”+“, nur den neuen Parametern) enthält im Funktionsrumpf in Kommentarzeichen versteckt, Anweisungen für das Synthesewerkzeug (sog. PRAGMAs) z.B. bei SYNOPSYS:

          function "+"(L: INTEGER; R: UNSIGNED) return UNSIGNED is
          -- pragma label_applies_to plus
          -- synopsys subpgm_id 241
          constant length: INTEGER := R'length + 1;
          begin
              return CONV_UNSIGNED(plus(CONV_SIGNED(L, length), CONV_SIGNED(R, length)), length-1);
          end;

Der Funktionsrumpf besteht nur aus dem Aufruf der Funktionen “plus” und den Konvertierungsroutinen, deren Funktionsrumpf wird bei der Simulation ausgeblendet. Diese Funktionen sind in eigenen Packages der Synthesetoolhersteller definiert. Wichtig ist eigentlich nur, daß die Deklarationen der Funktionsköpfe in den Packages GLEICH sind (wird zur Zeit noch nicht durch einen Standard vorgeschrieben). Dann könnte man auch das Synthesewerkzeug “gefahrlos??” wechseln (naja, never change a running system…)

Während Algorithmen verflechtete Logik optimieren können, können bestimmte Hardwarekonstrukte während der Synthese nur schwer verändert werden. Die Verwendung von IF Schleifen z.B. impliziert unterschiedliche Prioritäten, d.h. es muß eine Art Kette von Multiplexern gebildet werden. Im Fall der CASE Anweisung existieren keine Prioritäten, so daß sich eine parallele Struktur ergibt.

Es ist auch nicht sinnvoll innerhalb eines Chips mit einem Bussystem zu arbeiten, da man nie zu jedem Zeitpunkt gewährleisten kann, daß nur ein Treiber aktiv ist. Außerdem hängen die Gatterlaufzeiten von der Technologie ab, so daß in der momentan eingesetzten Technologie keine Probleme auftreten, aber diese Bedingungen bei einem Wechsel durchaus nicht mehr erfüllt sind. Tritt der Fall ein, daß ein Busteilnehmer eine '0' übertragen will und ein anderer eine '1', dann fließt an dieser Stelle viel Strom und eine Beschädigung des Bausteins ist nicht mehr auszuschließen. Welche Alternativen gibt es?

Aufgrund der unterschiedlichen Gatterlaufzeiten für steigende und fallende Flanken kann der Designer nicht gewährleisten, daß zu jedem Zeitpunkt nur ein Treiber aktiv ist. Um diese Unsicherheit zu vermeiden, verwendet man anstatt der Tristate Gatter Multiplexer. Der Aufbau einer Busstruktur mit Multiplexern wird auf der folgenden Folie dargestellt.

Grundsätzlich sollte man innerhalb des Chips mit den drei Signalen DIN, DOUT und DOUT_EN arbeiten. An der Chipgrenze setzt der Designer eine bidirektionale Padzelle ein, die intern genau diese drei Signale zur Verfügung stellt. Über verschiedene Multiplexer wird das entsprechende Datensignal ausgewählt, das am Schluß aus dem Chip geführt wird.

Bei einer integierten Schaltung ist die kritischste Phase die Hochlaufphase. Man muß immer gewährleisten, daß eine Schaltung nach dem Einschalten in den gleichen Zustand gebracht wird. Dies erreicht der Designer damit, daß er an jedem speichernden Element (Flipflop) denselben Systemreset anschließt.

Für alle Signale, denen einen Wert innerhalb eines getakteten Prozesses zugewiesen wird, werden Flip Flops erzeugt. Aus diesem Grund empfiehlt es sich, Register und rein kombinatorische Logik in getrennte Prozesse zu schreiben. Es bleibt dem Designer überlassen, ob er dabei die zu den Registern gehörige (vorgeschaltete) Logik (hier Logic A) ebenfalls in einen eigenen Prozeß schreibt, oder mit in den getakteten Prozeß aufnimmt. Auf jeden Fall sollte aber die nachgeschaltete Logik (hier Logic B) in einen anderen Prozeß geschrieben werden, da sie nichts mit der Generierung der hier dargestellten Flip Flops zu tun hat.

Im ersten Fall wird für die Variable kein FlipFlop erzeugt. Die Variable bekommt einen Wert zugewiesen, der anschließend sofort wieder ausgelesen wird und somit nicht gemerkt (“gespeichert”) werden muß. Dagegen im zweiten Beispiel wird erst die Variable gelesen und erhält dann den Wert “INPUT * 2”. Das bedeutet, der Wert der Variablen TEMP muß nach dem Verlassen des Prozesses gespeichert werden, um bei der nächsten Änderung von “INPUT” den aktuellen Wert an das Signal “OUTPUT” zu übergeben. Für diesen Vorgang ist ein speicherndes Element erforderlich.

Es werden insgesamt 3 Flipflops erzeugt. Ein Flipflop für das Signal “OUTPUT”. Regel: bei allen Signalen, die in einem getakteten Prozeß einen Wert zugewiesen bekommen, wird ein Flipflop erzeugt. Für die Variablen LFSR(2) und LFSR(1) benötigen wir auch noch je ein speicherndes Element, da diese erst gelesen werden und dann die eigentliche Zuweisung erfolgt.

Ein Ziel, bzw. die Praxis ist, daß man bestimmte Teile eines VHDL Entwurfes in anderen Entwürfen wiederverwendet. Dies wird in der Praxis am häufigsten durch Kopieren und Einfügen gemacht. Dies geht aber nur insoweit die Funktionalität des alten Entwurfes nicht geändert werden muß. Um für diesen Fall die Adaptierung des alten VHDL Codes zu erleichtern, kann man in Frage kommende VHDL Entwürfe parametrisierbar gestalten. Durch das Festlegen der Parameterwerte wird dann auch die Struktur oder die Funktione des entsprechenden Moduls den aktuellen Bedürfnissen angepaßt.

Eine Möglichkeit ein Design zu parametrisieren, ist die Verwendung von Konstanten. Das Besipiel zeigt einen Zähler mit Reset und Enable Signal. Wenn das Enable Signal gesetzt ist, wird immer weiter gezählt. Die obere Zählergrenze wird durch eine Konstante (MAX_VALUE) festgelegt, deren Wert in dem Package T_PACK definiert wird. Überall wo COUNTER_C eingebaut wird (mittels einer Komponenteninstantiierung), wird der Zählerbereich neu festgelegt (über die Konstantendefinition).

Wenn man den gleichen Zähler mehrfach mit unterschiedlichen Zählergrenzen in einem Entwurf verwenden möchte, so muß man statt Konstanten Generics verwenden. Generics werden wie Ports in der Entity deklariert und erhalten einen Wert in der übergeordenten Komponenteninstantiierung. In der entsprechenden Komponenteninstantiierung muß also neben einer Port Map auch eine Generic Map vorhanden sein. Man kann einem Generic aber auch einen Default-Wert in der Entitydeklaration geben, welcher verwendet wird, wenn keine Wert in der Komponenteninstantiierung zugewiesen wird.

Die Komponentendeklaration muß nun eine zusätzliche Genericdeklaration enthalten.

Wie funktioniert aber das Einbinden von der Komponente COUNTER_G? Wenn ein Defaultwert für die Generics gegeben ist, dann wird nicht unbedingt eine Generic Map in der Komponenteninstantiierung benötigt (siehe COUNTER1). Allerdings kann der Defaultwert natürlich in einer Generic Map durch explizite Zuweisung einees Wertes überschrieben werden (siehe COUNTER2).

Der Hauptvorteil der Verwendung von Generics ist, daß das selbe VHDL Model mit unterschiedlichen Genericwerten und damit mit unterschiedlicher Funktionalität eingebunden werden kann. Wie auch immer können nur Generics vom Typ integer synthetisiert werden.

Wenn eine Komponente mehrfach instantiiert wird, wäre eine Instantiierung jeder Komponente einzeln etwas mühsam. Für diesen Fall bietet die Generate Anweisung eine Lösung. Innerhalb der Anweisung stehen nebenläufige Anweisungen, die sooft eingebaut werden, sooft die Schleife “durchlaufen” wird. So wird der Zähler im obigen Beispiel 3 mal eingebunden, mit jeweils unterschiedlichen Zählergrenzen. Der Schleifenzähler wird wiederum implizit deklariert und kann nur gelesen werden.

Um die Generate Anweisung noch komfortabler zu machen, gibt es die If-Generate Anweisung. Nur wenn der entsprechend Boolsche Ausdruck mit wahr berechnet wird, wird die entsprechende Hardware implementiert werden. In dem Boolschen Ausdruck können auch Konstantne und Generics überprüft werden. Die If-Generate Anweisung umfaßt wieder nur nebenläufige Anweisunge, so daß kein Else Zweig oder ähnliches benötigt wird.

Im obigen Beispiel werde acht Zähler implementiert. Für K=1…5 ist die oberste Zählergrenze nach 2^K-1 (1,3,7,15,31) berechnet. Für höhere Werte von K beträgt die Zählergrenze fest 31.

Konstanten und Generics sin sehr hilfreich, wenn ein VHDL Modul für die Synthese konfiguriert werden soll (welche Funktion soll implementiert werden).

Soll während des Betriebs ein Parameter geändert werden (welche Funktion soll ausgeführt werden), kann dies nur über Signale gemacht werden.

Die Zählergrenze im obigen Beispiel kann während des Betriebs auf Werte zwischen 1 und 1023 gesetzt werden. Um dies zu erreichen, muß ein gewisser Mehraufwand beim Codieren betrieben werden. Das Signal MAX_VALUE muß außerdem zum Zeitpunkt der aktiven Taktflanke stabil anliegen. Wenn dies nicht garantiert werden kann, müssen speichernde Elemente eingeugt werden, die nur zu ganz bestimmten Zeitpunkten aktualisiert werden.


Chapters of System Design > Synthesis