Entwurf eines Aufzugs in VHDL
Dieses Dokument existiert auch in einer
Postscript-Version,
allerdings ohne Anhang.
Inhaltsverzeichnis
  -  Überblick
  
-  Der Aufzug
       
	 -  Spezifikation der Ports
	 
-  Aufzugsautomat
       
 
-  Der Controller
       
	 -  Spezifikation der Ports
	 
-  Die Mathematik des Controllers
	 
-  Interne Variablen und Signale
       
 
-  Das System
  
-  Aufgaben
       
	 -  Entwurf einer struktur-orientierten Beschreibung des Controllers
	 
-  Gerechtigkeit
	 
-  Sicherheit
       
 
-  Anhang
       
	 -  Der Aufzug in VHDL
	 
-  Der Controller in VHDL
	 
-  Das System in VHDL
	 
-  Aktionsdatei fü den Simulator
	 
-  Die durchgerechnete Beispielsimulation
	 
-  Das verwendete Smax-Paket von H. Buettner
       
 
Dieser Text beschreibt und erläutert die Implementation des 
Aufzugsystems in VHDL. Einige Simulationsbeispiele befinden sich im Anhang. 
Das gesamte System zeichnet sich durch seine Flexibilität aus. Es ist
nicht auf 3 Stockwerke beschränkt, sondern fährt beliebig viele 
Stockwerke an. Die genaue Anzahl wird mit einem generic Parameter 
festgesetzt. Um dies zu erreichen, verwendet die Implementation die 
Möglichkeiten zur Bitmanipulation von Smax. Die Programme 
können nur nach vorheriger Übersetzung des Smax-Paketes 
kompiliert und simuliert werden.
Die Implementation teilt sich in drei Teile. Der erste Teil ist der Aufzug
selber, womit der sich bewegende Kasten gemeint ist. Der Aufzug ist als dummer 
Automat konstruiert; er erhält Befehle und führt sie aus. Der zweite 
Baustein ist der Controller, er wertet den Zustand des Aufzuges aus, nimmt die 
Bedienungen der Passagiere entgegen und gibt dem Aufzug dementsprechende 
Befehle zurück. Diese ersten zwei Bausteine sind zwar separat 
implementiert, können jedoch nicht einzeln existieren. Aus diesem Grunde 
wurde der dritte Teil, abstrakt System genannt, geschaffen. Das 
System verkabelt beide Bausteine miteinander und legt die Anzahl der zu 
befahrenden Stockwerke fest.
Um die gewünschte Flexibilität zu erreichen, mußte ein 
Weg gefunden zu werden, die Stockwerke beliebig skalierbar zu verwalten. Die 
Lösung ist, das "Gebäude" als Bitfeld zu kodieren. Jedes Stockwerk 
wird durch ein Bit in diesem Bitfeld repräsentiert; das oberste Stockwerk 
das höchstwertige Bit, und das Erdgeschoß das unterste Bit.
Dieser Text ist als Beschreibung und Dokumentation zum VHDL-Text 
gedacht. Die VHDL-Beschreibung ist ohne diesen Text nicht allzu 
verständlich, und dieser Text macht ohne das danebenliegende Programm 
wahrscheinlich auch recht wenig Sinn.
Wie bereits erwähnt ist der Aufzug ein blöder Automat, der 
einfach nur das tut was man ihm sagt. Um die Aufgabe noch etwas interessanter 
zu gestalten, hat der Aufzug eine Tür, die korrekt geöffnet und 
geschlossen werden muß. Weiterhin hält der Aufzug nicht in 
Stockwerken an, die nicht bedient werden sollen.
  Tabelle 1 - Ports des Aufzuges
   | Name | Richtung | Typ | Beschreibung | 
 | progress | in | bit | Bei jeder aufsteigenden Flanke dieses Signals führt der Automat einen Zustandsübergang aus | 
 | go_up | in | bit | Befehl, aufwärts zu fahren | 
 | go_down | in | bit | Befehl, abwärts zu fahren | 
 | open_door | in | bit | Wenn 1, dann soll die Tür geöffnet werden, wenn 0, dann soll die Tür geschlossen werden oder geschlossen bleiben. | 
 | at | out | bit_vector | Das Stockwerk, in dem sich der Aufzug befindet. Wenn sich der Aufzug bewegt, dann wird das Stockwerk angezeigt, in dem der Aufzug als nächstes ankommen wird. Genau ein Bit in diesem Vektor ist gesetzt. | 
 | door_open | out | bit | Status der Tür, ob geöffnet (1) oder geschlossen (0) | 
 | moving | out | bit | Bewegungszustand: Aufzug bewegt sich (1) bzw. Aufzug ist gestoppt (0) | 
Der Aufzug kann wie folgt als Automat beschrieben werden. Da sich der 
Automat auf jedem Stockwerk gleich verhält, ist hier nur ein Zyklus 
dargestellt. "S" ist die Kodierung des jeweiligen Stockwerks.
  
   Zustandskodierung: - moving & door_open & mvwait & at -
    Zustandskodierung: - moving & door_open & mvwait & at - 
    Kodierung der Eingabe: - go_up & go_down & open_door - 
  
Die Zustände können wie folgt beschrieben werden:
  -  001S 
- Sozusagen der Basiszustand. Der Aufzug 
       steht mit geschlossener Tür wartend in einem 
       Stockwerk. Von hier kann der Aufzug entweder nach oben 
       oder unten fahren sowie die Tür öffnen.
  
-  011S 
-  Die Tür ist geöffnet. Die 
       Tür muß vor der Abfahrt erst wieder 
       geschlossen werden.
  
-  100S+1 
-  Der Aufzug hat sich nach oben in 
       Bewegung gesetzt.
  
-  100S-1 
-  Der Aufzug hat sich nach unten in 
       Bewegung gesetzt.
  
-  101S 
-  Der Aufzug ist kurz davor, das 
       nächste Stockwerk zu erreichen. Von diesem 
       Zustand aus kann der Aufzug entweder anhalten 
       (zurück nach 001S) oder aber nach oben 
       oder unten weiterfahren.
Es ist sofort ersichtlich, daß diese Spezifikation nicht Narrensicher
ist. Etliche Übergänge des Automaten sind unspezifiziert. So darf
es z.B. nicht passieren, daß im Zustand 011S der Befehl zur
Abfahrt gegeben wird, ohne die Tür zu schließen. Um so etwas zu
verhindern, gibt es ja auch den Controller.
Nachdem an dem Aufzug einiges an Intelligenz gespart wurde, benötigt 
der Controller gleich eine doppelte Portion davon. Er muß den Aufzug 
steuern und muß dabei aufpassen, diesen nicht in unspezifizierte 
Zustände zu schicken. Auch sollte der Aufzug weder in den Boden 
gerammt noch aus der Decke herausgeschossen werden. Gleichzeitig muß 
der Controller die Aufträge von außen entgegennehmen und diese 
früher oder später möglichst gerecht bedienen. Und zum 
Schluß war da noch das berühmte Kaugummi auf dem Knopf, d.h. 
auch ein permanentes Knopfdrücken in einem beliebigen Stockwerk darf 
den Aufzug nicht zu sehr von seinem korrekten Tun ablenken.
Es zeigt sich, daß gerade dieses Kaugummi ein größeres 
Problem für die Logik darstellt. Eine einfache Möglichkeit 
wäre es, nicht auf gedrückte Knöpfe zu reagieren sondern auf 
die Taktflanke eines Knopfes. Das führt jedoch zu Fehlverhalten des 
Aufzuges: Sobald das Kaugummi auf dem Knopf klebt, wird es in diesem 
Stockwerk keine Taktflanke mehr geben. Passagiere, die in diesem Stock 
zusteigen wollen, dürfen sich dann wundern, weshalb der Aufzug stets an 
ihnen vorbeifährt.
Auch stellt sich die Frage, wie mit Knopfdrücken im Stockwerk, in dem 
sich der Aufzug gerade befindet, umgegangen wird. Auch hier gibt es die 
zunächst offensichtliche Methode, dieses offensichtlich unsinnige 
Drücken zu ignorieren. Bei näherer Betrachtung ist das 
Drücken jedoch nicht immer sinnlos. Wenn der Aufzug seine Tür 
geschlossen hat, aber noch nicht abgefahren ist, so darf der Knopfdruck 
natürlich nicht vergessen werden. Vielmehr wird von dem Aufzug erwartet, 
daß er seine Tür noch einmal öffnet. Leider kommt hier wieder 
das Kaugummi ins Spiel; falls ein Kaugummi auf dem Knopf klebt, darf das den 
Aufzug natürlich nicht am Abfahren hindern.
Es wird daher der folgende Algorithmus gewählt:
  -  Generell werden alle Knopfdrücke im aktuellen Stockwerk 
       ignoriert, mit den folgenden Ausnahmen:
  
-  Flankenwechsel von '0' auf '1' werden nicht ignoriert
  
-  Wenn der Aufzug sich bereits bewegt, oder zumindest der Befehl 
       zur Abfahrt bereits gegeben wurde, wird der Knopfdruck auch 
       entgegengenommen
  
-  Wenn der Aufzug sowieso nichts zu tun hat, wird der Knopfdruck 
       auch akzeptiert.
Eine kleine Lücke in der ganzen Intelligenz bereitet das Kaugummi noch 
immer. Sobald jetzt ein Knopf ständig gedrückt ist, und keine 
anderen Aufträge anliegen, stellt sich der Aufzug in das jeweilige 
Stockwerk und macht ständig die Tür auf und zu und auf und zu und 
... aber ansonsten würde ja auch niemand den Kaugummi bemerken und 
endlich mal entfernen.
  Tabelle 2 - Ports des Controllers
   | Name | Richtung | Typ | Beschreibung | 
 | inknopf | in | bit_vector | Bedienungsknöpfe innerhalb des Aufzugs | 
 | outknopf | in | bit_vector | Bedienungsknöpfe in den Stockwerken. inknopf und outknopf werden identisch behandelt. | 
 | at | in | bit_vector | Standort des Liftes. Wenn sich der Aufzug noch bewegt, zeigt dieser Port das Stockwerk an, das als nächstes erreicht wird. Nur ein Bit dieses Vektors ist gesetzt. | 
 | door_open | in | bit | Zustand der Aufzugstür: offen (1) oder geschlossen (0) | 
 | moving | in | bit | Bewegungszustand des Aufzugs: fahrend (1) oder stehend (0) | 
 | go_up | out | bit | Befehl an den Aufzug, aufwärts zu fahren | 
 | go_down | out | bit | Befehl an den Aufzug, abwärts zu fahren | 
 | open_door | out | bit | Befehl an den Aufzug, die Tür zu öffnen (1) bzw. die Tür zu schließen oder sie geschlossen zu halten (0) | 
Die Bitfeldmathematik aus dem Smax-Packet hilft dem Controller, die 
Knöpfe zu verwalten und die Befehle zu berechnen. Alle 
Knopfdrücke werden in dem Bitfeld requests verwaltet. 
Wenn ein Bit dieses Feldes '1' ist, so muß dieses Stockwerk bedient
werden. Zusammen mit dem Port at ergeben sich die folgenden 
Berechnungsmöglichkeiten:
  -  requests and at 
-  ist genau dann '1', 
       wenn das aktuelle Stockwerk bedient werden soll. 
       Da at das nächste Stockwerk anzeigt, solange 
       sich der Aufzug bewegt, zeigt dieser Ausdruck, ob 
       der Aufzug im nächsten Stockwerk anhalten 
       soll. Wird dieser Ausdruck '1', so halten wir also den 
       Aufzug an, indem go_up und 
       go_down beide auf '0' gesetzt 
       werden.
  
-  requests and (at - 1) 
-  
       (at-1) ist eine Maske, in der alle 
       Bits unterhalb des aktuellen Stockwerks auf '1' 
       stehen. Wenn dieser Ausdruck ungleich 0 ist, so gibt 
       es Stockwerke unterhalb des aktuellen, die bedient 
       werden müssen.
  
-  requests and not (at - 1) 
-  not 
       (at-1) ist eine Maske, in der alle 
       Bits oberhalb des aktuellen Stockwerks auf '1' 
       stehen. Wenn dieser Ausdruck ungleich 0 ist, so gibt 
       es Stockwerke oberhalb des aktuellen, die bedient 
       werden müssen.
  
-  requests = 0 
-  In diesem Fall hat das System nichts 
       mehr zu tun.
Die Variablen und Signale, die innerhalb des Controllers verwendet werden, 
benötigen wahrscheinlich auch noch einige Erklärung. Das Problem 
des Controllers ist die Asynchronizität, mit der er fertig werden
muß. Auf jede Änderung von außen muß er reagieren,
doch leider halten sich diese äußeren Ereignisse nicht immer
an den Zeitplan. Die internen Variablen existieren einzig zur Koordination.
So ist beispielsweise der Fall denkbar, daß (a) der Controller den
Befehl zur Abfahrt in einem Stockwerk gibt; (b) bevor der Aufzug sich in
Bewegung gesetzt hat, wird der Knopf des aktuellen Stockwerks gedrückt.
Da der Port  moving noch auf 0 steht, denkt der Controller,
daß der Aufzug noch im Stockwerk steht und gibt den Befehl, die
Tür zu öffnen. (c) Die Tür öffnet sich, und mit offener
Tür fährt der Aufzug los. Deshalb muß sich der Controller
selber merken, welche Befehle er schon erteilt hat.
  -  idle 
-  Dieses Signal wird auf '1' gesetzt, falls 
       keine weiteren Stockwerke anzufahren sind. In diesem 
       Fall wird ein permanenter Knopfdruck im aktuellen 
       Stockwerk nicht mehr ignoriert, so daß die 
       Tür im aktuellen Stockwerk wieder 
       geöffnet wird.
  
-  movereq 
-  Wird auf '1' gesetzt, wenn der 
       Befehl zur Abfahrt erteilt wird, und 
       zurückgesetzt, wenn der Aufzug 
       tatsächlich am Fahren ist, um genau den oben 
       beschriebenen Effekt zu vermeiden. Sobald dieser 
       Befehl erteilt wurde, werden auch wieder 
       Knopfdrücke im Stockwerk nicht mehr ignoriert.
  
-  oldreq 
-  speichert den letzten Status der 
       Knöpfe zur Feststellung einer Taktflanke eines 
       beliebigen Knopfes.
  
-  doorstat 
-  Dieser Bitvektor besteht aus 2 
       Bits, in denen eingetragen ist, welcher Befehl an die 
       Tür gegeben wurde. doorstat(0) 
       ist '1', wenn der Befehl "Tür schließen" 
       gegeben aber noch nicht ausgeführt wurde; 
       doorstat(1) ist '1', wenn der Befehl 
       "Tür öffnen" gegeben aber noch nicht 
       ausgeführt wurde. Diese 2 Bits sind 
       wahrscheinlich die obskursten im Controller, doch 
       zumindest das letztere ist durchaus nötig. Es 
       verhindert, daß der Befehl zum Abfahren 
       gegeben wird bevor sich die Aufzugstür 
       ordnungsgemäß geschlossen hat.
  
-  going_up 
-  Diese Variable ist zentral 
       für die Gerechtigkeit des Systems. Falls diese 
       Variable '1' ist, so fährt der Aufzug nach oben, 
       falls '0', so fährt er nach unten. Die Fahrtrichtung 
       wird nur geändert, wenn in der jeweiligen 
       Richtung keine Anforderungen jenseits des aktuellen 
       Stockwerks mehr vorliegen.
Aufzug und Controller sind die zwei Teile des Gesamtsystems, und dieses 
kleine Programmstück ist der Kleber zwischen ihnen. Hier wird die Anzahl 
der zu versorgenden Stockwerke festgelegt; die Ausgänge des Aufzugs 
werden mit den Eingängen des Controllers verkabelt und umgekehrt. Eine 
kleine Aufgabe übernimmt das System allerdings doch: es versorgt den 
Aufzugsautomaten mit einem Taktsignal. Alle 100ns wird das 
progress-Signal invertiert, so daß der Aufzugsautomat
alle 200ns einen Zustandsübergang vollführt.
Die Vektoren inknopf und outknopf werden 
überhaupt nicht verkabelt. Doch von hier aus können sie im 
Simulator mit force-Anweisungen nach Belieben verändert 
werden.
Auch schon bei einer moderaten Gebäudehöhe von 5 
Stockwerken besitzt der Controller bereits 15 Bits an internen Zuständen 
und 3 Ausgabebits, d.h. der komplette Automat hätte 215+3=262144 
Zustände. Der Controller hat in dieser Konfiguration (da Gleichbehandlung 
von inneren und äußeren Knöpfen) 12 Eingangsbits. Auch 
unter der Annahme, daß nur ein kleiner Prozentsatz der Zustände 
erreichbar ist, ergäbe das ganze noch ein eindrucksvolles Wirrwarr,
das ich mir hier erspare.
  -  Die Tür des Aufzuges wird immer geschlossen.
  
-  Ist die Tür geschlossen, und existiert keine Anfrage im 
       aktuellen Stockwerk, und existieren Anfragen in anderen 
       Stockwerken, so wird auf jeden Fall der Befehl zur Abfahrt in 
       Richtung einer anderen Anfrage gegeben (vorletzter elsif-
       Zweig des Controllers).
  
-  Der Aufzug wechselt seine Fahrtrichtung nur, wenn in der 
       jeweiligen Fahrtrichtung keine Anfragen jenseits des aktuellen 
       Stockwerks mehr zu befriedigen sind (ebenfalls im vorletzten 
       elsif-Zweig des Controllers; siehe die Smax-Befehle)
  
-  Wenn in der Gegenrichtung der Aufzugfahrt noch Anfragen 
       vorliegen, so wechselt der Aufzug früher oder später 
       seine Fahrtrichtung, denn im untersten (obersten) Stockwerk 
       können keine weiteren Anfragen unterhalb (oberhalb) des 
       aktuellen Stockwerks vorliegen.
Die obigen Punkte stellen sicher, daß jede Anfrage früher oder 
später erfüllt wird. Eine kleine Ausnahme gibt es allerdings noch: 
Wenn jemand im aktuellen Stockwerk gerade dann auf den Knopf drückt, 
wenn der Aufzug die Tür schließt, so wird die Tür erneut 
geöffnet. Es ist also möglich, den Aufzug durch geschicktes 
Drücken in einem Stockwerk festzuhalten.
In CTL müßte beschrieben werden, daß es global auf allen 
Pfaden unvermeidlich ist, in einen Zustand zu gelangen, in dem 
requests(Stockwerk) gleich '0' ist. Wie an dem Programmtext 
abzulesen ist, wird dieses Bit nur dann von '1' auf '0' zurückgesetzt,
wenn sich der Aufzug in dem Stockwerk befindet und darüber hinaus die 
Tür geöffnet war und nun wieder geschlossen wird, d.h. die 
Anforderung befriedigt wurde. Insgesamt muß es heißen: 
AF(requests(Stockwerk)='0')=1. Die gegebene Bedingung 
muß für alle Zustände des Automaten erfüllt sein, denn 
entweder es liegt keine Anfrage in diesem Stockwerk vor und das Bit ist
sowieso '0', oder wir müssen tatsächlich dorthin fahren. An
dieser Stelle darf nicht gefordert werden, daß in requests
alle Bits zu '0' werden, da natürlich ständig Knöpfe
gedrückt werden können. Allerdings muß der Ausdruck im
Normalfall noch auf die tatsächlich erreichbaren Zustände im
Automaten beschränkt werden. Und im Sinne der obigen Ausnahme muß
auch noch als Fairneßbedingung angegeben werden, daß niemand so
blöd ist und regelmäßig aufs Knöpfchen drückt.
Wie im VHDL-Programm nachgelesen werden kann, können die 
Befehle go_up bzw. go_down nur dann erteilt 
werden, wenn sich der Aufzug nicht bewegt. In Hinsicht auf die 
Bewegungsrichtung können dem Aufzug daher nie widersprüchliche 
Befehle erteilt werden.
In CTL könnte formuliert werden, "von allen Zuständen, in denen 
sich der Aufzug nach oben bewegt, ist es unvermeidlich, daß der Aufzug 
anhält, und bis dorthin wird der Befehl, nach oben zu fahren, 
beibehalten":
  -  go_up -> AU (go_up and not 
       go_down, not (go_up or 
       go_down))
und natürlich symmetrisch
  -  go_down -> AU (go_down and not 
       go_up, not (go_up or 
       go_down))
Auch hier sollte natürlich noch alles auf die erreichbaren 
Zustände eingeschränkt werden.
Frank Pilhofer
<fp -AT- fpx.de>
Back to the Homepage
Last modified: Thu May  4 12:50:19 1995