Skip to content

Dokumentation

Jonathan edited this page Apr 30, 2021 · 22 revisions

Dokumentation PicSim

In dieser Dokumentation werden zunächst Hinweise zur Verwendung des Simulators gegeben, danach werden Simulatoren im Allgemeinen vorgestellt und schließlich geht es um die Besonderheiten und Funktionen dieses PIC-Simulators.

Hinweise zur Verwendung des Simulators

  • Der Simulator unterstützt nur LST-Dateien in UTF-8 Format.
  • LST-Dateien müssen richtig formatiert sein. Der Simulator setzt dies voraus.
  • Offiziell läuft der PicSim sowohl auf Windows als auch Android. Die Apk- und Exe-Datei befinden sich im Release-Tab des Repositories. Tatsächlich ließe sich der Simulator auch auf macOS, Linux und iOS kompilieren lassen. Mit diesem Hintegedanken wurde der Simulator entwickelt. Dies wurde aber bisher noch nicht getestet.

Allgemein

Grundsätzliches zu einem Simulator

Ein Simulator versucht die Realität nachzubilden. Da die Realität allerdings sehr komplex ist, muss diese abstrahiert werden. Dazu werden Modelle gesucht, welche bestimmte Spezifikationen besitzen, die wichtig für den Anwendungszweck der Simulation sind. Für den PIC-Simulator ist das Ziel schon vorgegeben. Es soll ein PIC simuliert werden.
Simulationen können unterteilt werden in Simulationen mit oder ohne der Verwendung Informationstechnik. Eine Simulationen ohne Informationstechnik ist z.B. der Auto-Crashtest, bei welchem ein Verkehrsunfall wird.[^SimulationWiki] Damit keine Menschen dabei gefährdet werden sollen, werden dafür Crashtest-Dummys verwendet. Da die Realität bei einem Crashtest sehr vereinfacht wird, ist es möglich verschiedene Autos beim Crash besser vergleichen zu können. Simulationen mit Informationstechnologien sind besser bekannt als Computersimulationen. Dazu zählen z.B. Fahrsimulatoren, Flugsimulatoren usw. Es gibt verschiedene Gründe eine Simulationen zu entwickeln. Oft wären Test am realen System zu teuer oder ethisch nicht vertretbar. Es kann aber auch sein, dass das reale System noch gar nicht existiert (z.B. Strömungsmodelle bei einem neuen Flugzeug) oder zu komplex oder noch zu unverstanden ist (z.B. Urknall) um es an einem realen System zu testen.[^SimulationGründe]

Vor- und Nachteile einer Simulation

Vorteile einer Simulation sind hauptsächlich die oben genannten Gründe (Grundsätzliches zu einem Simulator). Für den PIC-Simulator sind nur zwei Gründe wirklich relevant: Aufwand und Kosten. Durch eine Simulation werden immer gleiche Bedingungen erzeugt. Dadurch lässt sich das Programmieren mit einem PIC einfacher lernen. Denn es wird keiner Hardware aus einem Computer vorausgesetzt. Es gibt keine Hardwarefehler und der ausgeführte Programmcode lässt sich einfacher überwachen.
Da ein Simulator das Verhalten eines Systems nur vereinfacht darstellt, kann ein Simulator nicht das gesamte System repräsentieren. In diesem Fall wäre ein Emulator wesentlich besser geeignet. Dieser emuliert sowohl Software als auch Hardware.

Realisierung

Konzept

Der Simulator soll dem realen PIC sehr ähnlich. Deswegen werden Daten möglichst in Bit Schreibweise geschrieben sein. Da Dart allerdings keine Bits bzw. Bytes als Datentypen kennt, werden alle Daten intern als Strings gespeichert. Weil Strings auch in Dart einfache Char-Arrays sind, können Bit-Befehle wie bcf oder bsf sehr einfach implementiert werden. Der Simulator arbeitet auch wie der PIC nur mit den Instructions. Außerdem wird der ProgramCounter (PC) im Simulator mit einem Integer-Wert dargestellt. Dadurch sind Zugriffe auf das Array des Programmcodes einfacher realisierbar. Wie beim realen Vorbild setzt sich dieser aus PCL und PCLATH zusammen.
Der Simulator besteht aus zwei Anzeigen: einer Startseite bei der ein Programm ausgewählt und geladen werden kann und einer weiteren Seite, die den eigentlichen Simulator ausmacht. Die Logik des Simulators ist weitesgehend getrennt von der Benutzeroberfläche. Dadurch ließe sich der Simulator mit geringem Aufwand in einer Kommandozeile oder mit einer anderen Benutzeroberfläche laufen lassen.

Struktur

Nachdem die LST Datei ausgewählt und in den PicSim geladen wurde, zeigt die GUI durch einen Highlighter den nächsten Befehl an der abgearbeitet wird. Im Hintergrund wurden bis dahin, wie im Ablaufdiagramm zu sehen ist, die Instructions in das Programm-Array geladen, die Variablen initalisiert und das Programm wartet auf die Nutzereingabe. Sobald der Nutzer den Start-Button klickt startet das Programm mit dem ersten Befehl, analysiert die Instruction, arbeitet diesen Befehl ab, der Programm Counter wird angepasst, der Highlighter springt zum entsprechenden Befehl und der nächste Befehl wird ausgeführt. Sollte jedoch der Stop Button geklickt worden sein wartet das Programm bis zum nächsten Start, bevor der nächste Befehl ausgeführt wird. Beim Step wartet das Programm vor jedem Befehl auf die nächste Eingabe.

GUI
GUI
Ablaufdiagramm
Ablaufdiagramm

Um einen PIC zu simulieren müssen dessen Komponenten softwaremäßig nachgebildet werden. Hierfür wird der Hauptspeicher mit seinen Regisstern als String Array abgespeichert. In jeder "Speicherzelle" steht ein String mit 8-Zeichen. Initialisiert werden diese Speicherzellen mit acht Nullen. Alle Speicherzellen können angeklickt und manuell in Hex- oder Binär-Schreibweise gesetzt werden. Der Wert wird zur einfacheren Lesbarkeit immer als Hex angezeigt. Intern wird jedoch ein 8-Zeichen String verwendet.

GUI-Top_Speicher
RAM-Speicher

Das Programm wird aus der LST-Datei ausgelesen. Dabei wird die Instruction extrahiert und als Program-Array abgespeichert. Auf diese Speicherstellen zeigt der Programmcounter (PC). Dieser wird als Int-Wert realisiert welcher auf den auszuführenden Befehl zeigt. Nach jedem duchgeführten Befehl wird dieser hochgezählt. Sprungbefehle geben den absuluten Wert an an den gesprungen werden soll. Der Stack wurde wie das Programm als Array mit dazugehörigem Stack-Pointer realisiert. Dies ermöglicht, dass bei einem Überlauf über den Inhalt von 8 wieder das erste Element überschrieben werden kann. Der Stack ist mit null initalisiert und wird bei jedem CALL weiter gefüllt und bei jedem RETURN wieder geleert. Der Stack wird in der GUI als Hex-Zahl angegeben, wie in der folgenden Abbildung zu sehen ist, intern werden die Zahlen jedoch als acht Zeichen langer String gespeichert.

GUI-Top_Stack
Stack

Um die Runtime zu ermitteln wird unsere globale Runtime Variable (int) bei jedem Befehl um die jeweilige Anzahl Cyles hochgezählt die eine Instruction benötigt. Je nach eingestellter Quarzfrequenz wird die absolute Zeit ermittelt und in der GUI angezeigt. Das FSR-Register, das W-Register, der PCL, das PCLATH und das Status-Register werden auch in der GUI ausgegeben. Alle sowohl als 8-Zeichen-String als auch zur einfacheren Lesbarkeit als Hex-Zahl. Die Anzeige ist in der folgenden Abbildung farblich markiert. Das wReg (W-Register) ist nicht Teil des Storages sondern wird als eigene String-Variable realisiert. Der PCL besteht aus den unteren acht Bits des ProgramCounters. Für die GUI wird dazu der Integer-Wert in einen String mit Binärzahlen konvertiert. Danach wird der String auf acht Stellen normalisiert und schließlich angezeigt. Das FSR ist Teil des Storage und steht dort an Stelle vier. Es wird als Speicher für die indirekte Adressierung verwendet Das PCLATH ist ebenso Teil des Storage, es steht an Stelle zehn. Verwendet wird es um die Programm-Page umzuschalten. Alle Register können manuell, durch anklicken, mit einer Hex- oder Binär-Zahl gesetzt werden.

Ebenso ist in der Abbildung die einstellbare Quarzfrequenz, die Runtime sowie der Button Runtime Reset zu sehen. Die Runtime wird intern als Int-Variable bei jedem Befehl hochgezählt. Die Variable wird pro Cycle um eins inkrementiert. Die Cycle-Anzahl wird dann mithilfe der Quarzfrequenz in die tatsächliche Laufzeit umgerechnet und ausgegeben. Der Button Reset Runtime setzt den Wert wieder zurück auf null.

GUI-Top_wReg+Runtime
W-Register und Laufzeitmessung

Das Tris-Register (RA-RE) stellt die In/Out-Schnittsstellen dar. Dieses Register ist Teil des Storages an Stelle fünf und sechs. Hier werden die Pins auf Input oder Output gesetzt. Die Werte können auch manuell durch klicken auf das entsprechende Bit eingestelt werden.

GUI-Top_Tris
Tris-Register

Klassendiagramm:

class-diagram
Klassendiagramm

Wie in diesem Diagramm zu erkennen ist, werden bei diesem Simulator Logik und UI strikt getrennt. Die Datei main.dart beinhaltet die Homepage auf der eine Datei für den Simulator ausgewählt werden kann. Die Klassen dieser Datei erben alle vom Framework von Flutter, welches die UI rendert. Dasselbe gilt auch für die UI welche in der simscreen.dart-Datei deklariert wird. Die Logik erstreckt sich über zwei Klassen: den InstructionCycler und den InstructionRecognizer. Im InstructionRecognizer werden alle Befehle definiert, die der Simulator simulieren kann. Von außen aufgerufen wird nur die recognize()-Funktion. Diese nimmt eine Instruction und den aktuellen ProgramCounter entgegen und liefert nachdem der Befehl ausgeführt wurde den neuen ProgrammCounter zurück.
Der InstructionCycler arbeitet etwas abstrahierter und besitzt die Funktionen start(), stop() und step(), welche von der Benutzeroberfläche aufgerufen werden. Damit wird der Programmablauf gesteuert. Außerdem wird in dieser Klasse auch auf Interrupts (interrupt()) geprüft. Auch der Timer0 (timer0()) wird hier inkrementiert.

Dart als Programmiersprache für den Simulator

Da die Programmiersprache für den PIC-Simulator frei wählbar war, entschieden wir uns relativ schnell für die Sprache Dart. Dart bietet eine sehr einfache Syntax, die vergleichbar mit Typescript und Kotlin ist. Ein weiterer Grund der für Dart spricht ist das UI-Framework Flutter. Mit Flutter lassen sich sehr einfach ansprechende User Interfaces entwickeln. Da Flutter und Dart außerdem auf vielen verschiedenen Geräten laufen können, lag es nahe auch die Crossplatform-Möglichkeiten zu nutzen.

Einige Beispiele

Realisierung der Flags

Die Flags zum Steuern des PICs werden in drei Registern gespeichert, dem Status-Register an Stelle drei im Storage, dem Option-Register an Stelle 128 und dem Intcom-Register an Stelle 11. Somit sind die Flags wie jedes Register ein String mit 8-Zeichen, wobei jedes einzelne Flag einer Stelle des Strings entspricht. In der GUI werden diese wie unten zu sehen aufgelistet. Dabei kann jeder Wert einzeln durch klicken invertiert werden. Die Funktionen der einzelnen Flags sind hier nachlesbar (ab S 15).

GUI-Top_Flags
Flags

Interrupts

Bisher wurde nur das Timer-Interrupt in den Simulator implementiert. Die Basis ist aber für alle fehlenden Interrupts gegeben. Bei jedem Befehl den der Simulator ausführt wird immer auch überprüft, ob ein Intterupt im Interrupt-Register ausgelöst wurde. Falls dieser Fall eintritt, springt der Simulator an die vordefinierte Stelle (Stelle: 4) im Programmcode. Von dort aus wird dann die ISR ausgeführt.

bool interrupt() {
 if (storage.value[11][0] == "1" && storage.value[11][2] == "1" &&
 storage.value[11][5] == "1") {
 	recognizer.call(int.parse(storage.value[10] + storage.value[2], radix: 2), "00000000000100");

 	// set ISR-address
 	storage.value[10] = "00000000";
 	storage.value[2] = "00000100";

 	return true;
 }

return false;

} 

Beim Interrupt wird ein Sprung ausgeführt. Dafür wird der Programcounter auf 4 gesetzt. Denn dort startet die Intterupt Service Routine. Bevor der Sprung ausgeführt wird, muss zuerst überprüft werden, ob überhaupt ein Interrupt ausgelöst werden soll. Dies geschieht in der if-Abfrage. Dafür wird geschaut, ob im Interrupt-Register (Index: 11) die Bits für ein Timer-Interrupt (GIE, T01E, T01F) gesetzt sind.

Zusammenfassung

Der Simulator beherrscht die meisten Befehle und Funktionen die ein echter PIC beherrscht. Und die dazugehörige Benutzeroberfläche ermöglicht eine einfache Bedienung für das Debugging und das Ausführen von Programmen. Natürlich fehlen noch einige Funktionen, wie z.B. der Watchdog, die restlichen Funktionen des TRIS-Registers und die restlichen Interrupts. Diese könnten mit geringem Aufwand jeweils hinzugefügt werden. Beispielsweise existiert schon das Grundgerüst für Interrupts, da der Timer0-Interrupt schon implementiert ist. So ist das auch mit dem Watchdog und dem Prescaler. Die Prescaler-Funktion hierfür existiert bereits und kann dafür wiederverwendet werden.

Fazit

Die Entwicklung des Simulators hat u.A. zum Verständnis des PICs beigetragen. Dieses tiefergehende Verständnis hätte auch in einer Klausur weiterhelfen können.
Die Designentscheidung Dart und Flutter zu verwenden war richtig. So konnten die Kenntnisse mit der Programmiersprache und dem UI-Framework stark vergrößert werden. Dies kann in folgenden Projekten weiterhelfen. Durch Flutter war es auch möglich direkt von Beginn an eine funktionierende Benutzeroberfläche zu entwickeln. So fiel das Debugging der implementierten Funktionen einfacher aus als Anfangs erwartet, weil Fehler nicht immer über die Ausgabe einer Konsole gesucht werden mussten.

Literatur

[^SimulationWiki]: "Simulation" https://de.wikipedia.org/wiki/Simulation 27.04.2021 [^SimulationGründe]: "Simulationen" https://javainformatikblog.wordpress.com/simulationen/ 27.04.2021

Clone this wiki locally