Die RISC-Architektur, als "Reduced Instruction Set Computer"-Architektur eingeführt, hat starke historische Wurzeln. Die Entwicklung der ersten Prozessortypen war eher auf die Implementierung immer komplexerer Befehlssätze ausgerichtet (CISC-Architektur), wofür es mehrere Gründe gab:
Der Unterschied der Zugriffsgeschwindigkeit zwischen Hauptspeicher und Speicher innerhalb der CPU führte dazu, dass Befehle, die Programmsequenzen in der CPU implementierten, wesentlich schneller liefen als externe Sequenzen. Der Unterschied in der Geschwindigkeit zwischen Mikroprogrammspeicher und Hauptspeicher betrug etwa den Faktor 10. Dadurch konnte man die 10fache Anzahl an Befehlen für ein Mikroprogramm nutzen. Der Geschwindigkeitsunterschied existiert jedoch seit langem nicht mehr. Wie in Kapitel 8gezeigt wird, weisen neueste Forschungsergebnisse allerdings einen durchaus ähnlichen Weg, wenn auch aus anderen Gründen.
Durch die eingeführte Mikroprogrammierung war der Aufwand zur Erweiterung des Befehlssatzes relativ gering. Dies führte zum Beispiel zu nachladbaren Befehlssätzen bei Großrechenanlagen.
Die Einführung komplexer Befehle führte vor allem bei der Assembler-Programmierung zu kompakteren Programmen. Dadurch verringerten sich sowohl die Speicherkosten als auch die Ausführungszeit.
Für die Hochsprachenprogrammierung erhoffte man sich, durch komplexe Be-fehle eine Brücke zwischen Maschinen- und Hochsprache zu schaffen.
Der Zwang der Aufwärtskompatibilität brachte aus Marktgründen die Notwendigkeit mit sich, immer komplexere Befehlssätze zu implementieren.
Ein komplexer Maschinenbefehlssatz galt als Beweis der Leistungsfähigkeit eines Rechners.
Diesen Artikel und eine ganze Reihe weiterer Grundlagenthemen zu Prozessoren finden Sie auch in unserem tecCHANNEL-Compact "Prozessor-Technologie". Die Ausgabe können Sie in unserem Online-Shop versandkostenfrei bestellen. Ausführliche Infos zum Inhalt des tecCHANNEL-Compact "Prozessor-Technologie" finden Sie hier.
Abkehr von CISC
Die (virtuellen und realen) Vorteile gerieten in den 70er Jahren stark ins Schwanken. Folgende Effekte bewirkten eine Umkehr der Aussagen:
Die Fortschritte in der Speichertechnologie führten zu einer drastischen Verringerung des Geschwindigkeitsunterschieds zwischen Haupt- und CPU-Speicher und vor allem zu wesentlich billigerem Speicher.
Die unerwartet schlechte Ausnutzung des Befehlssatzes durch die Compiler bedeutete eine sehr geringe Ausnutzung der Möglichkeiten einer CPU. Nach Untersuchungen von IBM wurden in 80 Prozent des Codes nur fünf Prozent des Befehlssatzes verwendet. In 95 Prozent des Codeumfangs tauchten zehn Prozent aller möglichen Instruktionen und in 99 Prozent gerade einmal 15 Prozent aller Befehle auf. Daraus entstand die so genannte 10:90-Regel.
Die Ersetzung eines komplexen Maschinenbefehls durch mehrere einfache erwies sich gelegentlich sogar als zeitsparender.
Die Mikroprogramme wurden immer umfangreicher, so dass sie letztendlich nicht mehr in der CPU fest implementiert waren, sondern nachladbar gestaltet wurden. Die Folge waren Nachladestrategien, Schutzmechanismen sowie eine weitere Verkomplizierung der CPU und dadurch verlängerte Entwurfszeiten.
Die Komplexität von Mikroprogrammen, insbesondere das erforderliche hohe Maß an Parallelität, bedingte eine erhöhte Fehleranfälligkeit dieser Mikroprogramme. Entwicklungssprachen für diese Form der Programmierung waren nicht vorhanden, so dass die Komplexität letztendlich durch die Entwickler gemeistert werden musste.
Definition von RISC
Aus all diesen Gesichtspunkten ergaben sich neue Entwicklungsansätze für Mikroprozessorgenerationen. Die heutige Definition eines RISC-Prozessors, dessen Struktur erst in den nächsten Abschnitten dargestellt wird, kann anhand folgender Eigenschaften gut beschrieben werden:
Die Anzahl der Maschinenbefehle ist kleiner oder gleich 150.
Die Anzahl der Adressierungsmodi ist 4.
Die Anzahl der Befehlsformate ist 4.
Die Anzahl der allgemeinen CPU-Register ist 32.
Die Ausführung aller oder der meisten Maschinenbefehle erfolgt innerhalb eines (internen) Takts.
Der Speicherzugriff erfolgt nur über Load-/Store-Befehle.
Die Steuerung innerhalb der CPU ist fest verdrahtet.
Die CPU unterstützt in ihrem Befehlssatz höhere Programmiersprachen (beispielsweise durch CASE-Konstrukte).
Werden von diesen acht Bedingungen mindestens fünf erfüllt, kann die Architektur als RISC bezeichnet werden. Insbesondere die Eigenschaft der Befehlsausführung innerhalb eines Takts (zumindest bei Ausnutzung der Instruktions-Pipeline) erweist sich dabei als beschleunigend, denn dies führt tatsächlich zu schnelleren Programmen. Andererseits ist die pure Beschränkung auf wenige Befehle bei extremer Ausführung nicht sinnvoll, wie man an der 1-Befehl-Maschine erkennt: Diese Maschine kann lediglich den Befehl SUBBEQ X, Y, Z (subtract and branch if equal) ausführen, indem sie X = X-Y ausführt und an Z springt, wenn das Ergebnis 0 ist.
Es stellt sich damit die Frage nach dem Optimum zwischen Einfachheit und Ausführungsgeschwindigkeit. Diese Frage ist in den Projekten Anfang der 80er Jahre durchaus anders beantwortet worden als das heute der Fall ist. Der Streit um die Philosophie, CISC oder RISC, tritt jedoch Mitte der 90er Jahre klar in den Hintergrund; allein die Ausführungszeit von Befehlen ist noch relevant.
In diesem Kapitel wird der historische Weg von den Analysen der Befehlssätze und der Häufigkeit des Einsatzes einzelner Befehle über die Daten- und Kontrollflussanalyse in der CPU bis hin zur realen Performance-Bestimmung einer RISC-CPU mit verschiedenen Programmen nachvollzogen.
Analyse der Befehlssätze
Um die Reduzierung eines Befehlssatzes möglichst effektiv vornehmen zu können, formulierten die ersten Universitätsprojekte RISC I/II (Patterson, University of California at Berkeley, 1980) entsprechende Entwurfsziele. Im Anschluss daran konnten diverse Programme, in den Hochsprachen C und Pascal geschrieben, analysiert werden. Dabei bestand natürlich die Schwierigkeit, dass Compiler die Programme übersetzten, die für die herkömmlichen Architekturen optimiert waren. Die Entwurfsziele waren wie folgt definiert:
Ein-Zyklus-Befehle: Pro Takt sollte ein Maschinenbefehl ausgeführt werden. Der Komplexitätsgrad und die Ausführungsgeschwindigkeit eines RISC-Maschinenbefehls sollte in etwa zur VAX-II/780 (Mikroprogramm!) äquivalent sein.
Verzicht auf Mikroprogrammierung: Die Mikroprogrammierung sollte durch ein fest verdrahtetes Steuerwerk ersetzt werden. Hierdurch sollte neben der Schnelligkeit auch Chipfläche eingespart werden.
Einheitliches Befehlsformat: Sämtliche Befehle sollten das gleiche Format und insbesondere die gleiche Länge besitzen. Hierdurch sollte eine einfachere Dekodierung und eine einheitlichere Behandlung der Befehle auf Kosten der Programmlänge geschaffen werden.
Load-Store-Architektur: Das Interface zwischen Speicher und CPU sollte ausschließlich auf Load- und Store-Befehlen basieren, während alle anderen Befehle wie Arithmetik nur auf den Registern arbeiten durften. Dies implizierte eine größere Anzahl von Registern.
Unterstützung höherer Programmiersprachen: Um den Vorteil der Assembler-Programmierung, schnelleren Code zu erzeugen, zu relativieren, sollten Maschinenbefehle in der CPU so angeboten werden, dass Compiler sehr gut optimierten Code erzeugen könnten. Dies betraf insbesondere Verzweigungsbefehle und ist weiterhin ein aktuelles Thema (siehe nachfolgende Kapitel).
Keine Unterstützung für Gleitkomma-Arithmetik und Betriebssystem: Die Gleitkomma-Arithmetik hat man bewusst weggelassen. Sie ist in experimentellen Projekten meist hinderlich und prinzipiell ähnlich gestaltbar wie die Festkomma-Arithmetik. Interessanter ist hier der Verzicht auf Betriebssystembefehle wie etwa Supervisor-Calls, der prinzipieller Natur war.
32-Bit-Architektur: Projekte waren auf die (1980 avantgardistische) Datenbreite von 32 Bit ausgelegt; 8-, 16- und 32-Bit-Daten sollten bearbeitbar sein.
Typische Operanden und Befehle
Zur Festlegung des Befehlssatzes der RISC-I-Architektur musste man Programme auf zwei Charakteristika hin untersuchen: Typische Operanden und typische Befehle in C oder PASCAL sowie deren Übersetzung in Maschinensprache. Das RISC-I-Projekt zeigt demnach die grundsätzliche Wechselwirkung von Rechnerarchitektur und Compiler. Die acht Programme, die zur Analyse herangezogen wurden, bestanden aus:
P1: Pascal-Compiler, geschrieben in Pascal
P2: Unterprogramm in einem Entwurfssystem
P3: Pascal Prettyprinter
P4: Vergleichsprogramm zum Vergleich zweier Dateien
C1: C-Compiler, geschrieben in C
C2: Programm zum Zeichnen von VLSI-Masken-Layouts
C3: Programm zum Formatieren von Text
C4: Sortierprogramm unter Unix
Die typischen Programme der 90er Jahre, Textverarbeitung und Grafikprogramme, sind sicher unterrepräsentiert, die Mischung entspricht eher dem Standard von 1980. Die folgende Tabelle zeigt die dynamische Häufigkeit der Operanden im Code.
Operandentyp | P1 | P2 | P3 | P4 | C1 | C2 | C3 | C4 | Durchschnitt |
---|---|---|---|---|---|---|---|---|---|
Angaben in Prozent | |||||||||
Ganzzahlige Konstante | 14 | 18 | 11 | 20 | 25 | 11 | 29 | 28 | 20 |
Skalare | 63 | 68 | 46 | 54 | 37 | 45 | 66 | 62 | 55 |
Felder / Strukturen | 23 | 14 | 43 | 25 | 36 | 43 | 5 | 10 | 25 |
Die Unterstützung für die Operandentypen durch die Maschinenbefehle kann in mehrfacher Form erfolgen: Floating-Point-Befehle (hier nicht aufgeschlüsselt) sowie Adressierungsformen bieten hier eine Reihe von Möglichkeiten.
Die Hochsprachenkonstrukte in diesen Programmen lassen sich in verschiedene Kategorien einteilen: Zuweisungsbefehle (assign), Kontrollflussstrukturen (if, case, for, with, loop, while) und Prozeduraufrufe (call/return). Die in den Programmen auftretenden Häufigkeiten sind in folgender Tabelle dargestellt.
Befehlstyp | P1 | P2 | P3 | P4 | C1 | C2 | C4 | C4 | Durchschnitt Pascal | Durchschnitt C |
---|---|---|---|---|---|---|---|---|---|---|
Angaben in Prozent | ||||||||||
assign | 39 | 52 | 35 | 53 | 22 | 50 | 25 | 56 | 45 | 38 |
if | 35 | 30 | 36 | 16 | 59 | 31 | 61 | 22 | 29 | 43 |
call | 15 | 14 | 16 | 15 | 6 | 17 | 9 | 16 | 15 | 12 |
with | 2 | 0 | 5 | 13 | 2 | 2 | 3 | 5 | 5 | 3 |
loop | 5 | 5 | 5 | 4 | 9 | 0 | 1 | 1 | 5 | 3 |
case | 4 | 0 | 1 | 0 | 2 | 0 | 0 | 0 | 0 | kleiner 1 |
Gewichtete Häufigkeiten
Die dynamische Häufigkeit (hierin ist die Häufigkeit des Durchlaufs einbezogen) von Befehlen ist relevant, wenn man die tatsächliche Ausführungszeit bestimmt. Dies erfolgte durch eine Übersetzung in die entsprechenden Maschinenbefehle unter Berücksichtigung der notwendigen Speicherzugriffe (Code und Daten!). Folgendes Beispiel möge dies kurz erläutern:
Für den Vergleich werden drei Befehle, eine Variable und zwei Konstanten geladen (sechs Ladeoperationen). Die eigentliche if-Bedingung einschließlich des else-Zweiges benötigt nur eine Verzweigung (eine Ladeoperation), die beiden Datenzuweisungen laufen mit ein oder zwei Befehlen und einem Store-Befehl (drei bis vier Ladeoperationen) ab. Dabei ist diese Assembler-Übersetzung schon optimiert. Die sich aus diesen Untersuchungen ergebenden, gewichteten Häufigkeiten (durch Multiplikation) für die einzelnen Befehle ergeben dann folgendes Bild:
Befehlstyp | Dynam. Häufigkeit in Prozent (Pascal/C) | Gewichtung mit Anzahl der Maschinenbefehle (Pascal/C) | Gewichtung mit Anzahl der Speicherzugriffe (Pascal/C) |
---|---|---|---|
| |||
call/return | 15/12 | 31/33 | 44/45 |
loop | 5/3 | 42/32 | 33/26 |
assign | 45/38 | 13/13 | 14/15 |
if | 29/43 | 11/21 | 7/13 |
with | 5/0 | 1/0 | 1/0 |
case | 1/ kleiner 1 | 1/1 | 1/1 |
goto | 0/3 | 0/0 | 0/0 |
Konsequenzen für eine RISC-CPU
Nach den Analysen der Befehlssätze und der zum Betrieb notwendigen Befehle kann man dazu übergehen, die CPU gemäß den RISC-Prinzipien zu entwickeln. Hierbei kann man von folgenden, gegenseitig abhängigen Schritten ausgehen:
Wahl der Anzahl der Pipeline-Stufen
Wahl der intrinsischen Datenbreiten
Wahl des Registermodells
Wahl des Befehlssatzes
Korrektur des Pipelining-Modells zur möglichst schnellen (ungestörten) Ausführung der Programme
Die Abhängigkeiten der Schritte sind häufig impliziter, als sie auf den ersten Blick erscheinen. Beispielsweise ist die intrinsische Datenbreite ein Parameter, der die elementare Datenoperation (16 Bit, 32 Bit und so weiter) bestimmt. Allerdings beeinflusst sie auch den Befehlssatz und das Registermodell, weil die Befehle in diesem Datenformat auch kodiert werden müssen.
Bei der Wahl des Phasen-Pipelinings und der Anzahl der Stufen müssen folgende Vorgaben beachtet werden:
Die einzelnen Phasen, die innerhalb der Architektur parallel ablaufen, müssen in ihrem Zeitbedarf zueinander balanciert sein. Diese Forderung ist wichtig, um nicht einzelne Phasen, etwa das Instruction-Fetch in typischen CISC-Architekturen, künstlich verlängern zu müssen, da die Load-Phase des vorhergehenden Befehls noch nicht abgeschlossen ist. Diese Forderung kann auch zur Konsequenz haben, dass eine bestimme Phase nochmals unterteilt wird, um dann in diesen Teilen ebenfalls parallel zueinander ablaufen zu können.
Der Designer des Mikroprozessors muss alle Möglichkeiten ausschöpfen, um strukturelle Hazards zu verhindern oder zumindest in ihren Auswirkungen zu mildern. Hierbei ist es insbesondere notwendig, die Anzahl der externen Operandenzugriffe zu minimieren. Sie führen bei gleichzeitiger Nutzung von Adress- und Datenbus für Fetch, Load und Write back bei einem einfach ausgelegten Bussystem zwangsweise zum Konflikt.
Die dritte Forderung besteht in der Entdeckung und Behebung, möglichst sogar der Vermeidung von Kontrollfluss- und Daten-Hazards, sprich der Vermeidung von Abhängigkeiten zwischen den Instruktionen. Treten diese dennoch auf, muss man zumindest Wartezyklen einfügen, da ansonsten falsche Ergebnisse entstehen.
MPM3: Beispiel für ein Prozessormodell mit Phasen-Pipelining
Die aufgestellten Anforderungen an ein Prozessormodell gemäß den RISC-Prinzipien einschließlich eines Phasen-Pipelinings werden in eine konkrete Modellarchitektur umgesetzt, wobei im Unterschied zu den vorhergehenden Modellen lediglich die Prinzipien im Vordergrund stehen.
Das Mikroprozessormodell Nummer 3 (MPM3) wird hierfür als ein Prozessor mit im Vergleich zum MPM2 gleichwertigen arithmetischen und logischen Kapazitäten, aber einem abweichenden Programmiermodell definiert. Die Abweichungen ergeben sich hierbei aus den bisherigen Forderungen zum möglichst optimalen Phasen-Pipelining.
Prozessor-Architekturklasse und Programmiermodell
Die vorangegangenen Darstellungen lassen kaum Zweifel daran, dass für einen RISC-Prozessor mit Phasen-Pipelining die Anzahl der externen Speicherzugriffe pro Instruktion möglichst gering sein muss, um die strukturellen Hazards weitgehend zu vermeiden. Aus diesem Grund entspricht das MPM3 gemäß der Prozessorklassifizierung nach Operandenzugriff (siehe Klassifizierungssysteme für Prozessoren) einer Registerarchitektur mit ausschließlichem Datenaustausch zwischen internen Registern und externem Speicher durch Load-/Store-Befehle. Dies bezeichnet man im Allgemeinen als Load-/Store-Architektur.
Das Programmiermodell enthält acht General-Purpose-Register R0 ... R7, die die Mindestausstattung für eine derartige Architektur sind. Daneben besitzt der MPM3 weitere (Special Purpose)-Register, deren Zweck in Zusammenhang mit den Befehlen des MPM3 deutlich wird. Die Datenbreite ist einheitlich mit 16 Bit vorgesehen, wobei bei den speziellen Registern Abweichungen durch Auffüllung mit "0" ausgeglichen werden. Die in Klammern angegebenen Registerbezeichnungen für einige Special-Purpose-Register betreffen die interne Kodierung bei Austauschbefehlen.
Das Statusregister enthält die "klassischen" arithmetischen Flags Carry, Zero, Overflow und Negative sowie das Kontrollfluss-Flag Interrupt. Daneben existiert noch das Sonder-Flag Mode, das den Zustand innerhalb einer Interrupt Service Routine bei Simulationen anzeigt.
Instruktionssatz MPM3
Der gravierendste Unterschied in den Instruktionssätzen der Modelle MPM2 und MPM3 besteht auf den ersten Blick in der Einführung von Move-Instruktionen im MPM3, die eine Datenkopie zwischen zwei Registern beinhalten. Der Load- und Store-Befehl bleibt im MPM3 jeweils für den Transfer von Daten zwischen Register und Speicher erhalten, während die Transfer- und Exchange-Instruktionen des MPM2 komplett entfallen und im Move enthalten sind.
Der zweite Unterschied liegt in den im MPM3 zugelassenen Adressierungen, die sich so weit wie möglich auf interne Register beziehen und bei externen Operationen teilweise verkürzte Operanden zulassen. Da das MPM3 keine Akkumulatorarchitektur mit dem zentralen Register darstellt, muss der Zieloperand ebenfalls angegeben werden, falls er nicht implizit mit einem Quelloperanden übereinstimmt. Die Adressierungsarten und die Instruktionen sind teilweise eng miteinander gekoppelt und werden daher im Folgenden gemeinsam diskutiert.
Der dritte Unterschied besteht aus einer neuen Form der Kodierung einer Instruktion. Sie lässt nunmehr kein einheitliches Format für alle Befehle mehr zu, da sich die Kodierung einschließlich der Operanden auf ein 16-Bit-Format beschränken muss. Dieser Unterschied hat die gravierendsten Auswirkungen.
Für die Kodierung steht nun nicht mehr die besondere Dekodierbarkeit, das heißt ein sorgfältig nach Befehlsinhalten getrenntes Format, im Vordergrund. Die Kodierungen der bis zu drei möglichen Operanden bestimmen jetzt das Befehlscodeformat. Das oberste Gebot dabei ist es, die Anzahl der Buszugriffe für jedenBefehl möglichst zu beschränken, wobei die Fetch-Phase exakt einen Buszugriff benötigen wird. Das Design des MPM3 teilt die Instruktionen des Prozessors in vier Befehlsgruppen ein, hier als Kodierungsformate bezeichnet und im folgenden Kapitel ausführlich diskutiert.
Das 3- und 2- Register-Format
Für eine Vielzahl von Befehlen ist die Angabe von drei Operanden notwendig: Ziel, Quelle1, Quelle0. Für diese Operanden sind im MPM3 lediglich die General-Purpose-Register R0 bis R7 notwendig, für die eine Kodierung in drei Bit ausreicht. In diesem Format lassen sich 32 verschiedene Instruktionen in fünf Bit kodieren. Kennzeichnend für diese Kodierungsgruppe ist die Kodierung "11" in den höchsten Bits.
Das 2-Register-Format
Für alle Register R0 bis R12 gilt ein Format, bei dem lediglich Ziel- und Quellregister anzugeben sind und das aus diesem Grund 2-Register-Format heißt. Diese Register müssen in jeweils vier Bit kodiert werden, so dass hierfür das im Bild dargestellte Codeformat gilt. Innerhalb dieser Gruppe lassen sich 64 Befehle in sechs Bit kodieren. Kennzeichnend ist hier "10" in den höchstwertigen Bits.
Das "Register, Data" und allgemeine Format
Um Programmkonstanten gleich welcher Art in ein Register zu laden, ist ein Format zum Transfer dieser Daten innerhalb der 16-Bit-Kodierung der Instruktionen notwendig. Natürlich ist es nicht möglich, dass Konstanten in voller 16-Bit-Breite kodierbar sind, da sowohl der Befehl als auch das Zielregister Bits belegen.
Die Lösung besteht darin, acht Bit für die Programmkonstanten, drei Bit für das Zielregister (R0 ... R7) und neben der Kodierungsgruppe ("01") drei Bit für acht Befehle zu belegen. Das Kodierungsformat sieht damit wie in Bild dargestellt aus. Als Sonderfälle sind - wie im vorigen Fall der 2-Register-Kodierung - zwei Kodierungsformate aus der Codegruppe für allgemeine Befehle vorgesehen. Dies erfolgt, um die geringe Anzahl an Befehlen in dieser Gruppe zu erhöhen.
Die zusätzlichen Instruktionen bestehen aus den bedingten Branch-Befehlen, deren Bedingung in den vier Kodierungsbits untergebracht ist, sowie aus dem relativen, unkonditionierten Sprungbefehl JMP. Dieser stellt auf Grund des Fehlens einer Bedingung oder eines Zielregisters 11 Bit für die Konstante bereit. Relative Branch-Sprünge können somit einen Adressraum von +258/-252, JMP-Sprünge +4098/-4092 Adressen umfassen - für viele Applikationen ausreichend. Die Kodierung der PC-relativen Information erfolgt im Wort-Format (16 Bit Differenz).
Allgemeines Kodierungsformat
Abgesehen von den oben bereits integrierten Ausnahmen kodiert der MPM3 alle allgemeinen Befehle, die insbesondere keine Operanden benötigen, in der Codegruppe mit "00" als höchste Bits. Bit 13 dieser Codegruppe bleibt auf "0", um es von den Sonderformaten aus dieser Gruppe unterscheiden zu können. In den verbleibenden 13 Bit lassen sich 8192 verschiedene Instruktionen unterbringen.
Transferbefehle
Eine andere Betrachtungsweise für den Instruktionssatz geht davon aus, welche Befehle eigentlich mit welcher Adressierung sinnvoll oder notwendig sind. Hierzu teilt man alle Befehle in die Befehlsgruppen Transferbefehle, arithmetisch-logische Befehle, Flag-Befehle und Kontrollflussbefehle ein.
Die Gruppe der Transferbefehle setzt sich aus den Instruktionen MOV, MOVH, LD, ST, PUSH und POP zusammen. Die Tabelle gibt einen Überblick über die Bedeutung dieser Mnemonics und die zugelassenen Adressierungsarten.
Mnemonic | Operanden | Bedeutung |
---|---|---|
dreg, sreg: Destination- bzw. Source-Register R0 ... R12; dgpreg, sgpreg: Destination- bzw. Source-Register R0 ... R7; adrreg: Adressregister R0 ... R7; const8: Programmkonstante 8 Bit | ||
MOV | <dreg>, <sreg><dreg>, const8 | Move Quelloperand in Zieloperand. Quelloperand kann Register oder 8-Bit-Konstante sein |
MOVH | <dreg>, const8 | Move Konstante (obere acht Bit) in obere acht Bit des Zielregisters |
LD | <dgpreg>, [<adrreg>] | Lade Zielregister mit dem Inhalt der Datenspeicherstelle, deren Adresse im Source-Register steht |
ST | [<adrreg>], <sgpreg> | Speicher Registerinhalt in Speicherstelle, Adresse im Zielregister |
PUSH | <sreg> | Kopiere Registerinhalt auf den Stack |
POP | <dreg> | Kopiere aktuelle Stack-Speicherstelle in Register |
PUSH und POP sind mit einer registerdirekten und einer impliziten Adressierung zugelassen. Die implizite Adressierung bezieht sich automatisch auf den Stack, wobei der MPM3 Predekrement beim Schreiben (PUSH) sowie Postinkrement beim Lesen (POP) auf den Stackpointer anwendet. Als Quelle und Ziel sind mit Ausnahme des PC alle Register möglich, also auch die Special-Purpose-Register. Diese werden durch Angabe der Registernummer kodiert.
Die Befehle LD und ST, die die Kopplung zwischen Registern und Speicher erledigen, kommen ausschließlich mit einer registerindirekten Adressierung zum Einsatz. Der Grund liegt in dem Anspruch, das Bus-Interface möglichst gering zu belasten. Eine direkte Adressierung hätte hier neben dem Fetch der Instruktion zwei zusätzliche Buszyklen benötigt, während die registerindirekte Adressierung nur einen zusätzlichen Zyklus zum Fetch benötigt. Als Registeroperanden sind in diesem Fall nur die General-Purpose-Register zulässig.
Die MOV- und MOVH-Befehle dienen zum Austausch der Daten zwischen Registern und der Belegung von Registern mit Konstanten. Die MOV-Instruktion ist dabei mit allen Registern als Operand zulässig. Da, wie aus der Kodierung hervorgeht, noch Platz für acht Bit innerhalb des Befehlsformats ist, finden konstante Werte aus diesem Wertebereich in dem Befehl selbst Platz und führen in der Load-Phase nicht zu einer zusätzlichen Busbelastung.
Die Interpretation des konstanten Werts erfolgt mit Vorzeichen, sowohl die unteren acht Bit im Register als auch die oberen sind belegt, wobei die Erweiterung vorzeichenrichtig erfolgt (Bit 7 der Konstante kopiert der Prozessor auf die Bits 8 bis 15 des Registers).
Die MOVH-Instruktion hingegen überschreibt nur die oberen acht Bit des gewählten Registers mit dem konstanten Wert. Transferbefehle beeinflussen keine Flags im Statusregister.
Arithmetisch-logische Befehle im MPM3
Die zweite Gruppe besteht aus allen Befehlen zur Arithmetik sowie zur logischen Verknüpfung. Die Anwendung dieser Befehle ist zunächst nur für General-Pur-pose-Register vorgesehen, wobei es Ausnahmen von dieser Regel gibt.
Die arithmetisch-logischen Befehle sind abhängig von der Anzahl der Operanden im 2- oder im 3-Register-Format kodiert. Im 3-Register-Format stehen insgesamt 5 Bit zur Kodierung der Operation zur Verfügung, im 2-Register-Format sogar 6 Bit. Hierdurch sind alle Befehle dieser Gruppe im MPM3 codierbar. Das allgemeine Format für Befehle mit zwei Source-Operanden lautet:
<mnemo> <dgpreg>, <sgpreg1>, <sgpreg0>
Die Befehle mit nur einem Quellregister (und einem Zielregister) sind wie erwähnt im 2-Register-Format kodiert. Hierzu zählen das Dekrement und Inkrement sowie die Schiebe- und Rotationsbefehle ASL, ASR, LSR, ROL und ROR. Als Nebeneffekt sind für diese Befehle alle Register R0 bis R12 erreichbar.
Die andere Ausnahme besteht in dem Vergleichsbefehl (CMP), für den aus Performance-Gründen sowohl eine Codierung im 2-Register-Format (Vergleich für alle Register R0 bis R12) als auch im Register, Data-Format vorgesehen ist. Die CMP-Instruktion benötigt zwei Source-, jedoch kein Zielregister (Destination). Darüber hinaus ist die Integration von Vergleichen mit Programmkonstanten für die Geschwindigkeit der Programme sehr wichtig.
Der Vergleichsbefehl setzt auch das Overflow-Flag, um bei Vergleichen für vorzeichenbehaftete Zahlen eine korrekte Entscheidung treffen zu können. Bis auf die Rückspeicherung entspricht dieses Verhalten dem des Subtraktionsbefehls.
Flag- und Kontrollfluss-Befehle
Die wenigen Flag-Befehle kodiert der MPM3 sehr einfach in einer auf 16 Bit erweiterten Form, die in der Tabelle auf der folgenden Seite deutlich wird.
Die letzte Gruppe, die Kontrollfluss-Befehle, besteht aus Verzweigungs-, Sprung- und Unterprogrammbefehlen. Die Befehle NOP, RTI und RTS besitzen eine implizite Adressierung und sind im allgemeinen Format kodiert. Für die anderen Befehle bieten sich eine registerindirekte und eine relative Adressierung an, um ohne zusätzliche Belastung des Busses die Sprünge durchzuführen.
Die relative Adressierung ist bei Branch-Befehlen durch 8 Bit beschrieben, bei dem Sprungbefehl (JMP) durch eine 11-Bit-Konstante. Unbedingte Sprünge können aus diesem Grund weitere Entfernungen im Code überwinden, ohne auf die registerindirekte Adressierung zurückgreifen zu müssen.
Bei den Branch-Befehlen sind die Kodierungen für Branch Above (BA, springt bei NF = 0 und ZF = 0) sowie Branch Below or Equal (BBE, verzweigt bei NF = 1 oder ZF = 1) neu hinzugekommen. Diese Befehle eignen sich für vorzeichenrichtige Vergleiche und Kontrollstrukturen, während BGT, BLE und so weiter für vorzeichenlose Zahlen zuständig sind. Die Befehle BAE und BB entsprechen BPL beziehungsweise BMI.
Für den CALL-Befehl muss jeder Prozessor die Rücksprungadresse zwischenspeichern. Während dies bei vielen CISC-Architekturen, wie etwa dem MPM2 [3], automatisch auf dem Stack geschieht, legt der MPM3 diese im Link-Address- Register LA (R11) ab. RTS liest diese beim Rücksprung ohne zusätzlichen Speicherzugriff aus dem Link Address Register aus. Soll dieses Register beim Aufruf eines Unterprogramms nicht überschrieben werden, muss man den Inhalt aktiv auf den Stack retten. Dazu führt das Programm einen PUSH R11 vor dem Aufruf und ein POP R11 nach dem Aufruf aus.
Für einen Interrupt Request gilt das Gleiche, ergänzt durch das Link-Status-Register (LS, R10). Hier werden beide Register mit den Werten vor Aufruf der Service- Routine beschrieben, die Adresse der Service-Routine muss zudem im Vector-Register (VR, R12) stehen. Diese Form der Registerverwaltung ist zwar komplexer, entspricht jedoch der RISC-Philosophie. Entwickler und Compiler-Bauer haben dadurch die Möglichkeit, nur die notwendigen Sicherungen durchzuführen und somit Zeit zu sparen.
Codetabelle des MPM3
Die im Bild dargestellte Kodierungstabelle für MPM3 zeigt das höher wertige Byte der jeweiligen Kodierung für eine Instruktion. Um mehr Platz für Operanden zur Verfügung zu haben, belegen einige Befehle weniger als acht Bit. Dadurch treten einige Instruktionen mehrfach in der Tabelle auf, so zum Beispiel MOV register, #data (40h ... 47h). Dies bedeutet in der Praxis, dass die Bitfolge "01000xxx" den Befehl MOV mit der Adressierungsart Immediate kodiert.
Die in der Tabelle eingetragenen Adressierungsarten bedeuten im Einzelnen:
Ohne Eintragung: Implicit, Instruktion benötigt keine Operanden.
Imm.: Immediate, die Adressierung erfolgt im Format <register>, #data, wobei <register> eine registerdirekte (alle Register), #data die unmittelbare Adressierung nutzt.
Reg.-dir.: registerdirect, alle Operanden werden als interne Prozessorregister interpretiert. Die Anzahl variiert von einem Register (PUSH, POP) über zwei (MOV, CMP, ASL, ASR, LSR, ROL, ROR) auf drei Register (ADD, SUB, AND, EOR, OR), wobei grundsätzlich folgende Formate gelten:
<operation> <ziel>, <quelle0>, <quelle1>
mit ziel = <quelle0> <operation> <quelle1> bzw. ziel = <operation> <quelle>
<operation> <ziel>, <quelle>
<operation> <quelle0>, <quelle1> (nur CMP)
Indirect: Die Indirektion bezieht sich immer auf einen Registerinhalt als Quelle (LD) oder Ziel (ST, JMP, CALL). Die Angabe des zweiten Operanden für LD und ST erfolgt registerdirekt.
Relat.: Relative, die Adressierung erfolgt relativ zum aktuellen Program Counter im 8-Bit- (Branch-) beziehungsweise 11-Bit-Format (JMP-Instruktion).
Bei den Branch-Befehlen gibt es mnemotechnische Doppelbedeutungen: BPL/ BAE, BMI/BB, BCS/BGE und BCC/BLT sind identisch.
Ablauf der Instruktionen im MPM3
Seitens der Befehle und Adressierungsarten wurde in der MPM3-Definition alles vorbereitet, um einen Ablauf jeder Instruktion in vier etwa gleichgewichtigen Phasen durchzuführen:
Fetch/Predecode: Die Fetch- und Predecode-Phase beinhaltet das Laden des Instruktionscodes einschließlich aller Operanden. Dies ist in einem Takt möglich, da das Datenbus-Interface zum Speicher eine Breite von 16 Bit hat und die Befehle ebenso breit sind. Das Predecode beinhaltet die Umwandlung des ursprünglichen Formats in ein einheitliches internes Befehlsformat zur weiteren Verarbeitung sowie die Detektierung von Branch- und Sprungbefehlen.
Decode/Load: Die Decode-/Load-Phase erledigt alle notwendigen Dekodierungen zur weiteren Verarbeitung und lädt alle Operanden in interne Register zur Weiterverarbeitung. Dies beinhaltet auch ein mögliches Data Forwarding. Kontrollflussbefehle können frühestens in dieser Phase entscheiden, an welcher Stelle das Programm weitergeführt wird. Hierzu ist bei bedingten Verzweigungen eventuell ein Data Forwarding notwendig, um die Flags korrekt auswerten zu können.
Execute/Memory: Arithmetische und logische Operationen laufen in dieser Phase ab. Für Load-Befehle erfolgt hier der Zugriff auf das Memory, so dass bei einer Von-Neumann-CPU mit einem Bussystem eine Verzögerung der nachfolgenden Befehle auftritt. Entsprechendes gilt für einen Store-Befehl und den daraus resultierenden Schreibzugriff auf das externe Memory.
Write back: Die MPM3 trägt alle Ergebnisse in dieser abschließenden Phase in die Zielregister ein.
Ausblick
Die Darstellungen im vorangegangenen Kapitel beruhen im Wesentlichen auf einer Abstimmung zwischen dem Befehlssatz, den notwendigen Adressierungsarten, dem Registersatz einschließlich der Special-Purpose-Register sowie der Pipeline-Grundstruktur. Die Struktur dieser Pipeline wird im nächsten Teil unserer Artikelserie im Detail behandelt.
Diesen Artikel und eine ganze Reihe weiterer Grundlagenthemen zu Prozessoren finden Sie auch in unserem tecCHANNEL-Compact "Prozessor-Technologie". Die Ausgabe können Sie in unserem Online-Shop versandkostenfrei bestellen. Ausführliche Infos zum Inhalt des tecCHANNEL-Compact "Prozessor-Technologie" finden Sie hier. (ala)