Verifikation von GALS Systemen

Werbung
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
Herunterladen