Codegenerierung aus Simulink- und Stateflow-Modellen individuell anpassen
Johannes KanigJohannesKanig
VeröffentlichtGeändert
Traditionell ist viel manuelle Nacharbeit nötig, wenn man modellbasierte Entwicklung und automatische Codegenerierung an spezifische Anforderungen anpassen möchte. QGEN, ein Codegenerator für Simulink- und Stateflow-Modelle, bietet aber vielfältige Anpassungsmöglichkeiten und bindet bei Bedarf auch Plug-Ins ein.
Anzeige
Arbeit mit dem QGEN-GUI: Codegenerierung aus einem Simulink-Modell.Adacore
Modellbasierte Entwicklung und automatische Codegenerierung gehören zu den wichtigsten Innovationen bei der Entwicklung von Embedded-Systemen. Denn: domainspezifische Modellierungssprachen erlauben es Domain-Experten, Software-Spezifikationen zu schreiben; automatische Codegeneratoren erzeugen aus der Spezifikation direkt ausführbaren Code. Dennoch brachten diese Innovationen keineswegs das Ende der Handarbeit: Wie Untersuchungen herausfanden, übersetzen sechs von zehn Unternehmen, die High-Integrity-Software herstellen, ihre Modelle immer noch entweder manuell in den Quellcode oder bearbeiten den durch einen vorhandenen Codegenerator von der Stange erzeugten Code aufwendig nach. Diese Erkenntnis führte zur Entwicklung von QGEN.
Eine manuelle Bearbeitung oder Nachbereitung von generiertem Code ist bisher erforderlich, weil die Generatoren ihre Strategie zur Codeerzeugung nicht individuell anpassen. Unternehmen verwenden aber meist interne Standards für das Coding, die nur teilweise mit internationalen Standards wie MISRA-C übereinstimmen. Da also die Standardlösungen nicht perfekt zu spezifischen Einsatzbedingungen passen, werden In-House-Werkzeuge für die Nachbearbeitung des generierten Codes entwickelt, oder – noch schlimmer – die Übersetzung von Modellen in Quellcode erfolgt manuell.
Eckdaten
Automatisch generierter Code ist oft nicht so aufgebaut, wie es sich Entwickler wünschen. Da der Generator die jeweiligen Wünsche nicht kennt, bietet zum Beispiel QGEN eine Menge an Optionen bis hin zu komplexen Python-Skripts, die den erzeugten C-Quellcode an die eigenen Bedürfnisse anpasst.
Anzeige
QGEN ist ein Codegenerator für Simulink- und Stateflow-Modelle, der nicht nur von Haus aus unterschiedliche Strategien der Codegenerierung unterstützt, sondern Nutzern auch tiefgreifende Anpassungen ermöglicht. Damit können Anwender ihre spezifischen Bedürfnisse erfüllen. Wie derlei Anpassungen aussehen und wie sie sich auswirken, zeigt nachfolgend ein einfaches Simulink-Beispiel. Die Anpassungsmöglichkeiten reichen von einfachen Konfigurationsoptionen bis zu solchen, mit denen Power-User Teile der Codegenerierung von QGEN ersetzen können.
Strategien der Codegenerierung
Entsprechend der klassischen Codegenerierungs-Strategie für synchrone Modellierungssprachen besteht die gesamte Strategie der Codegenerierung von QGEN in der Herstellung von zwei Hauptfunktionen: erstens die Initialisierung eines Systems (init) und zweitens die Ausführung durch eine zugrunde liegende Laufzeit-Umgebung in einer konstanten Periode (compute).
Anzeige
Klassische Hauptfunktionen
extern void init(); /* initialize the persistent state */
Ausgehend von dieser Strategie ist es möglich, QGEN so zu konfigurieren, dass er anderen Schnittstellencode für das System erzeugt. Anwender können alle Codegenerierungs-Optionen ohne Änderung des Modells oder seiner Konfiguration in Simulink verändern, das heißt, dass sie das Modell ohne Modifikation in verschiedenen Kontexten wiederverwenden können.
Zum Beispiel ist es mit den Optionen –wrap-state und –wrap-io möglich, den persistenten Zustand und die formalen Input-/Output-Parameter in Datenstrukturen einzubinden (Structs). Die Option –wrap-state ermöglicht die einfache Wiederverwendung des gleichen Algorithmus mit mehreren Daten, während es –wrap-io erlaubt, mehrere Parameter mit einer einzigen Datenstruktur zu übergeben. Das behebt potenzielle Probleme, etwa wenn der Ziel-Prozessor nur über eine begrenzte Anzahl von Registern für die Speicherung von Funktionsparametern verfügt. Das kombinierte Ergebnis beider Optionen ist der folgende Code:
Durch die Option –global-io ist es stattdessen möglich, die formalen Parameter einer Funktion in globalen Variablen abzulegen, um sie so beispielsweise bestimmten Speicheradressen zuzuordnen:
Anzeige
Globale Variable
state myState; input myInput; output myOutput;
extern void init(); /* will access myState */ extern void compute(); /* will access myState, myInput and myOutput */
Blockspezifische Anpassungen
Die Schalter –wrap-state, –wrap-io und –global-io sind nur einige der möglichen Optionen für die Codegenerierung, die die Art beeinflusst, wie die Code-Interfaces aussehen. Für den eigentlichen Funktionscode bietet QGEN mindestens zwei Gestaltungsmöglichkeiten: Die erste arbeitet auf der Ebene der arithmetischen Operationen. Angenommen im generierten Code befindet sich eine Matrix-Summierung mit Integer-Saturation. Der typische Code sieht dann etwa so aus:
Matrix-Summierung
for (i = 0; i < rows-1; i++) { for (j = 0; j < columns-1; j++) { if (A[i][j] > 0) { if (B[i][j] > MAX – A[i][j]) { C[i][j] = MAX; } else { C[i][j] = A[i][j] + B[i][j]; } } else { if (B[i][j] < MIN – A[i][j]) { C[i][j] = MIN; } else { C[i][j] = A[i][j] + B[i][j]; } } } }
Dieser Code ist zwar semantisch korrekt, bietet aber keine hohe Performance, vor allem dann, wenn der Zielprozessor intrinsische Funktionen unterstützt, die das gleiche Ergebnis möglicherweise mit Vektorisierungsfunktionen erzielen. Dies ist unter anderem der Fall für ARM-, Power-PC- oder Renesas-Prozessoren. Hier ist es mit dem Schalter –arith-conf und einer Konfigurationsdatei möglich, QGEN so einzustellen, dass intrinsische Funktionen aufgerufen werden. Beispielsweise weist die folgende Zeile QGEN an, jedes Mal int32_sat_add aufzurufen, wenn zwei int32 summiert werden:
Mit dieser Zeile in der Konfigurationsdatei wird der generierte Code für das obige Matrix-Beispiel so aussehen:
Anzeige
Matrix-Summierung mit Funktion
#include „myLib.h“ … for (i = 0; i < rows-1; i++) { for (j = 0; j < columns-1; j++) { C[i][j] = int32_sat_sum(A[i][j], B[i][j]); } }
Es ist natürlich möglich, intrinsische Operationen auch für Matrizen zu verwenden, beispielsweise indem man die folgende Zeile der Konfigurationsdatei zufügt:
In diesem Fall verkürzt sich der erzeugte Code auf:
Matrix-Operation aufrufen
#include „myLib.h“ … vint32_sat_sum(A, B, C);
Die Substitution von arithmetischen Operationen mit Aufrufen intrinsischer Funktionen wird für alle Datentypen und Dimensionen, einschließlich der realen komplexen Typen unterstützt.
Anzeige
Externe Funktionen
Neben der Konfiguration der Rechenoperationen erlaubt QGEN auch, externe Funktionen für spezifische Bausteine aufzurufen. Vielleicht möchte der Benutzer eine eigene Funktion für Lookup-Tabellen verwenden. Er könnte zwar mit S-Funktionen das gleiche Ergebnis erzielen, aber dazu müsste er das Modell ändern, um den Baustein der Lookup-Tabellen durch S-Funktionen zu ersetzen. Durch die QGEN-Option –block-conf kann er bei der Codegenerierung den nativen Simulink-Baustein beim Aufruf einer externen Funktion beibehalten. Beispielsweise ist es möglich, die folgende Zeile in einer Konfigurationsdatei einzusetzen:
Jedesmal wenn das Modell einen Lookup_n-D-Baustein mit einer Anzahl von Dimensionen gleich 2 enthält, erzeugt diese Konfiguration einen Anruf für lookup_com. Die Parameter, die der Funktion übergeben werden, sind diejenigen, die in der Konfigurationsdatei spezifiziert wurden. Sie umfassen die Parameter der Bausteine und die Dimensionen des Inputs.
Anzeige
Customizing in der Tiefe
Über die bisher dargestellten Möglichkeiten hinaus lässt sich QGEN durch benutzerspezifische Plug-ins auch sehr tiefgreifend anpassen. QGEN arbeitet immer mit einer standardisierten internen Darstellung, die es in einer XML-Datei veröffentlichen kann. Das Schema der XML-Datei wird formalisiert als Ecore-Metamodell. In der Praxis kann QGEN eine XML-Datei ausgeben, die die intermediäre Darstellung des Simulink-Modells für die verschiedenen Durchgänge der Modellzusammenstellung umfasst. Wird die XML-Datei gespeichert, so ist es möglich, sie zu modifizieren und sie wieder in QGEN zu importieren. Auf diese Weise lassen sich anwenderspezifische Plug-Ins entwickeln. Diese laufen ohne den QGEN-Quellcode neu zu kompilieren, sie dürfen auch in anderen Sprachen geschrieben sein, zum Beispiel Ada, C, C++, Java, Python oder Modelltransformationswerkzeuge unter Eclipse wie QVT oder ATL.
Ein Beispiel: Angenommen der Modellierungsstandard für ein bestimmtes Projekt erfordert es, alle Simulink-Variablen, deren Name mit dem Präfix „obs_“ beginnt, auf nicht-optimierbare globale Variablen abzubilden. Dies wäre in Simulink etwa durch Speicherklassen möglich. Allerdings erfordert das erstens, alle Signale mit „obs_“ zu suchen und zweitens die Speicherklasse entsprechend zu ändern. Modifiziert man das Modell auf diese Weise, so schränkt man die Wiederverwendbarkeit des ursprünglichen Modells ein. In QGEN ist es möglich, das gleiche Ergebnis so zu erreichen:
1. QGEN mit einem Simulink-Modell ausführen, aber vor der Ausgabe von C oder Ada mit dem Schalter –steps anhalten. In diesem speziellen Fall sollen erst Preprocessing, Sequenzierung, Codemodell-Generierung, -Optimierung und -Expansion ablaufen und dann QCEN das XML exportieren.
2. Das XML-Dokument bearbeiten. Dabei kann der Benutzer seine bevorzugte Programmiersprache, zum Beispiel Python, oder sein bevorzugtes Werkzeug verwenden. In diesem speziellen Fall ist es ausreichend, für alle „“-Knoten, deren Name mit „obs_“ beginnt, die Sichtbarkeit zu ändern, zum Beispiel so:
Sichtbarkeit von Variablen per Skript ändern
for e in doc.getElementsByTagName(‚variable‘): if e.getAttribute(‘name‘).startWith(‚obs_‘): e.setAttribute(‚visibility‘, ‚GLOBAL‘)
3. Das XML-Dokuments in einer neuen Datei speichern.
4. QGEN erneut ausführen und dabei die neue XML-Datei als Input übergeben.
Es ist also ganz einfach, eigene Plug-Ins in QGEN zu entwickeln. Diese können nicht nur die Codegenerierung beeinflussen, sondern zum Beispiel auch Makefiles erzeugen, Analysetools anbinden oder Dokumentationen erzeugen. QGEN erweist sich so als überaus flexibles Werkzeug, mit dem sich auch sehr spezielle Anforderungen abdecken lassen.
Johannes Kanig
ist Senior Software Engineer bei Adacore in Paris.