Das war's für heute. Bis zum nächsten Mal. So, schönen guten Tag. Assembler-Programmierung, Assembler und Rechner-Architektur-Grundlagen. Was ist die Ausgangssituation für unsere Betrachtung? In der Pionierzeit hatte man die wesentlichen Komponenten der Hardware bereits als solche identifiziert. Die haben sich im bis heute nicht geändert. Wusste also ziemlich früh in der Genese der Informatik, welche Komponenten wie zusammenwirken müssen, um eine universelle programmierbare Maschine konstruieren zu können. Genau darüber werden wir im weiteren Verlauf des Vortrags reflektieren. Und wir erinnern uns, dass die Hardware als solche die Plattform bietet, die unverändert zur Verfügung steht und ihre Flexibilität dadurch gewinnt, dass Programme auf ihr laufen können. Die Programme machen die Hardware flexibel einsetzbar, weil die Hardware so konstruiert ist, dass sie diese Flexibilität eben besitzt. Man muss sich fragen, welche Hardware Bestandteile, welche Komponenten zu einer Architektur zusammengefügt. Leisten diese Flexibilität und wie können Programme so abgearbeitet werden, dass das ganze Programm unabhängig passiert. Also der Befehlszyklus. In den Anfangsjahren waren die Hardware-Architekten immer auch Programmierer. Und andersrum, denn man musste intime Kenntnisse der zugrunde liegenden Hardware besitzen, um die Rechner überhaupt nutzen zu können. Woran lag das? Die Hardware war sehr teuer, es gab nur ganz wenig davon und man musste die Daten auf Assemblerebene von Register zu Register transferieren. Dazu musste man ganz genau wissen, wie die Hardware eigentlich intern arbeitet. Und mit der Zeit und der Verbreitung von Rechnern kam auch die Software hinterher, was das Design angeht. Es ist Software Engineering entstand und das Übergreifziel von höheren Programmiersprachen ist es gerade, die Details der zugrunde liegenden Maschine zu verbergen. Sprich, man soll nicht mehr wissen, wie viele Register es gibt, was die genau für eine Aufgabe haben und wie die ganzen architekturellen Merkmale hardwareseitig zusammenwirken. Weil das dem menschlichen Denken hinsichtlich einer Programmieraufgabe nun mal recht weit entrückt ist. Wenn man sich jetzt mit Mikrocontrollern als ein ganz weites Feld der Praxis beschäftigt, dann hat man so einen Hybridfall vorliegen. Man muss nicht zwangsweise jedes Detail des Mikrocontrollers, der zugrunde liegenden Funktionen, Hardware-Plattformen, beispielsweise den ATmega-Prozessor in der Arduino-Mikrocontroller-Familie kennen. Aber man braucht schon mehr Kenntnisse, als das jetzt ein Software-Ingenieur benötigt, der Anwendungsprogramme in einer höheren Programmiersprache wie Java oder ähnliches schreibt. Das heißt, so eine kleine Renaissance, der Hardware näheren Betrachtungsweise kommt über den Mikrocontroller in die Informatik, was die Lehre angeht an Schulen und Hochschulen, da ist momentan das Physical Computing so die Flagge unter dem der Mikrocontroller segelt, aber in welcher Form das dann auch jeweils weitergetragen wird. Und Mikrocontroller sind auch in der industriellen Praxis die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die, die die häufigste Plattform von Rechnern überhaupt. Ja, das ist nicht der Desktop-Rechner, sondern Mikrocontroller, weil das in Form von eingebetteten Systemen an allen möglichen Stellen verbaut ist, die man oftmals nicht sieht. Das ist ja gerade der Sinn der Sache. Aber wenn Sie sich mit Mikrocontrollern beschäftigen, beispielsweise mit der Arduino-Plattform, haben Sie die Schnittstelle zwischen dem, was heutzutage eher als Informatik bezeichnet wird, Datenbanken, Programmierung, vielleicht künstliche Intelligenz und solche Dinge und der den Bereich, der früher heute teilweise noch so als technische Informatik betrachtet wird wozu auch die Elektronik gehört. Das ist ein sehr spannendes Anwendungsfeld und da kann es nicht schaden, sich auch in gewissem Maße mit der Assembler-Programmierung zu beschäftigen, also der einzigen Sprache, die ein Prozessor wirklich versteht. Bedenken Sie immer, egal in welcher höheren Programmiersprache Sie programmieren, Ihr Rechner kann genau eine Sprache verstehen, nämlich seine Maschinensprache und keine andere. Wie sieht es jetzt aus? Welche Komponenten braucht man, um diese Universalität der Rechner? Sicherzustellen, wie sieht die Architektur aus? Das heißt, wie wirken diese Komponenten zusammen und wie wird Software abgearbeitet, sodass die universelle Maschine zur universell programmierbaren Maschine wird? Diesen zentralen Fragen werden wir nachgehen. In der Pionierzeit gab es im Wesentlichen zwei architekturelle Ansätze, sind einmal die Harvard und die von Neumann Architektur. Die unterscheiden sich darin, dass in der Harvard Architektur Programme und Daten in getrennten Speichern vorgehalten werden, während die von Neumann Architektur beides in einem gemeinsamen Speicher verwaltet. Die von Neumann Architektur hat sich in Anführungsstrichen durchgesetzt, die ist zumindest am weitesten verbreitet. Allerdings werden sie gerade im Mikrocontrollerbereich noch viele Architekturen nach dem Harvard-Prinzip finden. Der ATmega-Prozessor, der beispielsweise auf der Arduino-Plattform verbaut ist, erfolgt der Harvard-Architektur wie andere Mikrocontroller auch. Jetzt wird der Informatik oft nachgesagt, dass sie sich schnell ändert. Das ist so halb wahr, es gibt Bereiche, die sich in der Tat sehr schnell ändern, aber die Grundlagen der Informatik, auch der technischen Informatik und ganz besonders der Rechnerarchitektur, die sind seit Jahrzehnten stabil. Zwar gibt es natürlich Spezial-Hardware für Bildverarbeitung im medizinischen Bereich beispielsweise oder Grafikprozessoren, die nach anderen Prinzipien arbeiten. Aber der universalen Rechner, der ist heute genauso wie zu Gründerzeit auch. Nur halt sind die Bestandteile viel schneller geworden. Wenn dem also so ist, wenn man einen universell einsetzbaren, programmierbaren Rechner konstruieren kann, welche Komponenten braucht man? Was muss man können und wie lässt sich das auf die Komponenten runter bräuche? Reflektiert man darüber, dann wird man feststellen, so ein Rechner muss natürlich rechnen können. Das heißt, das ist eine Recheneinheit, die in irgendeiner Form besitzt. Der muss Ergebnisse und Zwischenergebnisse speichern können. Das heißt, er muss eine Speichermöglichkeit besitzen. Es muss einen Chef geben, der das Ganze koordiniert, eine Steuerzentrale. Und es muss eine Kommunikation mit der Außenwelt stattfinden können. Spricht Daten, Sensorwerte. Die müssen in das System gelangen, die werden dann verarbeitet und das System muss aufgrund der Verarbeitung dann irgendwelche Ausgaben tätigen, damit über Aktoren Reaktionen erfolgen können. Da haben sie wieder das EFA-Prinzip. Eingabe, Verarbeitung, Ausgabe. Und mit diesen vier prinzipiellen Komponenten können sie praktisch alles machen. Wer macht denn jetzt was? Naja, die Recheneinheit ist der Prozessor. Wir werden gleich noch Details kennenlernen, welche Komponenten auf dem Prozessor das Ding jetzt leisten. Speichermöglichkeit, Hauptspeicher. Es gibt auch andere Speicher, Register beispielsweise, aber die großen Datenmengen während der Abarbeitung, die landen im Hauptspeicher. Die Steuerzentrale wird durch die Steuerzentrale, das Steuerwerk umgesetzt und die Kommunikation mit der Außenwelt ist die Ein- und Ausgabeeinheit. So. Das heißt also, wir können uns diese vier Blöcke mal aufzeichnen und so passiert natürlich noch nichts, weil die im luftleeren Raum jeder für sich schwebt. Wir müssen die irgendwie verbinden. Wenn man das macht, dann erinnert man sich, dass die Leitungen "Bussystem" heißen und dass es davon drei Stück gibt. Datenbus, Adressbus und Steuerbus. Und wie die Namen schon verraten, laufen die Daten über einen separaten Bus. Das ist der Datenbus. Die Adressen über den Adressbus und die Steuersignale über den Steuerbus. Kann man nicht nur ein Leitungs-Konglomerat sozusagen bilden? Ja, kann man. Nur bedenken Sie bei allem, was man so lernt, dass unterschiedliche Kriterien Designentscheidungen sind. Wenn es nur einen Bus gibt, also nur eine Straße, dann ist da ganz schnell Stau. Die Parallelisierbarkeit, der gleichzeitig Abarbeitbarkeit von Dingen sind an Grenzen gesetzt. Haben Sie Daten, Adressen und Steuersignale getrennt? Können Sie gegebenenfalls Dinge parallel erledigen? Und daher trennt man diese. Verbindungsleitung auf in die drei untereinheiten. Der Prozessor selbst, der besteht im Wesentlichen aus der Einheit, die tatsächlich rechnet im Rechenwerk, der alu, arithmetisch logische Einheit, und dem Chef, der koordinativen Einheit im Leit- oder Steuerwerk. Bedenken Sie, wenn Sie Schaltbilder lesen, dass das Bussystem in der Regel, je nachdem was im Schaltbild gerade hervorgehoben werden soll, durch einen Strich einfach gekennzeichnet wird, da sind dann aber sehr viele Leitungen hinter. Wenn Sie bei 8-Bit-Systemen zum Beispiel den Datenbus betreiben oder betrachten, dann sind da 8 Leitungen hinter dem Strich verborgen. Da verliert man völlig den Überblick, wenn man das jedes Mal aufzeichnet. Das sieht da eigentlich recht übersichtlich aus und ist es auch vom Strukturbild her. Diese vier Komponenten samt Verbindungsleitung und dem Bussystem ermöglichen es tatsächlich eine universelle Maschine zu konstruieren. Nur macht die noch nichts, sie muss erst mal programmiert werden. Es ist die Frage, naja, also wir brauchen genauso wie für eine universelle Hardware auch ein universelles Verfahren, nachdem diese universelle Hardware die Programme abarbeitet. Und das muss Programm unabhängig funktionieren. Das Abarbeitungsprinzip ist Programm unabhängig. Es wird zwar durch das Programm beeinflusst, aber das Prinzip der Abarbeitung von Programmen ist natürlich vom Programm selbst unabhängig. Wie sieht so ein Befehlzyklus dann also aus? Einfacher könnte er kaum sein. Man holt sich den nächsten Befehl, schaut nach welcher Befehl es denn ist, und dann kann man es sich ansehen. Und dann kann man es ansehen. Und dann kann man es ansehen. Führt ihn aus und wiederholt das bis der Strom weg ist. Fertig, das war's. Das ist der Befehlszyklus. Das heißt, egal was sie programmieren, egal in welcher Programmiersprache, es funktioniert immer in diesen drei Schritten. Die Hochsprache wird über den Compiler in Assembler-Sprache umgewandelt. Nur diese Sprache versteht der Prozessor. Und dann wird Schritt für Schritt, Befehl für Befehl, nach genau diesem Schema abgearbeitet. Wir holen den nächsten Befehl, schauen nach, um welchen Befehl es sich handelt, führen die Aktion, die der Befehl verlangt, aus, holen den nächsten Befehl und das Ganze geht so lange weiter, bis der Strom weg ist. Jetzt ist das noch mal etwas detaillierter aufgeführt. Wir fangen an Befehl holen, wird im Englischen als Fetch bezeichnet. Dann schauen wir nach welcher Befehl denn vorliegt, Decode und führen den Befehl aus Execute. Jetzt sollen wir ja den nächsten Befehl holen. Und da gibt es zwei Befehl. Verschiedene Verfahren. Wenn es sich nicht um einen Sprung handelt, dann wird der Befehlszähler um eins erhöht. Dieses um eins erhöhen müssen Sie im übertragenen Sinne verstehen. Was heißt denn jetzt hier um eins? Immer um eine Wortgröße. Befehle haben eine gewisse Breite und sie müssen um auf den nächsten Befehl zu kommen, so und so viel Bit weiter springen. Das ist das um eins erhöht. Das verbirgt sich dahinter. Zum nächsten Befehl könnte man auch sagen. Wenn ein Sprung vorliegt, dann setzen wir den Befehlszähler nicht auf den nächsten Befehl, sondern auf die Adresse des Befehls, wohin gesprungen werden soll. Auf die Sprungadresse. Das heißt, wir brauchen so ein ganzes zentrales Register, den befehlt Sailor, den Instruction Pointer. Und da sind wir auf Basis der letzten Fragen, die man sich klar machen muss und das sind auch gleichzeitig die Antworten, die man kennen muss, wenn man programmiert. Das ist im Wesentlichen der zentrale Teil des Programmiermodells, also der Dinge, die der Programmierer vom System hardwarenah wissen muss. Wo landet der nächste Befehl? Wo steht die Adresse des nächsten Befehls? Und wo stehen Statusmeldungen? Die Antworten sind eigentlich recht gut zu merken. Wo landet der Befehl? Im Befehlsregister. Man hat also einen kleinen, sehr schnellen, auf dem Prozessor liegenden Speicher vorgesehen. Ein Register und das Register, wo der aktuelle Befehl landet, damit die ... der Prozessor ihn abarbeiten kann, ist das Befehlsregister im englischen IAR Instruction Register genannt. Okay. Dann brauchen wir ja noch den Folgebefehl bzw. die Adresse des Folgebefehls. Wo steht denn die? Im Befehls-Zähl-Register. Und da gibt es zwei Namen für Instruction Pointer oder Program Counter. Und Statusmeldungen, die stehen in den Status- oder Zustandsregistern, oftmals auf Flag-Registern genannt. Machen sie sich klar, wenn sie je Hardware näher sie programmieren, desto mehr Automatismen stehen ihnen nicht zur Verfügung. Wenn sie beispielsweise Rechenoperationen durchführen, auf Assembler-Ebene. Dann müssen sie als Programmierer sicherstellen, dass da kein Bereichsüberlauf stattfindet. Sie haben ja zum Beispiel 8 Bit pro Operant und wenn sie dann die addieren, könnte ja sein, dass das Ergebnis größer ist, als in 8 Bit reinpassen. Sie erreichen einen Überlauf. Das Programm interessiert sich dafür nicht. Das müssen sie dann als Programmierer überprüfen. Und wo kriegen sie aber die Informationen her? Vielen Dank für's Zuschauen. Ob ein Überlauf stattgefunden hat oder nicht, dafür sind diese Flag-Register vorgesehen. Die müssen sie sich also anschauen, welche bietet ihr Prozessor und wie können sie die ansprechen? Also, der aktuelle Befehl steht im Befehlsregister, die Adresse des Folgebefehls im Befehls-Zählregister und die Statusmeldung in den Flag-Registern. Entsprechend haben sie im Programmiermodell auch diese ganz zentralen Register vorrätig. Sie sehen hier in der Abbildung genau das, das Befehlsregister, den Befehlszähler und Zustandsregister und dann haben sie im Rechenwerk neben der arithmetisch-logischen Einheit, die dann die Logikoperation und Rechenoperation tatsächlich durchführt, auch Arbeits- und Statusregister. Arbeitsregister dafür, dass sie mal Werte aus dem Speicher laden können und vorhalten können und Zwischenergebnisse können sie dann manchmal auch drin speichern. Statusregister entsprechend, um Status abzufragen. Wenn sie dann ein Modellsystem hernehmen, was dringend empfohlen werden kann. Sie können natürlich auch mit den echten Prozessoren arbeiten, aber da werden sie ganz schneller schlagen von der Komplexität, die diese Geräte haben. Sie können natürlich beispielsweise den ATmega hernehmen. Der auf dem Arduino verbaut ist und den in den Assembler programmieren. Da könnt ihr ja machen. Nur ist der Prozessor sehr mächtig und hat ja den den Nachteil, dass man da ganze Zeit unter Wasser ist, bevor man irgendwie produktiv sein kann. Und ob das fürs Lernen so gut ist, sei mal dahingestellt, weil sie sich mit vielen Dingen beschäftigen müssen, die sie eigentlich gar nicht unbedingt wissen. Wollen, wenn sie nicht professioneller Programmierer auf diesem Gebiet werden wollen oder müssen, wenn man nur unterrichtet, dann der reichen der Grundkenntnisse und die kann man sich am besten anhand von didaktischen Werkzeugen, die für diesen Zweck entwickelt wurden, erarbeiten. Ein bewährter auch an Schulen bewährter Modellrechner ist der CSI. Und wenn sie die gerade reflektierte Theorie. Nochmal hier im kleinen Bild anschauen. Dann haben wir ja diese zentralen Register. Wir wissen das ist Steuerwerk, Rechenwerk als Teil des Prozessors und das finden Sie genau hier wieder. Der Befehlszähler, den haben Sie hier. Das Befehlsregister haben Sie hier. Dann haben Sie hier ein Rechenwerk. Was XR und AK ist, das müssen Sie vermuten. Das müssen Sie dann natürlich in der Dokumentation des Modellrechners nochmal nachschlagen, so wie Sie das bei realen Rechnern auch machen müssen. Und dann haben wir hier Arbeitsregister und Statusregister. Die sind hier nicht vorgesehen, aber ein großer Hauptspeicher. Und da haben Sie natürlich einen gewissen Befehlzeits, den müssen Sie sich auch anlesen. Und hier haben Sie die Möglichkeit, die Geschwindigkeit zu regulieren. Sodass Sie tatsächlich dann sehen können, wenn jetzt hier irgendwas passiert, wie die Daten über den Bus in die CPU wandern und dann da irgendwas passiert und dann wandern die wieder zurück und landen irgendwo im Speicher. Das heißt, das, was die Befehle machen, kann man tatsächlich visualisiert nachverfolgen. Schritt für Schritt da durchgehen. Das ist ja nicht dafür gedacht, dass Sie da lange Programme schreiben. Das macht man in Assembler ja sowieso möglichst nicht. Dafür gibt es ja Hochsprachen. Aber fürs Lernen, fürs Verständnis ist das äußerst hilfreich. Was sind jetzt Assemblerbefehle? Bedenken Sie, Assemblerbefehle sind immer noch für Menschen halbwegs lesbar, da steht zumindest "add", wenn sie addieren wollen. Das ist aber eine Abkürzung für eine Binärsequenz, die dahinter steht. Das ist also schon streng genommen, müssen sie mit Nullen und Einsen programmieren, aber die werden 1 zu 1 umgesetzt in entsprechenden Namen, halbwegs sprechend zumindest, sodass der Mensch da überhaupt noch irgendwie programmieren kann. Vielen Dank für's Zuschauen! Es gibt nicht die Assembler-Sprache, sondern jede Prozessorfamilie hat eine eigene, ist also Prozessorfamilien abhängig und wenn sie da durchgehen, sagen wir mal, vielleicht ATMEGA mäßig, wollen sie sich auch auf Assembler-Ebene ein bisschen intensiver mit beschäftigen, dann können sie sich oder müssen sie sich natürlich den Befehlsumfang erarbeiten und da unterteilt man nach Befehlsarten und nach Befehlsumfang. Der Befehlsumfang folgt dem RISC- oder KISC-Modell "Reduced" oder "Complex Instruction Set Computer". Die Namen sind mehr Schall und Rauch als alles andere, also auch bei "Reduced Instruction Set Computer" können sie nicht von 5 Befehlen ausgehen. Der ATMEGA beispielsweise, der auf dem Arduino verbaut ist, der folgt ... dem RISC-Modell, also dem "Reduced Instruction Set" und hat irgendwie 140 plus Befehle. Also, das ist mehr historisch verankert und vielleicht hat es tiefere Gründe im Prozessordesign, das mag sein, aber für den Anwender, auch für den Assembler- irgendwie durchdringen muss. Natürlich lernt man am meisten durchmachen, wie immer beim Programmieren, aber da kommen dann die Befehlsartend ins Rennen. Es gibt halt, wie sie auch von den Hochsprachen wissen, es gibt Sprungbefehle, es gibt logische Befehle, arithmetische Transportbefehle und Prozessor- Kontrollbefehle. Ja, was weniger bekannt ist in den Hochsprachen, was sie darauf weder achten sollen noch achten müssen, sind halt Transportbefehle und Prozessor-Kontrollbefehle. Es gibt ja gerade Hochsprachen um die möglichst gänzlich ignorieren zu können. Das können sie in der Assembler natürlich nicht. Sie müssen da jedes Bit höchst persönlich von A nach B schicken und kontrollieren, ob das auch angekommen ist. Was man wissen muss und auch bei der Einarbeitung in neue Technologien, dann Prozessor-Familien, die man immer mal im Hinterkopf behalten muss, sind die verschiedenen Adressierungsarten. Da haben sie in der Hochsprache so gut wie gar nichts mit zu tun. Auf hardware-nahe Arbeit natürlich umso intensiver. Welche Adressierungsarten gab es da? Noch mal, es ist einmal die unmittelbare Adressierung. Das heißt, es gibt die Möglichkeit, die Operanten, oder den Operant, je nachdem, den Befehl direkt mitzugeben. Ja, wenn Sie eine Konstante haben bei einer Rechenoperation, da brauchen Sie gar keinen Speicherzugriff, das ist das springende Punkt, sondern Sie können den Wert, den Sie da verarbeiten wollen, einfach an den Befehl anhängen, fertig, unmittelbarer Adressierung. Zu unterscheiden von der absoluten Adressierung, da gibt es nämlich einen Speicherzugriff. Sie haben den Befehl und da rangetackert ist die Adresse, die direkte Speicheradresse, wo der Operant im Speicher zu finden ist. Meistens hexadezimal codiert. Absolut, weil sie tatsächlich dahinspringen. Nicht absolut, sondern relativ ist die relative Adressierung. Wie funktioniert die? Die Adresse, die an dem Befehl angehangen wird oder ist, wird noch um ein Offset verändert, um tatsächlich an den Operanten im Speicher zu kommen. Sagen wir mal, die Adresse ist hexadezimal 5, ja und der Offset ist 10, dann addieren Sie noch den Offset zu der Adresse und kommen dann bei der Speicheradresse sowieso, da finden Sie dann den tatsächlichen Operanten im Speicher. Relative Adressierungen, sehr häufig. Bei der indirekten Adressierung haben Sie praktisch Doppelte Indirektheit, denn die Adresse, die sie dem Befehl mitgeben, ist nicht die wirkliche Adresse des Operanten, sondern das ist die Adresse im Speicher, wo die Adresse gefunden wird, unter dem der Operant zu finden ist. Ja, also... Wenn jetzt hier zum Beispiel beim Befehl "Adresse 15" steht, dann gucken Sie im Speicher an der Stelle 15 nach. Was da drin steht, interpretieren Sie als Adresse, sagen wir mal, da steht "45" drin und dann gucken Sie an der Speicherstelle "45" nach, da steht dann der Operanten, wie Sie eigentlich wollen. Das sind so gewöhnungsbedürftige Mechanismen, die natürlich in der Hochsprache verborgen werden sollen. Und auch verborgen werden, die aber letzten Endes auf Maschinen-Ebene genauso umgesetzt werden müssen. Denn wie sollen sie sonst universelle Programmierbarkeit und die damit einhergehende Flexibilität garantieren? Ja, das geht nur mit diesen Mechanismen, die natürlich sehr fehleranfällig sind. Da ist langsames Programmieren dann wieder die schnelle Variante. Und die schon auch gewöhnungsbedürftig sind, wenn es ums Anwenden geht. Und um die Denkweise, die dahintersteht, da muss man erstmal eine ganze Weile sich reindenken. Gut, viel Erfolg auf Ihrer Reise in die hardwarenahe Programmierung.