Institut für Theoretische Informatik Masterarbeit Verifikation von GALS Systemen Henning Günther Matrikelnummer: 2860185 23. Juni 2011 Prüfer: Prof. Dr. Jiří Adámek Betreuer: Dr. Stefan Milius Technische Universität Braunschweig Institut für Theoretische Informatik ITI Eidesstattliche Erklärung Hiermit erkläre ich an Eides statt, dass ich die vorliegende Arbeit selbstständig verfasst und keine anderen als die angegebenen Hilfsmittel verwendet habe. Braunschweig, 2. September 2011 (Henning Günther) iii Zusammenfassung Viele Hard- und Software-Systeme lassen sich als ein System aus synchronen Komponenten auffassen, die asynchron miteinander kommunizieren. Solche GALS-Systeme („globally asynchronous, locally synchronous“) treten in vielen industriellen Anwendungen, wie zum Beispiel in verteilten, sicherheitskritischen Überwachungssystemen, aber auch im Chipentwurf mit mehreren Zeitgebern auf. Die Verifikation dieser Systeme wird momentan dadurch behindert, dass es kaum Werkzeuge gibt, die mit ihnen umgehen können und die resultierenden Modelle zu viele Zustände aufweisen, da noch wenig Techniken bekannt sind, die GALS-Systeme vereinfachen können. Diese Arbeit stellt daher einen Formalismus zur Spezifikation von GALS Systemen vor, der es erlaubt mehrere in dem synchronen Formalismus SCADE implementierte Komponenten zu einem asynchgronen Gesamtsystem zusammen zu fassen. Außerdem wird basierend auf dem SPIN Model-Checker ein Algorithmus zur Verifikation präsentiert und außerdem Möglichkeiten für Optimierungen aufgezeigt. Schlüsselwörter: GALS, verification, SPIN, modeling v Inhaltsverzeichnis Abbildungsverzeichnis xi Tabellenverzeichnis xiii Quelltextverzeichnis xv 1. Einleitung 1.1. Inhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Andere Arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 2 2. GALS-Architekturen 2.1. Formale Definition . . . . . . . . . . . . . . . . . 2.2. Semantik . . . . . . . . . . . . . . . . . . . . . . 2.2.1. Synchrone Ausführung . . . . . . . . . . 2.2.2. Vollständig asynchrones System . . . . . 2.2.3. Asynchrone Ausführung mit Fairness . . 2.2.4. Asynchrone Ausführung mit Schranken 2.3. Kontrakte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 8 11 11 12 13 13 14 3. Grundlagen 3.1. SPIN . . . . . . . . . . . . . . . . . . . . . . 3.2. SCADE . . . . . . . . . . . . . . . . . . . . 3.3. LTL – Linear temporal logic . . . . . . . . 3.4. Büchi-Automaten . . . . . . . . . . . . . . . 3.4.1. Verallgemeinerter Büchi-Automat . 3.5. LTL Übersetzung . . . . . . . . . . . . . . . 3.5.1. Übersetzung des Existenzquantors 3.6. Binäre Entscheidungsdiagramme . . . . . . 3.7. Datentypen als Entscheidungsdiagramme . 3.7.1. Integer . . . . . . . . . . . . . . . . 3.7.2. Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 17 18 19 20 21 23 25 28 29 32 4. Lösungsansatz 4.1. Offene Fragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 34 5. GTL – GALS Translation Language 5.1. Grammatik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 36 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii Inhaltsverzeichnis 5.2. Formeln . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3. Operationelle Semantik . . . . . . . . . . . . . . . . . 5.3.1. Variablen . . . . . . . . . . . . . . . . . . . . 5.3.2. Aufbau . . . . . . . . . . . . . . . . . . . . . . 5.3.3. Ableitungsregeln . . . . . . . . . . . . . . . . 5.3.4. Interpretation als GALS-System . . . . . . . . 5.3.5. Interpretation der Kontrakte als GALS-System . . . . . . . 39 39 40 40 41 44 45 . . . . . . . . . . 47 47 49 53 56 60 60 60 60 62 63 7. Implementierung 7.1. Backend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2. Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3. Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 66 68 69 8. Fallbeispiele 8.1. Quelle-Senke Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2. Mutex Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 71 72 9. Ausblick 77 Literaturverzeichnis 79 A. Installation A.1. Voraussetzungen . A.2. Quelltext beziehen A.3. Kompilieren . . . A.4. Verwendung . . . . . . . 83 83 84 84 84 . . . . . 87 87 89 90 92 93 6. Übersetzung 6.1. Übersetzungskonstruktion . . . . . . . . . . . 6.1.1. Korrektheit der Übersetzung . . . . . 6.2. Promela-C-Integration . . . . . . . . . . . . . 6.3. Native Promela Übersetzung . . . . . . . . . 6.4. SCADE Übersetzung . . . . . . . . . . . . . . 6.4.1. Korrektheit . . . . . . . . . . . . . . 6.5. Optimierungen . . . . . . . . . . . . . . . . . 6.5.1. Abstraktion durch statische BDD . . 6.5.2. Abstraktion durch dynamische BDD 6.6. Fehlereingrenzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B. Quelltextdokumentation B.1. Language.GTL.Backend . . . B.2. Language.GTL.Backend.All . . B.3. Language.GTL.Backend.Scade B.4. Language.GTL.ErrorRefiner . B.5. Language.GTL.Expression . . viii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inhaltsverzeichnis B.6. Language.GTL.LTL . . . . . . . . . . B.6.1. Formulas . . . . . . . . . . B.6.2. Buchi translation . . . . . . B.7. Language.GTL.Model . . . . . . . . B.8. Language.GTL.Parser . . . . . . . . . B.9. Language.GTL.Parser.Lexer . . . . . B.10. Language.GTL.Parser.Syntax . . . . . B.11. Language.GTL.Parser.Token . . . . . B.12. Language.GTL.PrettyPrinter . . . . . B.13. Language.GTL.PromelaCIntegration . B.14. Language.GTL.PromelaDynamicBDD B.15. Language.GTL.Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 98 99 100 102 102 102 105 107 108 109 113 ix Abbildungsverzeichnis 2.1. 2.2. 2.3. 2.4. 2.5. 2.6. 2.7. 2.8. Mealy-Automat . . . . . . . . . . . . . . . . . . . . . . . . Mealy-Automat mit Bedingungen . . . . . . . . . . . . . . Aus Mealy-Automaten zusammen gesetztes GALS-System Transitionssystem des GALS Systems aus 2.3 . . . . . . . Synchrone Ausführung . . . . . . . . . . . . . . . . . . . Asynchrone Ausführung . . . . . . . . . . . . . . . . . . . Mögliche Ausführungspfade eines asynchronen Systems . Verhalten und Kontrakte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. 3.7. 3.8. 3.9. 3.10. 3.11. Die Expand-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übersetzung von Geschichtsvariablen . . . . . . . . . . . . . . . . . . . . . Die einfachsten zwei Entscheidungsdiagramme . . . . . . . . . . . . . . . . Zusammengesetztes Entscheidungsdiagramm . . . . . . . . . . . . . . . . . Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ . . . . . . . . . . . . . Äquivalentes Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ . . . . . . Reduziertes Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ . . . . . . . Äquivalentes reduziertes Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ Eine Funktion, um endliche Mengen in BDDs umzuwandeln . . . . . . . . Algorithmus um BDDs in Mengen umzuwandeln . . . . . . . . . . . . . . . Entscheidungsdiagramm der Menge {2, 3, 5} . . . . . . . . . . . . . . . . . 5 6 7 7 12 12 13 15 22 24 25 26 26 27 27 28 29 29 30 4.1. GTL Nomenklatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Beispielkomponente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. SCADE-Wrapper für Beispielkomponente . . . . . . . . . . . . . . . . . . . 33 34 34 6.1. Simulation durch C-Integration von Promela . . . . . . . . . . . . . . . . . 6.2. Fehlereingrenzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 64 7.1. 7.2. 7.3. 7.4. GTL Implementierung . . . . . . . . . . . . . . UML Diagramm der Backend-Implementierung UML-Diagramm der Modell-Implementierung . UML-Diagramm für Ausdrücke . . . . . . . . . . . . . 65 67 68 69 8.1. Quelle-Senke Kontrakt-Automaten . . . . . . . . . . . . . . . . . . . . . . . 72 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi Tabellenverzeichnis 3.1. Hilfsfunktionen für den LTL Übersetzungsalgorithmus . . . . . . . . . . . . 23 8.1. Quelle-Senke Verifikationsergebnisse . . . . . . . . . . . . . . . . . . . . . . 8.2. Mutex Verifikationsergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . 72 76 xiii Quelltextverzeichnis 6.1. 6.2. 6.3. 6.4. 6.5. 6.6. Komponenten-Übersetzung als Promela-Prozess Verifikationsziel-Übersetzung als never-Prozess . Initialisierungsprozess . . . . . . . . . . . . . . . C-Integration-Beispiel . . . . . . . . . . . . . . . Berechnung der unteren Schranke . . . . . . . . Generierung von möglichen Werten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 48 49 55 58 58 8.1. 8.2. 8.3. 8.4. 8.5. 8.6. Quelle-Senke Beispiel . Mutex Client . . . . . . Mutex Server . . . . . . Mutex Instanzen . . . . Mutex Verbindungen . Mutex Verifikationsziel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 73 74 75 75 75 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv 1. Einleitung Trotz vieler Fortschritte auf dem Gebiet der formalen Verifikation bleibt das Problem der Zustandsexplosion weiterhin der hauptsächliche Hinderungsgrund für die Verwendung von formalen Verifikationstechniken in der Industrie [CGJ+ 01]. Gleichzeitig werden sicherheitskritische Systeme, von deren korrekter Funktion im schlimmsten Fall tausende Menschenleben abhängen immer komplexer und schwieriger zu durchschauen. Diese Arbeit beschäftigt sich mit einem Teilgebiet der formalen Verifikation, so genannten GALSSystemen. Ein GALS1 System besteht aus vielen synchronen, also deterministisch und getakteten Komponenten. Diese können innerhalb des GALS-Systems nun asynchron miteinander kommunizieren, was bedeutet, dass der Nachrichtenaustausch zwischen einzelnen Komponenten verzögert sein kann und Komponenten mit verschiedenen Taktraten laufen können. Beispiele für GALS-Systeme sind: • Computernetzwerke: Die Kommunikation der Rechner miteinander ist mit nicht genau vorhersehbaren Verzögerungen verbunden. Jeder Rechner besitzt außerdem seinen eigenen Prozessor und damit seinen eigenen Takt. • System-on-a-chip Designs, die mehrere Zeitgeber für verschiedene Komponenten verwenden. • Sicherheitskritische physische Systeme, die über größere Gebiete verteilt sind (Eisenbahnübergänge, Ampelschaltungen, Kontrollsysteme für Kraftwerke, etc.) bestehen meistens aus mehreren unabhängigen Komponenten. Die Verifikation von GALS-Systemen steht vor zwei Problemen: • Zum einen fehlen Werkzeuge, die sowohl synchrone wie auch asynchrone Systeme unterstützen. Formalismen, die für synchrone Systeme entworfen sind, haben in der Regel keine Möglichkeit, Asynchronität oder Nichtdeterminismus zu modellieren. Asynchrone Formalismen bieten zwar meist Unterstützung für synchrone Systeme, allerdings in den meisten Fällen nicht sehr elegant oder performant. • Zum anderen können GALS-Systeme beträchtliche Größen annehmen und damit ohne die manuelle Einführung von Vereinfachungen und Abstraktionen nur noch schwer in absehbarer Zeit zu verifizieren sein. Das manuelle Einführen von Abstraktionen ist aber sehr schwierig und kann bei ungenauen Abstraktionen zu schwer 1 englisch für „globally asynchronous, locally synchronous“, also „Global asynchron, lokal synchron“ 1 1. Einleitung auffindbaren Fehlern in der Verifikation führen. Automatisch zu überprüfen, ob eine gegebene Abstraktion korrekt ist, ist in keinem aus der Literatur bekannten Formalismus vorgesehen. Diese Arbeit versucht beide Probleme durch die Einführung eines neuen Formalismus zu lösen, der die Vorteile von synchronen und asynchronen Formalismen vereint und zusätzlich die Möglichkeit bietet, sichere Abstraktionen durch Kontrakte einzuführen. 1.1. Inhalt Zunächst werden in Kapitel 1.2 ähnliche Ansätze zur Lösung der angesprochenen Probleme präsentiert. Im nächsten Kapitel (Kapitel 2) werden dann GALS-Architekturen formal definiert und verschiedene Ausführungssemantiken eingeführt. Kapitel 3 beschäftigt sich mit Grundlagen, die für das Verständnis der nachfolgenden Kapitel unabdingbar sind. Daraufhin wird in Kapitel 4 der allgemeine Lösungsansatz für die Verifikation von GALSSystemen erklärt. Im Kapitel 5 beginnt dann die eigentliche Leistung dieser Arbeit: Die GTL2 -Sprache wird definiert und mit einer Semantik versehen. Daraufhin wird in Kapitel 6 beschrieben, wie sich die vorher definierte Sprache in den Formalismus verschiedener Verifikationssprachen übersetzen lässt. Es werden außerdem Möglichkeiten zur Optimierung vorgestellt und ein Verfahren zur Erkennung von fehlerhaften Spezifikationen erläutert. Die Implementierung der beschriebenen Sprache und ihrer Transformationen wird dann in Kapitel 7 behandelt. Für den Praktiker wird die Arbeit in Kapitel 8 interessant: Hier wird die beschriebene Implementierung anhand von einem kleinen und einem großen Beispiel getestet und die Resultate bewertet. Schließlich liefert Kapitel 9 eine Zusammenfassung der gesamten Arbeit und versucht, die Arbeit in Perspektive zu setzen sowie einen Ausblick auf zukünftige Entwicklungen zu geben. 1.2. Andere Arbeiten Die Idee, GALS-Systeme durch eine Kombination von synchronen und asynchronen Sprachen zu verifizieren, wird in verschiedenen Arbeiten behandelt: 1. Damien Thivolle und Hubert Garavel erklären die grundsätzliche Herangehensweise und zeigen anhand der Verifikation eines Kommunikationsprotokolls die Vorteile dieses Ansatzes [GT09]. Es wird erklärt, wie sich synchrone Komponenten als Funktionen ansehen lassen und wie sie sich in ein asynchrones Verifikationstool einbinden lassen. Im Gegensatz zu dieser Arbeit wird aber kein neuer Formalismus für die Spezifikation von GALS-Systemen eingeführt. Auch Techniken zur Optimierung, wie sie in dieser Arbeit behandelt werden, fehlen. 2 2 GTL steht für „gals translation language“ also eine Sprache um GALS Systeme in andere Formalismen zu übersetzen. 1.2. Andere Arbeiten 2. Ein Artikel von Doucet et al. beschreibt die Übersetzung des synchronen Formalismus SIGNAL nach Promela [DMK+ 06]. Die Verifikation wird dann vollständig von SPIN übernommen und nicht wie in dieser Arbeit aufgeteilt in einen synchronen und asynchronen Teil. 3. „Multiclock Esterel“ erweitert die Beschreibungssprache von SCADE um die Möglichkeit, mehr als einen Takt für verschiedene Komponenten anzugeben [RS00]. Der Formalismus bleibt allerdings vollständig deterministisch und jede Komponente lässt sich in eine äquivalente SCADE Komponente übersetzen. 4. Der Formalismus „Communicating Reactive State Machines“ wird in [RSD+ 04] verwendet, um GALS Systeme zu modelieren und zu verifizieren. 5. Das „IF Toolset“ führt einen Formalismus zur Spezifikation von asynchronen Systemen ein, der wie der hier vorgestellte Formalismus auf der Komposition von Komponenten durch Verbindungen basiert [BGO+ 04]. Der Schwerpunkt der Arbeit liegt in der Bereitstellung eines Formalismus, der mächtig genug ist, um die vielen verschiedenen Design-Formalismen zu vereinen. 3 2. GALS-Architekturen Ein GALS System – GALS steht für „Globally asynchronous, locally synchronous“ – besteht aus mehreren synchronen Komponenten, die asynchron miteinander kommunizieren. Die synchronen Komponenten lassen sich dabei als Mealy-Automaten auffassen3 . In einem Mealy-Automaten hängt die Ausgabe sowohl von der Eingabe als auch dem aktuellen Zustand ab [Mea55]. Mealy-Automaten sind so allgemein formuliert, dass sich jedes in einem synchronen Formalismus formulierte Modell in einen bisimularen Mealy-Automaten transformieren lässt. Ein Beispiel für einen Mealy-Automaten ist in Abbildung 2.1 dargestellt. Transitionen sind hier in der Form i/o angegeben, wobei i die Eingabe und o die Ausgabe bezeichnen. a/b q0 q1 a/c b/a a/b b/a q2 b/c Abbildung 2.1.: Mealy-Automat Ein Schritt im Automaten bedeutet, dass das aktuelle Eingabe-Symbol gelesen wird und die Transition gewählt wird, die vom aktuellen Zustand ausgeht und mit dem aktuellen Eingabe-Symbol übereinstimmt. Das Ziel der Transition ist der neue aktuelle Zustand des Automaten und das Ausgabe-Symbol der Transition wird zur Ausgabe hinzugefügt. Der Beispiel-Automat in Abbildung 2.1 hat also beispielsweise die folgende Ablauf-Sequenz: q0 a / q1 a / q0 a / q1 b / q2 / ... Der Automat transformiert damit die Eingabe „aaab . . . “ in die Ausgabe „bcba . . . “. Der Beispiel-Automat verwendet als Ein- und Ausgabe-Symbole Zeichen und stellt damit einen 3 Mächtigere Formalismen wie beispielsweise Turing-Maschinen sind ungeeignet, da sie einen unendlichen Zustandsraum aufweisen können und daher mit aktuellen Model-Checkern nicht überprüft werden können. 5 2. GALS-Architekturen Aus- und Eingabekanal zur Verfügung. Benutzt man stattdessen n-Tupel als Eingabe und m-Tupel als Ausgaben, erhält man einen Automaten, der n Eingabe- und m AusgabeKanäle bietet. In jedem Schritt wird dann aus jedem Eingabekanal ein Zeichen gelesen und in jeden Ausgabekanal eins geschrieben. Für die vollständige Beschreibung eines Mealy-Automaten mit Kanälen ist die Repräsentation durch Tupel ausreichend, für eine übersichtliche Notation und Darstellung jedoch nicht geeignet, da die Anzahl der aufzuschreibenden Transitionen exponentiell mit der Anzahl der Eingabe-Kanäle wächst4 . Daher ist es sinnvoll, für die Spezifikation von MealyAutomaten die Tupel-Komponenten mit Variablen zu benennen und Bedingungen (engl. conditions) über diese Variablen an die Transitionen zu schreiben. Eine Bedingung ist hierbei eine boolesche Formel, die nur die Variablen der Tupelkomponenten enthält. Ist die Formel für eine Belegung der Variablen wahr, so gilt die Transition auch für das entsprechende Tupel. Benennt man beispielsweise die Komponenten der Tupel N × B × B mit α, β und γ, so beschreibt die Bedingung α = 3 ∧ γ = 0 die Tupel (3, 0, 0) und (3, 1, 0). Eine Transition α = 3 ∧ γ = 0/(1, 2) beschreibt also eigentlich die Transitionen (3, 0, 0)/(1, 2) und (3, 1, 0)/(1, 2). Diese Art der Notation ist hilfreich, wenn für eine Transition die Werte von bestimmten Kanälen keine Rolle spielen. Abbildung 2.2 zeigt einen Beispiel-Automaten, der Tupel der Form B × N als Eingaben verwendet. ¬on ∨ x ≥ 5/3 on ∧ x < 5/1 q0 q1 x < 5/2 x ≥ 5/0 Abbildung 2.2.: Mealy-Automat mit Bedingungen Um ein GALS-System zu erstellen, kann man nun die Ausgabekanäle von Automaten mit den Eingabekanälen von anderen Automaten verbinden und so ein Netzwerk aus Mealy-Automaten erhalten. Ist eine Ausgabe eines Automaten A mit einer Eingabe eines Automaten B verbunden, so verwendet B als Eingabe in jedem Schritt die letzte von A produzierte Ausgabe. Abbildung 2.3 zeigt ein solches System. Das Gesamtsystem hat sämtliche unverbundenen Aus- und Eingaben als Aus- und Eingaben. Ein Schritt des Gesamtsystems wird nun durch einen Schritt eines beliebigen Mealy-Automaten realisiert. Durch diese Eigenschaft wird das GALS-System nicht-deterministisch und asynchron. Ein Problem ergibt sich nun, wenn die Quelle einer Verbindung noch keinen Schritt durchgeführt hat: in diesem Fall gibt es 4 6 Hat man n Eingabe-Kanäle, die jeweils m verschiedene Zeichen enthalten können, so benötigt jeder Zustand mn ausgehende Transitionen, da der Automat für jede Eingabe eine gültige Transition aufweisen sollte. keinen letzten Wert für die Verbindung und die Eingabe des Ziel-Automaten ist undefiniert. Dieser Fall kann durch die Definition von Default-Werten oder durch das Verbot, Schritte mit Automaten durchzuführen, deren Eingaben (teilweise) undefiniert sind gelöst werden. In dieser Arbeit wird das Problem durch die Einführung von impliziten oder expliziten Default-Werten gelöst. /0 p0 P1 p1 /1 on out q0 on ∧ x < 5/1 P2 q1 x ≥ 5/0 x ¬on ∨ x ≥ 5/3 x < 5/2 res Abbildung 2.3.: Aus Mealy-Automaten zusammen gesetztes GALS-System Das in Abbildung 2.3 gezeigte System hat also nur die Eingabe x und die Ausgabe res. Der Gesamtzustand des Systems ist nun durch den Zustand der Verbindungen und den aktuellen Zustand der Prozesse definiert. Der Startzustand des abgebildeten Systems kann also beispielsweise durch (p0 , q0 , ⊥) dargestellt werden, wobei ⊥ angibt, dass die Verbindung zwischen out und on einen undefinierten Wert besitzt. Eine Transition wird dargestellt durch ein Tupel, dass die Ein- und Ausgaben des Systems sowie den Mealy-Automaten, der den aktuellen Schritt durchführt, enthält. Die Transition (x = 4, P1 )/3 gibt also beispielsweise an, dass die Eingabe x den Wert 4, die Ausgabe res den Wert 3 besitzt und der Automat P1 einen Schritt durchführt hat. Das resultierende Transitionssystem für das GALS-System aus Abbildung 2.3 ist beispielsweise in Abbildung 2.4 dargestellt. P2 /3 x < 5, P2 /2 x ≥ 5, P2 /0 p0 , q0 , ⊥ P1 /⊥ P1 /⊥ p1 , q0 , 0 p1 , q1 , 0 P1 /⊥ P1 /⊥ x < 5, P2 /1 p0 , q0 , 1 p0 , q1 , 1 P1 /⊥ x < 5, P2 /2 x ≥ 5, P2 /0 Abbildung 2.4.: Transitionssystem des GALS Systems aus 2.3 Möchte man verhindern, dass die Ausgaben des Gesamtsystems häufig einen undefinierten 7 2. GALS-Architekturen Wert (⊥) annehmen, so kann man beispielsweise definieren, dass eine undefinierte Ausgabe bedeutet, dass der letzte Wert der Ausgabe erhalten bleibt. Um das zu erreichen nimmt man die Ausgaben des Systems mit in den Zustand des Gesamtsystems. Dies vergrößert natürlich den Zustandsraum des resultierenden Systems, aber reduziert undefinierte Ausgaben des Gesamtsystems. 2.1. Formale Definition Notation 1. Die Projektion, die für eine gegebene Indexmenge I die entsprechenden Elemente aus einem Tupel X auswählt, ohne die Reihenfolge zu verändern wird mit X|I abgekürzt. Es gilt also beispielsweise: (a, b, c, d)|{0,2} = (a, c) Eine synchrone Komponente mit n Eingängen und m Ausgängen lässt sich als ein modifizierter Mealy-Automat A = (Q, Σ, Ω, δ, q0 ) darstellen: • Q ist eine (endliche) Zustandsmenge. • Σ = Σ0 × · · · × Σn ist die Menge der Eingabesymbole. Da der Automat n Eingänge besitzt, ist die Eingabe ein Tupel aus n Symbolen. • Ω = Ω0 × · · · × Ωm ist die Menge der Ausgabesymbole. • δ : Q × Σ → Q × Ω ist die Übergangsfunktion, die einen Zustand und eine Eingabe auf einen neuen Zustand und eine Ausgabe abbildet. Da es sich um eine Funktion und keine Relation handelt, ist der Automat deterministisch. • q0 ∈ Q ist der Startzustand des Automaten. Definition 2. Ein GALS-System ist ein Tripel G = (A, p, C) mit A als einer Menge von Automatennamen, p als eine Funktion, die Automatennamen einen konkreten Mealy-Automaten zuordnet und C ⊆ (A × N) × (A × N) das die Verbindungen zwischen Ein- und Ausgaben der Automaten definiert. Ein Tupel (a, n, b, m) ∈ C bezeichnet also eine Verbindung der n-ten Ausgabe des MealyAutomaten p(a) mit der m-ten Eingabe des Mealy-Automaten p(b). Um die Referenzierung von Automatenkomponenten zu ermöglichen, benutzen wir folgende Konvention: p(a) = (Qa , Σa , Ωa , δ a , q0a ) 8 2.1. Formale Definition Eine Aus- oder Eingabe ist spezifiziert durch den Automaten und den Index im Einoder Ausgabetupel. Für eine Verbindung ((a, i), (b, j)) ∈ C muss immer gelten, dass die Ausgabesymbole den Eingabesymbolen entsprechen, also Ωai = Σbj gilt. Notation 3. Es kann ohne Beschränkung der Allgemeinheit davon ausgegangen werden, dass eine Ordnung auf den Automatennamen existiert, die bei der Erstellung von Eingabe- und Ausgabetupeln eingehalten wird. Dann wird die Projektion, die aus einem solchen Tupel X alle Elemente selektiert, die mit dem Automatennamen a indiziert sind, mit X|a notiert. Es gilt dann also beispielsweise: s ∈ Σa0 × Σa2 × Σb1 × Σb2 s = (a, b, c, d) s|b = (c, d) Die Ein- und Ausgaben des GALS Systems ergeben sich aus den Automaten des Systems sowie den Verbindungen. Ist eine Eingabe eines Automaten mit keiner Ausgabe verbunden, so ist sie automatisch eine Eingabe des Gesamtsystems. Die Eingaben I(G) des Gesamtsystems sind also Y I(G) = {Σan | a ∈ A, n ∈ N, ¬∃((X, i), (Y, j)) ∈ C : Y = a ∧ j = n} Für die Ausgaben O(G) gilt entsprechend Y O(G) = {Ωan | a ∈ A, n ∈ N, ¬∃((X, i), (Y, j)) ∈ C : X = a ∧ i = n} Der Zustandsraum S(G) des Systems ergibt sich dann aus den Zuständen der einzelnen Automaten (SQ (G)) sowie dem aktuellen Inhalt der Verbindungen (SC (G)): Y SQ (G) = Qa a∈A SC (G) = Y Σai ((a,i),_)∈C S(G) = SQ (G) × SC (G) Da die Eingaben für einen konkreten Automaten sich nun entweder im Zustandsraum S(G) oder in den globalen Eingaben I(G) befinden können, definiert man eine Familie von Hilfsfunktionen πIa ∈ S(G) × I(G) → Qa × Σa πIa ((q, s), i) = ((q|a , s|a ), i|a ) 9 2. GALS-Architekturen für a ∈ A, die die benötigten Parameter für den gegebenen Automaten a extrahieren. Notation 4. Um alle zum Automaten a gehörigen Komponenten eines Tupels X unter berücksichtigung der Reihenfolge durch alle Komponenten aus dem Tupel Y zu ersetzen, wird die folgende Notation verwendet: X[a 7→ Y ] Es gilt: X[a 7→ Y ]|a = Y und ∀b ∈ A, b 6= a : X[a 7→ Y ]|b = X|b Analog definiert man eine weitere Familie von Funktionen a πO ∈ S(G) × Qa × Ωa → S(G) × O(G) a πO ((q, s), q 0 , o) = ((q[a 7→ q 0 ], s[a 7→ o]), (⊥, . . . , ⊥)[a 7→ o]) die Ausgaben und Zustand eines Automaten zurück in den Zustandsvektor des Gesamtsystems sowie die Gesamtausgaben schreibt. Um die Notation zu erleichtern, wird zusätzlich noch eine Zustandsübergangsfunktion λ definiert, die den Zustandsübergang des GALS Systems bei Ausführung eines Automatenschritts angibt. λ ∈ S(G) × I(G) × A → S(G) × O(G) a λ(q, i, a) = πO (q, δ a (πIa (q, i))) Der Initialzustand α(G) ∈ S(G) ergibt sich als Kombination aller Initialzustände der Komponenten und den undefinierten Verbindungen α(G) = (hq0a ia∈A , (⊥, . . . , ⊥)) Das System in Abbildung 2.3 wird formal beispielsweise so dargestellt: G = ({a1 , a2 }, p, {(a1 , 0), (a2 , 0)}) Wobei die Funktion p definiert ist als: p : a1 → 7 ({p0 , p1 }, ∅, B, δ 1 , p0 ) a2 → 7 ({q0 , q1 }, B × N, N, δ 2 , q0 ) 10 2.2. Semantik Wobei δ 1 und δ 2 wie folgt definiert sind: δ 1 : p0 p1 2 δ : (q0 , ¬on ∨ x ≥ 5) (q0 , on ∧ x < 5) (q1 , x < 5) (q1 , x ≥ 5) 7→ 7→ 7→ 7→ 7→ 7→ (p1 , 0) (p0 , 1) (q0 , 3) (q1 , 1) (q1 , 2) (q0 , 0) Daraus ergeben sich die abgeleitetenden Eingaben, Ausgaben und Zustandsmengen zu: I(G) = N O(G) = N S(G) = {p0 , p1 } × {q0 , q1 } × B 2.2. Semantik Man kann nun das maximale Transitionssystem des GALS-Systems wie folgt über die Transitionsrelation T definieren: s i/o / s0 T ⇔ ∃a ∈ A : λ(s, i, a) = (s0 , o) Je nach dem, was man für Systeme betrachtet ist dieses Transitionssystem aber nicht unbedingt realistisch: Es schließt beispielsweise nicht aus, dass immer nur Schritte von einer Komponente durchgeführt werden, während alle anderen Komponenten nie einen Schritt machen (sie „verhungern“ sozusagen). Viele Formeln, die bei einer realistischen Ausführung des Systems erfüllt sind können so Fehler produzieren und eine formale Verifikation unmöglich machen. Daher werden in diesem Abschnitt verschiedene Ausführungsarten mit ihren Vor- und Nachteilen vorgestellt. Dazu wird zunächst informell die Ausführungsart beschrieben und danach formal die Herleitung des entsprechenden Transitionssystems erklärt. 2.2.1. Synchrone Ausführung Bei der synchronen Ausführung führen alle Systeme gleichzeitig ihren Berechnungsschritt aus. Da eine echte Gleichzeitigkeit aber von den in dieser Arbeit verwendeten Verifikationsformalismen nicht unterstützt wird, muss sie dadurch angenähert werden, dass die Komponenten zwar nacheinander ihre Schritte ausführen, aber dies immer in der gleichen Reihenfolge tun. 11 2. GALS-Architekturen ... P1 P2 P3 P 1 P2 P3 Abbildung 2.5.: Synchrone Ausführung In Abbildung 2.5 wird eine mögliche Ausführung der drei Prozesse P1 , P2 und P3 gezeigt. Andere Ausführungsmöglichkeiten unterscheiden sich nur durch die Reihenfolge, in der die Prozesse ihren Berechnungsschritt ausführen. Für ein System mit n Prozessen gibt es also n! Möglichkeiten der Ausführung. Vorteile dieser Architektur sind eine extrem einfache Implementierung, sowie wenige Ausführungsreihenfolgen, die bei der Verifikation in Betracht gezogen werden müssen. Das zu verifizierende Zustandsmodell des Systems hat also sehr viel weniger Zustände als die der anderen Architekturen. Der Nachteil ist jedoch, dass es für viele Szenarien sehr unrealistisch ist, perfekte Synchronität zu fordern. In Kommunikationsnetzen bedeuten schon minimale Verzögerungen bei der Zustellung von Nachrichten, dass Prozesse nicht mehr echt synchron laufen, selbst wenn ihre Uhren genau gleich gehen. Formal lässt sich das Transitionssystem für die synchrone Ausführung herleiten, indem man den Automaten, der als nächstes ausgeführt werden soll in den Zustand übernimmt. Sei ohne Beschränkung der Allgemeinheit A = {P0 , P1 , . . . , Pn−1 }. Der Zustandsvektor des Gesamtsystems ist dann (a, s), wobei a ∈ A ein Automatenname und s ∈ S(G) der Zustand des Systems ist. Die Zustandsübergangsrelation ergibt sich dann wie folgt: (Pj , s) li /lo / (Pk , s0 ) ⇔ k = (j + 1) mod n ∧ λ(s, li , a) = (s0 , lo ) 2.2.2. Vollständig asynchrones System Im Gegensatz zur synchronen Architektur steht die vollständig asynchrone: Hier kann zu jedem Zeitpunkt jeder Prozess unabhängig vom Fortschritt der anderen einen Berechnungsschritt ausführen. Eine mögliche asynchrone Ausführung von drei Prozessen ist in Abbildung 2.6 gezeigt. ... P2 P2 P2 P3 P 3 P3 P1 P3 Abbildung 2.6.: Asynchrone Ausführung Eine asynchrone Architektur löst das Problem der synchronen Architektur, indem sie sämtliche theoretisch mögliche Verschachtelungen der Ausführungen der Prozesse bei der Ve- 12 2.2. Semantik rifikation berücksichtigt. Dies führt aber zu zwei neuen Problemen: Zum einen nimmt die Zustandsgröße des Systems eventuell gewaltig zu; n Prozesse haben nach m Ausführungsschritten bereits nm mögliche Ausführungen. Zwar führen meist viele unterschiedliche Verschachtelungen zu den selben Zuständen, in diesem Fall kann die Technik der „partial order reduction“ [God95] verwendet werden, aber im schlimmsten Fall führt eine vollständig asynchrone Architektur zu einer gewaltigen Zustandsexplosion. Das zweite Problem ist, dass diese Architektur auch extrem unrealistische Ausführungen in Erwägung zieht: Ein Prozess kann zum Beispiel immer rechnen, während ein anderer nie zum Zuge kommt. In der Verifikation können so Fehlerzustände erkannt werden, die in der Realität nie vorkommen. start P1 P1 P2 P3 P2 P1 P2 P3 P3 P1 P2 P3 Abbildung 2.7.: Mögliche Ausführungspfade eines asynchronen Systems 2.2.3. Asynchrone Ausführung mit Fairness Um das Problem zu umgehen, dass einzelne Prozesse „verhungern“, also nie einen Rechenschritt ausführen dürfen, kann man so genannte Fairness-Kriterien definieren: Diese besagen, dass nur Ausführungen für die Verifikation in Betracht gezogen werden, in denen jeder Prozess immer mal wieder an die Reihe kommt. Viele Verifikationsformalismen unterstützen die Modellierung von Fairness durch die Definition von Zuständen, die immer mal wieder erreicht werden müssen, damit die Ausführung in Betracht gezogen wird. Da Fairness-Eigenschaften aber etwas umständlich zu formulieren sind und für den Rest der Arbeit keine Bedeutung haben, wird das resultierende Transitionssystem hier nicht explizit angegeben. Mehr zum Thema Fairness kann in [KPRS06] nachgelesen werden. 2.2.4. Asynchrone Ausführung mit Schranken Obwohl das Hinzufügen von Fairness-Eigenschaften die Fälle entfernt, in denen ein Prozess niemals zur Ausführung kommt, so werden immer noch extrem unrealistische Szenarien betrachtet: In der Realität wird es beispielsweise nie vorkommen, dass ein Prozess nur ein mal einen Berechnungsschritt durchführt, während ein anderer im gleichen Zeitraum 1000 ausführt. Wesentlich realistischere Ausführungen erreicht man, wenn einzelnen Prozessen 13 2. GALS-Architekturen nur für einen gewissen Zeitraum erlaubt, mehr oder weniger Schritte als die anderen auszuführen. Hierzu zählt man die Berechnungsschritte, die jeder Prozess bereits ausgeführt hat und überprüft, dass zu jedem Zeitpunkt der Verifikation die Bedingung „Keine zwei Prozesse liegen um mehr als x Berechnungsschritte von einander entfernt“ erfüllt ist. Genau wie Fairness-Eigenschaften ist diese Eigenschaft jedoch auch formal unhandlich zu definieren, nicht relevant für den Rest der Arbeit und daher weg gelassen. 2.3. Kontrakte Eine Komponente in einem GALS-System besitzt durch ihre Spezifikation als MealyAutomat eine Menge von Verhaltensweisen. Jede Verhaltensweise ist eine Kombination aus Eingaben und Ausgaben. Betrachtet man den Raum aller Verhaltensweisen, also aller Kombinationen von Eingaben und Ausgaben, so nimmt jede Komponente einen Teilraum ein. Formuliert man nun mithilfe von LTL-Formeln Bedingungen an das Gesamtsystem aus Komponenten, so spezifiziert man für jede Komponente einen neuen Raum, nämlich den des erlaubten Verhaltens. Erfüllt das Gesamtsystem die LTL-Formeln, so ist das Verhalten jeder Komponente ein Teilraum des erlaubten Verhaltens. Ein Problem in der formalen Verifikation ist, dass der Automat, der eine Komponente repräsentiert, sehr komplex seien kann. Die Verifikation benötigt dann sehr viel Speicher, um jeden Zustand der Kompontente zu erfassen. Es ist aber häufig möglich, einen ähnlichen Automaten zu finden, der wesentlich kleiner ist und trotzdem jedes Verhalten zeigt, dass die ursprüngliche Komponente besaß. Dieser Automat kann auch mehr Verhalten besitzen, vorausgesetzt, dieses Verhalten liegt immer noch innerhalb des erlaubten Verhaltens. Ein solcher Automat heißt Kontrakt und lässt sich beispielsweise als LTL-Formel darstellen. Der erläuterte Zusammenhang zwischen Verhalten, erlaubtem Verhalten und Kontrakten ist in Abbildung 2.8 illustriert. Ist ein vereinfachender Kontrakt-Automat gefunden, so muss die formale Verifikation zunächst beweisen, dass der Kontrakt von der ursprünglichen Komponente eingehalten wird. Dies kann beispielsweise festgestellt werden, indem der Kontrakt in den Formalismus der Komponente übersetzt wird und dort verifiziert wird. Ist gesichert, dass alle Kontrakte von den Komponenten erfüllt werden, so werden die Kontrakte verwendet, um das Gesamtsystem zu verifizieren. Sind die Kontrakte allerdings zu locker formuliert, spezifizieren also mehr Verhalten als die zu verifizierende Formel erlaubt, so werden bei der Verifikation Fehler gefunden, die bei einer normalen Verifikation ohne Kontrakte nicht auftreten würden. Eine Lösung für dieses Problem wird in Abschnitt 6.6 vorgestellt. Die Formulierung von Kontrakten stellt einen Balance-Akt dar: Formuliert man die Kontrakte zu scharf, so hat der resultierende Kontrakt-Automat ähnlich viele Zustände wie die Komponente und es gibt keinen Gewinn durch die Verwendung von Kontrakten. Wird der 14 2.3. Kontrakte Kontrakt Verhalten Erlaubtes Verhalten Abbildung 2.8.: Verhalten und Kontrakte Kontrakt jedoch zu lose formuliert so bekommt der Kontrakt-Automat viele Transitionen und zeigt Verhalten, dass die Verifikation der Systemeigenschaft unmöglich machen. Um gute Kontrakte zu formulieren, muss der Anwender zwischen relevanten und irrelevanten Verhaltensweisen unterscheiden. Ist beispielsweise für die zu verifizierende Eigenschaft unerheblich, welchen konkreten Wert eine Variable aufweist, sondern nur wichtig, dass der Wert eine bestimmte Eigenschaft erfüllt, so lässt sich häufig ein Kontrakt finden, der den Wert der Variable nicht-deterministisch auf einen Wert setzt. 15 3. Grundlagen In diesem Kapitel werden für diese Arbeit relevante Konzepte kurz zusammengefasst und erklärt. Abschnitte über Konzepte, die dem Leser bereits bekannt sind können also ohne Probleme übersprungen werden. 3.1. SPIN SPIN ist ein Model-Checker für asynchrone Software-Modelle, die in der Sprache Promela (Process Meta Language) definiert sind [Hol03]. Das Werkzeug verwendet „Explicit-State Model Checking“-Techniken, berechnet also jeden möglichen Zustand des Systems und überprüft, ob die zu verifizierenden Eigenschaften erfüllt sind. SPIN unterstützt eine Reihe von Optimierungstechniken, darunter • „Partial order reduction“ um den Zustandsraum von Modellen zu verkleinern [God95]. • Zustandskompressionstechniken, die den Speicherbedarf von Verifikationen senken können [Hol97]. • Nutzung von Multi-Prozessor-Systemen zur Geschwindigkeitsverbesserung [Hol08]. Es werden sowohl die Verifikation von Safety-Eigenschaften („Es passiert nie etwas schlimmes“) als auch von Liveness-Eigenschaften („Es passiert immer mal wieder etwas Gutes“) unterstützt. Die Verifikation von Modellen erfolgt nicht direkt durch SPIN selbst, sondern es wird C-Code für einen domänenspezifischen Modell-Checker generiert. Dies hat unter anderem den Vorteil, dass es leicht ist, externe C-Bibliotheken für Teile der ModellBeschreibungen zu verwenden. 3.2. SCADE SCADE ist ein Formalismus und Werkzeug zur Erstellung sicherheitskritischer Softwaresysteme5 . Zentraler Bestandteil ist die auf der synchronen Sprache Lustre [HCRP91] 5 SCADE wird von der Firma esterel-technologies.com Esterel Technologies vertrieben, zu finden unter http:// 17 3. Grundlagen aufbauende Beschreibungssprache. Die Sprache ist Datenfluss-orientiert, es werden also Gleichungen für die Ein- und Ausgabevariablen formuliert. Es stehen aber auch andere Mechanismen, wie zum Beispiel Safe State-Machines [And03] zur Verfügung. Außerdem beinhaltet die SCADE Suite noch einen Model-Checker, den Design Verifier, mit dessen Hilfe sich Eigenschaften von Modellen nachprüfen lassen6 . Der Design Verifier hat die Einschränkung, dass sich keine Liveness-Eigenschaften mit ihm nachweisen lassen. Aussagen wie „Die Ausgangsvariable x wird irgendwann einmal wahr“ lassen sich also nicht beweisen. Stattdessen werden solche Aussagen in SCADE beispielsweise verschärft zu „Die Ausgangsvariable x wird innerhalb von t Ausführungsschritten einmal wahr“. 3.3. LTL – Linear temporal logic LTL-Formeln können benutzt werden, um das zeitabhängige Verhalten von Systemen zu beschreiben [BK08]. Der LTL-Formalismus wurde von Amir Pnueli [Pnu77] erfunden. Die LTL-Logik stellt eine Erweiterung der Aussagenlogik dar, so dass nicht nur Aussagen über den jetzigen Zustand getroffen werden können, sondern auch über noch folgende Zustände. Die Aussagenlogik wird um folgende Operatoren erweitert: • Der next-Operator (Auch mit bezeichnet) sagt aus, dass eine Formel im nächstens Zustand gilt. Die Formel ϕ spezifiziert also Pfade der Form / • • ϕ / • _ _ _/ • Mit dem always-Operator () versehene Formeln gelten sowohl im aktuellen wie auch in allen folgenden Zuständen. Die Formel ϕ erlaubt also alle Pfade der Form / • ϕ • ϕ / • _ _ _/ ϕ • Der finally-Operator () gibt an, dass eine Formel irgendwann in der Zukunft einmal gelten wird. Die Formel ϕ spezifiziert zum Beispiel Pfade der Form • / • _ _ _/ • ϕ / • _ _ _/ • Formeln die gelten sollen, bis eine bestimmte Bedingung erfüllt ist, lassen sich mit dem until-Operator (U ) angeben. Wird beispielsweise gefordert, dass die Formel ϕ gilt, bis ψ gilt, so lässt sich dies schreiben als ϕU ψ. Ein Beispielpfad für diese 6 18 Entwickelt von der Firma Prover, zu finden unter http://prover.com 3.4. Büchi-Automaten Formel ist / • _ _ _/ • ϕ ϕ • ϕ / • _ _ _/ ψ Um die vollständige Mächtigkeit von LTL zu erreichen reicht es allerdings auch schon, nur die Operatoren und U zu haben, denn der finally-Operator lässt sich ausdrücken als ϕ = >U ϕ und der always-Operator als ϕ = ¬(>U ¬ϕ) Alle anderen Operatoren sind also zwar nützlich, aber nicht benötigt. 3.4. Büchi-Automaten Büchi-Automaten stellen eine Erweiterung von endlichen Automaten auf unendliche Eingaben dar [Muk96]. Sie wurden erstmals 1966 von dem Mathematiker Julius Richard Büchi eingeführt [Bü66]. Anders als endliche Automaten, die eine Eingabe akzeptieren, wenn die Ausführung in einem finalen Zustand endet, akzeptiert ein Büchi-Automat eine Eingabe genau dann, wenn die Ausführung unendlich oft einen finalen Zustand erreicht. Formal ist ein Büchi-Automat ein Tupel A = (Q, Σ, δ, µ, q0 , F ) wobei die Symbole folgende Bedeutung haben: • Q ist die Menge der Zustände des Automaten. • Die Menge von atomaren Aussagen Σ, die gültig oder ungültig sein können. • δ ⊆ Q × Q ist die Übergangsrelation des Automaten. • µ : Q → 2Σ gibt an, welche Aussagen in einem Zustand gelten müssen. • q0 ⊆ Q ist die Startzustandsmenge. • F ⊆ Q ist die Finalzustandsmenge. Eine Folge von Aussagen a0 a1 a2 . . . wird nun also genau dann akzeptiert, wenn es eine Folge von Zuständen q0 q1 q2 . . . gibt, wobei stets gilt qn δqn+1 und in der mindestens ein Zustand aus F unendlich oft vorkommt. Außerdem muss jede Aussage an mit der Menge von Aussagen µ(qn ) kompatibel sein, das heißt an ∈ µ(an ). 19 3. Grundlagen 3.4.1. Verallgemeinerter Büchi-Automat Für das Übersetzungsverfahren in Abschnitt 3.5 sind Büchi-Automaten wegen ihrer Beschränkung auf eine Finalzustandsmenge ungeeignet. Algorithmen wie [GPVW96] verwenden daher eine abgewandelte Form von Büchi-Automaten. Ein Verallgemeinerter Büchi-Automat7 unterscheidet sich von einem normalen Büchi-Automaten durch die Definition des Akzeptanzverhaltens. Während ein Büchi-Automat akzeptiert, wenn unendlich oft ein Finalzustand betreten wird, ist F hier eine Menge von Finalzustandsmengen (F ⊆ P(Q)). Der Automat akzeptiert nun, wenn aus jeder Finalzustandsmenge mindestens ein Zustand unendlich oft betreten wird. Ein verallgemeinerter Büchi-Automat lässt sich in einen normalen Büchi-Automaten übersetzen, indem man für jede Finalzustandsmenge eine „Ebene“ einführt. Jede Ebene enthält die gleichen Zustände und Transitionen wie der ursprüngliche Automat, nur dass beim Verlassen von den Finalzuständen der aktuellen Finalzustandsmenge die nächste Ebene betreten wird. Dadurch wird erreicht, dass ein Zyklus nur dann zustande kommt, wenn ein Zustand aus allen Finalzustandsmengen betreten wird. Für einen verallgemeinerten Büchi-Automaten (Q, Σ, δ, µ, q0 , F ) konstruiert man also einen Büchi-Automaten (Q0 , Σ, δ 0 , µ0 , q00 , F 0 ). Ohne Beschränkung der Allgemeinheit kann F = {f0 , f1 , . . . , fn−1 } angenommen werden. Die neue Zustandsmenge enthält nun eine Kopie der ursprünglichen Zustände für jede Finalzustandsmenge. Q0 = {(q, f ) | q ∈ Q, f ∈ F } Die Übergangsfunktion δ 0 kopiert zunächst alle ursprünglichen Übergänge und fügt dann Übergänge zwischen den Ebenen hinzu, wenn der Ursprungszustand in der aktuellen Finalzustandsmenge liegt. δ 0 = {((q1 , fi ), (q2 , fj )) | (q1 , q2 ) ∈ δ, (q1 ∈ fi ∧ j = (i + 1) mod n) ∨ (q1 6∈ fi ∧ i = j)} Die atomaren Aussagen, die in den Zuständen gelten müssen werden einfach übernommen. µ0 ((q, f )) = µ(q) Die Finalzustandsmenge besteht aus den Zuständen, die in der Finalzustandsmenge der aktuellen Ebene liegen. F 0 = {(q, f ) | f ∈ F, q ∈ f } 7 Im englischen als „generalized buchi automaton“ bezeichnet und als GBA abgekürzt. 20 3.5. LTL Übersetzung 3.5. LTL Übersetzung Um LTL Formeln einfacher übersetzen zu können und zu kanonisieren, werden diese in Büchi-Automaten übersetzt. Diese Übersetzung benutzt den in [GPVW96] beschriebenen Algorithmus. Da der Übersetzungsalgorithmus keine always-Konstrukte zulässt, müssen diese zunächst mit der folgenden Identität transformiert werden: always ϕ = ¬>U ¬ϕ Außerdem müssen für den Algorithmus alle Negationen so weit wie möglich nach innen geschoben werden, bis sie nur noch vor atomaren Aussagen stehen. Um die Größe der Formeln nicht unnötig zu erhöhen wird dual zum until-Operator U der Operator V eingeführt, der über die folgende Identität definiert ist: ϕV ψ = ¬(¬ϕU ¬ψ) Für die Konstruktion des Büchi-Automaten wird ein Graph aufgebaut, der schrittweise erweitert wird, bis der Büchi-Automat vollständig ist. Die Knoten des Graphen benötigen die folgenden Felder: Name Ein eindeutiger Bezeichner für den Knoten. Es wird vorausgesetzt, dass es eine Funktion NewName existiert, die bei jedem Aufruf einen neuen, eindeutigen Namen zurück gibt. Incoming Gibt die Knoten an, die eine Kante in diesen Knoten besitzen. Das Symbol init wird verwendet, um anzuzeigen, dass der Knoten initial ist. New Eine Liste von Formeln, die noch nicht bearbeitet wurde. Old Die Liste der Formeln, die bereits abgearbeitet wurden. Next Formeln, die in allen Nachfolgeknoten gelten müssen. Der Anfangsknoten hat einen beliebigen Namen, das Symbol Init in der Incoming-Menge und die gesamte zu übersetzende Formel als New-Feld. Die restlichen Felder sind leer. Der zentrale Bestandteil des Algorithmus ist die expand-Funktion. Diese nimmt einen Knoten und die Menge aller bisher generierten Knoten und erstellt durch rekursive Aufrufe ihrer selbst die resultierende Knotenmenge. Abbildung 3.1 zeigt die Implementierung dieser Funktion. Die Hilfsfunktionen New1, New2 und Next1 sind hierbei über Tabelle 3.1 definiert. 21 3. Grundlagen Expand(Node, NodeSet) 1 if N ode.New == ∅ 2 if ∃N ∈ N odeSet : N.Old == N ode.Old and N.Next == N ode.Next 3 N.Incoming = N.Incoming ∪ N ode.Incoming 4 return NodeSet 5 else return Expand([Name = NewName(), Incoming = {N ode.Name}, New = N ode.Next, Old = ∅, Next = ∅], {Node} ∪ NodeSet) 6 else 7 η = N ode.New [0] 8 N ode.New = N ode.New \ {η} 9 if η == P or η == ¬P or η == > or η == ⊥ 10 if η == ⊥ or ¬η ∈ N ode.Old 11 return NodeSet 12 else 13 N ode.Old = N ode.Old ∪ {η} 14 return Expand(Node, NodeSet) 15 elseif η == ϕU ψ or η == ϕV ψ or η == ϕ ∨ ψ 16 Node1 = [Name = NewName(), Incoming = N ode.Incoming, New = N ode.New ∪ ({New1(η)} \ N ode.Old ), Old = N ode.Old ∪ {η}, Next = N ode.Next ∪ {Next1(η)}] 17 Node2 = [Name = NewName(), Incoming = N ode.Incoming, New = N ode.New ∪ ({New2(η)} \ N ode.Old ), Old = N ode.Old ∪ {η}, Next = N ode.Next] 18 return Expand(Node2 , Expand(Node1 , NodeSet)) 19 elseif η == ϕ ∧ ψ 20 return Expand([Name = N ode.Name, Incoming = N ode.Incoming, New = N ode.New ∪ ({ϕ, ψ} \ N ode.Old ), Old = N ode.Old ∪ {η}, Next = N ode.Next], NodeSet) 21 elseif η == next ϕ 22 return Expand([Name = N ode.Name, Incoming = N ode.Incoming, New = N ode.New , Old = N ode.Old ∪ {η}, Next = N ode.Next ∪ {ϕ}], NodeSet) Abbildung 3.1.: Die Expand-Funktion 22 3.5. LTL Übersetzung η ϕU ψ ϕV ψ ϕ∨ψ New1 {ϕ} {ψ} {ϕ} Next1 {ϕU ψ} {ϕV ψ} ∅ New2 {ψ} {ϕ, ψ} {ψ} Tabelle 3.1.: Hilfsfunktionen für den LTL Übersetzungsalgorithmus Um eine gegebene Formel ϕ zu übersetzen, ruft man die Funktion also wie folgt auf: Expand([Name = NewName(), Incoming = {Init}, New = {ϕ}, Old = ∅, Next = ∅], ∅) = ns Per Konstruktion ist am Ende des Durchlaufs das New -Feld aller Knoten leer. Die atomaren Aussagen in den Old -Feldern der Knoten stellen die Aussagen dar, die in dem entsprechenden Zustand des Büchi-Automaten wahr sein müssen. Um den Graphen zu konstruieren, müssen noch die die Informationen der Incoming-Felder verwendet werden, um die Folgezustände von jedem Zustand zu berechnen. Die Next-Felder enthalten keine relevanten Informationen für den Büchi-Automaten. Es ergibt sich also ein verallgemeinerter Büchi-Automat (Q, Σ, δ, µ, q0 , F ) mit Q = {N ode.Name | Node ∈ ns} δ = {(N ode1.Name, N ode2.Name) | Node1 ∈ ns, Node2 ∈ ns, N ode1.Name ∈ N ode2.Incoming} µ(n) = {f | Node ∈ ns, N ode.Name = n, f ∈ N ode.Old , atom(f )} q0 = {N ode.Name | Node ∈ ns, Init ∈ N ode.Incoming} F = {{N ode.Name | Node ∈ ns, µU ψ 6∈ N ode.Old ∨ ψ ∈ N ode.Old } | µU ψ ∈ subformula(ϕ)} Dieser kann nun mithilfe der in Abschnitt 3.4.1 definierten Methode in einen normalen Büchi-Automaten umgewandelt werden. 3.5.1. Übersetzung des Existenzquantors Mithilfe des Existenzquantors ist es möglich, Werte zu einem bestimmten Zeitpunkt zu binden und zu einem späteren Zeitpunkt wieder zu verwenden.8 Beispielsweise gibt die 8 Eine sehr viel generellere Definition des Existenzquantors findet sich in [MP92]. Es wird jedoch kein allgemeiner Übersetzungsalgorithmus angegeben, weshalb in dieser Arbeit eine sehr eingeschränkte Version 23 3. Grundlagen Formel exists u = Engine.state : next u = Engine.state an, dass die Variable „Engine.state“ im nächsten Zustand den gleichen Wert wie im aktuellen besitzen soll. Um dieses Konstrukt zu übersetzen kann man Variablen einführen, die die Historie von Variablen speichern. Muss also beispielsweise der Wert einer Variable überprüft werden, der drei Schritte in der Vergangenheit liegt, so müssen drei neue Variablen in das Modell eingefügt werden, die die letzten drei Werte der Variablen speichern. Die Anzahl der Schritte, die in die Vergangenheit geschaut werden muss ist allerdings nicht immer endlich. Betrachten wir die leicht abgewandelte Formel exists u = Engine.state : always u = Engine.state die aussagt, dass der Wert einer Variable für immer konstant bleiben soll, so ist leicht einzusehen, dass man für die Verifikation unendlich viele Geschichtsvariablen einführen müsste. Verwendet man also diese Übersetzung, so muss man die Verwendung des Existenzquantors einschränken: Die durch den Quantor gebundene Variable darf nicht innerhalb eines always-Operators vorkommen. Der Algorithmus zur Übersetzung ist in Abbildung 3.2 angegeben. TranslateHistory(expr , vars) 1 if expr == (exists u = x : f ) 2 vars[u].var = x 3 vars[u].level = 0 4 return TranslateHistory(f, vars) // v ist Variable 5 elseif expr == v 6 if vars[v] == Nil 7 return v 8 else return vars[v] 9 elseif expr == (next f ) 10 ∀v : vars[v].level = vars[v].level + 1 11 return (next TranslateHistory(f , vars)) 12 elseif expr == (alwaysf ) 13 return (always TranslateHistory(f , ∅)) 14 elseif expr == (lhs op rhs) // op ist ∧, ∨, <, > oder eine andere Relation 15 return (TranslateHistory(lhs, vars) op TranslateHistory(rhs, vars)) Abbildung 3.2.: Übersetzung von Geschichtsvariablen verwendet wird. 24 3.6. Binäre Entscheidungsdiagramme 3.6. Binäre Entscheidungsdiagramme Versucht man, in einem Programm Funktionen genau wie Daten zu behandeln, so stößt man auf verschiedene Probleme: • Wird die Funktion durch ihren Code repräsentiert, so ist die Darstellung nicht nur abhängig von der Wahl der Programmiersprache, sondern auch uneindeutig, da es in einer Turing-vollständigen Sprache unendlich viele Quelltexte gibt, die eine gegebene Funktion kodieren. • Verwendet man zur Repräsentation die Wertetabelle der Funktion, so ist zwar eine eindeutige Kodierung sichergestellt, allerdings kann schon die Kodierung einer Funktion mit sehr kleinem Wertebereich enorm viel Speicher veranschlagen (Die Kodierung einer Funktion von 32-bit Integer nach Bool würde zum Beispiel schon 0.5 GB Daten benötigen). Um diese Probleme zu lösen, kann man binäre Entscheidungsdiagramme (BDD)9 verwenden [Knu11]. Diese lassen sich verwenden, um Funktionen der Form f : Bn → B eindeutig zu kodieren (Wobei n beliebig, aber endlich ist). Ein binäres Entscheidungsdiagramm ist ein gerichteter, azyklischer Graph des folgenden Aufbaus: • Den einfachsten Fall stellen die Diagramme dar, die nur aus den Symbolen > oder ⊥ bestehen (Abbildung 3.3). Diese repräsentieren die Funktion, die für alle Eingaben ⊤ ⊥ Abbildung 3.3.: Die einfachsten zwei Entscheidungsdiagramme wahr ist (>) und die Funktion, die für alle Eingaben unwahr ist (⊥). • Möchte man die Funktion, die sich, falls die Variable α wahr ist, wie die Funktion f1 und ansonsten wie f2 verhält, kodieren, so erstellt man einen neuen Knoten mit der Bezeichnung α und verbindet ihn mit einer durchzogenen Linie mit dem Diagramm für f1 und mit einer gestrichelten Linie mit dem Diagramm von f2 (Abbildung 3.4). Mit diesen einfachen Konstruktionsregeln lassen sich nun beliebige Funktionen konstruieren. Beispielsweise kann die Funktion (α ∧ β) ∨ γ wie in Abbildung 3.5 kodiert werden. Diese Art der Kodierung hat nun aber das folgende Problem: Sie ist nicht eindeutig. Beispielsweise stellt das Diagramm in Abbildung 3.6 dieselbe Funktion dar. Das liegt daran, 9 In der englisch-sprachigen Literatur als „binary decision diagrams“ bezeichnet und mit BDD abgekürzt 25 3. Grundlagen α f1 f2 Abbildung 3.4.: Zusammengesetztes Entscheidungsdiagramm α β γ γ ⊤ γ ⊥ Abbildung 3.5.: Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ dass das Diagramm in Abbildung 3.5 viele redundante Knoten enthält: Beide Äste des linken γ-Knoten führen zum gleichen Knoten, dass heißt an dieser Stelle spielt die Belegung der Variablen keine Rolle. Die anderen beiden γ-Knoten sind äquivalent, da ihre Kinderknoten gleich sind. Entfernt man alle diese Redundanzen, so erhält man das reduzierte Entscheidungsdiagramm in Abbildung 3.7. Die Bedingung, dass das Diagramm keine redundanten Knoten aufweisen darf, reicht allerdings noch nicht aus, um eine Eindeutigkeit zu erzwingen, wie das Diagramm in Abbildung 3.8 zeigt. Um dieses Problem zu lösen, kann man fordern, dass die Diagramme zusätzlich auch geordnet sind, dass heißt es gibt eine totale Ordnung auf den Variablen und Variablen höherer Ordnung haben Verbindungen zu Variablen niedriger Ordnung, aber nicht umgekehrt. Das Diagramm in Abbildung 3.7 hat also die Ordnung α > β > γ, während in Abbildung 3.8 die Ordnung γ > α > β eingehalten wird. Die so eingeführten geordneten, reduzierten binären Entscheidungsdiagramme haben damit eine Reihe von Vorteilen gegenüber anderen Funktionskodierungen: • Sie sind eindeutig, jede Funktion hat genau ein Entscheidungsdiagramm. Außerdem sind sie schon eindeutig über ihren Anfangsknoten definiert, so dass ein Test auf Gleichheit in konstanter Zeit möglich ist (Zum Vergleich: Bei der Kodierung als Wertetabelle benötigt man 2n Vergleiche wobei n die Anzahl der Variablen ist und bei der Kodierung als Quelltext ist ein Test auf Äquivalenz in vielen Fällen prinzipiell unmöglich10 ). 10 Gezeigt durch die Unentscheidbarkeit des Halteproblems [Tur36] 26 3.6. Binäre Entscheidungsdiagramme α β γ γ ⊤ ⊥ Abbildung 3.6.: Äquivalentes Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ α β γ ⊥ ⊤ Abbildung 3.7.: Reduziertes Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ • Eine Auswertung der Funktion ist effizient möglich: An jedem Knoten wird entschieden, ob die entsprechende Variable wahr oder falsch ist und der entsprechende Ast verfolgt. Endet man bei dem Knoten >, so ist das Ergebnis der Funktion wahr, endet man bei ⊥, so ist es falsch. • Für viele Funktionen ist das entsprechende Entscheidungsdiagramm sehr klein. Allerdings lässt sich zeigen, dass es Funktionen gibt, für die keine effiziente Repräsentation als Entscheidungsdiagramm existiert, wie zum Beispiel die Multiplikationsfunktion [Bry98]. • Speichert man mehrere Entscheidungsdiagramme, so kann man den Speicherbedarf enorm verringern, indem die Diagramme sich gleiche Knoten teilen. In dem Rest dieser Arbeit sind mit dem Begriff „Entscheidungsdiagramm“ immer geordnete, reduzierte und geteilte Entscheidungsdiagramme gemeint. Mehr zu Entscheidungsdiagrammen im Allgemeinen lässt sich in [Knu11] finden. 27 3. Grundlagen γ α β ⊥ ⊤ Abbildung 3.8.: Äquivalentes reduziertes Entscheidungsdiagramm der Funktion (α ∧ β) ∨ γ 3.7. Datentypen als Entscheidungsdiagramme Da Enscheidungsdiagramme binäre Funktionen auf boolesche Werte kodieren, lassen sie sich auch verwenden, um Mengen von endlichen Datentypen zu kodieren. Das ist möglich, weil sich jeder endliche Datentyp binär kodieren lässt (Notfalls durch Durchnummerierung aller möglichen Werte). Die entsprechende Funktion gibt also wahr zurück, wenn sich das Element, dass der binären Kodierung entspricht sich in der Menge befindet. Ist also ein endlicher Datentyp T zusammen mit einer Kodierungsfunktion c : T → {0, 1}n gegeben, so lässt sich ein BDD für eine beliebige Menge Q ⊆ T konstruieren, indem man für jedes Element q ∈ Q der Menge das BDD konstruiert, dass die Funktion f : {0, 1}n → {0, 1} repräsentiert, die nur bei dem Wert c(q) wahr ist (Das ist die charakteristische Funktion von c(q)). Die so generierten BDD werden dann per Disjunktion zu einem BDD zusammengefügt. Diese Konstruktion ist aber extrem ineffizient, da sehr viele BDDs erstellt und sofort wieder verworfen werden. Effizienter ist es, das BDD mit einem divide-and-conquer-Algorithmus zu erstellen: Dieser erhält eine Menge der noch zu kodierenden Werte sowie die Bitposition, an der gerade kodiert wird (beginnend bei Bit null). Der Algorithmus teilt nun die gegebene Menge Q in zwei Mengen: Die eine enthält die Werte deren Kodierung an der aktuellen Bitposition eine null aufweisen, die andere Menge enthält die restlichen Werte. Auf diesen Mengen wird der Algorithmus nun rekursiv aufgerufen. Die vollständige Implementierung ist in Abbildung 3.9 zu sehen. Um ein BDD nun wieder in eine Menge zu verwandeln, kann man das Diagramm rekursiv durchlaufen. Die entsprechende Funktion erhält das aktuell betrachtete Teildiagramm sowie die Bitposition und den binären Wert der bis zu dieser Position kodiert wurde. Enspricht das Diagramm dem Nullblatt, so bedeutet dies, dass vom Teildiagramm keine Werte kodiert werden, es wird also die leere Menge zurück gegeben. Ist die Bitposition am Ende des kodierbaren Bereichs angelangt, so wird der aktuelle Wert von dem Teildiagramm kodiert und damit als einzelnes Element dekodiert und zurück gegeben. Entspricht der aktuelle Hauptknoten des Diagramms nicht der aktuellen Bitposition, so ist der Wert an der aktuellen Bitposition egal, das Ergebnis ist also die Vereinigung der Aufrufe mit Wert null und eins an der aktuellen Bitposition. Ansonsten wird der linke Ast des Knotens 28 3.7. Datentypen als Entscheidungsdiagramme CreateBDD(Q, Pos) 1 if Q == ∅ 2 return Leaf(0) 3 if Pos == n 4 return Leaf(1) 5 Ql = {v | v ∈ Q, c(v)[Pos] == 0} 6 Qr = {v | v ∈ Q, c(v)[Pos] == 1} 7 return Node(Pos, CreateBDD(Ql , Pos + 1), CreateBDD(Qr , Pos + 1)) Abbildung 3.9.: Eine Funktion, um endliche Mengen in BDDs umzuwandeln mit dem Wert eins belegt, der rechte mit null und die Ergebnisse der rekursiven Aufrufe vereinigt. Der vollständige Algorithmus ist in Abbildung 3.10 angegeben. DecodeBDD(Tree, Pos, Value) 1 if Tree == Leaf(0) 2 return ∅ 3 elseif Pos == n 4 return {c−1 (Value)} 5 elseif Tree == Leaf (1) or T ree.id < Pos 6 return DecodeBDD(Tree, Pos + 1, Valuek(1 << Pos)) ∪ DecodeBDD(Tree, Pos + 1, Value) 7 else 8 return DecodeBDD(T ree.left, Pos + 1, Valuek(1 << Pos)) ∪ DecodeBDD(T ree.right, Pos + 1, Value) Abbildung 3.10.: Algorithmus um BDDs in Mengen umzuwandeln Im folgenden sollen sowohl Datentypen wie auch Algorithmen auf ihnen in BDD-Form realisiert werden. 3.7.1. Integer Ganze Zahlen mit oder ohne Vorzeichen lassen sich sehr leicht binär kodieren. Kodiert man natürliche Zahlen beispielsweise mit den drei Bits b0 , b1 und b2 (Wobei b0 das niederwertigste Bit kodiert), so lässt sich die Menge {2, 3, 5} auffassen als das Diagramm in Abbildung 3.11. 29 3. Grundlagen b0 b1 b1 b2 b2 ⊤ ⊥ Abbildung 3.11.: Entscheidungsdiagramm der Menge {2, 3, 5} Verwendet man Mengen, um verschiedene mögliche Werte einer Variable zu speichern, so kann es nützlich für die Geschwindigkeit der Verifikation sein, wenn man effiziente Algorithmen finden kann, um Operationen gleichzeitig auf den einzelnen Werten der Menge auszuführen. Für eine binäre Operation ◦ wird also ein effizienter Algorithmus auf Entscheidungsdiagrammen gesucht, der für zwei Mengen A und B die Menge {a ◦ b | a ∈ A, b ∈ B} berechnet. Im folgenden wird die Implementierung einiger einfacher Operationen erläutert. Addition Um beispielsweise die elementweise Addition von zwei Mengen angeben zu können, definiert man zunächst eine Hilfsfunktion „plus“, die zusätzlich noch einen Parameter enthält, die den Übertrag der letzten Bit-Addition speichert. Addiert man nun zwei Diagramme, deren Hauptknoten dieselbe Markierung x haben, so ergibt sich ein neues Diagramm mit dem Hauptknoten der Markierung x, der wie folgt aufgebaut ist: x plus , A B C x ! x ,0 D = plus(A,D,0) ∨ plus(B,C,0) plus(A,C,1) ∨ plus(B,D,0) Der linke Ast des Resultats muss hierbei den Fall behandeln, dass das Ergebnis der Addition im Bit x den Wert 1 erhält. Dies kann nur dann passieren, wenn genau eines der ArgumentBits 1 ist. Hierfür gibt es trivialerweise zwei Möglichkeiten, bei der einen ist das erste Argument 1 und der linke Ast des ersten Argument wird ohne Überhang mit dem rechten Ast des zweiten Arguments addiert; bei der anderen Möglichkeit ist es genau umgekehrt. Da beide Möglichkeiten auftreten können, müssen die Ergebnisse vereinigt werden, was auf Entscheidungsdiagramm-Ebene einer Disjunktion entspricht. Für den rechten Ast des Resultats muss man nun die Möglichkeiten betrachten, bei denen das Bit x den Wert 0 30 3.7. Datentypen als Entscheidungsdiagramme erhalten kann. Dies kann geschehen, wenn beide Bits der Argumente 0 sind, oder wenn beide 1 sind. Im letzteren Fall muss dann beim rekursiven Aufruf der Übertrag hinzugefügt werden. Wird bei der Addition bereits ein Übertrag berücksichtigt, so ändert sich die Argumentation leicht, ist aber im wesentlichen symmetrisch: x plus , A x ! x ,1 B C = plus(A,C,1) ∨ plus(B,D,0) D plus(A,D,1) ∨ plus(B,C,1) Im Fall, dass der Knoten eines der Argumente niedrigere Ordnung besitzt – hier wird nur der Fall betrachtet, dass das zweite Argument niedrigere Ordnung hat, da die Addition symmetrisch ist – kann man sich überlegen, dass das zweite Argument hierbei äquivalent zu einem Diagramm ist, bei dem ein mit x markierter Knoten oben steht, dessen Äste beide zum ursprünglichen Diagramm führen. Setzt man dies dann in die erste Gleichung ein, so erhält man (für y < x): x plus , A x ! y ,0 = plus(A,R,0) ∨ plus(B,R,0) B C | {z }D plus(A,R,1) ∨ plus(B,R,0) R Die Gleichung mit Übertrag ist genau äquivalent zu den vorherigen Fällen und daher weg gelassen. Um das Ergebnis der Addition zweier BDD auszurechnen ruft man nun die definierte Hilfsfunktion mit dem Parameter 0 für den Übertrag auf. Subtraktion Für die Subtraktion muss der Code für die Addition nur leicht verändert werden: x minus , A ,0 B C ,1 B C minus(A,C,1) ∨ minus(B,D,1) ,0 B C | {z }D minus(A,D,0) ∨ plus(B,C,1) x ! y , A = D x minus(A,C,0) ∨ minus(B,D,0) x ! , minus minus(A,D,0) ∨ minus(B,C,1) x A = D x minus x ! x = plus(A,R,0) ∨ plus(B,R,0) plus(A,R,1) ∨ plus(B,R,0) R 31 3. Grundlagen 3.7.2. Tupel Tupel lassen sich einfach binär kodieren, indem man die Binärkodierung aller Elemente hintereinander schreibt. Diese Art der Kodierung hat den Vorteil, dass es einfach möglich ist, Restriktionen auf einzelnen Elementen zu formulieren. Möchte man beispielsweise alle Tupel kodieren, deren zweites Element eine bestimmte Bedingung erfüllt, so reicht es, die Bedingung nur auf dem Element selbst zu übersetzen und alle Kennzeichnungen der Knoten in dem resultierenden Diagramm um die Bitbreite des ersten Elements zu erhöhen. 32 4. Lösungsansatz In dieser Arbeit sollen Kontrakte verwendet werden, um das Zustandsexplosions-Problem [CGJ+ 01] bei der Verifikation von großen GALS-Systemen zu verhindern. Mit Hilfe von Kontrakten lässt sich das Verhalten von jeder Komponente in einem GALSSystem beschreiben. Die Komponenten sind in der synchronen Modellierungssprache SCADE (siehe Abschnitt 3.2) modelliert. Die Kontrakte treffen Aussagen über die Ein- und Ausgangsvariablen der Komponenten. Kommunikation zwischen den Komponenten wird ermöglicht, indem Ausgabevariablen mit Eingabevariablen verbunden werden. Eine Verbindung gibt dabei immer die Ursprungskomponente und ihre Ausgabevariable sowie die Zielkomponente mit ihrer Eingabevariable an. Diese Zusammenhänge sind in Abbildung 4.1 gezeigt. Grafisch lässt sich eine einzelne Komponente in Anlehnung an die Notationssprache UML [Obj10] wie in Abbildung 4.2 darstellen. Die abgebildete Komponente stellt dabei einen Generator dar, der einen booleschen Eingang „on“ und einen numerischen Ausgang „output“ besitzt. Der Kontrakt sagt aus, dass falls der Eingang auf „wahr“ gesetzt wird, der Ausgang immer höchstens den Wert 10 ausgibt. Eine Verifikation eines solchen GALS-Systems muss nun zwei Aufgaben erfüllen: 1. Die Korrektheit der angegebenen Kontrakte muss überprüft werden. Die Kontrakte sind korrekt, wenn das unter liegende SCADE Modell die Kontrakt-Formel erfüllt. Um diese Eigenschaft nachzuweisen wird der SCADE Design Verifier verwendet. 1 1 GTL Spezifikation 1 1 Komponente 2 * Verbindung * 1 * Kontrakt Verifikationsziel * * SCADE Modell * 1 1 * * * Variable 2 * Abbildung 4.1.: GTL Nomenklatur 33 4. Lösungsansatz Generator always on => output < 10 +output: int -on: bool Abbildung 4.2.: Beispielkomponente Generator +output: int -on: bool <10? Abbildung 4.3.: SCADE-Wrapper für Beispielkomponente 2. Es muss nachgewiesen werden, dass das Zusammenspiel der Kontrakte das globale Verifikationsziel erfüllt. Diese Eigenschaft wird überprüft, indem das System aus Komponent-Kontrakten in den Promela Formalismus übersetzt wird und die Gültigkeit der Formel mit SPIN nachgewiesen wird. Die erste Eigenschaft wird überprüft, indem in SCADE ein Wrapper um die Komponente konstruiert wird, der alle Eingaben der Komponente repliziert und auf den Ausgaben die Tests durchführt, die der Kontrakt spezifiziert. Der entsprechende Wrapper für die Beispielkomponente aus Abbildung 4.2 ist in Abbildung 4.3 skizziert. Für den Nachweis der zweiten Eigenschaft ist es erforderlich, die Kontrakte in Komponenten zu übersetzen, die jedes Verhalten zeigen können, dass der Kontrakt ihnen vorgibt. Hierfür werden im Promela-Formalismus nicht-deterministische Automaten konstruiert, deren Zusammenspiel dann mit SPIN überprüft wird. 4.1. Offene Fragen Mit dem in dieser Arbeit vorgestellten Ansatz lassen sich Sicherheitseigenschaften von GALS-Systemen gut nachweisen, da für Sicherheitseigenschaften wichtig ist, dass Komponenten ein oder mehrere bestimmte Verhalten nicht zeigen. Die Kontrakte stellen daher auch sicher, dass eine Komponente nicht mehr Verhalten zeigt, als es der Kontrakt zulässt. Bei der Verifikation von Lebendigkeitseigenschaften stellt sich jedoch ein Problem: Lebendigkeitseigenschaften fordern, dass eine Komponente ein bestimmtes Verhalten garantiert besitzt. Da die Korrektheit eines Kontraktes bedeutet, dass der Kontrakt mindestens so viel Verhalten wie die Komponente selbst besitzt, kann der Kontrakt Verhalten erlauben, dass die Lebendigkeitseigenschaft erfüllt. Das bedeutet aber nicht zwangsläufig, dass die Komponente dieses Verhalten auch besitzt. Dieses Problem wird in dieser Arbeit aber nicht behandelt sondern es wird davon ausgegangen, dass alle zu verifizierenden Eigenschaften Sicherheitseigenschaften sind. 34 5. GTL – GALS Translation Language Um ein zu verifizierendes GALS-Modell vollständig zu definieren, müssen die folgenden Eigenschaften spezifiziert werden: • Die synchronen Komponenten, aus denen das Gesamtsystem zusammen gesetzt wird. Jede synchrone Komponente ist eine Instanz eines in einer synchronen Sprache spezifizierten Modells. • Die Verbindungen zwischen den Komponenten. Eine Verbindung verknüpft eine Ausgabevariable einer Komponente mit einer Eingabevariable einer anderen. • Die zu verifizierende Eigenschaft in Form einer LTL Formel über die Variablen der einzelnen Komponenten. Komponenten Die GTL-Sprache verwendet externe Formalismen, um die synchronen Komponenten zu beschreiben. Auf die Implementierungen der synchronen Komponenten wird in der Beschreibung nur verwiesen. Hierfür wird das Schlüsselwort model verwendet. Das folgende Codefragment deklariert eine Komponente mit dem Namen EntrySensor, die im SCADE-Formalismus definiert wurde: model [ s c a d e ] E n t r y S e n s o r ( " L i g h t B a r r i e r " ) ; Die Ausdrücke in Klammern („LightBarrier“) machen formalismus-spezifische Angaben, in diesem Fall geben sie beispielsweise deb Baneb des zu der Komponente gehörenden SCADE-Knotens an. Zu beachten ist, dass in der Komponentendeklaration keine Ein- oder Ausgabekanäle definiert werden. Die GTL-Sprache setzt voraus, dass diese implizit in dem Modell-Formalismus vorhanden sind. Kontrakte Um nun Zusicherungen zu formulieren, die die Komponente einhalten muss, kann man die Deklaration um so genannte Kontrakte erweitern: model [ s c a d e ] E n t r y S e n s o r ( " L i g h t B a r r i e r " ) { o b s c u r e d and ( n e x t o b s c u r e d ) and ( not o f f l i n e ) => a l e r t ; } Dieser Kontrakt besagt beispielsweise, dass wenn der Sensor der Lichtschranke zwei Zeitschritte verdeckt ist und der Sensor nicht ausgeschaltet ist, auf jeden Fall Alarm ausgelöst wird. Kontrakte dürfen nur Aussagen über Variablen der lokalen Komponente machen. Aus Gründen der Lesbarkeit können Kontrakte mit dem einleitenden Schlüsselwort „contract“ versehen werden. 35 5. GTL – GALS Translation Language Verbindungen Die Verbindungen zwischen Komponenten werden durch connect-Deklarationen angegeben. Eine Verbindung gibt dabei eine Variable einer Komponente an, von der sie ausgeht und eine Variable eines anderen Modells, zu der sie geht. c o n n e c t E n t r y S e n s o r . a l e r t T r a p D o o r . open ; Diese Verbindung gibt beispielsweise an, dass das Ausgabesignal alert der Komponente EntrySensor mit dem Eingabesignal open der Komponente TrapDoor verbunden sein soll. Es können nur Ausgabesignale mit Eingabesignalen verknüpft werden. Ist das selbe Ausgabesignal mit mehreren Eingabesignalen verknüpft, so erhält jede Eingabe eine Kopie des von der Ausgabe gemachten Wertes. Sind jedoch mehrere Ausgabesignale mit der gleichen Eingabe verbunden, so erhält die Eingabe jeweils den Wert der zuletzt aktiven Komponente. Verifikationsziel Um nun Aussagen über das Gesamtsystem formulieren zu können, verwendet man das verify-Schlüsselwort. Mit diesem lassen sich LTL-Formeln angeben, die Gültigkeit im System besitzen sollen. verify { always ( S y s t e m . o f f l i n e => not T r a p D o o r . open ) ; } Im Gegensatz zu Kontrakten können diese Formeln Variablen aus mehreren Modellen enthalten. Die gesamte Grammatik der GTL-Sprache ist im nächsten Abschnitt 5.1 angegeben. Ausführlichere Syntax-Beispiele findet man im Abschnitt 8. 5.1. Grammatik Eine globale Deklaration ist entweder eine Modell-Deklaration, eine Verbindungsdeklaration oder eine Formel, die das Gesamtsystem erfüllen muss. hdeclarationi ::= hmodel_decli | hconnect_decli | hverify_decli Eine Modell Deklaration besteht aus dem Schlüsselwort model, gefolgt von dem Namen des synchronen Formalismus, in dem das Modell definiert ist (beispielsweise scade). Danach folgt der Name der Komponente und eine Liste von Argumenten, die spezifisch für den synchronen Formalismus angeben, wie das Modell zu laden ist. Als letztes kommt der Rumpf der Modelldeklaration, der die Kontrakte spezifiziert, die das Modell erfüllt. hmodel_decli ::= ‘model’ ‘[’ hidi ‘]’ hidi ‘(’ (hstringi (‘,’ hstringi)*)? ‘)’ hmodel_contracti 36 5.1. Grammatik Ein Kontrakt ist eine Liste von LTL-Formeln, die die Komponente erfüllt oder Initialisierungswerten für die Variablen der Komponente. Erfüllt das Modell keine Formel, so kann die Modell-Deklaration auch mit einem Semikolon beendet werden. hmodel_contracti ::= ‘{’ (hcontract_bodyi ‘;’)* ‘}’ | ‘;’ hcontract_bodyi ::= hformulai | ‘init’ hidi hinti Eine Verbindungsdeklaration besteht aus dem Schlüsselwort connect und der Angabe einer Variable der Quellkomponente und der Zielkomponente. Die Variable wird dabei durch den Namen der Komponente, einen Punkt und den Namen der Variable spezifiziert. hconnect_decli ::= ‘connect’ hidi ‘.’ hidi hidi ‘.’ hidi ‘;’ Formeln, die über das Gesamtsystem verifiziert werden sollen, lassen sich in verify-Blöcken spezifizieren. Diese enthalten eine Liste von Formeln. hverify_decli ::= ‘verify’ ‘{’ (hformulai ‘;’)* ‘}’ Eine Formel kann verschiedene Formen annehmen: • Als atomare Aussage besteht sie aus einer Variable, Konstante oder Relation zwischen zwei Ausdrücken. • Formeln können mit den logischen Operatoren and, or, implies sowie not wieder zu neuen Formeln zusammen gesetzt werden. • Mithilfe der temporallogischen Operatoren always, next und finally können neue Formeln aus anderen gebildet werden. • Das Konstrukt exists erlaubt die Bindung einer Variable an den aktuellen Wert einer anderen und somit die Referenzierung von früheren Werten. • Automaten erlauben die Konstruktion von Zustandsautomaten, die abhängig von den Eingabewerten Ausgabewerte generieren können. Ein Zustandsautomat besteht aus einer Menge von Zuständen. hformulai ::= hatomi | ‘not’ hformulai | hformulai ‘and’ hformulai | hformulai ‘or’ hformulai | hformulai ‘implies’ hformulai | ‘always’ hformulai | ‘next’ hformulai | ‘finally’ hinti hformulai | ‘exists’ hidi ‘=’ hliti ‘:’ hformulai 37 5. GTL – GALS Translation Language | ‘automaton’ ‘{’ (hstatei)* ‘}’ | ‘(’ hformulai ‘)’ hliti ::= hinti | hvari Ein Zustand eines Automaten besteht hierbei aus dem Namen des Zustands, sowie der Annotierung, ob es sich um einen Initial- und/oder einen Finalzustand handelt und einer Menge von Formeln und Transitionen in andere Zustände. Die Formeln in einem Zustand sind Formeln, die gelten müssen, wenn der Zustand betreten werden soll. Die Transitionen aus dem Zustand in einen anderen Zustand können optional mit Bedingungen in Form von Formeln versehen werden, die gelten müssen, damit die Transition schalten kann. hstatei ::= ‘initial’? ‘final’? ‘state’ hidi ‘{’ (hstate_contenti ‘;’)* ‘}’ hstate_contenti ::= hformulai | ‘transition’ (‘[’ hformulai ‘]’)? hidi Atomare Aussagen stellen Formelteile dar, die nicht durch LTL darstellbar sind (siehe Abschnitt 5.2) und daher im LTL-Übersetzungsalgorithmus als Atome behandelt werden müssen. Eine atomare Aussage kann entweder eine boolesche Variable, eine boolesche Konstante oder eine Relation zwischen zwei numerischen Ausdrücken sein. Außerdem kann mit dem Schlüsselwort „in“ angegeben werden, dass der Wert einer Variable aus einer festen Menge stammen muss. hatomi ::= hvari | ‘true’ | ‘false’ | hexpri ‘<’ hexpri | hexpri ‘>’ hexpri | hexpri ‘<=’ hexpri | hexpri ‘>=’ hexpri | hexpri ‘=’ hexpri | hvari ‘in’ ‘{’ (hliti (‘,’ hliti)*)? ‘}’ Ein Ausdruck ist entweder eine numerische Konstante, eine Variable oder eine arithmetische Operation (Addition, Subtraktion, Multiplikation oder Division werden unterstützt) von zwei Ausdrücken. hexpri ::= hliti | hexpri ‘+’ hexpri | hexpri ‘-’ hexpri | hexpri ‘*’ hexpri | hexpri ‘/’ hexpri | ‘(’ hexpri ‘)’ Eine Variable kann unqualifiziert sein und nur ihren Namen angeben, oder zusätzlich noch den Namen der Komponente aus der sie stammt angeben und damit als qualifiziert gelten. 38 5.2. Formeln hvari ::= hidi | hidi ‘.’ hidi hidi ::= (‘a’-‘z’ ‘A’-‘Z’ ‘0’-‘9’)+ hinti ::= (‘0’-‘9’)+ hstringi ::= ‘"’ (‘a’-‘z’ ‘A’-‘Z’ ‘0’-‘9’)* ‘"’ 5.2. Formeln Die Ausdrücke, die zur Spezifikation von Kontrakten sowie zur Formulierung von einzuhaltenden Bedingungen verwendet werden, stellen eine Untermenge der so genannten LTL-Formeln (LTL steht für „linear temporal logic“, siehe dazu auch Abschnitt 3.3) dar. Allerdings sind die Atome der LTL-Formeln, die in dieser Arbeit betrachtet werden nicht nur boolesche Variablen und Konstanten, sondern auch Relationen zwischen numerischen Variablen, Ausdrücken und/oder Konstanten. Da der SCADE Design Verifier nicht in der Lage ist, Liveness-Eigenschaften zu verifizieren, können in den Formeln allerdings keine until-Konstrukte verwendet werden. 5.3. Operationelle Semantik Struturelle operationelle Semantiken werden verwendet, um die Bedeutung von Programmen festzulegen, indem meist die resultierenden Transitionssysteme hergeleitet werden [Plo81].11 Hierfür werden so genannte Ableitungsregeln benutzt, um anzugeben, welche Semantik sich aus welchen Konstrukten herleiten lässt. Eine Ableitungsregel besteht aus einem Namen, Prämissen p0 , . . . , pn und einer Ableitung c, dargestellt als name p0 ... c pn Die Bedeutung einer Ableitungsregel ist: Gelten die Prämissen p0 bis pn , so kann Regel name angewandt werden um die Aussage c herzuleiten. Die Notation kann auch verwendet werden, um Ableitungsbäume darzustellen: r1 r2 p0 p1 c1 c2 p2 Dieser Baum stellt die Aussage dar, dass zunächst aus den Prämissen p0 und p1 mithilfe der Regel r1 die Aussage c1 abgeleitet werden kann. Mit dem Ergebnis c1 sowie der Prämisse p2 kann nun mit der Regel r2 die Aussage c2 bewiesen werden. 11 Im Unterschied zu den meisten anderen Arbeiten wird hier allerdings kein Transitionssystem abgeleitet, sondern eine abstrakte GTL-Spezifikation hergeleitet. 39 5. GTL – GALS Translation Language 5.3.1. Variablen In den LTL-Formeln können frühere Werte von Variablen referenziert werden. Hierfür werden die Variablen mit einer natürlichen Zahl versehen, die die Anzahl von Schritten angibt, die in die Vergangenheit geschaut werden soll. Definition 5. Die Menge aller in einer GTL-Spezifikation vorkommenden Bezeichner heißt Id . In der echten Programmimplementierung handelt es sich hierbei um Strings aus Buchstaben und Zahlen. Variablen kommen in der operationellen Semantik in zwei Formen vor: In der zu verifizierenden Formel kommen sie qualifiziert mit dem Modellnamen vor, die Variablen sind also Elemente der Menge Id × Id × N. In Modell-Kontrakten ist der Modellname klar, daher sind die Variablen hier unqualifiziert und damit Element der Menge Id × N. Variablen können je nach Typ unterschiedliche Werte haben. Definition 6. Die Menge aller Möglichen Werte, die die Variablen eines Systems annehmen können wird mit Val bezeichnet. 5.3.2. Aufbau Ein GALS-System besteht aus Komponentenkonfigurationen, Verbindungen und einer Verifikationsformel. Definition 7. Eine Komponentenkonfiguration m ∈ M ist gegeben durch einen Namen, den synchronen Automaten (Element der Menge A), den Kontrakt (falls die Komponente mehrere Kontrakte besitzt werden diese durch Konjunkion zusammen gefasst) und Initialisierungswerten für die Variablen. M = Id × A × LTL(Id ) × (Id → Val ) Die Initialisierungswerte werden über eine Abbildung von Variablennamen auf Werte Val dargestellt. Definition 8. Die Verbindungen zwischen Komponenten werden als Viertupel dargestellt, die die Komponente und die Variable angibt, von dem die Verbindung ausgeht und Komponente und Variable, in dem die Verbindung endet. C = Id × Id × Id × Id Definition 9. Die Semantik s eines GTL-Modells wird angegeben als ein Element der Menge aller möglichen GTL-Spezifikationen LGT L . Eine konkrete Semantik besteht dabei aus einer 40 5.3. Operationelle Semantik Menge von Komponenten, einer Menge von Verbindungen zwischen den Komponenten sowie einer Formel, die verifiziert werden soll. LGT L = P(M) × P(C) × LTL(Id × Id × N) Die LTL-Formel ist über Paare von Namen definiert, wobei der erste den Namen des Modells angibt und der zweite den der Variable im Modell. 5.3.3. Ableitungsregeln Um nun die Ableitungsregeln zu formulieren zu können, die angeben, wie ein gegebenes Textmodell in ein GTL-Modell übersetzt wird, müssen zuerst die Ableitungsarten eingeführt werden. Zunächst gibt es die globale Ableitungsrelation `. Die Aussage γ, T ` m sagt also aus: Gegeben ein Textmodell γ und eine Typenbindung T : Id ×Id → Type×{Inp, Outp}, die jeder Komponenten-Variable einen Typen und eine Richtung (Eingabe oder Ausgabe) zuordnet, lässt sich das GTL-Modell m ∈ LGT L ableiten. Die Ableitung `C leitet den Kontrakt von Modellen sowie die Initialisierungswerte ihrer Variablen her. Die Aussage γ, T `C (c, d) gibt also an: Gegeben eine textuelle Repräsentation γ und eine Typbindung T lässt sich der Kontrakt(LTL-Formel) c und die Initialisierung d : Id → Val ableiten. Formeln werden mithilfe von `V abgeleitet. Diese Relation gibt an, ob sich ein Ausdruck zu einer Formel eines bestimmten Typs ableiten lässt. Die Aussage e, T, γ `V (f, t) sagt also aus, dass gegeben die Typisierung T , einer Variablenbindung γ und den Ausdruck e sich die LTL-Formel f vom Typ t herleiten lässt. Um die korrekte Typisierung von Modellen sicher stellen zu können, benötigt man noch eine weitere Ableitungsart: `T gibt an, ob eine synchrone Komponente, die durch die übergebenen Parameter spezifiziert wird, eine gegebene Typisierung erfüllt und einem gegebenen Automaten entspricht. Die Aussage scade, („model.scade“) `T T, a bedeutet also: Das Scade-Modell „model.scade“ erfüllt die Typenbindung T und entspricht dem synchronen Automaten a. Da diese Ableitung abhängig vom synchronen Formalismus ist, wird sie hier nicht explizit angegeben. Zunächst wird definiert, wie sich ein leeres Textmodell herleiten lässt; die Komponenten und Verbindungen sind leer, die zu verifizierende Formel ist >, also immer wahr. empty , T ` (∅, ∅, >) Damit eine Modelldeklaration gültig ist, muss sich der Inhalt des Kontraktes c mit `C ableiten lassen und das referenzierte synchrone Modell wohlgetypt im Verhältnis zum 41 5. GTL – GALS Translation Language restlichen Modell sein: model α, T ` (ms, cs, vs) c, {(v, t, d) | (n, v, t, d) ∈ T } `C f, d β, args `T T, a model[β] n(args) {c} α, T ` (ms ∪ {(n, a, f, d)}, cs, vs) Eine Deklaration einer Verbindung ist gültig, wenn beide Variablen den gleichen Typ besitzen. Außerdem muss die erste Variable eine Ausgabe-, die andere eine EingabeVariable sein. connect α, T ` (ms, cs, vs) T (cmf , cvf ) = (t, Outp) T (cmt , cvt ) = (t, Inp) connect cmf .cvf cmt .cvt ; α, T ` (ms, cs ∪ {(cmf , cvf , cmt , cvt )}, vs) Eine Verifikationsblock muss sich mit `C zu einer LTL-Formel ableiten lassen. Gibt es mehr als einen Block, so werden die Formeln per Konjunktion zusammengefasst. verify α, T ` (ms, cs, vs) c, T `C (f, ∅) verify {c} α, T ` (ms, cs, vs ∧ f )) Ein leerer Kontrakt erlaubt dem Prozess jedes Verhalten und wird daher durch die LTLFormel > repräsentiert. -contract , T `C (>, ∅) Eine gültige Initialisierungsdeklaration liegt dann vor, wenn der Typ der initialisierten Variable dem des Wertes entspricht. init α, T `C (c, i) d ∈ T (c) init v d; α, T `C (c, i ∪ {(v, d)}) Eine Kontraktformel ist gültig, wenn sie sich erfolgreich zu einer LTL-Formel des Typs „bool“ ableiten lässt. Zu beachten ist, dass das Schlüsselwort contract auch weggelassen werden kann. α, T `C (c, i) e, T, ∅ `V (f, bool) formula contract e; α, T `C (c ∧ f, i) Eine Variable kann zwei verschiedene Bedeutungen haben: Entweder ist sie eine durch einen ∃-Quantor gebundene Variable (1) oder eine normale qualifizierte oder unqualifizierte Variable. In beiden Fällen ist der Typ des Ausdrucks der Typ der Variable. var(1) (v, q, n) ∈ γ T (q) = t v, T, γ `V (q n , t) var(2) ¬∃q, n : (v, q, n) ∈ γ T (v) = t v, T, γ `V (v, t) Die Regeln für die logischen Konnektoren not, true, false, and, or und implies sind genau 42 5.3. Operationelle Semantik wie in der normalen Logik definiert: not true false e, T, γ `V (e0 , bool) not e, T, γ `V (¬e0 , bool) true, T, γ `V (>, bool) false, T, γ `V (⊥, bool) and or impl e1 , T, γ `V (e01 , bool) e2 , T, γ `V (e02 , bool) e1 and e2 , T, γ `V (e01 ∧ e02 , bool) e1 , T, γ `V (e01 , bool) e2 , T, γ `V (e02 , bool) e1 or e2 , T, γ `V (e01 ∨ e02 , bool) e1 , T, γ `V (e01 , bool) e2 , T, γ `V (e02 , bool) e1 implies e2 , T, γ `V (¬e01 ∨ e02 , bool) Der ∃-Quantor bindet eine neue Variable an die aktuelle Version einer existierenden. Das bedeutet, dass auf frühere Werte einer Variable zurück gegriffen werden kann, wenn die Variable in einem next-Kontext verwendet wird. exists f, T, γ ∪ {(u, e, 0)} `V f 0 exists u=e: f, T, γ `V f 0 Der next-Operator wird in sein LTL-Äquivalent übersetzt. Allerdings müssen die gebundenen Variablen auf den neuen Kontext angepasst werden, indem sie auf einen Wert früher in der Geschichte referenziert werden. f, T, {(u, e, n + 1) | (u, e, n) ∈ γ} `V (f 0 , bool) next next f, T, γ `V (f 0 , bool) Tritt ein always-Operator auf, so werden für die Ableitung der Unterformel alle Bindungen entfernt, da gebundene Variablen nicht innerhalb eines always-Operators auftauchen dürfen. f, T, ∅ `V (f 0 , bool) always always f, T, γ `V (¬(>U (¬f 0 )), bool) Der finally-Operator ist nur eingeführt, um die Aussage „Innerhalb der nächsten i Schritte passiert f “ einfacher zu formulieren. Er kann rekursiv mithilfe des or- und next-Operators definiert werden. f, T, γ `V f 0 finally-0 finally0 f, T, γ `V f 0 finally-i f or (next finally(i − 1) f ), T, γ `V f 0 finallyi f, T, γ `V f 0 Gleichheitstests können auf allen Variablen gleichen Typs durchgeführt werden. f, T, γ `V (f 0 , t) g, T, γ `V (g 0 , t) equal f = g, T, γ `V (f 0 = g 0 , bool) nequal f, T, γ `V (f 0 , t) g, T, γ `V (g 0 , t) f !=g, T, γ `V (¬(f 0 = g 0 ), bool) 43 5. GTL – GALS Translation Language Während Datentyp-spezifische Relationen wie < oder > nur auf speziellen Typen ausgeführt werden können (in diesem Fall „int“). f, T, γ `V (f 0 , int) g, T, γ `V (g 0 , int) lesser f < g, T, γ `V (f 0 < g 0 , bool) greater f, T, γ `V (f 0 , int) g, T, γ `V (g 0 , int) f > g, T, γ `V (f 0 > g 0 , bool) Statt Variablen können für Integer auch Konstanten verwendet werden. const n∈N n, T, γ `V (n, int) Einfache arithmetische Operationen werden ebenfalls unterstützt. plus f, T, γ `V (f 0 , int) g, T, γ `V (g 0 , int) f +g, T, γ `V (f 0 + g 0 , int) f, T, γ `V (f 0 , int) g, T, γ `V (g 0 , int) minus f -g, T, γ `V (f 0 − g 0 , int) times div f, T, γ `V (f 0 , int) g, T, γ `V (g 0 , int) f *g, T, γ `V (f 0 · g 0 , int) f, T, γ `V (f 0 , int) g, T, γ `V (g 0 , int) 0 f /g, T, γ `V ( fg0 , int) Um die Aussage „Variable v hat den Wert s1 oder s2 oder. . . “ abzukürzen, kann man eine Menge von Werten angeben, die die Variable annehmen darf. elem ∀i ∈ {0, . . . , n} : si , T, γ `V (s0i , t) T (v) = t W v in {s0 , . . . , sn }, T, γ `V ( i∈{0,...,n} v = s0i , bool) 5.3.4. Interpretation als GALS-System Gegeben ein GTL-Modell (m, c, v) kann man ein GALS-System (A, p, C) konstruieren, das die synchronen Komponenten enthält. Die Automatenmenge enthält die Namen aller Komponenten des GTL-Modells. A = {name | (name, _, _, _) ∈ m} Die Abbildung der Automatennamen auf Automaten ordnet den synchronen Automaten zu. a (n, a, _, _) ∈ m p(n) = ⊥ sonst 44 5.3. Operationelle Semantik Für die Verbindungen benötigt man eine Hilfsfunktion r : Id × Id → N, die einer Komponentenvariable die entsprechende Position im Eingabe-/Ausgabe-Tupel zuordnet. C = {(af , r(af , vf ), at , r(at , vt ) | (af , vf , at , vt ) ∈ c} Um das GALS-System zu erhalten, das das Zusammenspiel der Kontrakte repräsentiert muss man statt der synchronen Automaten die mithilfe des in Abschnitt 3.5 angebenen Algorithmus übersetzten LTL-Kontraktformeln verwenden. 5.3.5. Interpretation der Kontrakte als GALS-System Anstatt die synchronen Automaten der Komponenten für die Komposition des GALSSystems zu verwenden, ist es auch möglich, die Kontrakte wie in Abschnitt 2.3 erläutert als „obere Schranke“ für das System-Verhalten zu verwenden. Da jeder Kontrakt durch eine LTL-Formel beschrieben wird, lässt sich mit dem Algorithmus aus Abschnitt 3.5 ein äquivalenter Büchi-Automat konstruieren. Dieser enthält in jedem Zustand eine Menge von atomaren Aussagen, die gelten müssen, damit der Zustand betreten werden darf. Für eine Menge von Variablen V und die Typzuordnung T : V → Type definiert jede atomare Aussage f eine Menge von Belegungen l(f ), die die entsprechende Aussage erfüllen. Beispiel 10. Für die atomare Aussage x ≤ y ergibt sich mit der Variablenmenge {x, y} und der Typzuordnung T = {(x, N), (y, N)} die Zuordnungen {{(x, 0), (y, 0)}, {(x, 0), (y, 1)}, {(x, 0), (y, 2)}, . . . , {(x, 1), (y, 1)}, {(x, 1), (y, 2)}, . . . } Gegeben ein Büchi-Automat (Q, Σ, δ, µ, q0 , ∅) lässt sich ein äquivalenter nicht-deterministischer Mealy Automat (Q ∪ {init}, Σ, Ω, δ 0 , {init}) erzeugen mit: ∀q, q 0 ∈ Q : q 6= init ⇒ (qδq 0 ∧ µ(q 0 ) = p) ⇔ (∀a ∈ l(p) : (q, inp(a))δ 0 (q 0 , outp(a))) ∀q ∈ q0 : µ(q) = p ⇔ (∀a ∈ l(p) : (init, inp(a))δ 0 (q, outp(a))) wobei inp den Vektor von Eingaben aus einer Belegung extrahiert und outp den Vektor von Ausgaben. 45 6. Übersetzung Dieser Abschnitt beschäftigt sich mit der Übersetzung der GTL in verschiedene Zielformalismen. Im Rahmen dieser Arbeit wurden drei verschiedene Übersetzungsmethoden entwickelt und zwei Optimierungsstrategien implementiert: • Die erste Übersetzungsmethode verwendet die von SCADE generierten C-Modelle um ein Gesamtsystem in Promela zusammen zu setzen. • Die Kontrakte der Komponenten können mithilfe des SCADE-Design-Verifiers überprüft werden. • Das Kontraktsystem kann nach Promela übersetzt werden. – Statische BDD können verwendet werden, um den Zustandsraum bei der Verifikation zu verkleinern. – Dynamische BDD dienen dem selben Zweck, haben aber weniger Einschränkungen. Zunächst wird eine allgemeine Konstruktion angegeben, mit der ein GALS-System nach Promela übersetzt werden kann. Diese wird dann verwendet, um die anderen Übersetzungsmethoden zu erklären. Dazu verwendet die allgemeine Konstruktion drei Übersetzungsfunktionen JKC , JKA und JKD , die von den konkreten Übersetzungen bereit gestellt werden müssen. Da die SCADE-Übersetzung keinen Promela-Code generiert, verwendet sie als einzige Übersetzung auch nicht die allgemeine Übersetzungsmethode. 6.1. Übersetzungskonstruktion Gegeben ein wie in Abschnitt 5.3.2 spezifiziertes System s ∈ S mit s = (ms, cs, vs) muss nun eine Übersetzung in ein äquivalentes Promela-Modell gefunden werden, die die Semantik des Systems erhält. Für jede Komponente (m, (contr , init)) ∈ ms mit dem Namen m, dem Kontrakt contr und der Initialisierung init wird nun der Kontrakt in einen äquivalenten Büchi-Automaten (Q, Σ, δ, µ, q0 , ∅) übersetzt (Siehe Abschnitt 3.5). Hierbei ist zu beachten, dass die generierten Automaten Bedingungen auf den Variablen als Ein- und Ausgabesymbole verwenden, da Relationen wie x ≤ y von dem LTL-Übersetzungsalgorithmus als atomare Aussagen betrachtet werden. 47 6. Übersetzung Für die Übersetzung werden die Funktionen JKC und JKA benötigt. Die Funktion JKC generiert aus den übergebenen Atomen einen Promela-Ausdruck, der abhängig vom globalen Zustand testet, ob alle Bedingungen, die durch die übergebenen Atome an die EingabeVariablen gestellt sind, erfüllt sind. Diese Anweisung muss blockieren, bis die Bedingungen erfüllt sind und muss seiteneffektfrei sein, den globalen Zustand also nicht verändern. Ähnlich dazu generiert die Funktion JKA eine Anweisung, die die Ausgabevariablen entsprechend den übergebenen Atomen anpasst und damit den globalen Zustand verändert. Die generierte Anweisung darf niemals blockieren. Für jede Komponente wird nun ein äquivalenter Prozess wie folgt definiert: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Quelltext 6.1: Komponenten-Übersetzung als Promela-Prozess proctype m ( ) { i f [∀i ∈ q0 : : : atomic { Jµ(i)KC ; Jµ(i)KA ; goto s t _ i } ] fi ; [∀q ∈ Q : s t _ q : i f [∀q 0 ∈ Q, qδq 0 : : : atomic { Jµ(i)KC ; Jµ(i)KA ; g o t o s t _ q0 } ] fi ; ] } Die zu verifizierende Formel v wird negiert ebenfalls in einen Büchi-Automaten (Q, Σ, δ, µ, q0 , F ) übersetzt und in eine äquivalente Promela never-Deklaration übersetzt: Quelltext 6.2: Verifikationsziel-Übersetzung als never-Prozess 1 2 3 4 5 6 7 8 9 10 never { i f [∀i ∈ q0 : : : atomic { Jµ(i)KC ; goto s t _ i } ] fi ; [∀q ∈ Q : [q ∈ F : a c c e p t _ q : ] 48 6.1. Übersetzungskonstruktion 11 12 13 14 15 16 17 18 19 st_q : i f [∀q 0 ∈ Q, qδq 0 : : : atomic { Jµ(q 0 )KC ; g o t o s t _ q0 } ] ] } Um den Initialzustand zu erreichen, wird vor dem Starten aller Prozesse noch eine Initialisierung durchgeführt. Hierfür muss die konkrete Übersetzung die Funktion JKD bereit stellen. Diese generiert, gegeben eine Komponente, eine Variable und einen Initialisierungswert für die Variable, eine Anweisung, die den globalen Zustand so verändert, dass die Variable nun den entsprechenden Wert besitzt. Quelltext 6.3: Initialisierungsprozess 1 2 3 4 5 6 7 8 9 10 11 12 init { [∀(m, a, f, d) ∈ ms : [∀(v, val) ∈ d : Jm, v, valKD ] ] atomic { [∀(m, a, f, d) ∈ ms : run m ( ) ; ] } } 6.1.1. Korrektheit der Übersetzung Um zu beweisen, dass die angegebene Promela-Übersetzung korrekt ist, muss gezeigt werden, dass eine Semantik des Systems, repräsentiert durch eine Untermenge des vollständigen Transitionssystems T 0 ⊆ T (Siehe Abschnitt 2.2), mit der Semantik des übersetzten Promela-Modells bisimular ist. Um dies zu zeigen, wird die Promela-Semantik verwendet, wie sie in [GMP04] beschrieben ist. Da in dieser Semantik gefordert ist, dass jede Anweisung ein implizites Label erhält, werden folgende Labels für die Anweisungen in Quelltext 6.1 (Seite 48) vergeben: • Die If -Anweisung in Zeile 2 erhält das Label Start. • In diesem Zweig wird der Ausdruck in Zeile 4 mit dem Label CI_i versehen. 49 6. Übersetzung • Die nachfolgende Zuweisung in Zeile 5 wird mit dem Label AI_i gekennzeichnet. • Der If-Zweig in Zeile 12 kann über das Label C_q_q 0 angesprungen werden. • Die darauf folgende Anweisung in Zeile 13 bekommt das Label A_q_q 0 zugewiesen. Mit diesen Kennzeichnungen ergibt sich nun die next-Funktion der Semantik wie folgt: L CI_q A_q C_q_q 0 A_q_q 0 next(L) A_q st_q A_q_q 0 st_q 0 Auch die erforderliche g-Funktion, die die Zweige einer If -Anweisung angibt, kann somit hergeleitet werden als: L Start st_q g(L) {CI_i | i ∈ q0 } {C_q_q 0 | q 0 ∈ Q, qδq 0 } Für den Ausführungsmodus(mode) ergibt sich: L Start CI_i AI_i st_q C_q_q 0 A_q_q 0 mode(L) ilv atm atm ilv atm atm Zunächst ist es nützlich ein paar allgemeine Aussagen aufzustellen, die die Verifikation der Korrektheit der Übersetzung erleichtern. Es ist leicht einzusehen, dass für alle Prozesse die Umgebung φe gleich ist, da die Prozesse keine lokalen Variablen deklarieren. Daher kann für die Betrachtung der Gesamtumgebung des Promela-Systems die Umgebung eines beliebigen Prozesses herangezogen werden. Um nun zu zeigen, dass das definierte GTL-System (ms, cs, vs) bisimilar zum übersetzten Promela Modell ist, werden folgende Anforderungen an die Semantik gestellt: 1. Es muss eine Bijektion i zwischen Zuständen der Verbindungen sowie Eingaben s ∈ SC (G) × I(G) und der Promela-Umgebung σe existieren. 2. Die Definitionen JαKD müssen eine initiale Umgebung σe0 definieren, die isomorph zum Initialzustand α ist: i(α) = σe0 (6.1) 50 6.1. Übersetzungskonstruktion 3. Befinden sich beide Systeme in isomorphen Zuständen, so wird der von JKC erzeugte Ausdruck genau dann wahr, wenn es im abstrakten Modell einen entsprechenden Übergang zwischen den Zuständen gibt: exec(Jµ(q 0 )KC , σe ) ⇔ i(s, β) = σe ⇒ ∀q 0 ∈ Qa : (6.2) a a a a 0 (∀q ∈ Q : δ (q, (s| , β| )) = (q , o)) 4. Die Anweisung, die von JKA generiert wird, darf nie blockieren und muss isomorphe Zustände beibehalten: Jµ(q 0 )KA 0 0 / hσe , L i hσe , Li ⇔ 0 a (6.3) i(s, β) = σe ⇒ ∀q ∈ Q : (∀q ∈ Qa : δ a (q, (s|a , β|a )) = (q 0 , o) ∧ 0 i(s[a 7→ o], β[a 7→ o]) = σe ) Nun kann man die Relation ∼ = angeben, die Zustände des abstrakten Modells mit Zuständen des übersetzten Promela-Modells in Relation setzt. Diese wird wie folgt definiert: Zwei Zustände stehen genau dann in Relation, wenn ihre globalen Zustände isomorph sind und sich jeder Prozess des Promela-Modells am Label befindet, das mit dem Zustand im abstrakten Modell korrespondiert, oder sich am Label Start befindet und der abstrakte Prozess im Zustand init ist. (q0 , . . . , qN , c0 , . . . ) ∼ =γ ⇔ i((c0 , . . . )) = γ(0).σe ∧ ∀j ∈ {1 . . . N } : (γ(j).σl = st_qj ∨ (γ(j).σl = Start ∧ qj = init)) Nun muss gezeigt werden, dass es sich bei der eben definierten Relation tatsächlich um eine Bisimulationsrelation handelt. Hierfür muss nachgewiesen werden, dass es für jede Transition, die ein bisimilarer Zustand durchführen kann, eine Transition des anderen Zustands gibt und die Zielzustände der beiden Transitionen auch wieder bisimilar sind. Betrachten wir also einen Zustand des abstrakten Modells s = (q0 , . . . , qN , c0 , . . . ) und einen Zustand des Promela-Modells γ. Sind diese Zustände bisimular, so gilt nach Konstruktion i((c0 , . . . )) = γ(0).σe und für jeden Prozesszustand qj entweder γ(j).σl = st_qj 51 6. Übersetzung oder γ(j).σl = Start ∧ qj = init Nun wird gezeigt, dass falls einer dieser Zustände eine Transition in einen neuen Zustand zulässt, es einen Übergang im abstrakten Modell gibt, der in einen Zustand führt, der mit dem neuen Zustand des Promela-Modells bisimilar ist. Gilt der erste Fall, so lässt sich herleiten exec(C_qj _qj0 , γ(j).σe ) Basic-proc next(C_qj _qj0 ) = A_qj _qj0 Jµ(qj0 )KC / proc γ(j).σe , A_qj _q 0 γ(j).σe , C_qj _qj0 j IfDo-proc Jµ(qj0 )KC / proc hγ(j).σe , st_qj i γ(j).σe , A_qj _qj0 Weiterhin ist nach Voraussetzung bekannt, dass die Anweisung Jµ(qj0 )KA nie blockieren darf, also lässt sich herleiten Jµ(qj0 )KA 0 / proc σ , st_q 0 γ(j).σe , A_qj _qj0 e j Aus der Promela-Semantik lässt sich nun für ein γ mit γ(j).σl = st_qj herleiten: Single-int Jµ(qj0 )KC / proc hγ(j).σe , st_qj i γ(j).σe , A_qj _qj0 0 Jµ(q )KC γ j / int γ 0 Atm-mod mode(C_qj _qj0 ) = atm atm γ j / mod γ 0 wobei γ 0 = γ[ γ(j).σe , A_qj _qj0 /j]. Nach der gleichen Regel lässt sich auch herleiten Single-int Jµ(qj0 )KA 0 / proc σ , st_q 0 γ(j).σe , A_qj _qj0 e j Jµ(qj0 )KA / γ 00 int γ0 Atm-mod γ0 atm j / mode(A_qj _qj0 ) = atm mod γ 00 wobei γ 00 = γ[ σe0 , st_qj0 /j]. Nun lassen sich die beiden atomaren Transitionen zusammenfassen: atm atm j 00 / γ j / mod γ 0 γ0 mod γ Atm-sim 52 atm γ j / sim γ 00 6.2. Promela-C-Integration GTL Scade Promela C Verifier Abbildung 6.1.: Simulation durch C-Integration von Promela Fasst man nun alle hier angegebenen Ableitungsschritte zusammen, so ergibt sich exec(C_qj _qj0 , γ(j).σe ) Jµ(qj0 )KA 0 / proc σ , st_q 0 γ(j).σe , A_qj _qj0 j e atm γ j / sim γ[ σe0 , st_qj0 /j] Diese zwei Vorbedingungen sind nach Gleichung 6.2 und 6.3 genau dann erfüllt, wenn δ a (qj , ((c0 , . . . )|a , β|a )) = (qj0 , o) gilt. Dies ist äquivalent dazu dass λ(((q0 , . . . , qj , . . . , qN ), c), j, β) = (((q0 , . . . , qj0 , . . . , qN ), c[a 7→ o]), (⊥, . . . , ⊥)[a 7→ o]) gilt. Nach Gleichung 6.3 gilt außerdem, dass der veränderte globale Zustand σe0 isomorph zum abstrakten Zustand c[a 7→ o] ist: i(c[a 7→ o], β) = σe0 womit die Bisimularität für diesen Fall gezeigt ist, denn i(c[a 7→ o], β) = σe0 ∧ ((q0 , . . . , qj , . . . , qN ), c) ∼ =γ ⇒ ((q0 , . . . , qj0 , . . . , qN ), c[a 7→ o]) ∼ = γ[ σe0 , st_qj0 /j] Der Beweis für den Fall, dass sich der Prozess am Label Start befindet ist, bis auf Änderung der Label-Namen analog und daher ausgelassen. 6.2. Promela-C-Integration Diese Übersetzungsmethode führt keine Optimierungen oder Abstraktionen durch, sondern simuliert das Modell exakt so, wie durch die Spezifikation angegeben. Die Kontrakte für die synchronen Komponenten werden also ignoriert. Um die Komponenten zu simulieren reichen die Informationen in der GTL-Spezifikation nicht aus, denn die Komponenten 53 6. Übersetzung sind hier nur als Referenzen vorhanden. Die eigentliche Implementierung findet in den synchronen Formalismen statt, also in dieser Arbeit in SCADE. Die SCADE-Komponenten müssen also nach Promela übersetzt werden, damit das Gesamtmodell simuliert und verifiziert werden kann. Eine direkte Übersetzung ist zwar prinzipiell möglich, aber aufgrund der Mächtigkeit der SCADE Sprache mit sehr viel Aufwand verbunden. Einfacher ist es, die von SCADE angebotene C-Übersetzung zu verwenden und den generierten C-Code in Promela einzubinden, was SPIN seit Version 4 erlaubt. Zunächst wird jede synchrone Komponente mit Hilfe des SCADE-Compilers nach C übersetzt. Da der Übersetzungsprozess für jede Komponente einzeln durchgeführt werden muss, muss darauf geachtet werden, dass die Modelle keine Namenskonflikte aufweisen oder die gleichen Sub-Komponenten enthalten [Hen09]. Der Code-Generator generiert nun für jede Komponente zwei Datenstrukturen, die erste enthält die Eingabevariablen und erhält das Namensprefix „inC_“, die zweite enthält die Ausgabevariablen sowie den internen Zustand der Komponente und ist mit dem Prefix „outC_“ versehen. Außerdem werden zwei Funktionen erstellt: Die erste initialisiert den internen Zustand der Komponente und hat das Suffix „_reset“, die zweite führt einen einzelnen Berechnungsschritt der Komponente durch und ist genau wie die Komponente benannt. Die Übersetzung geht nun wie folgt vor: Zunächst wird für jeden Prozess eine Instanz der Zustands-Datenstruktur zum globalen Zustandsvektor hinzugefügt. Dies geschieht über die Verwendung des „c_state“-Konstruktes in Promela. Eine Komponente „Engine“ würde beispielsweise den folgenden Code generieren: c _ s t a t e " outC_Engine Engine_state " " Global " Dies fügt dem Zustandsvektor die Variable „Engine_state“ hinzu. Das Schlüsselwort „Global“ führt dazu, dass auf die Variable von jedem Prozess aus zugegriffen werden kann. Die Eingabe-Datenstruktur der Komponenten ist für den Zustand des Gesamtsystems nicht entscheidend, sondern wird nur benötigt, um die Eingabesignale der Komponente vor dem Berechnungsschritt zu sammeln. Deswegen ist es ausreichend, die Struktur mit dem „c_decl“-Konstrukt zu deklarieren: c_decl { inC_Engine Engine_input ; } Zur korrekten Simulation der synchronen Komponente müssen nun folgende Dinge geschehen: 1. Am Anfang der Simulation muss die Zustands-Datenstruktur der Komponente initialisiert werden. Hierfür generiert der Code-Generator eine „reset“ Funktion: c_code { 54 6.2. Promela-C-Integration } E n g i n e _ r e s e t ( & now . E n g i n e _ s t a t e ) ; 2. In jedem Berechnungsschritt müssen die Eingaben für die Komponente aus den Ausgabe-Datenstrukturen der verbundenen Komponenten kopiert werden. c_code { E n g i n e _ i n p u t . power = now . P o w e r _ s t a t e . on ; E n g i n e _ i n p u t . mode = now . S p e e d R e g u l a t o r _ s t a t e . o u t p u t ; } 3. Die generierte Schrittfunktion der Komponente muss aufgerufen werden. c_code { E n g i n e ( & E n g i n e _ i n p u t , & now . E n g i n e _ s t a t e ) ; } Die gesamte Übersetzung des vorgestellten Modells sieht also so aus: Quelltext 6.4: C-Integration-Beispiel c_code { \# include " Engine . h " } c _ s t a t e " outC_Engine Engine_state " " Global " c_decl { inC_Engine Engine_input ; } p r o c t y p e Power { . . . } proctype S p e e d R e g u l a t o r { ... } proctype Engine { c_code { E n g i n e _ r e s e t ( & now . E n g i n e _ s t a t e ) ; }; do : : c_code { E n g i n e ( & E n g i n e _ i n p u t , & now . E n g i n e _ s t a t e ) ; } od } 55 6. Übersetzung Problematisch bei der C-Übersetzung ist allerdings zu sehen, dass der übersetzte Code alle Ausgabevariablen der Komponente in den Zustandsraum der Komponente übernimmt, obwohl sie eventuell gar nicht für den Folgezustand der Komponente entscheidend sind. Das kann dazu führen, dass die Verifikation mit der C-Übersetzung mehr Zustände generiert als es eine äquivalente, auf direkter Übersetzung basierende Übersetzung tun würde. Das Problem kann für die C-Übersetzung nicht umgangen werden, da die Information, ob eine Ausgabevariable für den nächsten Zustand verantwortlich ist, tiefergehende Analysen des SCADE Modells erfordern würden, die auf einer quasi-Übersetzung des SCADE-Codes nach Promela gleich käme. 6.3. Native Promela Übersetzung Diese Übersetzung verwendet die Kontrakte, um die Komponenten zu repräsentieren, aber führt keinerlei Optimierungen durch. Jedes Atom in einem Zustand, dass eine Ausgabevariable o determiniert, wird in ein Einschränkungs-Tupel (lu , ll , va , vf , eeq , eneq ) übersetzt, wobei die Variablen folgende Bedeutung haben: • lu enthält alle Ausdrücke, die eine obere Schranke für die Variable angeben. Es gilt: ∀l ∈ lu : o < l • Analog enthält ll alle Ausdrücke, die eine untere Schranke darstellen. • va enthält eine Menge von Werten, die von der Variable angenommen werden dürfen. Es gilt: o ∈ va • Werte, die nicht angenommen werden dürfen, werden in vf gesammelt. o 6∈ vf • eeq gibt eine Menge von Ausdrücken an, die gleich der Variable seien müssen: ∀e ∈ eeq : o = e • Genauso gibt eneq eine Menge von Ausdrücken an, die ungleich zu der Variable sein müssen: ∀e ∈ eneq : o 6= e 56 6.3. Native Promela Übersetzung Wird eine Ausgabevariable von mehreren Atomen determiniert, so müssen die resultierenden Tupel zusammen geführt werden. Gegeben zwei Tupel T1 = (lu , ll , va , vf , eeq , eneq ) T2 = (lu0 , ll0 , va0 , vf0 , e0eq , e0neq ) ergibt sich das Tupel, dass beide Einschränkungen erfasst als T1 ⊕ T2 = (lu ∪ lu0 , ll ∪ ll0 , va ∩ va0 , vf ∪ vf0 , eeq ∪ e0eq , eneq ∪ e0neq ) Für eine Ausgabevariable o ergibt sich die Funktion t, die das entsprechende Einschränkungstupel berechnet durch t(o < e) = ({e}, ∅, ∅, ∅, ∅, ∅) t(o ≤ e) = ({e + 1}, ∅, ∅, ∅, ∅, ∅) t(o > e) = (∅, {e}, ∅, ∅, ∅, ∅) t(o ≥ e) = (∅, {e − 1}, ∅, ∅, ∅, ∅) t(o = e) = (∅, ∅, ∅, ∅, {e}, ∅) t(o 6= e) = (∅, ∅, ∅, ∅, ∅, {e}) t(o ∈ i) = (∅, ∅, i, ∅, ∅, ∅) t(o 6∈ i) = (∅, ∅, ∅, i, ∅, ∅) Enthält ein Atom keine Ausgabevariable, so wird es in einen äquivalenten PromelaAusdruck übersetzt. Enthalten mehrere Atome keine Ausgabevariable, so werden die resultierenden Ausdrücke per Konjunktion verknüpft. Eine Menge von Atomen a wird also in ein Tupel (p, e) aus einer Abbildung von AusgabeVariablen auf Einschränkungstupel p und einem Promela-Ausdruck e übersetzt. Die Übersetzung JKC liefert also nur den Promela-Ausdruck e, der die Bedingungen auf den Eingabe-Variablen repräsentiert: JaKC = e Beispielsweise wird die Bedingung x ≤ 10 ∧ x 6= y in den folgenden Promela-Code übersetzt: x <= 1 0 && x ! = y Die Übersetzung der Einschränkungstupel gemäß der Semantik JKA ist etwas komplizierter, da nicht-deterministisch alle möglichen Belegungen für die Ausgabevariable generiert werden müssen. Dazu wird zunächst die Ausgabevariable o auf die größte untere Schranke gesetzt, indem die Schranken miteinander verglichen werden. Existiert keine untere Schranke, so wird der kleinste Wert des Wertebereichs der Variable gewählt. Ansonsten wird mit Promela If -Anweisungen ein Entscheidungsbaum aufgebaut. Existieren also bei- 57 6. Übersetzung spielsweise die drei unteren Schranken ll = {e1, e2, e3}, so sieht die Übersetzung wie folgt aus: Quelltext 6.5: Berechnung der unteren Schranke 1 2 3 4 5 6 7 8 9 10 11 12 13 if :: :: e 1 < e2 ; i f : : e 1 < e3 ; o = e1 : : else ; o = e3 fi else ; i f : : e 2 < e3 ; o = e2 : : else ; o = e3 fi fi Danach wird in einer Do-Schleife die Ausgabevariable so lange hoch gezählt, bis eine der oberen Schranken erreicht wird. Für jeden Wert muss nun noch geprüft werden, ob er die restlichen Bedingungen des Einschränkungstupels erfüllt. Gilt also beispielsweise lu = {u1, u2} sowie vf = {f 1, f 2}, so ergibt sich der folgende Code: Quelltext 6.6: Generierung von möglichen Werten 14 do : : o < u 1 && o < u2 ; 15 o = o +1 16 : : o== f 1 | | o== f 2 ; 17 skip 18 : : else ; 19 break 20 od Da sich der gesamte Code-Block zur Ausgabe-Erzeugung innerhalb eines atomic-Blocks befindet und der Ablauf an keiner Stelle blockieren kann, läuft der gesamte Code in einem Schritt ab. Zusätzlich zu den Semantiken JKC und JKA muss noch die Bijektion i und JKD definiert werden (vgl. Seite 49). In dieser Übersetzung ist diese sehr einfach, da die Werte der Verbindungen direkt in Variablen gespeichert werden. Die Bijektion konstruiert also nur eine Zuordnung σe , die die Werte für jede Verbindung aus dem Zustands- und Eingabevektor extrahiert. Es gilt also: i((v0 , . . . , vn ), (vn+1 , . . . , vm )) = σe wobei def σe (j) = vj 58 6.3. Native Promela Übersetzung Da nur genau so viele Variablen deklariert werden, wie auch Verbindungen existieren ist es leicht einzusehen, dass i in der Tat bijektiv ist. Nun ist noch zu zeigen, dass i die verlangten Eigenschaften 2.-4. von Seite 50 aufweist. Für ein i(s, β) = σe muss nachgewiesen werden, dass die Äquivalenz aus (6.2) exec(Jµ(q 0 )KC , σe ) ⇔ (∀q ∈ Qa : δ a (q, (s|a , β|a )) = (q 0 , o)) für alle Zustände q 0 ∈ Qa erfüllt ist. Da Jµ(q 0 )KC eine Promela-Anweisung generiert, die nur ausgeführt werden kann, wenn die Belegungen in σe den Bedingungen aus µ(q 0 ) entsprechen, existiert auch nur in diesem Fall ein Übergang zu dem Zustand q 0 . Genauso kann die Anweisung Jµ(q 0 )KC nur dann ausführbar sein, wenn es einen entsprechenden Übergang gibt, da die Bedingungen aus dem Zustand q 0 hierfür alle erfüllt sein müssen. Desweiteren muss noch die Kompatibilität der Bijektion i mit der Semantik JKA gezeigt werden. Hierzu muss die Korrektheit der Äquivalenz aus Gleichung (6.3) nachgewiesen werden. Seien hierfür wieder s, β und σe mit i(s, β) = σe gegeben. Existiert nun ein Übergang im Promela-Modell mit hσe , Li Jµ(q 0 )KA / hσe0 , L0 i so ergibt sich, dass hierbei genau die Variablen verändert werden, die in µ(q 0 ) vorkommen und Ausgabe-Variablen sind. Nach der Konstruktion durch die Einschränkungstupel (Definition von JKA ) ergibt sich außerdem, dass die nicht-deterministischen Zuweisungen alle innerhalb der durch das für die Übersetzung gegebene GTL-Modell definierten Wertebereiche liegen. Umgekehrt sichert die Korrektheit der Übersetzung der Einschränkungstupel, dass für jeden Zustandsübergang des abstrakten Modells das Promela-Modell den gewünschten Übergang durchführen kann. Die Default-Werte für die Variablen werden mit der Semantik JKD definiert. Diese weist den angegebenen Verbindungen einfach den angegebenen Wert zu. Für die initiale Umgebung gilt dann: def JαKD = σe wobei σe (j) = α(j) es ist leicht einzusehen, dass diese Definition die Forderung aus Gleichung 6.1 erfüllt. 59 6. Übersetzung 6.4. SCADE Übersetzung Diese Übersetzungsmethode wird benutzt, um die Korrektheit der Kontrakte für synchrone Komponenten zu verifizieren. Dazu wird für jede Komponente ein SCADE-Observer erstellt, der dann im Design-Verifier der SCADE-Suite auf Korrektheit getestet werden kann. Der generierte Observer überwacht alle Ein- und Ausgänge der Komponente und gibt einen booleschen Ausgabefluss zurück, der wahr ist, wenn die geforderte Eigenschaft erfüllt ist und falsch ist, wenn sie verletzt ist. Der Design-Verifier kann nun benutzt werden, um sicher zu stellen, dass der ausgegebene Datenfluss immer wahr ist. Der Büchi-Automat, der die zu verifizierende Eigenschaft der Komponente darstellt, wird hierfür in eine SCADE-Zustandsmaschine übersetzt. Jeder Zustand der Maschine definiert einen Wert für die Resultat-Variable. Die Transitionen von Zustand A in Zustand B erhalten genau die Bedingungen, die in Zustand B gelten müssen. Zusätzlich zu den vom Büchi-Automaten vorgesehenen Transitionen erhält jeder Zustand noch eine Transition in den Fehlerzustand: In diesem hat die Resultat-Variable den Wert falsch. Die Transition ist von niedrigster Priorität, wird also nur dann geschaltet, wenn keine andere Transition schalten kann. 6.4.1. Korrektheit Um die Korrektheit der Übersetzung nach SCADE nachzuweisen kann die strukturelle operationelle Semantik von Lustre aus [CP95] verwendet werden. Da die momentane Übersetzungsmethode aber auf die Generierung von Automaten angewiesen ist, welche von der operationellen Semantik nicht behandelt werden, ist der Korrektheitsbeweis der Übersetzung für spätere Arbeiten weg gelassen. 6.5. Optimierungen 6.5.1. Abstraktion durch statische BDD Um die in der GTL-Spezifikation gegebenen Kontrakte für die Komponenten des GALSSystems für die Verifikation benutzen zu können, müssen diese in den Promela-Formalismus übertragen werden. Durch die LTL→Büchi-Übersetzung (Siehe Abschnitt 3.5) liegen die abstrahierten Komponenten bereits als Büchi-Automaten vor. Die Zustände der Automaten enthalten aber nicht nur konkrete Wertzuweisungen für die Variablen der Komponenten, sondern auch Einschränkungen der Wertebereiche, die sogar von anderen Variablen abhängen können (Zum Beispiel Relationen wie x < y + 3). Um zumindest Einschränkungen der Wertebereiche, die nicht von anderen Variablen abhängen (Zum Beispiel x < 5), übersetzen zu können, kann man BDDs benutzen. 60 6.5. Optimierungen Dazu wird jede Relation der Form xRc, wobei x eine Variable und c eine Konstante ist in ein BDD übersetzt. Diese Übersetzungsmethode ist somit eingeschränkt auf diese Art von Relationen. Relationen zwischen mehreren Ein- oder Ausgabe-Variablen sind daher nicht möglich. Das BDD kann nun statt der Werte, die es repräsentiert zur Verifikation verwendet werden. Es wird allerdings nicht das BDD selbst verwendet, sondern ein Identifier, der es repräsentiert. Da die Verifikation so allerdings keinen direkten Zugriff mehr auf die Information hat, welches BDD von einem Identifier repräsentiert wird, muss vor der Verifikation eine Tabelle erstellt werden, in der gespeichert wird, welche BDD kompatibel miteinander sind, das heißt, welche von den BDD repräsentierte Mengen Untermengen von welchen anderen BDD-Mengen sind, damit die Bedingungen von Zustandsübergängen geprüft werden können. Übersetzung Um die Übersetzung durch statische BDD zu beschreiben, müssen die Funktionen JKC , JKA sowie JKD definiert werden. Da die atomaren Aussagen auf Relationen mit genau einer Variable beschränkt sind, lässt sich eine Menge von atomaren Ausagen σ ⊆ Σ als eine Abbildung η von Variablen auf Entscheidungsdiagramme darstellen: η : Id → BDD Ist keine Relation für eine Variable angegeben, so ist die Variable unbeschränkt und durch das BDD repräsentiert, dass alle möglichen Werte enthält. Die Funktion JKC betrachtet die Eingabevariablen des übergebenen Semantiksymbols η, also die Menge {(v, η(v)) | v ∈ Inp i } Für jedes dieser Tupel wird nun ein Promela Ausdruck generiert, der überprüft, ob die Variable v mit einem zu η(v) kompatiblen BDD belegt ist. Ist die Variable also mit dem BDD f ∈ BDD belegt, so muss gelten: f ∩ η(v) 6= ∅ Die Anweisungen, die von der JKA -Funktion generiert werden, weisen den Ausgabevariablen der Komponente neue BDDs zu. Das bedeutet, dass für jedes Tupel der Menge {(v, η(v)) | v ∈ Out i } die Anweisung 61 6. Übersetzung v = η(v) ; generiert wird. Für jede Variable der Komponente wird durch JKD eine globale Integer Variable generiert, die die Repräsentation des entsprechenden BDDs enthält. Sind die Variablen von zwei Komponenten durch eine connect-Deklaration verbunden, so wird nur eine Variable für die Eingangsvariable generiert, damit das Modell nicht unnötig vergrößert wird. Schreibt der Ausgabeprozess auf seine Variable, so wird das Ergebnis stattdessen direkt in die gemeinsam verwendete Variable geschrieben. Probleme Die statische BDD Abstraktion hat das Problem, dass Relationen zwischen Variablen nicht möglich sind. Damit ist sie ungeeignet für die meisten realistischen Modelle, in denen es fast immer direkte Beziehungen zwischen Ein- und Ausgabevariablen gibt. Auf einen Beweis der Korrektheit der Übersetzung wird aus diesem Grund auch verzichtet. 6.5.2. Abstraktion durch dynamische BDD Um die Probleme der statischen BDD-Übersetzung zu lösen, bietet es sich an, die Berechnung der BDDs erst während der Verifikation durchzuführen. Der Vorteil ist, dass man nun auch mehrere Variablen miteinander in Beziehung setzen kann und auch Modelle verifizieren kann, die Zyklen enthalten. Es kann allerdings passieren, dass bei der Verifikation mehrfach die gleiche BDD Operation ausgeführt wird; dies lässt sich aber durch den Einsatz eines Operationscaches vermeiden. Für die Implementierung der dynamischen BDDs wurde die C-Bibliothek CUDD 12 gewählt, da diese im Gegensatz zu anderen Bibliotheken Zugriff auf ihre internen Schnittstellen bietet und somit sehr gut zu erweitern ist. Die Integration in SPIN erfolgt über die C-Schnittstelle, die es erlaubt, zur Berechnung von Folgezuständen C-Code zu verwenden. Wie auch bei der Abstraktion durch statische BDD werden die LTL-Formeln, die die Komponenten abstrahieren, in Büchi-Automaten übersetzt. Jede atomare Aussage, die eine Ausgabevariable enthält wird nun in die Form αRe gebracht, wobei α eine Ausgabevariable, R eine beliebige Relation und e ein Ausdruck, der keine Ausgabevariablen enthält ist. Enthält die atomare Aussage keine Ausgabevariable, so wird eine Testfunktion generiert, die prüft, ob die BDD der Eingabevariablen die Relation erfüllen. Ansonsten wird eine Funktion generiert, die zunächst das BDD für den Ausdruck e berechnet und dann einen BDD aller Werte berechnet, die die Relation erfüllen. Für die 12 Die Abkürzung steht für Colorado University Decision Diagram Package. Ein Benutzerhandbuch, sowie die Quellen sind online erhältlich [Som09]. Die Bibliothek ist unter einer BSD-artigen Lizenz veröffentlicht. 62 6.6. Fehlereingrenzung Relation = ist dieser BDD einfach der BDD für e, während er für 6= das Komplement ist. Ist die Relation beispielsweise >, so berechnet die Funktion das Minimum des BDDs für e und erstellt ein BDD, dass alle Werte, die größer sind enthält. 6.6. Fehlereingrenzung Das Ergebnis einer Verifikation, die die Kontrakt-Spezifikationen zur Optimierung verwendet, sind eine oder mehrere Fehlerspuren. Diese geben eine zeitlich geordnete Kette von Bedingungen über die Variablen der Komponenten des Systems. Ein Beispiel für eine solche Spur ist die Kette [(a < 3, b ∈ {3, 5, 6}), (a > 4), (b 6= 5)] Diese Kette von Bedingungen spezifiziert aber nicht ein Verhalten, sondern mehrere. Die folgenden Verhalten des Systems sind beispielsweise spezifiziert: [(a = 2, b = 3), (a = 6), (b = 1)] [(a = 1, b = 3), (a = 5), (b = 1)] Aus dieser Menge von spezifizierten Verhaltensweisen müssen nun nicht alle einen echten Fehler des Systems darstellen. Tatsächlich reicht es, wenn ein Verhalten einen Fehler hervorruft. Möglich ist aber auch, dass die Kontrakte dem System ein Verhalten erlauben, was das echte System niemals erzeugt. In diesem Fall ist die Spezifikation des Systems zu grob und die Fehlerspuren nicht immer echte Fehler. Um nun herauszufinden, welche konkreten Fehlerspuren das spezifizierte System nun tatsächlich hat, wird eine erneute Verifikation durchgeführt. Diesmal wird das echte Systemverhalten als Grundlage herangezogen, wobei aber das Verhalten auf die Spuren begrenzt wird, die durch die Fehlerspur angegeben werden. Meldet die Verifikation des so eingegrenzten Systems ebenfalls einen Fehler, so erhält man nicht nur die Bestätigung, dass die vorher erzeugte Fehlerspur echt ist, sondern auch ein konkretes Systemverhalten, dass zu einem Fehler führt. Zeigt sich kein Fehler, so kann dies ein Hinweis sein, dass nicht genügend scharfe Kontrakte formuliert wurden. Um diese Technik konkret umsetzen zu können, müssen an vielen Stellen der Implementierung Erweiterungen vorgenommen werden. Hierzu betrachten wir Abbildung 6.2: • Zunächst muss der aus der GTL-Spezifikation generierte Promela-Code in der Lage sein, Fehlerspuren zu erzeugen, die Aufschluss darüber geben, welche Transitionen des generierten Büchi-Automaten zu dem Fehler führten. Zwar erzeugt auch SPIN schon Fehlerspuren, jedoch geben diese Auskunft über die Ausführungsposition im Quelltext und sind daher extrem schwer zurück auf die Zustände des BüchiAutomaten zu rechnen. Die hier verwendete Lösung besteht darin, im generierten 63 6. Übersetzung GTL Scade Promela (Abstraktion) Verifier Promela (C-Integration) Fehlerspur (abstrakt) Promela (eingeschränkt) Verifier Fehlerspur Abbildung 6.2.: Fehlereingrenzung Quelltext vor jedem Betreten eines Zustands mit der printf -Anweisung eine Ausgabe zu erzeugen, die den Namen des betretenden Zustands enthält. Spielt man eine generierte Fehlerspur nun in SPIN ab, so erhält man genau die Ausgaben für die betretenden Zustände und kann eine genaue Fehlerspur für den Büchi-Automaten berechnen. • Die Fehlerspur wird kodiert als eine Liste, in der jedes Element einem Zeitschritt entspricht. Ein Element enthält eine Abbildung von den Variablen des Systems auf BDDs, die den erlaubten Wertebereich dieser Variablen zum Zeitpunkt des Elements festlegen. • Die so generierte Fehlerspur muss jetzt noch benutzt werden, um in der Verifikation per C-Integration die Pfade zu beschränken. Dies geschieht, indem die Schnittmenge des generierte Büchi-Automat für die zu verifizierende Eigenschaft und des Automaten aus der Fehlerspur gebildet wird. Damit werden im Modell nur Pfade verifiziert, die der Fehlerspur entsprechen. 64 7. Implementierung Die Implementierung besteht zum einen aus der eigentlichen Anwengung – gtl – und zum anderen aus verschiedenen Bibliotheken, die zusätzlich entwickelt werden. Diese sind: • language-promela – Stellt Datenstrukturen für den Promela-Syntax bereit, formatiert Promela-Quelltext für die Ausgabe und parst Promela-Code. • language-scade – Ein Parser und Code-Generator für den SCADE-Syntax. • bdd – Eine Bibliothek, die binäre Entscheidungsdiagramme („binary decision diagrams“ – BDD) implementiert. GTL Parser GTL AST Promela Type checker SCADE GTL Spec KCG Promela SCADE Testnode C CUDD Dynamic BDD Verifier Native Verifier SCADE Design Verifier Abbildung 7.1.: GTL Implementierung Abbildung 7.1 zeigt den Datenfluss der gtl-Anwendung. Zunächst wird mithilfe des Parsers eine textuelle GTL-Repräsentation in einen abstrakten Syntax-Baum13 transformiert. Der 13 englisch: abstract syntax tree, AST 65 7. Implementierung Parser wird im Abschnitt B.8, Modul „Language.GTL.Parser“ beschrieben, der SyntaxBaum in B.10, Modul „Language.Parser.Syntax“. Daraufhin wird der Syntax-Baum an die Typüberprüfung weiter gereicht. Diese extrahiert die Typinformationen aus den verwendeten synchronen Komponenten und überprüft, ob alle Kontrakte und Verifikationsformeln wohl-getypt sind (Siehe Abschnitt 5.3). Für das SCADE-Backend müssen also die Dateien mit den Beschreibungen der SCADE-Komponenten geparst werden, die Modelle in dem entstandenen Syntax-Baum gefunden werden und die SCADE-Typen in GTL-Typen umgewandelt werden. Daraufhin wird der Syntax-Baum in eine Instanz des GTLSpec-Datentyps umgewandelt (Beschrieben in Abschnitt B.7 und implementiert durch das Modul „Language.GTL.Model“). Ab hier entscheidet sich nun, welche Transformation vom Benutzer gewählt wurde. Für die SCADE-Verifikation der Komponenten wird für jede Komponente ein SCADE-Observer erzeugt, der dann zusammen mit dem Quelltext der Modelle mit dem SCADE Design-Verifier geprüft wird (Beschrieben in Abschnitt B.3, Modul „Language.GTL.Backend.Scade“). Die Verwendung von anderen synchronen Formalismen wird durch das Interface in Modul „Language.GTL.Backend“ in Abschnitt B.1 ermöglicht. Für die C-Übersetzung wird der SCADE Code-Generator KCG aufgerufen, der wie in Abschnitt 6.2 beschrieben C-Code für alle Komponenten liefert. Es wird dann Promela-Code generiert, der die einzelnen C-Code-Modelle vereint. Die Implementierung dieses Verfahrens wird in Abschnitt B.13, Modul „Language.GTL.PromelaCIntegration“ genauer beschrieben. Für die Übersetzung der Kontrakte mithilfe von binären Entscheidungsdiagrammen, wie in Abschnitt 3.6 beschrieben, wird Promela-Code generiert und dann gegen die CUDDBibliothek gelinkt. Das Verfahren wird im Modul „Language.GTL.PromelaDynamicBDD“ in Abschnitt B.14 genauer beschrieben. 7.1. Backend Die Backend-Implementierung stellt eine Möglichkeit zur Verfügung, synchrone Verifikationsformalismen in das GTL-Tool zu integrieren. Dazu steht die Klasse „GTLBackend“ (Siehe Abschnitt B.1) bereit. Abbildung 7.2 zeigt das UML-Diagramm des Paketes14 . Instanzen der Klasse müssen eine Funktion backendName bereit stellen, die den Namen des Backends zurück gibt (Diese Information wird benutzt um das richtige Backend für ein gegebenes Modell zu finden). Wird ein Modell gefunden, dass dieses Backend verwendet, so wird die Initialisierungs-Funktion initBackend mit den übergebenen Modellparametern aufgerufen. Diese muss eine Instanz des assoziierten Datentyps „GTLBackendModel“ zurück liefern. 14 Da UML nicht für die Modellierung funktionaler Sprachen wie Haskell ausgelegt ist, sind einige SyntaxElemente sehr liberal verwendet. Die Abbildung ist nicht als formal korrektes UML-Diagramm gedacht sondern als Orientierungshilfe. 66 7.1. Backend Language.GTL.Backend GTLBackendModel b:type GTLBackend b +backendName(): String +initBackend(args:[String]): IO (GTLBackendModel b) +typeCheckInterface(model:GTLBackendModel b, iface:ModelInterface): Either String ModelInterface +cInterface(mdl:GTLBackendModel b): CInterface +backendVerify(mdl:GTLBackendModel b, expr:Expr String Bool): IO (Maybe Bool) provides CInterface Scade All Scade AllBackend ModelInterface +cIFaceIncludes: [String] +cIFaceStateType: [String] +cIFaceInputType: [String] +cIFaceStateInit(args:[String]): String +cIFaceIterate(inp:[String],outp:[String]): String +cIFaceGetOutputVar(vars:[String],name:String): String +cIFaceGetInputVar(vars:[String],name:String): String +cIFaceTranslateType(tp:TypeRep): String +inputTypes: Map String TypeRep +outputTypes: Map String TypeRep Abbildung 7.2.: UML Diagramm der Backend-Implementierung Mit dem initialisierten Backend-Modell können nun verschiedene Aktionen ausgeführt werden: Typüberprüfung Die Funktion typeCheckInterface überprüft und vervollständigt eine gegebene Typbindung. Hierfür müssen die Typen der Variablen aus der BackendAbhängigen Beschreibungssprache extrahiert werden und mit den bereits im Modell spezifizierten Variablen verglichen werden. Existiert hierbei kein Konflikt, so werden die Typbindungen vereinigt und zurück gegeben. C-Übersetzung Mithilfe der Funktion cInterface wird ein Wert vom Typ „CInterface“ zurück gegeben, mit dem es möglich sein muss, das entsprechende Modell nach C zu übersetzen. Es wird hierbei davon ausgegangen, dass die generierte C-Struktur einer Zustandsmaschine ähnelt. Der Datentype „CInterface“ stellt hierbei folgende Informationen zur Verfügung: • Die einzubindenden Header-Dateien (cIFaceIncludes). • Die Typen der benötigten Zustandsvariablen (cIFaceStateType). • Die Typen der Eingabe-Variablen (cIFaceInputType). • Eine Funktion um einen Aufruf zu generieren, der die Zustandsmaschine initialisiert (cIFaceStateInit). Die Funktion erhält die Namen der Zustandsvariablen als Parameter. • Eine weitere Funktion um einen Aufruf zu generieren, der einen Schritt in der Zustandsmaschine durchführt (cIFaceIterate). Als Parameter werden die Namen von Zustands- und Eingabevariablen übergeben. 67 7. Implementierung Language.GTL.Model GTLSpec +gtlSpecModels: Map String GTLModel +gtlSpecVerify: Expr (String,String) Bool +gtlSpecConnections: [(String,String,String,String)] 1 * GTLModel +gtlModelContract: Expr String Bool +gtlModelBackend: AllBackend +gtlModelInput: Map String TypeRep +gtlModelOutput: Map String TypeRep +gtlModelDefaults: Map String (Maybe Dynamic) Abbildung 7.3.: UML-Diagramm der Modell-Implementierung • Funktionen um einzelne Variablen aus den Zustands- bzw. Eingabe-Variablen zu extrahieren (cIFaceGetOutputVar bzw. cIFaceGetInputVar). • Eine Funktion um GTL-Typen nach C zu übersetzen (cIFaceTranslateType). Lokale Verifikation Um die Korrektheit der Kontrakte für eine Komponente sicherzustellen kann die Funktion backendVerify verwendet werden. Gegeben eine LTLFormel muss diese in der Lage sein zu entscheiden, ob das Modell den Kontrakt einhält oder nicht. Dazu wird ein Wert vom Typ „Maybe Bool“ zurück gegeben. Ist der Wert „Nothing“, so ist die Verifikation unentscheidbar, ansonsten gibt der Bool-Wert an, ob die Verifikation erfolgreich war. 7.2. Modell Im Modul „Language.GTL.Model“ (Siehe auch Abschnitt B.7) wird das Datenmodell des GTL-Tools implementiert. Abbildung 7.3 zeigt die verwendeten Datenstrukturen. Die Klasse „GTLSpec“ stellt eine vollständig geparste und auf Typfehler überprüfte GTLDatei dar. Das Attribut gtlSpecModels enthält eine Abbildung von Modellnamen auf Werte vom Typ „GTLModel“ und repräsentiert alle in der Spezifikation angegebenen Modelle. Die Verifikationsformel ist im Attribut gtlSpecVerify angegeben. Die Formel ist über ein Paar von Strings spezifiziert, da alle Variablennamen qualifiziert sein müssen. Über das Attribut gtlSpecConnections werden die Variablenverbindungen spezifiziert und als Viertupel repräsentiert, wobei die Komponenten des Tupels folgende Bedeutung besitzen: 1. Name des Modells der Ausgabe-Variable. 2. Name der Ausgabe-Variable. 3. Name des Modells der Eingabe-Variable. 4. Name der Eingabe-Variable. 68 7.3. Ausdrücke Language.GTL.Expression v:type a:type Expr ExprVar ExprElem +new(varName:v,level:Integer): Expr v a +new(var:v,set:[Integer], isin:Bool): Expr v Bool ExprConst ExprNot +new(value:a): Expr v a +new(expr:Expr v Bool): Expr v Bool ExprBinInt +new(op:IntOp,lhs:Expr v Int, rhs:Expr v Int): Expr v Int ExprAlways +new(expr:Expr v Bool): Expr v Bool ExprBinBool +new(op:BoolOp,lhs:Expr v Bool, rhs:Expr v Bool): Expr v Bool ExprNext +new(expr:Expr v Bool): Expr v Bool IntOp ExprRel +new(rel:Relation,lhs:Expr v Int, rhs:Expr v Int): Expr v Bool BoolOp And Or OpPlus OpMinus OpMult OpDiv Implies Abbildung 7.4.: UML-Diagramm für Ausdrücke Jedes Modell enthält dabei die folgenden Attribute: • Der Kontrakt des Modells ist in dem Attribut gtlModelContract abgelegt. • Das Backend des Modells wird in dem Attribut gtlModelBackend gespeichert. Der Typ des Backends wird dabei aus dem in der Modell-Datei spezifizierten String hergeleitet. • Ein- und Ausgabe-Variablen mit ihren entsprechenden Typen sind in den Attributen gtlModelInput sowie gtlModelOutput hinterlegt. • Initialisierungswerte für Variablen sind in gtlModelDefaults zu finden. 7.3. Ausdrücke Ausdrücke werden in der GTL-Sprache für Kontrakte und Verifikationsziele verwendet. Ein Ausdruck wird durch eine Instanz des Datentyps „Expr“ repräsentiert, der über den Namenstypen der Variablen v und den Typen des Ausdrucks a parametrisiert ist. Abbildung 7.4 zeigt eine Übersicht über die involvierten Datentypen. Ein Ausdruck kann dabei eine der folgenden Formen annehmen: • Es kann sich um eine einfache Variable handeln (ExprVar). Diese wird mit ihrem Namen initialisiert und kann einen beliebigen Typen haben. 69 7. Implementierung • Konstanten können mit dem Konstruktor ExprConst erstellt werden. Die resultierenden Ausdrücke haben dann denselben Typen wie die Konstante selbst. • Arithmetik-Operationen über zwei Int-Ausdrücken werden durch den ExprBinIntKonstruktor ermöglicht. • Boolesche binäre Ausdrücke werden durch den ExprBinBool-Konstruktor repräsentiert. • Zwei numerische Ausdrücke lassen sich mit dem Konstruktor ExprRel in eine Relation setzten. Der resultierende Type ist dann Bool. • Die Aussage „Variable v ist (nicht) Element der Menge x“ lässt sich mit dem Konstruktor ExprElem realisieren. • Negation wird mithilfe des Konstruktors ExprNot bereit gestellt. • Die temporalen Operatoren „always“ und „next“ stehen durch die Konstruktoren ExprAlways und ExprNext bereit. 70 8. Fallbeispiele 8.1. Quelle-Senke Beispiel Dieses minimalistische Beispiel soll die grundsätzliche Funktionsweise des Verifikationsalgorithmus und der BDD-Optimierung erläutern und demonstrieren, wie durch die Optimierung der Zustandsraum von Modellen reduziert werden kann. Das System besteht aus zwei Komponenten: Die Quelle hat einen Ausgang und gibt die Sequenz der natürlichen Zahlen modulo 10 zurück, also 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, . . . Der Ausgang der Quelle ist mit der Eingabe der Senke verbunden. Diese prüft, ob der Wert an ihrem Eingang kleiner als 12 ist und setzt dann den Ausgang auf den Wert 0, andernfalls auf 1. Zu verifizieren ist in diesem Beispiel, dass der Ausgabewert der Senke stehts 0 ist. Für den Kontrakt bietet sich also an zu spezifizieren, dass die Quelle stets Werte produziert, die kleiner als 10 sind. Die Senke produziert für Werte kleiner 10 stets die Ausgabe 0, was sich ebenfalls als Kontrakt formulieren lässt. Die resultierende GTL-Beschreibung sieht wie folgt aus: Quelltext 8.1: Quelle-Senke Beispiel model [ s c a d e ] s o u r c e ( " s o u r c e _ s i n k . s c a d e " , " S o u r c e " ) { c o n t r a c t always o u t p < 1 0 ; i n i t outp 9 ; } model [ s c a d e ] s i n k ( " s o u r c e _ s i n k . s c a d e " , " S i n k " ) { i n i t outp 0 ; c o n t r a c t always ( i n p < 1 0 => o u t p = 0 ) ; } connect s o u r c e . outp s i n k . inp ; verify { always s i n k . o u t p = 0 ; } 71 8. Fallbeispiele Modus Native BDD Zustände 25 6 Transitionen 49 11 Speicherverbrauch 4,653 MB 4,653 MB Tabelle 8.1.: Quelle-Senke Verifikationsergebnisse Das Kontrakt-Automaten System, dass sich aus dieser Beschreibung ergibt ist in Abbildung 8.1 gezeigt. outp < 10 outp inp outp = 1 inp ≥ 10 outp Abbildung 8.1.: Quelle-Senke Kontrakt-Automaten Wenig erstaunlich ist nun das Ergebnis der Verifikation: Da die BDD-Abstraktion die Bedingung outp < 10 als eine Transition betrachtet, ist das resultierende Transitionssystem in der BDD-Verifikation bedeutend kleiner als in der C-Integration (Siehe Tabelle 8.1). 8.2. Mutex Beispiel Das Problem des gegenseitigen Ausschluss, besser bekannt unter der englischen Bezeichnung „mutual exclusion“ oder kurz mutex wurde erstmals 1965 von Edsger Dijkstra formalisiert [Dij65]. Zwei oder mehr Prozesse versuchen in diesem Problem, Zugriff auf eine Ressource zu erhalten, die aber nur einem Prozess zur Zeit zur Verfügung stehen darf. Die Ressource kann hierbei beispielsweise ein Ausgabegerät sein, dass bei gleichzeitiger Benutzung fehlerhafte Ausgaben produziert, oder eine Speicherstelle, die inkonsistent beschrieben wird, wenn zwei Prozesse gleichzeitig auf sie schreiben. Die Aufgabe ist nun, ein Protokoll zu entwerfen, wie die Prozesse sich die Ressource teilen können und trotzdem die folgenden Eigenschaften erfüllt sind: • Die Ressource wird immer nur von einem oder keinem Prozess zur Zeit verwendet. Diese Eigenschaft stellt eine so genannte Sicherheitseigenschaft15 dar, denn sie sagt aus, dass das System nie in einen unsicheren Zustand gerät. • Wenn ein Prozess die Ressource haben möchte, so wird er sie irgendwann auch erhalten. Diese Eigenschaft verhindert, dass man die Sicherheitseigenschaft einfach erfüllt, indem man die Ressource nie an einen Prozess vergibt. Es handelt sich 15 engl. safety property 72 8.2. Mutex Beispiel hierbei um eine so genannte Lebendigkeitseigenschaft16 , denn sie sagt aus, dass das System immer „am Leben“ bleibt, indem alle Prozesse bedient werden (kein Prozess muss ewig warten). Die Prozesse können sich in einem von vier Zuständen befinden: nc In diesem Zustand benötigt der Prozess die Ressource nicht. Alle Prozesse starten in diesem Zustand. acq Der Prozess würde die Ressource gerne nutzen, besitzt sie aber noch nicht. Kann ihm die Ressource gerade nicht zugeteilt werden, so muss er in diesem Zustand warten. cs Die Ressource befindet sich nun im Besitz des Prozesses. Solange er mit der Ressource arbeiten muss, bleibt er in diesem Zustand. rel Hier gibt der Prozess die Kontrolle über die Ressource wieder ab. In diesem Beispiel soll das Problem wie folgt gelöst werden: Ein Server ist für die Verwaltung der Ressource verantwortlich. Die Prozesse oder Klienten sind mit ihm verbunden und stellen, sobald sie sich im Zustand acq befinden, eine Anfrage an den Server. Dieser muss nun entscheiden, wie er die Ressource vergibt. Zunächst modellieren wir einen Prozess. Ein Prozess kommuniziert seinen aktuellen Zustand über die Enum-Variable st an den Server. Seine Eingabe-Variable proceed gibt an, ob es dem Prozess momentan erlaubt ist, die Ressource an sich zu nehmen. Quelltext 8.2: Mutex Client 1 model [ none ] c l i e n t ( ) { 2 input bool proceed ; 3 o u t p u t enum { nc , acq , cs , r e l } s t ; Am Anfang befindet sich der Prozess im Zustand nc: 4 init st ’ nc ; Das Verhalten des Prozesses wird nun über eine Zustandsmaschine angegeben. Der Prozess kann immer entweder im aktuellen Zustand verweilen, oder in den nächsten übergehen. Der Übergang vom Zustand acq zu cs ist allerdings nur möglich, wenn die Variable proceed gesetzt ist. 5 6 7 8 9 automaton { i n i t s t a t e nc { s t = ’ nc ; t r a n s i t i o n acq ; t r a n s i t i o n nc ; 16 engl. liveness property 73 8. Fallbeispiele 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 } }; } s t a t e acq { s t = ’ acq ; transition transition } state cs { s t = ’ cs ; transition transition } state rel { st = ’ rel ; transition } [ proceed ] cs ; [ ! proceed ] acq ; rel ; cs ; nc ; Der Server besitzt für jeden Prozess einen Eingang, der ihn über den aktuellen Zustand des Prozesses informiert. Außerdem kann er jedem Prozess die Ressource zuteilen, indem er den entsprechenden Eintrag in dem procouts-Array setzt. Quelltext 8.3: Mutex Server 27 model [ none ] s e r v e r ( ) { 28 i n p u t enum { nc , acq , cs , r e l } ^ 3 p r o c s t a t e s ; 29 output bool ^3 p r o c o u t s ; Am Anfang ist es allen Prozessen untersagt die Ressource zu besitzen: 30 init procouts [ false , false , f a l s e ] ; Danach gilt: Fordert der erste Prozess die Ressource an, so wird sie ihm genau dann überlassen, wenn sich Prozess zwei und drei nicht im kritischen Abschnitt befinden. Das gleiche gilt symmetrisch für die anderen beiden Prozesse. Gilt keiner der Fälle, so darf kein Prozess die Ressource besitzen. 31 32 33 34 35 36 37 38 39 always ( p r o c s t a t e s [ 0 ] = ’ a c q and p r o c s t a t e s [ 1 ] ! = ’ c s and p r o c s t a t e s [ 2 ] ! = ’ c s and p r o c o u t s = [ t r u e , f a l s e , f a l s e ] ) or ( p r o c s t a t e s [ 0 ] ! = ’ a c q and p r o c s t a t e s [ 1 ] = ’ a c q and p r o c s t a t e s [ 0 ] ! = ’ c s and p r o c s t a t e s [ 2 ] ! = ’ c s and p r o c o u t s = [ f a l s e , t r u e , f a l s e ] ) 74 8.2. Mutex Beispiel 40 41 42 43 44 45 46 47 } or ( p r o c s t a t e s [ 0 ] ! = ’ a c q and p r o c s t a t e s [ 1 ] ! = ’ a c q and p r o c s t a t e s [ 2 ] = ’ a c q and p r o c s t a t e s [ 0 ] ! = ’ c s and p r o c s t a t e s [ 1 ] ! = ’ c s and p r o c o u t s = [ f a l s e , f a l s e , t r u e ] ) or ( p r o c o u t s = [ f a l s e , f a l s e , f a l s e ] ) ; Nun werden die definierten Modelle instanziiert, wobei drei Instanzen des Klienten und eine Instanz des Servers angelegt wird. Quelltext 8.4: Mutex Instanzen 48 49 50 51 52 i n s t a n c e c l i e n t c0 ; instance c l i e n t c1 ; instance c l i e n t c2 ; instance server s ; Die Variablen der Instanzen müssen natürlich verbunden werden, damit eine Kommunikation stattfindet. Quelltext 8.5: Mutex Verbindungen 53 c o n n e c t c0 . s t s . p r o c s t a t e s [ 0 ] ; 54 c o n n e c t c 1 . s t s . p r o c s t a t e s [ 1 ] ; 55 c o n n e c t c 2 . s t s . p r o c s t a t e s [ 2 ] ; 56 57 c o n n e c t s . p r o c o u t s [ 0 ] c0 . p r o c e e d ; 58 c o n n e c t s . p r o c o u t s [ 1 ] c 1 . p r o c e e d ; 59 c o n n e c t s . p r o c o u t s [ 2 ] c 2 . p r o c e e d ; Das Verifikationsziel ist die Sicherheitseigenschaft aus der Problem-Beschreibung: Es ist immer nur maximal ein Prozess in seiner kritischen Sektion. Quelltext 8.6: Mutex Verifikationsziel 60 v e r i f y { 61 always ( c0 . s t = ’ c s => ! ( c 1 . s t = ’ c s or c 2 . s t = ’ c s ) ) ; 62 always ( c 1 . s t = ’ c s => ! ( c0 . s t = ’ c s or c 2 . s t = ’ c s ) ) ; 63 always ( c 2 . s t = ’ c s => ! ( c0 . s t = ’ c s or c 1 . s t = ’ c s ) ) ; 64 } Tabelle 8.2 zeigt die Ergebnisse der erfolgreichen Verifikation. Eine Verifikation mit der BDD-Optimierung ist im aktuellen Entwicklungsstadium des Tools noch nicht möglich. 75 8. Fallbeispiele Modus Native Zustände 11175 Transitionen 118773 Speicherverbrauch 5,337 MB Tabelle 8.2.: Mutex Verifikationsergebnisse 76 9. Ausblick Die vorliegende Arbeit zeigt den aktuellen Entwicklungsstand der GTL-Spezifikationssprache (April 2011). Im Rahmen des VerSyKo-Projekts wird jedoch weiter an Verifikationsalgorithmen für verteilte GALS-Architekturen geforscht. Die Ergebnisse dieser Forschung werden sehr wahrscheinlich großen Einfluss auf die weitere Entwicklung der GTL-Sprache haben. Unter anderem werden folgende Gebiete untersucht: • Inkorporation von zeit-basierter Modelierung und Verifikation. Hierbei soll die Verifikation von Echtzeit-Eigenschaften ermöglicht werden. • Vervollständigung des Datenmodells. Momentan unterstützt die GTL Sprache nur einen Bruchteil der Datentypen der zugrundeliegenden Formalismen. Eine Erweiterung der Datentypen ermöglicht die Verifikation von realistischeren Modellen. • Verwendung von anderen synchronen Formalismen. Der Verifikationsalgorithmus des GTL-Programms erlaubt die gleichzeitige Verwendung von verschiedenen synchronen Formalismen zur Spezifikation von Komponenten. Die Erweiterung um Formalismen außer SCADE ist ein weiteres Ziel der Entwicklung. • Unterstützung von anderen Verifikationsformalismen. In der aktuellen Version des Toolkits wird nur die Verifikation mit SPIN unterstützt. Es kann interessant sein, andere Formalismen wie UPPAAL zu verwenden und die Verifikationsergebnisse zu vergleichen. • Die Entwicklung einer domänenspezifischen Modellierungsschicht mit grafischen Elementen ist angedacht, um die Benutzung zu vereinfachen. • Die Verwendung der „Counterexample-Guided Abstraction Refinement“(CEGAR) Technik, wie in [CGJ+ 03] beschrieben. Diese Technik kann verwendet werden, um die Kontrakte im Falle einer Unterspezifikation zielgerichtet zu verschärfen. 77 Literaturverzeichnis [And03] André, Charles: Semantics of S.S.M. (Safe State Machine) / I3S Laboratory – UMR 6070 University of Nice-Sophia Antipolis / CNRS. BP 121 F 06903 Sophia Antipolis cédex, April 2003. – Forschungsbericht [BGO+ 04] Bozga, Marius ; Graf, Susanne ; Ober, Ileana ; Ober, Iulian ; Sifakis, Joseph: The IF Toolset. Version: 2004. http://dx.doi.org/10.1007/ 978-3-540-30080-9_8. In: Bernardo, Marco (Hrsg.) ; Corradini, Flavio (Hrsg.): Formal Methods for the Design of Real-Time Systems Bd. 3185. Springer Berlin / Heidelberg, 2004. – DOI 10.1007/978–3–540–30080–9_8, S. 131–132 [BK08] Baier, Christel ; Katoen, Joost-Pieter: Principles of Model Checking. The MIT Press, 2008. – ISBN 978–0262026499 [Bry98] Bryant, Randal E.: On the Complexity of VLSI Implementations and Graph Representations of Boolean Functions with Application to Integer Multiplication. In: IEEE Transactions on Computers 40 (1998), S. 205–213 [Bü66] Büchi, Julius R.: Symposium on Decision Problems: On a Decision Method in Restricted Second Order Arithmetic. Version: 1966. http://dx.doi.org/ 10.1016/S0049-237X(09)70564-6. In: Ernest Nagel, Patrick S. (Hrsg.) ; Tarski, Alfred (Hrsg.): Logic, Methodology and Philosophy of Science, Proceeding of the 1960 International Congress Bd. 44. Elsevier, 1966. – DOI 10.1016/S0049– 237X(09)70564–6. – ISSN 0049–237X, 1 - 11 [CGJ+ 01] Clarke, Edmund ; Grumberg, Orna ; Jha, Somesh ; Lu, Yuan ; Veith, Helmut: Progress on the State Explosion Problem in Model Checking. Version: 2001. http://dx.doi.org/10.1007/3-540-44577-3_12. In: Wilhelm, Reinhard (Hrsg.): Informatics Bd. 2000. Springer Berlin / Heidelberg, 2001. – DOI 10.1007/3–540–44577–3_12, S. 176–194. – 10.1007/3-540-44577-3_12 [CGJ+ 03] Clarke, Edmund ; Grumberg, Orna ; Jha, Somesh ; Lu, Yuan ; Veith, Helmut: Counterexample-guided abstraction refinement for symbolic model checking. In: Journal of the ACM 50 (2003), September, S. 752–794. http://dx.doi. org/10.1145/876638.876643. – DOI 10.1145/876638.876643. – ISSN 0004– 5411 [CP95] Caspi, Paul ; Pouzet, Marc: A Functional Extension to Lustre. In: Orgun, M. A. (Hrsg.) ; Ashcroft, E. A. (Hrsg.): International Symposium on Languages for Intentional Programming. Sydney, Australia : World Scientific, May 1995 79 Literaturverzeichnis [Dij65] Dijkstra, Edsger W.: Solution of a problem in concurrent programming control. In: Comm. ACM 8 (1965), Nr. 9, S. 569. http://dx.doi.org/10.1145/ 365559.365617. – DOI 10.1145/365559.365617 [DMK+ 06] Doucet, F. ; Menarini, M. ; Krüger, I. H. ; Gupta, R. ; Talpin, J. P.: A Verification Approach for GALS Integration of Synchronous Components. In: Electron. Notes Theor. Comput. Sci. 146 (2006), January, S. 105–131. http://dx.doi.org/10. 1016/j.entcs.2005.05.038. – DOI 10.1016/j.entcs.2005.05.038. – ISSN 1571– 0661 [GMP04] Gallardo, María del M. ; Merino, Pedro ; Pimentel, Ernesto: A generalized semantics of PROMELA for abstract model checking. In: Formal Aspects of Computing 16 (2004), August, S. 166–193. http://dx.doi.org/10.1007/ s00165-004-0040-y. – DOI 10.1007/s00165–004–0040–y. – ISSN 0934–5043 [God95] Godefroid, Patrice: Partial-Order Methods for the Verification of Concurrent Systems, Universite de Liege, Diss., 1995 [GPVW96] Gerth, Rob ; Peled, Doron ; Vardi, Moshe Y. ; Wolper, Pierre: Simple On-the-fly Automatic Verification of Linear Temporal Logic. In: Dembinski, Piotr (Hrsg.) ; Sredniawa, Marek (Hrsg.): Proceedings of the Fifteenth IFIP WG6.1 International Symposium on Protocol Specification, Testing and Verification Bd. 38, Chapman & Hall, Ltd., 1996 (IFIP Conference Proceedings). – ISBN 0–412–71620–8, S. 3–18 [GT09] Garavel, Hubert ; Thivolle, Damien: Verification of GALS Systems by Combining Synchronous Languages and Process Calculi. In: Proceedings of the 16th International SPIN Workshop on Model Checking Software. Berlin, Heidelberg : Springer-Verlag, 2009. – ISBN 978–3–642–02651–5, S. 241–260 [HCRP91] Halbwachs, N. ; Caspi, P. ; Raymond, P. ; Pilaud, D.: The synchronous dataflow programming language Lustre. In: Proceedings of the IEEE 79 (1991), September, Nr. 9, S. 1305–1320 [Hen09] Henry, Jean: Integration of SCADE Models Generated Code / Esterel Technologies. 2009 (ESEG-EN-003). – Engineering note [Hol97] Holzmann, Gerard J.: State Compression in SPIN: Recursive Indexing And Compression Training Runs. In: Proceedings of the third international SPIN workshop, 1997 [Hol03] Holzmann, Gerard J.: The SPIN Model Checker: Primer and Reference Manual. Addison-Wesley Professional, 2003 http://www.amazon.com/exec/ obidos/redirect?tag=citeulike07-20&path=ASIN/0321228626. – ISBN 0321228626 [Hol08] Holzmann, Gerard J.: A Stack-Slicing Algorithm for Multi-Core Model Checking. In: Electron. Notes Theor. Comput. Sci. 198 (2008), February, 3–16. http://dx. 80 Literaturverzeichnis doi.org/10.1016/j.entcs.2007.10.017. – DOI 10.1016/j.entcs.2007.10.017. – ISSN 1571–0661 [Knu11] Knuth, Donald E.: The Art of Computer Programming, Volume 4A: Combinatorial Algorithms, Part 1. Addison-Wesley Professional, 2011 http://www.amazon. com/Art-Computer-Programming-Combinatorial-Algorithms/dp/ 0201038048. – ISBN 0201038048 [KPRS06] Kesten, Yonit ; Pnueli, Amir ; Raviv, Li-On ; Shahar, Elad: Model Checking with Strong Fairness. In: Formal Methods in System Design 28 (2006), S. 57–84. http://dx.doi.org/10.1007/s10703-006-4342-y. – DOI 10.1007/s10703– 006–4342–y. – ISSN 0925–9856. – 10.1007/s10703-006-4342-y [Mea55] Mealy, George H.: A Method for Synthesizing Sequential Circuits. In: Bell System Technical Journal 34 (1955), Nr. 5, S. 1045–1079 [MP92] Manna, Zohar ; Pnueli, Amir: The Temporal Logic of Reactive and Concurrent Systems – Specification. Springer-Verlag, 1992 (1). – ISBN 0–387–97664–7 [Muk96] Mukund, Madhavan: Finite-state Automata on Infinite Inputs. 1996 [Obj10] Object Management Group: Unified Modeling Language Infrastructure / Object Management Group. 2010 (2.3). – Forschungsbericht [Plo81] Plotkin, Gordon D.: The origins of structural operational semantics. In: Journal of Logic and Algebraic Programming 60 (1981), S. 60–61 [Pnu77] Pnueli, Amir: The temporal logic of programs. In: Foundations of Computer Science, 1977., 18th Annual Symposium on, 1977. – ISSN 0272–5428, S. 46 –57 [RS00] Rajan, B. ; Shyamasundar, R.K.: Multiclock Esterel: a reactive framework for asynchronous design. In: Parallel and Distributed Processing Symposium, 2000. IPDPS 2000. Proceedings. 14th International, 2000, S. 201 –209 [RSD+ 04] Ramesh, S. ; Sonalkar, Sampada ; D’silva, Vijay ; Chandra R., Naveen ; Vijayalakshmi, B.: A Toolset for Modelling and Verification of GALS Systems. Version: 2004. http://dx.doi.org/10.1007/978-3-540-27813-9_47. In: Alur, Rajeev (Hrsg.) ; Peled, Doron (Hrsg.): Computer Aided Verification Bd. 3114. Springer Berlin / Heidelberg, 2004. – DOI 10.1007/978–3–540–27813–9_47, S. 385–387 [Som09] Somenzi, Fabio ; University of Colorado at Boulder – Department of Electrical, Computer, and Energy Engineering (Hrsg.): CUDD: CU Decision Diagram Package. Engineering Center, EE 1B61, Boulder, CO 80309: University of Colorado at Boulder – Department of Electrical, Computer, and Energy Engineering, 2 2009. http://vlsi.colorado.edu/~fabio/CUDD [Tur36] Turing, A. M.: On Computable Numbers, with an application to the Entscheidungsproblem. In: Proc. London Math. Soc. 2 (1936), Nr. 42, S. 230–265 81 A. Installation Hier wird beschrieben, wie das GTL-Tool installiert werden kann. A.1. Voraussetzungen Die folgenden Software-Pakete werden für die Kompilierung der Software benötigt: • Der Haskell-Compiler GHC 17 . Es bietet sich die Installation der „Haskell Platform“ an, die neben dem Compiler noch benötigte Distributionstools und Bibliotheken mitbringt. Die Linux-Distributionen „Ubuntu“, „Debian“, „Fedora“, „Arch Linux“, „Gentoo“ und „NixOS“ bieten diese bereits über ihre interne Paketverwaltung an. Für andere Systeme ist die Plattform unter http://hackage.haskell.org/platform/ erhältlich. • Die Haskell-Bibliotheken „binary“ und „bzlib“, welche beide über die Haskell-Paketverwaltung „hackage“ verfügbar sind und damit automatisch bei der Kompilierung heruntergeladen und installiert werden. Für die Nutzung aller Features des GTL-Tools sind außerdem die folgenden Softwarekomponenten erforderlich: • Der Model-Checker SPIN, erhältlich unter http://spinroot.com. Die Software ist in C geschrieben und hat keine externen Abhängigkeiten und sollte daher auf fast jeder Plattform verfügbar sein. • Die BDD-Bibliothek CUDD, die man unter http://vlsi.colorado.edu/~fabio/ CUDD/ in Quelltextform findet. • Für die Überprüfung der synchronen Komponenten ist das Entwicklungswerkzeug SCADE erforderlich. Als einzige Komponente ist diese nicht frei verfügbar, sondern muss von der Firma „Esterel Technologies“ (http://esterel-technologies.com lizensiert werden. Soll die aktuellste Version der Quelltexte bezogen werden, so benötigt man außerdem die Versionsverwaltung git, erhältlich unter http://git-scm.com/. 17 Glasgow Haskell Compiler 83 A. Installation A.2. Quelltext beziehen Für den Quelltext zu der Anwendung entpackt man entweder die mitgelieferten Archive, oder lädt den aktuellsten Entwicklungsstand der Pakete per git herunter. Der entsprechende Befehl lautet g i t c h e c k o u t h t t p s : / / g i t h u b . com / h g u e n t h e r / name . g i t Wobei name den Namen des Paketes bezeichnet. Die zu herunterladenen Pakete sind: • language-scade • language-promela • bdd • gtl A.3. Kompilieren In jedem Quelltext-Verzeichnis muss nun der Befehl cabal i n s t a l l ausgeführt werden. Hierbei ist zu beachten, dass das Paket „gtl“ als letztes installiert werden muss, da es von allen anderen abhängt. A.4. Verwendung Das Resultat der oben angegebenen Installationsschritte ist die Anwendung „gtl“, die im Plattform-abhängigen Anwendungsverzeichnis zu finden ist. Um die Optionen und Parameter der Anwendung zu begutachten lässt sich die Anwendung mit dem Parameter „--help“ starten: gtl --help Liegt eine GTL-Spezifikation im in Abschnitt 5.1 beschriebenen Format vor, so lassen sich die verschiedenen Verifikationsmodi wie folgt ausführen („spec.gtl ist hier immer die GTL-Spezifikationsdatei): • Die SCADE-Verifikation der einzelnen Komponenten wird mit 84 A.4. Verwendung gtl -m local spec.gtl ausgeführt. Die SCADE-Modelle werden dabei aus der Datei extrahiert, die im ersten Parameter des Modells übergeben wird. Der Name in der SCADE-Datei wird über den zweiten Parameter festgelegt. Der folgende Code gibt also an, dass sich der gesuchte SCADE-Knoten in der Datei „ExampleCar.scade“ befindet und dort den Namen „Car“ trägt. model [ s c a d e ] c a r ( " E x a m p l e C a r . s c a d e " , " C a r " ) ; • Um ein Promela-Modell zu erhalten, dass die Kontrakte nativ übersetzt, zu erhalten, wird gtl -m native spec.gtl ausgeführt. Das erzeugte Promela-Modell kann nun mit SPIN verifiziert werden. • Die dynamische BDD-Optimierung lässt sich wie folgt aufrufen: gtl -m promela-buddy spec.gtl • Die C-Integration der Komponenten wird durchgeführt mit gtl -m native-c spec.gtl Man erhält hierbei ein Promela-Modell, dass den C-Code der Komponenten einbindet. Die Kontrakte der Komponenten werden nicht verwendet. Es müssen bei der Kompilierung des Verifikations-Binaries die Ordner, die den generierten C-Quelltext enthalten mit angegeben werden. Hat man durch die Verifikation mit SPIN eines im zweiten oder dritten Punkt erzeugten GALS-Modells in Promela eine Fehlerspur „error.gtltrace“ erhalten, so kann man diese in der C-Integration verwenden, um zu überprüfen, ob es sich um einen echten Fehler oder eine Unterspezifikation der Kontrakte handelt, indem man gtl -m native-c spec.gtl -t error.gtltrace verwendet. 85 B. Quelltextdokumentation Diese Dokumentation wurde automatisch mit einer modifizierten Version des haddockDokumentations-Tools18 aus den Quelltexten der für die Arbeit erstellten Anwendung generiert. Die Dokumentation enthält Informationen über alle Datentypen, Klassen und Funktionen der Anwendung und gibt Aufschlüsse über die interne Funktionsweise der Anwendung. B.1. Language.GTL.Backend module Language.GTL.Backend ( ModelInterface, GTLBackend(GTLBackendModel, backendName, initBackend, typeCheckInterface, cInterface, backendVerify), CInterface(CInterface, cIFaceIncludes, cIFaceStateType, cIFaceInputType, cIFaceStateInit, cIFaceIterate, cIFaceGetOutputVar, cIFaceGetInputVar, cIFaceTranslateType), mergeTypes ) where Provides an abstraction over many different synchronous formalisms. 18 Erhältlich unter http://haskell.org/haddock. 87 B. Quelltextdokumentation type ModelInterface = (Map String TypeRep, Map String TypeRep) class GTLBackend b where A GTLBackend is a synchronous formalism that can be used to specify models and perform verification. Methods backendName :: b -> String The name of the backend. Used to determine which backend to load. initBackend :: b -> [String] -> IO (GTLBackendModel b) Initialize a backend with a list of parameters typeCheckInterface :: -> -> b GTLBackendModel b ModelInterface -> Either String ModelInterface The backend The backend data A type mapping for the in- and outputs Perform type checking on the synchronized model cInterface :: -> -> b GTLBackendModel b CInterface The backend The backend data Get the C-interface of a GTL model backendVerify :: b -> GTLBackendModel b -> Expr String Bool -> IO (Maybe Bool) Perform a backend-specific model checking algorithm. Returns Nothing if the result is undecidable and Just True, if the verification goal holds. instance GTLBackend Scade data CInterface 88 B.2. Language.GTL.Backend.All = { , , , , , , , } CInterface cIFaceIncludes :: [String] A list of C-headers to be included cIFaceStateType :: [String] A list of C-types that together form the signature of the state of the state machine cIFaceInputType :: [String] The type signature of the input variables. Input variables aren’t considered state. cIFaceStateInit :: [String] -> String Generate a call to initialize the state machine cIFaceIterate :: [String] -> [String] -> String Perform one iteration of the state machine cIFaceGetOutputVar :: [String] -> String -> String Extract an output variable from the machine state cIFaceGetInputVar :: [String] -> String -> String Extract an input variable from the state machine cIFaceTranslateType :: TypeRep -> String Translate a haskell type to C A C-interface is information that is needed to integrate a C-state machine. mergeTypes :: Map String TypeRep -> Map String TypeRep -> Either String (Map String TypeRep) Merge two type-mappings into one, report conflicting types B.2. Language.GTL.Backend.All module Language.GTL.Backend.All ( AllBackend(AllBackend, allTypecheck, allCInterface, allVerifyLocal), initAllBackend ) where 89 B. Quelltextdokumentation Provides a common interface for all backend types. data AllBackend = { , , } AllBackend allTypecheck :: ModelInterface -> Either String ModelInterface allCInterface :: CInterface allVerifyLocal :: Expr String Bool -> IO (Maybe Bool) Essentially a GTLBackend with the parameters instantiated, thus eliminating the type variable. initAllBackend :: -> -> String [String] IO (Maybe AllBackend) The name of the backend The arguments with which to initialize the backend Try to initialize the correct backend for a given backend name and arguments. B.3. Language.GTL.Backend.Scade module Language.GTL.Backend.Scade ( Scade(Scade), scadeTranslateTypeC, scadeTypeToGTL, scadeTypeMap, scadeInterface, buildTest, buchiToScade, startState, failTransition, failState, buchiToStates, stateToTransition, litToExpr, relToExpr, relsToExpr ) where data Scade = Scade instance Show Scade instance GTLBackend Scade scadeTranslateTypeC :: TypeRep -> String scadeTypeToGTL :: TypeExpr -> Maybe TypeRep 90 B.3. Language.GTL.Backend.Scade scadeTypeMap :: [(String, TypeExpr)] -> Either String (Map String TypeRep) scadeInterface :: String -> -> [Declaration] ([(String, TypeExpr)], [(String, TypeExpr)]) The name of the Scade model to analyze The parsed source code Extract type information from a SCADE model. Returns two list of variable-type pairs, one for the input variables, one for the outputs. buildTest :: -> -> -> String [VarDecl] [VarDecl] Declaration Name of the SCADE node Input variables of the node Output variables of the node Constructs a SCADE node that connects the testnode with the actual implementation SCADE node. buchiToScade :: -> -> -> -> String Map String TypeExpr Map String TypeExpr Buchi (Set (GTLAtom String)) Declaration Name of the resulting SCADE node Input variables Output variables The büchi automaton Convert a büchi automaton to SCADE. startState :: Buchi (Set (GTLAtom String)) -> State The starting state for a contract automaton. failTransition :: Transition Constructs a transition into the failState. failState :: State The state which is entered when a contract is violated. There is no transition out of this state. buchiToStates :: Buchi (Set (GTLAtom String)) -> [State] Translates a buchi automaton into a list of SCADE automaton states. 91 B. Quelltextdokumentation stateToTransition :: Integer -> BuchiState st (Set (GTLAtom String)) f -> Transition Given a state this function creates a transition into the state. litToExpr :: Integral a => Expr String a -> Expr relToExpr :: GTLAtom String -> Expr relsToExpr :: [GTLAtom String] -> Expr B.4. Language.GTL.ErrorRefiner module Language.GTL.ErrorRefiner ( Trace, CNameGen, parseTrace, filterTraces, writeTraces, readBDDTraces, atomToC, relToC, exprToC, intOpToC, traceToPromela, traceElemToC, traceToBuchi ) where This module helps with the generation, storing, analyzing and processing of trace files. type Trace = [[GTLAtom (String, String)]] A trace is a list of requirements. Each requirement corresponds to a step in the model. Each requirement is a list of atoms that have to be true in the corresponding step. type CNameGen = String -> String -> Integer -> String Converts GTL variables to C-names. Takes the component name, the variable name and the history level of the variable. parseTrace :: -> -> FilePath FilePath IO [(String, Integer)] The promela file of the model The path to the promela trail file Parse a SPIN trace file by calling it with the spin interpreter and parsing the output. Produces a list of tuples where the first component is the name of the component that just performed a step and the second one is the state number that it transitioned into. 92 B.5. Language.GTL.Expression filterTraces :: String -> [String] Given the output of a spin verifier, extract the filenames of traces. writeTraces :: FilePath -> [Trace] -> IO () Write a list of traces into a file. readBDDTraces :: FilePath -> IO [Trace] Read a list of traces from a file. atomToC :: -> -> CNameGen GTLAtom (String, String) String Function to generate C-names GTL atom to convert Given a function to generate names, this function converts a GTL atom into a C-expression. relToC :: Relation -> String Convert a GTL relation to a C operator exprToC :: CNameGen -> Expr (String, String) Int -> String Convert a GTL expression to a C-expression intOpToC :: IntOp -> String Convert a GTL integer operator to a C-operator traceToPromela :: CNameGen -> Trace -> [Step] Convert a trace into a promela module that checks if everything conforms to the trace. traceElemToC :: CNameGen -> [GTLAtom (String, String)] -> String Convert a element from a trace into a C-expression. traceToBuchi :: CNameGen -> Trace -> Buchi (Maybe String) Convert a trace into a buchi automaton that checks for conformance to that trace. B.5. Language.GTL.Expression 93 B. Quelltextdokumentation module Language.GTL.Expression ( Expr(ExprVar, ExprConst, ExprBinInt, ExprBinBool, ExprRel, ExprElem, ExprNot, ExprAlways, ExprNext), typeCheck, GTLType(typeCheckBin, typeCheckUn), pushNot, getVars, maximumHistory, mapVars, BoolOp(And, Or, Implies), IntOp(OpPlus, OpMinus, OpMult, OpDiv), Relation(BinLT, BinLTEq, BinGT, BinGTEq, BinEq, BinNEq), parseGTLType, castSer, toBoolOp, toRelOp, toElemOp, ExistsBinding, toIntOp, relNot, relTurn ) where Provides the expression data type as well as the type-checking algorithm. data Expr v a where ExprVar :: v -> Integer -> Expr v a ExprConst :: a -> Expr v a ExprBinInt :: IntOp -> Expr v Int -> Expr v Int -> Expr v Int ExprBinBool :: BoolOp -> Expr v Bool -> Expr v Bool -> Expr v Bool ExprRel :: Relation -> Expr v Int -> Expr v Int -> Expr v Bool ExprElem :: v -> [Integer] -> Bool -> Expr v Bool ExprNot :: Expr v Bool -> Expr v Bool ExprAlways :: Expr v Bool -> Expr v Bool ExprNext :: Expr v Bool -> Expr v Bool A type-safe expression type. v is the type of variables (for example String) and a is the type of the expression. instance instance instance instance instance Typeable2 Expr (Eq a, Eq v) => Eq (Expr v a) (Ord a, Ord v) => Ord (Expr v a) (Show a, Show v) => Show (Expr v a) (Binary a, Binary v, Typeable a) => Binary (Expr v a) typeCheck 94 B.5. Language.GTL.Expression :: => -> (Ord a, Show a, GTLType t, Show t) Map a TypeRep (Maybe String -> String -> Either String a) -> -> ExistsBinding a GExpr -> Either String (Expr a t) Type mapping Function to convert variable names The expression to convert Typecheck an untyped expression. Converts it into the Expr type which is strongly typed. Returns either an error message or the resulting expression of type Bool. class Typeable t => GTLType t where A GTL type can provide means to parse unary and binary operators of its type. The default is to fail the parsing. Methods typeCheckBin :: => -> (Ord a, Show a, GTLType t) Map a TypeRep (Maybe String -> String -> Either String a) -> ExistsBinding a -> t -> BinOp -> GExpr -> GExpr -> Either String (Expr a t) The type mapping A function to convert variable names All existentially bound variables An instance of the type (can be undefined) The operator to type check The left hand side of the operator The right hand side of the operator Type checks a binary operator of the given type. typeCheckUn :: (Ord a, Show a, GTLType t) => Map a TypeRep -> (Maybe String -> String -> Either String -> ExistsBinding a -> t -> UnOp -> GExpr -> Either Strin instance GTLType Bool instance GTLType Int 95 B. Quelltextdokumentation pushNot :: Expr v Bool -> Expr v Bool Pushes a negation as far into the formula as possible by applying simplification rules. getVars :: Expr v a -> [(v, Integer)] Extracts all variables with their level of history from an expression. maximumHistory :: Ord v => Expr v a -> Map v Integer Extracts the maximum level of history for each variable in the expression. mapVars :: (v -> w) -> Expr v a -> Expr w a Change the type of the variables in an expression. data BoolOp = | | And Or Implies ∧ ∨ ⇒ Binary boolean operators with the traditional semantics. instance instance instance instance instance Enum BoolOp Eq BoolOp Ord BoolOp Show BoolOp Binary BoolOp data IntOp = | | | OpPlus OpMinus OpMult OpDiv + * / Arithmetik binary operators. instance instance instance instance instance Enum IntOp Eq IntOp Ord IntOp Show IntOp Binary IntOp data Relation 96 B.5. Language.GTL.Expression = | | | | | BinLT BinLTEq BinGT BinGTEq BinEq BinNEq < <= > >= = != Integer relations. instance instance instance instance instance Enum Relation Eq Relation Ord Relation Show Relation Binary Relation parseGTLType :: String -> Maybe TypeRep Convert a String into a type representation. Only covers types which are allowed in the GTL. castSer :: (Typeable a, Typeable b, Monad m) => c a -> m (c b) Lift gcast in a monad and fail with an error if the cast fails toBoolOp :: BinOp -> Maybe BoolOp Cast a binary operator into a boolean operator. Returns Nothing if the cast fails. toRelOp :: BinOp -> Maybe Relation Cast a binary operator into a relation. Returns Nothing if the cast fails. toElemOp :: BinOp -> Maybe Bool Cast a binary operator into an element operator. Returns Nothing if the cast fails. type ExistsBinding a = Map String (a, Integer) Binds variables to other variables from the past. toIntOp :: BinOp -> Maybe IntOp Cast a binary operator into an arithmetic operator. Returns Nothing if the cast fails. relNot :: Relation -> Relation Negates a relation relTurn :: Relation -> Relation Switches the operands of a relation. Turns x ≤ y into y ≥ x. 97 B. Quelltextdokumentation B.6. Language.GTL.LTL module Language.GTL.LTL ( LTL(Atom, Bin, Un, Ground), BinOp(And, Or, Until, UntilOp), UnOp(Not, Next), GBuchi, Buchi, BuchiState(BuchiState, isStart, vars, finalSets, successors), ltlToBuchiM, translateGBA, buchiProduct ) where Implements Linear Time Logic and its translation into Büchi-Automaton. B.6.1. Formulas data LTL a = | | | Atom a Bin BinOp (LTL a) (LTL a) Un UnOp (LTL a) Ground Bool A LTL formula with atoms of type a. instance Eq a => Eq (LTL a) instance Ord a => Ord (LTL a) instance Show a => Show (LTL a) data BinOp = | | | And Or Until UntilOp Minimal set of binary operators for LTL. instance Eq BinOp instance Ord BinOp instance Show BinOp data UnOp 98 ltlToBuchi, B.6. Language.GTL.LTL = | Not Next Unary operators for LTL. instance Eq UnOp instance Ord UnOp instance Show UnOp B.6.2. Buchi translation type GBuchi st a f = Map st (BuchiState st a f) A buchi automaton parametrized over the state identifier st, the variable type a and the final set type f type Buchi a = GBuchi Integer a (Set Integer) A simple generalized buchi automaton. data BuchiState st a f = { , , , } BuchiState isStart :: Bool vars :: a finalSets :: f successors :: Set st Is the state an initial state? The variables that must be true in this state. In which final sets is this state a member? All following states A state representation of a buchi automaton. instance (Show st, Show a, Show f) => Show (BuchiState st a f) ltlToBuchi :: (Ord a, Show a) => LTL a -> Buchi (Map a Bool) Converts a LTL formula to a generalized buchi automaton. ltlToBuchiM :: (Ord a, Monad m, Show a) => ([(a, Bool)] -> m b) -> LTL a -> m (Buchi b) Same as ltlToBuchi but also allows the user to construct the variable type and runs in a monad. translateGBA :: (Ord st, Ord f) => GBuchi st a (Set f) -> GBuchi (st, Int) a Bool Transforms a generalized buchi automaton into a regular one. 99 B. Quelltextdokumentation buchiProduct :: => -> (Ord st1, Ord f1, Ord st2, Ord f2) GBuchi st1 a (Set f1) GBuchi st2 b (Set f2) -> GBuchi (st1, st2) (a, b) (Set (Either f1 f2)) First buchi automaton Second buchi automaton Calculates the product automaton of two given buchi automatons. B.7. Language.GTL.Model module Language.GTL.Model ( GTLModel(GTLModel, gtlModelContract, gtlModelBackend, gtlModelInput, gtlModelOutput, gtlModelDefaults), GTLSpec(GTLSpec, gtlSpecModels, gtlSpecVerify, gtlSpecConnections), gtlParseModel, gtlParseSpec ) where This module provides a data structure for type-checked GTL specifications. data GTLModel 100 B.7. Language.GTL.Model = { GTLModel gtlModelContract :: Expr String Bool , gtlModelBackend :: AllBackend , gtlModelInput :: Map String TypeRep , gtlModelOutput :: Map String TypeRep , gtlModelDefaults :: Map String (Maybe Dynamic) } The contract of the model as a boolean formula. An abstract model in a synchronous specification language. The input variables with types of the model. The output variables with types of the model. Default values for inputs. Nothing means any value. A parsed and typechecked GTL model. data GTLSpec = { , , } GTLSpec gtlSpecModels :: Map String GTLModel All models in the specification. gtlSpecVerify :: Expr (String, String) Bool A formula to verify. gtlSpecConnections :: [(String, String, String, String)] Connections between models. A GTL specification represents a type checked GTL file. gtlParseModel :: ModelDecl -> IO (Either String (String, GTLModel)) Parse a GTL model from a unchecked model declaration. gtlParseSpec :: [Declaration] -> IO (Either String GTLSpec) Parse a GTL specification from an unchecked list of declarations. 101 B. Quelltextdokumentation B.8. Language.GTL.Parser module Language.GTL.Parser ( gtl ) where Implements a parser for the GTL specification language. gtl :: [Token] -> [Declaration] B.9. Language.GTL.Parser.Lexer module Language.GTL.Parser.Lexer ( lexGTL ) where The GTL Lexer lexGTL :: String -> [Token] Convert GTL code lazily into a list of tokens. B.10. Language.GTL.Parser.Syntax module Language.GTL.Parser.Syntax ( Declaration(Model, Connect, Verify), ModelDecl(ModelDecl, modelName, modelType, modelArgs, 102 B.10. Language.GTL.Parser.Syntax modelContract, modelInits, modelInputs, modelOutputs), ConnectDecl(ConnectDecl, connectFromModel, connectFromVariable, connectToModel, connectToVariable), VerifyDecl(VerifyDecl, verifyFormulas), GExpr(GBin, GUn, GConst, GConstBool, GVar, GSet, GExists), InitExpr(InitAll, InitOne) ) where Data types representing a parsed GTL file. data Declaration = | | Model ModelDecl Connect ConnectDecl Verify VerifyDecl Declares a model. Declares a connection between two models. Declares a property that needs to be verified. A GTL file is a list of declarations. instance Show Declaration data ModelDecl = { ModelDecl modelName :: String , modelType :: String , modelArgs :: [String] , modelContract :: [GExpr] , modelInits :: [(String, InitExpr)] , modelInputs :: Map String String , } modelOutputs :: Map String String The name of the model in the GTL formalism. The synchronous formalism the model is written in (for example scade) Arguments specific to the synchronous formalism, for example in which file the model is specified etc. A list of contracts that this model fulfills. A list of initializations for the variables of the model. Declared inputs of the model with their corresponding type Declared outputs of a model Declares a synchronous model. 103 B. Quelltextdokumentation instance Show ModelDecl data ConnectDecl = { , , , } ConnectDecl connectFromModel :: String connectFromVariable :: String connectToModel :: String connectToVariable :: String Model of the source variable Name of the source variable Model of the target variable Name of the target variable Declares a connection between two variables instance Show ConnectDecl data VerifyDecl = { } VerifyDecl verifyFormulas :: [GExpr] The formulas to be verified. A list of formulas to verify. instance Show VerifyDecl data GExpr = | | | | | | GBin BinOp GExpr GExpr GUn UnOp GExpr GConst Int GConstBool Bool GVar (Maybe String) String GSet [Integer] GExists String (Maybe String) String GExpr An untyped expression type. Used internally in the parser. instance Eq GExpr instance Ord GExpr instance Show GExpr data InitExpr = | InitAll InitOne Integer The variable is initialized with all possible values. The variable is initialized with a specific value. Information about the initialization of a variable. instance Eq InitExpr instance Show InitExpr 104 B.11. Language.GTL.Parser.Token B.11. Language.GTL.Parser.Token module Language.GTL.Parser.Token ( Token(Identifier, Key, Bracket, Dot, Semicolon, Colon, Comma, ConstString, ConstInt, Unary, Binary), KeyWord(KeyAll, KeyConnect, KeyContract, KeyModel, KeyOutput, KeyInit, KeyInput, KeyVerify, KeyExists), BracketType(Parentheses, Square, Curly), UnOp(GOpAlways, GOpNext, GOpNot, GOpFinally), BinOp(GOpAnd, GOpOr, GOpImplies, GOpIn, GOpNotIn, GOpLessThan, GOpLessThanEqual, GOpGreaterThan, GOpGreaterThanEqual, GOpEqual, GOpNEqual, GOpPlus, GOpMinus, GOpMult, GOpDiv) ) where 105 B. Quelltextdokumentation data Token = | | | | | | | | | | Identifier String Key KeyWord Bracket BracketType Bool Dot Semicolon Colon Comma ConstString String ConstInt Integer Unary UnOp Binary BinOp instance Show Token data KeyWord = | | | | | | | | KeyAll KeyConnect KeyContract KeyModel KeyOutput KeyInit KeyInput KeyVerify KeyExists instance Show KeyWord data BracketType = | | Parentheses Square Curly instance Show BracketType data UnOp = | | | GOpAlways GOpNext GOpNot GOpFinally (Maybe Integer) instance Eq UnOp instance Ord UnOp instance Show UnOp 106 B.12. Language.GTL.PrettyPrinter data BinOp = | | | | | | | | | | | | | | GOpAnd GOpOr GOpImplies GOpIn GOpNotIn GOpLessThan GOpLessThanEqual GOpGreaterThan GOpGreaterThanEqual GOpEqual GOpNEqual GOpPlus GOpMinus GOpMult GOpDiv instance Eq BinOp instance Ord BinOp instance Show BinOp B.12. Language.GTL.PrettyPrinter module Language.GTL.PrettyPrinter ( getDotBoundingBox, buchiToDot, gtlToTikz, modelToTikz, pointToTikz, dotToTikz, splinePoints, atomToLatex, estimateWidth ) where This module provides functions to render GTL specifications to Tikz Latex drawing commands. It can thus be used to get a pretty image for a GTL file. getDotBoundingBox :: DotGraph a -> Rect Get the bounding box of a preprocessed graph. buchiToDot :: GBuchi Integer (Map (GTLAtom String) Bool) f -> DotGraph String Convert a Buchi automaton into a Dot graph. 107 B. Quelltextdokumentation gtlToTikz :: GTLSpec -> IO String Convert a GTL specification to Tikz drawing commands. This needs to be IO because it calls graphviz programs to preprocess the picture. modelToTikz :: GTLModel -> IO (String, Double, Double) Convert a single model into Tikz drawing commands. Also returns the width and height of the bounding box for the rendered picture. pointToTikz :: Point -> String Helper function to render a graphviz point in Tikz. dotToTikz :: => (Show a, Ord a) Maybe (Map a (Map String TypeRep, Map String TypeRep, String, Double, Double)) -> -> DotGraph a String Can provide interfaces for the contained models if needed. Convert a graphviz graph to Tikz drawing commands. splinePoints :: [a] -> [(a, a, a, a)] Convert a list of points into a spline by grouping them. atomToLatex :: GTLAtom String -> String Render a GTL atom to LaTeX. estimateWidth :: GTLAtom String -> Int Estimate the visible width of a LaTeX rendering of a GTL atom in characters. B.13. Language.GTL.PromelaCIntegration module Language.GTL.PromelaCIntegration ( translateGTL, varName, stateVars, inputVars, generatePromelaCode ) where 108 neverClaim, B.14. Language.GTL.PromelaDynamicBDD Verifies a GTL specification by converting the components to C-code and simulating all possible runs. translateGTL :: -> -> Maybe FilePath GTLSpec IO String An optional path to a trace file The GTL code Compile a GTL declaration into a promela module simulating the specified model. Optionally takes a trace that is used to restrict the execution. Outputs promela code. varName :: -> -> -> -> CInterface String String Integer String The component name The variable name The history level of the variable Convert a GTL name into a C-name. stateVars :: String -> CInterface -> [String] inputVars :: String -> CInterface -> [String] neverClaim :: -> -> -> Trace Expr (String, String) Bool Map String GTLModel Module The trace The verify expression All models Convert a trace and a verify expression into a promela never claim. If you don’t want to include a trace, give an empty one ‘[]’. generatePromelaCode :: GTLSpec -> Map (String, String) Integer -> [Module] Create promela processes for each component in a GTL specification. B.14. Language.GTL.PromelaDynamicBDD 109 B. Quelltextdokumentation module Language.GTL.PromelaDynamicBDD ( TransModel(TransModel, varsInit, varsIn, varsOut, stateMachine, checkFunctions), TransProgram(TransProgram, transModels, transClaims, claimChecks), deleteTmp, verifyModel, traceToAtoms, varName, translateContracts, translateModel, translateNever, AtomCache, OutputMapping, parseGTLAtom, parseGTLRelation, createBDDAssign, createBDDCompare, BDDConst(bddConst), createBDDExpr, buildTransProgram ) where Implements a verification mechanism that abstracts components by using their contract to build a state machine that acts on BDD. data TransModel = { , , , , } TransModel varsInit :: Map String String varsIn :: Map String Integer varsOut :: Map String (Map (Maybe (String, String)) (Set Integer)) stateMachine :: Buchi ([Integer], [Integer], [GTLAtom String]) checkFunctions :: [String] An internal representation of a translated GTL model. instance Show TransModel data TransProgram = { , , } TransProgram transModels :: Map String TransModel transClaims :: Buchi [Integer] claimChecks :: [String] An internal representation of a translated GTL program. instance Show TransProgram deleteTmp :: FilePath -> IO () Helper function to securely delete temporary files. Deletes a file if it exists, if not, ignore it. 110 B.14. Language.GTL.PromelaDynamicBDD verifyModel :: -> -> -> Bool String GTLSpec IO () Keep temporary files generated by spin, gcc, etc. Name of the GTL file without extension The GTL file contents Do a complete verification of a given GTL file traceToAtoms :: -> TransProgram [(String, Integer)] -> Trace The program to work on The transitions, given in the form (model,transitionnumber) Given a list of transitions, give a list of atoms that have to hold for each transition. varName :: Bool -> String -> String -> Integer -> String Helper function to convert the name of a GTL variable into the translated Crepresentation. translateContracts :: TransProgram -> [Module] Convert a translated GTL program into a PROMELA module. translateModel :: -> -> String TransModel [Step] The name of the model The actual model Convert a translated GTL model into a PROMELA process body. translateNever :: Buchi [Integer] -> [Step] Translate a buchi automaton representing a verify expression into a never claim. type AtomCache = Map (GTLAtom (Maybe String, String)) (Integer, Bool, String) A cache that maps atoms to C functions that represent them. The C functions are encoded by a unique number, whether they are a test or assignment function and their source code representation. type OutputMapping = Map String (Map (Maybe (String, String)) (Set Integer)) A map from component names to output variable informations. 111 B. Quelltextdokumentation parseGTLAtom :: -> AtomCache Maybe (String, OutputMapping) -> -> GTLAtom (Maybe String, String) ((Integer, Bool), AtomCache) A cache of already parsed atoms Informations about the containing component The atom to parse Parse a GTL atom to a C-function. Returns the unique number of the function and whether its a test- or assignment-function. parseGTLRelation :: => -> BDDConst a AtomCache Maybe (String, OutputMapping) -> -> -> -> (Integer, Bool, String) A cache of parsed atoms Informations about the containing component Relation The relation type to parse Expr (Maybe String, String) a Left hand side of the relation Expr (Maybe String, String) a Right hand side of the relation Parse a GTL relation into a C function. Returns a unique number for the resulting function, whether it is a test or assignment function and its source-code representation. createBDDAssign :: => BDDConst a Integer -> String -> String -> Map (Maybe (String, String)) (Set Integer) -> Relation -> Expr (Maybe String, String) a -> String Create a BDD assignment createBDDCompare 112 How many temporary variables have been used so far? The current component name The name of the target variable A mapping of output variables The relation used to assign the BDD The expression to assign the BDD with B.15. Language.GTL.Translation :: => -> -> -> -> -> BDDConst a Integer How many temporary variables have been used? Maybe String If the comparision is part of a contract, give the name of the component, otherwise Nothing Relation The relation used to compare the BDDs Expr (Maybe String, String) a Expression representing BDD 1 Expr (Maybe String, String) a Expression representing BDD 2 String Create a comparison operation between two BDD. class BDDConst t where A class of types that have a representation as a BDD. Methods bddConst :: t -> String Convert a value to the BDD C-representation. instance BDDConst Bool instance BDDConst Int createBDDExpr :: => BDDConst a Integer -> -> -> Maybe String Expr (Maybe String, String) a ([String], [String], Integer, String) The current number of temporary variables The current component The GTL expression Convert a GTL expression into a C-expression. Returns a list of statements that have to be executed before the expression, one that has to be executed afterwards, a number of temporary variables used and the resulting C-expression. buildTransProgram :: GTLSpec -> TransProgram B.15. Language.GTL.Translation 113 B. Quelltextdokumentation module Language.GTL.Translation ( GTLAtom(GTLRel, GTLElem, GTLVar), mapGTLVars, gtlAtomNot, gtlToBuchi, gtlsToBuchi, getAtomVars, gtlToLTL ) where Translates GTL expressions into LTL formula. data GTLAtom v = | | GTLRel Relation (Expr v Int) (Expr v Int) GTLElem v [Integer] Bool GTLVar v Integer Bool A representation of GTL expressions that can’t be further translated into LTL and thus have to be used as atoms. instance instance instance instance Eq v => Eq (GTLAtom v) Ord v => Ord (GTLAtom v) Show v => Show (GTLAtom v) Binary v => Binary (GTLAtom v) mapGTLVars :: (v -> w) -> GTLAtom v -> GTLAtom w Applies a function to every variable in the atom. gtlAtomNot :: GTLAtom v -> GTLAtom v Negate a GTL atom. gtlToBuchi :: (Monad m, Ord v, Show v) => ([GTLAtom v] -> m a) -> Expr v Bool -> m (Buchi a) Translates a GTL expression into a buchi automaton. Needs a user supplied function that converts a list of atoms that have to be true into the variable type of the buchi automaton. gtlsToBuchi :: (Monad m, Ord v, Show v) => ([GTLAtom v] -> m a) -> [Expr v Bool] -> m (Buchi a) Like gtlToBuchi but takes more than one formula. getAtomVars :: GTLAtom v -> [(v, Integer)] Extract all variables with their history level from an atom. gtlToLTL :: Expr v Bool -> LTL (GTLAtom v) Translate a GTL expression into a LTL formula. 114