====== Assembler-Programmierung mit dem RISC Simulator ====== ===== - Hexadezimalsystem ===== Assembler ist eine etwas besser leserliche Form der Maschinensprache (Sprache, die die CPU 'versteht'). Mit Assembler hast du bereits im Little Man Computer Bekanntschaft gemacht. Befehle in Maschinensprache werden im Binärsystem ausgedrückt. Selbst für relativ kleine Zahlen resultiert dies daher in ziemlich langen Ausdrücken. Deshalb schreibt man diese oft ins Hexadezimalsystem um, was folgende Vorteile hat: * viel kompakter * einfache Umrechnung Binärsystem <-> Hexadezimalsystem (einfacher als z.B. ins Dezimalsystem) * Die Umrechnung Dezimalsystem <-> Hexadezimalsystem erfolgt wie die Umrechnung Dezimalsystem -> Binärsystem mit dem [[https://www.matheretter.de/rechner/dezimalbinar|Restwertverfahren]] * Der Online-Umrechner gibt's wie gewohnt hier: [[https://zahlensysteme-rechner.de]] Das **Hexadezimalsystem** ist das Zahlensystem mit: * Basis $16$ * Nennwerten $0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F$ * Typischerweise wird Präfix $0x$ vor Hexadezimalzahl geschrieben **Beispiele:** * Umrechnung Hexadeimalsystem -> Dezimalsystem $0xB3 = 11 \cdot 16^1 + 3 \cdot 16^0 = 179$ * Umrechnung Hexadeimalsystem -> Binärsystem $0xB3 = 11 \cdot 16^1 + 3 \cdot 16^0 = 1011 0011$ * Umrechnung Binärsystem -> Hexadezimalsystem $1100 1011 = 1100 \cdot 16^1 + 1011 \cdot 16^0 = (8+4) \cdot 16^1 + (8+2+1) \cdot 16^0 = C \cdot 16^1 + B \cdot 16^0 = 0xCB$ **Aufgaben:** Stelle folgende Zahlen im Binärsystem dar und wandle die ins Dezimalsystem um: * 0xFF * 0x100 * 0x3A Stelle folgende Zahlen im Hexadezimalsystem dar und wandle sie ins Dezimalsystem um: * 1010 1010 * 0111 1101 * 1111 1110 ++++Lösung| * 0xFF = 1111 1111 = 255 * 0x100 = 1 0000 0000 = 256 * 0x3A = 0011 1010 = 58 * 1010 1010 = 0xAA = 170 * 0111 1101 = 0x7D = 125 * 1111 1110 = 0xFE = 254 ++++ ===== - Arbeitsumgebung ===== Den ARM-Risc-Simulator von Peter Higginson findet man hier: [[https://peterhigginson.co.uk/RISC/|https://peterhigginson.co.uk/RISC/]] Eine kleine Hilfe und die ganze Versionsgeschichte findet man direkt mit //Help// Das ist die Übersicht aller möglichen Befehle: [[https://www.peterhigginson.co.uk/RISC/instruction_set.pdf|https://www.peterhigginson.co.uk/RISC/instruction_set.pdf]] Der Simulator läuft direkt im Browser und besteht aus drei Teilen: {{ :ef_informatik:arm-simulator.png?800 |}} * Rot: Programmeditor * Blau: CPU, Logik, Busse * Grün: RAM * Dazu kommt noch die Steuerung über die Buttons links unten. **Aufgabe 1:** * Stelle zunächst die RAM-Darstellung von Dezimal (wie unprofessionell!) auf Hexadezimal um. Das machst du in den //Options//. * In //Select// findest du mehrere Beispielprogramme. Wähle //add// aus. Das Programm wird im Editor angezeigt und gleichzeitig ab Adresse 000 in den Speicher geladen. * Lass das Programm mit //Step// schrittweise ablaufen und beobachte, welche Daten in welcher Reihenfolge fliessen. * Du kannst die Animation mit den beiden Pfeiltaste //<>// bremsen oder beschleunigen. Mit //def fast// in den //Options// kannst du die Animation auch komplett unterdrücken. * Später kannst du das ganze Programm mit //Run// auf einmal laufen lassen. * Versuche, die beiden Zahlen nicht zu addieren, sondern zu multiplizieren. **Aufgabe 2:** * Sieh dir das Beispielprogramm //example// an. **Aufgabe 3:** * Schreibe ein Programm, dass die beiden Dezimalzahlen 48801 und 53214 addiert. * Lege das Ergebnis an den Speicheradressen 102 und 103 ab. ++++Lösung| LDR R0,100 LDR R1,101 ADD R2,R1,R0 ADC R3,R7 STR R2,102 STR R3,103 DAT ... DAT DAT 48801 DAT 53214 ++++ **Aufgabe 4:** * Schreibe ein Programm, dass die beiden Dezimalzahlen 48801 und 53214 multipliziert. * Lege das Ergebnis an den Speicheradressen 102 und 103 ab. * Verwende den Befehl //MLX//. * Beachte genau, was in den Registern passiert! ++++Lösung| LDR R0,100 LDR R1,101 MLX R1,R0 STR R1,102 STR R2,103 DAT ... DAT DAT 48801 DAT 53214 ++++ ===== - Zahlen jonglieren ===== Unsere Simulator-CPU kann nur 16-Bit-Zahlen. Was war nochmals die grösste darstellbare unsigned Zahl? Wir können wir zwei 32-Bit-Zahlen addieren oder sogar multiplizieren? **Aufgabe 5:** * Schreibe ein Programm, das die beiden Dezimalzahlen 2'596'896'414 und 3'123'455'219 addiert. * Lege das Ergebnis an den Speicheradressen 104, 105 und 106 ab. ++++Lösung| LDR R0,100 LDR R1,102 ADD R1,R0 STR R1,104 LDR R0,101 LDR R1,103 ADC R1,R0 STR R1,105 ADC R2,R7 STR R2,106 DAT ... DAT DAT 0x7e9e DAT 0x9ac9 DAT 0x24f3 DAT 0xba2c ++++ **Aufgabe 6:** * Schreibe ein Programm, dass die beiden Dezimalzahlen 2'596'896'414 und 3'123'455'219 multipliziert. * Lege das Ergebnis an den Speicheradressen 104-107 ab. Tipps:\\ Zerlege die beiden Zahlen in je zwei Teile, nämlich ein oberes und ein unteres Word (1 Word = 2 Bytes): $$2'596'896'414 = \text{0x}\,\text{9AC9}\,\text{7E9E} = \text{0x}\,\text{9AC9} \cdot 2^{16} + \text{0x}\,\text{7E9E} \cdot 2^0$$ $$3'123'455'219 = \text{0x}\,\text{BA2C}\,\text{24F3} = \text{0x}\,\text{BA2C} \cdot 2^{16} + \text{0x}\,\text{24F3} \cdot 2^0$$ Nun kommt Binom ;-) $$\text{0x}\,\text{9AC9}\,\text{7E9E} \cdot \text{0x}\,\text{BA2C}\,\text{24F3} = \text{0x}\,\text{9AC9} \cdot \text{0x}\,\text{BA2C} \cdot 2^{32} + \text{0x}\,\text{9AC9} \cdot \text{0x}\,\text{24F3} \cdot 2^{16} + \text{0x}\,\text{BA2C} \cdot \text{0x}\,\text{7E9E} \cdot 2^{16} + \text{0x}\,\text{7E9E} \cdot \text{0x}\,\text{24F3} \cdot 2^0$$ Jetzt kann man die einzelnen Summanden einfach richtig im Speicher verteilen: | $$2^0$$ | 204 | | $$2^{16}$$ | 205 | | $$2^{32}$$ | 206 | | $$2^{48}$$ | 207 | Aber Achtung! Es gibt immer wieder Überträge, die in die Additionen für die einzelnen Speicheradressen mit einfliessen müssen. ++++Lösung| E0: LDR R0,100 LDR R1,102 MLX R1,R0 STR R1,104 STR R2,105 ADC R3,R7 STR R3,106 E16a: LDR R1,103 MLX R1,R0 ADC R3,R7 ADD R1,105 LDR R4, 106 ADC R2,R4 ADC R3,R7 STR R1,105 STR R2,106 STR R3,107 E16b: LDR R0,101 LDR R1, 102 MLX R1,R0 ADC R3,R7 ADD R1,105 LDR R4, 106 ADC R2,R4 ADC R3,R7 STR R1,105 STR R2,106 STR R3,107 E32: LDR R0,101 LDR R1, 103 MLX R1,R0 ADC R3,R7 ADD R1,106 LDR R4, 107 ADC R2,R4 ADC R3,R7 STR R1,106 STR R2,107 STR R3,108 DAT ... DAT DAT 0x7e9e DAT 0x9ac9 DAT 0x24f3 DAT 0xba2c ++++ ===== - Vergleiche, Sprünge, Schleifen ===== Aus fast allen Programmiersprachen gibt es Bedingungen, die entweder Verzweigungen (if...else...) oder Schleifen (for... oder while...) steuern. Damit ein Compiler oder Interpreter diese Anweisungen in Maschinensprache übersetzen kann, muss es das also auch dort geben. **Aufgabe 7:** * Öffne in //Select// das Beispiel //ascii//. * Dieses Programm gibt in einer Schleife alle druckbaren Zeichen (32-127) der ASCII-Tabelle aus. * Lass das Programm mit //Step// schrittweise ablaufen und beobachte, wo genau sich die Schleife befindet. * Welche Zeile ist die Bedingung? Welche der Rücksprung? Wohin geht der Rücksprung? * Was genau macht das Programm, nachdem die Schleife verlassen wird? **Aufgabe 8:** a) Schreibe ein Programm, das einen Countdown 10,9,...1,0 ausgibt. ++++Lösung| MOV R1,#10 MOV R2,#1 LOOP: OUT R1,4 SUB R1,R2 CMP R1,#0 BGE LOOP HLT ++++ b) Schreibe ein Programm, das Zahlen 1 bis 100 addiert und am Ende die Summe ausgibt. ++++Lösung| MOV R1,#0 MOV R2,#1 LOOP: ADD R1,R2 ADD R2,#1 CMP R2,#100 BLE LOOP OUT R1,4 HLT ++++ **Aufgabe 9:** Schreibe ein Programm, welches das Maximum zweier Zahlen bestimmt: Der Benutzer soll hintereinander aufgefordert werden, zwei Zahlen einzugeben. Von diesen wird die grössere bestimmt und ausgegeben.\\ \\ Wenn man dieses Programm z.B. in Python schreibt, implementiert man dazu eine Verzweigung (if-el(se)if). In Assembler wird dies wie eine Schleife (z.B. in letzter Aufgabe) mit Sprüngen implementiert. ++++Lösung| INP R0,2 INP R1,2 CMP R0,R1 BLE LOWER OUT R0 BRA DONE LOWER OUT R1 DONE HLT ++++ ===== - Speichertypen ===== Sobald ein Programm gestartet wird, erhält es vom Betriebssystem RAM zugewiesen. Dieses RAM ist in zwei Blöcke unterteilt, die für unterschiedliche Operationen eingesetzt werden: Die Heap und der Stack ([[https://de.wikipedia.org/wiki/Dynamischer_Speicher|Wikipedia]]). In unserem Simulator beginnt das Programm bei Adresse 0000 und der Stack bei Adresse 0400 (s. Register SP). Der Stack wird absteigend aufgefüllt und belegt so den unteren Teil im Main Memory. Alles dazwischen kann als Heap verwendet werden... ==== - Heap ==== Bisher haben wir Daten jeweils an einer bestimmten Adresse im Speicher abgelegt. Bei dieser Methode muss man immer auch die Speicheradresse kennen, sonst sind die Daten nicht mehr auffindbar. Den Adressbereich im Speicher, der dafür zur Verfügung steht, nennt man die Heap. Auf der Heap werden einerseits Daten gespeichert, die über einen grossen Teil des Programm benötigt werden, und andrerseits alle Objekte, Arrays sowie Elemente, deren Grösse sich ändern kann. Da unterschiedliche Datentypen unterschiedliche Speichergrössen haben, wird die Heap sehr unübersichtlich, und die Verwaltung der Speicheradressen ist extrem wichtig und ziemlich aufwändig. Werden einzelne Speicherbereiche nicht mehr gebraucht, sollten sie freigegeben werden, und später sollten die so entstandenen Lücken durch sog. Defragmentierung wieder geschlossen werden. ==== - Stack ==== Der Stack ist ein Speicher, der als Stapel angeordnet ist: Es kann immer nur auf das oberste Element zugegriffen werden, und beim Zugriff wird das oberste Element gewöhnlich auch gerade entfernt, sodass nun das zweioberste Element wieder gelesen werden kann. Somit müssen die Elemente, die auf dem Stack abgelegt sind, in exakt umgekehrter Reihenfolge wieder gelesen werden. Der Prozessor hat für den Stack spezielle Befehle, die schnell arbeiten und wenig Verwaltung erfordern: PSH (Push) und POP. Der Stack wird eingesetzt für * die Speicherung der Parameter bei Funktionsaufrufen * funktionslokale Variablen. ===== - Funktionen ===== Funktionen sind Programmteile, die in genau gleicher Art immer wieder benötigt werden. Anstatt überall die Codezeilen erneut einzufügen, wird sie einmal geschrieben und von überall her aufgerufen. **Aufgabe 10:** * Schreibe ein Programm, das * zwei Zahlen einliest und auf dem Stack ablegt * die Funktion 'Addition' aufruft * das Ergebnis vom Stack holt und ausgibt. * Schreibe (weiter unten) eine Funktion 'ADDTN', die * die beiden Zahlen vom Stack holt * addiert * und das Ergebnis wieder auf den Stack legt. ++++Lösung| INP R0,2 INP R1,2 PSH R0 PSH R1 JMS ADDTN POP R0 OUT R0,4 HLT ADDTN: POP R1 POP R0 ADD R0,R1 PSH R0 RET ++++ **Aufgabe 11:** * Erweitere das Programm aus Aufgabe 10, dass es * noch eine dritte Zahl einliest * mit der Funktion 'ADDTN' zur Summe der ersten beiden Zahlen addiert. * das Ergebnis vom Stack holt und ausgibt. ++++Lösung| INP R0,2 INP R1,2 PSH R0 PSH R1 JMS ADDTN POP R0 INP R1,2 PSH R0 PSH R1 JMS ADDTN POP R0 OUT R0,4 HLT ADDTN: POP R1 POP R0 ADD R0,R1 PSH R0 RET ++++ **Aufgabe 12:** * Schreibe ein Programm, das * eine (kleine!) Zahl einliest * die Zahl an die Funktion 'FCLT' übergibt * den Rückgabewert ausgibt. * Schreibe (weiter unten) eine Funktion 'FCLT', die die Fakultät der übergebenen Zahl berechnet und zurück gibt. ++++Lösung| Iterativ: INP R0,2 PSH R0 JMS FCLT POP R0 OUT R0,4 HLT FCLT: POP R0 MOV R1,#1 //Zähler MOV R2,#1 //Produkt LOOP: MUL R2,R1 ADD R1,#1 CMP R1,R0 BLE LOOP PSH R2 RET Rekursiv, aber mit Stack Underrun: INP R0,2 PSH R1 JMS FCLT POP R0 OUT R0,4 HLT FCLT: SUB R0,#1 CMP R0,#1 BLE DONE PSH R0 JMS FCLT POP R1 POP R0 MUL R1,R0 BRA END DONE: MOV R1,#1 END: PSH R1 RET Rekursiv: INP R0,2 PSH R0 JMS FCLT POP R0 OUT R0,5 HLT FCLT: POP R0 MOV R1,#1 LOOP: CMP R0,#1 BLE DONE MUL R1,R0 SUB R0,#1 BRA LOOP DONE: PSH R1 RET ++++ ===== - Rekursion ===== Beachte dazu den Artikel in [[https://de.wikipedia.org/wiki/Rekursion|Wikipedia]], vor allem die Begriffserklärung und den Abschnitt 3. Die Fakultät kann man nicht nur wie in Aufgabe 12 **iterativ**, sondern auch **rekursiv** berechnen: Die Funktion [[Fakultät (Mathematik)|Fakultät]] einer natürlichen Zahl $n \geq 1$ ist definiert als das Produkt der Zahlen 1 bis $n$: $$n! = 1\cdot 2 \cdot 3 \dotsm n$$ Das lässt sich auch als Zahlenfolge und somit rekursiv darstellen: $$(n+1)! = (n+1)\cdot n!$$ Das lässt sich auch genauso programmieren. In C# könnte eine solche Methode Fakultät() etwa so aussehen: static long Fakultät(long Zahl) { long lRet = 1; if (Zahl > 1) { lRet = Zahl * Fakultät(Zahl - 1); } return lRet; } **Aufgabe 13:** * Schreibe ein Programm, das * eine (kleine!) Zahl einliest * die Zahl an die Funktion 'FCLT' übergibt * den Rückgabewert ausgibt. * Schreibe (weiter unten) eine Funktion 'FCLT', die die Fakultät der übergebenen Zahl berechnet und zurück gibt * Die Berechnung soll rekursiv erfolgen, dh. FCLT ruft sich selber immer wieder auf. * Dabei muss zwingend ein Abbruchkriterium programmiert werden! ++++Lösung| Iterativ: INP R0,2 MOV R1,#1 //Zähler MOV R2,#1 //Produkt LOOP: MUL R2,R1 ADD R1,#1 CMP R1,R0 BLE LOOP OUT R2,4 HLT Rekursiv, aber mit Stack Underrun: INP R0,2 PSH R0 JMS FCLT POP R0 OUT R0,4 HLT FCLT: SUB R0,#1 CMP R0,#1 BLE DONE PSH R0 JMS FCLT POP R1 POP R0 MUL R1,R0 BRA END DONE: MOV R1,#1 END: PSH R1 RET Rekursiv: INP R0,2 JMS FCLT OUT R1,5 HLT FCLT: PSH {R0,LR} SUB R0,#1 CMP R0,#1 BLT DONE JMS FCLT BRA BACK DONE: MOV R1,#1 BACK: POP {R7,R0} MUL R1,R0 BRA R7 RET ++++