Nachdem nun die hauptsächlichen Sachen eigentlich "durch" sind, kann man evtl. mal irgendwann noch ein zwei seltene ("unnütze") Themen ergänzen und evtl. auch noch die schonmal angesprochene "Codebesprechung" öffnen.
Vor allem aber will ich ein kleines Kapitel über eine andere Prozessorarchitektur anfügen, die jeder 3te Erdenbewohner in Form eines kleinen Rechenzentrums (mittlere Datentechnik) in seiner Jackentasche spazierenträgt.
Es soll kurz um ARM gehen.
Warum ? Gar nicht unbedingt nur wegen der Verbreitung, sondern v.a. wegen der schicken Konstruktion der Architektur und dem ziemlich guten Bezug zum 6502 - der natürlich nicht wirklich da ist, aber irgendwie hat das gefühlt viel von einer direkten Fortsetzung. Und da der BBC Micro ja primär auch mit einem 6502 kam, ist diese Idee evtl. gar nicht so abwegig. Vor allem aber macht das auch Sinn, weil man dann noch eine andere Sache und evtl. die Gemeinsamkeiten und auch Unterschiede gut erkennt - mithin dann doch die "Gleichartigkeit" all dieser Maschinen auf dieser Ebene sieht.
Die andere Sache ist: Es ist komplett möglich, sowas jetzt zu verstehen ! Inklusive komischer Begriffe der besonders informatischen Art.
ARM stand für Acorn Risc Maschines. Es steht jetzt für Adavanced Risc Maschines. Und beides hat das RISC im Namen.
Dabei handelt es sich um eine Art Prozessoren, die Mitte der 1980er Jahre nach dem Projekt 801 der IBM eingeführt worden waren und dann zunehmend den Workstation und HighEnd PC-Bereich angeführt haben. Und aktuell ist das Thema auch immer noch.
Diese CPUs machen einiges ein klein wenig anders, als bisher (1970er). Insbesondere verzichten sie auf komplexe Befehle - quasi schonmal eine Gemeinsamkeit mit dem 6502. Dazu kommt, daß sie eine große Anzahl Register mitbringen - beim 6502 hat man ja schön gesehen, wie nervig es sein kann, wenn nur ein einziges echtes Rechenregister da ist. Und sie können - das ist eine Einschränkung - nicht direkt im RAM operieren, sondern nur auf den Registern. Das macht die Sache schnell, aber Kommandos wie AND $5F00 sind nicht möglich. Man muß stattdessen die Register mit Werten beladen, diese umrechnen und wieder abspeichern, was sich Load-Store-Architektur nennt.
Der große Vorteil davon ist nun, daß bei RISC eine sogenannte Pipeline möglich wird, weil ja jeder Befehl nur auf den Registern läuft und das Laden davon abgetrennt ist. Dies bedeutet dann, daß die CPU quasi parallel einen Befehl abarbeitet und dazu den nächsten schonmal aus dem RAM holt. Sofern man diese Kette nicht unterbricht, ist das logischerweise "schneller".
Und was gibt es nun für Befehle ?
Natürlich laden und speichern. Logisch.
Und wohin ? - in die Register. Davon gibt es 16 Stück ! Davon R0 bis R12 frei benutzbar. Und alle 32-Bit breit.
Und dort kann man dann mit den Werten was machen.
Zum Beispiel ADD oder ADC zum Addieren. ADC addiert mit Carry-Flag, ADD ohne.
und analog SUB und SBC zum Subtrahieren.
Dabei schreibt man Befehle immer in so einer Form:
ADD Register-für-Ergebnis, Register-für-Operand1, Register-für-Operand2
Will man Register R12 und R7 addieren, schreibt man also ADD R0, R12, R7.
Will man 2 zum Register R7 addieren, schreibt man ADD R0, R7, #2.
Will man 1 zum Register R7 addieren und in Register R7 das Ergebnis haben, schreibt man ADD R7, R7, #1
Und das sollte bekannt sein ! Es ist ja i.P. der wichtige INX, INY Befehl des 6502 - nur daß man das Zählregister selbst wählen kann.
Konsequenterweise gibt es dann keine Inkrement/Dekrement Befehle als "Extras".
(Dekrement wäre SUB R7, R7, #1)
Um eine Zahl in ein Register zu schreiben, benutzt man entweder den Load Befehl oder MOV.
MOV R7, #123
bringt den Startwert 123 nach R7.
MOV R0, R1
bringt den Inhalt von R1 nach R0 (das Erste ist immer das Ergebnisregister).
Und auch das ist i.P. bekannt: Es entspricht ja den TAY, TAX, TXA, TYA Befehlen, nur wieder viel genereller. Man kann jeden Registerinhalt in jedes andere nach freier Wahl kopieren.
Und: Man kann zusätzlich noch "austauschen" - Der Swap Befehl SWP tauscht die Inhalte zweier Register (SWP R0, R1).
Und nun kommt eine Besonderheit, des ARMs - die so besonderes gar nicht ist, aber an einer interessanten Stelle sitzt. Man kann nämlich i.a. einen Operanden direkt IM GLEICHEN BEFEHL shiften. Meist ist das der letzte angegebene.
MOV RO, R1 LSL#2
macht einen LogicShiftLeft um zwei Stellen mit dem Register R1 - multipliziert den Wert aus R1 also mit 4 - und kopiert ihn nach R0.
Das Gleiche geht mit anderen Befehlen wie
ADD R3, R5, R6 LSR#4
was R6 durch 16 teilt, dann diesen Wert zu dem in R5 addiert und das Ergebnis in R3 speichert.
Und das geht dann auch z.B. bei den Multiplikationsbefehlen (MUL, MLA) und auch bei den Logik-Befehlen AND, ORR, EOR
Ein Bit umschalten, wird so einfach zu einem
EOR R7, R7, #1 LSL#7 (wobei man das so wohl eher nicht benutzt)
und
ORR R2, R9, R9 LSL#16 verschiebt bei einem 32Bit Wert in R9 das untere Nibble in den oberen Bereich und kopiert das Ergebnis nach R2.
All diese SHIFTs kosten keine zusätzliche Rechenzeit.
Und man hat verschiedene SHIFT Befehle, ASL, ASR, LSL, LSR, ROR, RRX.
Also: arithmethisches Shiften li/re, logisches Shiften li/re, Rotieren und Rotieren mit CarryFlag.
Es fehlen noch Vergleiche und Flags.
Die gibt es auch - und wieder mit ein paar Besonderheiten.
CMP vergleicht zwei Sachen und setzt die Flags entsprechend. Also z.B. CMP R0, R1.
CMN vergleicht ebenfalls - aber der zweite Wert wird vorher mit NOT invertiert. CMN R0, R1 ist also eine Art CMP R0, NOT(R1).
Auf die Flags reagiert man dann wie am 6502 mit den Sprungbefehlen, den relativen Sprüngen. Und die heißen eigentlich auch genauso, wie dort:
Es wird immer ein B vorangestellt - wie Branch - und dann die Flagauswertung, z.B. NE - Not Equal - Zero Flag gesetzt. Es gibt z.B. neben
BNE noch BMI, BPL (Minus/Plus) oder BCC, BCS (CarryClear/Set) oder BVC,BVS (OverflowFlag Clear/Set). Bekannt...
Und es existieren andere Kombinationen von Flags bzw. sinnvolle Bezeichungen wie
BGE (greater-than-or-equal), BGT (greater-than), BLE (lower-than-or-equal), BLT (lower-than) - also hier Kombis aus Carry und Zero-Flag.
Das "Lustige" ist jetzt, der ARM kann nicht nur die Sprungbefehle in dieser Art, sondern quasi so ziemlich jeden Befehle durch Anfügen der Flagauswertungen in Abhängigkeit von den gerade gesetzten Flags ausführen oder eben nicht.
Man muß sich also den Sprungbefehl BNE eigentlich als einen Sprung vorstellen, der durch Flags eingeschränkt wird.
Genauso kann man aber auch einen Addierbefehl dadurch einschränken, indem man schreibt: ADDNE - addiere wenn Zero-Flag nicht gesetzt oder ADDEQ - addiere wenn Zeroflag gesetzt oder ADDPL - addiere wenn das Vorzeichenflag Plus anzeigt. Sowas nennt man Conditional Execution.
Und das wird unterstützt dadurch, daß man Flags NICHT SETZEN MUSS ! Soll heißen, die normalen Befehle fassen die Flags überhaupt nicht an, wenn man es nicht erlaubt. So wird ein ADD R0,R1,#$FFFFFFFF zwar das Register R1 ziemlich sicher überlaufen lassen, aber das CarryFlag bleibt davon erstmal unberührt. Das Gleiche bei MOV R4,#0 - sollte ja definitv eine "0" ergeben, aber das Zero-Flag wird nicht automatisch gesetzt.
Wenn man das haben möchte, hängt man noch was an den Befehl an, nämlich ein "S" - wie "Set Flags". D.h.
ADDNES R0, R4, R5 LSL#4
wird nur ausgeführt, wenn NE gilt (kein Zero-Flag). Es setzt dann aber selber wieder die Flags - wegen "S".
Dabei shiftet es R5 4 Stellen nach links (also R5 mal 16) und addiert den Wert zu R4. Das Ergebnis kommt nach R0. Wenn dabei 0 entsteht, wird das Zero-Flag gesetzt, bei Überlauf das Carry-Flag usf.
Dadurch ergeben sich Kombinationen, wie
ADDS R0,R0,#8
ADDNE R1,R1,#1
ADDEQ R2,R2,#1
BEQ irgendwohin
weiter
wobei nur das erste ADDS die Flags ändert und R0 um +8 hochzählt. Dann durch das zweite ADDNE das R1 erhöht wird, wenn keine "0" entstanden ist und später ein Sprung kommt, der aber das Zero-Flag aus dem ersten Kommando auswertet - das ADDNE hat ja keine Flags gesetzt.
Was macht das dritte ADDEQ ? Was wird hier gezählt ?
Nur das CMP ( CMN ) Kommando setzt immer Flags.
Sprünge über den gesamten Adressraum macht man mit B $Adresse und wenn man eine Subroutine, dort allerdings nicht so weit weg (bis ca. +/- 32 MByte), anspringt mit BL $Adresse (Branch with Link). Dabei wird einfach die aktuelle Adresse im Register R14 gemerkt. Und da das Register R15 generell als Programmcounter fungiert, kann man durch einfaches Umkopieren wieder zurückspringen ins Hauptprogramm: MOV R15, R14 oder allgemeiner MOV PC, R14. Quasi ein RTS.