Uploaded by Hossaini Syed Hassan

Computersysteme2

advertisement
Wolfram Schiffmann, Theo Ungerer, Jörg Lenhardt,
Andreas Klos, Marius Rosenbaum
Computersysteme II
Das Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere das Recht der Vervielfältigung und Verbreitung sowie der
Übersetzung und des Nachdrucks, bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Kein Teil des Werkes darf in irgendeiner Form (Druck,
Fotokopie, Mikrofilm oder ein anderes Verfahren) ohne schriftliche Genehmigung der FernUniversität reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.
Der Inhalt dieses Studienbriefs wird gedruckt auf Recyclingpapier (80 g/m2, weiß), hergestellt aus 100 % Altpapier.
Vorwort
Liebe Studierende,
wir begrüßen Sie herzlich zum Kurs Computersysteme II (1609). Dieser Kurs
führt Sie in die Grundlagen der Rechnerarchitektur ein und schließt thematisch
an den Kurs Computersysteme I (1608) an. Die Kapitel 1-4 entsprechen den
jeweiligen Kurseinheiten 1-4, d.h. die Bearbeitungszeiträume und zugehörige
Einsendetermine beziehen sich auf die entsprechenden Kapitelnummern. Die
Begriffe Kapitel und Kurseinheiten werden ab sofort synonym verwendet. Am
Ende des Kurses ist ein gemeinsames Literatur- und Stichwortverzeichnis (Index)
angehängt. Zudem werden theoretische Konzepte durch Beispiele und Selbsttestaufgaben verdeutlicht. Die Lösungen der Selbsttestaufgaben finden Sie jeweils
am Ende eines Kapitels.
Der Kurs wurde zum Wintersemester 2017/2018 komplett überarbeitet und
entsprechend der neuen Schwerpunkte gegliedert. Kapitel 1 beginnt mit einer
Einführung in die Befehlssatzarchitektur, d.h. die Beschreibung der Schnittstelle
zwischen Hard- und Software. In einem eher praktisch ausgerichteten Abschnitt
lernen Sie zudem den MIPS-Prozessor und dessen Befehlssatz ausführlich kennen.
Hierbei soll Ihnen neben dem theoretischen Wissen auch die Programmierung
und Analyse eines Assemblerprogramms näher gebracht werden.
Kapitel 2 schließt an den Kurs Computersysteme I an und behandelt die Abarbeitung eines Befehls innerhalb des Prozessors. Hierzu gehört der Aufbau der
Mikroarchitektur und das Zusammenarbeiten der verschiedenen Komponenten.
Es wird erklärt, wie sich Einzyklus- und Mehrzyklusarchitekturen voneinander unterscheiden und wie durch Befehlspipelining die Anzahl der Befehle pro
Taktzyklus erhöht werden kann.
Kapitel 3 beschäftigt sich mit der Speicherung von Programmcode und
Daten, mit denen der Prozessor arbeitet. Anstelle eines gemeinsamen großen
Speichers wird eine mehrstufige Hierarchie von Speichern mit verschiedenen Speichertechnologien eingesetzt, u.a. Register, Flash-Speicher und Magnetspeicher.
Durch den Einsatz verschiedener Technologien lässt sich die Lücke zwischen
hoher Prozessortaktrate und langsameren Speichertaktraten ausgleichen. In
diesem Kapitel wird zudem besonders auf die Funktionsweise von Caches und
die Vorteile einer Virtuellen Speicherverwaltung eingegangen.
Kapitel 4 behandelt weitergehende Architekturkonzepte, welche eine zusätzliche Beschleunigung des Prozessors ermöglichen. Hierbei wird besonders
die Superskalartechnik betrachtet, bei der im Prozessor mehrere Ausführungseinheiten vorhanden sind und mehrere Befehle gleichzeitig geholt, dekodiert,
abgearbeitet und beendet werden können.
Bei der Überarbeitung weggefallen ist der Bereich des parallelen Rechnens.
Dieser kann von interessierten Studierenden in den Kursen Parallele Programmierung und Grid Computing (1727) und Advanced Parallel Computing (1729)
vertieft werden.
i
Literaturhinweis
Dieser Kurs beruht auf folgender Basis-Literatur:
• Mikrocontroller und Mikroprozessoren von U. Brinkschulte und T. Ungerer
• Digital Design and Computer Architecture von D. Harris und S. Harris
Dort können Sie sich weitergehend über die im Kurs behandelten Themen
informieren und darüber hinausgehend über Inhalte, welche aus Platzgründen
nicht behandelt werden konnten. Ein ausführlicheres Literaturverzeichnis sowie
ein Stichwortverzeichnis finden Sie am Ende des Kurses.
Wir wünschen Ihnen nun viel Spaß beim Bearbeiten des Kurses!
Ihre Kursbetreuer
ii
Inhaltsverzeichnis
1 Befehlssatzarchitekturen
1.1 Lernziele . . . . . . . . . . . . . . . . . .
1.2 Einführung . . . . . . . . . . . . . . . .
1.3 Grundlagen der Befehlssatzarchitektur .
1.3.1 Befehlssatz- und Mikroarchitektur
1.3.2 Adressraumorganisation . . . . .
1.3.3 Datenformate . . . . . . . . . . .
1.3.4 Befehlssatz . . . . . . . . . . . .
1.3.5 Befehlsformate . . . . . . . . . .
1.3.6 Adressierungsarten . . . . . . . .
1.3.7 CISC- und RISC-Prinzipien . . .
1.4 Die MIPS-Architektur . . . . . . . . . .
1.5 Codeanalyse . . . . . . . . . . . . . . . .
1.6 Zusammenfassung . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2 Mikroarchitekturen
2.1 Lernziele . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Einzyklus-Mikroarchitektur . . . . . . . . . . . . . . . . . .
2.2.1 Zustandselemente . . . . . . . . . . . . . . . . . . . .
2.2.2 Datenpfad . . . . . . . . . . . . . . . . . . . . . . . .
2.2.3 Steuereinheit . . . . . . . . . . . . . . . . . . . . . .
2.2.4 Vor- und Nachteile . . . . . . . . . . . . . . . . . . .
2.3 Mehrzyklen-Mikroarchitektur . . . . . . . . . . . . . . . . .
2.3.1 Funktionsweise . . . . . . . . . . . . . . . . . . . . .
2.3.2 Vor- und Nachteile . . . . . . . . . . . . . . . . . . .
2.4 Pipeline-Mikroarchitektur . . . . . . . . . . . . . . . . . . .
2.4.1 Das Pipeline-Prinzip . . . . . . . . . . . . . . . . . .
2.4.2 Stufen einer Befehls-Pipeline . . . . . . . . . . . . . .
2.4.3 Die MIPS-Pipeline . . . . . . . . . . . . . . . . . . .
2.4.4 Pipeline-Konflikte . . . . . . . . . . . . . . . . . . . .
2.4.5 Datenkonflikte und deren Lösungsmöglichkeiten . . .
2.4.6 Steuerflusskonflikte und deren Lösungsmöglichkeiten
2.4.7 Strukturkonflikte und deren Lösungsmöglichkeiten . .
2.4.8 Ausführung in mehreren Takten . . . . . . . . . . . .
2.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . .
iii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
0
1
2
7
7
8
11
15
17
20
27
29
39
43
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
50
51
51
52
58
60
62
63
65
66
66
69
70
74
75
83
87
89
90
iv
Inhaltsverzeichnis
3 Speicherorganisation
3.1 Lernziele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Hauptspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.1 Speicherhierarchie . . . . . . . . . . . . . . . . . . . . . .
3.2.2 Technologische Grundlagen . . . . . . . . . . . . . . . .
3.2.3 Static Random Access Memory . . . . . . . . . . . . . .
3.2.4 Dynamic Random Access Memory . . . . . . . . . . . . .
3.2.5 Flash-Speicher . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Cache-Speicher und -Organisation . . . . . . . . . . . . . . . . .
3.3.1 Voraussetzungen und Begriffsdefinitionen . . . . . . . . .
3.3.2 Vollassoziativer Cache-Speicher . . . . . . . . . . . . . .
3.3.3 Direkt abgebildeter Cache-Speicher . . . . . . . . . . . .
3.3.4 n-Wege-satzassoziativer Cache-Speicher . . . . . . . . . .
3.3.5 Platzierung des Caches in Bezug auf die virtuelle Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . .
3.3.6 Verdrängungsstrategien, Schreibzugriffe . . . . . . . . . .
3.3.7 Cache Kohärenzprotokolle . . . . . . . . . . . . . . . . .
3.4 Virtuelle Speicherverwaltung . . . . . . . . . . . . . . . . . . . .
3.4.1 Segmentierung . . . . . . . . . . . . . . . . . . . . . . .
3.4.2 Seitenwechselverfahren . . . . . . . . . . . . . . . . . . .
3.5 Anbindung des Hauptspeichers und von Ein- und Ausgabekomponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.5.1 DRAM-Speichercontroller . . . . . . . . . . . . . . . . .
3.5.2 Memory Mapped I/O . . . . . . . . . . . . . . . . . . . .
3.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . .
133
133
137
143
4 Weiterführende Prozessortechniken
4.1 Lernziele . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Parallelität auf Befehlsebene . . . . . . . . . . . . . .
4.2.1 Steigerung der Prozessorleistung . . . . . . . .
4.2.2 Very Long Instruction Word . . . . . . . . . .
4.2.3 In-Order-Execution-Pipeline . . . . . . . . . .
4.2.4 Out-of-Order-Execution-Pipeline . . . . . . .
4.2.5 Sprungvorhersage und spekulative Ausführung
4.3 Mehrfädige Prozessoren . . . . . . . . . . . . . . . . .
4.3.1 Grundtechniken der Mehrfädigkeit . . . . . .
4.3.2 Simultan mehrfädige Prozessor-Technik . . . .
4.4 Mehrkernprozessoren . . . . . . . . . . . . . . . . . .
4.5 Entwicklung der Prozessor-Technik . . . . . . . . . .
4.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . .
147
148
149
149
153
156
160
165
180
182
186
187
190
194
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
95
96
97
98
100
103
104
105
106
109
113
116
118
120
121
124
128
129
131
Literaturverzeichnis
197
Index
202
Kapitel 1
Befehlssatzarchitekturen
Kapitelinhalt
1.1
Lernziele . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
Einführung . . . . . . . . . . . . . . . . . . . . . . . .
2
1.3
Grundlagen der Befehlssatzarchitektur
. . . . . . .
7
1.4
Die MIPS-Architektur . . . . . . . . . . . . . . . . .
29
1.5
Codeanalyse . . . . . . . . . . . . . . . . . . . . . . . .
39
1.6
Zusammenfassung . . . . . . . . . . . . . . . . . . . .
43
1.1. Lernziele
„Ich denke, dass es weltweit einen Markt für vielleicht fünf Computer gibt.“
Thomas Watson (Chairman von IBM) zugeschrieben, 1943
Dieses zwar nicht nachgewiesene, aus damaliger Sicht aber durchaus denkbare
Zitat hat sich glücklicherweise als kompletter Irrtum herausgestellt. Computer
haben heute mehr denn je eine umfassende und vielfältige Verbreitung im
Alltag und werden diese auch in den kommenden Jahren weiter ausbauen. Im
vorherigen Kurs Computersysteme I (1608) haben Sie bereits die Grundlagen für
das Verständnis von elektronischen Schaltungen kennen gelernt, aus denen jeder
Computerchip aufgebaut ist. Auch haben Sie erste Erkenntnisse gesammelt, wie
ein Computer prinzipiell funktioniert1 und aus welchen Funktionseinheiten ein
Prozessor besteht. Bevor wir daran anschließen und zeigen, wie ein Prozessor
die Befehle im Detail abarbeitet, werden wir in diesem Kapitel zunächst die
Sicht eines Assemblerprogrammierers bzw. Compilers übernehmen. Hierbei wird
der Prozessor von außen betrachtet und untersucht, welche Funktionalität der
Befehlssatz eines Prozessors dem Programmierer typischerweise bietet.
Abschnitt 1.1 führt die in diesem Kapitel angestrebten Lernziele auf. Abschnitt 1.2 dient als Gesamtüberblick über die im Kurs behandelten Inhalte.
Abschnitt 1.3 führt in die Grundlagen des Befehlssatzes ein, d. h. die verschiedenen Befehlsformate und -typen, Adressierungsarten, Datenformate, Adressräume
usw. In Abschnitt 1.4 und Abschnitt 1.5 werden Sie anschließend den Befehlssatz
eines konkreten Prozessors kennenlernen und mit diesem praktische Übungen
durchführen.2
1.1
Lernziele
Dieses Kapitel führt in die Grundlagen der Befehlssatzarchitektur ein. Sie werden
dabei kennenlernen, welche Funktionen und Befehle ein Prozessor typischerweise nach außen bietet, ohne dabei auf die interne technische Realisierung
einzugehen. Sie werden lernen, wie und wo Daten bzw. Programmcode im
Computer gespeichert bzw. adressiert werden, wie typische Datenformate aussehen, welche Befehlsarten und Befehlsformate es gibt und wie sich verschiedene
Architekturen voneinander unterscheiden können. Außerdem werden Sie lernen,
wie man Assembler-Programme für einen Prozessor schreiben und analysieren
kann. Hierfür werden Sie sich mit dem Befehlssatz eines Beispiel-Prozessors
auseinandersetzen und so die zunächst theoretischen Inhalte auch praktisch
wiederholen.
1
2
Von-Neumann-Prinzip mit Steuerwerk, Rechenwerk, Speicher und Ein-/Ausgabe.
Bzw. in einem Simulator für diesen Prozessor.
1
2
Kapitel 1. Befehlssatzarchitekturen
1.2
Befehlssatzarchitektur
Einführung
Im letzten Kapitel des Kurses 1608 haben Sie gelernt, wie ein Computer nach
dem von-Neumann-Prinzip aufgebaut ist. Er besteht aus vier Funktionseinheiten:
dem Rechen- und Leitwerk, die zusammen den Prozessor bilden, dem Hauptspeicher und der Ein-/Ausgabe. Das Zusammenspiel dieser vier Komponenten
bestimmt die Leistungsfähigkeit eines Computers. Speicher und Ein-/Ausgabe
werden über die Systembusschnittstelle mit dem Prozessor verbunden und belegen jeweils verschiedene Bereiche des ansprechbaren Adressraumes. Der Aufbau
des Speichersystems und dessen Einfluss auf die Leistungsfähigkeit eines Computers wird im Kapitel 3 ausführlich behandelt. Ein-/Ausgabeeinheiten bestehen
im Allgemeinen aus spezialisierten Controller-Bausteinen, die Peripheriegeräte
zur Interaktion mit dem Menschen (Tastatur, Monitor, Maus, Drucker), anderen
Computern (lokale Netzwerke, Internet) oder nicht-flüchtigen Speichermedien (Festplatten, optischen Speichern) unterstützen. Die Controller-Bausteine
werden ähnlich wie Speicher über einen speziellen Adressbereich angesprochen.
Entscheidenden Einfluss auf die Leistung und die Einsatzmöglichkeiten eines
Computers hat das Programmiermodell des Prozessors. Es beschreibt die Sicht
eines Systemprogrammierers auf den Prozessor und beinhaltet alle notwendigen
Details, um ablauffähige Maschinenprogramme für diesen zu erstellen. Ebenso
muss auch der Übersetzer das Programmiermodell kennen, um Hochsprachenprogramme in die entsprechende Maschinensprache eines Prozessor zu übersetzen.
Da das Programmiermodell im Wesentlichen durch den Befehlssatz des Prozessors festgelegt ist, spricht man von der Befehlssatzarchitektur (Instruction
Set Architecture, ISA) oder auch nur von der Architektur eines Prozessors. Die
Architektur beschreibt dabei lediglich das Verhalten des Prozessors, nicht aber
seine konkrete Implementierung. Diese hängt u. a. von der logischen Organisation und dem internen Zusammenschluss der verschiedenen Funktionseinheiten
und Bausteine ab, siehe Abbildung 1.1.
Abbildung 1.1: Ebenen der Rechnerarchitektur
Ähnlich wie ein Architekt zunächst mit dem Bauherrn die gewünschten Eigenschaften eines Bauwerks festlegt und diese bestmöglich auf die spätere Nutzung
abstimmt (z. B. Anzahl, Größe, Ausstattung und Anordnung der benötigten
Räume), geht auch ein Rechnerarchitekt systematisch an den Entwurf eines
Prozessors heran. Zunächst wird die Befehlssatzarchitektur des Prozessors festgelegt. Sie beschreibt die für die spätere Anwendung benötigten prozessorinternen
1.2. Einführung
Speichermöglichkeiten (Register), Operationen, Datenformate und Unterbrechungslogik. Aus dieser Architekturbeschreibung leitet der Rechnerarchitekt
dann eine logische Struktur zur Implementierung ab, die Mikroarchitektur genannt wird. Dabei legt er fest, welche Funktionseinheiten (z. B. Register(sätze),
ALUs3 , Multiplexer usw.) benutzt werden sollen, welche Datenpfade (Data
Path) zwischen den Funktionseinheiten vorhanden sein sollen und wie alle diese
Komponenten koordiniert werden (Control Path). Die Umsetzung dieser logischen Organisation in einer bestimmten Hardware-Technologie bezeichnet man
als technologische Realisierung (in Analogie zu einem Bauwerk wären dies die
verwendeten Baumaterialien).
Im Laufe der Entwicklung von Computersystemen kamen verschiedene Technologien zur Realisierung von Prozessoren zum Einsatz. Es begann mit elektromechanischen Bauelementen zu Beginn des 20. Jahrhunderts, welche später
durch Elektronenröhren und im Anschluss daran durch einzelne Transistoren
ersetzt wurden. Ab dem Ende der fünfziger Jahre wurden schließlich integrierte
Schaltkreise (Integrated Circuits, ICs) entwickelt, welche komplette Schaltungen
mit mehreren Transistoren auf einem einzigen Halbleiterchip realisieren. Die
Integrationsdichte4 hat sich dabei seit der Einführung von Integrierten Schaltkreisen stetig gesteigert. Gordon Moore, Mitbegründer des weltweit bekannten
Prozessorherstellers Intel, stellte ab 1965 in einem Beitrag der Zeitschrift „Electronics“ fest, dass sich bis zu diesem Zeitpunkt die Anzahl der Transistoren
pro Chip jedes Jahr verdoppelt hat. Dieser als Mooresches Gesetz bekannt
gewordene Zusammenhang hat sich prinzipiell bis heute fortgesetzt. Lediglich die
„Zeitkonstante“ muss etwas größer gewählt werden. Sie beträgt bei Speicherchips
mit regulärer Struktur etwa 18 Monate, bei Prozessoren wegen der erhöhten
Komplexität etwa 24 Monate. Es ist zu beachten, dass es sich hierbei nicht
um ein Gesetz im Sinne eines Naturgesetzes handelt, sondern um einen aus
einer empirischen Untersuchung abgeleiteten Zusammenhang. Auf zukünftige
Entwicklungen in der Chipfertigung können daher keine Aussagen getroffen
werden.
Um eine logische Organisation in Hardware umzusetzen, müssen mehrere
Schichten geometrischer Strukturen (Chip Layouts) auf eine Halbleiterscheibe (Wafer ) geätzt werden. Die elektronischen Funktionseinheiten setzen sich
dann aus diesen geätzten Strukturen zusammen. Im Laufe der Jahre wurde die
Verfahrenstechnik zur Herstellung solcher Mikrochips stetig verbessert, siehe
Abbildung 1.2. Heute (Stand 2017) ist man in der Lage, Strukturen von weniger
als 14 Nanometern herzustellen (Ein Nanometer entspricht 10−9 m, also einem
Millionstel Millimeter). Durch die feiner werdenden Strukturen ist es möglich,
immer mehr Transistoren bei gleichbleibender Fläche auf einem Chip unterzubringen und gleichzeitig die Taktfrequenz zu erhöhen. Wegen der kleineren
Strukturen können die Transistoren schneller schalten und durch kürzere Verbindungsleitungen miteinander gekoppelt werden. Dadurch verkürzen sich auch
die Laufzeiten zwischen den Transistoren, was eine Erhöhung der Taktfrequenz
3
4
Arithmetisch-logische Einheit, engl. arithmetic logic unit
Die Integrationsdichte gibt die Anzahl an Transistoren pro Flächeneinheit an.
3
Mikroarchitektur
Integrierter
Schaltkreis
Mooresches Gesetz
Strukturgrößen
4
Abwärme-Problem
Kapitel 1. Befehlssatzarchitekturen
realisierbar macht.
Mikrochips lassen sich seit der Entstehung in verschiedene Generationen
unterteilen, siehe Tabelle 1.1. Während man bei der SSI-Technologie mit Strukturgrößen von um die 100 Mikrometer gerade mal 100 Transistoren auf einem
Mikrochip integrieren konnte, sind heute (Stand 2017) mit fortgeschrittenen
GSI-Technologien bei Integrationsdichten von ca. 14 Nanometern 7,2 Milliarden
Transistoren pro Chip realisierbar5 . Hauptproblem zukünftiger Entwicklungen ist vor allem die Kühlung der Mikrochips, deren Wärmedichte die einer
Herdplatte bei weitem übersteigt.
Tabelle 1.1: Technologien integrierter Schaltungen.
Integrationsdichte
Small
Medium
Large
Very Large
Ultra Large
Super Large
Extra Large
Giga
Scale
Scale
Scale
Scale
Scale
Scale
Scale
Scale
Integration
Integration
Integration
Integration
Integration
Integration
Integration
Integration
Kurzbezeichnung
Anzahl Transistoren
SSI
MSI
LSI
VLSI
ULSI
SLSI
ELSI
GSI
100
1.000
10.000
100.000
1.000.000
10.000.000
100.000.000
> 1.000.000.000
Abbildung 1.2: Entwicklung von Integrationsdichten und Strukturgrößen
5
Intel’s 22-Core Xeon Bradwell-E5
1.2. Einführung
Es gibt verschiedene Arten von Mikrochips, die sich bezüglich der Regelmäßigkeit ihrer geätzten Strukturen unterscheiden. Speicherchips sind am
regelmäßigsten aufgebaut, wodurch hiermit die höchsten Integrationsdichten
erreicht werden können. Prozessoren erreichen aufgrund ihrer Komplexität und
Unregelmäßigkeit weniger als die Hälfte der Integrationsdichte von Speicherbausteinen. Neben Prozessoren gibt es auch noch anwendungsspezifische Bausteine,
die entweder maskenprogrammierbar (Application Specific Integrated Circuit,
ASIC) oder elektrisch programmierbar sind (Field Programmable Gate Array,
FPGA). Die Hersteller dieser Bausteine verwenden statt der Transistorenanzahl
als Komplexitätsmaß häufig die Zahl der Gatteräquivalente. Als Faustregel gilt,
dass zur Realisierung eines Gatters etwa vier Transistoren benötigt werden.
Betrachten wir als nächstes den Zusammenhang zwischen der Befehlssatzarchitektur und den beiden darunter liegenden Schichten. Die bisher im Kurs 1608
eingeführten Prozessoren basieren auf mikroprogrammierten Steuerwerken. Diese Art der logischen Organisation hatte sich in den sechziger und siebziger Jahren
entwickelt. Damals hatten die Hauptspeicher nur geringe Speicherkapazitäten
und man versuchte daher, möglichst mächtige Maschinenbefehle bereitzustellen,
um die zur Lösung eines Problems benötigten Verarbeitungsschritte in einem
kurzen Programm codieren zu können. Gleichzeitig wollte man die Maschinenprogrammierung ähnlich komfortabel gestalten wie die Programmierung in einer
höheren Programmiersprache.
Die Befehlssätze derartiger CISC-Prozessoren (Complex Instruction Set
Computer ) bieten eine große Vielfalt an Adressierungsarten, die unterschiedlich
viele Befehlsphasen (Taktzyklen) erfordern und nur mittels Mikroprogrammierung effizient implementiert werden können. Abbildung 1.3 a) zeigt beispielhaft
die Abarbeitung von zwei unterschiedlich langen CISC-Befehlen. Die Auslastung
der einzelnen Prozessor-Funktionseinheiten ist dabei schlecht, da die Maschinenbefehle nur nacheinander abgearbeitet werden können. So wird beispielsweise bei
einem arithmetischen Befehl mit einem Speicherzugriff die ALU im Rechenwerk
nur einen Taktzyklus lang genutzt, während der gesamte Befehl meist mehr als
zehn Taktzyklen erfordert. Durch Einschränkungen der Befehlssatzarchitektur –
vor allem bei den Adressierungsmöglichkeiten – erreicht man mit der Einführung so genannter RISC-Prozessoren 6 (Reduced Instruction Set Computer ) eine
deutliche Vereinfachung der Implementierung. Das Ziel der Architekturveränderungen bestand darin, die Implementierung aller Maschinenbefehle auf eine
feste Anzahl von Mikroschritten zu beschränken, siehe skalarer RISC-Prozessor
(ohne Pipeline) in Abbildung 1.3 b). Pro Taktzyklus ist immer genau ein Mikroschritt ausführbar. Durch die Vereinheitlichung der Befehle in der Anzahl
der Mikroschritte war man dann in der Lage, das Pipelining-Prinzip mit einer
überlappenden Verarbeitung anzuwenden, siehe skalarer RISC-Prozessor (mit Pipeline) in Abbildung 1.3 c). Im Idealfall kann mit diesem Ansatz die Auslastung
der Prozessor-Funktionseinheiten auf 100% gesteigert und in jedem Taktzyklus
ein Befehl beendet werden7 . RISC-Prozessoren wurden zunächst skalar ausge6
7
Ab ca. 1980er Jahre
Mehr zum Pipeline-Prinzip folgt in Kapitel 2.
5
anwendungsspezifische
Mikrochips
CISC-Prinzip
RISC-Prinzip
skalare RISC
6
Kapitel 1. Befehlssatzarchitekturen
legt, d. h. es gab nur eine ALU in der Ausführungsstufe. Durch Hinzunahme
von mehreren Ausführungseinheiten entstehen so genannte Superskalare-RISCProzessoren, die gleichzeitig mehrere Befehle holen, verplanen (Scheduling) und
parallel ausführen können.8 Auf diese Weise können im Vergleich zum skalaren
Prozessor mit jedem Taktzyklus sogar mehrere Befehle gleichzeitig beendet
werden, siehe superskalarer RISC-Prozessor in Abbildung 1.3 d).
Taktzyklen
a) CISC-Prozessor
b) skalarer RISC-Prozessor (ohne Pipeline)
c) skalarer RISC-Prozessor (mit 5-stufiger Pipeline)
d) 3-fach superskalarer RISC-Prozessor (mit Pipeline)
Abbildung 1.3: Verschiedene Abarbeitungsreihenfolgen.
superskalare RISC
Bei superskalaren RISC-Prozessoren erfolgt die Zuteilung der Befehle dynamisch zur Laufzeit mittels Hardware in einer entsprechenden Pipelinestufe.
Darüber hinaus gibt es auch Prozessoren mit parallel arbeitenden Ausführungseinheiten, die auf einem statischen Scheduling basieren. Hierbei wird das
Scheduling nicht erst zur Laufzeit ausgeführt sondern bereits vom Compiler
8
Mehr zur Superskalarität folgt in Kapitel 4.
1.3. Grundlagen der Befehlssatzarchitektur
7
bei der Übersetzung in das Maschinenprogramm vorgegeben. Man spricht von
VLIW-Prozessoren (Very Long Instruction Word ), weil der Compiler mehrere statisches Scheduling
parallelisierbare Befehle zu einem sehr langen Maschinenwort zusammenfügt, bei VLIW
die dann im Prozessor auf die einzelnen Ausführungseinheiten verteilt werden.
Eine Kombination von statischem und dynamischem Scheduling bildet das
EPIC (Explicitly Parallel Instruction Computing), das für die IA64-Architektur
des Intel-Itanium-Prozessors entwickelt wurde, sich auf dem Markt aber nicht
durchsetzen konnte. Die zentrale Idee bestand darin, durch den Compiler das
Hardware-Scheduling zu unterstützen und so den Hardware-Aufwand im Prozessor zu verringern.
1.3
1.3.1
Grundlagen der Befehlssatzarchitektur
Befehlssatz- und Mikroarchitektur
Eine Befehlssatzarchitektur (Instruction Set Architecture, ISA) definiert
die Grenze zwischen Hardware und Software. Sie umfasst den für den Systemprogrammierer und für den Compiler sichtbaren Teil des Prozessors und bildet
die Schnittstelle nach außen. Als Synonym wird auch von der Prozessorarchitektur bzw. dem Programmiermodell eines Prozessors gesprochen. Hierzu
gehören neben dem Befehlssatz (Menge der verfügbaren Befehle), das Befehlsformat, die Adressierungsarten, das System der Unterbrechungen und das Speichermodell (Register und Adressraumaufbau). Eine Prozessorarchitektur macht
hingegen keine Aussagen über die Details der Hardware oder die technischen
Realisierung eines Prozessors. Sie legt lediglich das äußere Erscheinungsbild
eines Prozessors fest, ohne dabei auf interne Vorgänge einzugehen.
Eine Mikroarchitektur (entsprechend dem englischen Begriff Microarchitecture) bezeichnet die Implementierung einer Prozessorarchitektur in einer
speziellen Verkörperung der Architektur – einem Mikroprozessor. Hierzu gehören die Struktur der Hardware und der Entwurf der Kontroll- und Datenpfade.
Zu den Mikroarchitekturtechniken zählen die Art und Stufenzahl des BefehlsPipelining, der Grad der Verwendung der Superskalartechnik, die Art und
Anzahl der internen Ausführungseinheiten eines Mikroprozessors sowie der Einsatz und die Organisation von Cache-Speichern9 . Die Mikroarchitektur definiert
also die logische Organisation eines Prozessors, während die Befehlssatzarchitektur diese Eigenschaften nicht erfasst. Ein Systemprogrammierer oder ein
optimierender Compiler benötigt daher auch Kenntnisse über Mikroarchitektureigenschaften, um effizienten Code für einen Mikroprozessor zu erzeugen und
zu optimieren. Architektur- und Implementierungstechniken werden beide auch
als Prozessortechniken bezeichnet.
Die Trennung von Architektur und Mikroarchitektur macht Benutzerprogramme unabhängig von der konkreten Implementierung des Prozessors, auf dem
9
Unter einem Cache-Speicher versteht man einen kleinen, schnellen und assoziativen
Pufferspeicher, in dem Kopien derjenigen Teile des Hauptspeichers gepuffert werden, auf die
aller Wahrscheinlichkeit nach vom Prozessor in naher Zukunft zugegriffen wird.
Befehlssatzarchitektur
Mikroarchitektur
Prozessortechniken
8
Prozessorfamilie
Programmiermodell
Kapitel 1. Befehlssatzarchitekturen
sie ausgeführt werden. Mikroprozessoren, die derselben Architekturspezifikation
folgen, sind binärkompatibel zueinander. Die verschiedenen Implementierungstechniken zeigen sich dann durch die unterschiedlichen Verarbeitungsgeschwindigkeiten bei der Programmausführung.
Man spricht von einer Prozessorfamilie , wenn alle Prozessoren die gleiche
Basisarchitektur haben, wobei häufig die neueren oder die komplexeren Prozessoren der Familie die Architekturspezifikation erweitern. In einem solchen Fall
der Erweiterung ist nur eine Abwärtskompatibilität mit den älteren bzw. einfacheren Prozessoren der Familie gegeben, d. h. der Programmcode der älteren
Prozessoren läuft auch auf den neueren Prozessoren, jedoch nicht unbedingt
umgekehrt.
Das Programmiermodell eines Prozessors lässt sich durch das Beantworten
der folgenden fünf Fragen konkretisieren:
• Wo werden die Daten gespeichert (Register- und Speichermodell)?
• Wie werden Daten repräsentiert (Datenformate)?
• Welche Operationen können auf den Daten ausgeführt werden (Befehlssatz)?
• Wie werden die Befehle codiert (Befehlsformate)?
• Wie wird auf die Operanden zugegriffen (Adressierungsarten)?
Diese Fragen definieren die Befehlssatz-Architektur und werden in den
folgenden Unterkapiteln näher behandelt.
1.3.2
Registerarten
Adressraumorganisation
Die Adressraumorganisation bestimmt, wo die zu verarbeitenden Daten gespeichert werden und wie diese aus dem Programm heraus adressiert werden
können. Dies umfasst zum einen die für den Programmierer sichtbaren Register
und zum anderen den für den Prozess verfügbaren Hauptspeicherbereich. Wird
eine virtuelle Speicherverwaltung verwendet, so kommt zum physikalischen
Adressraum zusätzlich noch ein logischer Adressraum hinzu, siehe Kapitel 3.
Ein Prozessor enthält eine kleine Anzahl von Registern , d. h. schnellen
Speicherplätzen, auf die in einem Taktzyklus zugegriffen werden kann. Aufgebaut werden diese durch Flipflops (siehe Kurs 1608) und befinden sich direkt
im Prozessorkern. Bei den heute üblichen Pipeline-Prozessoren wird in der
Operandenholphase der Befehls-Pipeline auf die Registeroperanden zugegriffen und in der Resultatspeicherphase in die Register zurückgeschrieben. Diese
vom Programmierer ansprechbaren Register werden als Architekturregister bezeichnet, da sie in der „Architektur“ sichtbar sind. Hierbei kann weiter
unterschieden werden in allgemeine Register (auch Universalregister oder
Allzweckregister genannt), Multimediaregister, Gleitkommaregister und
Spezialregister (Befehlszähler, Statusregister etc.). Zu beachten ist, dass die
1.3. Grundlagen der Befehlssatzarchitektur
im Register gespeicherte Bitfolge allein keine Aussage über deren Inhalt bzw.
Wert macht. Erst mit einem zugehörigen Datenformat wird ersichtlich, ob es
sich z. B. um eine Zahl im Zweierkomplement oder um einen in ASCII10 kodierten String handelt. Die zu den Registern zugehörigen Funktionseinheiten
(Ganzzahl-Einheiten, Gleitkomma-Einheiten etc.) müssen das Datenformat bei
der Ausführung von Befehlen und Operationen auf den Registerwerten berücksichtigen. Register haben durch ihre Größe Einfluss auf die Wortbreite des
Prozessors und den ansprechbaren Adressraum. Wir sprechen in diesem Kurs
von einem n-Bit-Prozessor, wenn die allgemeinen Register n-Bit breit sind.
Da diese Register häufig auch für die Speicheradressierung verwendet werden, ist
dann auch die Breite der effektiven Adresse n-Bit und der adressierbare Speicherbereich 2n Byte groß. Im Bereich von Desktop- und Workstation-Prozessoren
liegt die Wortbreite heute üblicherweise bei 64-Bit (AMD64-Erweiterung von
x86, Itanium, UltraSPARC und weitere). Bei Mikrocontrollern sind auch oft
weiterhin 8-Bit- und 16-Bit-Prozessorkerne im Einsatz.
Bei der Datenübertragung zwischen Speicher und Register kann die Größe Datenlängen
der übertragenen Datenportion mit dem Maschinenbefehl festgelegt werden.
Übliche Datenlängen sind Byte (8 Bits), Halbwort (16 Bits), Wort (32 Bits) und
Doppelwort (64 Bits). In der Assemblerschreibweise werden diese Datengrößen
oft durch die Buchstaben b, h, w und d abgekürzt, wie z. B. lw, um ein Wort
zu laden. Beim Intel x86 oder bei Mikrocontrollern – das sind aus einem Mikroprozessor und verschiedenen Schnittstelleneinheiten bestehende vollständige
Mikrorechner auf einem einzigen Chip – werden auch die Definitionen Wort (16
Bits), Doppelwort (32 Bits) und Quadwort (64 Bits) angewandt.
Neben den Architekturregistern gibt es üblicherweise noch zahlreiche weitere
Register innerhalb des Prozessors, die für interne Vorgänge verwendet werden
und für den Programmierer nach außen hin nicht zugreifbar sind (z. B. als Puffer
bei Datenübertragungen oder Umbenennungsregister in der Piepline etc.).
Außer den direkt adressierbaren Registern werden alle weiteren Adressbereiche eines Prozesses meist11 auf einen einzigen, durchgehend nummerierten logischer
logischen Adressraum abgebildet. Dieser beinhaltet von der Adresse 0 an Adressraum
aufsteigend folgende Bereiche:
• Programmcode und statische Daten
• dynamische Daten (Heap)
• Laufzeitstapel (Stack)
• Bereich für Ein-/Ausgabe und Steuerdaten
Der Heap wächst im Programmverlauf von „unten nach oben“, d. h. von
niedrigeren zu höheren Adressen. Er bietet Platz für die zur Laufzeit dynamisch
10
American Standard Code for Information Interchange; Zeichenkodierung für häufig
verwendete Buchstaben, Zahlen und Sonderzeichen.
11
D.h. bei Verwendung einer virtuellen Speicherverwaltung, siehe Kapitel 3.
9
10
Kapitel 1. Befehlssatzarchitekturen
erzeugten Daten. Der Stack hingegen beginnt meist bei größeren Adressen
und wächst „nach unten“. Er dient zur Verwaltung von Unterbrechungen und
Unterprogrammaufrufen und nimmt z. B. die jeweiligen Rücksprungadressen und
Registerinhalte auf.12 Einem Prozess muss entsprechend genügend Speicherplatz
zugeordnet werden, damit sich Stack und Heap niemals überschneiden, da
die resultierenden Fehler ansonsten zu einem Programmabbruch oder einem
unvorhergesehenem Programmverhalten führen. Mehr zur Speicherverwaltung
wird in Kapitel 3 folgen.
Der Speicherbereich für Ein-/Ausgabe dient zur Kommunikation mit externen Geräten. Eine Möglichkeit dazu ist die Verwendung von Steuerregistern.
Diese im externen Gerät befindlichen Register werden in den Speicherbereich
eingeblendet und können so aus dem Programm heraus adressiert werden
(Memory-Mapped I/O). Alternativen dazu sind die Verwendung eines separaten
Adressraumes oder die Verwendung von I/O-Ports, auf welche wir hier nicht
weiter eingehen werden.
Der Adressraum kann byteadressierbar oder wortadressierbar sein. Bei
einem byteadressierbaren Adressraum kann jedes Byte einzeln adressiert werden.
Bei wortadressierbaren Speichern kann jeweils nur ein Wort, d. h. das Vielfache
eines Bytes adressiert, ausgelesen und beschrieben werden. Die Speicherzugriffe
müssen dafür ausgerichtet (aligned ) sein: Ein Zugriff auf ein Speicherwort mit
einer Länge von n Bytes ab der Speicheradresse A heißt ausgerichtet, wenn
A modulo n = 0 gilt, d. h. der Rest der ganzzahligen Division von A durch
n den Wert 0 ergibt. Speicheradressen werden üblicherweise in hexadezimaler
Schreibweise angegeben. Bei Wortadressierung mit 32 Bit unterscheiden sich
die Adressen benachbarter Speicherwörter um den Wert 4.
Little- und
Für die Speicherung der Bytes innerhalb eines Wortes unterscheidet man
Big-Endian-Format zwei Arten der Anordnung:
• Das Big-Endian-Format(„most significant byte first“) speichert von
links nach rechts, d. h., die Adresse des Speicherworts ist die Adresse des
höchstwertigen Bytes des Speicherworts.
• Das Little-Endian-Format („least significant byte first“) speichert von
rechts nach links, d. h., die Adresse eines Speicherworts ist die Adresse des
niedrigstwertigen Bytes des Speicherworts.
Die Anordnung betrifft nur die Speicherung der Bytes innerhalb eines Wortes.
Für die Assemblerprogrammierung ist diese Unterscheidung meistens irrelevant,
da beim Laden eines Operanden aus dem Speicher in ein Register die Maschine den zu ladenden Wert so anordnet, wie man es erwartet, nämlich die
höchstwertigen Stellen links (Big-Endian-Format). Dies geschieht auch für das
Little-Endian-Format, das bei einigen Prozessorarchitekturen, insbesondere den
Intel-Prozessoren, aus Kompatibilitätsgründen mit älteren Prozessoren weiter
12
Vgl. Kurseinheit 4 aus Computersysteme I. Alternativ werden Rücksprungadressen auch
in einem speziellen Register gesichert.
1.3. Grundlagen der Befehlssatzarchitektur
verwendet wird. Beachten muss man das Byte-Anordnungsformat eines Prozessors hingegen beim direkten Zugriff auf einen Speicherplatz als Byte oder
Wort oder bei der Arbeit mit einem Debugger, einem Werkzeug zum Finden
von Fehlern in Programmen. Die Byte-Reihenfolge wird außerdem relevant,
wenn Daten zwischen zwei Rechnern verschiedener Architekturen ausgetauscht
werden. Heute setzt sich insbesondere durch die Bedeutung von Rechnernetzen
das Big-Endian-Format durch, das häufig auch als Netzwerk-Format bezeichnet
wird. In Abbildung 1.4 ist beispielhaft eine Zahl im Dezimal-, Binär- und Hexadezimalformat dargestellt und es wird gezeigt, wie diese im Speicher im Bigbzw. Little-Endian-Format gespeichert wird.
Abbildung 1.4: Anordnung der Bytes im Speicher bei dem Big- und LittleEndian-Format.
1.3.3
Datenformate
Wie in höheren Programmiersprachen gibt es auch in maschinennahen Sprachen
verschiedene Datenformate. Diese geben vor, wie eine Bitfolge zu interpretieren
ist bzw. wie ein bestimmter Wert als Bitfolge repräsentiert werden kann. Im
Vergleich zu höheren Sprachen sind die Datenformate bei Maschinensprachen
oft einfach gehalten und es wird sich auf wenige, häufig benötigte Datenformate
beschränkt.
Gebräuchliche Datenformate sind Einzelbit-, Ganzzahl- (Integer), Gleitkomma, und Multimediaformate, welche im Folgenden näher erläutert werden.
Einzelbitdatenformate können alle Datenlängen von 8 Bit bis hin zu 256
Bit aufweisen. Das Besondere dabei ist, dass einzelne Bits eines solchen Worts
manipuliert werden können.
Ganzzahldatenformate (siehe Abbildung 1.5) können unterschiedlich lang Ganzzahlund jeweils mit Vorzeichen (signed ) oder ohne Vorzeichen (unsigned ) definiert datenformate
sein. Die Darstellung als Zweierkomplement mit implizitem Vorzeichen wurde
11
12
Kapitel 1. Befehlssatzarchitekturen
bereits im Kurs 1608 eingeführt. Gelegentlich findet man auch gepackte (packed )
und ungepackte (unpacked ) BCD-Zahlen (Binary Coded Decimal ) und ASCIIZeichen (American Standard Code for Information Interchange). Eine BCD-Zahl
codiert die Ziffern 0 bis 9 als Dualzahlen in vier Bits. Das gepackte BCD-Format
codiert zwei BCD-Zahlen pro Byte, also acht BCD-Zahlen pro 32-Bit-Wort. Das
ungepackte BCD-Format codiert eine BCD-Zahl an den vier niederwertigen
Bit-Positionen eines Bytes, also nur vier BCD-Zahlen pro 32-Bit-Wort. Der
ASCII-Code belegt ein Byte pro Zeichen, sodass vier ASCII-codierte Zeichen in
einem 32-Bit-Wort untergebracht werden.
Abbildung 1.5: Ganzzahldatenformate.
1.3. Grundlagen der Befehlssatzarchitektur
Die Gleitkommadatenformate (siehe Abbildung 1.6) wurden mit dem Gleitkomma–
IEEE-754-Standard definiert und unterscheiden Gleitkommazahlen mit einfacher datenformate
(32 Bit) oder doppelter (64 Bit) Genauigkeit. Das Format mit erweiterter
Genauigkeit umfasst 80 Bit und kann herstellergebunden variieren. Beispielsweise
verwendeten die Intel Pentium-Prozessoren intern ein solches erweitertes Format
mit 80 Bit breiten Gleitkommazahlen.
Eine Gleitkommazahl f wird nach dem IEEE-Standard wie folgt dargestellt:
f = (−1)s · 1.m · 2e−b
Dabei steht s für das Vorzeichenbit (0 für positiv, 1 für negativ), e für
den verschobenen (biased ) Exponenten, b für die Verschiebung (bias) und m
für den Mantissenteil. Die führende Eins in der obigen Gleichung ist implizit
vorhanden und benötigt kein Bit im Mantissenfeld. Für den Mantissenteil m gilt:
m = .m1 · · · mp mit 13
p = 23 für die einfache,
p = 52 für die doppelte und
p = 63 für die erweiterte Genauigkeit (inkl. führender Eins der Mantisse)
Die Verschiebung b ist definiert als:
b = 2ne−1 − 1
wobei ne die Anzahl der Exponentenbits (je nach Genauigkeit 8, 11 oder
15) angibt. Den Exponenten E erhält man aus der Gleichung:
E = e − b = e − (2ne−1 − 1)
Abbildung 1.6: Gleitkomma-Datenformate.
13
Beachten Sie den führenden Dezimalpunkt.
13
14
Multimedia–
datenformate
Kapitel 1. Befehlssatzarchitekturen
Multimediadatenformate definieren 64 oder mehr Bit breite Wörter.14
Durch Multimedia-Erweiterungen des Befehlssatzes ist es möglich, häufig verwendete und wiederkehrende Operationsfolgen, wie sie bei Multimedia-Anwendungen
typischerweise auftreten, effizienter und mit einer einzigen Operation abzuarbeiten. Dafür werden spezielle Multimediaeinheiten verwendet, welche MultimediaOperationen ausführen und die Programmlaufzeit so erheblich verkürzen können.
Man unterscheidet allgemein zwei Arten von Multimediadatenformaten: die
bitfeldorientierten Formate unterstützen Operationen auf Pixeldarstellungen,
wie sie für die Videocodierung oder -decodierung benötigt werden. Die graphikorientierten Formate unterstützen komplexe graphische Datenverarbeitungsoperationen. Die Multimediadatenformate für die bitfeldorientierten Formate
sind in 8 oder 16 Bit breite Teilfelder zur Repräsentation jeweils eines Pixels
aufgeteilt. Die graphikorientierten Formate beinhalten zwei oder mehr einfach
genaue Gleitkommazahlen.
Darüber hinaus gibt es noch weitere Befehlssatzerweiterungen (z. B. für
Verschlüsselung, Virtualisierung), welche wiederum ihre eigenen Datenformate
verwenden. Auf diese soll hier aber nicht weiter eingegangen werden.
Selbsttestaufgabe 1.1
1. Wandeln Sie folgende Dezimalzahlen in das 32-Bit IEEE-754-Format um:
• 12.78125
• -784
• -0.15625
2. Wandeln Sie folgende 32-Bit-Zahlen im IEEE-754-Format in das Dezimalsystem um:
• 11000010010110000000000000000000
• 01000100010111000100011000000000
• 00111111110111000000000000000000
3. Wandeln Sie folgende Ganzzahlen in das 32-Bit-Zweierkomplement um:
• 125
• -1348
• -80292
4. Geben Sie folgende Strings als eine Bytefolge im ASCII-Format an:
• „FernUni“
• „Cache“
• „Sandbox“
14
Die Befehlssatzerweiterung AVX512 verwendet z. B. Wortbreiten bis 512 Bit.
1.3. Grundlagen der Befehlssatzarchitektur
5. Wandeln Sie folgende Dezimalzahlen sowohl in die gepackte als auch die
ungepackte BCD-Darstellung um:
• 1548
• 4828
1.3.4
Befehlssatz
Der Befehlssatz (Instruction Set) definiert, welche Operationen auf den Daten Befehlssatz
ausgeführt werden können. Er legt daher die Grundoperationen eines Prozessors
fest, die ein Assembler-Programmierer (bzw. der Compiler) in seinem Programm
verwenden kann. Man kann dabei die folgenden Befehlsarten unterscheiden:
Datenbewegungsbefehle (Data Movement) übertragen Daten von einer
Speicherstelle zu einer anderen. Falls es einen separaten Ein-/Ausgabeadressraum gibt, so gehören hierzu auch die Ein-/Ausgabebefehle. Auch die Stapelspeicherbefehle Push und Pop fallen – sofern vorhanden – in diese Kategorie.
Arithmetisch-logische Befehle (Integer Arithmetic and Logical ) können
Ein-, Zwei- oder Dreioperandenbefehle sein. Prozessoren nutzen meist verschiedene Befehle für verschiedene Datenformate ihrer Operanden. Meist werden durch
den Befehlsopcode arithmetische Befehle mit oder ohne Vorzeichen unterschieden.
Beispiele arithmetischer Operationen sind Addieren ohne/mit Übertrag, Subtrahieren ohne/mit Übertrag, Inkrementieren und Dekrementieren, Multiplizieren
ohne/mit Vorzeichen, Dividieren ohne/mit Vorzeichen und Komplementieren im
Zweierkomplement. Beispiele logischer Operationen sind die bitweise Negation-,
Und-, Oder- und Antivalenz-Operationen.
Schiebe- und Rotationsbefehle (Shift, Rotate) schieben die Bits eines
Worts um eine Anzahl von Stellen entweder nach links oder nach rechts bzw.
rotieren die Bits nach links oder rechts, siehe Abbildung 1.7).
Abbildung 1.7: Einige Schiebe- und Rotationsbefehle.
Beim Schieben gehen die herausfallenden Bits verloren, beim Rotieren werden diese auf der anderen Seite wieder eingefügt. Beispiele von Schiebe- und
15
16
Kapitel 1. Befehlssatzarchitekturen
Rotationsoperationen sind das Linksschieben, Rechtsschieben, Linksrotieren
ohne Übertragsbit, Linksrotieren durchs Übertragsbit, Rechtsrotieren ohne
Übertragsbit und das Rechtsrotieren durchs Übertragsbit. Dem arithmetischen
Linksschieben entspricht die Multiplikation mit 2. Dem arithmetischen Rechtsschieben entspricht die ganzzahlige Division durch 2. Dies gilt jedoch nur für
positive Zahlen. Bei negativen Zahlen im Zweierkomplement muss zur Vorzeichenerhaltung das höchstwertige Bit in sich selbst zurückgeführt werden. Daraus
ergibt sich der Unterschied des logischen und arithmetischen Rechtsschiebens.
Beim Rotieren wird ein Register als geschlossene Bit-Kette betrachtet. Ein so
genanntes Übertragsbit (Carry Flag) im Prozessorstatusregister kann wahlweise
mitbenutzt oder als zusätzliches Bit einbezogen werden.
Multimediabefehle (Multimedia Instructions) führen taktsynchron dieselbe Operation auf mehreren Teiloperanden innerhalb eines Operanden aus,
siehe Abbildung 1.8. Man unterscheidet zwei Arten von Multimediabefehlen:
bitfeldorientierte und graphikorientierte Multimediabefehle.
Abbildung 1.8: Grundprinzip einer Multimediaoperation.
Bei den bitfeldorientierten Multimediabefehlen repräsentieren die Teiloperanden Pixel. Typische Operationen sind Vergleiche, logische Operationen, Schiebeund Rotationsoperationen, Packen und Entpacken von Teiloperanden in oder
aus Gesamtoperanden sowie arithmetische Operationen auf den Teiloperanden
entsprechend einer Saturationsarithmetik: Bei einer solchen Arithmetik werden
Zahlbereichsüberschreitungen auf die höchstwertige bzw. niederstwertige Zahl
abgebildet. Dadurch wird z. B. verhindert, dass die Summe zweier positiver
Zahlen negativ wird.
Bei den graphikorientierten Multimediabefehlen repräsentieren die Teiloperanden einfach genaue Gleitkommazahlen, also zwei 32-Bit-Gleitkommazahlen
in einem 64-Bit-Wort bzw. vier 32-Bit-Gleitkommazahlen in einem 128-BitWort. Die Multimediaoperationen führen dieselbe Gleitkommaoperation auf
allen Teiloperanden aus.
1.3. Grundlagen der Befehlssatzarchitektur
Gleitkommabefehle (Floating-point Instructions) repräsentieren arithmetische Operationen und Vergleichsoperationen, aber auch zum Teil komplexe
Operationen wie Quadratwurzelbildung oder transzendente Funktionen auf
Gleitkommazahlen.
Programmsteuerbefehle (Control Transfer Instructions) sind alle Befehle, die den Programmablauf direkt ändern, die bedingten und unbedingten
Sprungbefehle, Unterprogrammaufruf und -rückkehr sowie Unterbrechungsaufruf
und -rückkehr.
Systemsteuerbefehle (System Control Instructions) erlauben es in manchen Befehlssätzen, direkten Einfluss auf Prozessor- oder Systemkomponenten
wie z. B. den Cachespeicher oder die Speicherverwaltungseinheit zu nehmen15 .
Weiterhin gehören der HALT-Befehl zum Anhalten des Prozessors und Befehle
zur Verwaltung der elektrischen Leistungsaufnahme zu dieser Befehlsgruppe,
die üblicherweise nur vom Betriebssystem genutzt werden dürfen.
Synchronisationsbefehle ermöglichen es, Synchronisationsoperationen
zur Prozess- und Unterbrechungsbehandlung durch das Betriebssystem zu
implementieren.16 Wesentlich ist dabei, dass bestimmte, eigentlich sonst nur
durch mehrere Befehle implementierbare Synchronisationsoperationen ohne
Unterbrechung (auch als „atomar“ bezeichnet) ablaufen müssen. Ein Beispiel
ist der SWAP-Befehl, der als atomare Operation einen Speicherwert mit einem
Registerwert vertauscht. Noch komplexer ist der TAS-Befehl (Test and Set),
der als atomare Operation einen Speicherwert liest, diesen auf Null testet, ggf.
ein Bedingungsbit im Prozessorstatuswort setzt und einen bestimmten Wert
zurückspeichert. Die Ausführung als atomare Operation verhindert, dass z. B.
ein weiterer Prozessor zwischenzeitlich dieselbe Speicherzelle liest und dort noch
den alten, unveränderten Wert vorfindet.
1.3.5
Befehlsformate
Das Befehlsformat (instruction format) definiert, wie die Befehle codiert
sind. Eine Befehlscodierung beginnt mit dem Opcode (Operation Code), der den
Befehl selbst festlegt. In Abhängigkeit vom Opcode werden weitere Felder im Befehlsformat benötigt. Für arithmetisch-logische Befehle sind dies die Adressfelder,
um Quell- und Zieloperanden zu spezifizieren, und für die Lade-/Speicherbefehle
die Quell- und Zieladressangaben. Je nach Architektur kann es sich hierbei um
Register oder Speicheradressen handeln. Bei Programmsteuerbefehlen wird der
als nächstes auszuführende Befehl adressiert.
Je nach der Art, wie die arithmetisch-logischen Befehle ihre Operanden Adressformate
adressieren, unterscheidet man vier Klassen von Befehlssätzen:
• Das Dreiadressformat (3-Address Instruction Format), bestehend aus
dem Opcode, zwei Quell- (im Folgenden mit src1, scr2 bezeichnet) und
einem Zieloperandenbezeichner (dest):
15
Mehr zu Cache-Speichern und Speicherverwaltung folgt in Kapitel 3.
Unter einem Prozess versteht man ein in der Ausführung befindliches oder ausführbereites
Programm mit seinen Daten, den Konstanten und Variablen mit ihren aktuellen Werten.
16
17
18
Kapitel 1. Befehlssatzarchitekturen
opcode
dest
src1
scr2
• Das Zweiadressformat (2-Address Instruction Format), bestehend aus
dem Opcode, einem Quell- und einem kombinierten Quell-/Zieloperandenbezeichner, d. h. einem Operandenbezeichner, der sowohl Quell- als auch
Zieloperanden festlegt:
opcode
dest/src1
src2
• Das Einadressformat (1-Address Instruction Format), bestehend aus
dem Opcode und einem Quelloperandenbezeichner:
opcode
src
Hierbei wird ein im Prozessor ausgezeichnetes Register, das so genannte
Akkumulatorregister, implizit adressiert. Dieses Register enthält immer
einen Quelloperanden und nimmt das Ergebnis der Operation auf.
• Das Nulladressformat (0-Address Instruction Format), bestehend nur
aus dem Opcode:
opcode
Voraussetzung für die Verwendung eines Nulladressformats ist eine Stackarchitektur, die weiter unten beschrieben wird.
Architekturklassen
Die Adressformate hängen eng mit folgender Klassifizierung von Befehlssatzarchitekturen zusammen:
• Arithmetisch-logische Befehle sind meist Dreiadressbefehle, die zwei Operandenregister und ein Zielregister angeben. Falls nur die Lade- und
Speicherbefehle Daten zwischen dem Hauptspeicher (bzw. Cache-Speicher)
und den Registern transportieren, spricht man von einer Lade-/Speicherarchitektur (Load /Store Architecture). Da arithmetische und logische
Operationen nur mit Operanden aus Registern ausgeführt und die Ergebnisse auch nur in Registern gespeichert werden können, spricht man auch
von einer Register-Register-Architektur.
• Analog kann man von einer Register-Speicher-Architektur sprechen,
wenn in arithmetisch-logischen Befehlen mindestens einer der Operandenbezeichner ein Register bzw. einen Speicherplatz im Hauptspeicher
adressiert. Falls gar keine Register existieren, muss jeder Operandenbezeichner eine Speicheradresse sein und man kann von einer SpeicherSpeicher-Architektur sprechen.17 Die ersten Mikroprozessoren besaßen
ein Akkumulatorregister, das bei arithmetisch-logischen Befehlen immer
implizit sowohl Quelle als auch Ziel darstellte, so dass Einadressbefehle
genügten. Solche Akkumulatorarchitekturen sind gelegentlich noch
bei einfachen Mikrocontrollern und Digitalen Signalprozessoren (DSPs)
zu finden.
17
Der einzige uns bekannte Prozessor dieses Typs war der TI 9900 der Firma Texas
Instruments.
1.3. Grundlagen der Befehlssatzarchitektur
• So genannten Stack- oder Kellerarchitekturen verwalten ihre Operandenregister als Stapel (Stack ). Eine zweistellige Operation verknüpft
die beiden obersten Stackeinträge miteinander, löscht beide Inhalte vom
Registerstapel und speichert das Resultat auf dem obersten Register des
Stapels wieder ab.
In der Regel kann bei heutigen Mikroprozessoren jeder Registerbefehl auf
jedes beliebige Register gleichermaßen zugreifen. Bei älteren Prozessoren war
dies jedoch keineswegs der Fall. Noch beim Intel 8086 gab es viele Anomalien beim Registerzugriff. Beispiele für Stackarchitekturen sind die von der
Java Virtual Machine hergeleiteten Java-Prozessoren, die Verwaltung der Gleitkommaregister der Intel 8087- bis 80387-Gleitkomma-Coprozessoren sowie der
4-Bit-Mikroprozessor ATAM862 der Firma Atmel.
Die Befehlscodierung kann eine feste oder eine variable Befehlslänge benut- Befehlslänge
zen. Um die Decodierung zu vereinfachen, nutzen RISC-Befehlssätze meist ein
Dreiadressformat mit einer festen Befehlslänge. CISC-Befehlssätze dagegen nutzen Register-Speicher-Befehle und benötigen dafür meist variable Befehlslängen.
Um den Speicherbedarf zu minimieren, hat man früher mit variablen Opcodelängen bei CISC-Befehlssätzen gearbeitet. Hierzu wurden häufig benutzten Befehlen
möglichst kurze Opcodes zugeordnet, was den Gesamtspeicherplatz des Programms verringert. Variable Befehlslängen finden sich auch in Stackmaschinen
wie z. B. den Java-Prozessoren.
Abbildung 1.9 zeigt, wie sich die verschiedenen Befehlssatz-Architekturen
auf die Übersetzung eines Programms in (Pseudo-)Assemblersprache auswirken.
Das Beispielprogramm hat folgenden Aufbau:
C =A+B
D =C −B
Die Syntax der Assemblerbefehle schreibt vor, dass nach dem Opcode zuerst
der Zieloperandenbezeichner und dann der oder die Quelloperandenbezeichner stehen. Manche andere Assemblernotationen verfahren genau umgekehrt
und verlangen, dass der oder die Quelloperandenbezeichner vor dem Zieloperandenbezeichner stehen müssen. Gut zu erkennen ist, dass Register-RegisterArchitekturen und Register-Speicher-Architekturen Zwei- oder Dreiadressbefehle
verwenden. Akkumulator- bzw. Stackarchitekturen kommen hingegen mit Einoder sogar Nulladressbefehlen aus.
Hinweis
Betrachten Sie erneut Abbildung 1.9 und überlegen Sie, wie sich die
Befehlssatz-Architektur auf die Größe und die Laufzeit eines Programms
auswirkt! Beachten Sie außerdem, wie sich dies bei festen oder variablen
Befehlslängen ändern würde! Die Architektur hat, wie gut zu erkennen ist,
auch einen Einfluss auf die Anzahl der benötigten Speicherzugriffe.
19
20
Kapitel 1. Befehlssatzarchitekturen
Abbildung 1.9: Beispielprogramm in unterschiedlichen Befehlssatz-Architektur.
1.3.6
effektive, virtuelle
und physikalische
Adresse
Adressierungsarten
Die Adressierungsart definiert, wie aus dem Programm auf Daten zugegriffen
wird. Sie legt die verschiedenen Möglichkeiten fest, wie eine Operanden- oder
eine Sprungzieladresse in dem Prozessor berechnet wird. Die Adresse kann eine
im Befehlswort stehende Konstante, ein Register oder einen Speicherplatz im
Hauptspeicher spezifizieren.
Wenn ein Speicherplatz im Hauptspeicher adressiert wird, so heißt die durch
die Adressierungsart spezifizierte Speicheradresse die effektive Adresse. Eine
effektive Adresse entsteht im Prozessor nach Ausführung der jeweiligen Adressierungsart. Ohne virtuelle Speicherverwaltung entspricht die effektive Adresse
der physikalischen Adresse für den Zugriff auf den Hauptspeicher. Üblicherweise
verwenden heutige Prozessoren allerdings eine virtuelle Speicherverwaltung,
wodurch eine weitere Adressumrechnung notwendig wird. Die effektive Adresse
entspricht dann der virtuellen Adresse, welche mit Hilfe einer in Hardware
realisierten Speicherverwaltungseinheit (Memory Management Unit – MMU )
mit Segmentierung und/oder Seitenverwaltung in die physikalische Adresse
umgerechnet wird. Das Ergebnis der Adressumrechnung vom virtuellen in den
physikalischen Adressraum ist ebenfalls wieder die physikalische Adresse für den
eigentlichen Speicherzugriff. Der Zusammenhang der verschiedenen Bezeichnungen wird noch einmal in Abbildung 1.10 verdeutlicht. Im Folgenden betrachten
wir zunächst nur die Erzeugung einer effektiven Adresse aus den Angaben in
einem Maschinenbefehl18 .
Neben den „expliziten“ Adressierungsarten kann die Operandenadressierung
auch „implizit“ über die Befehlssatz-Architektur oder durch den Opcode des
Befehls festgelegt sein. In der oben erwähnten Stackarchitektur sind z. B. die
beiden Quelloperanden und der Zieloperand einer arithmetisch-logischen Operation implizit als die beiden bzw. der oberste Stackeintrag festgelegt. Ähnlich
ist bei einer Akkumulatorarchitektur das Akkumulatorregister bereits implizit
18
Näheres zur virtuellen Speicherverwaltung folgt in Kapitel 3
1.3. Grundlagen der Befehlssatzarchitektur
Physikalische Adresse
Effektive Adresse
Virtuelle Adresse
21
Virtuelle Speicherverwaltung
Abbildung 1.10: Adressumrechnung.
als ein Quell- und als Zielregister der arithmetisch-logischen Operationen fest
vorgegeben. Bei einer Register-Speicher-Architektur ist meist das eine Operandenregister fest vorgegeben, während der zweite Operand und das Ziel explizit
adressiert werden müssen.
Durch den Opcode eines Befehls wird bei Spezialbefehlen (Programm- oder
Systemsteuerbefehle) häufig ein besonderes Register als Quelle und/oder Ziel
adressiert. Weiterhin wird in vielen Befehlssätzen im Opcode festgelegt, ob ein
Bit des Prozessorstatusregisters mit verwendet wird.
Bei den im Folgenden aufgeführten expliziten Adressierungsarten können
drei Klassen unterschieden werden:
• die Klasse der Registeradressierung und unmittelbaren Adressierung,
• die Klasse der einstufigen und
• der Klasse der zweistufigen Speicheradressierungen.
Bei der Registeradressierung steht der Operand in einem Register und bei
der unmittelbaren Adressierung steht der Operand direkt im Befehlswort. In
beiden Fällen sind weder Adressrechnung noch ein zusätzlicher Speicherzugriff
nötig.
Bei der einstufigen Speicheradressierung steht der Operand im Speicher
und für die effektive Adresse ist nur eine Adressrechnung notwendig. Diese
Adressrechnung kann einen oder mehrere Registerinhalte sowie einen im Befehl
stehenden Verschiebewert oder einen Skalierungsfaktor, jedoch keinen weiteren
Speicherinhalt betreffen.
Weniger gebräuchlich sind die zweistufigen Speicheradressierungen, bei denen mit einem Teilergebnis der Adressrechnung wiederum auf den Speicher
zugegriffen wird, um einen weiteren Datenwert für die Adressrechnung zu holen.
Es ist somit ein doppelter Speicherzugriff notwendig, bevor der Operand für die
eigentliche Berechnung zur Verfügung steht.
Im Folgenden zeigen wir eine Auswahl von Datenadressierungsarten, Datenadressierungsdie in heutigen Mikroprozessoren und Mikrocontrollern Verwendung finden. arten
Abgesehen von der Register- und der unmittelbaren Adressierung sind dies
allesamt einstufige Speicheradressierungsarten.
22
Kapitel 1. Befehlssatzarchitekturen
Bei der Registeradressierung (Register ) steht der Operand direkt in einem
Register, siehe Abbildung 1.11. Der Bezeichner eines Registers im Assemblercode
wird bei der Übersetzung in Maschinensprache in eine interne Nummer des
Registers überführt. Wie bereits weiter oben erwähnt, handelt es sich hierbei
je nach Befehl um ein Ganzzahl-, Gleitkomma- Multimedia- oder sonst ein
Spezialregister.
Abbildung 1.11: Registeradressierung.
Bei der unmittelbaren Adressierung (immediate oder literal ) steht der
Operand als Konstante direkt im Befehlswort, siehe Abbildung 1.12. Je nach
Befehlslänge können hierfür unterschiedlich viele Bits zur Verfügung stehen.
Abbildung 1.12: Unmittelbare Adressierung.
Bei der direkten oder absoluten Adressierung (direct, absolute) steht
die Adresse eines Speicheroperanden im Befehlswort, siehe Abbildung 1.13.
Auch hierfür können je nach Befehlslänge unterschiedlich viele Bits zur Verfügung stehen. Die Länge der Speicheradresse gibt die Größe des logischen bzw.
physikalischen19 Adressraums an.
Abbildung 1.13: Direkte Adressierung.
19
Falls keine virtuelle Speicherverwaltung verwendet wird.
1.3. Grundlagen der Befehlssatzarchitektur
Bei der registerindirekten Adressierung (Register indirect oder Register
deferred ) steht die Operandenadresse in einem Register. Der Inhalt des Registers
dient als Zeiger auf eine Speicheradresse, siehe Abbildung 1.14.
Abbildung 1.14: Registerindirekte Adressierung.
Als Spezialfälle der registerindirekten Adressierung können die registerindirekten Adressierungen mit Autoinkrement/Autodekrement (Autoincrement/Autodecrement) betrachtet werden. Diese arbeiten wie die registerindirekte Adressierung, inkrementieren bzw. dekrementieren aber den Registerinhalt. In Abbildung 1.14 ist dies die „Speicheradresse“, die vor oder nach
dem Benutzen um die Länge des adressierten Operanden inkrementiert bzw.
dekrementiert wird. Dementsprechend unterscheidet man die registerindirekte
Adressierung mit Präinkrement, mit Postinkrement, mit Prädekrement
und mit Postdekrement (üblich sind Postinkrement und Prädekrement)20 .
Diese Adressierungsarten sind für den Zugriff auf Feldern (Arrays) in Schleifen
nützlich. Der Registerinhalt zeigt auf den Anfang oder das letzte Element eines
Feldes und jeder Zugriff erhöht bzw. erniedrigt den Registerinhalt um die Länge
eines Feldelements.
Bei der registerindirekten Adressierung mit Verschiebung (Displacement, Register indirect with Displacement oder based ) wird die effektive Adresse
eines Operanden als Summe eines Registerwerts und des Verschiebewerts (Displacement) berechnet, d. h. eines konstanten, vorzeichenbehafteten Werts, welcher
im Befehl steht, siehe Abbildung 1.15.
20
Im Befehlssatz oft geklammert angegeben als +(Reg), (Reg)+, −(Reg) und (Reg)−
23
24
Kapitel 1. Befehlssatzarchitekturen
Abbildung 1.15: Registerindirekte Adressierung mit Verschiebung.
Die indizierte Adressierung (indirect indexed ) errechnet die effektive
Adresse als Summe eines Registerinhalts und dem Wert eines weiteren Registers,
welches bei manchen Prozessoren als spezielles Indexregister vorliegt. Damit
können Datenstrukturen beliebiger Größe und mit beliebigem Abstand durchlaufen werden. Angewendet wird die indizierte Adressierung auch beim Zugriff auf
Tabellen, wobei der Index erst zur Laufzeit ermittelt wird, siehe Abbildung 1.16.
Abbildung 1.16: Indizierte Adressierung.
Befehlsadressierungsarten
Die indizierte Adressierung mit Verschiebung (indirect indexed with
Displacement) ist eine Erweiterung der indizierten Adressierung, bei der zur
Summe der beiden Registerwerte noch ein im Befehl stehender Verschiebewert
(Displacement) hinzuaddiert wird, siehe Abbildung 1.17.
Zur Änderung des Befehlszählregister (Program Counter – PC) durch Programmsteuerbefehle (bedingte oder unbedingte Sprünge sowie Unterprogrammaufruf und -rücksprung) sind nur zwei Befehlsadressierungsarten üblich:
Der befehlszählerrelative Modus (PC-relative) addiert einen Verschie-
1.3. Grundlagen der Befehlssatzarchitektur
Abbildung 1.17: Indizierte Adressierung mit Verschiebung.
bewert (Displacement, PC-Offset) zum Inhalt des Befehlszählerregisters bzw.
häufig auch zum Inhalt des inkrementierten Befehlszählers PC + 4, denn dieser
wird bei Architekturen mit 32-Bit-Befehlsformat meist automatisch um vier
erhöht. Die Sprungzieladressen sind häufig in der Nähe des augenblicklichen
Befehlszählerwertes, so dass nur wenige Bits für den Verschiebewert im Befehl
benötigt werden, siehe Abbildung 1.18.
Abbildung 1.18: Befehlszählerrelative Adressierung.
Der befehlszählerindirekte Modus (PC-indirect) lädt den neuen Befehlszähler aus einem allgemeinen Register. Das Register dient als Zeiger auf eine
Speicheradresse, bei der im Programmablauf fortgefahren wird, siehe Abbildung 1.19.
Tabelle 1.2 fasst die verschiedenen Adressierungsarten nochmals zusammen. In der Tabelle bezeichnet Mem[R2] den Inhalt des Speicherplatzes, dessen
Adresse durch den Inhalt des Registers R2 gegeben ist; const, displ können
Dezimal-, Hexadezimal-, Oktal- oder Binärzahlen sein; step bezeichnet die
Feldelementbreite und inst_step die Befehlsschrittweite in Abhängigkeit von
der Befehlswortbreite, z. B. vier bei Vierbyte-Befehlswörtern.
25
26
Kapitel 1. Befehlssatzarchitekturen
Abbildung 1.19: Befehlszählerindirekte Adressierung.
Tabelle 1.2: Beispielbefehle mit verschiedenen Adressierungsarten.
Adressierungsart Beispielbefehl
Bedeutung
Register
load R1,R2
R1 ← R2
unmittelbar
load R1,const
R1 ← const
direkt, absolut
load R1,(const)
R1 ← Mem[const]
registerindirekt
load R1,(R2)
R1 ← Mem[R2]
Postinkrement
load R1,(R2)+
R1 ← Mem[R2]
R2 ← R2 + step
Prädekrement
load R1,–(R2)
R2 ← R2 – step
R1 ← Mem[R2]
registerindirekt mit load R1,displ(R2)
Verschiebung
R1 ← Mem[displ+R2]
indiziert
load R1,(R2,R3)
R1 ← Mem[R2 + R3]
indiziert mit
Verschiebung
load R1,displ(R2,R3)
R1 ←Mem[displ+R2+R3]
befehlszählerrelativ
branch displ
PC←PC+inst_step+displ,
falls Sprung genommen
PC ← PC+inst_step , sonst
befehlszählerindirekt branch R2
PC ← R2,
falls Sprung genommen
PC ← PC+inst_step, sonst
1.3. Grundlagen der Befehlssatzarchitektur
27
Selbsttestaufgabe 1.2
Welche einfacheren Adressierungsarten lassen sich durch die registerindirekte
Adressierung mit Verschiebung ersetzen?
1.3.7
CISC- und RISC-Prinzipien
Bei der Entwicklung der Großrechner in den 60er und 70er Jahren hatten
technologische Bedingungen wie der teure und langsame Hauptspeicher21 zu einer
immer größeren Komplexität der Rechnerarchitekturen geführt. Um den teuren
Hauptspeicher optimal zu nutzen, wurden komplexe Maschinenbefehle entworfen,
die mehrere Operationen mit nur einem Opcode codieren können. Damit konnte
auch der im Verhältnis zum Prozessor langsame Speicherzugriff überbrückt
werden, denn ein Maschinenbefehl umfasste genügend Operationen, um die
Zentraleinheit für mehrere, eventuell sogar mehrere Dutzend Prozessortakte zu
beschäftigen.
Eine auch heute noch übliche Implementierungstechnik, um den Ablauf Mikroprogramkomplexer Maschinenbefehle zu steuern, ist die Mikroprogrammierung, bei mierung
der ein Maschinenbefehl durch eine Folge von Mikrobefehlen implementiert
wird. Diese Mikrobefehle stehen in einem Mikroprogrammspeicher innerhalb
des Prozessors und werden von einer Mikroprogrammsteuereinheit interpretiert,
vgl. Kurs 1608.
Die Komplexität der Großrechner zeigte sich in mächtigen Maschinenbefeh- CISC und RISC
len, umfangreichen Befehlssätzen, vielen Befehlsformaten, Adressierungsarten
und spezialisierten Registern. Rechner mit diesen Architekturcharakteristika
wurden später mit dem Akronym CISC (Complex Instruction Set Computers) bezeichnet. Ähnliche Architekturcharakteristika zeigten sich auch bei den
Intel-80x86- und den Motorola-680x0-Mikroprozessoren, die ab Ende der 70er
Jahre entstanden. Etwa 1980 entwickelte sich ein gegenläufiger Trend, der die
Prozessorarchitekturen bis heute maßgeblich beeinflusst hat: das RISC(Reduced
Instruction Set Computer ) genannte Architekturkonzept.
Bei der Untersuchung von Maschinenprogrammen war beobachtet worden,
dass manche Maschinenbefehle und komplexe Adressierungsarten fast nie verwendet wurden. Komplexe Befehle lassen sich durch eine Folge einfacher Befehle
ersetzen, komplexe Adressierungsarten entsprechend durch eine Folge einfacherer
Adressierungsarten. Die vielen unterschiedlichen Adressierungsarten, Befehlsformate und -längen von CISC-Architekturen erschwerten die Codegenerierung
durch den Compiler. Das komplexe Steuerwerk, das notwendig war, um einen
großen Befehlssatz in Hardware zu implementieren, benötigte viel Chip-Fläche
und führte zu langen Entwicklungszeiten.
Das RISC-Architekturkonzept wurde Ende der 70er Jahre mit dem Ziel entwickelt, durch vereinfachte Architekturen Rechner schneller und preisgünstiger zu
machen. Die Speichertechnologie war billiger geworden, erste Mikroprozessoren
konnten bereits seit Beginn der 1970er Jahre auf Silizium-Chips implementiert
21
Es gab noch keine Cache-Speicher!
28
Kapitel 1. Befehlssatzarchitekturen
werden. Einfache Maschinenbefehle ermöglichten es, bei der Befehlsausführung das Pipelining-Prinzip anzuwenden: Möglichst alle Befehle sollen dabei so
implementierbar sein, dass pro Prozessortakt die Ausführung eines Maschinenbefehls in der Pipeline beendet wird. Durch die Implementierung mittels einer
Befehlspipeline kann (für die meisten Befehle) auf die Mikroprogrammierung
verzichtet werden. Stattdessen werden die Befehle (Opcodes) durch ein schnelles
Schaltnetz in einem einzigen Taktzyklus decodiert. Nur sehr komplexe Befehle, wie z. B. bei den PC-Prozessoren von Intel oder AMD, werden weiterhin
durch ein Mikroprogramm-Steuerwerk dekodiert und intern in eine Reihe von
RISC-Befehlen überführt.
Dies war natürlich nur durch eine konsequente Verschlankung der Prozessorarchitektur möglich. Folgende Eigenschaften charakterisieren frühe RISCArchitekturen:
• Der Befehlssatz besteht aus wenigen, unbedingt notwendigen Befehlen
(Anzahl ≤ 128) und Befehlsformaten (Anzahl ≤ 4) mit einer einheitlichen
Befehlslänge von 32 Bit und mit nur wenigen Adressierungsarten (Anzahl
≤ 4). Damit wird die Implementierung des Steuerwerks erheblich vereinfacht und auf dem Prozessor-Chip Platz für weitere Funktionseinheiten
geschaffen.
• Eine große Registerzahl von mindestens 32 allgemein verwendbaren Registern ist vorhanden.
• Der Zugriff auf den Speicher erfolgt nur über Lade-/Speicherbefehle.
Alle anderen Befehle, d. h. insbesondere auch die arithmetischen Befehle,
beziehen ihre Operanden aus den Registern und speichern ihre Resultate in
Registern. Dieses Prinzip der Register-Register-Architektur ist für RISCRechner kennzeichnend und hat sich heute bei allen neu entwickelten
Prozessorarchitekturen durchgesetzt.
• Weiterhin wurde bei den frühen RISC-Rechnern die Überwachung der
Befehls-Pipeline von der Hardware in die Software verlegt, d. h., Abhängigkeiten zwischen den Befehlen und bei der Benutzung der Ressourcen
des Prozessors mussten bei der Codeerzeugung bedacht werden. Die Implementierungstechnik des Befehls-Pipelining wurde damals zur Architektur
hin offen gelegt. Eine klare Trennung von (Befehlssatz-)Architektur und
Mikroarchitektur war für diese Rechner nicht möglich. Das gilt für heutige
Mikroprozessoren – abgesehen von wenigen Ausnahmen – nicht mehr, jedoch ist die Beachtung der Mikroarchitektur für Compiler-Optimierungen
auch weiterhin notwendig.
Die RISC-Charakteristika galten zunächst nur für den Entwurf von Prozessorarchitekturen, die keine Gleitkomma- und Multimediabefehle umfassten,
da die Chip-Fläche einfach noch zu klein war, um solch komplexe Einheiten
aufzunehmen. Inzwischen können auch Gleitkomma- und Multimediaeinheiten
auf dem Prozessor-Chip untergebracht werden. Gleitkommabefehle benötigen
1.4. Die MIPS-Architektur
nach heutiger Implementierungstechnik üblicherweise drei Takte in der Ausführungsstufe einer Befehls-Pipeline. Da die Gleitkommaeinheiten intern als
Pipelines aufgebaut sind, können sie jedoch ebenfalls pro Takt ein Resultat
liefern.
RISC-Prozessoren, die das Entwurfsziel von durchschnittlich einer Befehls- Skalar und
ausführung pro Takt erreichen, werden als skalare RISC-Prozessoren be- superskalar
zeichnet. Doch gibt es heute keinen Grund, bei der Forderung nach einer
Befehlsausführung pro Takt stehen zu bleiben. Die Superskalartechnik ermöglicht es heute, pro Takt mehreren Ausführungseinheiten gleichzeitig bis zu
sechs Befehle zuzuordnen und eine gleiche Anzahl von Befehlsausführungen
pro Takt zu beenden. Solche Prozessoren werden als superskalare (RISC)Prozessoren bezeichnet, da die oben definierten RISC-Charakteristika auch
heute noch weitgehend beibehalten werden. Solche hochperformante Prozessoren
werden in Kapitel 4 ausführlich vorgestellt.
Im Folgenden werden wir exemplarisch die RISC-Prozessor-Architektur des
recht verbreiteten MIPS-Prozessors (Microprocessor without Interlocked Pipeline
Stages) behandeln. Wir werden den MIPS-Befehlssatz vorstellen und AssemblerProgramme für diesen analysieren. Außerdem werden wir einen MIPS-Simulator
(MARS, MIPS Assembler and Runtime Simulator ) vorstellen, mit dem Sie sich
intensiv mit dem MIPS auseinandersetzen, eigene Programme schreiben und
testen können.
1.4
Die MIPS-Architektur
In diesem und dem folgenden eher praktisch ausgerichteten Abschnitt wird die
Architektur des MIPS-Prozessors vorgestellt und Sie lernen, wie man Assemblerprogramme für den MIPS schreibt und diese in einem Simulator ausführen
kann. Der MIPS wurde ab 1981 an der Stanford-Universität entwickelt und
wird heute von der MIPS Technologies Inc. an verschiedene Hardwarehersteller
lizenziert. Ziel dabei war es, durch eine geeignete Definition der Befehlssatzarchitektur eine effiziente Pipeline-Architektur zu ermöglichen. Verbreitung
findet der MIPS-Prozessor heute besonders in Eingebetteten Systemen wie
z. B. in Druckern, Netzwerkgeräten oder Mobiltelefonen. Auch bei modernen
Tablet-Computern werden mehrkernige MIPS-Prozessoren verwendet. Aufgrund
der Pipeline-Mikroarchitektur des MIPS erreicht man einen hohen Befehlsdurchsatz bei geringer Leistungsaufnahme. Der MIPS gilt durch Eigenschaften wie
eine einheitliche Befehlslänge, die Realisierung einer Load-Store-Architektur
und die Beschränkung auf nur wenige Adressierungsarten als ein klassischer
Vertreter der RISC-Architektur. Ursprünglich handelte es sich um eine reine
32-Bit-Architektur, welche im Laufe der Entwicklung aber auf 64-Bit erweitert
wurde22 . Es können zudem mehrere Koprozessoren angesprochen werden, welche
unter anderem für Gleitkommaoperationen zuständig sind. Die Ausführungen in
diesem Abschnitt beziehen sich auf die Version R2000 des MIPS-Prozessors. Ein
22
Im Kurs beschränken wir uns auf eine reine 32-Bit Version.
29
30
OperandenSpeicherorte
Kapitel 1. Befehlssatzarchitekturen
bekannter und plattformübergreifender MIPS-Simulator für diesen Befehlssatz
ist der kostenfrei verfügbare MARS (MIPS Assembler and Runtime Simulator ).
Dieser erlaubt neben dem Entwickeln und Ausführen von Assemblerprogrammen
auch einen umfangreichen Einblick in die Abläufe innerhalb des Prozessors (z. B.
Speicherauszug und aktuelle Registerbelegungen). Mit Plugins kann die Funktionalität außerdem noch zusätzlich erweitert werden. Eine Kurzbeschreibung
des MARS folgt am Ende dieses Abschnittes.
Operanden sind im MIPS-Befehlssatz in einem bestimmten Datenformat
codiert. Ihre Daten können im Wesentlichen an einem von drei verschiedenen
Speicherorten abgelegt sein:
• Register
Die Daten liegen bereits in einem der direkt adressierbaren Register
innerhalb des Prozessors. Die Adressierung erfolgt dann z. B. über die
Registernummer, wobei der Zugriff sehr schnell erfolgen kann. Operanden
für Befehle, die logische oder arithmetische Operationen ausführen, müssen
daher bei der MIPS-Architektur stets in Registern liegen.
• Speicher
Die Daten liegen an einer Adresse im Speicher. Die Adressierung kann
z. B. über das in einem Register gespeicherte Datum erfolgen, das als
Adresse interpretiert wird. Operanden können mit den Transferbefehlen
vom Speicher gelesen bzw. in den Speicher geschrieben werden.
• Direkt im Befehl
Falls die Summe des benötigten Speicherplatzes für Befehl plus Daten die
Maschinenwortbreite nicht überschreitet, können die Daten auch direkt im
Befehl enthalten sein. Hierfür werden entsprechende Adressierungsarten
bereit gestellt.
MIPS-Register
Allgemein gibt es einen reziproken Zusammenhang zwischen der Speichergröße und der Zugriffsgeschwindigkeit. Der verfügbare Speicherplatz für Operanden innerhalb des Befehls oder in den Registern ist extrem begrenzt, jedoch ist
der Zugriff besonders schnell. Im Gegensatz dazu ist der Sekundärspeicher geeignet, um sehr große Datenmengen aufzunehmen, seine Zugriffsgeschwindigkeit
ist jedoch vergleichsweise gering.
Die MIPS-Architektur enthält 32 jeweils 32 Bit breite Universalregister
(General Purpose Registers), welche mit dem Präfix $ angesprochen werden
und abgesehen von dem Register $0 beliebig beschrieben werden können. Das
Register $0 ist fest mit dem Wert Null verdrahtet, sodass ein Schreibzugriff
keinen Einfluss hat und beim Lesen stets den Wert Null zurück gibt. Es gibt
Konventionen in Bezug auf Namen und Verwendung der Register, welche bei
der Programmierung beachtet werden müssen, siehe Tabelle 1.3. Temporäre
Variablen dürfen von Unterprogrammaufrufen beliebig überschrieben werden.
Langlebige Variablen müssen für das aufrufende Programm wieder verfügbar
gemacht und daher von einem Unterprogramm gesichert werden. Das Register
$31 wird implizit für die Verwaltung der Rücksprungadresse verwendet.
1.4. Die MIPS-Architektur
Tabelle 1.3:
Nummer
$0
$1
$2 - $3
$4 - $7
$8 - $15
$16 - $23
$24 - $25
$26 - $27
$28
$29
$30
$31
31
Namenskonventionen der MIPS-Universalregister
Name
Verwendung
$zero
0 (read-only)
$at
reserviert für Assembler
$v0 - $v1 Rückgabewert von Funktionsaufruf
$a0 - $a3 Argumente für Funktionsaufruf
$t0 - $t7 Langlebige Variablen
$s0 - $s7 Gesicherte Variablen
$t8 - $t9 Temporäre Variablen
$k0 - $k1 Reserviert für Betriebssystem
$gp
Data Segment Pointer
$sp
Stack Pointer
$fp
Frame Pointer
$ra
Return Address
Für den Koprozessor stehen weitere 32 Gleitkommaregister $fi (Floating
Point Registers) zur Verfügung, welche als 32 Register einfacher (32 Bit) bzw. 16
Register doppelter Genauigkeit (64 Bit) nach IEEE 754-Format genutzt werden
können. Für die doppelte Genauigkeit werden jeweils zwei 32-Bit-Register $f(2i),
$f(2i+1) zu einem 64-Bit-Register verbunden. Die Register sind hierfür in gerade
und ungerade Register unterteilt.
Zusätzlich gibt es nicht aufgeführte Spezialregister wie den Befehlszähler
(Program Counter, PC ), die Register Hi und Lo für das Ergebnis von Multiplikation bzw. Division sowie das Statusregister für den Zugriff auf Statusinformationen
des Prozessors.
Der Speicher ist byteadressierbar im Big-Endian-Format mit 32-Bit-Adressen, Speicherverwaltung
weshalb der Befehlszähler nach jedem Befehl um 4 erhöht wird. Alle Speicherzugriffe müssen ausgerichtet sein und erfolgen mittels der Lade- und Speicherbefehle
zwischen dem Speicher und den Universalregistern bzw. Gleitkommaregistern.
Der Zugriff auf die Universalregister kann byte- (8 Bit), halbwort- (16 Bit),
wort- (32 Bit) oder doppelwortweise (64 Bit) erfolgen, wobei beim Doppelwort
wieder zwei Universalregister zusammengeschlossen werden. Auf die Gleitkommaregister kann mit einfacher (32 Bit) oder doppelter Genauigkeit (64 Bit)
zugegriffen werden.
Alle Befehle sind 32 Bit lang und müssen im Speicher ausgerichtet sein. Das Befehlsformate
Opcode-Feld (op) ist 6 Bit breit und gibt die Art des Befehls an. Die Quellund Zielregisterangaben (rs, rt, rd) sind jeweils 5 Bit breit und werden für
die Adressierung der 32 Register eines Registersatzes benötigt. Für die Angabe von unmittelbaren Operanden (immediate) sind 16-Bit-Felder vorgesehen
und für befehlszählerrelative Verzweigungsadressen (target address) 26 Bit.
Verschiebewerte (sa) können durch 5-Bit-Werte angegeben werden.
32
Kapitel 1. Befehlssatzarchitekturen
Ein Befehl ist in einem der folgenden drei Befehlsformate codiert:
• R-Typ: für Register-Register-ALU-Operationen (wobei das 6-Bit-funcFeld die Operation codiert) und Transportbefehle.
31
26 25
op
21 20
16 15
rt
rs
11 10
65
sa
rd
0
func
• I-Typ: zum Laden und Speichern von Bytes, Halbwörtern und Wörtern,
für alle Operationen mit unmittelbaren Operanden, bedingte Verzweigungsbefehle sowie unbedingte Sprungbefehle mit Zieladresse in einem
Universalregister.
31
26 25
op
21 20
rs
16 15
rt
0
immediate
• J-Typ: für Jump- (J) und Jump-and-Link-Befehle (JAL).
31
26 25
op
MIPS-Befehle
0
target address
Es folgt eine Auflistung häufig verwendeter MIPS-Befehle . Da eine vollständige Auflistung aller Befehle und detaillierter Informationen zu diesen
den Rahmen des Kurses überschreiten würde, verweisen wir für weitere Informationen auf die Dokumentationen zum MIPS bzw. MARS. Allgemein gibt
es vier Klassen von Befehlen: Transportbefehle, arithmetisch-logische Befehle,
Verzweigungs- und Gleitkommabefehle. Viele der Befehle sind direkt in Hardware realisiert. Falls dies nicht zutrifft, werden die Befehle mit einem (?) markiert.
Viele Befehle kommen je nach verwendeten Operanden in abgewandelter Form
vor. Ein angehängtes „i“ an einen Befehl weist meistens auf die Verwendung
eines unmittelbaren Operanden hin, ein angehängtes „u“ für die Verwendung
eines vorzeichenlosen Wertes. Bei Gleitkommabefehlen stehen die Bezeichner „s“
für einfache Genauigkeit und „d“ für doppelte Genauigkeit.
Die Transportbefehle (siehe Tabelle 1.4) sind Registertransfer-, Ladeoder Speicherbefehle, welche Wörter, Halbwörter usw. aus dem Speicher laden
bzw. in diesen schreiben können. Da es sich beim MIPS um eine Load-StoreArchitektur handelt, sind die Transportbefehle die einzigen Befehle, bei denen
ein Speicherzugriff erfolgt. Die vorkommende Adressierungsart ist die Adressierung „registerindirekt mit Verschiebung“ in der Form „Universalregister +
16-Bit-Verschiebewert mit Vorzeichen“. Daraus können dann die registerindirekte Adressierung (Verschiebewert = 0) und die absolute Adressierung (mit
Basisregister $zero) abgeleitet werden.
Die arithmetisch-logischen Befehle (siehe Tabelle 1.5) untergliedern sich
in arithmetische, logische, Verschiebe- und Vergleichsbefehle. Wie bereits erwähnt, können einige Befehle mit unmittelbare Operanden (i – immediate)
1.4. Die MIPS-Architektur
Tabelle 1.4: Transportbefehle
Bedeutung
Laden eines Bytes
Speichern eines Bytes
Laden eines Halbwortes
Speichern eines Halbwortes
Laden bzw. Speichern eines Wortes
Laden bzw. Speichern eines Doppelwortes in die Register $i, $i+1
Laden einer 16-Bit-Adresse
Laden eines 16-Bit-Wertes (mit/ohne Verschiebung)
Laden eines Gleitkommaregisters
Speichern eines Gleitkommaregisters
Transfer zwischen Universalregistern
Transfer zwischen Gleitkommaregistern
Opcode
lb, lbu
sb
lh, lhu
sh
lw, sw
ld?, sd?
la?
lui, li?
l.s?, l.d?
s.s?, s.d?
move?
mov.s?,
mov.d?
mfhi, mflo, Transfer zwischen Universalregister und den Registern Hi und Lo
mthi, mtlo
mfc1, mtc1 Transfer zwischen Universalregistern und Gleitkommaregistern
und vorzeichenlosen Zahlen (u – unsigned) ausgeführt werden. Zu beachten ist
außerdem, dass logische Operationen bitweise arbeiten und damit eine Sonderstellung einnehmen. Bei der Multiplikation und Division wird das Ergebnis in
den Registern Hi und Lo abgespeichert (zwei 32-Bit-Operanden ergeben einen
64-Bit-Wert als Ergebnis).
Bei Verzweigungsbefehlen kann unterschieden werden zwischen bedingten und unbedingten Sprüngen, siehe Tabelle 1.6. Bei bedingten Sprüngen wird
der Sprung abhängig von der Auswertung der Sprungbedingung ausgeführt bzw.
nicht ausgeführt. In der Sprungbedingung werden entweder zwei Quellregister,
ein Quellregister mit einem unmittelbaren Operanden oder ein Quellregister
mit dem Wert Null verglichen. Es können folgende Vergleiche durchgeführt
werden: gleich (eq – equal), ungleich (ne – not equal), größer (gt – greater than),
größer gleich (ge – greater than equal), kleiner (lt – less than) und kleiner gleich
(le – less than equal). Die Zieladresse eines bedingten Sprungbefehls ist durch
einen 16-Bit-Verschiebewert angegeben, welcher zum Inhalt des inkrementierten
Befehlszählers (PC+4) addiert wird. Bei unbedingten Sprüngen kann unterschieden werden zwischen absoluter (Branch), befehlszählerrelativer (Jump) und
befehlszählerindirekter (Jump Register) Adressierung. Jump-And-Link-Befehle
(JAL) speichern zusätzlich die Adresse des Befehls, der dem Sprungbefehl folgt
(Rücksprungadresse bzw. PC+4), im Register $ra. Ähnlich wie beim Aufruf
eines Unterprogrammes kann damit ein Rücksprung zur „Unterbrechungsstelle“
erfolgen.
33
34
Kapitel 1. Befehlssatzarchitekturen
Tabelle 1.5: Arithmetisch-logische Befehle
Opcode
Bedeutung
add, addi, addu, Addition
addiu
sub, subu
Subtraktion
mult, multu
Multiplikation (Speicherung in Registern Hi und Lo)
div, divu
Division (Speicherung in Registern Hi und Lo)
abs?, neg?, rem? Absoluter Wert, Negation, Modulo
not?
Logisches NOT
and, andi
Logische UND-Verknüpfung
or, ori
Logische ODER-Verknüpfung
xor, xori
Logische XOR-Verknüpfung
nor
Logische NOR-Verknüpfung
sll, sllv
Logische Linksverschiebung (’v’ - mit variabler Anzahl)
srl, srlv
Logische Rechtsverschiebung (’v’ - mit variabler Anzahl)
sra, srav
Arithmetische Rechtsverschiebung (’v’ - mit variabler Anzahl)
rol?, ror?
Rotation links bzw. rechts
slt, slti, sltu, sl- Vergleich „kleiner“
tiu
seq?, sne?
Vergleich „gleich“ bzw. „ungleich“
sgt?, sgtu?
Vergleich „größer“
sge?, sgeu?
Vergleich „größer gleich“
sle?, sleu?
Vergleich „kleiner gleich“
1.4. Die MIPS-Architektur
35
Bei der Berechnung der Zieladresse für die Befehle J (Jump) und JAL (Jump
and Link) sind grundsätzlich zwei Vorgehensweisen denkbar: Fall 1 – der im
Befehlswort codierte unmittelbare Operand wird als Zahl im Zweierkomplement
zum Befehlszähler addiert. Fall 2 – der Operand ersetzt einen Teil (Bits 0 –
27) des aktuellen Befehlszählers. Beide Fälle werden nun kurz erläutert. In
Abbildung 1.20 ist der codierte Sprungbefehl gezeigt, in Abbildung 1.21 der
mögliche Sprungbereich im Speicher.
Opcode
Unmittelbarer Operand (immediate) I
0 0 0 0 1 0 i i i i i i i i i i i i i i i i i i i i i i i i i i
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Abbildung 1.20: Codierung des Jump-Befehls
232 − 1
232 − 1
0xFFFFFFFF
+227 − 1
0xE0000000
PCalt
PCalt
−227
0xD0000000
0
0
Fall 1
Fall 2
Abbildung 1.21: Sprung mit unmittelbarem Operanden
Im Fall 1 wird der unmittelbare Operand I aus den 26 unteren Bits gewonnen
und als Zahl im Zweierkomplement interpretiert. Da im Programmsegment an
Wortgrenzen gesprungen wird (alle Instruktionen beginnen an einer Wortgrenze),
ist dieser Wert noch um zwei Stellen nach links zu verschieben und anschließend
auf den aktuellen Befehlszähler zu addieren:
P Cneu = P Calt + (I 2)
(1.1)
27
Damit kann von der alten Befehlszählerposition maximal 2
bzw. 227 − 1 Bytes nach vorne gesprungen werden.
Byte zurück
36
Kapitel 1. Befehlssatzarchitekturen
Im Fall 2 ersetzt der um zwei Bitpositionen nach links verschobene unmittelbare Operand die unteren 28 Bit des aktuellen Befehlszählers:
P Cneu = P Calt & 0xF 0000000 | (I 2)
(1.2)
Somit wird der Speicher in 16 Bereiche der Größe 228 Byte eingeteilt, wobei
die oberen 4 Bit des aktuellen Befehlszählers den Bereich festlegen. Ein Sprung
aus den Bereichen hinaus ist in dieser Art nicht möglich. Soll der Bereich
verlassen werden, so muss auf einen Register-Sprung zurückgegriffen werden. Bei
der hier betrachteten MIPS-Architektur wird diese zweite Variante verwendet.
Opcode
j, b?, jr
jal?
jalr
beq, bne
beqz?, bnez?
bgt?, bge?
bgtz, bgez
blt?, ble?
bltz, blez
nop
Tabelle 1.6: Verzweigungsbefehle
Bedeutung
unbedingter Sprung mit befehlszählerrelativer, absoluter und
befehlszählerindirekter Adressierung
„Jump“ mit Speicherung von PC+4 im Register $ra
„Jump Register“ mit Speicherung von PC+4 im Register $ra
Verzweigung, falls Register-Register oder Register-Operand
„gleich“ oder „ungleich“ sind.
Verzweigung, falls Register „gleich“ oder „ungleich“ Null ist.
Verzweigung, falls Register-Register oder Register-Operand „größer“ oder „größer gleich“ sind.
Verzweigung, falls Register „größer“ oder „größer gleich“ Null ist.
Verzweigung, falls Register-Register oder Register-Operand „kleiner“ oder „kleiner gleich“ sind.
Verzweigung, falls Register „kleiner“ oder „kleiner gleich“ Null ist.
Nulloperation bzw. Leere Anweisung
Gleitkommabefehle operieren auf Gleitkommaregistern und führen die
Aktionen mit Gleitkommazahlen im IEEE-754-Format aus, siehe Tabelle 1.7.
Wie bereits erwähnt kann dabei zwischen einfacher (s) und doppelter Genauigkeit
(d) unterschieden werden. Bei Befehlen mit doppelter Genauigkeit können nur
die geraden Gleitkommaregister verwendet werden, da jeweils zwei Register
für eine Zahl zusammengeschlossen werden. Mit Konvertierungsbefehlen kann
zwischen den verschiedenen Zahlendarstellungen gewechselt werden.
MARS-Simulator
In diesem Unterabschnitt folgt eine kurze Einführung in den MARS-Simulator.
Für detailliertere Informationen verweisen wir auf die offizielle Internet-Seite
von MARS 23 .
23
http://courses.missouristate.edu/kenvollmar/mars/Help/MarsHelpIntro.html
1.4. Die MIPS-Architektur
Opcode
add._, sub._,
mul._, div._,
abs._
c._.s, c._.d
cvt._._
37
Tabelle 1.7: Gleitkommabefehle
Bedeutung
Addition, Subtraktion, Multiplikation, Division, Absolutwert (_
kann mit ’s’ bzw. ’d’ ersetzt werden)
Vergleich zwischen einfachen bzw. doppelt-genauen Gleitkommazahlen. (_ kann mit „eq“ (equal), „lt“ (less than), „le“ (less than
equal), ersetzt werden)
Konvertierungsbefehl (_ können mit ’s’ (einfach), ’d’ (doppelt),
’w’ (Ganzzahl), ersetzt werden)
Der MARS wurde an der Missouri State University entwickelt und stellt eine
interaktive Entwicklungsumgebung zur Assemblerprogrammierung des MIPS
bereit. MARS enthält einen Editor, Assembler und eine Laufzeitumgebung, um
die Binärprogramme für den MIPS auf einem Hostrechner ablaufen zu lassen,
auch wenn dieser einen anderen Prozessor enthält.
Im Wesentlichen bietet MARS folgende Funktionen an:
• Anzeigen von Register- und Speicherinhalten,
• Editieren eines Programms in MIPS-Assemblersprache,
• Übersetzen dieses Programms in Binärcode,
• Ausführen des Codes im Einzelschrittmodus oder mit anpassbarer Simulationsgeschwindigkeit,
• Setzen von Break-Points zum Debuggen,
• Ein- und Ausgabe von Zahlen bzw. Zeichenketten über einen Systemaufruf.
MARS unterstützt die Zuordnung von Speicherbereichen zum Assemblerpro- Speicherbereiche
gramm. Zur Steuerung eines Assemblers verwendet man so genannte Direktiven.
Die wichtigsten Direktiven sind .data und .text. Mit .data wird der statischen Datenteil eingeleitet, welcher dem Programm zur Laufzeit zur Verfügung
steht. Es folgen Definitionen für Speicherplätze mit unterschiedlichen Datenformaten und Bezeichner (labels), um aus dem Assemblerprogramm auf diese
Speicherplätze zugreifen zu können.
Um Programmzeilen auszukommentieren , gibt es in Hochsprachen übli- Kommentare
cherweise zwei Möglichkeiten: Zeilen- und Mehrzeilenkommentare. Der MARSAssembler unterstützt Zeilenkommentare, bei denen eine komplette Zeile bzw.
das Ende einer Zeile bis zum Zeilenumbruch auskommentiert werden können. Sie
werden mit dem Symbol # eingeleitet und können auch hinter einer AssemblerAnweisung stehen.
Im Folgenden sehen wir ein Beispiel für die Definition eines Datenbereichs
(aus Gründen der Übersichtlichkeit werden Zeilennummern ausgelassen):
38
Labels
Kapitel 1. Befehlssatzarchitekturen
.data
# Schlüsselwort für den statischen Datenteil
l1:
l2:
l3:
l4:
l5:
#
#
#
#
#
.byte ’c’
.asciiz "Text"
.byte 10
.word 1000
.space 15
Anlegen
Anlegen
Anlegen
Anlegen
Anlegen
des Bytes mit dem ASCII-Wert für ’c’
eines 0-terminierten Strings
des Bytes mit dem Wert 10
des 32-Bit-Wortes mit dem Wert 1000
von 15 Bytes mit dem Wert 0
Sprungziele (Labels) stehen am Zeilenanfang, Enden mit einem Doppelpunkt
und müssen im Programm eindeutig festgelegt werden. Es folgt die Definition
des Datentyps und die Vorbelegung des Speicherplatzes. Im Anschluss werden
Kommentare mit # eingeleitet, worauf wir weiter unten noch genauer eingehen.
Mit .text wird der Beginn des eigentlichen Assembler-Programmcodes gekennzeichnet. Der Programmbeginn wird mit dem Label main: markiert, welches
nicht für weitere Labels verwendet werden darf. Der Kopf eines Programmes
sieht üblicherweise wie folgt aus:
.text
# Schlüsselwort für den Begin des Programms
main:
# Einstiegspunkt
lw $t0, 0($t1) # hier beginnen die Assemblerbefehle
Neben dem Daten- und Programmteil gibt es noch weitere Direktive zur
Festlegung des Bereichs für den Stapelspeicher, für Ein- und Ausgaben und für
das Betriebssystem. Für die genaue Verwendung dieser Direktiven verweisen
wir auf die offizielle Dokumentation des MARS.
Der Two-Pass-Assembler bearbeitet das eingegebene Programm in zwei
Durchläufen. Im ersten Durchlauf werden die einzelnen Befehle in MIPSBinärcode übersetzt und den vergebenen Labeln die entsprechenden Speicheradressen zugewiesen. Vorwärtsreferenzen verbleiben beim ersten Durchlauf noch
offen, da die genauen Speicheradressen des Ziels noch nicht bekannt ist. Beim
zweiten Durchlauf werden diese Referenzen dann ebenfalls durch die zugehörige
Speicheradresse ersetzt, sodass danach das fertige binäre Maschinenprogramm
zur Ausführung im Simulator bereit steht.
Zur Erleichterung der Assemblerprogrammierung bietet der MARS-Assembler
auch sogenannte Pseudobefehle an, die intern durch entsprechende Befehle oder
Befehlsfolgen ersetzt werden. Als Beispiel sei hier der Pseudobefehl li $1,8
(load immediate) genannt, der durch den Befehl ori $1, $0, 8 (or immediate)
realisiert wird.
In Tabelle 1.8 werden die wichtigsten MARS-Systemaufrufe aufgeführt. Zur
Benutzung eines Systemaufrufs wird zunächst die entsprechende Codenummer
in das Register $v0 geladen. Dies kann z. B. mit dem bereits erwähnten Pseudobefehl li $v0, 1 erfolgen. Wenn der Systemaufruf dann mit syscall ausgelöst
wird, gibt MARS den Wert des Registers $a0 auf der Konsole aus.
1.5. Codeanalyse
39
Tabelle 1.8: Wichtige MARS-Systemaufrufe
9
Beschreibung
32-Bit-Wert dezimal auf Konsole ausgeben
mit dem Wert 0 terminierten String
auf Konsole ausgeben
32-Bit-Wert dezimal von Konsole einlesen
String von Konsole einlesen, mit 0
terminieren und in Puffer abspeichern
dynamischen Speicher bereitstellen
10
Programm beenden
—
$v0
1
4
5
8
Aufrufwerte
$a0=Wert der Zahl
Rückgabew.
—
$a0= Adresse des
Strings
—
—
$a0 enthält die
Adresse des Puffers,
$a1= Länge String
$a0=Blockgröße,
—
$v0= Wert
$v0= Adresse des
Blocks
—
Selbsttestaufgabe 1.3
Arbeiten Sie sich in den MARS ein und machen Sie sich mit der Programmoberfläche, den MIPS-Befehlen und den Registerbezeichnern vertraut. Schreiben
Sie erste kleinere Programme, die z. B. das Quadrat einer Zahl berechnen.
Selbsttestaufgabe 1.4
Die Fakultät einer Zahl ist definiert als:
a! := a · (a − 1) · (a − 2) · · · 2 · 1
Schreiben Sie ein Programm, welches einen Wert n (implizit gilt: n ≥ 0) aus
dem Speicher lädt und dessen Fakultät n! berechnet. Das Programm sollte
mit einer rekursiven Funktion fac arbeiten und Variablen bei jedem neuen
Funktionsaufruf auf den Stack sichern.
1.5
Codeanalyse
Es folgt die beispielhafte Analyse eines MIPS-Assemblerprogramms. Ziel dabei
ist es zu prüfen, was das Programm funktional realisiert. Da nicht davon
ausgegangen werden kann, dass der Programmierer seinen Code ausführlich
kommentiert, werden wir auf Kommentare vorerst verzichten. Als Hilfestellung
wurde das Programm allerdings bereits in logisch zusammenhängende Blöcke
unterteilt und mit nummerierten bzw. aussagekräftigen Labels versehen:
Das Assembler-Programm sieht folgendermaßen aus:
#################################################################
.text
lw $t1, src
lw $t2, mask1
lw $t3, mask2
40
Kapitel 1. Befehlssatzarchitekturen
lw $t4, mask3
and $t5, $t1, $t2
beqz $t5, m1
xor $t1, $t1, $t3
addi $t1, $t1, 0x1
m1: srl $t2, $t2, 1
and $t6, $zero, $zero
m2: and $t7, $t1, $t2
bnez $t7, m3
addi $t6, $t6, 1
sll $t1, $t1, 1
j m2
m3: addi $t7, $zero, 30
sub $t6, $t7, $t6
addi $t6, $t6, 127
sll $t6, $t6, 0x17
or $t5, $t5, $t6
srl $t1, $t1, 0x7
and $t1, $t1, $t4
or $t5, $t5, $t1
end:j end
.data
src:
mask1:
mask2:
mask3:
.word
.word
.word
.word
0xFFFFFF85
0x80000000
0xFFFFFFFF
0x7FFFFF
#################################################################
Allgemein sollten Umrechnungen der verschiedenen Zahlensysteme (Dez,
Bin, Hex) möglichst schnell und ohne großartiges Nachdenken erfolgen. Bei der
Analyse des Programms ist ein strukturelles Vorgehen sinnvoll und zu empfehlen.
Zuerst sollte sich ein Überblick über die Struktur des Programms durch dessen
Einteilung in die logischen Blöcken verschafft werden, wodurch bereits die ersten
Erkenntnisse sichtbar werden. So wird beim obigen Programm deutlich, dass
der .text-Bereich mit dem Programmcode am Kopf des Programmes steht
und der .data-Bereich im Anschluss daran folgt. Im .data-Bereich wird ein
Label src: verwendet, was darauf hindeutet, dass dieser Speicherbereich für
den Eingabewert (source) verwendet wird. Eine Bezeichnung dst könnte ebenso
auf eine Speicherstelle des Ergebnisses (destination) hindeuten. Des weiteren
werden drei Labels mit mask_x bezeichnet, woraus sich auf eine Verwendung
im Programm als Maske schließen lässt24 .
24
Machen Sie sich hier die bitweise Darstellung der Register deutlich!
1.5. Codeanalyse
41
Im .text-Bereich gibt es insgesamt sieben Blöcke, wobei auch hier Vorüberlegungen getroffen werden können. Der erste Block besteht aus vier lwAnweisungen (Load Word) und stellt somit einen initialisierenden Block mit
Ladeanweisungen dar. Der vierte Block mit dem unbedingten Rücksprung zur
Marke m2 zum Blockanfang ist leicht als Schleife zu identifizieren. Die Prüfung
der Abbruchbedingung und der bedingte Schleifenaustritt erfolgt durch die
Befehle and und bnez innerhalb des Blockes. Der letzte Block mit Marke end:
enthält eine unbedingte Sprunganweisung zu sich selbst. Dieser als Endlosschleife realisierte Befehl stellt das Programmende dar, da ab hier funktional keine
weitere Programmabarbeitung mehr erfolgt. Üblicherweise würde man hier einen
Rücksprung zum aufrufenden Prozess mit Übergabe eines Rückgabeparameters
erwarten. Da es beim MARS-Simulator aber kein übergeordnetes Betriebssystem
gibt sondern lediglich das MIPS-Programm als solches, dient die Endlosschleife
als klar definiertes Programmende.
Für die genaue Analyse des MIPS-Programms empfiehlt sich eine tabellarische Auflistung der verwendeten Register und das Notieren dessen aktueller
gespeicherter Inhalte. Nach Durchlaufen des initialen Blockes sieht die Registerbelegung folgendermaßen aus25 :
$t1: 0xFFFFFF85
$t3: 0xFFFFFFFF
$t2: 0x80000000
$t4: 0x007FFFFF
Im zweiten Block werden die Register $t1 und $t2 bitweise UND-verknüpft
und das Ergebnis 0x80000000 in $t5 gespeichert. Abhängig davon wird nun
zur Marke m1: gesprungen oder im nächsten Block fortgefahren. Der bedingte
Sprungbefehl lautet beqz, also Sprung beim Registerinhalt von Null. Funktional wird in diesem Block das obere Bit ausgeschnitten und in Register $t5
gespeichert. Da $t5 hier ungleich 0 ist, wird im dritten Block fortgefahren. Die
aktuelle Registerbelegung lautet:
$t1: 0xFFFFFF85
$t2: 0x80000000
$t3: 0xFFFFFFFF
$t4: 0x007FFFFF
$t5: 0x80000000
Register $t1 wird mit Register $t3 (enthält mask2) bitweise XOR-verknüpft.
Da der Inhalt von $t3 stets 1en enthält, erfolgt durch die XOR-Verknüpfung
eine Negation des Registers $t1. Im Anschluss erfolgt eine Addition mit 1.
Funktional sollte hierbei ersichtlich werden, dass durch Negation und Addition
von 1 das Zweierkomplement des Registers $t1 berechnet wurde. Die aktuelle
Registerbelegung lautet:
$t2: 0x80000000
$t4: 0x007FFFFF
$t3: 0xFFFFFFFF
$t5: 0x80000000
$t1 (binär): 00000000 00000000 00000000 01111011
25
Wir beschränken uns hier auf die im Programm verwendeten Register
42
Kapitel 1. Befehlssatzarchitekturen
Im vierten Block erfolgt das logische Rechtsschieben von Register $t2 (enthält mask1) und das Laden von Register $t6 mit 0. Die aktuelle Registerbelegung
lautet:
$t2: 0x40000000
$t3: 0xFFFFFFFF
$t4: 0x007FFFFF
$t5: 0x80000000
$t6: 0x00000000
$t1 (binär): 00000000 00000000 00000000 01111011
Der fünfte Block beinhaltet, wie bereits festgestellt, eine Schleife mit bedingtem Aussprung zur Marke m3. Der Schleifenaussprung hängt davon ab, ob
die UND-Verknüpfung der Register $t1 und $t2 ungleich 0 ist. Solange dies
nicht der Fall ist, durchläuft die Schleife die beiden folgenden Befehle, addiert
also 1 zum Register $t6 und schiebt das Register $t1 um eine Stelle nach
links. Funktional werden hier die führenden Nullen von Register $t1 bis zum
Bit 30 gezählt und die Anzahl im Register $t6 gespeichert. Das Register $t1
enthält den verschobenen Wert, sodass das Bit 30 als 1 gesetzt ist. Die aktuelle
Registerbelegung lautet:
$t2: 0x40000000
$t4: 0x007FFFFF
$t6: 0x00000018
$t3: 0xFFFFFFFF
$t5: 0x80000000
$t7: 0x40000000
$t1 (binär): 01111011 00000000 00000000 00000000
Der vorletzte Abschnitt (beginnend mit Marke m3:) wird einmal sequenziell
durchlaufen und das Programm endet im Anschluss in der Endlosschleife. Zu
Beginn des Abschnittes wird das Register $t7 mit 30 geladen und das Register
$t6 von diesem subtrahiert26 . Das Ergebnis wird erneut in Register $t6 gespeichert und anschließend mit 127 addiert. Funktional wurde hier, evtl. nicht auf
den ersten Blick ersichtlich, der verschobene Exponent einer IEEE-754-Zahl
bestimmt. Die aktuelle Registerbelegung lautet:
$t2: 0x40000000
$t4: 0x007FFFFF
$t6: 0x00000085
$t3: 0xFFFFFFFF
$t5: 0x80000000
$t7: 0x0000001E
$t1 (binär): 01111011 00000000 00000000 00000000
Im nächsten Schritt wird das Register $t6 um 0x17, also 23 Stellen nach
links verschoben und durch die OR-Verknüpfung alle 1en in das Register $t5
übernommen. Anschließend wird Register $t1 um 0x7 Stellen logisch nach
rechts verschoben und durch die UND-Verknüpfung mit $t4 die unteren 23
Stellen ausgeschnitten. Register $t1 (untere 23 Stellen) wird anschließend mit
Register $t5 (obere 9 Stellen) OR-verknüpft. Funktional wurden in diesem
26
Beachten Sie hierbei den Unterschied zwischen Dezimal- und Hexadezimalzahlen.
1.6. Zusammenfassung
43
Abschnitt die Mantisse einer IEEE-754-Zahl bestimmt und die drei Bestandteile
(Vorzeichenbit, verschobener Exponent und Mantissenteil zusammengefügt.) in
Register $t5 gespeichert.
Selbsttestaufgabe 1.5
Analysieren Sie folgendes MIPS-Assembler-Programm und geben Sie an, was
dieses funktional realisiert:
###########################################################
Zeile Marke Anweisung
01
02
03
04
05
06
.text
07
08
09
10
11
l1:
12
13
lb
lb
lw
lw
lw
$t4,
$s0,
$s1,
$s2,
$s3,
src
lp
mask1
mask2
mask3
and
$t4,
$t4,
$s1
jal
sll
sub
bnez
m2
$t4,
$s0,
$s0,
$t4,
$s0,
l1
1
1
srl
sw
$t4,
$t4,
$t4,
dst
8
$t4,
$t5,
m3
$t4,
$t4,
$t5,
m4
$t4,
$s2
0x400
14
m1:
j
m1
15
16
17
18
19
20
21
22
m2:
and
sgt
beqz
add
and
sgt
beqz
add
$t5,
$t5,
$t5,
$t4,
$t5,
$t5,
$t5,
$t4,
23
m4:
jr
$ra
m3:
0x300
$s3
0x4000
0x3000
24 .data
25 src:
.word 0x000000E4
26 dst:
.word 0xDEADBEEF
27 lp:
.byte 0x8
28 mask1: .word 0x000000FF
29 mask2: .word 0x00000F00
30 mask3: .word 0x0000F000
###########################################################
1.6
Zusammenfassung
In diesem Kapitel wurde Ihnen zunächst ein Überblick über die im Kurs behandelten Themen gegeben. Neben einer kurzen Wiederholung der geschichtlichen
44
Kapitel 1. Befehlssatzarchitekturen
Entwicklung hin zum heutigen Stand der Computertechnologie gehörte dazu
auch die erste Erwähnung von verschiedenen Konzepten wie RISC, CISC, Pipelining, Superskalarität, virtuelle Speicherverwaltung etc. Auf diese Inhalte
wird in den nächsten Kapiteln noch genauer eingegangen. Das zurückliegende
Kapitel führte anschließend in die Grundlagen der Befehlssatzarchitektur ein.
Sie haben gesehen, welche Funktionen und Befehle ein Prozessor typischerweise
nach außen bietet, wie und wo Daten bzw. Programmcode im Computer gespeichert bzw. adressiert werden, wie typische Datenformate aussehen, welche
Befehlsarten und Befehlsformate es gibt und wie sich verschiedene Architekturen
voneinander unterscheiden können. Außerdem haben Sie gelernt, wie AssemblerProgramme geschrieben und analysiert werden können. Hierfür haben Sie den
Befehlssatz des MIPS kennengelernt und im MARS praktische Erfahrungen in
der Assemblerprogrammierung gesammelt.
1.6. Zusammenfassung
Lösungen der Selbsttestaufgaben
Lösung Selbsttestaufgabe 1.1
1. Umwandlung von Dezimal nach IEEE-754:
• Vorzeichenbit bestimmen. Da 12.78125 positiv ist, ist das Vorzeichenbit 0.
• Als nächstes werden Vor- und Nachkommawerte in das Binärsystem
umgewandelt. Dazu kann man wie folgt vorgehen:
Zunächst wird der Wert vor dem Komma umgewandelt. Dazu teilt
man die Zahl wiederholt durch zwei, bis man bei Null angelangt ist.
Geht die Teilung ohne Rest auf (gerade Zahl), merkt man sich eine
0 und fährt direkt fort. Geht die Teilung nicht auf (ungerade Zahl),
merkt man sich eine 1, zieht von dem Ergebnis 0.5 ab und fährt dann
fort. Für die 12 sieht das beispielsweise so aus:
12/2 = 6
0
6/2 = 3
0
3/2 = 1.5 1
1/2 = 0.5 1
Für die Binärdarstellung von 12 ergibt sich damit 1100(2) (Letzte
Spalte von unten nach oben gelesen).
Als nächstes wird die Nachkommastelle umgewandelt. Hier kann man
nach einem ähnlichen Prinzip vorgehen. Der Nachkomma-Anteil wird
wiederholt mit zwei multipliziert. Ist das Ergebnis kleiner als eins,
merkt man sich eine 0 und fährt fort. Ist die Zahl grös̈er als eins, merk
man sich eine 1. Dann wird 1 abgezogen und fortgefahren. Dies führt
man so lange fort, bis man auf Null kommt, oder eine ausreichende
Zahl an Nachkommastellen hat (nicht immer ist es möglich, eine Zahl
genau darzustellen). Für 0.78125 ergibt sich damit:
0.78125 ∗ 2
1.5625 1
0.5625 ∗ 2 = 1.125
1
0.125 ∗ 2 = 0.25
0
0
0.25 ∗ 2 =
0.5
0.5 ∗ 2 = 1
1
Für die Nachkommastelle ergibt sich also: 0.11001(2) . Insgesamt ergibt
sich damit 12.78125(10) = 1100.11001(2)
• Als nächstes wird die Mantisse normalisiert, so dass sich nur noch
eine 1 vor dem Komma befindet: 1.10011001. Für die Mantisse zählen
die Zahlen nach dem Komma: 10011001. Das Komma um 3 Stellen
nach links verschoben, gilt für den Exponenten: 127 + 3 = 130. Da
130(10) = 10000010(2) gilt für die gesamte Zahl:
0 | 10000010 | 10011001000000000000000 (Hex: 0x414C8000)
45
46
Kapitel 1. Befehlssatzarchitekturen
Wird der Exponent nach rechts verschoben, wird die entsprechende Zahl
von 127 abgezogen. Auf diese Weise verhindert man negative Exponenten.
Für die anderen Zahlen ergibt sich entsprechend:
• 11000100010001000000000000000000 (Hex: 0xC4440000)
• 10111110001000000000000000000000 (Hex: 0xBE200000)
2. Umwandlung von IEEE-754 nach Dezimal:
• Vorzeichen bestimmen.
1 | 10000100 |10110000000000000000000 hat ein negatives Vorzeichen.
• Der Exponent ist 1000100(2) = 132(10) . Die Mantisse muss also
132 − 127 = 5 Stellen nach rechts verschoben werden.
• Die Mantisse ist 10110000000000000000000. Für 1.10110, ergibt sich,
nachdem das Komma um 5 Stellen nach rechts verschoben wurde:
110110.0
• Umwandlung in das Dezimalsystem: 1101102 = 21 + 22 + 24 + 25 = 54.
• Insgesamt ergibt sich also:
1100001001011000000000000000000032 = −5410
Entsprechend ergibt sich für die anderen Zahlen:
• 881.09375
• 1.71875
3. Dezimal nach 32-Bit-Zweierkomplement:
• 00000000000000000000000001111101
• 11111111111111111111101010111100
• 0xFFFEC65C
4. Bytefolgen im ASCII-Format:
• Dec: 70 101 114 110 85 110 105
• Bin: 01000011 01100001 01100011 01101000 01100101
• Hex: 0x53 0x61 0x6E 0x64 0x62 0x6F 0x78
5. gepackte/ungepackte BCD-Darstellung:
• Bin (gepackt): 00000000 00000000 00010101 01001000
Bin (ungepackt): 0000001 00000101 00000100 00001000
1.6. Zusammenfassung
47
• Hex (gepackt): 0x00004828
Hex (ungepackt): 0x04080208
Lösung Selbsttestaufgabe 1.2
Die registerindirekte Adressierung mit Verschiebung kombiniert die registerindirekte Adressierung mit der absoluten Adressierung, d. h. wird der Verschiebewert
auf Null gesetzt, so erhält man die registerindirekte Adressierung, wird dagegen
der Registerwert auf Null gesetzt, so erhält man die absolute Adressierung.
Lösung Selbsttestaufgabe 1.3
– ohne Lösung –
Lösung Selbsttestaufgabe 1.4
Beispielhafter Programmcode:
###########################################################
01 .data
02 src:
.word 0x5
# Speicherstelle für Wert n
03
04
05
06
.text
07
08
09
10
lw
li
jal
$a0,
$s1,
fac
src
1
# n wird in $a0 geladen
# Abbruchbedingung wird geladen
# Funktion fac wird aufgerufen
end:
j
end
fac:
addi
sw
sw
$sp,
$s0,
$ra,
$sp,
0($sp)
4($sp)
-8
# Auf dem Stack Platz für 2 Einträge schaffen
# Sichern des Registers $s0
# Sichern der Rücksprungadresse
11
12
13
14
add
beq
addi
jal
$s0,
$s0,
$a0,
fac
$zero,
$s1,
$a0,
$a0
done
-1
#
#
#
#
15
16
17
mult
mflo
j
$s0,
$v0
exit
$v0
# Multipliziere Ergebnis mit Argument
# Produkt als Ergebnis zurückgeben
# Sprung zur Marke exit
# Endlosschleife
Argument in $s0 laden
Wenn Argument = 1, springe zur Marke done
Ansonsten dekrementiere Argument
Rekursiver Aufruf von fac mit (n-1)
18
done:
li
$v0,
1
# Wenn Argument == 1, gib 1 zurück
19
20
21
22
exit:
lw
lw
addi
jr
$s0,
$ra,
$sp,
$ra
0($sp)
4($sp)
$sp,
#
#
#
#
8
Laden des Registers $s0 vom Stack
Laden der Rücksprungadresse vom Stack
Stackpointer zurücksetzen
Funktion beenden
###########################################################
Lösung Selbsttestaufgabe 1.5
Das Programm wandelt eine vorzeichenlose 8-Bit Integer-Zahl in die zugehörige
gepackte BCD-Darstellung (12 Bit) um (Algorithmus: Double Dabble).
48
Kapitel 1. Befehlssatzarchitekturen
Kapitel 2
Mikroarchitekturen
Kapitelinhalt
2.1
Lernziele . . . . . . . . . . . . . . . . . . . . . . . . . .
50
2.2
Einzyklus-Mikroarchitektur . . . . . . . . . . . . . .
51
2.3
Mehrzyklen-Mikroarchitektur . . . . . . . . . . . . .
62
2.4
Pipeline-Mikroarchitektur . . . . . . . . . . . . . . .
66
2.5
Zusammenfassung . . . . . . . . . . . . . . . . . . . .
90
50
Kapitel 2. Mikroarchitekturen
„Man versteht etwas erst wirklich, wenn man versucht, es zu implementieren.“
Donald E. Knuth zugeschrieben, 2002
Im vorherigen Kapitel wurde der Befehlssatz vorgestellt und Sie haben
gesehen, welche Funktionen ein Prozessor dem Compiler bzw. Assemblerprogrammierer grundsätzlich bereitstellt. Außerdem haben Sie die Grundlagen
der Befehlsverarbeitung wie Datenformate, Adressierungsarten, Befehlsformate
etc. kennengelernt. In diesem Kapitel gehen wir dazu über, uns die Mikroarchitektur eines Prozessors genauer anzuschauen. Diese nach außen nicht mehr
sichtbare Ebene gibt an, wie die Befehle, Adressierungsarten etc. konkret durch
einzelnen Funktionseinheiten implementiert werden können. Dieses Kapitel setzt
die Kenntnisse aus dem Kurs Computersysteme I (1608) voraus und schließt
inhaltlich an diesen an.
In Abschnitt 2.2 wird die Einzyklus-Mikroarchitektur eingeführt, die alle
MIPS-Befehle in einem einzigen großen Schaltnetz verarbeitet. Abschnitt 2.3
geht dann zur Mehrzyklen-Mikroarchitektur über, welche eine Einteilung der
Befehlsverarbeitung in mehrere Teilschritte vornimmt. Abschnitt 2.4 führt diese
Unterteilung konsequent weiter und zeigt, wie eine überlappende Befehlsverarbeitung ermöglicht werden kann und welche neuen Probleme dadurch entstehen.
2.1
Lernziele
In diesem Kapitel werden Sie lernen, wie der MIPS-Befehlssatz in einer konkreten Mikroarchitektur implementiert werden kann. Hierfür wird zunächst die
Einzyklus-Mikroarchitektur eingeführt, welche alle Befehle in einem einzigen
Schaltnetz verarbeitet. Anschließend wird gezeigt, wie die Befehlsverarbeitung
bei der Mehrzyklen-Mikroarchitektur in mehrere Teilschritte zerlegt werden
kann. Mit der Pipeline-Mikroarchitektur wird schließlich eine überlappende
Befehlsverarbeitung und somit Erhöhung der Prozessor-Taktrate ermöglicht.
Sie werden lernen, welche neuen Probleme bei Verwendung einer Pipeline entstehen und wie diese behandelt werden können. Hierzu gehören Datenkonflikte,
Steuerflusskonflikte und Strukturkonflikte. Zum Lösen eines Konfliktes bestehen
jeweils unterschiedliche Techniken in Soft- oder Hardware. Sie werden lernen,
wie die Mikroarchitektur schrittweise weiterentwickelt und verbessert wird. Diese
Erweiterung entspricht in etwa auch der in der Vergangenheit stattgefundenen Entwicklung von Prozessoren. Sie sollen verstehen, wodurch die einzelnen
Entwicklungsschritte motiviert waren (jeder Schritt löst ein vorangegangenes
Problem oder optimiert die Befehlsverarbeitung) und zusammen mit dem später
folgenden Kapitel 4 auch heute noch motiviert werden.
2.2. Einzyklus-Mikroarchitektur
2.2
51
Einzyklus-Mikroarchitektur
Im Folgenden wird die Einzyklus-Mikroarchitektur eines MIPS-Prozessors ausführlich beschrieben. Zunächst werden wir die einzelnen Zustandselemente genau
erläutern. Darauf folgt für einige Befehle der MIPS-Befehlssatzarchitektur eine
detaillierte Beschreibung der Datenpfade und der Steuereinheit1 . Abschließend
werden die Vor- und die Nachteile der Einzyklus-Mikroarchitektur dargelegt.
2.2.1
Zustandselemente
In diesem Unterabschnitt werden wir uns die einzelnen, vereinfachten2 Zustandselemente anschauen, die für das weitere Verständnis der EinzyklusMikroarchitektur wichtig sind. Die Zustandselemente sind in Abbildung 2.1
dargestellt. Diese setzen sich aus dem Befehlszählerregister (Program Counter –
PC), dem Registersatz, dem Befehlsspeicher und dem Datenspeicher zusammen.
Die Zustände der genannten Elemente werden durch verschiedene Steuersignale
von außen bestimmt.
PC'
PC
32
32
Befehlszähler
Lese-
Lese-
32 Adresse Daten
32
5
LeseRegister 1
5
LeseRegister 2
Befehlsspeicher
5
SchreibeRegister
Schreibe-
32 Daten
LeseDaten 1
32
32
Adresse
Datenspeicher
Registersatz
32
Schreibe-
32 Daten
LeseDaten 2
LeseDaten
WE
32
WE
Abbildung 2.1: Darstellung der einzelnen Zustandselemente.
Für die Abbildungen in diesem und im folgenden Unterkapitel gibt es einige
Konventionen, die wir berücksichtigen werden. Wie in Abbildung 2.1 zu sehen
ist, werden verschiedene Linienstärken genutzt. Hierbei geben dicke Linien
an, das es sich um einen 32-Bit-Datenbus handelt. Dünnere Linien deuten
5-Bit-Datenbusse bzw. Adressbusse oder Steuersignale an.
Der PC ist ein 32-Bit-Register und gibt die Adresse des nächsten Befehls Befehlszähleraus. Wenn zunächst von einem verzweigungsfreien, linearen Programmablauf register
ausgegangen wird, wird der PC mit jedem Taktzyklus inkrementiert. In der
1
Die Steuereinheit bezeichnet das Schaltnetz für die Steuersignale.
Um die Zustandselemente beim Start in einen definierten Zustand zu versetzen, haben
diese für gewöhnlich eine Reset-Eingabe; da wir den Befehlsspeicher als Nur-Lese-Speicher
betrachten, hat dieser nur einen Leseport (üblich bei eingebetteten Systemen), wohingegen
der Befehlsspeicher bei einigen Prozessoren beschreibbar sein muss (Personal Computer),
sodass das Betriebssystem ein neues Programm in den Speicher laden kann.
2
52
Befehlsspeicher
Registersatz
Datenspeicher
Synchronsequentielle
Schaltung
Kapitel 2. Mikroarchitekturen
Abbildung wird von einem byteweise adressierbaren 32-Bit-Speicher eines MIPSProzessors ausgegangen. Um die Adresse des nächsten Befehls zu erhalten, wird
der PC um vier inkrementiert3 .
Der Befehlsspeicher hat einen einzelnen Leseport. Dieser nimmt eine 32-BitBefehlsadresse als Eingabe auf und gibt den 32-Bit-Befehl von dieser Adresse
aus.
Der 32×32-Bit-Registersatz hat zwei Lese- und einen Schreibport. An den
Leseports liegt jeweils eine 5-Bit-Adresse als Eingabe (Lese-Register 1, LeseRegister 2) an. Jede dieser 5-Bit-Adressen beschreibt eines der 25 = 32 Register
als Quelloperanden. Diese werden an den Leseausgaben (Lese-Daten 1, LeseDaten 2) bereitgestellt. Am Schreibport liegt eine 5-Bit-Adresse (SchreibeRegister), ein 32-Bit-Datenwort (Schreibe-Daten), ein Steuersignal (WE – Write
enable) und ein Takt als Eingabe an. Wenn das Datenwort am Schreibe-DatenEingang an die 5-Bit-Adresse im Schreibe-Register-Eingang geschrieben werden
soll, muss das Steuersignal an WE = 1 sein. Anderenfalls werden die Inhalte
an den Adressen der Lese-Register an den Leseausgabe-Ports (Lese-Daten 1,
Lese-Daten 2) ausgegeben.
Der Datenspeicher hat einen Lese- und einen Schreib-Port. Das Steuersignal
an WE bestimmt, ob ein Datenwort von einer bestimmten Adresse im Datenspeicher gelesen oder an diese Adresse geschrieben wird. Falls WE = 1 ist, wird
das Datenwort vom Schreibe-Daten-Eingang mit steigender Taktflanke an die
Adresse im Datenspeicher geschrieben. Ansonsten wird der Inhalt an der Adresse
im Datenspeicher an Lese-Daten-Port ausgegeben.
Wenn die Adresse des Befehlsspeichers, des Datenspeichers oder des Registersatzes geändert wird, erscheinen die Daten nach einer gewissen Übertragungsdauer am jeweiligen Leseausgabeport (Lese-Daten). Dafür ist kein Taktgeber
erforderlich, wohingegen das Schreiben in die Speicher ausschließlich getaktet
mit der steigenden Taktflanke erfolgt. Daraus resultiert, dass der Zustand des
Systems sich nur mit der steigenden Flanke des Taktgebers ändert. Deshalb
handelt es sich bei der Einzyklus-Mikroarchitektur um synchron-sequentielle
Schaltungen.
2.2.2
Befehlsholephase
Datenpfad
Im Folgenden wird sukzessive der Datenpfad der Einzyklus-Mikroarchitektur
entwickelt. Dazu betrachten wir zunächst Speicher-Befehle. Im Anschluss daran
wird der Datenpfad erweitert, sodass arithmetische- und logische Befehle (RTyp-Befehle) ermöglicht werden. Abschließend wird untersucht, inwiefern der
Datenpfad ausgebaut werden muss, um Verzweigungs-Befehle zu unterstützen.
Zur Veranschaulichung werden in den folgenden Abbildungen die neu besprochene Elemente und Verbindungen in schwarzer Farbe dargestellt. Die bereits
erläuterten Anteile werden in Grau gezeigt.
In der Befehlsholephase (Instruction Fetch – IF) muss ein PC und ein
Befehlsspeicher bereit stehen. Der Ausgang des PC wird auf den Adresseingang
3
32 Bit = 4 Byte
2.2. Einzyklus-Mikroarchitektur
53
des Befehlsspeichers geschaltet. Wie in Abbildung 2.2 dargestellt ist, gibt der
Befehlsspeicher anschließend das entsprechende Befehlswort aus.
LeseLeseAdresse Daten
Befehlszähler
Befehl
LeseRegister 1
LeseDaten 1
Adresse
LeseRegister 2
Befehlsspeicher
SchreibeRegister
SchreibeDaten
WE
Registersatz
Datenspeicher
LeseDaten
SchreibeDaten
WE
LeseDaten 2
Abbildung 2.2: Holen eines Befehls.
Mit dem Befehlswort wird die Operation bestimmt, die der Prozessor als
nächstes ausführt.
Zunächst werden wir uns die einzelnen Datenpfadverbindungen für einen lw-Befehl
I-Typ-Transportbefehl (lw – Laden eines Wortes) erarbeiten. Dazu wird als
nächstes der Inhalt des Quellregisters ausgelesen. Dieser beinhaltet die Basisadresse, welche für die Bestimmung der Zieladresse im Datenspeicher benötigt
wird. Die Adresse für das Quellregister ist im rs-Feld des Befehlswortes (Befehl[25:21]4 ) festgelegt und dient als Eingabe an Lese-Register 1. Der Ausgang
Lese-Daten 1 liefert nach Ablauf der Zugriffszeit den Inhalt des Quellregisters.
Der beschriebene Vorgang ist in Abbildung 2.3 dargestellt.
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
LeseRegister 1
LeseDaten 1
LeseRegister 2
Befehlsspeicher
SchreibeRegister
SchreibeDaten
WE
Registersatz
Adresse
Datenspeicher
LeseDaten
SchreibeDaten
LeseDaten 2
WE
Abbildung 2.3: Lesen des Quelloperanden vom Register.
Beim lw-Befehl wird ein Offset benötigt, welcher im unmittelbaren Feld des Offset
Befehlswortes (Befehl[15:0]) enthalten ist. Da der Inhalt des 16-Bit unmittelbaren
Feldes entweder positiv oder negativ ist, wird das Vorzeichen bis auf 32-Bit
erweitert5 .
Anschließend wird, wie in Abbildung 2.4 gezeigt ist, die Basisadresse und Berechnung der
Adresse im
der Offset in der ALU addiert.
4
Das rs-Feld beginnt beim 21. und endet beim 25. Bit des Befehls. Diese Notation wird in
diesem und im folgenden Abschnitt verwendet.
5
Wenn das Vorzeichen des unmittelbaren Operanden negativ ist, wird eine Addition mit
dem Zweierkomplement des vorzeichenerweiterten, unmittelbaren Operanden durchgeführt.
Datenspeicher
54
Kapitel 2. Mikroarchitekturen
ALU Operation
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
LeseRegister 1
LeseDaten 1
Null
ALU
LeseRegister 2
Befehlsspeicher
SchreibeRegister
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
Adresse
LeseDaten
SchreibeDaten
15:0
16
Vorzeichenerweiterung
WE
32
Abbildung 2.4: Vorzeichenerweiterung des unmittelbaren Feldes und anschließende Addition mit der Basisadresse.
Zurückschreiben in
Registersatz
Als Ergebnis liefert die ALU die Adresse im Datenspeicher. Die Auswahl
der arithmetischen oder logischen Operation erfolgt dabei durch ein 3-BitSteuersignal. Die Resultate der ALU sind eine 32-Bit-Adresse und ein NullFlag (Zero Flag). Dieses Flag liefert eine Aussage darüber, ob das Ergebnis der ALU-Operation gleich null ist und wird für einige Befehle der MIPSBefehlssatzarchitektur genutzt. Das ALU-Ergebnis wird beim lw-Befehl direkt
mit dem Adresseingang des Datenspeichers verbunden.
Die Inhalte an der berechneten Adresse im Datenspeicher werden an LeseDaten ausgegeben und in das Zielregister zurückgeschrieben. Dieser Vorgang ist
in Abbildung 2.5 zu sehen.
ALU Operation
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
LeseRegister 1
LeseDaten 1
Null
ALU
LeseRegister 2
Befehlsspeicher
20:16
SchreibeRegister
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
Adresse
LeseDaten
SchreibeDaten
1
WE
15:0
16
Vorzeichenerweiterung
RegWrite
32
Abbildung 2.5: Zurückschreiben der ausgelesenen Inhalte des Datenspeichers an
die entsprechende Adresse im Registersatz.
rt-Feld
Hierbei wird ersichtlich, dass die Adresse für das Zielregister im rt-Feld
(Register Destination) des Befehlswortes (Befehl[20:16]) festgelegt ist. Die entsprechende Adresse liegt am Schreibe-Register-Eingang und die ausgelesenen
2.2. Einzyklus-Mikroarchitektur
55
Inhalte des Datenspeichers liegen am Schreibe-Daten-Eingang des Registersatzes
an. Dabei wird angenommen, dass das entsprechende Steuersignal (RegWrite)
am Registersatz gesetzt ist. In diesem Zustand werden die Daten bei steigender
Flanke des Taktgebers in das Zielregister geschrieben.
Um den Datenpfad für den lw-Befehl zu vervollständigen, muss der Prozessor PC-Inkrement
die Adresse des nächsten Befehls berechnen. Dazu wird in Abbildung 2.6 ein
zusätzlicher Addierer genutzt. Die neue Adresse P Ct+1 = P Ct + 4 wird in den
Befehlszähler bei der nächsten, steigenden Flanke des Taktgebers geschrieben.
ALU Operation
PC(t+1)
PC(t)
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
LeseRegister 1
LeseDaten 1
Null
ALU
LeseRegister 2
Befehlsspeicher
20:16
SchreibeRegister
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
WE
32
Addierer
4
Vorzeichenerweiterung
15:0
16
LeseDaten
SchreibeDaten
0
RegWrite
Adresse
Abbildung 2.6: Inkrementieren des Befehlszählers.
Nachfolgend wird der Datenpfad zum Speichern eines Wortes (sw – store sw-Befehl
word) erweitert. Wie beim Lade-Befehl wird beim sw-Befehl die Basisadresse
von der in Lese-Register 1 definierten Adresse gelesen und das unmittelbare
Feld des Befehlswortes auf 32 Bit erweitert. Um die Adresse im Datenspeicher
zu bestimmen, addiert die ALU die Basisadresse zum vorzeichenerweiterten,
unmittelbaren Operanden. Diese Funktionen werden bereits von dem bisher
entwickelten Datenpfad unterstützt.
Beim sw-Befehl wird von einer zweiten Adresse des Registersatzes gelesen.
Des Weiteren wird der ausgelesene Registerinhalt in den Datenspeicher geschrieben. Die Adresse ist im rt-Feld des Befehlswortes (Befehl[20:16]) enthalten
und wird am Eingang Lese-Register 2 bereitgestellt. Der Registerinhalt an
dieser Adresse wird an Lese-Daten 2 ausgegeben und durch einen Datenbus zum
Schreibe-Daten-Eingang des Datenspeichers geführt. Der WE-Port des Datenspeichers wird durch das Steuersignal MemWrite gesteuert. Für den sw-Befehl wird
das Schreiben in den Datenspeicher erlaubt (MemWrite = 1) und in den Registersatz verboten (RegWrite = 0), da vom Registersatz ausschließlich gelesen
werden soll. Dieser Ablauf wird in Abbildung 2.7 verdeutlicht.
56
Kapitel 2. Mikroarchitekturen
ALU Operation
PC(t+1)
PC(t)
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
LeseRegister 1
20:16
LeseRegister 2
Befehlsspeicher
20:16
SchreibeRegister
LeseDaten 1
Null
ALU
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
4
WE
1
MemWrite
32
Addierer
16
Vorzeichenerweiterung
15:0
LeseDaten
SchreibeDaten
0
RegWrite
Adresse
Abbildung 2.7: Lesen der zweiten Register-Adresse.
R-Typ-Befehle
Auswahl der
Operanden
Schreiben des
ALU-Ergebnisses
Für die Unterstützung von R-Typ-Befehlen (add, sub, and, or und slt)
wird nachfolgend der Datenpfad ausgebaut. Bei diesem Befehlstyp, werden die
Inhalte von zwei Register-Adressen ausgelesen und durch ALU-Operationen
verarbeitet. Bei den zuvor besprochenen Befehlen wurde einer der Operanden der
ALU direkt von der Funktionseinheit, der Vorzeichenerweiterung, zur Verfügung
gestellt. Wie Abbildung 2.8 entnommen werden kann, werden zur Bereitstellung
eines weiteren Befehlstyps zusätzliche Multiplexer vor Schreibe-Register bzw.
Schreibe-Daten des Registersatzes und einem der Eingänge der ALU benötigt.
Dies ermöglicht eine Auswahl, zwischen dem Registerinhalt an Lese-Daten
2 oder dem vorzeichenerweiterten, unmittelbaren Feld des Befehlswortes als
Operand für die ALU. Die Kontrolle des Multiplexers erfolgt über das Steuersignal ALUSrc. Wenn ALUSrc = 0 ist, wird der Operand in Register-Daten 2
und anderenfalls der vorzeichenerweiterte, unmittelbare Operand für die ALU
ausgewählt.
Zudem werden bei R-Typ-Befehlen die ALU-Ergebnisse direkt in das Register geschrieben. Aus diesem Grund folgt nach dem Datenspeicher ein weiterer
Multiplexer. Dadurch kann entschieden werden, ob der Inhalt aus dem Datenspeicher oder das ALU-Ergebnis unmittelbar in das Register geschrieben werden
sollen. Die Steuerung des Multiplexers erfolgt durch das MemtoReg Steuersignal.
Wenn MemtoReg = 0 ist, wird das ALU-Ergebnis, ansonsten der Inhalt des
Datenspeichers in das Register geschrieben.
Hierbei wird die Adresse des Zielregister aus dem rd-Feld des Befehlswortes
(Befehl[15:11]) gelesen. Deshalb wird ein dritter Multiplexer zum Datenpfad
hinzugefügt, durch den entschieden wird, welcher Teil des Befehlswortes für
die Adressierung im Registersatz verwendet wird (Befehl[15:11] für R-Typund Befehl[20:16] für I-Typ-Befehle). Dabei steuert das RegDst Steuersignal
2.2. Einzyklus-Mikroarchitektur
57
ALU Operation
PC(t+1)
PC(t)
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
20:16
LeseRegister 1
0
SchreibeRegister
0
Null
ALU
LeseRegister 2
Befehlsspeicher
MemtoReg
ALUSrc
LeseDaten 1
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
Adresse
LeseDaten
SchreibeDaten
1
WE
RegWrite
0
20:16
MemWrite
1
15:0
16
4
32
Addierer
RegDst
Vorzeichenerweiterung
15:11
Abbildung 2.8: Erweiterter Datenpfad für R-Typ-Befehle.
den Multiplexer. Wenn RegDst = 1 ist, wird Befehl[15:11] als Adresse auf
den Schreibe-Register Eingang geschaltet. Anderenfalls wird Befehl[20:16] als
Zieladresse im Registersatz verwendet.
Abschließend betrachten wir die Anpassung des Datenpfades für einen beding- beq-Befehl
ten Verzweigungsbefehl (beq – branch if equal). Beim beq-Befehl werden zwei
Registerinhalte verglichen. Wenn diese übereinstimmen, wird eine Verzweigung
durch das Hinzufügen eines Verzweigungs-Offsets zum PC ausgeführt. Dieser
Offset ist wiederum im unmittelbaren Feld des Befehlswortes (Befehl[15:0]) definiert. Da es sich sowohl um eine positive, als auch eine negative Zahl handeln
kann, wird das unmittelbare Feld vorzeichenerweitert und mit 4 multipliziert.
Der neue Wert des Befehlszählers ergibt sich folgendermaßen:
P Ct+1 = P Ct + 4 + SignImm 2
(2.1)
Wobei P Ct+1 den neuen-, P Ct den alten Wert des Befehlszählers und
SignImm den vorzeichenerweiterten, unmittelbaren Operanden bezeichnet.
In Abbildung 2.9 ist der zugehörige Datenpfad dargestellt. Daraus kann entnommen werden, dass zunächst das vorzeichenerweiterte, unmittelbare Feld um zwei
Stellen nach links verschoben wird. Dadurch wird eine effiziente Multiplikation
mit der Zahl 4 durchgeführt. Die beiden Registerinhalte werden in der ALU
auf Gleichheit überprüft. Dabei werden die Operanden voneinander subtrahiert.
Beläuft sich das ALU-Ergebnis auf den Wert null, wird das Nullflag auf 1 gesetzt.
Der neu hinzugefügte Multiplexer wird durch das Steuersignal PCSrc gesteuert.
Dieses setzt sich aus einer UND-Verknüpfung zwischen dem Null-Flag und dem
Branch-Steuersignal vom Befehlsdekodierer zusammen. Handelt es sich bei dem
58
Kapitel 2. Mikroarchitekturen
Befehl um eine Verzweigung, wird das Steuersignal auf 1 gesetzt. Das X an
den Steuersignalen MemtoReg und RegDst bedeutet, dass der Wert bei der
betrachteten Verzweigung keinen Beitrag leistet.
Mit den Verzweigungsbefehlen haben wir die Entwicklung des Datenpfades
für die Einzyklus-Mikroarchitektur des MIPS-Prozessors abgeschlossen. Dabei
haben wir die einzelnen Datenpfade für die verschiedenen Befehle sukzessive erweitert und neue Elemente hinzugefügt. Im Folgenden werden wir die
Steuersignale der Steuereinheit genau betrachten.
ALU Operation
PC(t+1)
PC(t)
LeseLeseAdresse Daten
Befehlszähler
Befehl
25:21
20:16
LeseRegister 1
0
SchreibeRegister
x
Null
ALU
LeseRegister 2
Befehlsspeicher
MemtoReg
ALUSrc
LeseDaten 1
ALUErgebnis
Registersatz
Adresse
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
LeseDaten
SchreibeDaten
0
WE
RegWrite
0
20:16
MemWrite
15:0
16
RegDst
32
Addierer
<<2
4
Addierer
x
Vorzeichenerweiterung
15:11
PCSrc
1
Abbildung 2.9: Erweiterter Datenpfad für bedingte Verzweigungsbefehle.
2.2.3
Befehlsdekodierer
Steuereinheit
Die Steuereinheit bestimmt die Steuersignale basierend auf dem Opcode (Befehl[31:26]) und dem func-Feld (Befehl[5:0]). In Abbildung 2.10 ist der zuvor
entwickelte Datenpfad des Einzyklus-MIPS-Prozessor mit der Steuereinheit
gezeigt.
Um die Auswahl der entsprechenden ALU-Operation durchzuführen, werden
bei R-Typ-Befehlen sowohl der Opcode, als auch das func-Feld genutzt. Zum
besseren Verständnis ist in Abbildung 2.10 die Steuereinheit in zwei Blöcke
unterteilt (markiert durch ein gestricheltes Rechteck) dargestellt. Hierbei berechnet der Befehlsdekodierer die Steuerausgaben vom Opcode und zusätzlich
das 2-Bit-ALUOp-Signal. Dieses Signal wird zusammen mit dem Inhalt des
func-Feldes genutzt, um die entsprechende ALU-Operation zu bestimmen.
2.2. Einzyklus-Mikroarchitektur
59
Steuereinheit
MemtoReg
Befehlsdekodierer MemWrite
31:26
Branch
Opcode
ALUSrc
RegDst
RegWrite
&
PCSrc
ALUOp
1:0
FunktionsALU Operation2:0
dekodierer
5:0
PC(t+1)
BeLese- fehl
LeseAdresse Daten
PC(t)
Befehlszähler
Func
25:21
LeseRegister 1
20:16
LeseRegister 2
Befehlsspeicher
SchreibeRegister
LeseDaten 1
Null
ALU
ALUErgebnis
Registersatz
Adresse
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
LeseDaten
SchreibeDaten
WE
20:16
32
Addierer
<<2
4
Addierer
15:0
16
Vorzeichenerweiterung
15:11
Abbildung 2.10: Kompletter Einzyklus MIPS Prozessor
Aus Tabelle 2.1 kann die Enkodierung des ALUOp-Signals entnommen
werden.
Tabelle 2.1: Enkodierung des ALUOp-Signals
ALUOp Bedeutung
00
Addition
01
Subtraktion
10
Nutze das func-Feld
11
n/a
Tabelle 2.2 stellt die Wahrheitstabelle für den Funktionsdekodierer dar.
Funktionsdekodierer
60
Kapitel 2. Mikroarchitekturen
Tabelle 2.2: Wahrheitstabelle des Funktionsdekodierers
ALUOp Func
ALU Operation
00
X
010 – add
X1
X
110 – sub
1X
100000 – add 010 – add
1X
100010 – sub 110 – sub
1X
100100 – and 000 – and
1X
100101 – or
001 – or
1X
101010 – slt
111 – set les than
Da das ALUOp-Signal niemals den Wert 11 annehmen kann, sind zur
Vereinfachung der Logik in der Wahrheitstabelle statt 01 und 10 sogenannte
don’t cares X1 und 1X zu sehen. Wenn das ALUOp-Signal gleich dem Wert 00
oder 01 ist, wird in der ALU eine Addition oder eine Subtraktion ausgeführt.
Beläuft sich das ALUOp-Signal auf den Wert 10, wird zusätzlich das funcFeld des Befehlswortes betrachtet, um die entsprechende ALU-Operation zu
bestimmen.
Die Wahrheitstabelle für die Steuersignale des Befehlsdekodierers ist in
Tabelle 2.3 für die zuvor besprochenen Befehle zusammengefasst.
R-Typ
lw
sw
beq
Tabelle 2.3: Wahrheitstabelle des Befehlsdekodierers
OpReg- Reg- ALU- Branch Mem- Memcode Write Dst
Src
Write toReg
000000 1
1
0
0
0
0
100011 1
0
1
0
0
1
101011 0
X
1
0
1
X
000100 0
X
0
1
0
X
ALUOp
10
00
00
01
Alle R-Typ-Befehle nutzen die gleichen Werte des Befehlsdekodierers. Diese
unterscheiden sich ausschließlich in der Ausgabe des Funktionsdekodierers. Des
Weiteren kann der Wahrheitstabelle entnommen werden, dass die Steuersignale
RegDst und MemtoReg der Befehle, die nur lesend auf den Registersatz zugreifen
(z. B. sw, beq), mit don’t cares gekennzeichnet sind.
2.2.4
Anwendung
Befehlszyklen
Vor- und Nachteile
Heutzutage wird die Einzyklus-Mikroarchitektur vor allem in der Lehre eingesetzt, da man die einzelnen Befehle anhand eines einfachen Aufbaus der
Datenpfade und der genutzten Zustandselemente leicht erklären kann. In der
Praxis findet diese Mikroarchitektur kaum Anwendung. Dies resultiert aus
der Tatsache, dass die betrachtete Mikroarchitektur je Zyklus nur eine einzige Operation ausführen kann. Dadurch werden die zur Verfügung stehenden
2.2. Einzyklus-Mikroarchitektur
61
Resourcen nicht optimal genutzt. Dies wird in Abbildung 2.11 hinsichtlich der
Ausführungszeit von vier Befehlszyklen verdeutlicht.
Abbildung 2.11: Ausführungszeiten einer Einzyklus-Mikroarchitektur für verschiedene Zyklen.
Dabei stellen die unterschiedlichen Grautöne jeweils verschiedene Ausführungsphasen der betrachteten Operationen dar. Bei näherer Betrachtung wird
ersichtlich, dass die zweite Operation die längste Ausführungszeit beansprucht,
wodurch sich die Dauer der übrigen Befehlszyklen auf Grund von Leerlaufphasen angleicht. Dadurch wird der zuvor angesprochenen Nachteil der EinzyklusMikroarchitektur näher verdeutlicht. Zudem muss sichergestellt sein, dass die
Dauer eines Zyklus lang genug ist, sodass der langsamste Befehl ausgeführt
werden kann. Hierdurch wird die Taktfrequenz deutlich beschränkt.
Des Weiteren fällt bei der entwickelten Einzyklus-Mikroarchitektur auf, Chipfläche
dass drei Addierer benötigt werden. Einen in der ALU und zwei weitere für
die PC-Logik. Diese Addierer müssen alle die Fähigkeit haben 32-Bit-Werte
zu verrechnen. Deshalb setzen sie sich aus einer Kombination von mehreren
Volladdierern zusammen. Daraus resultiert, dass ein 32-Bit-Addierer mehr
Chipfläche benötigen, als andere Strukturen, weshalb es erstrebenswert ist, die
Anzahl dieser Bauteile möglichst gering zu halten.
Selbsttestaufgabe 2.1
Beschreiben Sie den Datenfluss und die notwendigen Steuersignale zur Ausführung eines and-Befehls.
Selbsttestaufgabe 2.2
Unterscheiden sich der Datenfluss und die Steuersignale bei der Ausführung
eines or-Befehls im Vergleich zum and-Befehl? Wenn ja, inwiefern?
62
Kapitel 2. Mikroarchitekturen
Selbsttestaufgabe 2.3
Nun soll der entwickelte MIPS-Prozessor so erweitert werden, dass der addiBefehl unterstützt wird. Bei diesem Befehl wird der Wert in einem Register zum
unmittelbaren Operanden addiert und in ein Register geschrieben. Muss der
aktuelle Datenpfad in Abbildung 2.10 erweitert werden? Falls dies der Fall sein
sollte, wie müssen Sie den Datenpfad ändern? Welche Steuersignale müssen von
der Steuereinheit ausgehen? Wie sieht der Datenfluss bei dem addi-Befehl aus?
Selbsttestaufgabe 2.4
Erweitern Sie den Datenpfad, sodass der unbedingte Sprung-Befehl j unterstützt
wird. Dieser Befehl schreibt einen neuen Wert in den PC. Da der PC immer um
vier Byte erhöht wird, muss der Wert des PCs immer ein Vielfaches von Vier
sein. Daraus resultiert, dass die beiden Bits mit dem niedrigsten Stellenwert
(least significant bits) des PCs immer den Wert null haben. Die nächsten 26
Bits werden aus dem PC-Offset des Befehls (Befehl[25:0]) und den oberen vier
Bits des um vier inkrementierten, alten Wertes des PCs übernommen. Geben
Sie zusätzlich die Wahrheitstabelle des Befehlsdekodierers und die maximale
Sprungdistanz (in Befehlen) im Befehlsadressraum an.
2.3
Mehrzyklen-Mikroarchitektur
Im vorherigen Abschnitt haben wir die Einzyklus-Mikroarchitektur kennen
gelernt und die Befehlszyklen im Wesentlichen in zwei Schritte aufgeteilt:
(1) Getaktetes Holen eines Befehls,
(2) Ausführen des Befehls durch ein Schaltnetz.
Anschließend werden mit dem nächsten Taktzyklus die Ergebnisse im Registersatz oder im Datenspeicher übernommen. Des Weiteren haben wir die Vor- und
Nachteile dieser Mikroarchitektur erläutert.
Durch die Mehrzyklen-Mikroarchitektur werden die Schwächen der Einzyklus-Mikroarchitektur angegangen. Im Folgenden werden wir die Funktionsweise
der Mehrzyklen-Mikroarchitektur grob6 skizzieren und die Vor- und die Nachteile
betrachten. Für ein tieferes Verständnis dieser Mikroarchitektur wird auf [2]
verwiesen.
6
Bereits angesprochen in Kurs 1608
2.3. Mehrzyklen-Mikroarchitektur
2.3.1
63
Funktionsweise
Wir werden in diesem Unterabschnitt keine Datenpfade für die einzelnen Befehle
entwickeln, sondern uns von Anfang an mit einem vollständigen Prozessor
beschäftigen. Dieser ist in Abbildung 2.12 gezeigt und kann die in Abschnitt 2.2
angesprochenen Befehle der MIPS-Befehlssatzarchitektur ausführen.
Bei der Mehrzyklen-Mikroarchitektur wird der auszuführende Befehl in
mehrere Schritte aufgegliedert. In jedem Schritt kann der Prozessor auf den
Speicher oder den Registersatz lesend oder schreibend zugreifen oder die ALU
zur Ausführung von arithmetischen/logischen Operationen nutzen. Hierbei sind
die einzelnen Befehle in eine unterschiedliche Anzahl von (Takt) Schritten
aufgeteilt, sodass einfache Befehle schneller fertiggestellt werden als komplexere
Befehle.
Der Prozessor benötigt für die Ausführung der Befehle nur einen Addierer,
die ALU. Diese wird für verschiedene Zwecke in unterschiedlichen Schritten
der Befehlsabarbeitung verwendet. Die Entscheidung für welchen Zweck und
welche Operationen die ALU genutzt wird, erfolgt durch die Steuersignale der
Steuereinheit und den beiden Multiplexer an den Eingängen der ALU.
Im Gegensatz zur Einzyklus-Mikroarchitektur wird bei der MehrzyklenMikroarchitektur der Befehls- und der Datenspeicher in einem Speicher kombiniert. Der Befehl wird im ersten Schritt aus dem Speicher geladen und die
Daten können in späteren Schritten gelesen oder geschrieben werden.
Zunächst werden wir den Datenfluss und die einzelnen Steuersignale für den
lw-Befehl untersuchen. Wie bei der Einzyklus-Mikroarchitektur beinhaltet der
PC die Adresse für den auszuführenden Befehl. Im ersten Schritt wird dieser
Befehl vom Speicher gelesen und in einem Befehlsregister zwischengespeichert,
sodass der Befehl für zukünftige Zyklen zur Verfügung steht. Das IRWrite-Signal
wird gesetzt, wenn das Befehlsregister mit einem neuen Befehl aktualisiert
werden soll.
Im Anschluss daran wird die Basisadresse vom Registersatz gelesen. Die
entsprechende Adresse im Registersatz ist durch das rs-Feld des Befehls (Befehl[25:21]) gegeben. Die Basisadresse für den Speicherzugriff wird am LeseDaten-1-Port bereitgestellt. Des Weiteren benötigt der lw-Befehl einen Offset.
Dieser wird aus dem unmittelbaren Feld des Befehls (Befehl[15:0]) gelesen und
durch eine Vorzeichenerweiterung auf einen 32-Bit-Wert vergrößert. Über die
beiden Multiplexer, die mit den Steuersignalen ALUSrcA und ALUSrcB[1:0] verbunden sind, werden die Operanden für die ALU ausgewählt. Bei dem lw-Befehl
muss dafür ALUScrA = 1 und ALUSrcB[1:0] = 10 sein. Um die gewünschte
Addition in der ALU auszuführen, muss ALU Operation[2:0] der Bitfolge 010
entsprechen. Nach der Addition der beiden Operanden wird das ALU-Ergebnis
im ALUOut-Register zwischengespeichert. Es stellt die endgültige Adresse für
den Speicherzugriff dar.
Anschließend wird das Datenwort an der berechneten Adresse aus dem
Speicher gelesen. Dazu muss das Steuersignal IorD gesetzt sein. Das ausgelesene
Datenwort wird vom Speicher in das Speicherdatenregister geladen. Zum Schluss
Aufteilung des
Befehls
Ein Addierer
Ein Speicher
lw-Befehl
Operanden zur
Adressgenerierung
Schreiben in den
Registersatz
PC(t)
Befehlszähler
PC(t+1)
SchreibeDaten
Speicher
Adresse
MemData
WE
Speicherdatenregister
Befehlsregister
Daten
Befehl
15:0
15:11
20:16
25:21
5:0
5:0
31:26
Steuereinheit
1:0
ALUOp
RegWrite
Branch
PCWrite
MemWrite
IRWrite
ALUSrcA
16
Func
SchreibeDaten
SchreibeRegister
LeseRegister 2
WE
LeseRegister 1
32
B
A
<<2
LeseDaten 2
Registersatz
LeseDaten 1
Funktionsdekodierer ALU Operation2:0
Opcode
Befehlsdekodierer
ALUSrcB1:0
PCSrc
IorD
RegDst
MemtoReg
Vorzeichenerweiterung
4
3
2
1
0
Null
ALUErgebnis
ALU
&
ALUOut
≥1
64
Kapitel 2. Mikroarchitekturen
Abbildung 2.12: Mehrzyklen-Mikroarchitektur
2.3. Mehrzyklen-Mikroarchitektur
wird der Inhalt des Speicherdatenregisters in den Registersatz geschrieben.
Dazu muss das Steuersignal MemtoReg am entsprechenden Multiplexer gesetzt
und RegDst = 0 sein, damit das rt-Feld (Befehl[20:16]) des Befehlswortes als
Zieladresse im Registersatz genutzt wird.
Nachdem die zuvor genannten Schritte abgearbeitet wurden, muss der PC PC-Inkrement
um 4 inkrementiert werden. Dazu wird wieder die ALU genutzt, sobald diese
bereit zur Ausführung des nächsten Befehls ist. Um den PC zu erhöhen, muss das
Steuersignal ALUSrcA = 0 und ALUSrcB[1:0] = 01 sein. Das ALU Ergebnis wird
in den PC geschrieben, sofern das Steuersignal PCSrc = 0 ist. Das Steuersignal
PCWrite stellt sicher, dass in das PC-Register ausschließlich in bestimmten
Zyklen geschrieben wird.
Als nächstes werden wir die Befehle im R-Typ-Format untersuchen. Bei R-Typ-Befehle
diesen Befehlen wird wiederum der Befehl geholt und die beiden Quellregister
werden vom Registersatz gelesen. Die dazu notwendigen Adressen sind im rsund im rt-Feld des Befehls definiert. Um die beiden Operanden A und B in der
ALU nutzen zu können, müssen die entsprechenden Steuersignale an den beiden
Multiplexer anliegen (ALUSrcA = 1; ALUSrcB[1:0] = 00). Des Weiteren muss
Branch = 0 sein. Anschließend wird das ALU-Ergebnis in das ALUOut-Register
geschrieben. Der Inhalt des ALUOut-Registers wird in den Registersatz an die
entsprechende Adresse geschrieben. Diese ist durch das rd-Feld des Befehls
festgelegt. Um die Adresse als Zieladresse im Registersatz nutzen zu können,
muss das Steuersignal RegDst gesetzt und MemtoReg = 0 sein.
2.3.2
Vor- und Nachteile
Verglichen mit einem Einzyklus-Prozessor, richtet sich die Ausführungsdauer Befehlszyklen
nicht mehr nach dem Befehl, dessen Zyklus am längsten dauert, sondern nach
der Dauer der einzelnen Teilschritte der Befehlsabarbeitung. Dies wird durch
Abbildung 2.13 verdeutlicht. Dadurch wird bei der Mehrzyklen-Mikroarchitektur
Abbildung 2.13: Ausführungszeiten einer Mehrzyklus-Mikroarchitektur für
verschiedenen Zyklen.
die Abhängigkeit Der Taktfrequenz von der langsamsten Ausführungseinheit
vermieden. Die Zahl der Taktzyklen pro Befehl variiert in Abhängigkeit von
seiner Komplexität. Dennoch fällt bei einer genaueren Betrachtung der Befehle
65
66
Eine ALU und ein
Speicher
Kapitel 2. Mikroarchitekturen
pro Zyklus und der Verzögerungszeiten der einzelnen Schaltungselemente auf,
dass ein Mehrzyklen-Prozessor langsamer ist als ein Einzyklus-Prozessor. Dies
wird in [2] genauer erläutert.
Zudem benötigt der Prozessor eine ALU und kombiniert den Daten- und
den Befehlsspeicher in einer Einheit. Jedoch werden bei einem MehrzyklenProzessor zusätzliche Multiplexer und Register benötigt, auf die von außerhalb
nicht schreibend oder lesend zugegriffen werden kann. Diese Register werden
nicht-Architektur-, physikalisch- oder Hardware-Register genannt. Beim MIPS
Prozessor gibt es zum Beispiel 32 frei verwendbare Register, die als ArchitekturRegister bezeichnet werden.
2.4
Pipeline-Mikroarchitektur
In diesem Abschnitt werden wir die Mikroarchitektur dahingehend erweitern,
dass eine überlappende Abarbeitung von mehreren Befehlen und damit eine
Erhöhung des Durchsatzes erreicht wird. Dieses als Pipelining bezeichnete Prinzip wird in Unterabschnitt 2.4.1 und Unterabschnitt 2.4.2 zunächst allgemein
erläutert. In Unterabschnitt 2.4.3 wird dann gezeigt, wie die oben vorgestellte
MIPS-Einzyklus-Mikroarchitektur zu einer Pipeline-Mikroarchitektur erweitert
werden kann. Die Verwendung einer Pipeline bringt hingegen auch einige Konfliktmöglichkeiten mit sich, welche durch die überlappende Abarbeitung von
mehreren Befehlen entstehen. Auf Pipeline-Konflikte und deren Lösungsmöglichkeiten gehen wir ab Unterabschnitt 2.4.4 ein.
2.4.1
Pipeline-Prinzip
Das Pipeline-Prinzip
Betrachten wir zunächst noch einmal die Einzyklus-Mikroarchitektur aus Abschnitt 2.2. Bei genauem Hinsehen wird deutlich, dass sich die Verarbeitung
aller Befehle in vielen Punkten ähnelt. Ein Befehl wird zunächst geholt und
anschließend dekodiert. Für die eigentliche Ausführung des Befehls stehen
ein Registersatz und eine ALU zur Verfügung. Zusätzlich erfolgt bei einem
Lade-/Speicherbefehl ein Zugriff auf den Hauptspeicher. Anschließend wird das
Ergebnis der Operation oder des Speicherzugriffs in ein Register zurückgeschrieben.
Bei der Einzyklus-Mikroarchitektur werden alle diese Verarbeitungsschritte
für alle möglichen Befehlsarten in einem einzigen Takt durchlaufen. Da dieses
Schaltnetz vergleichsweise lang ist, wird die Taktrate des Prozessors stark
begrenzt. In einer Erweiterung dieser Architektur haben wir gesehen, wie bei
der Mehrzyklen-Mikroarchitektur in Abschnitt 2.3 die Verarbeitung in mehrere
Schritte aufgeteilt wird, sodass nicht mehr jeder Befehl jede Stufe durchlaufen
muss, sondern nur die für ihn benötigten Stufen.
Eine konsequente Fortführung dieser Unterteilung in mehrere Teilschritte ist
die überlappende und taktsynchrone Verarbeitung von mehreren Befehlen, auch
Pipelining genannt. Betrachtet man die Stufen als einzelne und voneinander
2.4. Pipeline-Mikroarchitektur
67
getrennte Schaltnetze, so wird deutlich, dass bei der bisherigen EinzyklusMikroarchitektur zu jedem Zeitpunkt immer nur eine Stufe verwendet wird,
während die übrigen Stufen ungenutzt bleiben. Effizienter wäre es, jede Stufe
gleichzeitig (für jeweils verschiedene Befehle) zu verwenden wird und somit
alle Teil-Schaltnetze auszulasten. Wenn z. B. der vorherige Befehl gerade geholt
wurde und nun dekodiert wird, könnte bereits der nächste Befehl in die Holephase
eintreten. Ebenso verhält es sich mit den anderen Stufen der Befehlsverarbeitung,
falls diese unabhängig voneinander sind und strikt getrennt werden.
Die Gesamtheit der einzelnen Verarbeitungseinheiten nennt man Pipeline.
Jede einzelne der insgesamt k Stufen wird Pipeline-Stufe oder PipelinePhase genannt, siehe Abbildung 2.14. Die einzelnen Stufen sind aus kombinatorischen Schaltnetzen (Si ) aufgebaut, die jeweils einen Teilschritt in der
Befehlsverarbeitung übernehmen und die i.d.R. aus unterschiedlich vielen Gattern bestehen. Pipeline-Stufen sind durch getaktete Pipeline-Register (auch
Latches genannt) voneinander getrennt, welche die Zwischenergebnisse der vorherigen Stufe aufnehmen und als Eingabe an die jeweils nächste Stufe weitergeben.
Bei Pipeline-Registern handelt es sich um nach außen „nicht-sichtbare“ PufferRegister. Der Compiler oder Programmierer hat auf diese, anders als bei den
Architekturregistern, keinen direkten Zugriff. Die Pipeline-Register werden alle
vom selben Taktsignal gesteuert und arbeiten somit synchron.
Takt
Eingabe
S1
S2
Sk
Ausgabe
Abbildung 2.14: Darstellung einer Pipeline
Die Schaltnetze Si haben je nach Tiefe bzw. Gatter-Anzahl verschiedene
Verzögerungszeiten. Der Taktzyklus (Taktperiode) ergibt sich bei einer PipelineMikroarchitektur durch die Dauer des (zeitlich) längsten Teil-Schaltnetzes
in der Pipeline. Da eine synchrone Verarbeitung der Pipeline-Stufen erfolgt,
muss so lange gewartet werden, bis alle Ergebnisse in den jeweiligen PufferRegistern der Pipeline-Stufen gespeichert wurden. Das Schaltnetz mit der
längsten Verzögerungszeit wird auch kritischer Pfad der Pipeline genannt und
gibt die minimal mögliche Taktperiode vor. Ziel beim Entwurf einer Pipeline
sollte es daher sein, alle Teilschritte möglichst gleich lang zu halten.
Sobald die Pipeline „aufgefüllt“ ist, kann unabhängig von der Stufenzahl
des Pipeline-Prozessors ein Befehl pro Taktzyklus beendet werden. Idealerweise
werden zu jedem Zeitpunkt k Befehle gleichzeitig verarbeitet und jeder Befehl
benötigt k Takte bis zum Verlassen der Pipeline.
68
Latenz
Durchsatz
Beschleunigung
Kapitel 2. Mikroarchitekturen
Für die weiteren Darstellungen wird angenommen, dass ein Prozessor n
Befehle ausführt, die in jeweils k Teilschritte zerlegt werden können.
Man definiert die Latenz einer Pipeline als die Anzahl der Takte, die ein
Befehl benötigt, um alle k Stufen einer Pipeline zu durchlaufen. Bei unserer
Pipeline-Struktur ist die Latenz normalerweise, d. h. ohne weitere Unterbrechungen, k Takte.
Der Durchsatz einer Pipeline wird definiert als die Anzahl der Befehle,
die eine Pipeline pro Takt verlassen können. Dieser Wert spiegelt die Rechenleistung einer Pipeline wieder und liegt unter optimalen Bedingungen7 bei 1.
Ein hypothetischer Prozessor ohne Pipeline benötigt für die Ausführung der n
Befehle insgesamt n · k Takte. Im Gegensatz dazu dauert die Ausführung in einer
k-stufigen Pipeline k + n − 1 Takte (unter der Annahme idealer Bedingungen
mit einer Latenz von k Takten und einem Durchsatz von 1). Dabei werden
k Taktzyklen benötigt, um die Pipeline aufzufüllen bzw. den ersten Verarbeitungsauftrag komplett auszuführen. n − 1 Taktzyklen werden benötigt, um die
restlichen n − 1 Verarbeitungsaufträge (Befehlsausführungen) durchzuführen.
Aus den Werten der Latenz und des Durchsatzes lässt sich der Wert für die
Beschleunigung (Speedup – S) berechnen als:
S=
n·k
=
k+n−1
k
n
k
+1−
1
n
Ist die Anzahl n der mit die Pipeline verarbeiteten Befehle sehr groß, so
wächst die Beschleunigung näherungsweise mit der Anzahl k der Pipeline-Stufen:
lim
n→∞ k
n
k
+1−
1
n
=k
Durch die mittels der überlappten Verarbeitung gewonnenen Parallelität
kann die Verarbeitungsgeschwindigkeit eines Prozessors beschleunigt werden,
indem die Anzahl der Pipeline-Stufen erhöht wird. Durch eine höhere Stufenzahl wird jede einzelne Pipeline-Stufe weniger komplex und benötigt weniger
Gatter für ihre Implementierung. Wegen der geringeren Verzögerungszeiten der
einzelnen Schaltnetze kann die Taktrate der Pipeline erhöht werden. Die Latenz
der Pipeline nimmt mit steigender Anzahl von Pipeline-Stufen hingegen zu. Im
Prinzip kann somit eine lange Pipeline schneller getaktet werden als eine kurze.
Dem steht jedoch die erheblich komplexere Verwaltung gegenüber, die durch die
hohe Anzahl an Pipeline-Stufen und zahlreichen auftretenden Pipeline-Konflikte
benötigt wird. Es muss ein Kompromiss zwischen möglichst vielen Stufen (hohe
Taktrate) und überschaubarem Verwaltungsaufwand gefunden werden.
Heutige Hochleistungsprozessoren kombinieren das Befehls-Pipelining mit
weiteren Mikroarchitektur-Erweiterungen, um einen Durchsatz größer als 1 zu
erreichen. Beispielhaft seien hier die Superskalar- oder VLIW-Techniken zu
nennen, auf welche wir in Kapitel 4 noch genauer eingehen werden.
7
Wie wir später noch sehen werden, können Pipeline-Konflikte zu einer Verringerung des
Durchsatzes führen.
2.4. Pipeline-Mikroarchitektur
2.4.2
Stufen einer Befehls-Pipeline
Betrachten wir nun, in welche Stufen sich die Befehlsabarbeitung unterteilen
lässt. Mit Blick auf die Einzyklus-Mikroarchitektur sind dies:
• Befehl holen
• Befehl dekodieren
• Operanden der ALU bereitstellen
• Operation in der ALU ausführen bzw. Speicherzugriff
• Resultat in Architekturregister zurückschreiben
Da beim Befehls-Pipelining alle Phasen möglichst gleich lang sein sollen,
betrachten wir die einzelnen Phasen noch genauer und versuchen, diese zu
optimieren:
• Ein Zugriff auf den Hauptspeicher benötigt immer eine gewisse Zeitdauer8 .
Würde in der Bereitstellungsphase jeder Befehl einzeln aus dem Speicher
geladen, so würde eine Verzögerung der Pipeline bzw. eine lange Phase entstehen. Um eine Befehlsbereitstellung dennoch in einem Takt zu
gewährleisten, wird ein Code-Cache verwendet, welcher die (wahrschein- Code-Cache
lich) als nächstes benötigten Befehle enthält und auf welchen wesentlich
schneller zugegriffen werden kann als auf den Hauptspeicher.9 Falls bei einem Cache-Fehlzugriff der Befehl dennoch aus dem Speicher geholt werden
muss, wird die Pipeline-Verarbeitung für einige Takte unterbrochen.
• Um die Befehlsdekodierung zu beschleunigen, bietet sich ein einheitliches Befehlsformat und eine geringe Komplexität beim Befehlssatz an.
Insbesondere führen einfache Adressierungsarten zu leicht und schnell dekodierbaren Befehlen. Diese Eigenschaften sind gerade die Charakteristika
einer RISC-Architektur, weswegen die Dekodierung hier besonders schnell
erfolgen kann.
• Vorteilhaft für die Operandenbereitstellung ist eine Register-RegisterArchitektur, bei der die Operanden nur aus den Universalregistern in
die Pipeline-Register übertragen werden müssen. Falls Speicheroperanden
zugelassen werden, wie es für einige CISC-Architekturen der Fall ist, dauert
das Laden der Operanden eventuell mehrere Takte und der Pipeline-Fluss
muss unterbrochen werden.
• Die Befehlsdekodierung ist für Befehlsformate einheitlicher Länge und
Befehle mit geringer Komplexität so einfach, dass sie mit der Operandenbereitstellung zu einer Stufe zusammengefasst werden kann. Die Dekodierung
8
9
Verglichen mit heutigen Prozessoren ca. 100 Takte.
Mehr zu Caches folgt in Kapitel 3.
69
70
Kapitel 2. Mikroarchitekturen
erfolgt durch ein schnelles Schaltnetz, womit auf ein mikroprogrammiertes
Steuerwerk verzichtet werden kann. In der ersten Takthälfte wird dekodiert
und die Register der Operanden werden angesprochen. In der zweiten
Takthälfte werden dann die Operanden aus den Universalregistern in die
Pipeline-Register übertragen.
• Die Ausführungsphase kann für einfache arithmetisch-logische Operationen
in einem Takt durchlaufen werden. Komplexere Operationen wie die
Division oder die Gleitkommaoperationen benötigen mehrere Takte, was
die Organisation der Pipeline erschwert. Eine Ausführung als interne
Pipeline oder die Verwendung von spezialisierten Ausführungseinheiten
bietet sich hier an. Wir kommen in Unterabschnitt 2.4.8 darauf zurück.
Daten-Cache
• Bei Lade- und Speicheroperationen muss zunächst die effektive Adresse
durch die ALU berechnet werden, bevor auf den Speicher zugegriffen werden kann. Der eigentliche Speicherzugriff kann durch einen Daten-Cache
beschleunigt werden, der ähnlich wie der Code-Cache die (wahrscheinlich)
als nächstes benötigten Daten enthält. Im Falle eines Cache-Fehlzugriffs
oder bei Prozessoren ohne Daten-Cache dauert der Speicherzugriff mehrere
Takte und die weitere Befehlsabarbeitung wird verzögert. In der nachfolgend betrachteten MIPS-Pipeline wird nach der Ausführungsphase eine
zusätzliche Speicherzugriffsphase eingeführt, in welcher der Zugriff auf den
Daten-Cache-Speicher erfolgt. Durch die Unterteilung der Ausführungsphase in separate Ausführung und Speicherzugriff wird eine Angleichung
der Verzögerungszeiten mit denen der übrigen Phasen erreicht, wodurch
eine Erhöhung der Taktfrequenz möglich wird.
• Das Rückschreiben des Resultatwerts in ein Register des Registersatzes
kann sehr schnell erfolgen. Es wird daher bereits in der ersten Takthälfte der Rückschreibephase durchgeführt und ermöglicht uns, wie wir
noch sehen werden, die Einsparung eines Leertaktes bei einigen PipelineKonflikten.
• Die Befehlshole- und Speicherzugriffs-Phase sind die einzigen Phasen, in
der ein Speicherzugriff erfolgen kann (jeweils für Code oder Daten). Die
Bereitstellung der Operanden und das Rückschreiben der Resultate wird
grundsätzlich nur aus bzw. in die Universalregister ermöglicht. Hierbei ist,
wie bereits erwähnt, eine RISC- und Register-Register-Architektur von
Vorteil.
2.4.3
Die MIPS-Pipeline
Kommen wir nun zu einem Beispiel für das Befehls-Pipelining und erweitern
die oben eingeführt MIPS-Mikroarchitektur um eine überlappende Befehlsverarbeitung.
2.4. Pipeline-Mikroarchitektur
71
Die Unterteilung wird dabei in fünf Teilschritte bzw. fünf Pipeline-Stufen
erfolgen:
MIPS-Pipeline• Befehlsbereitstellungs- oder IF-Phase (Instruction Fetch): Der Be- Stufen
fehl, der durch den Befehlszähler adressiert ist, wird aus dem Code-Cache
in einen Befehlspuffer, das sogenannte Befehlsregister, geladen. Der Befehlszähler wird anschließend um 4 erhöht. Bei einem Cache-Fehlzugriff
erfolgt das Nachladen aus dem Hauptspeicher.
• Dekodier- und Operandenbereitstellungsphase oder ID-Phase (Instruction Decode/Register Fetch): Aus dem Operationscode des Maschinenbefehls werden durch ein Dekodierschaltnetz Pipeline-interne Steuersignale
erzeugt. Die Operanden werden aus den Universalregistern des Registersatzes bereit gestellt und die Vorzeichenerweiterung findet statt.
• Ausführungs- oder EX-Phase (Execute/Address Calculation): Die Operation wird von der ALU ausgeführt. Bei Lade-/Speicherbefehlen wird die
effektive Adresse berechnet, bei Sprungbefehlen die Sprungzieladresse.
• Speicherzugriffs- oder MEM-Phase (Memory Access): Der Speicherzugriff auf den Daten-Cache wird durchgeführt.
• Resultatspeicher- oder WB-Phase (Write Back ): Das Ergebnis wird
in ein Universalregister des Registersatzes geschrieben.
Der zeitliche Ablauf der Befehlsverarbeitung ist in Abbildung 2.15 dargestellt. Zu sehen sind fünf Befehle, die jeweils um einen Takt versetzt in die
Pipeline geladen wurden und sich nun (grau hinterlegt) im gleichen Taktzyklus
in verschiedenen Phasen der Abarbeitung befinden. Während sich der erste
Befehl I1 schon in der WB-Phase befindet, wird der Befehl I5 gerade erst in der
IF-Phase geladen.
In Abbildung 2.16 wird gezeigt, wie die Einzyklus-Mikroarchitektur zu einer Pipeline-Mikroarchitektur erweitert werden kann. Zum Vergleich sollten
Sie die ursprüngliche Einzyklus-Mikroarchitektur aus Abbildung 2.10 hinzuziehen. Der Übersichtlichkeit halber führen wir nur die für eine Einführung
relevanten Ausschnitte der Pipeline auf und lassen u. a. die Steuereinheit mit
den entsprechenden Steuersignalen weg. Der interessierte Leser sei hier auf [2]
verwiesen.
Der Hauptunterschied besteht darin, dass die einzelnen Stufen durch verschiedene Pipeline-Register getrennt werden, welche die Zwischenergebnisse der
vorherigen Stufe aufnehmen und an die nächste Stufe weiterleiten. Wie bereits
erwähnt, beschränken wir uns hier nur auf die für das Verständnis wesentlichen
Puffer-Register.
• In der ersten Phase (IF) wird ein Befehl von der Bereitstellungseinheit
(bestehend aus Befehlszähler, Code-Cache, Addierer) in die Pipeline geladen. Hierzu wird der Befehl, auf den der Befehlszähler zeigt, aus dem
72
Kapitel 2. Mikroarchitekturen
Taktzyklen
I1
I2
I3
I4
I5
IF
ID
EX MEM WB
IF
ID
EX MEM WB
IF
ID
EX MEM WB
IF
ID
EX MEM WB
IF
ID
EX MEM WB
5-stufig
Abbildung 2.15: Gefüllte MIPS-Pipeline während der Befehlsverarbeitung.
Code-Cache geladen und in das Befehlsregister Ir übertragen. Bei einem
Cache-Fehlzugriff erfolgt ein Zugriff auf den Hauptspeicher. Der Befehlszähler wird um den Wert 4 erhöht, um auf den im linearen Programmverlauf
nächsten Befehl zu verweisen. Der erhöhte Wert des Befehlszählers wird
außerdem über das PC+4-Register an die nächste Stufe übergeben, da im
Falle eines Sprungbefehls dieser Wert für die Berechnung des Sprungziels
in der EX-Stufe verwendet wird. Im Falle eines vorangegangenen Sprungbefehls wird die Zieladresse aus der MEM-Stufe als neuer Wert in den
Befehlszähler übernommen.
• In der ID-Stufe wird der Befehl aus dem Befehlsregister entnommen
und in der ersten Takthälfte dekodiert. Die Steuereinheit erzeugt die
für die weitere Verarbeitung benötigten Steuersignale. In der zweiten
Takthälfte werden die Operanden aus dem Registersatz geladen und in
den ALU-Eingaberegistern Ai1 und Ai2 gespeichert. Außerdem findet die
Vorzeichenerweiterung des unmittelbaren Operanden auf 32 Bit statt und
wird in das Register Im übertragen. Der Wert des Befehlszählers PC+4
wird ebenfalls an die nächste Stufe weitergereicht.
• In der EX-Stufe wird die eigentliche Operation in der ALU ausgeführt
und bei einem Sprungbefehl die Sprungzieladresse berechnet. Als EingabeOperanden für die ALU werden der Wert aus Ai1 und je nach Befehl und
Steuersignal der Wert aus Ai2 oder Im genutzt. Das Ergebnis wird im
ALU-Ausgaberegister Ao gespeichert. Bei einem Speicherbefehl wird die
effektive Adresse in das Register Ao übertragen und ein zu speichernder
Wert aus Ai2 ohne Umweg über die ALU in das Speicherwert-Register Sv
2.4. Pipeline-Mikroarchitektur
Abbildung 2.16: Aufbau der MIPS-Pipeline-Mikroarchitektur
73
74
Kapitel 2. Mikroarchitekturen
übernommen. Bei einem Sprungbefehl wird der Wert des unmittelbaren
Operanden Im zum Wert von PC+4 addiert und in das Sprungziel-Register
Jta übernommen. Der Zielregister-Selektor wird in das Register Rt gespeichert und in den weiteren Phasen bis zur WB-Stufe mitgeführt.
• Die MEM-Stufe wird für Lade-, Speicher- und Sprungbefehle genutzt. Bei
einem Speicherbefehl wird der Inhalt des Speicherwert-Registers Sv an
die von der ALU berechneten effektiven Adresse aus Ao in den Speicher
geschrieben. Bei Ladebefehlen wird das Speicherwort an der effektiven
Adresse geladen und in das Ladewert-Register Lv übertragen. Bei einem
Sprungbefehl wird die berechnete Sprungziel-Adresse aus Jta in den
Befehlszähler übertragen. Liegt ein bedingter Sprung vor, so wird zuvor
noch die Auswertung der Sprungbedingung in der ALU abgewartet und
dann je nach Ergebnis gesprungen. Bei allen übrigen Befehlen wird in
dieser Phase nichts weiter gemacht, als den Wert des ALU-Ausgaberegister
Ao in das ALU-Ergebnisregister Ar zu übertragen.
• In der letzten Stufe (WB-Phase) wird abhängig vom Befehl entweder der
Wert des ALU-Ergebnisregister oder der des Ladewert-Registers in ein
Universalregister übertragen. Hierfür kann über einen Multiplexer der
entsprechende Kanal aktiviert werden. Der Registerziel-Selektor wird aus
dem Rt-Register übernommen.
Diese Pipeline-Architektur ist grundsätzlich in der Lage, eine überlappende
Verarbeitung zu ermöglichen. Es können allerdings mehrere potentielle Konfliktsituationen entstehen, die eine reibungslose Befehlsabarbeitung stören. Wenn z. B.
sowohl Befehle als auch Daten in der IF- bzw. MEM-Stufe aus dem Hauptspeicher nachgeladen werden müssen und dieser nur einen Speicherkanal (Memory
Port) besitzt, so kommt es zu einem Speicher-Lesekonflikt auf den Hauptspeicher.
In diesem Fall muss die Pipeline für einen der beiden konkurrierenden Befehle
angehalten werden, bis der Speicherkanal wieder zur Verfügung steht. Auch
gehen wir implizit davon aus, dass der Registersatz einen Schreibkanal und zwei
Lesekanäle besitzt, sodass gleichzeitig sowohl zwei Operanden in der ID-Phase
geholt werden als auch das Resultat in der WB-Phase zurückgeschrieben werden
kann. Entschärft wird dieses Problem, indem Operanden grundsätzlich in der
ersten Taktphase geschrieben (WB-Stufe) und in der zweiten Taktphase geholt
werden (ID-Stufe).
Darüber hinaus können weitere Konflikte in der Pipeline bestehen, auf die
wir im folgenden Unterabschnitt genauer eingehen.
2.4.4
Pipeline-Konflikte
Als Pipeline-Konflikt bezeichnet man die Unterbrechung des taktsynchronen
Durchlaufs der Befehle durch die einzelnen Stufen der Befehls-Pipeline. PipelineKonflikte werden durch Daten- und Steuerflussabhängigkeiten im Programm
2.4. Pipeline-Mikroarchitektur
75
oder durch die Nichtverfügbarkeit von Ressourcen (Ausführungseinheiten, Register etc.) hervorgerufen. Diese Abhängigkeiten könnten, falls sie nicht erkannt und
behandelt werden, zu fehlerhaften Datenzuweisungen führen. Die Situationen, die
zu Pipeline-Konflikten führen können, werden auch als Pipeline-Hemmnisse
(Pipeline Hazards) bezeichnet.
Es werden drei Arten von Pipeline-Konflikten unterschieden:
Pipeline-Konflikte
• Datenkonflikte treten auf, wenn ein Operand in der Pipeline (noch)
nicht verfügbar ist oder das Register bzw. der Speicherplatz, in den ein Resultat geschrieben werden soll, noch von einem vorherigem Befehl genutzt
wird. Datenkonflikte werden durch Datenabhängigkeiten im Befehlsstrom
erzeugt.
• Steuerflusskonflikte treten bei Programmsteuerbefehlen auf, wenn in
der Befehlsbereitstellungsphase die Zieladresse des als nächstes auszuführenden Befehls noch nicht berechnet ist bzw. im Falle eines bedingten
Sprunges noch nicht klar ist, ob überhaupt gesprungen wird.
• Struktur- oder Ressourcenkonflikte treten auf, wenn zwei PipelineStufen dieselbe Ressource benötigen, auf diese aber nur einmal zugegriffen
werden kann bzw. diese nur einmal vorhanden ist (z. B. gleichzeitiger
Speicherzugriff in mehreren Phasen).
Lösungen für Pipeline-Konflikte können generell auf zwei verschiedenen
Ebenen ansetzen. Einmal auf der Software-Ebene, d. h. durch den Compiler
oder Programmierer. Hierbei wird vorab (statisch) beim Übersetzen geprüft,
wo Abhängigkeiten im Programmcode bestehen und wie diese aufgelöst werden
können. Die andere Möglichkeit sind Hardware-Lösungen, welche die PipelineKonflikte dynamisch zur Laufzeit erkennen und beheben.
In den nächsten Unterabschnitten werden die drei Pipeline-Konflikt-Arten
und deren Lösungsmöglichkeiten näher diskutiert.
2.4.5
Datenkonflikte und deren Lösungsmöglichkeiten
Man betrachte zwei Befehle (Instructions) I1 und I2 , wobei I1 vor I2 ausgeführt
wird. Zwischen diesen Befehlen können verschiedene Arten von Datenabhängigkeiten bestehen, je nachdem, wann auf welche Register (oder Speicherstellen10 )
lesend bzw. schreibend zugegriffen wird. Eine Datenabhängigkeit kann zu einem
Datenkonflikt führen, falls es durch die Pipeline zu einer falschen Zugriffsreihenfolge auf Register und damit einem fehlerhaften Ablauf des Programms
kommt. Eine Datenabhängigkeit zwischen zwei Befehlen I1 und I2 existiert
unabhängig von der Anzahl an konkreten Pipelinestufen und der Anzahl an
Befehlen, die zwischen den beiden Befehlen I1 und I2 im Programm vorkommen.
Ob letztendlich aus einer Datenabhängigkeit auch ein Datenkonflikt folgt, wird
erst bei Betrachtung der konkreten Pipeline bzw. des Programmverlaufs deutlich.
10
Im Weiteren betrachten wir Datenkonflikte nur auf beim Zugriff auf Register.
76
Kapitel 2. Mikroarchitekturen
Liegen die Befehle so nah beieinander bzw. gibt es genügend viele Stufen in der
Pipeline, so kommt es zum Datenkonflikt.
Datenabhängigkeiten
Es gibt generell drei Arten von Datenabhängigkeiten , die zwischen zwei
Befehlen I1 und I2 existieren können:
RAW
• Es besteht eine echte Datenabhängigkeit (True Dependency) zwischen
I1 und I2 , wenn I1 seine Ausgabe in ein Register schreibt, welches von I2 als
Eingaberegister verwendet wird. Eine echte Datenabhängigkeit verursacht
einen Lese-nach-Schreibe-Konflikt (Read After Write – RAW).
WAR
• Es besteht eine Gegenabhängigkeit (Anti Dependency) zwischen I1 und
I2 , wenn I1 Daten aus einem Register liest, welches anschließend von I2
überschrieben wird. Durch eine Gegenabhängigkeit wird ein Schreibenach-Lese-Konflikt (Write After Read – WAR) verursacht.
WAW
• Es besteht eine Ausgabeabhängigkeit (Output Dependency) zwischen I1
und I2 , wenn beide Befehle in das gleiche Register zurückschreiben. Durch
eine Ausgabeabhängigkeit wird ein Schreibe-nach-Schreibe-Konflikt
(Write After Write – WAW) verursacht.
Datenabhängigkeiten können in einem Abhängigkeitsgraphen dargestellt
werden, dessen Knoten aus den Befehlen des Programms bestehen. Zwischen
datenabhängigen Befehlen werden dann gerichtete Kanten vom abhängigen zum
vorherigen Befehl eingezeichnet. Die Kanten werden mit Abkürzungen für die
jeweilige Datenabhängigkeit gekennzeichnet (ED – echte Datenabhängigkeit, GA
– Gegenabhängigkeit, AA – Ausgabeabhängigkeit). In einem Beispiel betrachten
wir folgende Befehlsfolge, dessen Abhängigkeitsgraph in Abbildung 2.17 zu
sehen ist:
I1:
I2:
I3:
I4:
addi
add
subi
subi
$t1,
$t4,
$t3,
$t3,
$t2,
$t1,
$t5,
$t6,
2
$t3
3
3
In diesem Fall bestehen folgende Datenabhängigkeiten:
• eine echte Datenabhängigkeit zwischen I1 und I2 , da I2 den Wert von
Register $t1 benutzt, der zuvor in I1 berechnet wurde.
• zwei Gegenabhängigkeiten zwischen I2 und I3 bzw. I2 und I4 , da sowohl
I3 als auch I4 in das Register $t3 schreiben, welches zuvor von I2 lesend
verwendet wurde.
• eine Ausgabeabhängigkeit zwischen I3 und I4 , da beide Befehle in das
gleiche Register $t3 zurückschreiben.
2.4. Pipeline-Mikroarchitektur
77
I1
ED
I2
GA
GA
I3
AA
I4
Abbildung 2.17: Abhängigkeitsgraph.
Gegenabhängigkeiten und Ausgabeabhängigkeiten werden häufig auch falsche
bzw. scheinbare Datenabhängigkeiten oder, entsprechend dem englischen
Begriff Name Dependency, auch Namensabhängigkeiten genannt. Diese Arten von Datenabhängigkeiten sind nicht problemimmanent, sondern werden
durch die Mehrfachnutzung von Speicherplätzen (in Registern oder im Arbeitsspeicher) hervorgerufen. Sie können durch Variablenumbenennungen oder
-umordnungen entfernt werden, was in Kapitel 4 des Kurses noch genauer gezeigt
wird.
Echte Datenabhängigkeiten werden häufig auch einfach als Datenabhängigkeiten bezeichnet. Sie repräsentieren den eigentlichen Datenfluss durch ein
Programm.
Die ersten drei Datenabhängigkeiten in Abbildung 2.17 erzeugen Datenkonflikte in der Reihenfolge RAW, WAR, WAW. Datenkonflikte werden zwar durch
Datenabhängigkeiten hervorgerufen, ihr Auftreten ist jedoch wesentlich von der
internen Pipeline-Struktur abhängig. Sind die datenabhängigen Befehle weit
genug voneinander entfernt sind, so wird es zu keinem Datenkonflikt kommen.
Wie weit die Befehle voneinander entfernt sein müssen, hängt davon ab, wie
viele Stufen die Pipeline hat und welcher Mikroschritt der Befehlsabarbeitung
in den jeweiligen Pipeline-Stufen durchgeführt wird.
WAR-Konflikte können nur dann in einer Pipeline auftreten, wenn die Befehle
sich bereits vor der Operandenbereitstellung überholen können. Das heißt, ein
WAR-Konflikt tritt auf, wenn ein nachfolgender Befehl sein Resultat bereits
78
Kapitel 2. Mikroarchitekturen
in ein Register schreibt, bevor der in Programmreihenfolge vorherige Befehl
den Registerinhalt als Operanden liest. Dieser Fall ist in der MIPS-Pipeline
ausgeschlossen, muss jedoch für die Pipelines moderner Superskalarprozessoren
bedacht werden.
WAW-Konflikte treten in Pipelines auf, die einen Register-Schreibzugriff
in mehr als einer Stufe erlauben oder die es einem Befehl ermöglichen, in der
Pipeline-Verarbeitung fortzufahren, obwohl ein vorhergehender Befehl angehalten worden ist. In der einfachen MIPS-Pipeline ist das Schreiben in die Register
nur in der WB-Stufe möglich und das „Überholen“ von Befehlen ausgeschlossen.
Diese Fälle sind deshalb nicht weiter beachtenswert, werden in Kapitel 4 bei
der Superskalarität aber wichtig.
Für einfache skalare Pipelines, wie die MIPS-Pipeline, sind nur echte Datenabhängigkeiten von Bedeutung, da die Befehle stets in-order ausgeführt werden.
Für Superskalarprozessoren, bei denen sich Befehle auch gegenseitig überholen
können, müssen zusätzlich Gegen- und Ausgabeabhängigkeiten beachtet werden.
Betrachten wir die für die MIPS-Pipeline relevanten echten Datenabhängigkeiten genauer: Wir nehmen erneut eine Folge von Register-Register-Befehlen
I1 und I2 an, bei der der Befehl I2 im Programm auf I1 folgt und bei der die
beiden Befehle echt-datenabhängig voneinander sind. Nehmen wir an, dass
der Ergebnistransfer von I1 zu I2 über das Register $t1 geschieht. In einer
Mikroarchitektur ohne Pipeline kommt es hier nicht zu einem Konflikt, da
der Befehl I2 erst in die IF-Stufe eintritt, wenn der Befehl I1 die WB-Stufe
bereits verlassen hat. Das Ergebnis von I1 steht zu diesem Zeitpunkt bereits
im Register $t1 bereit. Der eigentliche Zugriff von I2 auf $t1 erfolgt sogar noch
später, nämlich in der ID-Stufe. Bei einer Mikroarchitektur mit Pipeline kann
es jedoch vorkommen, dass sich I2 schon in der ID-Stufe befindet, während I1
noch in der EX-Stufe ist. Dies ist der Fall, wenn die Befehle I1 und I2 direkt
aufeinander im Programmcode folgen und keine sonstigen Verzögerungen wie
Leerbefehle auftreten. Das Ergebnis von I1 wird hierbei erst zwei Takte später
in der WB-Stufe in das Register zurückgeschrieben. I2 liest, falls nichts weiter
unternommen wird, den alten und somit falschen Inhalt von $t1. Abbildung 2.18
verdeutlicht dieses Problem: in Teil a) ohne Pipeline führt die Datenabhängigkeit
nicht zu einem Datenkonflikt. In Teil b) führt die Datenabhängigkeit zu einem
Lese-nach-Schreibe-Konflikt, da Befehl I2 bereits lesend auf $t1 zugreift, bevor
I1 dies schreibend tut.
Datenkonflikte müssen erkannt und behandelt werden, da es sonst zu einer
falschen Programmausführung kommen kann. Zur Behandlung von Datenkonflikten in einer Pipeline gibt es verschiedene Möglichkeiten.
Software-Lösungen von Datenkonflikten:
Eine Behandlung von Pipeline-Konflikten durch Software hat zur Folge,
dass die Pipeline-Organisation der Architektur offengelegt und somit dem
Compiler bzw. Programmierer bekannt gemacht werden muss. Es kann dabei
nicht von einer korrekten Ausführung datenabhängiger Befehle ausgegangen
2.4. Pipeline-Mikroarchitektur
79
Taktzyklen
I1
a)
IF
ID
I2
EX MEM WB
IF
$t1
ID
EX MEM WB
$t1
I1
b)
IF
ID
IF
EX MEM WB
$t1
$t1
ID
EX MEM WB
I2
Abbildung 2.18: Datenkonflikt aufgrund einer echten Datenabhängigkeit bei (a)
Mehrzyklen- Mikroarchitektur und (b) Pipeline-Mikroarchitektur.
werden, sondern die Korrektheit muss vorab im Programmablauf sichergestellt
werden. Auf Software-Ebene gibt es dazu zwei verschiedene Möglichkeiten:
• Einfügen von Leerbefehlen : Der Compiler untersucht den Programmcode Leerbefehle
auf (echte) Datenabhängigkeiten zwischen Befehlen und fügt entsprechend Leerbefehle ein, bis der Abstand zwischen zwei datenabhängigen
Befehlen groß genug ist, um Datenkonflikte zu verhindern. Diese auch
als Nullanweisung bzw. nop (no operation) bezeichneten Befehle haben
keine Auswirkungen auf das Programm, sondern erzeugen lediglich eine
Verzögerung von einem Takt. Das Einfügen von Leerbefehlen verringert
den effektiven Durchsatz einer Pipeline, da das Programm um zusätzliche
(eigentlich nicht benötigte) Befehle erweitert wird.
• Statische Befehlsumordnung : Anstatt neue Leerbefehle einzufügen, exis- Statische
tiert für den Compiler noch eine weitere Möglichkeit, um mit Datenab- Befehlsumordnung
hängigkeiten umzugehen. Erkennt der Compiler eine Datenabhängigkeit
zwischen den Befehlen I1 und I2 , welche aufgrund der Pipeline-Struktur
zu einem Datenkonflikt führt, so erweitert er den Abstand zwischen den
Befehlen anstelle durch neue Leerbefehle durch bereits im Programmcode
vorhandene Befehle. Die statische Befehlsumordnung ermöglicht es, die
80
Kapitel 2. Mikroarchitekturen
Befehlsreihenfolge in einem Programm zu ändern, ohne dabei das Ergebnis der Programmausführung zu beeinflussen. Der Compiler sucht dafür
Befehle, die voneinander unabhängig sind und die für eine Umordnung
verwendet werden können. Befehle, die eigentlich vor I1 stehen, können so
erst nach I1 ausgeführt werden. Der Befehl I1 wird dadurch „nach vorne
verschoben“. Die andere Möglichkeit wäre es, den Befehl I2 „nach hinten
zu schieben“, d. h. spätere Befehle zuerst auszuführen. In beiden Fällen
wird der Abstand zwischen I1 und I2 vergrößert. Können keine oder keine
weiteren unabhängigen Befehle mehr gefunden werden, so muss der Compiler auf das Einfügen von Leerbefehlen zurückgreifen. Im besten Fall kann
bei der statischen Befehlsumordnung komplett auf Leerbefehle verzichtet
werden, woraus eine hohe Effizienz der Pipeline resultieren würde. In der
Realität wird aber, aufgrund häufig vorkommender Datenabhängigkeiten,
keine so hohe Effizienz erreicht werden können und es muss auf Leerbefehle
zurückgegriffen werden.
Hardware-Lösungen von Datenkonflikten:
Hardware-Lösungen beruhen auf einer Behandlung von Datenkonflikten zur
Laufzeit und erhöhen die Komplexität der Pipeline-Organisation. Grundsätzlich
können folgende Hardware-Lösungen unterschieden werden:
Interlocking
• Leerlauf der Pipeline : Die einfachste Art, dynamisch mit einem Datenkonflikt zwischen I1 und I2 umzugehen, ist das Anhalten des Befehls I2 für
zwei Takte. Sobald eine Datenabhängigkeit zwischen einem Befehl I2 in der
ID-Stufe11 zu einem vorherigen Befehl I1 erkannt wurde, wird der Befehl
I2 angehalten. Dieses Vorgehen bezeichnet man auch als Pipeline-Sperrung
(Interlocking) oder Pipeline-Leerlauf (Stalling). Durch das Stoppen entsteht eine Verzögerung, auch Pipeline-Blase (Pipeline Bubble) genannt. Die
Wirkung ist dabei dieselbe, die ein vorab eingefügter Leerbefehl erzeugen
würde. Wir gehen hier davon aus, dass das Rückschreiben des Ergebnisses
in der WB-Stufe in der ersten Takthälfte erfolgt und der Operand den
nachfolgenden Befehlen in der zweiten Hälfte der ID-Stufe bereitsteht.
Ansonsten müsste die Pipeline für drei anstatt zwei Takte angehalten
werden. Für unser obiges Beispiel ergibt sich dann die in Abbildung 2.19.
dargestellte Situation.
Forwarding
• Forwarding: Die zweite Hardware-Lösung ist effizienter, erfordert allerdings auch einen höheren Hardware-Aufwand. Wenn ein Datenkonflikt
zwischen I1 und I2 erkannt wird, so sorgt eine Hardwareschaltung dafür,
dass der entsprechende Operand nicht aus dem Architekturregister, sondern direkt aus dem ALU-Ausgaberegister geladen wird. Das Ergebnis
steht nachfolgenden Befehlen schon bereit, nachdem I1 die EX-Phase
durchlaufen hat. Nachfolgende Befehle müssen nicht mehr warten, bis I1
11
Dies ist der frühstmögliche Zeitpunkt, an dem eine Datenabhängigkeit erkannt werden
kann.
2.4. Pipeline-Mikroarchitektur
81
Taktzyklen
I1
IF
ID
EX MEM WB
$t1
Verzögerung (2 Takte)
IF
ID
EX MEM WB
I2
Abbildung 2.19: Interlocking als Hardware-Lösung
die WB-Phase durchlaufen hat. In unserem Beispiel beseitigt das Forwarding beide benötigten Leerbefehle, siehe Abbildung 2.20.
Taktzyklen
I1
IF
ID
EX MEM WB
IF
ID
EX MEM WB
I2
Abbildung 2.20: Forwarding als Hardware-Lösung
• Forwarding mit Interlocking: Forwarding beseitigt zwar Datenabhängigkeiten zwischen Register-Register-Befehlen, nicht jedoch bei Lade/Speicherbefehlen. Angenommen, der Befehl I1 ist ein Ladebefehl und lädt
einen Wert in das Register $t1. Das Forwarding vom ALU-Ausgaberegister
wäre hier falsch, da die EX-Stufe nicht den zu ladenden Wert berechnet,
sondern die effektive Adresse des nachfolgenden Speicherzugriffs. Angenommen I1 und I2 wären datenabhängig, indem I2 lesend auf $t1 zugreift.
I2 muss dann so lange in der Pipeline angehalten werden, bis das Ergebnis
des Ladebefehls I1 am Ende der MEM-Stufe bereit steht. Anschließend
kann Forwarding genutzt werden, um das Ergebnis bereits vor der WBStufe zur Verfügung zu stellen. Bei Lade-/Speicherbefehlen muss zwingend
82
Kapitel 2. Mikroarchitekturen
ein Leerbefehl eingefügt werden, siehe Abbildung 2.21 a) für das Problem
und b) für die Lösung mit einer Verzögerung von einem Takt.
Es bleibt zu klären, wie Forwarding in der Pipeline implementiert werden
kann. In der einfachen MIPS-Pipeline genügt es, die Register-Zieladressen
der beiden Befehle, die sich in der EX- und in der MEM-Phase befinden,
mit den Register-Quelladressen des Befehls in der ID-Stufe zu vergleichen.
Kommt es zu einer Übereinstimmung, dann wird ein entsprechender Pfad
geschaltet, der den Wert des ALU-Ausgaberegisters (EX-Stufe) bzw. des
Ladewertregisters (MEM-Stufe) in das ALU-Eingaberegister (ID-Stufe)
übernimmt. Der Wert in dem eigentlich adressierten Universalregister
wird nicht weiter beachtet.
Taktzyklen
I1
a)
IF
ID
IF
EX MEM WB
$t1
$t1
ID
EX MEM WB
I2
I1
b)
IF
ID
EX MEM WB
Verzögerung (1 Takt)
IF
ID
EX MEM WB
I2
Abbildung 2.21: Forwarding mit Interlocking
Dynamische
Befehlsumordnung
• Dynamische Befehlsumordnung: Bei der dynamischen Befehlsumordnung
wird, wie bei der statischen Befehlsumordnung, die Befehlsreihenfolge
geändert (soweit dies möglich ist), um so Datenkonflikte aufzulösen. Der
Unterschied besteht nun darin, dass die Umordnung nicht vorab durch den
Compiler, sondern dynamisch während der Laufzeit durch die Hardware
2.4. Pipeline-Mikroarchitektur
83
erfolgt. Die einfache MIPS-Pipeline bietet keine solche Möglichkeit, da
Befehle immer einzeln geholt und dekodiert werden. Eine dynamische
Befehlsumordnung erfordert es, dass mehrere Befehle auf einmal geholt
werden, um diese dann in eine andere Reihenfolge zu bringen. Dies ist
z. B. bei einem Superskalarprozessor der Fall, der in Kapitel 4 besprochen
wird. Hierbei entsteht allerdings ein erheblicher Zusatzaufwand in der
Hardware.
2.4.6
Steuerflusskonflikte und deren Lösungsmöglichkeiten
Zu den Programmsteuerbefehlen gehören die bedingten und unbedingten Sprungbefehle, die Unterprogrammaufruf- und -rückkehrbefehle sowie die Unterbrechungsbefehle, die per Software Unterbrechungsroutinen aufrufen bzw. aus einer
solchen Routine zurückkehren. Abgesehen von einem nicht genommenen bedingten Sprung erzeugen alle diese Befehle eine Steuerflussänderung, da nicht der
nächste im Speicher bzw. Befehls-Cache stehende Befehl, sondern ein Befehl
an einer Zieladresse geholt und ausgeführt werden muss. Damit ergibt sich
eine Steuerflussabhängigkeit zu dem in der Speicheranordnung nächsten Befehl
des Programms. Steuerflussabhängigkeiten verursachen Steuerflusskonflikte Steuerflusskonflikte
in der MIPS-Pipeline, da der Programmsteuerbefehl erst in der ID-Stufe als
solcher erkannt wird und im Falle eines genommenen Sprungs ein Befehl des
falschen (linearen) Programmpfades in die Pipeline geladen wurde. Darüber
hinaus muss erst die Sprungzieladresse in der ALU berechnet werden, sodass
erst deutlich später feststeht, ob die in der linearen Befehlsfolge oder die ab der
Sprungzieladresse folgenden Befehle dem korrekten Programmpfad entsprechen.
Eine Besonderheit stellen die bedingten Sprungbefehle (Verzweigungen) dar, da
bei diesen Befehlen die Änderung des Programmflusses zusätzlich noch von der
Auswertung einer Sprungbedingung abhängt.
Sei I1 , I2 , I3 , I4 . . . eine Befehlsfolge, die in dieser Reihenfolge im Speicher
steht und nacheinander in die Pipeline geladen wird. Wir nehmen an, dass I1
ein unbedingter Sprung sei, bei dem die Sprungzieladresse zunächst berechnet
werden muss. Durch die Dauer der Befehlsverarbeitung über mehrere Phasen
entstehen Steuerflussabhängigkeiten zu den nachfolgenden Befehlen. Die Sprungzieladresse wird erst in der EX-Stufe berechnet und ersetzt den PC anschließend
in der MEM-Stufe. Zu diesem Zeitpunkt befindet sich der Befehl I2 bereits in der
EX-Stufe, I3 in der ID-Stufe und I4 in der IF-Stufe. Unter der Annahme, dass
die Sprungzieladresse nicht auf I2 verweist, müssen die vorher falsch geladenen
Befehle I2 , I3 , I4 gelöscht und der richtige Befehl (IB ) an der Sprungzieladresse
geladen werden.
Steuerflusskonflikte treten außerdem auf, wenn I1 ein bedingter Sprung ist.
Neben der Berechnung der Sprungzieladresse erfolgt zusätzlich noch die Auswertung der Sprungbedingung, d. h. es wird geprüft, ob überhaupt gesprungen wird
oder nicht. Diese Prüfung erfolgt ebenfalls in der EX-Phase, weshalb die Sprungrichtung frühstens in der MEM-Phase bereitsteht. Wird der Sprung genommen,
84
Kapitel 2. Mikroarchitekturen
ersetzt die Sprungzieladresse ebenfalls wieder den PC und die vorherigen falsch
geladenen Befehle müssen verworfen werden.
In beiden Fällen (bedingter oder unbedingter Sprung) erfolgt die Abarbeitung
der korrekten Befehlsfolge mit einer Verzögerung von drei Takten, da zunächst
drei Befehle des falschen Befehlspfades in die verschiedenen Pipeline-Stufen
geladen wurden, siehe Abbildung 2.22.
Taktzyklen
I1
I2
I3
I4
IF
ID
EX
MEM
WB
IF
ID
EX
MEM
WB
IF
ID
EX
MEM
WB
IF
ID
EX
MEM
WB
IF
ID
EX
MEM
PC
Verzögerung (3 Takte)
IB
WB
Abbildung 2.22: Verzögerung bei einem genommenen Sprung.
Um die Anzahl der Wartezyklen zu mindern, sollten die Sprungrichtung und
die Sprungzieladresse in der Pipeline so früh wie möglich berechnet werden. Eine
Möglichkeit wäre es, die Berechnungen bereits in der ID-Stufe vorzunehmen, d. h.
direkt nachdem der Befehl als Sprungbefehl erkannt wurde. Die ALU der EXStufe kann dann nicht länger für die Auswertung der Sprungbedingung verwendet
werden, da sie noch von dem vorhergehenden Befehl benötigt wird. In diesem
Fall würde es zu einem Strukturkonflikt kommen, weil zwei verschiedene Phasen
gleichzeitig auf die ALU zugreifen. Lösen könnte man diesen Strukturkonflikt,
indem eine zusätzliche Adressberechnungs-ALU in die ID-Stufe hinzugefügt wird,
die sowohl Sprungziel als auch Sprungrichtung berechnet. Das Zurückschreiben
der Zieladresse in den PC könnte dann bereits am Ende der ID-Stufe erfolgen
(falls der Sprung genommen wird) und die Verzögerung würde sich auf einen
Takt verringern. Allerdings entsteht nun ein neuer, nicht behebbarer PipelineKonflikt. Ein ALU-Befehl gefolgt von einem bedingten Sprung, der vom Ergebnis
des ALU-Befehls abhängt, wird einen Konflikt mit Verzögerung verursachen,
auch wenn das Ergebnis von der EX- in die ID-Stufe weitergeleitet wird. Das
Hauptproblem bei einer zusätzlichen Adressberechnungs-ALU ist jedoch, dass die
2.4. Pipeline-Mikroarchitektur
85
Dekodierung, die Sprungberechnung und das Rückschreiben des PC sequenziell
in einer einzigen Pipeline-Stufe ausgeführt werden müssen. Dies kann zu einem
kritischen Pfad in der Dekodierstufe führen, der die Taktfrequenz der gesamten
Pipeline reduziert. Steuerflusskonflikte können sowohl in Software als auch direkt
in der Hardware behandelt werden.
Software-Lösungen von Steuerflusskonflikten:
Bei der Software-Lösung werden immer die nächsten drei Befehle, die auf
einen Sprungbefehl folgen, in die Pipeline geladen. Sie bilden die so genannten
Verzögerungszeitschlitze (Delay Slots) . Auch hier wird, wie beim Nichterkennen von Datenkonflikten, die Pipeline-Implementierung von Programmsteuerbefehlen zur Architektur hin offen gelegt. Compiler oder Assemblerprogrammierer
müssen die Steuerflusskonflikte bereits im Voraus beheben, damit eine korrekte Programmausführung gewährleistet bleibt. Software-Lösungen können auf
verschiedenen Techniken beruhen:
• Verzögerte Sprungtechnik : Eine einfache Methode , um eine korrekte Verzögerte
Programmausführung ohne Hardware-Änderungen herzustellen, ist das Sprungtechnik
Auffüllen der Verzögerungszeitschlitze mit Leerbefehlen. Im Falle der
MIPS-Pipeline müssen durch den Compiler nach jedem Programmsteuerbefehl drei Leerbefehle eingefügt werden.
• Statische Befehlsumordnung: Der Compiler füllt den/die Verzögerungszeitschlitz(e) mit Befehlen, die in der logischen Programmreihenfolge vor
dem Sprung liegen. Dies ist nur möglich, wenn die Befehle keinen Einfluss
auf die Auswertung der Sprungrichtung haben. (Es wird angenommen,
dass die Sprungadresse nicht auf einen der Befehle in den Verzögerungszeitschlitzen zeigt.) In dem Fall der Vorziehung werden die Befehle, die in
die Verzögerungszeitschlitze verschoben wurden, ohne Rücksicht auf den
Sprungbefehl ausgeführt. Wenn es keine Befehle gibt, die in die Zeitschlitze verschoben werden können, müssen wiederum Leerbefehle eingefügt
werden. Wenn alle Verzögerungszeitschlitze gefüllt werden, dann werden
alle Leerbefehle ersetzt und es entstehen keine Verzögerungen mehr in der
Pipeline. Aus Hardware-Sicht ist dies die effizienteste Lösung, da trotz
hoher Auslastung keine Befehle gelöscht werden müssen, wodurch die
Komplexität beim Aufbau der Pipeline verringert wird.
• Statische Sprungvorhersage: Eine Alternative zur statischen Befehlsumordnung besteht darin, den Sprungbefehl spekulativ auszuführen bzw. Statische
nicht auszuführen. Die statische Sprungvorhersage ermöglicht es dem Sprungvorhersage
Compiler, dem Prozessor bereits vorab Informationen darüber zu geben, wie er nach dem Erkennen eines Sprungbefehls in der ID-Phase
fortfahren soll. Die Vorhersage kann zwischen „Sprünge werden immer
genommen“ und „Sprünge werden nie genommen“ liegen. Mischformen wie
„Vorwärtssprünge werden nie genommen, Rückwärtssprünge werden immer
86
Kapitel 2. Mikroarchitekturen
genommen“ erweisen sich dabei aufgrund von Programmstrukturen wie
Schleifen am sinnvollsten. Die statische Sprungvorhersage ist keine reine
Software-Lösung und erfordert bereits Anpassungen in der Hardware. Im
Falle einer Fehlspekulation auf einen nicht-genommenen Sprung müssen
die nachfolgenden drei Befehle aus der Pipeline entfernt werden. Im Falle
einer Spekulation auf einen genommenen Sprung muss bis zur MEMPhase gewartet werden, d. h. bis die Sprungzieladresse berechnet wurde.
Die nachfolgenden Zeitschlitze werden bis zum Ende dieser Berechnung
dynamisch durch Leerbefehle aufgefüllt. Beschleunigt werden kann das
Verfahren durch einen Sprungzieladress-Cache, der die zuletzt verwendeten Sprung- und Sprungzieladressen aufnimmt und diese wesentlich
schneller bereitstellen kann als die Berechnung über die ALU. Weiteres
zur statischen Sprungvorhersage und dem Sprungzieladress-Cache folgt in
Kapitel 4.
Entsprechend den Ergebnissen von Programmtestläufen ist die Wahrscheinlichkeit, dass ein Befehl in einen Verzögerungszeitschlitz verschoben werden
kann, größer als 60%, die Wahrscheinlichkeit für zwei Befehle bei ungefähr 20%
und die Wahrscheinlichkeit für drei kleiner als 10%. Eine reine Software-Lösung
ist daher weder sinnvoll noch sonderlich effizient.
Die verzögerte Sprungtechnik mit Verzögerungszeitschlitzen wurde bei den
ersten Generationen skalarer RISC-Prozessoren angewandt, wie z. B. dem
IBM801, dem Stanford MIPS oder dem Berkeley RISC I. In superskalaren
Prozessoren, die mehr als einen Befehl holen und gleichzeitig ausführen können,
erschwert die verzögerte Sprungtechnik die Befehlszuordnungslogik und die
Implementierung präziser Unterbrechungen. Unter einer präzisen Unterbrechung
versteht man dabei ein Verfahren, bei dem die Resultate aller Befehle, die in
der Programmreihenfolge vor dem Unterbrechungsereignis stehen, gültig sind
und diejenigen aller nachfolgenden Befehle verworfen werden. Abhängig von der
Architektur und der Art der Unterbrechung, wird das Resultat des verursachenden Befehls noch gültig gemacht oder verworfen, ohne weitere Auswirkungen
zu haben. Aus Kompatibilitätsgründen gibt es die verzögerte Sprungtechnik
vereinzelt immer noch in den Architekturen heutiger Mikroprozessoren, wie z. B.
in den SPARC- oder eben MIPS-basierten Prozessoren.
Praktisch alle heutigen Prozessoren behandeln Steuerflusskonflikte durch
die Hardware. Dies kann durch die folgenden Techniken geschehen:
Hardware-Lösungen von Steuerflusskonflikten:
• Pipeline-Leerlauf : Dies ist wieder die einfachste, aber ineffizienteste Methode, um mit Pipeline-Konflikten umzugehen. Die Hardware erkennt in
der ID-Stufe, dass der dekodierte Befehl ein bedingter Sprung ist und lädt
keine weiteren Befehle in die Pipeline, bis die Sprungzieladresse berechnet
und im Falle eines bedingten Sprungbefehls die Sprungentscheidung getroffen ist. Außerdem muss der nachfolgende Befehl, der durch die IF-Stufe
bereits in den Befehlspuffer geladen wurde, wieder gelöscht werden.
2.4. Pipeline-Mikroarchitektur
87
• Dynamische Sprungvorhersage: Bei der dynamischen Sprungvorhersage
entscheidet die Hardware zur Laufzeit selbst, wie mit einem Sprungbe- Dynamische
fehl umgegangen werden soll. Hierzu werden die Sprungrichtung und die Sprungvorhersage
Zieladresse vergangener Sprünge in einem Cache gespeichert und beim
erneuten Aufrufen des Sprungbefehls für eine Vorhersage genutzt. Verschiedene Sprungvorhersagetechniken sind denkbar, um zu entscheiden, ob
der Sprung als „genommen“ oder „nicht genommen“ vorhergesagt wird. Es
handelt sich hierbei um eine reine Hardware-Lösung, d. h. es sind im Gegensatz zur statischen Sprungvorhersage keine Änderungen am Befehlssatz
notwendig. Allerdings muss auch bei der dynamischen Sprungvorhersage
bei einer Fehlspekulation die Pipeline geleert werden. Für ein effizientes Arbeiten wird ebenfalls ein Sprungzieladress-Cache vorausgesetzt12 .
Weiteres zur dynamischen Sprungvorhersage folgt ebenfalls in Kapitel 4.
Selbsttestaufgabe 2.5
Es sei folgende Programmsequenz gegeben. Bestimmen Sie alle Daten- und
Steuerabhängigkeiten in dieser Programmsequenz.
I1:
I2:
I3:
I4:
I5:
I6:
I7:
I8:
2.4.7
addi
sub
sge
bnez
mult
j
addi
addi
$t1
$t4
$t7
$t7
$t3
I8
$t3
$t4
$t2,
$t1,
$t4,
I7
$t5,
2
$t3
$t0
$t3,
$t4,
2
1
$t6
Strukturkonflikte und deren Lösungsmöglichkeiten
Struktur- oder Ressourcenkonflikte treten in der einfachen MIPS-Pipeline nicht
auf. Schließlich ist es ein Ziel beim Pipeline-Entwurf, Strukturkonflikte möglichst zu vermeiden und da, wo sie nicht vermeidbar sind, zu erkennen und zu
behandeln.
Einen Strukturkonflikt kann man demonstrieren, indem die MIPS-Pipeline
leicht verändert wird: Sei die Pipeline so konstruiert, dass die MEM-Stufe in der
Lage ist, ebenfalls in den Registersatz zurückzuschreiben. Betrachten wir nun
zwei Befehle I 1 und I 2 , wobei I 1 vor I 2 geholt wird. Wir nehmen an, dass I 1
ein Ladebefehl ist, während I 2 ein datenunabhängiger Register-Register-Befehl
ist. Aufgrund der Speicheradressierung kommt die Datenanforderung von I 1
in den Registern zur gleichen Zeit an wie das Ergebnis von I 2 . Es entsteht
ein Ressourcenkonflikt, sofern nur ein einzelner Schreibkanal auf die Register
vorhanden ist, siehe Abbildung 2.23.
Hardware-Lösungen von Strukturkonflikten:
• Arbitrierung mit Interlocking: Strukturkonflikte können durch eine Ar- Arbitrierung
12
Sofern Sprünge als „genommen“ vorhergesagt werden sollen und die Sprungzieladresse
schon vor der MEM-Phase bereitstehen sollen.
88
Kapitel 2. Mikroarchitekturen
Taktzyklen
I1
IF
ID
EX
WB
MEM
WB
Registersatz
I2
IF
ID
EX
WB
MEM
WB
Abbildung 2.23: Beispiel eines Strukturkonflikts im Falle einer veränderten
MIPS-Pipeline.
bitrierungslogik erkannt und aufgelöst werden. Die Arbitrierungslogik
hält den im Programmfluss späteren der beiden um die Ressource konkurrierenden Befehle an. Wenn diese Technik dazu benutzt wird, um
Strukturkonflikte zu lösen, kommt man nicht ohne Verzögerungen aus. Im
Beispiel in Abbildung 2.23 darf der Befehl I1 in das Register schreiben,
während das Rückschreiben des Befehls I2 verzögert wird.
Übertaktung
• Übertaktung: Manchmal ist es möglich, die Ressource, die den Strukturkonflikt hervorruft, schneller zu takten als die übrigen Pipeline-Stufen.
In diesem Fall könnte die Arbitrierungslogik zweimal in einer PipelineStufe auf die Ressource zugreifen und die Ressourcenanforderungen in der
Ausführungsreihenfolge erfüllen.
Ressourcenreplizierung
• Ressourcenreplizierung: Die Auswirkungen von Strukturkonflikten können durch Vervielfachung der Hardware-Ressourcen gemindert werden.
Auf diese Weise treten keine Verzögerungen mehr auf. Im obigen Beispiel würde ein Registersatz mit mehreren Schreibkanälen in der Lage
sein, gleichzeitig in verschiedene Zielregister zu schreiben. Im Falle eines
Schreibzugriffs beider konkurrierender Befehle auf das gleiche Zielregister
ist jedoch wieder eine Arbitrierung und Verzögerung des zweiten Zugriffs
notwendig. Alternativ kann auch beim Registerschreibzugriff zweier direkt hintereinander stehenden Befehle der Wert, der vom ersten Befehl
geschrieben werden soll, gelöscht und stattdessen direkt der Wert des
zweiten schreibenden Befehls ins Register zurückgeschrieben werden. Das
bedeutet, übertragen auf unser Beispiel in Abbildung 2.23, dass der Wert,
der vom Befehl I1 berechnet wurde, gelöscht wird und stattdessen der
Wert des ALU-Ausgaberegisters von Befehl I2 ausgewählt und in das
Zielregister geschrieben wird. Dieses effiziente Verfahren beruht auf der
2.4. Pipeline-Mikroarchitektur
Beobachtung, dass das erste Ergebnis von Befehl I1 im Programmablauf
nie verwendet wird, da es vom zweiten Befehl sofort überschrieben wird.
2.4.8
Ausführung in mehreren Takten
Es kann sinnvoll sein, einzelne Pipeline-Stufen intern wiederum als Pipeline
zu organisieren. Betrachten wir dafür eine aufwendige Operation, wie sie z. B.
bei einem Gleitkomma- oder Multimediabefehl auftritt. Würde man versuchen,
diese in einem Takt in der EX-Phase auszuführen, dann würde diese Phase für
alle Befehle überproportional lang werden und die Taktrate dadurch deutlich
absinken. Wird für die lang-rechnenden Befehle eine eigene Recheneinheit13
verwendet, so kann die Ausführung der übrigen Befehle in der ALU weiterhin in
nur einem Takt erfolgen. In Abbildung 2.24 ist eine MIPS-Pipeline dargestellt,
die in der EX-Stufe zusätzlich zur ALU jeweils eine dreistufige Gleitkommaund Multimediaeinheit enthält. Gleitkomma- und Multimediabefehle können so
in mehreren Takten abgearbeitet werden, ohne dass die EX-Phase der übrigen
Befehle verlängert wird. Es muss beachtet werden, dass sich die Befehle nicht
überholen dürfen. Folgt ein ALU-Befehl auf einen Gleitkommabefehl, so wird
dieser ohne weitere Anpassung der Pipeline den Gleitkommabefehl in der EXStufe überholen, da der ALU-Befehl hier nur einem Takt benötigt. Es gibt zwei
Möglichkeiten, wie dieses Problem behoben werden kann:
• Interlocking: alle ALU -Befehle, die auf einen Gleitkomma- oder Multimediabefehl folgen, werden nach der ID-Stufe so lange angehalten, bis die
vorherigen Befehle die letzte EX-Stufe durchlaufen haben. Nun können
sich keine Befehle mehr gegenseitig überholen. Durch das Interlocking
wird allerdings die Ausführungsgeschwindigkeit der Pipeline verringert
(besonders, wenn sich verschiedene Befehle – ALU, FP, MU – oft abwechseln).
• Out-of-Order-Execution (OOE): eine andere Möglichkeit wäre es, ein
Überholen von Befehlen zu ermöglichen. Hierzu müssen alle Datenabhängigkeiten zwischen den Befehlen eliminiert werden und die ursprüngliche
Programmreihenfolge muss zwischengespeichert werden, damit die Befehle
später wieder in der richtigen Programmreihenfolge gültig gemacht werden
können.
Dieser Unterabschnitt ist bereits als Überleitung zum Thema Superskalarität
in Kapitel 4 zu sehen. Der Übergang von einer skalaren zu einer superskalaren Pipeline wird dadurch motiviert, dass mehrere Befehle gleichzeitig geholt,
dekodiert, ausgeführt und rückgeordnet werden können. Hierdurch wird die Auslastung der Ausführungseinheiten und der Durchsatz der Pipeline z.T. erheblich
gesteigert. Die Zuteilung der Befehle auf die verschiedenen Ausführungseinheiten
(Scheduling) kann statisch oder dynamisch erfolgen. Mehr zur Superskalarität
folgt in Kapitel 4.
13
Gleitkomma- und Multimediaeinheiten sind intern üblicherweise als Pipeline organisiert.
89
90
Kapitel 2. Mikroarchitekturen
ALU
IF
ID
FP1
FP2
FP3
MU1
MU2
MU3
MEM
WB
Abbildung 2.24: MIPS-Pipeline mit verschiedenen Ausführungseinheiten.
2.5
Zusammenfassung
In diesem Kapitel haben Sie gelernt, wie der MIPS-Befehlssatz in einer konkreten Mikroarchitektur implementiert werden kann. Hierzu wurde zunächst die
Einzyklus-Mikroarchitektur eingeführt, welche alle Befehle in einem einzigen
großen Schaltnetz verarbeiten kann. Anschließend wurde gezeigt, wie die Befehlsabarbeitung bei der Mehrzyklen-Mikroarchitektur in mehrere Teilschritte
zerlegt wird. Mit einer Pipeline-Mikroarchitektur wurde schließlich eine überlappende Befehlsverarbeitung und somit Beschleunigung der Taktrate und des
Befehlsdurchsatzes ermöglicht. Außerdem haben Sie gelernt, welche Probleme
bei Verwendung einer Pipeline entstehen und wie diese behandelt werden können.
Zum Ausblick auf Kapitel 4 wurden weitere Mikroarchitektur-Erweiterungen
angesprochen, welche den Durchsatz eines Prozessors weiter steigern können.
2.5. Zusammenfassung
91
Lösungen zu den Selbsttestaufgaben
Lösung Selbsttestaufgabe 2.1
Bei dem and-Befehl handelt es sich um einen R-Typ Befehl. Das Befehlsformat
eines R-Typ Befehls wird in Kapitel 1 erläutert. Dabei wird die auszuführende
Operation durch den Opcode und dem Wert im func-Feld beschrieben. In
Abbildung 2.25 sind die Steuersignale und der Datenfluss bei der Ausführung
eines and-Befehls zu sehen. Der Datenpfad ist gepunktet dargestellt. Der PC
Steuereinheit
MemtoReg
Befehlsdekodierer MemWrite
31:26
Branch
Opcode
ALUSrc
RegDst
0
RegWrite
&
PCSrc
ALUOp
1:0
FunktionsALU Operation2:0
dekodierer
5:0
Func
000
PC(t+1)
BeLese- fehl
LeseAdresse Daten
PC(t)
Befehlszähler
25:21
LeseRegister 1
20:16
LeseRegister 2
Befehlsspeicher
SchreibeRegister
0
LeseDaten 1
Null
ALU
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
1
0
Adresse
LeseDaten
SchreibeDaten
WE
0
20:16
15:0
16
32
Addierer
<<2
4
Addierer
1
Vorzeichenerweiterung
15:11
0
Abbildung 2.25: Steuersignale und Datenfluss bei der Ausführung eines andBefehls.
liefert die Adresse des Befehls im Befehlsspeicher. Von dieser Adresse wird das
Befehlswort gelesen. Da es sich um einen R-Typ-Befehl handelt, werden die
Registerinhalte durch das rs- und das rt-Feld des Befehlswortes bestimmt. Diese
werden als Operanden in der ALU genutzt. Um die Ausgabe an Lese-Daten 1
und 2 als Operanden in der ALU zu nutzen, muss das ALUSrc = 0 sein. Des
Weiteren muss das Steuersignal ALU Operation für den and-Befehl den Wert
92
Kapitel 2. Mikroarchitekturen
000 haben. Bei dem betrachteten Befehl wird das ALU Ergebnis direkt in den
Registersatz geschrieben. Hierzu muss das MemtoReg = 0 und RegDst = 1 sein,
sodass das rd-Feld des Befehlswortes als Adresse für das Schreiben des ALU
Ergebnisses genutzt werden kann.
Außerdem muss die Adresse im PC um vier erhöht werden. Der Datenfluss dafür
ist in Abbildung 2.25 als gestrichelte, schwarze Linie dargestellt.
Lösung Selbsttestaufgabe 2.2
Der Datenfluss unterscheidet sich nicht, da es sich wie in Selbsttestaufgabe
2.1 um einen R-Typ-Befehl handelt. Außer dem Steuersignal der ALU (ALU
Operation = 001) ändert sich nichts bei dem betrachteten Befehl.
Lösung Selbsttestaufgabe 2.3
Am Datenpfad müssen zur Ausführung des addi-Befehls keine Erweiterungen
durchgeführt werden. Der betrachtete Befehl hat das I-Typ Befehlsformat. Dieses Befehlsformat ist in Kapitel 1 ausführlich erläutert. Wie in Abbildung 2.26
dargestellt, wird beim addi-Befehl ein Registerinhalt (dessen Adresse im rs-Feld
des Befehlswortes beschrieben ist) und der vorzeichenerweiterte, unmittelbare
Operand in der ALU addiert. Dazu muss das ALUSrc = 1 sein. Bei diesem
Befehl hat das ALUOp-Signal den Wert 00. Deshalb wird das func-Feld des
Befehlswortes nicht betrachtet und als don’t care gekennzeichnet. Die ausgewählte Operation ist durch die Bitfolge 010 kodiert und liegt als Signal an der
ALU. Da das Ergebnis der ALU in den Registersatz geschrieben werden soll, ist
MemtoReg = 0. Im Abschluss wird das ALU-Ergebnis an die Adresse, die aus
dem rt-Feld (Befehl[20:16]) gelesen werden kann, geschrieben. Deshalb muss
RegDst = 0 sein.
Lösung Selbsttestaufgabe 2.4
In Abbildung 2.27 ist der erweiterte Datenpfad gezeigt. Dabei wird ersichtlich,
dass ein weiterer Multiplexer notwendig ist, um einen Sprung-Befehl zu ermöglichen. Um diesen Multiplexer zu steuern, wird das neue Steuersignal Jump
genutzt.
Die zugehörige Wahrheitstabelle für den Befehlsdekodierer ist in Tabelle 2.4
zu sehen.
j
Tabelle 2.4: Wahrheitstabelle des
OpReg- Reg- ALU- Branch
code Write Dst
Src
000010 0
X
X
X
Befehlsdekodierers
Mem- Mem- ALU- Jump
Write toReg Op
0
X
XX
1
Für das neu eingeführte Steuersignal Jump wird in der Tabelle eine neue
Spalte hinzugefügt. Diese Signal muss für den Sprung-Befehl den Wert eins
haben. Der betrachtete Befehl schreibt weder in den Registersatz noch in den
Datenspeicher, weshalb RegWrite = MemWrite = 0 ist. Bei diesem Befehl
werden Berechnungen nicht betrachtet. Aus diesem Grund sind die Signale
2.5. Zusammenfassung
93
Steuereinheit
MemtoReg
Befehlsdekodierer MemWrite
31:26
Branch
Opcode
ALUSrc
RegDst
0
RegWrite
00
&
PCSrc
ALUOp
1:0
FunktionsALU Operation2:0
dekodierer
X
5:0
Func
010
PC(t+1)
BeLese- fehl
LeseAdresse Daten
PC(t)
Befehlszähler
25:21
LeseRegister 1
20:16
LeseRegister 2
Befehlsspeicher
SchreibeRegister
1
LeseDaten 1
Null
ALU
ALUErgebnis
Registersatz
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
1
0
Adresse
LeseDaten
SchreibeDaten
WE
0
20:16
15:0
16
32
Addierer
<<2
4
Addierer
0
Vorzeichenerweiterung
15:11
0
Abbildung 2.26: Steuersignale und Datenfluss bei der Ausführung eines addiBefehls.
RegDst, AluSrc, Branch, MemtoReg und ALUOp mit dem don’t care-Symbol X
in der Tabelle gekennzeichnet.
Die maximale Sprungdistanz im Befehlsadressraum beläuft sich auf 226 .
Lösung Selbsttestaufgabe 2.5
Echte Datenabhängigkeiten: (I1-I2), (I2-I3), (I2-I8), (I3-I4)
Gegenabhängigkeiten: (I2-I5), (I2-I7), (I3-I8)
Ausgabeabhängigkeiten: (I2-I8)
Steuerflussabhängigkeiten: Die bedingte Sprunganweisung I4 erzeugt eine
Kontrollabhängigkeit. Die unbedingte Sprunganweisung I6 ist eine weitere
Steuerflussänderung.
94
Kapitel 2. Mikroarchitekturen
Steuereinheit
MemtoReg
Jump Befehls-
dekodierer MemWrite
31:26
Branch
Opcode
ALUSrc
RegDst
RegWrite
&
PCSrc
ALUOp
1:0
FunktionsALU Operation2:0
dekodierer
5:0
PC(t+1)
BeLese- fehl
LeseAdresse Daten
PC(t)
Befehlszähler
Func
25:21
LeseRegister 1
20:16
LeseRegister 2
Befehlsspeicher
SchreibeRegister
LeseDaten 1
Null
ALU
ALUErgebnis
Registersatz
Adresse
Datenspeicher
LeseDaten 2
SchreibeDaten
WE
LeseDaten
SchreibeDaten
WE
20:16
32
Addierer
<<2
4
Addierer
15:0
16
Vorzeichenerweiterung
15:11
31:28
25:0
<<2
27:2
Abbildung 2.27: Datenpfad für Selbsttestaufgabe 2.4.
Kapitel 3
Speicherorganisation
Kapitelinhalt
3.1
Lernziele . . . . . . . . . . . . . . . . . . . . . . . . . .
96
3.2
Hauptspeicher . . . . . . . . . . . . . . . . . . . . . .
97
3.3
Cache-Speicher und -Organisation . . . . . . . . . . 106
3.4
Virtuelle Speicherverwaltung . . . . . . . . . . . . . 128
3.5
Anbindung des Hauptspeichers und von Ein- und
Ausgabekomponenten . . . . . . . . . . . . . . . . . . 133
3.6
Zusammenfassung . . . . . . . . . . . . . . . . . . . . 143
96
Kapitel 3. Speicherorganisation
„640 KB (Arbeitsspeicher) sollten für jeden genug sein.“
Bill Gates zugeschrieben, 1981
In den vorangehenden Kapiteln wurden die Eigenschaften, der Aufbau und
die Technologien von Mikroprozessoren eingehend erläutert. Dabei wurde das
Hauptaugenmerk auf die Befehlsabarbeitung sowie auf Probleme in diesem
Zusammenhang gelegt. Offen ist noch, wo die Programme und Daten, die zur
Ausführung gebracht bzw. verarbeitet werden sollen, schlussendlich liegen. Dies
ist üblicherweise der Hauptspeicher, der notwendigen Programm-Code und
dazugehörige Daten vorhält.
In Abschnitt 3.1 werden die zu erreichenden Lernziele für dieses Kapitel
definiert. In Abschnitt 3.2 reißen wir die technologischen Grundlagen an. Diese
Grundlagen werden dabei etwas tiefer, aber weiterhin auf einer – einem Grundlagenkurs angemessenen – Abstraktionsebene beschrieben. Im darauf folgenden
Abschnitt 3.3 gehen wir auf die für heutige Hochleistungsrechner unverzichtbaren Cache-Speicher ein. Die virtuelle Speicherverwaltung lernen wir anschließend
in Abschnitt 3.4 kennen. Abschließend adressieren wir in Abschnitt 3.5 Schnittstellenbausteine zur Anbindung von Hauptspeicher und sonstiger Eingabe- bzw.
Ausgabe-Peripherie. In diesem letzten Abschnitt gehen wir auch auf den Zugriff
auf Eingabe-/Ausgabe-Geräte über Memory Mapped I/O1 (Memory Mapped
Input/Output) ein.
3.1
Lernziele
Nach dem Durcharbeiten und Studieren dieses Kapitels kennen Sie die Speicher
in Computersystemen. Sie verstehen die technologischen Grundlagen, die heutigen Speichern zugrunde liegen. Darüber hinaus erkennen Sie, welche Arten von
Speichern für welche Zwecke geeignet sind und warum diese eingesetzt werden.
Im weiteren Verlauf lernen Sie Cache-Speicher kennen, die zur Beschleunigung
von Zugriffen auf den Speicherinhalt des Hauptspeichers eingesetzt werden.
Zudem wird die virtuelle Speicherverwaltung vorgestellt und Sie verstehen,
wozu diese eingesetzt wird. Sie wissen nach der Bearbeitung dieses Kapitels,
wie Speicher über Chipsätze mit dem Hauptprozessor verbunden sind und wie
auf andere Komponenten eines Computersystems über Memory Mapped I/O
zugegriffen wird.
1
Bei Memory Mapped I/O werden Register und Speicherbereiche externer HardwareKomponenten in den allgemeinen Adressraum eingeblendet. Vereinzelt findet man hierfür die
deutsche Bezeichnung Speicher-abgebildete Ein- und Ausgabe, wir werden aber im Weiteren
den üblicherweise genutzten englischen Begriff verwenden.
3.2. Hauptspeicher
3.2
97
Hauptspeicher
Der Hauptspeicher moderner Computer und eingebetteter Systeme basiert je
nach Anwendungsfall auf verschiedenen Speichertechnologien und -architekturen.
In „normalen“ Computern (Personal Computern u. ä.) findet man üblicherweise
eine Von-Neumann-Speicherarchitektur vor, in der ein gemeinsamer Speicher – Von-Neumannüber einen Bus angeschlossen – sowohl für Daten als auch Programmcode ver- Architektur
wendet wird. Hierbei werden heute üblicherweise dynamische Speicher (DRAM
– Dynamic Random Access Memory) verwendet, mit denen kostengünstig sehr
große Speicher realisiert werden können. In eingebetteten Systemen und Servern
findet man eher eine Harvard-Speicherarchitektur vor, bei der Daten- und Pro- Harvard-Architektur
grammspeicher getrennt und über eigene Busse mit dem Prozessor verbunden
sind. Meist wird statischer Speicher (SRAM – Static Random Access Memory)
für den Datenspeicher und ein Nur-Lese-Speicher (heute oft Flash-basierter Speicher) für den Programmspeicher verwendet. Der Vorteil der Harvard-Architektur
ist die Verminderung des Von-Neumann-Falschenhalses, der bei gleichzeitigem
Zugriff auf Programmcode und Daten entsteht, siehe auch Abbildung 3.1.
Von-NeumannArchitektur
HarvardArchitektur
CPU
CPU
AdressBus
DatenBus
SteuerBus
Programm- und
Datenspeicher
AdressBus
DatenBus
SteuerBus
Programmspeicher
AdressBus
DatenBus
Datenspeicher
Abbildung 3.1: Von-Neumann- und Harvard-Architektur
Die Problematik des Von-Neumann-Flaschenhalses wird in der Abbildung Von-Neumannsehr deutlich: Wird in einem Zyklus ein Datenwort aus dem Speicher geladen, Flaschenhals
ist dieser für andere Zugriffe – z. B. um zeitgleich eine Instruktion zu laden –
nicht nutzbar. Ein notwendiger Zugriff ist somit blockiert, bis die Busse wieder
freigegeben sind (linke Seite der Abbildung 3.1). Bei der Harvard-Architektur
sind Zugriffe auf den Daten- und Programmspeicher – unabhängig voneinander – zum gleichen Zeitpunkt möglich. Es können somit sowohl ein Operand
(oder auch mehrere Operanden bei Multi-Port-Speichern) für eine gerade in
Ausführung befindliche Instruktion aus dem Datenspeicher als auch die nächste
auszuführende Instruktion aus dem Programmspeicher geladen werden. CacheSpeicher werden in modernen Computersystemen je nach Hierarchieebene in
Harvard-Architektur realisiert (je näher an der CPU, desto wahrscheinlicher).
98
Kapitel 3. Speicherorganisation
3.2.1
Speicherhierarchie
sch
wi
nd
igk
eit
sg
e
gr
iff
igt
ste ken
ät
n
zit it si
pa
B
Ka pro
Level-1-Cache
ten
Zu
Register
s
Ko
ste
igt
Es gibt verschiedene Arten von Speichern, die in heutigen Computer-Systemen
hierarchisch angeordnet und für verschiedene Zwecke verwendet werden. Manche
Speicher sind sehr schnell, aber dafür klein und teuer. Andere Speicher sind
sehr groß und günstig zu realisieren, dafür aber vergleichsweise langsam im
Zugriff. Zudem verlieren einige Speicher ihre Information, sobald diese von der
Betriebsspannung getrennt werden, andere können Informationen dauerhaft
halten. In Abbildung 3.2 ist die Speicherhierarchie eines modernen Rechnersystems als Pyramide dargestellt. Dabei werden kleine, schnelle Speicher weiter
oben angeordnet, während langsame, aber dafür größere Speicher weiter unten
angesiedelt sind. Üblicherweise steigt der Preis pro Bit an Information nach
oben hin – teilweise deutlich – an. Fett hervorgehoben sind die in diesem Kapitel
besprochenen Speicher.
Level-2-Cache
Hauptspeicher
Festplattenspeicher (magnetisch, Flash)
Optische Wechselspeichermedien
Abbildung 3.2: Speicherpyramide
Die verschiedenen Speicher können wie folgt charakterisiert werden:
• Register findet man auf der höchsten Ebene der Speicherpyramide. Diese
Speicher sind sehr klein und teuer, dafür aber sehr schnell. Zugriffe sind
üblicherweise im Prozessortakt möglich. Eine weitere Besonderheit von
Registern ist, dass meist auf mehrere Register gleichzeitig zugegriffen werden kann. Dabei wird nicht mit Adressen – wie z. B. beim Hauptspeicher –
gearbeitet, sondern die Register werden über dedizierte Auswahlleitungen
angesprochen. Einzelne Bits werden hier in Flip-Flops gespeichert. Die
technologische Realisierung von Registern wurde bereits im Kurs Computersysteme I (1608) eingeführt und Register waren immer wieder Inhalt
der ersten beiden Kapitel des Kurses Computersysteme II, weshalb in
diesem Kapitel nicht weiter auf diesen Punkt eingegangen wird.
3.2. Hauptspeicher
• Der Level-1-Cache (L1-Cache)2 wird zur Beschleunigung von Speicherzugriffen verwendet und ist für den Programmierer bzw. Nutzer von
Computersystemen üblicherweise transparent. Der Zugriff auf den Cache
der ersten Ebene erfolgt sehr schnell in einem oder wenigen Taktzyklen.
Der Cache-Speicher basiert technologisch auf dem SRAM-Speicher, der
in Unterabschnitt 3.2.3 behandelt wird. Üblicherweise findet man bei
modernen Computersystemen auf dieser Ebene eine Harvard-Architektur
vor, d. h., der Level-1-Cache ist in einen Daten- und Instruktions-Cache
aufgeteilt. Dabei kann zeitgleich auf Inhalte (Instruktionen und Daten)
zugegriffen werden. Cache-Speicher und deren Organisation werden in
Abschnitt 3.3 ausführlich beschrieben.
• Der Level-2-Cache (L2-Cache) wird auch zur Beschleunigung des Zugriffs auf die Daten und Instruktionen verwendet. Dieser ist meist deutlich
größer als der L1-Cache und anders organisiert. Häufig handelt es sich
hierbei um einen Speicher, in dem Instruktionen und Daten gemischt
abgelegt werden (Unified Cache), also um eine Von-Neumann-Architektur. Unified Cache
Es können weitere Cache-Hierarchieebenen existieren, die hier aber nicht
explizit wiedergegeben sind. Auch gibt es – insbesondere in eingebetteten
Systemen – Umsetzungen, bei denen vollständig auf Caches verzichtet
wird.
• Die nächste Ebene stellt den Hauptspeicher oder Arbeitsspeicher dar. Je
nach Einsatzgebiet werden verschiedene Speichertechnologien verwendet.
In modernen Desktop-Computern und Servern findet man üblicherweise
große Speicher auf Basis von dynamischem RAM – siehe hierzu Unterabschnitt 3.2.4 – die in Von-Neumann-Architektur an den Hauptprozessor
angebunden sind. In eingebetteten Systemen ist dieser Speicher häufig in Harvard-Architektur realisiert, d. h. mit getrennten Speichern für
Instruktionen und Daten. Für den Instruktionsspeicher werden meist Festwertspeicher – heute Flash-Speicher – verwendet, für den Datenspeicher
meist SRAM-Speicher, seltener DRAM-Speicher. Der Hauptspeicher ist für
den Programmierer direkt nutzbar. Die Zugriffszeit auf den Hauptspeicher
ist – insbesondere bei DRAM-Speicher – um ein bis zwei Größenordnungen
höher als auf SRAM-basierte Cache-Speicher.
• Festplattenspeicher werden zur dauerhaften Speicherung von Daten
(und damit auch Programmen) eingesetzt. Die Kapazität ist im Vergleich
zum Hauptspeicher sehr groß. Festplattenspeicher gibt es auf magnetischer
Basis und auf Basis der Flash-Technologie (SSD – Solid State Disk ). Die
Zugriffszeiten sind um einige Größenordnungen höher als die auf den
Hauptspeicher oder die Caches. Festplattenspeicher werden im Rahmen
dieses Kurses nicht vertieft besprochen.
2
Cache: aus dem Englischen, von ursprünglich französisch cacher, etwas verstecken.
99
100
Kapitel 3. Speicherorganisation
• Optische Wechselspeichermedien werden wie Festplatten zur dauerhaften Speicherung von Daten verwendet. Die Medien (CD-ROM3 , DVD4 ,
BluRay-Disks etc.) werden z. B. zum Austausch von Daten und zur Archivierung verwendet. Wechselspeicher sind nicht Inhalt dieses Kurses.
Auffrischung
flüchtig
Das Hauptaugenmerk dieses Kapitels ist auf Cache und Hauptspeicher
sowie deren technologische Hintergründe und Organisation gerichtet. Die FlashSpeicherzelle wird ebenfalls angesprochen, da diese in eingebetteten Systemen zur
Realisierung von Programmspeichern verwendet wird. Die gleiche Technologie
wird auch für Halbleiterfestplatten verwendet, die aber im Rahmen dieses Kurses
nicht vertieft behandelt werden.
DRAM-Speicher müssen in festen Abständen (üblicherweise im unteren Millisekundenbereich) aufgefrischt werden, damit die Inhalte erhalten bleiben. Bei
SRAM ist dies aufgrund der technologischen Realisierung als Flip-Flop-Speicher
nicht notwendig. Beide Speicher sind allerdings flüchtig, d. h. beim Trennen
von der Versorgungsspannung gehen die Informationen in diesen Speichern nach
kurzer Zeit verloren. Flash-Speicher hingegen halten die in ihnen gespeicherten
Informationen auch ohne eine Betriebsspannung über lange Zeiträume. Entsprechend eignen sich diese Speicher zur dauerhaften Speicherung von Daten. Heute
werden Flash-Speicher in Form von SSDs vermehrt als Ersatz für drehende, auf
Magnetismus basierenden Festplattenspeichern eingesetzt.
3.2.2
Technologische Grundlagen
Wenn wir über Hauptspeicher sprechen möchten, müssen wir uns fragen, wie
ein einzelnes Bit gespeichert werden kann. Dabei gibt es viele verschiedene
Möglichkeiten, wobei wir ausschließlich auf drei weit verbreitete Möglichkeiten
eingehen werden, und zwar das Speichern eines Bits in einem
• Flip-Flop, genutzt bei SRAM,
• Kondensator, genutzt bei DRAM und
• Floating-Gate-Transistor, genutzt bei Flash-Speichern.
SRAM und DRAM werden in Desktop-Rechnern und Servern als Arbeitsbzw. Cache-Speicher eingesetzt. Flash-Speicher findet man vor allem als Programmspeicher bei modernen Mikrocontrollern. Bevor wir uns der groben
Beschreibung der technischen Realisierung dieser Speichertechnologien zuwenden, soll kurz das Schulwissen zur Funktionsweise von Transistoren und Kondensatoren aufgefrischt werden. Dabei werden wir uns bei den Transistoren
ausschließlich auf die heute im Wesentlichen für Speicher verwendeten MetalloxidFeldeffekttransistor Feldeffekttransistoren5 , sog. MOSFETs (Metal Oxid Semiconductor Field Effect
Transistor), beschränken.
3
CD-ROM: Compact Disk Read Only Memory
DVD: Digital Versatile Disk
5
Früher wurden hierfür bipolare Transistoren verwendet.
4
3.2. Hauptspeicher
101
Im Rahmen dieses Kurses ist es ausreichend, sich einen Transistor als elektrisch steuerbaren Schalter vorzustellen. In Abbildung 3.3 sind die vier Anschlüsse Source (S), Drain (D), Gate (G) und Substrat bzw. Bulk (B) eines
Transistors dargestellt.
D
B
G
S
Abbildung 3.3: Anschlüsse eines Transistors
Der Gate-Anschluss (G) dient zur Steuerung des Transistors. Wir nehmen Gate-Anschluss
hier an, dass entweder die Betriebsspannung (UB ) oder 0 V (GN D) angelegt
wird6 . Mit einer entsprechend zu wählenden Spannung entsteht unter dem Gate
ein leitender Kanal. Dann kann ein Strom zwischen Source-Anschluss (S) und Source-Anschluss
Drain-Anschluss (D) fließen. Der Anschluss des Substrats (B) wird häufig mit Drain-Anschluss
dem Source-Anschluss kurzgeschlossen, da dies die technologische Umsetzung
deutlich vereinfacht. Die Transistoren können entweder selbstleitend oder sperrend und jeweils als n- und p-Kanal realisiert werden. Letzteres hat einen
Einfluss darauf, ob mit positiver oder negativer Spannung UGS die Bildung des
leitenden Kanals realisiert wird. Der Name n- und p-Kanal richtet sich nach den
Ladungsträgern, die den Kanal bei entsprechender Ansteuerung des Transistors
bilden. Darauf wird hier aber nicht weiter eingegangen. In Abbildung 3.4 sind die
zwei, am häufigsten im Digitalbereich zu findenden, selbstsperrenden Varianten
dargestellt.
UB
UB
RL
UL
D
G
UGS
a) n-Kanal-Transistor
S
−UDS
G
D
UDS
S
−UGS
RL
UL
b) p-Kanal-Transistor
Abbildung 3.4: Selbstsperrender n-Kanal- und p-Kanal-MOS-Transistor
Die beiden dargestellten Varianten a) und b) sind jeweils nicht leitend, wenn
die Spannung UGS zwischen Gate und Source 0 V beträgt. Für jeden Transistor
6
In der Digitaltechnik wird UB als logische 1, GN D als logische 0 interpretiert (positive
Logik)
102
Schwellenspannung
n-Kanal-Transistor
p-Kanal-Transistor
CMOS
Kapitel 3. Speicherorganisation
existiert eine Schwellenspannung7 Uth , bei deren Überschreitung (n-Kanal) bzw.
deren Unterschreitung (p-Kanal) dieser leitend wird.
Beim n-Kanal-Transistor gilt: 0 V < Uth < UB . Ist nun UGS < Uth , so ist der
Widerstand zwischen Drain und Source (RDS ) sehr groß. Es fließt kein Strom
durch den Transistor (IDS = 0 A); der Transistor sperrt. Wird die Spannung
UGS soweit erhöht, dass diese Uth übersteigt, wird der Transistor leitend. Der
Widerstand des Transistors geht gegen 0 (RDS ≈ 0 Ω). Die Spannung UDS
zwischen Drain und Source sinkt auf etwa 0 V. Somit kann ein Strom (IDS > 0 A)
von Drain zu Source fließen.
Beim p-Kanal-Transistor wird mit den gleichen Spannungsquellen gearbeitet,
nur ist nun die Spannung zwischen der Betriebsspannung UB und dem GateAnschluss relevant. Liegt eine Spannung von UB am Gate-Eingang an, so wird
eine Spannung UGS = 0 V gemessen und der Transistor sperrt. Wird der GateEingang auf GN D gesetzt, wird man eine negative Spannung (UGS = −UB )
messen. Uth liegt somit im negativen Bereich: −UB < Uth < 0 V. Bei einer
Spannung UGS < Uth wird der Transistor leitend (RDS ≈ 0 Ω). Somit kann ein
Strom (IDS > 0 A) durch den Transistor fließen. Wird UGS größer als Uth , so
wird der Widerstand RDS sehr groß; der Transistor sperrt.
Stellt man sich nun UB als logische 1 und 0 V (GND) als logische 0 vor, so
gilt folgendes: Beim n-Kanal-Transistor a) führt das Anlegen einer logischen 1 an
das Gate dazu, dass dieser Transistor leitend wird. Beim Anlegen einer logischen
0 sperrt der Transistor. Beim p-Kanal-Transistor b) führt das Anlegen einer
logischen 1 dazu, dass der Transistor sperrt und das Anlegen einer logischen 0
führt dazu, dass der Transistor leitend wird. Dieses gegensätzliche Verhalten
wird nun für die Konstruktion von sog. Complementary-MOS-Schaltungen
(CMOS) verwendet, um z. B. Gatter (NICHT, UND, ODER usw.) zu erstellen.
Wir werden die beiden vorgestellten Typen von MOS-Transistoren im weiteren
Verlauf zur Realisierung von Speichergliedern verwenden.
Ein weiteres elektronisches Bauteil, welches Ihnen schon aus dem Physikunterricht bekannt sein sollte, ist der Kondensator. Ein Kondensator kann eine
bestimmte Ladung aufnehmen, wenn eine Spannung angelegt wird. Bis der
Kondensator aufgeladen ist, ist dieser noch leitend. Dies natürlich unter der
Voraussetzung, dass die angelegten Spannungen im zulässigen Bereich sind. In
Abbildung 3.5 ist das Schaltsymbol dargestellt.
Abbildung 3.5: Kondensator
Im weiteren Verlauf wird der Kondensator in Verbindung mit DRAMSpeicherzellen verwendet werden.
7
th für Threshold von Threshold Voltage: Schwellenspannung
3.2. Hauptspeicher
3.2.3
103
Static Random Access Memory
Statische Speicher, genannt Static Random Access Memory (SRAM), werden
dort eingesetzt, wo hohe Zugriffsgeschwindigkeiten notwendig sind. Dies sind z. B.
Cache-Speicher, siehe Abschnitt 3.3, aber auch Daten- und Programmspeicher
bei Mikrocontroller-Systemen. Eine SRAM-Zelle zur Speicherung von einem
einzigen Bit kann aus sechs Transistoren aufgebaut werden, siehe Abbildung 3.6.
Daher kommt auch der Name Six-Device-Zelle. Diese Art der Schaltung realisiert Six-Device-Zelle
ein bistabiles Kippglied, also ein Flipflop, wie Sie es bereits aus Kurs 1608 kennen.
Der Aufbau ist – im Vergleich zum weiter unten beschriebenen dynamischen
Speicher – recht aufwändig.
UB
Q
T2
Q
T3
T5
T4
T0
T1
A
B
B
Abbildung 3.6: SRAM-Zelle
Über eine Auswahlleitung A, auch als Adress- oder Wortleitung bezeichnet,
wird eine SRAM-Zelle ausgewählt. Die Adressleitung steuert den Zugriff auf die
Speicherzelle über zwei n-Kanal-Transistoren T4 und T5 , deren Bulk-Anschluss
auf Masse gelegt ist. Wird die Adressleitung auf logisch 1 gesetzt, werden T4
und T5 leitend und es kann ein schreibender oder lesender Zugriff erfolgen. In
der Abbildung 3.6 ist zu erkennen, dass zwei Bitleitungen B und B existieren,
die über die beiden Transistoren T4 und T5 mit der eigentlichen Speicherzelle
verbunden sind. B ist dabei das Bit, welches gelesen oder geschrieben werden
soll, und B das invertierte Bit. Auf den ersten Blick mag es erscheinen, dass
eine Bitleitung B ausreichend wäre. Allerdings wäre ein Umschalten in den
jeweils anderen Zustand je nach Zustand des Flip-Flops nicht mehr möglich.
Der geneigte Leser sei dazu angehalten, dies selbst anhand der Abbildung
nachzuvollziehen.
104
Kapitel 3. Speicherorganisation
Wir betrachten nun zuerst den Schreibvorgang: Um eine logische 1 zu
SRAMspeichern, wird die Leitung B auf 1 gesetzt, B auf 0. Die Speicherzelle wird
Schreibvorgang
ausgewählt, indem A auf 1 gesetzt wird. Wenn wir nun der Verschaltung von
B auf der linken Seite der Abbildung folgen, erkennen wir, dass das Signal
über T5 in die eigentliche Speicherzelle übergeht und an den Gates von T2
und T0 anliegt. Dabei wird der p-Kanal-Transistor T2 oben rechts sperren und
der n-Kanal-Transistor T0 unten rechts leiten. Somit wird eine Verbindung
zu logisch 0 hergestellt. Auf der anderen Seite liegt über B eine logische 0
an dem p-Kanal-Transistor T3 oben links und an dem n-Kanal-Transitor T1
unten links an. T3 wird somit leitend, T1 sperrend. Wird nun A auf 0 gesetzt,
bleibt der Zustand erhalten (Q = 1; Q = 0), denn die geschalteten Transistoren
verursachen eine identische Ansteuerung. Beim Schreiben einer logischen 0 ist
das Verhalten analog, nur dass die Werte für B und B vertauscht sind. Die
Schreibdaten werden B über einen Inverter zugeführt.
SRAM-Lesevorgang
Beim Lesen werden die Bitleitungen über einen entsprechenden Port mit
einem Baustein zum Lesen verbunden. Die Adressleitung A wird auf logisch 1
gesetzt und somit wird B bzw. B genau auf den entsprechend vorab gespeicherten
Pegel gezogen. Dabei bleibt die Information in der Speicherzelle beim Auslesen
– im Gegensatz zum dynamischen Speicher – erhalten. Ist der Lesevorgang
abgeschlossen, wird A wieder auf logisch 0 gesetzt. Zum Lesen ist grundsätzlich
eine Leitung B ausreichend.
3.2.4
Dynamic Random Access Memory
Der Aufbau einer Speicherzelle für ein einzelnes Bit ist beim dynamischen
Speicher einfacher als beim SRAM. Es werden nur zwei elektronische Bauteile –
ein Kondensator als eigentlicher Speicher und ein Transistor zur Ansteuerung
– benötigt. In Abbildung 3.7 ist der Aufbau einer dynamischen Speicherzelle
schematisch dargestellt.
B
A
Abbildung 3.7: DRAM-Zelle
Der Kondensator, der eine bestimmte Ladung in Form von Elektronen bzw.
Defektelektronen8 aufnehmen kann, ist über einen n-Kanal-Transistor mit der
8
Ein Defektelektron (oder Loch) ist ein gedachtes postiv geladenes Teilchen, welches
aufgrund des Fehlens eines Elektrons existiert.
3.2. Hauptspeicher
Bitleitung B verbunden. Diese wird sowohl zum Lesen als auch zum Schreiben
verwendet. Der Transistor wird über die Adressleitung (oder Wortleitung) A
angesteuert. Der Bulk-Anschluss ist mit der Masseleitung verbunden.
Beim Schreiben wird die Adressleitung A auf logisch 1 geschaltet. Dadurch
wird der Transistor leitend und der Kondensator ist mit der Bitleitung elektrisch
verbunden. Soll eine logische 1 geschrieben werden, wird B auf eine positive
Spannung gebracht und der Kondensator lädt sich auf. Für eine logische 0 wird
die Bitleitung auf Masse-Potential gebracht und eine möglicherweise vorhandene
Ladung fließt über den Transistor ab. Nach der zum Schreiben des Inhalts notwendigen Zeit (Lade- bzw. Entladezeit des Kondensators) wird die Adressleitung
auf 0 geschaltet und der Kondensator von der Bitleitung getrennt.
Beim Lesen wird die Bitleitung B mit einem Eingang (Schwellwertschalter,
Schmitt-Trigger) verbunden. Die Adressleitung A wird auf logisch 1 gesetzt,
um den Transistor mit der Bitleitung B zu verbinden. Auf diese Weise kann
der Ladungszustand festgestellt werden. Ein geladener Kondensator wird als
logische 1 interpretiert, ein entladener Kondensator als logische 0. Auch hier
gilt wieder, dass die Zuordnung von 0 und 1 frei wählbar ist.
Kondensatoren verlieren über die Zeit ihre Ladung. D. h., Ladungsträger –
Elektronen – fließen als Leckströme vom Kondensator ab. Daher kommt auch
die Bezeichnung dynamisch. Nach einer gewissen Zeit muss der Speicherzustand
erst gelesen und dann erneut geschrieben werden. Für übliche DRAM-Speicher
liegt diese Refresh-Zeit bei etwa 60 Millisekunden.
Obwohl der Eingang einen hohen Eingangswiderstand hat, verliert der Speicher seine Informationen durch das Auslesen. Wird also eine Speicherstelle
gelesen, so muss der Inhalt anschließend wieder eingeschrieben werden. Der
Speicher verliert – wie auch SRAM-Zellen – nach dem Abschalten der Betriebsspannung nach einiger Zeit (dies können einige Minuten sein) seine gespeicherte
Information, da sich die Kondensatoren entladen.
3.2.5
105
DRAMSchreibvorgang
DRAM-Lesevorgang
dynamisch
Refresh
flüchtig
Flash-Speicher
Die letzte Speichertechnologie, die hier vorgestellt wird, ist der Flash-Speicher.
Dieser Speicher verliert im Gegensatz zum SRAM- und DRAM-Speicher seine
Informationen nach dem Abschalten der Betriebsspannung nicht. Es handelt
sich also um nicht-flüchtigen Speicher. Flash-Speicher werden u. a. als Massen- nicht-flüchtig
speicher als Ersatz für drehende, elektromagnetische Festplattenspeicher und als
Programmspeicher von Mikrocontroller-Systemen eingesetzt. Die Bezeichnung
Flash kommt daher, dass diese Speicher sehr schnell – eben blitzartig – gelöscht blitzartig
werden können.
Flash-Speicher werden in verschiedenen Organisationsformen angeboten –
NAND- und NOR-Flash-Speicher – auf die hier aber nicht weiter eingegangen NAND und NOR
werden soll. Es sei nur darauf hingewiesen, dass NAND-Speicher üblicherweise für Massenspeichermedien und NOR-Speicher für Programmspeicher bei
Mikrocontroller-Systemen verwendet werden.
106
Floating Gate
Kapitel 3. Speicherorganisation
Ein einzelnes Bit in einer Flash-Zelle wird in einem Floating-Gate-MOSTransistor (FG-MOSFET) gespeichert. Dabei handelt es sich um eine Erweiterung eines klassischen n-Kanal-MOSFET, in welchem eine weitere, nicht
mit anderen Komponenten verbundene, Elektrode im Isolator zwischen Gate
und Substrat eingebracht wird, das sog. Floating Gate. Dieses besteht aus
einem elektrisch leitfähigen Material, z. B. aus einem dotierten Halbleiter. In
Abbildung 3.8 ist der Aufbau dargestellt.
G
Floating Gate
Isolator
S
D
Substrat (Bulk)
Abbildung 3.8: Floating-Gate-MOS-Transistor, n-Kanal
Der Gate-Anschluss ist bei einem MOSFET durch einen Isolator vom Substrat getrennt. Es fließt bei angelegter Gate-Spannung kein (nennenswerter)
Strom. Über den Gate-Anschluss wird vielmehr ein leitender Kanal zwischen
den Anschlüssen für Source und Drain etabliert. Bei entsprechender Spannung
zwischen diesen kann ein Strom fließen.
Auf das Floating Gate kann nun durch bestimmte physikalische Effekte
eine Ladung in Form von Elektronen aufgebracht bzw. wieder entfernt werden9 .
Solange keine Elektronen auf dem Floating Gate sind, verhält sich der Transistor
wie ein üblicher MOSFET.
Wird das Floating Gate nun mit Elektronen angereichert, so besteht eine
negative Vorspannung. Die Standard-Spannung am Gate-Anschluss reicht dann
nicht mehr aus, um einen leitenden Kanal zwischen Source- und Drain-Anschluss
zu erzeugen. Diese zwei Zustände können festgestellt werden und somit kann
der FG-Transistor als Speicher für ein Bit verwendet werden10 . Die Ladung auf
dem Floating-Gate bleibt dauerhaft erhalten. Ein Refresh wie beim DRAM ist
nicht notwendig. Die Entladung des Floating Gates erfolgt durch das Anlegen
einer erhöhten Spannung.
3.3
Cache-Speicher und -Organisation
Die Geschwindigkeit von Prozessoren liegt schon seit vielen Jahren weit über
der Zugriffszeit, die moderne Hauptspeicher erreichen können. Folglich müssen
9
Auf diese Effekte wird im Kontext dieses Kurses nicht weiter eingegangen
Es existieren auch Realisierungen von Floating-Gate-Transistoren, die mehr als ein Bit
speichern können. Diese Abwandlungen werden hier aber nicht weiter betrachtet.
10
3.3. Cache-Speicher und -Organisation
Maßnahmen getroffen werden, damit der Prozessor rechtzeitig mit Instruktionen und Daten versorgt wird. Ansonsten wären die hohen Geschwindigkeiten
moderner Prozessoren nicht auszunutzen, weil diese ständig auf Befehle und
Daten aus dem Speicher warten müssten.
Hier setzen die in diesem Abschnitt behandelten Cache-Speicher an. Bei
diesen handelt es sich um äußerst schnelle Speicher (meist auf Basis der aufwändigen SRAM-Technologie), die nah am Prozessorkern angeordnet sind und auf
die mit entsprechender Geschwindigkeit zugegriffen werden kann. Wesentliches
Merkmal der Cache-Speicher ist deren Organisation und der damit zusammenhängende assoziative Zugriff. Da die Cache-Speicher deutlich kleiner (im Bereich Assoziativität
von wenigen Kilobyte bis zu einigen Megabyte) als Hauptspeicher (im Bereich
von Gigabyte bis zu Terabyte) sind, kann nur ein kleiner Ausschnitt aus dem
eigentlichen Hauptspeicher im Cache-Speicher abgelegt werden.
Im Folgenden werden – nach einigen Definitionen in Unterabschnitt 3.3.1 –
drei verschiedene Realisierungsmöglichkeiten von Cache-Speichern behandelt:
Den Anfang bilden die vollassoziativen Cache-Speicher (Full Associative Cache)
im Unterabschnitt 3.3.2, die in Bezug auf die Zugriffsgeschwindigkeit und Trefferrate die höchste Leistung erbringen, dafür aber nur kleine Speicher ermöglichen.
Das Gegenstück sind die direkt-abgebildeten Cache-Speicher (Direct Mapped
Cache) im Unterabschnitt 3.3.3, mit denen große Cache-Speicher realisiert werden, bei denen aber die Trefferrate aufgrund der Befüllung deutlich geringer
ist. Die n-Wege-satzassoziativen Cache-Speicher (n-Way Set-Associative Cache)
im Unterabschnitt 3.3.4 stellen einen guten Mittelweg aus den beiden anderen
Implementierungsmöglichkeiten dar. Mit diesen können recht große Caches
realisiert werden, da sie eine relativ gute Speicherplatzausnutzung haben.
Hinweis
Zur Vorbereitung sollten Sie sich nun im Simulator MARS mit den verschiedenen Realisierungen und deren Auswirkungen vertraut machen. Hierfür
steht ein kleines Assembler-Programm zur Verfügung, dass einen MergesortAlgorithmus implementiert. Gehen Sie bitte wie folgt vor:
1. Starten Sie den MIPS-Simulator MARS
2. Laden Sie die Datei mergesort.asm. Sie finden diese in der LVU.
3. Machen Sie sich mit dem Code vertraut, der einen Mergesort-Algorithmus als Assembler-Programm implementiert. Sollte Ihnen der
Mergesort-Algorithmus nicht bekannt sein, eignen Sie sich notwendiges
Wissen über Drittliteratur (Internet) an.
4. Assemblieren Sie das Programm durch das Betätigen des entsprechenden Icons:
. MARS wechselt automatisch in den Ausführungsmodus
(Execute).
107
108
Kapitel 3. Speicherorganisation
5. Machen Sie sich mit den Möglichkeiten des Debuggings vertraut (Ausführung, Einzelschritt, Rückschritt usw.):
. Es
empfiehlt sich, einen Breakpoint auf das Ende des Programmes zu
setzen:
6. Nachdem Sie sich nun mit der Ausführung des Assembler-Programms
vertraut gemacht haben, wenden Sie sich nun einem Zusatzwerkzeug,
nämlich dem Data Cache Simulation Tool, zu.
Sie können das Werkzeug über das Hauptmenü Tools → Data Cache
Simulator starten. Vergessen Sie nicht, das Werkzeug mit dem Simulator durch einen Klick auf Connect to MIPS mit dem Simulator zu
verbinden.
7. Experimentieren Sie etwas mit den Einstellmöglichkeiten des CacheSimulators. Wenn Sie dies abgeschlossen haben, prüfen Sie die Trefferraten bei folgenden Einstellungen:
Placement
Direct Mapping
Fully Associative
N-way Set Associative
Set Size
(1)
(8)
4
Blocks
8
8
8
Block Size
4
4
4
Hit Rate?
3.3. Cache-Speicher und -Organisation
3.3.1
109
Voraussetzungen und Begriffsdefinitionen
Cache-Speicher bilden immer nur einen kleinen Teil des übergeordneten Speichers
(bzw. Caches höherer Stufe oder Hauptspeicher) ab. Damit ein Cache-Speicher
überhaupt eine Beschleunigung bei den Speicherzugriffen ermöglicht, muss
die Trefferrate entsprechend hoch sein. Die Trefferrate ist der prozentuale Trefferrate
Anteil an den Speicherzugriffen, zu denen der entsprechende Eintrag im Cache
aufgefunden wird, siehe weiter unten.
Die Trefferrate hängt im Wesentlichen von der Lokalität der Zugriffe ab: Lokalität
• Zeitliche Lokalität (auch temporale Lokalität): Der Zugriff auf die
gleichen Daten bzw. den gleichen Programmcode wiederholt sich. Entsprechend steigt die Wahrscheinlichkeit, dass auf Daten (oder Programmcode)
erneut zugegriffen wird, auf die bzw. auf den in der Vergangenheit bereits
zugriffen wurde.
Beispielsweise werden innerhalb einer Schleife immer wieder die gleichen
Instruktionen ausgeführt. Auch auf Daten, wie z. B. eine Zählervariable
im Speicher, werden sehr wahrscheinlich mehrfach, zeitlich nahe beieinanderliegend, zugegriffen.
• Örtliche Lokalität (auch spatiale Lokalität): Sowohl Daten als auch
Programmcode werden in einer gewissen Ordnung im Speicher abgelegt.
So sind die Elemente einer Matrix üblicherweise in benachbarten Speicherbereichen hinterlegt. Erfolgt ein Zugriff auf eine bestimmte Adresse, ist
es wahrscheinlich, dass auch auf eine „nahe gelegene“ Adresse zugegriffen
wird (d. h., die Differenz beider Adressen ergibt eine kleine Zahl). Bei
Programmcode ist es so, dass die nachfolgenden Befehle in den nächsten
Bytes zu finden sind, sofern kein Sprung ausgeführt wurde.
Ein wichtiger Punkt bezüglich Cache-Speichern betrifft die Art und Weise, wie Inhalte innerhalb dieser Speicher lokalisiert werden. Während normale
Hauptspeicher ortsadressiert sind, d. h., zu einer gegebenen Adresse gehört ein
fester Ort – immer an der gleichen „Stelle“ –, sind Cache-Speicher inhaltsadressiert bzw. es wird mit einer Kombination aus Inhalts- und Ortsadressierung auf
das gesuchte Datum zugegriffen. Entsprechend wird ein Teil der Adresse, das sog.
Tag verwendet, um festzustellen, ob das gesuchte Datum im Cache-Speicher
enthalten ist. Dabei kann dieses ggf. an einer beliebigen Stelle hinterlegt sein
(vollassoziativer Cache-Speicher) oder an einer (teilweise) fest vorgegebenen
Stelle liegen (n-Wege-satzassoziativer oder direkt abgebildeter Cache-Speicher).
Diese Art des Zugriffs wird auch als assoziativer Speicherzugriff bezeichnet.
Der Cache-Speicher besteht aus einer bestimmten Anzahl von Einträgen.
Jeder dieser Einträge enthält wiederum die eigentliche Daten, ein Tag und
Zugriffs-/Verwaltungsinformationen:
Ortsadressierung
Inhaltsadressierung
Tag
Assoziativer
Speicherzugriff
• Der Cache-Block ist der Speicherplatz für die im Cache-Speicher ab- Cache-Block
gelegten (also zwischengespeicherten) Daten (oder Programmcode). In
110
Cache-Zeile
Tag
Gültigkeit
Einlagerung
Verdrängung
Satz
Vollassoziativer
Cache
Kapitel 3. Speicherorganisation
einen Cache-Block passt genau eine Cache-Zeile (Cache Line), die zwischen Hauptspeicher und Cache-Speicher je nach Bedarf hin oder her
kopiert wird. Hier werden üblicherweise mehrere Speicherwörter hinterlegt
(Größenordnung 16 bis 128 Byte). Mindestens ein Byte oder Wort der
Cache-Zeile wurde, wenn es im Cache-Speicher liegt, in der Vergangenheit
verwendet. Dabei ist zu beachten, dass neben dem eigentlich zugegriffen
Datum auch die weiteren Daten eingelagert werden (also die gesamte
Cache-Zeile).
• Das Tag dient nun zur Identifizierung des Eintrags und zur Prüfung, ob
ein gesuchter Eintrag im Cache vorhanden ist. Die Art und Weise, wie dies
erfolgt, ist je nach Implementierung des Cache-Speichers unterschiedlich.
Siehe hierzu die Unterabschnitte 3.3.2 bis 3.3.3.
• Die Zugriffs- und Verwaltungsinformationen enthalten z. B. Angaben darüber, ob eine Cache-Zeile gültig ist oder verändert wurde. Zudem
sind Informationen für die Verdrängung von Einträgen hinterlegt. Zumindest je ein Bit für die Gültigkeit (V-Bit, valid) des Eintrags als auch ein
Bit, welches anzeigt, ob die Cache-Zeile verändert wurde (D-Bit, dirty)
ist i.u. a.A. aufzufinden.
Wird nun ein Datum an einer bestimmten Adresse angefragt, erfolgt zuerst
eine Anfrage an den Cache, ob ein Eintrag zu dieser Adresse vorliegt. Dazu wird
der Tag-Teil der Adresse mit dem gespeicherten Tag verglichen und darüber
hinaus die Gültigkeit des Eintrags über die Verwaltungsinformationen geprüft.
Wenn das Tag gefunden und der Eintrag gültig ist, wird das angefragte Datum
aus dem Cache – und damit deutlich schneller als aus dem Hauptspeicher – dem
Prozessor zur weiteren Verarbeitung zur Verfügung gestellt. Sollte es keinen
Eintrag geben, muss im Falle eines Lesezugriffs die entsprechende Cache-Zeile
in den Cache-Speicher eingelagert, also aus dem Hauptspeicher (oder einer
höher liegenden Cache-Hierarchie-Ebene) geladen werden. Es kommt auch vor,
dass Cache-Zeilen mit neuen Einträgen überschrieben werden müssen. Man
spricht von Verdrängung von Cache-Einträgen. Hierbei ist zu beachten, dass
veränderte Einträge auch zurück in den Speicher geschrieben werden müssen,
bevor diese verdrängt werden. Beim Schreibzugriff gibt es sowohl die Möglichkeit,
den Cache-Speicher mit der zugegriffen Cache-Zeile zu befüllen als auch nur
den Hauptspeicher zu verändern, siehe Unterabschnitt 3.3.6.
Die Cache-Blöcke werden zu Sätzen zusammengefasst, die dann jeweils für
eine bestimmte Adresse zuständig sind (d. h., den Inhalt zu einer festgelegten
Adresse findet man nur innerhalb eines definierten Satzes). Ist nun m die Anzahl
der Cache-Blöcke und n die Anzahl der Cache-Blöcke pro Satz (Assoziativität), so besteht ein Cache-Speicher aus m
Sätzen. In Abbildung 3.9 ist dieser
n
Zusammenhang graphisch dargestellt.
Bei vollassoziativen Cache-Speichern gibt es nur einen Satz, der alle CacheBlöcke enthält (m = n). Somit kann der Inhalt einer jeden Adresse an einer
beliebigen Stelle (in einem beliebigen Cache-Block) abgelegt werden. In der oben
3.3. Cache-Speicher und -Organisation
n-Wege assoziativ
m
n
Sätze, n Cache-Blöcke/Satz
Dekoder
m Sätze, 1 Cache-Block/Satz
Satz
Satz
Dekoder
direkt abgebildet
Satz
vollassoziativ
1 Satz, m = n Cache-Blöcke
111
Abbildung 3.9: Sätze, Cache-Zeilen und Assoziativität bei verschiedenen CacheOrganisationen
stehenden Abbildung hat der vollassoziative Cache-Speicher zehn Cache-Blöcke;
in jedem kann eine Cache-Zeile mit Inhalten an einer beliebigen Speicheradresse
abgelegt werden. Bei direkt abgebildeten Cache-Speichern enthält jeder Satz
genau einen Cache-Block, d. h., n = 1 und es gibt genauso viele Sätze wie CacheBlöcke. Ein Datum an einer bestimmten Adresse kann in diesem Fall nur an einer
einzigen Stelle im Cache-Speicher abgelegt werden. Bei n-Wege-satzassoziativen
Cache-Speichern liegt n üblicherweise zwischen zwei und acht (maximal m2 ).
Jeder Satz enthält somit eine bestimmte Anzahl (Vielfaches von 2) CacheBlöcken. In jedem dieser Sätze kann der Inhalt zu einer bestimmten Adresse an
verschiedenen Stellen hinterlegt sein. In Abbildung 3.9 besteht jeder Satz aus
vier Cache-Blöcken. Somit gibt es für eine bestimmte Adresse vier Möglichkeiten
innerhalb eines für diese Adresse festgelegten Satzes, die dazugehörige CacheZeile abzulegen. Dies ist in Tabelle 3.1 nochmals zusammengefasst.
Direkt abgebildeter
Cache
N-WegeSatzassoziativer
Cache
Tabelle 3.1: Sätze und Assoziativität verschiedener Cache-Implementierungen
Organisation
Anzahl der Sätze Assoziativität
vollassoziativ
1
m(= n)
direkt abgebildet
m
1
m
n-Wege-satzassoziativ
n
n
Eine Problematik, die insbesondere bei direkt abgebildeten, aber auch bei nWege-satzassoziativen Cache-Speichern auftreten kann, ist das Cache-Flattern Cache-Flattern
(Cache Thrashing). Hierbei wird im schnellen Wechsel immer wieder auf Adressen
mit gleichem Tag-Anteil zugegriffen. Das führt zu einem ständigen Verdrängen
von Einträgen, die in kurz danach erfolgenden Zugriffen wieder benötigt werden.
Dies ist insbesondere dann problematisch, wenn – wie bei direkt abgebildeten
112
Cache-Treffer
Trefferrate
Kapitel 3. Speicherorganisation
Caches häufig der Fall – der Cache-Speicher noch freie Einträge zur Verfügung
hat, diese aber für die aktuell in Verwendung befindlichen Adressen nicht genutzt
werden können.
Ideal für die Ausnutzung und damit auch für die Zugriffsgeschwindigkeit
sind folglich vollassoziative Cache-Speicher, da diese ohne Beschränkung bezüglich der Platzierung der Cache-Zeile genutzt werden können. Dafür sind
der Implementierungsaufwand und damit die Kosten zur Realisierung erheblich
höher. Entsprechend können auf diese Weise nur kleine Speicher mit einigen
hundert Bytes Inhalt realisiert werden. Das Gegenstück – der direkt abgebildete
Cache-Speicher – ermöglicht zwar aufgrund der einfacheren Implementierung
deutlich größere Speicher (im Megabyte-Bereich), ist allerdings bei Weitem
weniger effizient: Viele Speicherstellen bleiben ungenutzt und die Gefahr für
das Cache-Flattern steigt deutlich an. Ein guter Kompromiss wird durch die
Verwendung von n-Wege-satzassoziativen Speichern erreicht: Der Implementierungsaufwand ist vertretbar gering und die Effizienz in der Ausnutzung ist
hoch.
Wird ein Datum an einer bestimmten Speicheradresse angefragt, wird in
einem ersten Schritt der Cache-Speicher geprüft. Sollte sich das gesuchte Speicherwort im Cache-Speicher befinden (Tag der Adresse passt zu einem Tag im
Cache-Speicher), dann hat man einen Cache-Treffer (Cache Hit). Das Gegenstück wird mit Cache-Fehlzugriff (Cache Miss) bezeichnet. Die Trefferrate
T (Hit Rate) gibt an, wie viele der angefragten Adressen im Cache-Speicher zu
einem Treffer führten. Bei einer Trefferrate von T = 0,9 bedeutet dies, dass 90 %
der Anfragen aus dem Cache-Speicher bedient werden konnten. Die Trefferrate
sollte sehr hoch liegen, damit sich der Mehraufwand der Nutzung eines CacheSpeichers lohnt. Dass Gegenstück zur Trefferrate ist die Fehlzugriffsrate (Miss
Rate) M , die als M = 1 − T definiert ist.
Selbsttestaufgabe 3.1 Entwurfsalternativen bei zweistufigem Cache
Gegeben sei ein Computersystem mit einer zweistufigen Cache-Hierarchie. Für
die Realisierung der Cache-Speicher sollen zwei Entwurfsalternativen – A und B –
miteinander verglichen werden, deren mittlere Zugriffszeiten in folgender Tabelle
zusammengefasst sind. Dabei stehen tL1 und tL2 für die mittlere Zugriffszeit auf
den L1- bzw. L2-Cache.
Alternative
A
B
tL1
10 ns
12 ns
tL2
30 ns
25 ns
Die mittlere Zugriffszeit auf den Hauptspeicher tHS sei bei beiden Entwurfsalternativen gleich und betrage 100 ns.
a) Bestimmen Sie die Gleichung zur Berechnung der mittleren SpeicherZugriffszeit, wenn die Trefferraten für den L1-Cache-Speicher mit h1 und
für den L2-Cache-Speicher mit h2 bezeichnet werden.
3.3. Cache-Speicher und -Organisation
113
b) Anhand einer repräsentativen Sammlung anwendungsspezifischer BenchmarkProgramme wurden folgende Trefferraten ermittelt. Dabei stehen hL1 und
hL2 für die Trefferraten des L1- bzw. L2-Caches.
Alternative
A
B
hL1
70 %
80 %
hL2
50 %
30 %
Berechnen Sie für die gegebenen Trefferraten die konkreten mittleren
Zugriffszeiten.
Ein Konflikt tritt genau dann auf, wenn eine weitere Cache-Zeile in den Konflikt
Cache-Speicher geladen werden soll, dieser aber bereits belegt ist (beim direkt
abgebildeten sofern der Satz, der die entsprechende Cache-Zeile aufnehmen
soll, bereits gefüllt ist; beim n-Wege-satzassoziativen, wenn alle Cache-Blöcke
innerhalb des entsprechenden Satzes bereits belegt sind).
Im weiteren Verlauf werden die hier angesprochenen Realisierungsmöglichkeiten im Detail – unter der Annahme einer 32-Bit-Speicherarchitektur –
beschrieben. Anschließend wird auf die Verdrängungsproblematik sowie auf
die Behandlung von Schreibzugriffen eingegangen. Darüber hinaus wird die
Platzierung der Cache-Speicher in Bezug auf die virtuelle Speicherverwaltung
(siehe hierzu Abschnitt 3.4) dargestellt. Abschließend werden Probleme bei der
Kohärenz-Erhaltung diskutiert (insbesondere bei Multiprozessor- und MulticoreSystemen sowie Systemen, die den direkten Speicherzugriff erlauben).
3.3.2
Vollassoziativer Cache-Speicher
Bei einem vollassoziativen Cache-Speicher wird die Speicheradresse in zwei Teile,
das Tag und die Byte-Auswahl aufgeteilt, siehe hierzu Abbildung 3.10.
Tag
31
Byte
5 4
0
Abbildung 3.10: Adressaufteilung beim vollassoziativen Cache-Speicher
In der Abbildung wird die Adresse des gesuchten Datums in zwei Felder
aufgeteilt: Das Tag, das die oberen 27 Bits umfasst sowie den Teil für die ByteAuswahl, die unteren fünf Bits. Das Tag wird verwendet, um im Cache-Speicher
zu prüfen, ob die entsprechende Cache-Zeile mit dem identischen Tag bereits in
den Cache-Speicher geladen wurde. Ist dies der Fall, erfolgt der Zugriff direkt auf
den Cache-Speicher, ansonsten wird das Datum (und die Cache-Zeile) aus dem
114
Kapitel 3. Speicherorganisation
Speicher geholt und dem Prozessor (sowie die Cache-Zeile dem Cache-Speicher)
zugeführt.
Die Byte-Auswahl gibt schon einen Hinweis auf die Anzahl der Bytes in
jeder Cache-Zeile, nämlich 25 = 32 Bytes. Somit umfasst in diesem Beispiel jede
Cache-Zeile 32 Byte bzw. acht 32-Bit-Wörter. In Abbildung 3.11 ist der Ablauf
des Zugriffs auf eine bestimmte Cache-Zeile bei der gegebenen Speicheradresse
0xA4F37DE4 dargestellt.
Tag
Byte
10100100111100110111110111100100
31
5 4
0
001002 = 410
Byte 4
=?
Tag: 0x5279BEF
Byte 0
Tag-RAM
Byte 4
Byte 31
Cache-Lines
Abbildung 3.11: Zugriff auf eine Cache-Zeile beim vollassoziativen CacheSpeicher
Tag-RAM
In dem Beispiel wird ein Eintrag im Cache-Speicher aufgefunden, da ein Tag
im Cache mit dem Tag in der Speicheradresse übereinstimmt (die Binärdarstellung des Tags in der gegebenen Adresse ist identisch zum Tag 0x5279BEF
im Cache-Speicher). Der Tag-RAM (Tag Random Access Memory) ist der
Speicherbereich im Cache, der sämtliche Tags zu den entsprechenden CacheZeilen speichert. Wenn – wie im Fall der gezeigten Abbildung 3.11 – ein Eintrag
im Cache-Speicher gefunden wird, kann im nächsten Schritt über die ByteAuswahl das entsprechende Byte selektiert werden. Aufgrund der Aufteilung
in Tag- und Byte-Anteil von 27:5 werden zu jedem Tag 32 Byte abgelegt. In
der Abbildung sind hierbei drei Byte (Byte 0, Byte 4 und Byte 31) dargestellt.
Die Punkte dazwischen deuten an, dass alle 32 Byte hier hinterlegt sind. In der
gegebenen Byte-Auswahl wird Byte 4 selektiert.
Bei einem vollassoziativen Cache-Speicher muss das Tag, der in der Adresse
übergeben wird, gleichzeitig mit allen Tags im Tag-RAM verglichen werden.
Entsprechend aufwändig gestaltet sich die Realisierung des Cache-Speichers,
vgl. Abbildung 3.12.
Beim Zugriff auf den Cache-Speicher werden zunächst sämtliche gespeicher-
3.3. Cache-Speicher und -Organisation
115
Adresse
Tag
Byte
31
2
0
Tag
V*
Daten/Cache-Lines
=
=
=
1
=
Treffer (Hit)
=
=
In einem Schritt
werden alle Tags
im Cache mit dem
Tag der Adresse
verglichen.
=
=
=
=
=
=
Vergleicher
ausgewählte Cache-Zeile
3
4
*Verwaltungs-Bits
CPU
Abbildung 3.12: Ablauf einer Anfrage mit Treffer im vollassoziativen CacheSpeicher
te Tags aus dem Tag-RAM mit dem Tag der angefragten Adresse verglichen
(1). Hieran wird ersichtlich, worin die technologische Komplexität eines vollassoziativen Cache-Speichers begründet ist: Es wird für jeden Cache-Block ein
Vergleicher benötigt. Entsprechend aufwändig ist auch die vorgeschaltete Logik,
um das Tag der Adresse (auch einige Bits umfassend) an die große Anzahl
an Vergleichern anzuschließen. Sollte ein Treffer festgestellt werden, also ein
Eintrag im Cache mit dem Tag der Adresse übereinstimmen – in der Abbildung
ist dies durch die Hervorhebung des Vergleichers sowie der entsprechenden
Cache-Zeile gekennzeichnet – muss noch die Gültigkeit (V-Bit) des Eintrags
über die Verwaltungsbits geprüft werden (2). Ist beides – Übereinstimmung der
Tags und Gültigkeit – gegeben, wird die Cache-Zeile ausgewählt (3). Mittels
der Byte-Auswahl kann das entsprechende Byte (oder Wort) anschließend dem
Prozessorkern zugeführt werden (4)11 .
Solange der vollassoziative Cache-Speicher nicht vollständig gefüllt ist, können ohne Probleme weitere, noch nicht enthaltene Cache-Zeilen eingelagert
werden. Ist der Cache-Speicher allerdings vollständig gefüllt, muss vor der Einlagerung einer neuen Cache-Zeile Platz geschaffen werden, also eine andere
verdrängt werden. Es sollte eine Cache-Zeile ausgewählt werden, die sinnvollerweise aus dem Cache-Speicher entfernt werden kann. Auf diese sollte möglichst
nicht sofort wieder zugegriffen werden (zumindest sollte die Wahrscheinlichkeit
entsprechend gering sein). Mit der Verdrängung und den dazugehörigen Verdrängungsstrategien von Cache-Einträgen beschäftigt sich Unterabschnitt 3.3.6.
11
Üblicherweise wird bei modernen Prozessoren oftmals die gesamte Cache-Zeile in ein
Register des Prozessors geladen und von dort weiterverarbeitet. Für die Vorstellung der
Funktionsweise ist es allerdings ausreichend, davon auszugehen, dass nur das ausgewählte
Byte übertragen wird
116
Kapitel 3. Speicherorganisation
Die Vor- und Nachteile des vollassoziativen Cache-Speichers sind hier nochmals zusammengefasst:
• Vorteile
– Cache-Einträge können beliebig eingeordnet werden.
– Höhere Cache-Auslastung, Füllung bis zu 100 %.
– Hohe Trefferraten erreichbar.
• Nachteile
– Sehr komplexe Implementierung.
– Sehr hoher Hardware-Aufwand.
– Nur kleine Caches mit wenigen Kilobyte Speicher realisierbar
– Verdrängungslogik notwendig.
3.3.3
Direkt abgebildeter Cache-Speicher
Das Gegenstück zum oben beschriebenen vollassoziativen, ist der direkt abgebildete Cache-Speicher. Hier kann ein bestimmtes Speicherwort nur an einer
festgelegten Stelle im Cache-Speicher abgelegt werden, die über ein weiteres
Feld in der Adresse indiziert wird. Darunter leidet zwar die Cache-Ausnutzung,
aber die technische Implementierung wird deutlich vereinfacht. Die Aufteilung der Adresse beim direkt abgebildeten Cache-Speicher ist beispielhaft in
Abbildung 3.13 dargestellt.
Tag
31
Byte
Index
16 15
5 4
0
Abbildung 3.13: Adressaufteilung beim direkt abgebildeten Cache-Speicher
Index
Auch hier enthält die Adresse eine Byte-Auswahl (im Beispiel auch fünf Bit
und somit 32 Byte in jeder Cache-Zeile) und ein Tag, der hier aber auf 16 Bit
verkürzt ist. Dafür gibt es nun als weiteren Teil einen Index. Mit dem Index
wird im Falle des direkt abgebildeten Cache-Speichers genau ein Cache-Eintrag
(Cache-Zeile) ausgewählt. Im Beispiel sind für den Index 11 Bit vorgesehen; entsprechend können genau 211 = 2048 Cache-Einträge ausgewählt werden. Dieser
Zugriff mittels Index entspricht einer Ortsadressierung. Bei einem gewählten
Eintrag muss natürlich das Tag der gegebenen Adresse weiterhin mit dem gespeicherten Tag verglichen werden, um festzustellen, ob der hinterlegte Eintrag
auch der gesuchte ist. Die Größe des Cache-Speichers kann in diesem Fall direkt
aus der Aufteilung der Adresse abgelesen werden; diese beträgt 2#Index · 2#Byte ,
wobei das Doppelkreuz vor den Feldbezeichnern der Anzahl der Bits je Feld entspricht. Im obigen Beispiel sind dies also 211 · 25 = 216 = 65.536 Bytes = 64 kB.
3.3. Cache-Speicher und -Organisation
117
Adresse
Tag
Index Byte
31
3
0
Tag
V*
Daten/Cache-Lines
Dekoder
1
2
Treffer (Hit)
4
=?
Vergleicher
ausgewählte Cache-Zeile
5
*Verwaltungs-Bits
CPU
Abbildung 3.14: Ablauf einer Anfrage mit Treffer im direkt abgebildeten CacheSpeicher
Der Ablauf einer Anfrage an einen direkt abgebildeten Cache-Speicher ist in
Abbildung 3.14 dargestellt.
Beim direkt abgebildeten Cache-Speicher wird zunächst über einen IndexTeil innerhalb der Adresse ein bestimmter Satz ausgewählt, der ggf. die gesuchte
Cache-Zeile enthält (1). Im nächsten Schritt wird das Tag im Cache mit dem
Tag der Adresse verglichen (2). Zeitgleich kann die Gültigkeit des Eintrags
geprüft werden (3). Stimmt das Tag überein und ist der Eintrag gültig, wird
die Cache-Zeile ausgewählt (4) und über die Byte-Auswahl der Adresse kann
das entsprechende Byte dem Prozessorkern zugeführt werden (5)12 .
Im Gegensatz zum bereits beschriebenen vollassoziativen und dem in Unterabschnitt 3.3.4 vorgestellten n-Wege-satzassoziativen Cache-Speicher, ist beim
direkt abgebildeten Cache-Speicher keine Verdrängungsstrategie notwendig. Dies
liegt darin begründet, dass zu einer gegebenen Adresse nur ein Satz ausgewählt
wird und dieser genau eine Cache-Zeile aufnehmen kann. Ist an dieser Stelle
bereits eine gültige Cache-Zeile vorhanden, wird diese (und nur diese) verdrängt,
unabhängig davon, ob noch ungenutzter Platz im Cache-Speicher vorhanden
ist.
Die Vor- und Nachteile des direkt abgebildeten Cache-Speichers sind hier
nochmals zusammengefasst:
• Vorteile
– Sehr einfache Implementierung möglich.
– Geringer Hardware-Aufwand.
12
Bezüglich der Übertragung der gesamten Cache-Zeile gilt wiederum die Aussage, die beim
vollassoziativen Cache-Speicher getroffen wurde.
118
Kapitel 3. Speicherorganisation
– Große Cache-Speicher mit einigen Megabyte an Speicher realisierbar.
– Keine Verdrängungslogik notwendig.
• Nachteile
– Cache-Zeilen mit gleichem Index-Teil in der Adresse liegen an der
identischen Stelle im Cache.
– Cache-Flattern aufgrund wechselseitigem Ein- und Auslagern.
– Mäßige Trefferrate wegen deutlich schlechterer Auslastung.
3.3.4
n-Wege-satzassoziativer Cache-Speicher
Der n-Wege-satzassoziative Cache verbindet (teilweise) die Vorteile des vollassoziativen und des direkt abgebildeten Cache-Speichers und reduziert deren
Nachteile (geringe Komplexität im Hardware-Aufbau verbunden mit besserer
Auslastung/-nutzung des Speichers). Die Adressaufteilung entspricht der des
direkt abgebildeten Cache-Speichers, vgl. Abbildung 3.15.
Tag
31
Byte
Index
16 15
5 4
0
Abbildung 3.15: Adressaufteilung beim direkt abgebildeten Cache-Speicher
Im Gegensatz zum direkt abgebildeten Cache-Speicher, bei dem in jedem Satz
nur eine Cache-Zeile hinterlegt werden kann, ist es beim n-Wege-satzassoziativen
Cache-Speicher so, dass mehrere – nämlich n – Einträge für Cache-Zeilen
je Satz zur Verfügung stehen. Auf diese Weise kann der Cache-Speicher bei
moderatem Anstieg der Hardware-Komplexität weitgehend besser ausgelastet
werden als der einfach direkt abgebildete. Das Zeitverhalten verbessert sich
und das Cache-Flattern wird verringert. Der Ablauf einer Anfrage an einen
n-Wege-satzassoziativen Cache-Speicher ist in Abbildung 3.16 dargestellt.
Ähnlich wie beim direkt abgebildeten Cache wird zuerst über einen Index
ein bestimmter Satz ausgewählt (1). Dabei hat jeder Satz mehrere (nämlich n)
Einträge, entsprechend müssen nun in einem Schritt n Vergleiche durchgeführt
werden (2); es werden also n Vergleicher benötigt. Im Beispiel aus der Abbildung handelt es sich um einen 2-fach-satzassoziativen Cache-Speicher. Jeder
Satz enthält zwei Cache-Blöcke und kann somit zwei Cache-Zeilen aufnehmen.
Entsprechend werden hier zwei Vergleicher benötigt. Sollte es zu einem Treffer
kommen, muss die Gültigkeit des gefundenen Eintrag geprüft werden (3). Anschließend kann nach einer positiven Überprüfung die zugehörige Cache-Zeile
ausgewählt werden (4) und das selektierte Speicherwort (Byte) an den Prozessor
übertragen werden (5)13 .
13
Bezüglich der Übertragung der gesamten Cache-Zeile gilt wiederum die Aussage, die beim
vollassoziativen Cache-Speicher getroffen wurde.
3.3. Cache-Speicher und -Organisation
119
Adresse
Tag
Index Byte
31
3
0
Tag
V*
Daten/Cache-Lines
Dekoder
1
Treffer (Hit)
2
4
=?
=?
n Vergleicher
ausgewählte Cache-Zeile
5
*Verwaltungs-Bits
CPU
Abbildung 3.16: Ablauf einer Anfrage mit Treffer im n-Wege satzassoziativen
Cache-Speicher
Solange der entsprechende Satz nicht vollständig gefüllt ist, können ohne
Probleme weitere, noch nicht enthaltene Cache-Zeilen eingelagert werden. Ist der
Satz allerdings vollständig gefüllt, muss vor einer Einlagerung einer neuen CacheZeile Platz geschaffen – also eine andere verdrängt – werden, wie wir dies bereits
beim vollassoziativen Cache-Speicher gesehen haben. Mit der Verdrängung und
den dazugehörigen Verdrängungsstrategien von Cache-Einträgen beschäftigt
sich Unterabschnitt 3.3.6.
Die Vor- und Nachteile des direkt abgebildeten Cache-Speichers sind hier
nochmals zusammengefasst:
• Vorteile
– Cache-Zeilen können innerhalb eines Satzes an verschiedenen Stellen
abgelegt werden.
– Deutlich geringere Komplexität bezüglich des Aufbaus im Vergleich
zum vollassoziativen Cache-Speicher.
– Bessere Ausnutzung im Vergleich zum direkt abgebildeten CacheSpeicher.
– Gute Mischung aus direkt abgebildetem und vollassoziativem CacheSpeicher.
– Größere Caches als beim vollassoziativen Ansatz realisierbar.
• Nachteile
– Höherer Hardware-Aufwand als bei dem direkt abgebildeten CacheSpeicher.
– Verdrängungslogik notwendig.
120
Kapitel 3. Speicherorganisation
3.3.5
virtueller Cache
Platzierung des Caches in Bezug auf die virtuelle
Speicherverwaltung
Im Vorgriff auf Abschnitt 3.4, welcher sich mit der virtuellen Speicherverwaltung
beschäftigt, soll hier auf die Möglichkeit der Platzierung von Cache-Speichern
in Relation zu der virtuellen Speicherverwaltung eingegangen werden. Die virtuelle Speicherverwaltung setzt virtuelle in physikalische Adressen um. Auf diese
Weise wird es möglich, zum einen mehr Speicherplatz anzubieten als tatsächlich physikalischer Speicher vorhanden ist, und zum anderen die Ausführung
mehrerer Prozesse zu ermöglichen, indem jedem dieser Prozesse ein eigener
virtueller Speicherbereich zugeordnet wird. Im Wesentlichen sind zwei Varianten von Cache-Platzierungen möglich: Der virtuell adressierte Cache-Speicher
(auch virtueller Cache) und der physikalisch adressierte Cache-Speicher (auch
physikalischer Cache). In Abbildung 3.17 ist der virtuelle Cache dargestellt.
virtuelle
Adresse
MMU
phyisikalische
Adresse
Speicher
CPU
Cache
Daten
Abbildung 3.17: Virtueller Cache
Der Zugriff auf die Cache-Inhalte erfolgt vor der Adressumsetzung durch die
virtuelle Speicherverwaltung (MMU – Memory Management Unit). Somit wird
sowohl das Tag, Index als auch die Byte-Auswahl aus der virtuellen Adresse
gewonnen. Vorteilhaft ist, dass keine Zeit durch die Adressumsetzung der virtuellen Speicherverwaltung bei einem Cache-Treffer verloren geht. Nachteilig ist,
dass „Busschnüffeln“ (siehe Unterabschnitt 3.3.7) nicht mehr ohne weiteres möglich ist, da verschiedene Komponenten für identische physikalische Inhalte i. A.
unterschiedliche virtuelle Adressen verwenden. Zudem muss der Cache-Speicher
hier bei einem Prozesswechsel gelöscht werden, da ansonsten falsche Treffer
erfolgen könnten. Hierbei entsteht ein Zeitverlust: Zum einen durch das Leeren
des Speichers, zum anderen durch die Fehlzugriffe bis der Cache wieder gefüllt
ist. Diese Problematik kann durch die Einführung von Prozessidentifikatoren
gemildert werden. Wenn auf diese Weise Cache-Einträge bestimmten Prozessen
zugeordnet sind, werden Fehlzugriffe vermieden, auch wenn der Cache-Speicher
nicht geleert wird.
physikalischer Cache
In Abbildung 3.18 ist der physikalische Cache dargestellt.
Beim physikalischen Cache erfolgt der Zugriff nach der Adressumsetzung
durch die virtuelle Speicherverwaltung (MMU). Entsprechend werden Tag, Index
und Byte-Auswahl aus der physikalischen Adresse gewonnen. Von Vorteil ist,
3.3. Cache-Speicher und -Organisation
virtuelle
Adresse
MMU
121
phyisikalische
Adresse
Speicher
CPU
Cache
Daten
Abbildung 3.18: Physikalischer Cache
dass physikalische Adressen i. A. kleiner sind als virtuelle und ein entsprechend
kleinerer Tag-RAM notwendig ist. Zudem ist in diesem Fall auch ein „Busschnüffeln“ möglich. Nachteilig ist der Zeitverlust durch die Umsetzung der virtuellen
in eine physikalische Adresse vor dem Zugriff auf den Cache-Speicher.
Eine Mischform aus virtuellem und physikalischen Cache-Speicher ist in Mischform
Abbildung 3.19 dargestellt.
MMU
phyisikalische
Adresse
Tag
virtuelle
Adresse
CPU
Index
Byte-Auswahl
Speicher
Cache
Daten
Abbildung 3.19: Mischform aus virtuellem und physikalischem Cache
Bei dieser Realisierungsvariante wird das Tag aus der physikalischen, Index
und Byte-Auswahl aus der virtuellen Adresse gewonnen. Dadurch kann parallel
zur Adressumsetzung der virtuellen Speicherverwaltung mit dem Index und der
Byte-Auswahl auf den Cache-Speicher zugegriffen und die entsprechende CacheZeile selektiert werden. Nachteilig ist weiterhin der hohe zeitliche Aufwand zur
Adressumsetzung durch die virtuelle Speicherverwaltung.
3.3.6
Verdrängungsstrategien, Schreibzugriffe
Bei den oben beschriebenen vollassoziativen und n-fach-satzassoziativen CacheSpeichern sind Ersetzungsstrategien notwendig, sobald ein neuer Eintrag in
einen Satz geschrieben werden soll, in dem bereits alle Cache-Blöcke belegt sind.
Beim direkt abgebildeten ist keine Ersetzungstrategie erforderlich, da für eine
bestimmte Cache-Zeile immer genau ein Satz mit einem Cache-Block existiert,
die automatisch verdrängt wird, falls nötig.
122
Kapitel 3. Speicherorganisation
Beim vollassoziativen Speicher müssen Einträge verdrängt werden, sobald der
gesamte Cache-Speicher gefüllt ist. Beim n-fach-satzassoziativen Cache-Speicher
immer dann, wenn die n möglichen Einträge in einem Satz bereits belegt sind.
Wir betrachten hier vier Verdrängungsstrategien:
FIFO
FIFO First-In – First-Out: Der älteste Eintrag im Satz wird zugunsten eines
neu einzulagernden Eintrags verdrängt. Diese Strategie ist nachteilig in
Hinsicht auf Cache-Einträge, die zwar bereits sehr lange im Cache-Speicher
hinterlegt sind, aber auch häufig benötigt werden. Diese werden dann
verdrängt, obwohl ein zukünftiger Zugriff sehr wahrscheinlich ist.
LRU
LRU Least Resently Used: Der am längsten nicht mehr genutzte Eintrag wird
verdrängt. Hierbei wird für jeden Eintrag ein Zeitstempel mitgeführt, der
bei jedem Zugriff aktualisiert wird. Bei einer notwendigen Verdrängung
wird der Eintrag ausgewählt, auf den am längstem nicht mehr zugriffen
wurde, in der Hoffnung, dass auf diesen auch in Zukunft nicht mehr
zugriffen wird. LRU wird meist als pseudo-LRU implementiert, wobei hier
nicht mit Zeitstempeln (und entsprechend vielen Stellen und aufwändiger
Logik) gearbeitet wird, sondern nur mit wenigen Bits, um ein LRUVerfahren möglichst gut nachzubilden. Im Extremfall kann mit einem
Statusbit pro Cache-Zeile gearbeitet werden. Dieses wird bei jedem Zugriff
auf 1 gesetzt und zeigt damit an, dass auf diese kürzlich zugegriffen wurde.
Wenn der letzte mit 0 gekennzeichnete Eintrag auf 1 gesetzt wird, werden
alle anderen auf 0 zurückgesetzt.
pseudo-LRU
LFU
LFU Least Frequently Used: Bei diesem Verfahren wird der am seltensten angefragte Eintrag verdrängt. Auch hier muss eine entsprechende Buchführung
stattfinden, die z. B. als einfacher Zähler (gesättigt) realisiert werden kann.
Im Gegensatz zum LRU-Verfahren ist dabei nicht erheblich, wie lange der
letzte Zugriff zurückliegt und somit besteht die Gefahr, dass sehr alte,
kaum noch genutzte Einträge im Cache-Speicher verbleiben, auf die zwar
anfänglich sehr oft zugegriffen wurden, die aber aktuell eigentlich nicht
mehr benötigt werden.
RND
RND Random: Bei diesem Verfahren wird zufällig ein Eintrag verdrängt. Dieses
Verfahren bringt den Nachteil mit sich, dass Einträge verdrängt werden
können, die gerade sehr häufig verwendet werden.
Die LRU- bzw. pseudo-LRU-Strategie liefert die besten Ergebnisse. Die
Implementierung ist allerdings aufwändiger als FIFO oder RND. Es existieren
noch weitere Verfahren, auf die aber nicht weiter eingegangen wird.
Bei den bisherigen Beschreibungen sind wir ausschließlich von lesenden
Speicherzugriffen ausgegangen. Offen ist noch, wie mit schreibenden Speicherzugriffen bei der Verwendung von Cache-Speichern umgegangen werden soll. Vorab
muss entschieden werden, was bei einem Cache-Schreibe-Fehlzugriff passiert.
3.3. Cache-Speicher und -Organisation
123
Dabei gibt es zwei Möglichkeiten:
1. mit Bereitstellung (With Allocation): Bei dieser Möglichkeit wird der mit Bereitstellung
zu schreibende, nicht im Cache vorhandene Eintrag aus dem Speicher in
den Cache geladen, um ihn dann zu ändern.
2. ohne Bereitstellung (Without Allocation): In diesem Fall wird der ohne Bereitstellung
fehlende Cache-Eintrag nicht in den Cache-Speicher geladen, sondern nur
das Datum im Hauptspeicher geändert.
Für den Fall, dass ein Cache-Schreibe-Treffer vorliegt oder ein CacheSchreibe-Fehlzugriff mit Bereitstellung, existieren drei Varianten, mit der Änderung umzugehen: Durchschreibeverfahren, Umschreibeverfahren und Rückschreibeverfahren.
Beim Durchschreibeverfahren (Write Through) wird sowohl der CacheEintrag als auch der Speicher verändert. Nachteilig an diesem Vorgehen sind
die sehr langen Zugriffszeiten auf den Hauptspeicher (hier vergehen mindestens
100 Prozessorzyklen). Außerdem wird der Hauptspeicher bzw. der Speicherbus
mit einer Vielzahl von Zugriffen belastet, die häufig unnötig sind (z. B. bei
Variablen, die häufig geändert werden, wie Zähler). Vorteilhaft ist allerdings
die Erhaltung der Speicherkonsistenz über alle Cache-Stufen hinweg und die
Erhöhung der Trefferrate bei folgenden Zugriffen auf die gleiche oder eine
naheliegende Speicherstelle.
Um die Problematik des Speicherzugriffs zu entschärfen, kann ein Schreibpuffer in Kombination mit dem Durchschreibeverfahren eingesetzt werden. Bei
einem Schreibzugriff wird das zu schreibende Wort in einen schnellen Pufferspeicher geschrieben und von dort aus in den langsamen Hauptspeicher. Der
Prozessor kann dann direkt nach dem Schreiben in den Puffer die Programmausführung fortsetzen. Bei sehr vielen Schreibzugriffen wird sich allerdings
auch dieser Puffer füllen. Sobald er vollständig gefüllt ist, wird der Prozessor
abermals ausgebremst.
Beim Umschreibeverfahren (Write Around ) wird der Cache-Speicher
nicht aktualisiert, sondern nur in den Hauptspeicher geschrieben. Der CacheEintrag wird damit ungültig. Vorteilhaft ist dieses Vorgehen im Wesentlichen
bei vielen aufeinanderfolgenden Schreibzugriffen, ohne dass ein Lesen des CacheEintrags erfolgt. In diesem Fall werden viele unnötige Cache-Zugriffe vermieden.
Nachteilig ist allerdings, dass bei einem nachfolgenden Lese-Zugriff ein CacheFehlzugriff auftritt.
Beim Rückschreibeverfahren (Write Back ) wird nur der Cache-Eintrag
aktualisiert, nicht aber unterliegende Cache-Speicher bzw. der Hauptspeicher.
Vorteilhaft ist hierbei, dass der Prozessor sofort mit der Programmabarbeitung
fortfahren kann. Der darunterliegende Cache-Speicher und der Hauptspeicher
werden erst dann aktualisiert, wenn der Cache-Eintrag aus dem Speicher verdrängt wird. Die Leistungsfähigkeit wird hierdurch gesteigert, allerdings ist
die Implementierung deutlich aufwändiger. Auch der Speicherzustand ist nicht
mehr konsistent, kann aber durch Cache-Kohärenzprotokolle in einem stabilen
Zustand gehalten werden, siehe hierzu den folgenden Unterabschnitt 3.3.7.
Durchschreibeverfahren
Schreibpuffer
Umschreibeverfahren
Rückschreibeverfahren
124
Kapitel 3. Speicherorganisation
3.3.7
Cache Kohärenzprotokolle
Wie wir in den letzten Ausführungen gelernt haben, beschleunigen CacheSpeicher die Ausführungsgeschwindigkeit moderner Prozessoren. Allerdings
birgt der Einsatz von Cache-Speichern auch Probleme. Eines ergibt sich dann,
wenn mehrere Komponenten auf die Inhalte des Hauptspeichers zugreifen und
davon ausgehen, dass diese jeweils den aktuell gültigen Wert enthalten. Dies
können z. B. verschiedene Prozessoren in einer Multiprozessor-Umgebung oder
auch verschiedene Cores in einem Multicore-Prozessor sein. Aber auch bei
anderen Komponenten wie Festplatten, die über direkten Speicherzugriff (DMA
– Direct Memory Access) auf den Hauptspeicher zugreifen, treten diese Probleme
auf.
Nun ist aber nicht immer gewährleistet, dass das angefragte Datum aus
dem Hauptspeicher tatsächlich aktuell ist. Es könnte z. B. im Cache-Speicher
einer anderen Komponente ein aktuellerer Wert vorliegen, der noch nicht in
den Hauptspeicher zurückgeschrieben wurde. Aber auch der umgekehrte Fall ist
möglich: eine Komponente greift auf Cache-Speicher-Inhalte zu, obwohl diese
bereits veraltet sind. Aktuellere Werte könnten sowohl im Hauptspeicher als
auch in Cache-Speichern anderer Komponenten abgelegt sein.
In diesem Kontext treten die Begrifflichkeiten Konsistenz und Kohärenz
auf:
Konsistenz
• Konsistenz: Ein System ist konsistent, wenn alle Kopien eines Speicherworts im Hauptspeicher und den verschiedenen Cache-Speichern immer
identisch sind.
Kohärenz
• Kohärenz: Ein System ist Cache-kohärent, wenn ein Lesezugriff immer den Wert des zeitlich letzten Schreibzugriffs auf das entsprechende
Speicherwort liefert. Allgemeiner ausgedrückt bezeichnet Kohärenz das
korrekte Voranschreiten des Systemzustands durch ein abgestimmtes Zusammenwirken der Einzelzustände.
Cache-KohärenzProtokolle
Die Erhaltung eines konsistenten Systemzustands ist also deutlich schwieriger
zu realisieren und in vielen Fällen auch nicht notwendig. Gerade beim Einsatz
von Cache-Speichern ist die Sicherstellung der Kohärenz ausreichend. Um nun die
Cache-Kohärenz zu wahren, kommen Cache-Kohärenz-Protokolle zum Einsatz.
Prinzipiell gibt es zwei Ansätze, um die Cache-Kohärenz zu erhalten:
• Write-update-Protokoll: Wird eine Kopie in einem Cache-Speicher
verändert, müssen alle Kopien aktualisiert werden. Dies kann verzögert
passieren, aber spätestens bei einem Zugriff muss es erfolgen.
• Write-invalidate-Protokoll: Alle Kopien in anderen Cache-Speichern
werden als „Ungültig“ markiert, bevor eine Veränderung stattfindet.
MESI-Protokoll
Üblicherweise kommt das zweitgenannte Verfahren bei Multiprozessorsystemen zum Einsatz. Ein Protokoll ist das MESI-Protokoll (Modified, Exclusive,
3.3. Cache-Speicher und -Organisation
Shared and Invalid), welches hier genauer untersucht wird. Bei Multiprozessorsystemen wird oft das Bus-Schnüffeln (Bus Snooping) eingesetzt. Hierdurch
können die Prozessoren, die über einen Systembus an einen gemeinsamen Hauptspeicher angeschlossen sind, erkennen, wenn ein anderer Prozessor auf den
Speicher zugreift. Die festgestellten Adressen werden dann mit den lokalen
Cache-Speichern verglichen und der Status (MESI-Bits) wird dann aktualisiert, wenn ein Treffer vorliegt (Übereinstimmung der Adresse mit dem lokalen
Cache-Inhalt). In diesem Fall sind zwei Möglichkeiten gegeben:
• Schreibzugriff : Die Cache-Line wird im lokalen Cache-Speicher für ungültig erklärt (Stichwort: V-Bit), sofern diese bisher nur gelesen wurde.
• Lesezugriff oder Schreibzugriff : Wenn der lokale Cache-Inhalt bereits
verändert wurde, wird die Bustransaktion des anderen Prozessors unterbrochen und der Bus wird übernommen, um die betroffene Cache-Line in den
Speicher zu schreiben. Anschließend wird die ursprüngliche Bustransaktion
erneut ausgeführt.
Bei MESI hat eine Cache-Zeile einen von vier möglichen Zuständen:
• Exclusive modified (M): Durch einen Schreibzugriff wurde die lokale Kopie
der Cache-Zeile verändert und befindet sich ausschließlich in diesem CacheSpeicher.
• Exclusive unmodified (E): Die Cache-Zeile befindet sich ausschließlich im
lokalen Cache-Speicher, wurde allerdings bisher nicht verändert.
• Shared unmodified (S): Mehrere unveränderte Kopien der Cache-Zeile
befinden sich in unterschiedlichen Cache-Speichern.
• Invalid (I): Die Cache-Zeile ist ungültig, z. B. durch einen schreibenden
Zugriff in einem anderen Cache-Speicher.
In Abbildung 3.20 ist der Zustandsgraph mit den verschiedenen Übergängen
des MESI-Protokolls dargestellt.
Ausgehend von der Sichtweise eines Prozessors auf seinen Cache-Speicher
und entsprechende Bustransaktionen wird die Abbildung wie folgt interpretiert,
wenn eine bestimmte Cache-Zeile verarbeitet (lesen/schreiben) werden soll:
(I) Die Cache-Zeile hat aktuelle den Zustand invalid (I), ungültig. Dies bedeutet, dass die Cache-Zeile entweder noch gar nicht in den Cache-Speicher
geladen, zwischenzeitlich ersetzt oder aufgrund eines anderen Zugriffs als
ungültig markiert wurde. Sollte nun ein Lesen stattfinden, erfolgt einer
von zwei möglichen Übergängen:
1. Die Cache-Zeile ist in keinem anderen Cache-Speicher enthalten, es
erfolgt ein Übergang (RME) in den Zustand exclusive unmodified
(E). Die Cache-Zeile wird geladen ↑ .
125
126
Kapitel 3. Speicherorganisation
SHR
RMS
↑
W
×
SHR
WM
SHW
↑
↓
SH
W
R
SH
+
RH
H
E
RM
↓
RH
shared
unmodified
(S)
SHW
invalid
(I)
exclusive
modified
(M)
exclusive
unmodified
(E)
WH
RH
WH
RH
RMS
RME
WH
WM
SHR
SHW
read hit
read miss, shared
read miss, exclusive
write hit
write miss
snoop hit on read
snoop hit on write oder lesen
einer Cache-Line zum Ändern
↓
×
+
↑
veränderte Cache-Line zurückschreiben
andere Cache-Lines ungültig markieren
lesen einer Cache-Line zum Ändern
Cache-Line laden
Abbildung 3.20: Zustandsgraph des MESI-Protokolls, [1]
2. Die Cache-Zeile ist (1) in einem anderen Cache-Speicher modifiziert
(Zustand exclusive modified (M)) oder (2) in einem oder mehreren
Cache-Speichern unverändert (Zustand shared unmodified (S)) vorhanden. In beiden Fällen erfolgt ein Übergang (RMS) in den Zustand
shared unmodified (S). Im ersten Fall wird über das Busschnüffeln
der Zugriff auf die Adresse erkannt und die aktuelle Version der
Cache-Zeile bereitgestellt. Im zweiten Fall ist die Cache-Zeile im
Hauptspeicher im aktuell gültigen Zustand enthalten. Die CacheZeile wird geladen ↑ .
Bei einem schreibenden Zugriff auf die Cache-Zeile, erfolgt ein Übergang
(WM) in den Zustand exclusive modified (M). Die Cache-Zeile wird
zum Zwecke der Änderung in den Cache-Speicher geladen (lesen) + .
3.3. Cache-Speicher und -Organisation
Die anderen Prozessoren erkennen dies durch Busschnüffeln und setzen
ihre ggf. vorhandenen Kopien der Cache-Zeile auf den Zustand invalid
(I). Sollte die Cache-Zeile in einem anderen Cache-Speicher modifiziert
worden sein, wird die Bustransaktion unterbrochen, um den aktuellen
Inhalt bereitstellen zu können.
(S) Die Cache-Zeile hat den aktuellen Zustand shared unmodified (S), ist also
in mehreren Cache-Speichern unverändert enthalten. Wird nun ein Zugriff
durch einen anderen Prozessor auf die Cache-Zeile erkannt (snoop hit),
sind zwei Übergänge möglich:
1. Wird ein lesender Zugriff erkannt, verbleibt man (SHR) in dem
Zustand shared unmodified (S).
2. Wird ein schreibender Zugriff erkannt oder ein Lesen einer CacheZeile zum Zwecke der Änderung, erfolgt ein Übergang (SHW) in den
Zustand invalid (I).
Ein erneutes Lesen des Cache-Zeilen-Inhalts führt zum Verbleib (RH) im
Zustand shared unmodified (S). Ein Verändern des Inhalts, also ein Schreiben in die Cache-Zeile, überführt (WH) diese in den Zustand exclusive
modified (M). Kopien in anderen Cache-Speichern müssen als ungültig
markiert werden × .
(E) Die Cache-Zeile hat den aktuellen Zustand exclusive unmodified (E),
liegt also nur im Cache-Speicher des betrachteten Prozessors vor und ist
unverändert. Wird nun ein Zugriff durch einen anderen Prozessor auf die
Cache-Zeile erkannt (snoop hit), sind zwei Übergänge möglich:
1. Wird ein lesender Zugriff erkannt, erfolgt ein Übergang (SHR) in den
Zustand shared unmodified (S).
2. Wird ein schreibender Zugriff erkannt oder ein Lesen einer CacheZeile zum Zwecke der Änderung, erfolgt ein Übergang (SHW) in den
Zustand invalid (I).
Ein erneutes Lesen des Cache-Zeilen-Inhalts führt zum Verbleib (RH) im
Zustand exclusive unmodified (E). Ein Verändern des Inhalts, also ein
Schreiben in die Cache-Zeile, überführt (WH) diese in den Zustand exclusive modified (M). Andere Cache-Speicher sind hiervon nicht betroffen,
da es sich um die einzige gültige Kopie dieser Cache-Zeile handelt.
(M) Die Cache-Zeile hat den aktuellen Zustand exclusive modified (M), liegt
also nur im Cache-Speicher des betrachteten Prozessors vor, ist allerdings
verändert (geschrieben) worden. Wird nun ein Zugriff durch einen anderen
Prozessor auf die Cache-Zeile erkannt (snoop hit), wird die Transaktion
für einen lesenden Zugriff unterbrochen und die Cache-Zeile in aktualisierter Form zurückgeschrieben ↓ . Im Falle eines lesenden Zugriffs erfolgt
ein Übergang (SHR) in den Zustand shared unmodified (S). Sollte ein
127
128
Kapitel 3. Speicherorganisation
schreibender Zugriff erfolgen oder ein Lesen durch einen anderen Prozessor
zum Zwecke der Änderung, erfolgt ein Übergang (SHW) in den Zustand
invalid (I).
Für den Fall, dass der betrachtete Prozessor selbst die Cache-Zeile erneut
liest oder schreibt, erfolgt jeweils ein Verbleib (WH, RH) im Zustand
exclusive modified (M).
3.4
Virtuelle Speicherverwaltung
Mit der virtuellen Speicherverwaltung wird es möglich, virtuelle Adressräume auf einen physikalischen Adressraum abzubilden. Auf diese Weise kann
z. B. verschiedenen Prozessen ein jeweils eigener, von 0 an durchnummerierter
Adressraum zugewiesen werden. Identische virtuelle Adressen können mehrfach
vorkommen. Sie werden aber i. A. auf unterschiedliche physikalische Adressen
abgebildet.
Beispielsweise wird der Zugriff auf die virtuelle Adresse 0x1234 bei Prozess
A auf eine andere physikalische Adresse verweisen als ein Zugriff auf die gleiche
Adresse 0x1234 des Prozesses B. Zudem ermöglicht die virtuelle Speicherverwaltung, Teile des Hauptspeichers in einen Hintergrundspeicher (Festplatte)
auszulagern. Zwei wesentliche Vorteile können durch die virtuelle Speicherverwaltung erreicht werden:
1. Es können mehrere Prozesse mit jeweils eigenem Adressraum nebeneinander existieren, ohne sich gegenseitig zu stören.
2. Es kann mehr Speicher genutzt werden, als tatsächlich physikalischer
Speicher vorhanden ist.
Insbesondere ersteres ist heute eine unverzichtbare Technik, die von modernen Betriebssystemen genutzt wird, um eine Vielzahl von Prozessen quasi
gleichzeitig (oder tatsächlich gleichzeitig) ablaufen zu lassen. Aufgrund der
immer größeren Speicher ist der zweite Punkt nicht mehr ganz so wichtig wie
noch vor einigen Jahrzehnten. Allerdings gibt es auch heute noch Anwendungen,
die sehr viel Speicher – mehr als physikalisch verfügbar – benötigen.
In Abbildung 3.21 ist verallgemeinert der Zusammenhang zwischen virtuellen
und physikalischen Adressräumen sowie dem Hintergrundspeicher dargestellt.
In der Abbildung exitieren k virtuelle Adressräume, die jeweils ni − 1, i ∈
{1, . . . , k} Speicherplätze (z. B. Bytes) zur Verfügung stellen. Die Speicherbereiche werden dann jeweils entsprechenden Bereichen im physikalischen Speicher
zugeordnet. Tatsächlich existent sind natürlich nur die physikalischen Einträge.
Speicherverwaltungs- Die Umsetzung der Adressen erfolgt durch eine Speicherverwaltungseinheit.
einheit
Inhalte können auch in den Hintergrundspeicher ausgelagert werden. Wird
ein ausgelagerter Teil angesprochen, so wird dieser durch das Betriebssystem
wieder in den Speicher geladen und auf diese Weise verfügbar gemacht. So
virtueller AR 1
virtueller AR k
3.4. Virtuelle Speicherverwaltung
129
nk − 1
n−1
0
n1 − 1
0
phsikalischer
Speicher
Hintergrundspeicher
0
Abbildung 3.21: Virtuelle Speicherverwaltung
kann es vorkommen, dass nach einiger Zeit ein und derselbe Speicherbereich an
verschiedenen Stellen im physikalischen Speicher hinterlegt ist.
Im folgenden werden wir zwei Verfahren der virtuellen Speicherverwaltung
kennenlernen: Die Segmentierung (Segmentation) und das Seitenwechselverfahren (Paging). Ersteres wird heute seltener eingesetzt, während letzteres
häufig bei modernen RISC-Prozessoren genutzt wird. Die Beschreibung ist an
die virtuelle Adressverwaltung des Intel-x86-Standards angelehnt, aber deutlich
vereinfacht dargestellt. Im Detail wird im Rahmen dieses Kurses nur die Adressumsetzung an sich erläutert, auf Rechteverwaltung u. ä. wird nicht eingegangen.
Es wird von einem 32-Bit-Adressraum ohne Techniken zur Adressraumerweiterung ausgegangen.
3.4.1
Segmentierung
Bei der Segmentierung wird der Speicher in Segmente eingeteilt. Ein Segment Segmentierung
kann dabei an einer beliebigen physikalischen Speicheradresse zwischen 0 und
232 − 1 beginnen und eine beliebige Größe zwischen 1 und 232 Bytes umfassen. In
Abbildung 3.22 sind der physikalische Speicher sowie vier Segmente dargestellt.
Schraffierte Bereiche entsprechen freiem Speicherplatz.
Wie aus der Abbildung ersichtlich ist, können Segmente unterschiedlich groß
sein. Die Lücken zwischen den Segmenten entstehen durch Ein- und Auslagerung
zwischen Hauptspeicher und Hintergrundspeicher sowie durch Freigabe und
Anlegen neuer Segmente. Dabei werden bei Platzbedarf Segmente ausgelagert,
die in Zukunft wahrscheinlich nicht mehr benötigt werden. In die so entstehenden
Lücken werden neue Segmente oder solche, die aus dem Hintergrundspeicher
geladen werden, platziert. Da diese in den seltensten Fällen genau in eine Lücke
passen, verbleiben ungenutzte Restbereiche. Man spricht hierbei von externer externe
Fragmentierung. Durch Umordnung der Segmente kann der Speicher wieder Fragmentierung
defragmentiert werden. Dies ist allerdings aufwändig.
130
Kapitel 3. Speicherorganisation
232 − 1
Segment 4
Segment 3
Segment 2
Segment 1
0
Abbildung 3.22: Segmente im physikalischen Speicher
Die Umsetzung einer virtuellen Adresse in eine physikalische Adresse ist in
Abbildung 3.23 dargestellt.
virtuelle Adresse
Selektor
Offset
47
32 31
0
Deskriptortabelle
Ctrl2
Länge
+
Byte
Länge
Segment-Basisadresse
(8192 Einträge)
DTR
31
1
physikalischer Speicher
1
Deskriptortabellen-Basisregister
2
Kontrollbyte
0
Abbildung 3.23: Umsetzung der virtuellen Adresse in eine physikalische Adresse
bei der Segmentierung
virtuelle Adresse
Offset
Deskriptortabelle
Selektor
Die Adressumsetzung beginnt bei der virtuellen Adresse. Diese entspricht
der effektiven Adresse einer Speicherstelle, die durch die Adressierungsarten
gewonnen wird – bei der RISC-Architektur nur durch Lade- und Speicher-Befehle.
Die virtuelle Adresse ist in unserem Beispiel 48 Bit breit und besteht aus einem
16 Bit breiten Selektor und einem 32-Bit-Offset. Der Selektor enthält neben
Zugriffsrechten einen 13 Bit breiten Index in die Deskriptortabelle. Somit
kann mit einem Selektor einer von 8.192 möglichen acht Byte breiten Einträgen
3.4. Virtuelle Speicherverwaltung
der genannten Tabelle ausgewählt werden. Die Deskriptortabelle selbst ist auch
ein Segment und hat eine maximale Größe von 64 kB (8 Byte pro Eintrag ·
8.192 = 65.536 Byte = 64 kB). Die Basisadresse ist im DesktriptortabellenBasisregister DTR hinterlegt. Die Tabelle kann somit an einer beliebigen Stelle
(Byte-Grenze) im Speicher platziert werden.
Der durch den Index selektierte Eintrag der Tabelle ist ein Segmentdeskriptor, welcher alle notwendigen Informationen über das ausgewählte Segement
enthält: eine 32 Bit breite Segment-Basisadresse, die Länge (20 Bit) des Segments sowie ein Kontroll-Byte mit Zugriffs- und Verwaltungsinformationen.
Die Länge kann entweder in Byte-Schritten oder in 4 kB-Schritten angegeben
werden14 . Somit kann ein Segment maximal 220 · 4 kB = 4 GB groß sein.
Das eigentliche Byte, welches adressiert wird (bzw. das erste Teilbyte eines
Wortes), wird durch Addition der Segement-Basisadresse im Deskriptor und
des Offsets der virtuellen Adresse gewonnen. Bei dieser Berechnung wird intern
geprüft, dass die Adresse noch im gültigen Bereich des Segments liegt (Basisadresse + Länge). Ist dies nicht der Fall, würde es eine Zugriffsverletzung geben,
die durch das Betriebssystem behandelt werden muss. Die berechnete Adresse ist die physikalische Adresse. Falls nun noch ein Seitenwechselverfahren
nachgeschaltet ist, so wird die berechnete Adresse lineare Adresse genannt
und dient als Eingabe für das Seitenwechselverfahren.
Um den Zugriff auf das gleiche Segment zu beschleunigen, werden Segmentregister eingesetzt. Jedes enthält dann für eine gegebene virtuelle Adresse den
Deskriptor. Bei der x86-Architektur stehen sechs Segment-Register (vier für
Datensegemente und jeweils eins für Code- bzw. Stack-Segement) zur Verfügung.
3.4.2
131
Segmentdeskriptor
physikalische
Adresse
lineare Adresse
Segmentregister
Seitenwechselverfahren
Das Seitenwechselverfahren ist das heute primär eingesetzte Verfahren zur
Realisierung einer virtuellen Speicherverwaltung. Der Speicher wird in Seiten
fester Größe eingeteilt, die an festen Positionen im Speicher platziert werden.
Wir gehen im weiteren Verlauf von einem 32-Bit-Adressraum und 4 kB
großen Seiten aus, die immer an 4-kB-Grenzen ausgerichtet sind. Somit kann
eine Seite beginnend bei der Speicheradresse 0, alle 212 B = 4096 B = 4 kB
beginnen. In Abbildung 3.24 ist die Umsetzung der virtuellen – bzw. linearen,
wenn eine Segmentierung vorgeschaltet ist – Adresse dargestellt.
Die Adressauflösung beginnt mit der virtuellen Adresse, die 32 Bit breit ist.
Sie teilt sich in drei Felder auf: zwei Indizes und einen Offset. Die beiden 10 Bit
breiten Indizes verweisen auf Einträge in zwei Stufen organisierter Tabellen.
Somit kann jeweils ein Eintrag aus 210 = 1024 möglichen Einträgen ausgewählt
werden.
Die erste Stufe beginnt mit den oberen zehn Bit (Bits 22 bis 31) der virtuellen Adresse, um auf einen Eintrag im STV (Seitentabellenverzeichnis) Seitentabellenzuzugreifen. Die oberen 20 Bit der Basisadresse des STV sind im STVR verzeichnis
(Seitentabellenverzeichnis-Basisregister) hinterlegt. Die unteren 12 Bit dieser
14
So bei der Verwaltung unter einem Intel-x86-kompatiblen System.
132
Kapitel 3. Speicherorganisation
virtuelle/lineare Adresse
Index
31
Offset
Index
22 21
12 11
0
STV2
SV3
Seite
K
K
C4
B5
K
00b
C
B
00b
(1024 Einträge)
(1024 Einträge)
1
1
STVR
19
Byte
0
2
3
4
5
Seitentabellenverzeichnis-Basisregister
Seitentabellenverzeichnis
Seitenverzeichnis
Kontrollbyte
Obere 20 Bit der Basisadresse der Folgestufe
Abbildung 3.24: Umsetzung der virtuellen Adresse in eine physikalische Adresse
bei dem Seitenwechselverfahren
Seitenverzeichnis
interne
Fragmentierung
Adresse sind immer fest auf 0 gesetzt. Somit kann diese Tabelle an einer beliebigen 4-kB-Grenze im Speicher platziert werden. Es handelt sich bei dieser
Tabelle selbst um eine Seite. Durch Konkatenation der Basisadresse mit dem
10-Bit-Index und zwei 0-Bits erhält man die Anfangsadresse eines Eintrags in
diesem Verzeichnis. Die Einträge sind jeweils vier Byte (32 Bit) lang.
Jeder Eintrag enthält Zugriffs- und Verwaltungsinformationen sowie abermals
20 Bit der Basisadresse eines Seitenverzeichnisses (SV). Auch das Seitenverzeichnis ist wieder eine 4-kB-Seite und nach den bereits genannten Regeln an
entsprechenden Grenzen ausgerichtet. Durch abermalige Konkatenation der Basisadresse mit dem zweiten 10-Bit-Index (Bits 12 bis 21) der virtuellen Adresse
sowie zwei 0 Bits erhält man den gesuchten Eintrag im SV. Dieser ist analog
zum STV-Eintrag aufgebaut und enthält insbesondere die oberen 20 Bit der
Basisadresse der gesuchten Seite. Durch die Konkatenation mit dem 12 Bit
breiten Offset (Bits 0 bis 11 der virtuellen Adresse) erhält man die physikalische
Adresse des gesuchten Bytes (bzw. das Anfangs-Byte des gesuchten Worts).
Die 1024 Einträge des STV können auf 1024 SVs verweisen, die ihrerseits
wiederum auf jeweils 1024 Seiten je 4 kB Größe zeigen können. Somit kann ein
Speicher von 1024 · 1024 · 4 kB = 4 GB adressiert werden.
Seiten können aus- und bei Bedarf wieder eingelagert werden. Jedem Prozess wird dabei eine bestimmte Anzahl von Seiten zugeordnet, die ausreichen,
alle notwendigen Daten und Code-Teile im Speicher unterzubringen. Da der
Bedarf i. A. nicht ein Vielfaches von 4 kB ist, wird die letzte zugeteilte Seite
im Mittel nur zu 50 % gefüllt sein. Man spricht hier von der internen Fragmentierung. Eine externe Fragmentierung – wie bei der Segmentierung – kann
beim Seitenwechselverfahren nicht auftreten.
3.5. Anbindung des Hauptspeichers und von Ein- und Ausgabekomponenten
133
Aufgrund der doppelten Indirektion sind drei Speicherzugriffe notwendig,
um ein Speicherwort zu selektieren. Dies ist sehr aufwändig und muss – damit
sinnvoll gearbeitet werden kann – beschleunigt werden. Hierfür wird beim
Seitenwechselverfahren ein Translation Lookaside Buffer (TLB) verwendet, Translation
der die Umsetzungen der letzten Adressen speichert. Beim TLB handelt es sich Lookaside Buffer
um einen vollassoziativen Cache-Speicher, siehe Unterabschnitt 3.3.2. Als Tag
dienen die oberen 20 Bit der virtuellen Adresse, als Inhalt werden die oberen 20
Bit der physikalischen Adresse hinterlegt. Entsprechend können – bei Zugriffen
auf die gleiche Seite – zwei Speicherzugriffe auf das STV und SV vermieden
werden.
Mit der Segmentierung und dem Seitenwechselverfahren wurde ein grober
Überblick über die virtuelle Speicherverwaltung präsentiert, der vertieft z. B. im
Kurs 1744 (PC-Technologie) gelehrt wird. Im weiteren Verlauf wird nun noch
die Anbindung des Hauptspeichers an den Zentralprozessor beschrieben.
3.5
Anbindung des Hauptspeichers und von Einund Ausgabekomponenten
In den letzten Abschnitten wurden verschiedene Speicher sowie Speichertechnologien beschrieben. In diesem Abschnitt wenden wir uns der Anbindung
des Hauptspeichers an den Hauptprozessor (Unterabschnitt 3.5.1) zu. Zudem
wird der Zugriff auf externe Komponenten mittels Memory Mapped I/O in
Unterabschnitt 3.5.2 vorgestellt.
3.5.1
DRAM-Speichercontroller
Im Gegensatz zum Zugriff auf SRAM-basierten Speicher, gestaltet sich der
Vorgang bei DRAM deutlich aufwändiger. Entsprechend wird ein komplexerer
Controller für die Steuerung des Zugriffs benötigt. Dieser erzeugt die notwendigen Signale zur Auswahl der zu ladenden Speicherwörter, kümmert sich um die
Abarbeitung verschiedener Zugriffe und ordnet diese nach bestimmten Kriterien
an. Dabei werden üblicherweise gegenläufige Zielrichtungen optimiert: Effizienz
des Speicherzugriffs, Vorhersehbarkeit der Abarbeitung und Flexibilität. Nachdem wir im Unterabschnitt 3.2.4 bereits die zugrundeliegende Technologie zum
Speichern eines Bits in einem DRAM-Speicher besprochen haben, werden wir
uns hier dem Aufbau von synchronen DRAM-Bausteinen (SDRAM) zuwenden.
DRAM-Bausteine werden heutzutage zum Beispiel auf DIMM-Riegeln (Dual Dual Inline Memory
Inline Memory Module) verbaut und in PC-Systemen eingesetzt. Darüber hinaus Module
besteht die Möglichkeit, die Bausteine direkt auf eine Platine zu löten, wie dies
z. B. bei Grafikkartenspeicher erfolgt. Beim DRAM-Speicher werden einzelne
Speicherzellen zu Zeilen verkettet, siehe Abbildung 3.25.
DRAM-Zeile
In jeder Zeile sind n Zellen Zi , i ∈ {0, . . . , n − 1} zusammengefasst und
über eine Adressleitung A gemeinsam ansteuerbar. Bei einer Auswahl der Zeile
134
Kapitel 3. Speicherorganisation
A
Z0
Z1
B0
Zn−2
Z2
B1
Bn−2
B2
Zn−1
Bn−1
Abbildung 3.25: DRAM-Zeile
DRAM-Feld
werden sämtliche Zellen angesprochen und über die Bitleitungen Bi entweder
beschrieben oder ausgelesen. Die Anzahl der Zellen ist eine Zweierpotenz,
z. B. 4096 = 212 . Eine bestimmte Anzahl an Zeilen wird dann zu einem Feld
zusammengefasst, siehe Abbildung 3.26.
An−1
An−2
A3
A2
A1
A0
B0
B1
B2
B3
Bn−3 Bn−2 Bn−1
Abbildung 3.26: DRAM-Feld
Auf diese Weise werden n Zeilen zusammengefasst. Jede Zelle einer Spalte ist
dabei über eine gemeinsame Bitleitung verbunden. Auch hier ist die Anzahl der
zusammenfassten Zeilen eine Zweierpotenz. Werden 4096 Zeilen zu je 4096 Zellen
zu einem Feld verbunden, hat dieses eine Speicherkapazität von 224 = 16 MBit =
2 MB. Um eine bestimmte Zelle auszuwählen werden nun nacheinander zwei 12
Row Address Strobe Bit breite Auswahlsignale (Strobe-Signale) für die Zeile (RAS – Row Address
Strobe) und für die Spalte (CAS – Column Address Strobe) verwendet. In
Column Address
Strobe
Abbildung 3.27 ist der Gesamtaufbau am Beispiel eines 256-Bit-DRAM-Speichers
dargestellt.
Über einen Adressdekoder wird die Zeilenadresse dekodiert und die entsprechende Zeile ausgewählt. Diese wird dem Lese-/Schreibverstärker zugeführt, um
zum einen die Inhalte erneut in die Zeile zurückzuschreiben. Zum anderen wird
3.5. Anbindung des Hauptspeichers und von Ein- und Ausgabekomponenten
Zeilenadressdekoder
Zeilenadresse
4
Lese-/Schreibverstärker
Zeilenpuffer
Spaltenadresse
4
Datensignal
Spaltenadressdekoder
Abbildung 3.27: DRAM-Feld eines 16-mal-16-Bit-Speichers
über einen weiteren Adressdekoder die Spalte ausgewählt und das Datensignal
(Bit, Wort) ausgegeben. Aufgrund der Notwendigkeit, die ausgelesenen Daten
einer Zeile wieder in die Speicherzellen zu schreiben, macht sich das Aufteilen
der Ansteuerung in RAS- und CAS-Signal zeitlich nicht negativ bemerkbar,
vereinfacht aber den Aufbau des Speichers deutlich. In der dargestellten Form
lässt sich ein einzelnes Bit auslesen. Soll ein Wort ausgelesen oder geschrieben werden, so werden weitere Bits der Zeile hinzugenommen (die Bits des
CAS-Signals verringern sich entsprechend15 ) oder Teile des Wortes werden in
verschiedenen SDRAM-Bausteinen adressiert. Üblicherweise existieren in einem
SDRAM-Baustein mehrere Felder, die entsprechend ausgewählt werden. Um
die Zugriffsgeschwindigkeit zu erhöhen, werden Speicherbänke verwendet, die Speicherbank
jeweils über eigene Adressregister und Lese-/Schreibverstärker verfügen. Während Daten aus einer Bank ausgelesen werden, kann eine Adresse an eine andere
Bank angelegt werden. Latenzen können so reduziert werden.
Bei heute eingesetztem SDRAM erfolgen die Lese- und Schreibzugriffe synchron
synchron zu einem Takt, der vom Speichercontroller bereitgestellt wird. Vor
der Einführung von SDRAM wurden DRAM-Speicher asynchron angesprochen.
Wichtig ist, dass der synchrone Zugriff vom Speichercontroller bis zum Eingang
des SDRAM-Bausteins erfolgt. Innerhalb des Bausteins erfolgen die Zugriffe auf
Zeilen und Spalten weiterhin asynchron über Strobe-Signale.
Anfänglich wurde bei SDRAM pro Takt ein Speicherwort (meist 64 Bit) mit
der steigenden Taktflanke übertragen (Abbildung 3.28a). In diesem Zusammen15
Wird ein Byte aus 512 möglichen angesprochen, so werden neun Adressleitungen benötigt.
Werden 32-Bit-Wörter als kleinste Einheit adressiert, werden nur sieben Leitungen (27 =
128 · 4 Byte) benötigt.
135
136
Single Data Rate
Double Data Rate
Kapitel 3. Speicherorganisation
hang spricht man auch von SDR-SDRAM (Single Date Rate SDRAM). Nach der
Übertragung vergingen einige Takte, in denen das nächste Speicherwort aus den
DRAM-Zellen geladen wurde (Latenz). Bei DDR-SDRAM (Double Data Rate
SDRAM ) werden nun zwei aufeinanderfolgende Speicherwörter vorgeladen und
diese in einem Takt mit der steigenden und fallenden Flanke eines Taktes übertragen (Abbildung 3.28b). Anschließend vergehen einige Takte, bis die nächsten
zwei Speicherwörter aus den DRAM-Zellen geladen wurden. Um diese Lücken
bei der Übertragung zu füllen, lädt DDR2-SDRAM vier Speicherwörter vor
und überträgt diese in zwei direkt aufeinanderfolgenden Takten jeweils mit der
steigenden und fallenden Flanke (Abbildung 3.28c). Bei DDR3-SDRAM werden
acht Speicherwörter in vier Takten übertragen (Abbildung 3.28d). Man macht
sich hier wiederum die Lokalitätseigenschaften von Daten und Programmcode
zu nutze und überträgt Speicherwörter, die sehr wahrscheinlich in Zukunft angesprochen werden. Diese werden dann in Cache-Speichern für einen schnelleren
Zugriff vorgehalten.
1 Wort/Takt
Latenz
nächste
Übertragung
2 Wörter/Takt
Latenz
nächste
Übertragung
a) SDR
b) DDR
4 Wörter/ 2 Takte
nächste
Übertragung
Latenz
c) DDR2
8 Wörter/ 4 Takte
Latenz
nächste
Übertragung
d) DDR3
Abbildung 3.28: Datenübertragungsraten bei SDRAM: Single Data Rate und
Double Date Rate
Mit DDR3-SDRAM waren die Möglichkeiten der konsekutiven Übertragung
mehrere Speicherwörter in mehreren aufeinanderfolgenden Takten ausgeschöpft.
Somit reduzieren sich die Verbesserungen bei DDR4-SDRAM auf eine erhöhte
Speicherdichte auf den Modulen, geringere Spannungsversorgungen und höhere
Datenübertragungsraten. Zudem werden DIMMs mit einer Speicherkapazität
bis zu 64 GB möglich (DDR3 unterstützt bis zu 16 GB pro DIMM). DDR5SDRAM reduziert abermals die Leistungsaufnahme und verdoppelt zudem die
Speicherbandbreite und Kapazität im Vergleich zu DDR4-SDRAM.
3.5. Anbindung des Hauptspeichers und von Ein- und Ausgabekomponenten
Die Anbindung des DRAM-basierten Hauptspeichers an den Hauptprozessor
erfolgt üblicherweise über einen Speicher-Controller (Memory Controller – MC).
In Arbeitsplatzrechnern sind der Hauptspeicher und weitere Komponenten bei
älteren Systemen noch über einen Chipsatz angebunden. Dieser bestand aus
zwei Bausteinen, der North- und der South-Bridge – im Intel-Sprachgebrauch
auch als Memory Controller Hub (MCH) bzw. Input/Output Controller Hub
(ICH) bezeichnet. Dabei sind im MCH die sehr schnellen Komponenten (Grafikkarte und Speicher) mit dem Hauptprozessor über einen Bus verbunden. An
diesem Baustein ist auch der ICH angeschlossen. Langsamere Komponenten,
wie Erweiterungsschnittstellen (z.B. PCI – Peripheral Components Interconnect)
oder Festplatten-Schnittstellen (P-ATA und S-ATA für Parallel bzw. Serial
Advanced Technology Attachment) waren über die ICH verbunden. In aktuellen
Systemen sind die schnellen Schnittstellen (Grafik, Speicher-Controller) bereits
in den Hauptprozessor verlegt worden, um eine noch schnellere Anbindung zu
ermöglichen. Übrig bleibt daher vom Chipsatz nur noch der ICH, der dann im
Intel-Sprachgebrauch oft als Platform Controller Hub (PCH) bezeichnet wird.
In Abbildung 3.29 sind die beiden Möglichkeiten der Anbindung dargestellt.
a) MCH und ICH
Speicher-Controller
Chipsatz
Memory Controller
Hub
I/O Controller Hub
Platform Controller
Hub
Hauptspeicher
MC
CPU
PCH
PCIe
langsame
Komponenten
Grafikkarte
MC
MCH
ICH
PCIe
Hauptspeicher
langsame
Komponenten
Grafikkarte
CPU
137
b) PCH
Abbildung 3.29: Anbindung des Hauptspeichers an die CPU
3.5.2
Memory Mapped I/O
Um komfortabel auf Peripherie-Geräte zugreifen zu können, werden deren Register in den allgemeinen Adressraum eingeblendet. Diese sind somit über normale
Speicherzugriffsinstruktionen (LOAD oder STORE) les- und beschreibbar. Diese
Art des Zugriffs wird Memory Mapped I/O genannt16 . Somit ist im Adress- Memory Mapped
raum nicht nur der physikalische Hauptspeicher zu finden, sondern eine ganze I/O
16
Es gibt weitere Möglichkeiten, auf externe Komponenten zuzugreifen, z. B. Port Mapped
I/O oder Channel I/O. Bei Port Mapped I/O werden spezielle Befehle (out, in) verwendet,
138
Kapitel 3. Speicherorganisation
Reihe von Registern und Speicherbereichen anderer Komponenten oder auch
ungenutzte bzw. mit Spezialfunktionen belegte Bereiche. In Abbildung 3.30 ist
als Beispiel die Speicheraufteilung des ARM Cortex-M4 und die physikalische
Realisierung im NXP K60 dargestellt.
0xffff_ffff
511MB
0xe010_0000
0xe00f_ffff
Herstellerspezifisch
Geräte
priv. Schnittstellen
1MB
0xe000_0000
0xdfff_ffff
externe Geräte
1024MB
Analog-Digital-Wandler,
Zeitgeber,
USB, CAN, I2 C usw.
0xa000_0000
0x9fff_ffff
Upper
externer RAM
1024MB
0x6000_0000
0x5fff_ffff
SRAM
Daten
64 kB
Code
64 kB
Lower
Peripherie
512MB
0x4000_0000
0x3fff_ffff
Daten
512MB
Flashspeicher
0x2000_0000
0x1fff_ffff
512MB
512 kB
Code
Interruptvektortabelle
Adressraum
(4 GB)
Realierung beim K60
0x0000_0000
Abbildung 3.30: Speicheraufteilung beim ARM Cortex-M4 und physikalische
Realisierung am Beispiel des NXP K60
In der Abbildung ist auf der linken Seite die Speicheraufteilung des Adressraums in logische Teile dargestellt, rechts eine mögliche physikalische Realisierung. Der Adressraum teilt sich in verschiedene Bereiche unterschiedlicher
Größe auf. Für Programmcode und Daten sind die ersten 1024 MB vorgesehen
(in der Abbildung im Adressbereich von 0x0000_0000 bis 0x3fff_ffff). Es
folgen 512 MB für Peripheriegeräte, die dann über normale Speicherzugriffe
angesprochen werden können. Anschließend sind weitere Bereiche für externen
RAM (1024 MB), externe Geräte (1024 MB), externe und interne private Busse
(1 MB) sowie herstellerspezifische Implementierungen (511 MB) vorgesehen.
Wichtig ist, dass nicht der gesamte Adressraum mit tatsächlich physikalischer
Hardware hinterlegt sein muss. Dies wird am Beispiel des NXP K60 für die
ersten drei Speicherbereiche – Programmcode, Daten und Peripherie – ersichtlich.
Die unteren 512 kB des Programmcode-Bereichs sind durch einen entsprechend
großen Flash-Speicher realisiert. In diesem ist auch die Interruptvektortabelle
untergebracht. Ein weiterer Teil dieses Bereichs – die oberen 64 kB – sind
durch SRAM-Speicher realisiert. Dieser wird mit Lower bezeichnet. Der Bereich
um auf einen getrennten Adressraum zuzugreifen. Channel I/O wird durch Spezialprozessoren
realisiert und fand insbesondere im Großrechnerbereich Anwendung. Auf beide Verfahren
wird im Rahmen dieses Kurses nicht weiter eingegangen.
3.5. Anbindung des Hauptspeichers und von Ein- und Ausgabekomponenten
dazwischen – Adressen existieren natürlich – ist nicht physikalisch realisiert.
Ein Zugriff hierauf würde zu einer Ausnahmesituation (Exception) führen, die
entsprechend behandelt werden muss. Gleiches gilt für den Datenbereich, bei dem
ausschließlich die ersten 64 kB durch tatsächlichen physikalisch existierenden
SRAM realisiert sind. Hier gibt es allerdings noch eine Besonderheit, dass
ein weiterer Teil im Datenbereich für das sogenannte Bit Banding, welches
weiter unten noch erklärt wird, Verwendung findet. Schlussendlich werden
Peripheriegeräte im entsprechenden Speicherbereich abgebildet. Dabei kann
es sich z. B. um Timer, AD-Wandler handeln, aber auch Schnittstellen, wie
USB (Universal Serial Bus), CAN (Controller Area Network ) oder I2 C (InterIntegrated Circuit), sind hier mit ihren Registern eingeblendet.
Der Zugriff auf Geräte wird auf diese Weise sehr einfach. Es sind keine
Sonderbefehle wie beim Port Mapped I/O notwendig. Auf Geräte kann über
einfache Lade- und Speicherinstruktionen zugegriffen werden. Allerdings gibt es
auch Probleme, die in der Vergangenheit oft im PC-Bereich sichtbar wurden.
Bestimmte (oft höhere) Speicherbereiche wurden für Geräte Ein- und Ausgabe
reserviert. Über die Zeit wurden allerdings immer größere physikalische Speicher
möglich, was dazu führte, dass zwar der vollständige Speicher physikalisch
realisiert werden konnte, aber dieser nicht unbedingt vollständig nutzbar war.
Ein Beispiel ist die 3-GB-Barriere 17 , die bei der 32-Bit-Adressierung eines 32-BitBetriebssystems verhinderte, die insgesamt möglichen 4 GB des Hauptspeichers
als physikalischen Speicher genutzt werden konnten.
Häufig müssen in Konfigurationsregistern externer Komponenten nur einzelne
Bits gelesen oder beschrieben werden. Der zweite Fall ist bei einem Byteoder Wort-weisen Zugriff aufwändig. Dabei muss das Speicherwort erst in ein
Register geladen, das entsprechende Bit gesetzt (1) oder zurückgesetzt (0)
und der neue Wert zurückgeschrieben werden. Folgender Code-Ausschnitt in
ARM-Assembler18 demonstriert den Ablauf:
1
2
3
4
5
6
7
. equ IO_COMPONENT_R_Y 0 x40000020
. equ SET_BIT_10 0 x200
...
ldr r2 , = IO_COMPONENT_R_Y
ldr r3 , [ r2 ]
orr r3 , r3 , # SET_BIT_10
str r3 , [ r2 ]
In dem Beispiel-Code soll das Bit 10 im Register der Komponente, die an
Speicherstelle 0x4000_0020 zu finden ist, gesetzt werden. Einfach den Wert
17
Hierbei kommt zum Tragen, dass ab etwa 3 GB im Adressraum die Peripheriegeräte
eingeblendet werden. Je nach verwendeter Hardware kann dann ein Teil des verbleibenden
Gigabyte Speichers nicht genutzt werden.
18
Der ARM-Assembler unterscheidet sich zwar geringfügig in der Syntax vom hier eingeführten MIPS-Assembler, die zu zeigenden Konzepte sind allerdings auch ohne eine Einführung in
diesem Assembler verständlich.
139
140
Bit Banding
Kapitel 3. Speicherorganisation
0x200 an die entsprechende Speicherstelle zu schreiben, würde zwar das Bit
auf 1 setzen, aber auch alle anderen 31 verbleibenden Bits auf 0 zurücksetzen.
Dies ist i. A. nicht erwünscht. Ein korrektes Vorgehen ist im Code ersichtlich:
Zuerst wird die Adresse des Komponentenregisters nach r2 geladen (Zeile 4).
Dann wird der Inhalt aus dem Speicher in Register r3 geladen (Zeile 5). Danach
wird r3 und die Konstante SET_BIT_10 mit einem logischen ODER verknüpft
(Zeile 6). Abschließend wird das Ergebnis zurück in den Speicher geschrieben
(Zeile 7).
Ein in diesem Kontext interessantes Konzept bietet das Bit Banding.
Hier wird für jedes Bit eines bestimmten Speicherbereichs (üblicherweise der
Speicherbereich, in dem Ein- und Ausgabekomponenten eingeblendet werden)
ein ganzes 32-Bit-Speicherwort zugeordnet, um dieses komfortabel setzen bzw.
rücksetzen zu können. Für einen Speicherbereich von 1 MB wird somit ein BitBand-Bereich von 32 MB benötigt. Viele ARM Cortex-M3- und -M4-Prozessoren
bieten Bit-Band-Unterstützung an. In Abbildung 3.31 ist das Bit Banding im
Adressraum des oben gezeigten Cortex-M4 dargestellt.
0xffff_ffff
0x43ff_ffff
Bit Band Alias (32 MB)
0x4200_0000
...
(31 MB)
Bit Band (1 MB)
0x400f_ffff
0x4000_0000
0x23ff_ffff
0x6000_0000
0x5fff_ffff
Bit Band Alias (32 MB)
0x2200_0000
Peripherie
0x4000_0000
0x3fff_ffff
(31 MB)
Daten
0x2000_0000
0x1fff_ffff
Bit Band (1 MB)
0x200f_ffff
0x2000_0000
Code
0x0000_0000
Adressraum
(4 GB)
Bit-Banding-Bereiche
Abbildung 3.31: Bit Banding
Bit-Band-Alias
Beim Cortex-M4 existieren zwei Bit-Band-Bereiche jeweils in der Größe von
einem Megabyte, einmal im Datenbereich des SRAMs und einmal im Bereich der
Peripherie-Bausteine. Zu jedem gehört ein Bit-Band-Alias-Bereich, der 32 MB
groß ist. Die Behandlung beider ist identisch, daher wird hier exemplarisch
nur der SRAM-Bereich beschrieben. Für jedes Bit im SRAM-Bereich ab der
Adresse 0x2000_0000 bis zu 0x200f_ffff wird ein 32-Bit-Wort im Bit-BandAlias-Bereich (Adressen 0x2200_0000 bis 0x23ff_ffff) reserviert. Jedes dieser
Wörter kann mit normalen Lade- und Speicher-Befehlen gelesen bzw. geschrieben
3.5. Anbindung des Hauptspeichers und von Ein- und Ausgabekomponenten
werden. Allerdings hat nur das unterste Bit dieser Wörter eine Bedeutung. Auf
diese Weise kann ein einzelnes Bit in der Bit-Band-Region durch ein Schreiben
eines 32-Bit-Wortes in der Bit-Band-Alias-Region manipuliert werden. Zudem
können entsprechend einzelne Bits gelesen und ausgewertet werden.
Die Bit-Band-Alias-Adresse A für das Bit n (0-31) des Speicherworts an der
Adresse X (zwischen 0x2000_0000 und 0x200f_ffff) berechnet sich wie folgt:
A = 0x2200_0000 + (X − 0x2000_0000) · 32 + n · 4
Die Umsetzung eines Bits im Bit-Band-Bereich auf ein 32-Bit-Wort im
Bit-Band-Alias-Bereich ist in Abbildung 3.32 dargestellt.
0x2200_0008
0x2200_0004
0x2200_0000
0x2000_0008
0x2000_0004
0x2000_0000
Abbildung 3.32: Abbildung von Bits aus dem Bit-Band-Bereich in den Bit-BandAlias-Bereich
Das erste Bit des Speicherworts an der Adresse 0x2000_0000 wird auf das
erste 32-Bit-Wort ab Adresse 0x2200_0000 in dem Bit-Band-Alias-Bereich
abgebildet, das zweite Bit an das zweite 32-Bit-Wort usw. Um nun wie im
obigen Beispielprogramm das Bit 10 an Position 0x4000_0020 zu ändern, ist
mit Bit Banding folgender Code notwendig. Zu beachten ist, dass die Zuweisung
mit .equ (Zeile 1) zur Zeit der Assemblierung und nicht zur Laufzeit erfolgt.
1
2
3
4
5
. equ B B _ I O _ C O M P O N E N T _ B I T 1 0 (0 x42000000 + 0 x20 * 32 + 10 * 4)
...
ldr r2 , = B B _ I O _ C O M P O N E N T _ B I T 1 0
mov r4 , #1
str r4 , [ r2 ]
Die Bit-Band-Alias-Adresse wird in Zeile 1 durch den Assembler berechnet. Die so berechnete Adresse wird in Register r2 geladen (Zeile 3). Die zu
schreibende Konstante wird in Register r4 hinterlegt (Zeile 4). Anschließend
erfolgt der Schreibzugriff auf die Bit-Band-Alias-Adresse (Zeile 5). Somit wird
ein einziges Bit geändert, ohne andere Bits zu beeinflussen.
141
142
Kapitel 3. Speicherorganisation
Die Berechnung der Bit-Banding-Alias-Adresse bei Byte-weisem Zugriff
ist identisch. Auch hier wird für jedes Bit innerhalb eines Bytes ein 4-ByteSpeicherwort im Bit-Banding-Bereich verwendet. Der Zugriff erfolgt allerdings
mit den Byte-Varianten der Lade- und Speicherbefehle.
Bei der Rückrechnung einer Bit-Band-Alias-Adresse in die zugehörige BitBand-Adresse sowie Bit-Nummer muss zwischen Byte- und Wort-Zugriff unterschieden werden. Ausgangspunkt ist aber in beiden Fällen die Bit-Band-AliasAdresse, wie diese in Abbildung 3.33 für den Byte-Zugriff und Abbildung 3.34
für den Wort-Zugriff dargestellt ist. Die Interpretation der einzelnen Bits unterscheidet sich allerdings.
Byte im Bit-Band-Bereich
BB-Alias-Bereich
Bit-Nr.
0 0 1 0 0 0 1 a a a a a a a a a a a a a a a a a a a a b b b 0 0
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Abbildung 3.33: Bit-Band-Alias-Adresse interpretiert für Byte-Zugriff
BB-Alias-Bereich
Wort im Bit-Band-Bereich
Bit-Nr.
0 0 1 0 0 0 1 a a a a a a a a a a a a a a a a a a b b b b b 0 0
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Abbildung 3.34: Bit-Band-Alias-Adresse interpretiert für Wort-Zugriff
In den obigen Abbildungen ist der BB-Alias-Bereich das festgelegte Präfix
für den Bit-Band-Alias-Bereich. Die Adresse im Bit-Band-Bereich ist durch
Verschiebung um fünf Bit-Positionen in die Bit-Band-Alias-Adresse eingegangen
(mit a in der Abbildung gekennzeichnet), die Bit-Nummer (Bit-Nr.) durch
Multiplikation mit 4.
Die Berechnung der Bit-Nummer und der Byte- bzw. Wort-Adresse im
Bit-Band-Bereich erfolgt in zwei Schritten für den Byte-Zugriff:
BIT P OS = (BBA & 0x1c) 2
BY T EADR = ((BBA & 0x1ffffe0) 5) | 0x20000000
(3.1)
(3.2)
bzw. für den Wort-Zugriff:
BIT P OS = (BBA & 0x7c) 2
W ORDADR = ((BBA & 0x1ffff80) 5) | 0x20000000
(3.3)
(3.4)
In Gleichungen 3.1 und 3.2 sind die Berechnungen der Bit-Position und der
Byte-Adresse im Bit-Band-Bereich dargestellt. Zur Berechnung der Bit-Position
3.6. Zusammenfassung
143
BITPOS wird die Bit-Band-Alias-Adresse BBA mit 0x1c maskiert. Dadurch
erhält man die drei Bits, die die Bit-Position kodieren. Diese müssen noch
um zwei nach rechts geschoben werden. Die Byte-Adresse BYTEADR erhält
man, indem die Bit-Band-Alias Adresse mit 0x1ffffe0 maskiert wird. Dabei
werden die Informationen zur Bitposition sowie die oberen 7 Bit, die den BitBand-Alias-Bereich darstellen, aus der Adresse entfernt19 . Anschließend wird
die Adresse um 5 Bit-Positionen nach rechts verschoben und mit 0x20000000
bitweise ODER-verknüpft, um dadurch schlussendlich die Bit-Band-Adresse des
gesuchten Bytes zu gewinnen.
In ähnlicher Weise wird für Wort-Adressen vorgegangen, nur das hier andere
Masken verwendet werden.
Für eine gegebene Bit-Band-Alias-Adresse 0x220421e4 ergibt sich für den
Byte-Zugriff die Bit-Nummer 1 und die Byte-Adresse 0x2000210f. Für die
gleiche Adresse ergibt sich bei einem Wort-Zugriff die Bit-Nummer 25 und die
dazugehörige Wort-Adresse 0x2000210c, vgl. auch Abbildung 3.35.
0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1 1 1 0 0 1 0 0
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Abbildung 3.35: Beispiel einer Bit-Band-Alias-Adresse
Selbsttestaufgabe 3.2 Bit Banding
1. Geben Sie für folgende Adressen und Bits die Bit-Band-Alias-Adresse an:
(a) 0x2000_06b4, Bit 18
(b) 0x4000_f208, Bit 7
2. Geben Sie für folgende Bit-Band-Alias-Adresse die Adresse im Bit-BandBereich und die Bit-Nummer an:
(a) 0x2229_1d38
(b) 0x43ff_9bf0
3.6
Zusammenfassung
In diesem Kapitel haben wir uns mit den verschiedenen Speichertechnologien
und -arten auseinandergesetzt. Sie haben die Von-Neumann- und HarvardArchitektur und damit die zwei wesentlichen Möglichkeiten, Speicher an den Zentralprozessor anzubinden, kennen gelernt. Die Problematik des Von-NeumannFlaschenhalses wurde diskutiert. Im nächsten Schritt wurde die Speicherhierarchie, vom Register bis hin zu optischen Speichermedien, eingeführt. Anschließend
19
Aufgrund der nun folgenden Verschiebung könnte statt e0 für das untere Byte der Maske
auch jeder andere Wert verwendet werden. Dies gilt aber nicht für die Rückgewinnung einer
Wort-Bit-Band-Adresse und der zugehörigen Bit-Nummer!
144
Kapitel 3. Speicherorganisation
haben wir uns mit den technologischen Grundlagen auseinandergesetzt. Dabei wurden Transistoren und Kondensatoren als wesentliche Bauelemente von
SRAM- und DRAM-Speichern eingeführt. Im Anschluss wurde die konkrete
Realisierung von SRAM-Speicher in Form von Six-Device-Zellen sowie die Realisierung von DRAM-Speicher mittels Kondensator und Transistor vorgestellt.
Abschließend haben Sie dauerhafte Speicherung mittels eines Floating-GateTransistors, wie er in Flash-Speichern eingesetzt wird, kennen gelernt.
Aufbauend auf diesen Grundlagen wurden ausführlich die Cache-Speicher
besprochen. Sie haben die Motivation zum Einsatz von Cache-Speichern gesehen und die verschiedenen Realisierungen – vollassoziativ, direkt abgebildet
und n-Wege-assoziativ – kennen gelernt. Auch die Platzierung von Caches in
Bezug auf die virtuelle Speicherverwaltung wurde im Vorgriff auf den darauf
folgenden Abschnitt besprochen. Der Umgang mit Verdrängung von Einträgen
im Cache-Speicher sowie mit Schreibzugriffen wurde anschließend präsentiert.
Abschließend wurde die Erhaltung der Cache-Kohärenz umfassend anhand des
MESI-Protokolls beschrieben.
Im darauf folgenden Abschnitt haben Sie die virtuelle Speicherverwaltung,
wie diese in heutigen Computersystemen häufig eingesetzt wird, kennen gelernt.
Nach einer Motivation wurden die wesentlichen Verfahren – Segmentierung
und Seitenverwaltung – vorgestellt. Die Funktionsweisen und Probleme beider
Verfahren wurden dabei erörtert.
Abschließend haben wir im letzten Inhaltsabschnitt dieses Kapitels die
Anbindung des Hauptspeichers sowie von Ein- und Ausgabekomponenten an
den Prozessor untersucht. Im ersten Schritt wurde hierbei der Aufbau eines
DRAM-Controllers sowie dessen Platzierung in modernen Computersystemen
dargelegt. Im zweiten Schritt haben wir die Kommunikation auf Basis von
Memory Mapped I/O und mit dem Bit Banding ein interessantes Verfahren zur
Bit-Manipulation kennen gelernt.
Insgesamt haben Sie auf diese Weise einen breiten Überblick über verschiedene Aspekte von Speichern in Rechensystemen erlangen können. Vertiefende
Kurse werden aufbauend auf diesem Wissen detaillierter auf Besonderheiten
und Technologien eingehen.
3.6. Zusammenfassung
Lösungen der Selbsttestaufgaben
Lösung Selbsttestaufgabe 3.1
a) Die mittlere Speicherzugriffszeit berechnet sich aus der Trefferrate des
L1-Cache-Speichers h1 multipliziert mit der mittleren Zugriffszeit tL1 auf
diesen Speicher. Dazu werden die verbleibenden Zugriffe (1 − h1 ) addiert,
die sich wiederum wie folgt aufteilen: Trefferrate des L2-Cache-Speichers h2
multipliziert mit der mittleren Zugriffszeit tL2 zuzüglich der verbleibenden
Zugriffe 1 − h2 multipliziert mit der Zugriffszeit auf den Hauptspeicher
tHS . Insgesamt ergibt sich folgende Formel:
tA/B = h1 · tL1 + (1 − h1 ) · (h2 · tL2 + (1 − h2 ) · tHS )
Dabei wird davon ausgegangen, dass die Speicher der verschiedenen Ebenen
(L1-Cache, L2-Cache und Hauptspeicher) nacheinander und nicht parallel
angefragt werden.
b) Für die konkret gegeben Werte ergeben sich somit für die beiden Alternativen folgende mittlere Zugriffszeiten:
tA = 0,7 · 10 ns + 0,3 · (0,5 · 30 ns + 0,5 · 100 ns) = 26,5 ns
tB = 0,8 · 12 ns + 0,2 · (0,3 · 25 ns + 0,7 · 100 ns) = 25,1 ns
Entwurfsalternative B hat somit die geringere mittlere Speicherzugriffszeit.
Lösung Selbsttestaufgabe 3.2
1. Folgende Bit Band-Alias-Adressen gehören zu den gegebenen Bit-BandAdressen und Bits:
(a) 0x2000_06b4, 18:
0x2200_0000 + (0x2000_06b4 - 0x2000_0000) ·32+18·4 = 0x2200_d6c8
(b) 0x4000_f208, 7:
0x4200_0000+ (0x4000_f208 - 0x4000_0000) ·32+7·4 = 0x421e_411c
2. Folgende Bit-Band-Adresse und Bit-Nummer gehören zu den gegebenen
Bit-Band-Alias-Adressen:
(a) 0x2229_1d38:
0x2001_48e9, Bitposition 6
(b) 0x43ff_9bf0
0x400f_fcdf, Bitposition 4
145
146
Kapitel 3. Speicherorganisation
Kapitel 4
Weiterführende Prozessortechniken
Kapitelinhalt
4.1
Lernziele . . . . . . . . . . . . . . . . . . . . . . . . . . 148
4.2
Parallelität auf Befehlsebene . . . . . . . . . . . . . . 149
4.3
Mehrfädige Prozessoren . . . . . . . . . . . . . . . . . 180
4.4
Mehrkernprozessoren . . . . . . . . . . . . . . . . . . 187
4.5
Entwicklung der Prozessor-Technik . . . . . . . . . . 190
4.6
Zusammenfassung . . . . . . . . . . . . . . . . . . . . 194
148
Kapitel 4. Weiterführende Prozessortechniken
„The dirty little secret of OOO (Out-Of-Order) is that we are often not very
much OOO at all.“, Andy Glew, a pioneer of out-of-order execution and one of
the chief architects of the Pentium Pro/II/III.
Die Befehlsausführung außerhalb der Programmreihenfolge ermöglicht es,
die Leistungsfähigkeit eines Prozessors zu steigern. Das obige Zitat zeigt, dass
dies kein leichtes Unterfangen ist. Im Folgenden werden wir uns näher mit der
Parallelität auf Befehlsebene und möglichen Lösungen beschäftigen.
Im Kapitel 2 haben Sie die Technik des Pipelinings kennengelernt. Hierbei
überlappen sich die unterschiedlichen Befehlsphasen mehrerer Befehle wie bei
einer Fließbandproduktion. Nach dem Befüllen der Pipeline wird durch die
zeitliche Überlappung – im Idealfall – pro Takt ein Befehl beendet. Voraussetzung
dafür ist eine schnelle Bereitstellung der Befehle und Daten über getrennte
Caches (Harvard-Architektur), deren Aufbau und Funktionsweise Sie in Kapitel 3
kennengelernt haben. Sie wissen jedoch auch, dass sich der Durchsatz aufgrund
vom Cache-Fehlzugriffen, sowie Daten- und Steuerflusskonflikten verringern
kann. Um eine hohe Leistung der Pipeline zu gewährleisten, muss die Anzahl
solcher Blockierungen durch geeignete Mikroarchitektur-Techniken minimiert
werden. Darüber hinaus will man bei fortgeschrittenen Prozessoren die Leistung
bei gleicher Taktrate (Halbleitertechnologie) weiter steigern. Dies gelingt durch
parallel arbeitende Pipelines bzw. Ausführungseinheiten, welche wir in diesem
Kapitel vorstellen werden.
Abschnitt 4.3 führt in die Techniken superskalarer Prozessoren ein, die
in den meisten heutigen Computersystemen zu finden sind. In Abschnitt 4.4
werden mehrfädige Prozessoren behandelt. In Abschnitt 4.5 lernen Sie MehrkernProzessoren kennen. Schließlich wird Abschnitt 4.6 eine Übersicht über bisherige
und aktuelle Entwicklungen geben.
4.1
Lernziele
In diesem Kapitel lernen Sie superskalare Prozessoren kennen, die durch Ausnutzung von Parallelität auf der Befehlsebene (Instruction Level Parallelism, ILP)
eine schnellere Abarbeitung von Programmen ermöglichen. Sie werden lernen,
wie eine superskalare Pipeline aufgebaut ist, welche Aufgaben die einzelnen
Stufen haben, wie durch dynamisches Scheduling ohne erneute Übersetzung eine
Befehlsausführung außerhalb der Programmreihenfolge möglich wird und gleichzeitig die sequentielle Programmsemantik gewährleistet wird. Sie sollen erkennen,
warum eine Sprungvorhersage und Registerumbennung benötigt wird und wie
eine spekulative Ausführung realisiert werden kann. Sie werden lernen, wie
Blockierungen (z. B. aufgrund von Cache-Fehlzugriffen) mit Hilfe einer mehrfädigen Ausführung (Multithreading) verdeckt werden. Durch diesen Thread-Level
Parallelism (TLP) kann die Leistung eines superskalen Prozessors weiter gesteigert werden. Schließlich lernen Sie den Aufbau der aktuell weit verbreiteten
4.2. Parallelität auf Befehlsebene
Mehrkern-Prozessoren (Multicore) kennen und mit einem Ausblick werden Sie
auch Erkenntnisse über künftige Entwicklungsrichtungen der Prozessor-Technik
gewinnen.
4.2
Parallelität auf Befehlsebene
In diesem Abschnitt wollen wir einen Überblick über weiterführende ProzessorTechniken geben. Ziel ist immer, die Prozessorleistung zu erhöhen. Daher gehen
wir zunächst darauf ein, wie man die Prozessorleistung bewerten kann. Dann
werden wir auf das Superpipelining als einfachste Methode zur Leistungssteigerung eingehen. Eine weitere Möglichkeit besteht darin, zwei oder mehrere
Befehle gleichzeitig auszuführen. Dies kann z. B. dadurch realisiert werden, dass
man diese (Teil-) Befehle in einem neuen längeren Befehl zusammenfasst. Man
erhält dann eine VLIW -Befehlssatzarchitektur (Very Long Instruction Word ),
die z. B. durch eine Mikroarchitektur mit zwei oder mehr parallelen Pipelines
implementiert werden kann. Die andere Variante besteht darin, im Befehlsstrom
einer etablierten Befehlssatzarchitektur durch eine spezielle Mikroarchitektur
nach Möglichkeiten zur Parallelverarbeitung auf Befehlsebene zu suchen. Derartige superskalare Prozessoren sind heute am häufigsten anzutreffen, da sie
existierende binäre Maschinencodes ohne Vorverarbeitung durch den Compiler
schneller abarbeiten.
4.2.1
Steigerung der Prozessorleistung
Um die Leistungsfähigkeit eines Prozessors zu bewerten, benutzt man am besten
die Laufzeit zur Abarbeitung typischer Anwendungsprogramme. Diese Zeit
ergibt sich aus der Anzahl der benötigten Taktzyklen multipliziert mit der
Taktzykluszeit.
Lauf zeit = (Anzahl der T aktzyklen) · T aktzykluszeit
Die Anzahl der Taktzyklen kann durch die Zahl der abgearbeiteten Befehle
(auch Befehlspfadlänge genannt) ausgedrückt werden, wenn man die mittlere
Zahl von Taktzyklen pro Befehl kennt. Damit ergibt sich:
Lauf zeit = (Anzahl Bef ehle) · (T aktzyklen pro Bef ehl) · T aktzykluszeit
Die o. g. Gleichung zeigt, wie die drei Ebenen der Prozessorarchitektur auf
die Laufzeit wirken. Die Zahl der für ein (Hochsprachen) Programm erzeugten Befehle hängt von der zugrundeliegenden Befehlssatzarchitektur und der
Compilertechnik ab. RISC-Architekturen benötigen für das gleiche Programm
zwar mehr Maschinenbefehle als CISC-Architekturen, aufgrund ihrer logischen
Implementierung (Pipelining) werden jedoch weniger Taktzyklen pro Befehl
benötigt. Wegen der geringeren Laufzeit zwischen zwei Pipeline-Stufen kann
zudem mit einer kürzeren Taktzykluszeit gearbeitet werden. Die Zykluszeit
149
150
Kapitel 4. Weiterführende Prozessortechniken
beschreibt schließlich die technologische Implementierung (Realisierung) einer
Prozessorarchitektur. Hiermit ergibt sich:
Lauf zeit
= Anzahl der Bef ehle ·
{z
}
| {z }
|
Befehlssatzarchitektur
1/Leistungsfähigkeit
T aktzyklen pro Bef ehl ·
{z
}
|
Mikroarchitektur
T aktzykluszeit
{z
}
|
Implementierung
CPI-Wert
IPC-Wert
Performanz P
(4.1)
Die Anzahl der Taktzyklen pro Befehl drückt man durch den CPI-Wert
(Cycles Per Instruction) aus. Skalare RISC-Mikroarchitekturen, die wir in
Kapitel 2 kennengelernt haben, erreichen im Idealfall CPI-Werte nahe 1, da sie
in der Ausführungsphase immer nur eine einzige Ausführungseinheit verwenden.
Im Allgemeinen ist der CPI-Wert jedoch aufgrund von Pipelinekonflikten größer
als 1.
In diesem Kapitel werden wir Prozessoren kennenlernen, die in der Ausführungsphase zwei oder mehr Befehle pro Taktzyklus ausführen können. Solche
superskalare RISC-Prozessoren erreichen im Idealfall den Kehrwert der maximal
erreichbaren Befehlsparallelität. Die CPI-Werte superskalarer Prozessoren liegen
also unter 1. Der Kehrwert des CPI-Wertes gibt an, wie viele Befehle im Mittel
pro Taktschritt abgearbeitet werden können. Man bezeichnet diesen Wert als
IPC Instructions Per Clock. Es gilt also IP C = CP1 I .
Für eine bestimmte Befehlssatzarchitektur ist nach Gleichung 4.1 die Anzahl
der Befehle für ein typisches Anwendungsprogramm konstant. Da sich die
Leistungsfähigkeit oder Performanz P eines Prozessors aus dem Kehrwert der
Laufzeit ergibt und der Kehrwert der Taktzykluszeit der Frequenz f entspricht,
können wir folgenden Zusammenhang herstellen:
P ∝ IP C · f
Die Leistungsfähigkeit eines Prozessors ist also zu beiden Parametern proportional und kann daher gesteigert werden, indem entweder der IP C oder
die Frequenz f oder beides gleichzeitig erhöht wird. In Abbildung 4.1 sind die
Möglichkeiten zur Steigerung der Leistungsfähigkeit zusammengefasst.
Superpipelining
Um auf der Mikroarchitektur-Ebene die maximal mögliche Taktfrequenz eines
Prozessors zu erhöhen, kann man die einzelnen Pipeline-Stufen aufspalten und so
die Gesamtzahl der Stufen vergrößern. Wie wir in Kapitel 2 gesehen haben, steigt
der maximal erreichbare Speedup proportional zur Zahl der Pipeline-Stufen. Da
durch die feinere Unterteilung der Pipeline-Stufen auch die Verzögerungszeit
der zugehörigen Schaltnetze sinkt, kann die Taktzykluszeit verringert und damit
die Taktfrequenz erhöht werden.
4.2. Parallelität auf Befehlsebene
Leistungsfähigkeit
erhöhen
n
IP
he
ö
C
h
r
er
ze
h
en
F
qu
re
Superpipelining
151
öh
Kombination
en
Parallele
Befehlsausführung
Abbildung 4.1: Methoden zur Steigerung der Leistungsfähigkeit
Diese Vorgehensweise ist als Superpipelining bekannt und wird bei allen
aktuellen Prozessoren eingesetzt. Während z. B. der UltraSPARC T1 nur sechs
Pipeline-Stufen hatte, findet man bei späteren Prozessoren Pipelines mit mehr
als 30 Stufen (z. B. 31 beim Pentium 4E Prescott). Leider führt Superpipelining
auch zu höheren Latenzzeiten, da z. B. bei einer falschen Sprungvorhersage
die Zeit für das Leeren (flush) bzw. erneute Füllen der Pipeline proportional
zur Stufenanzahl ist. Dies führt insbesondere bei spekulativer Ausführung von
Sprüngen zu Leistungseinbußen, wenn sich die Vorhersage des Befehlspfades bei
einem Sprung als falsch herausstellt.
Parallelverarbeitung
Selbst die schnellsten Speicher (L1-Caches) sind im Vergleich zu den mit Superpipelining erreichbaren Prozessortaktfrequenzen sehr langsam. So hat ein
heutiger L1-Cache mit 32 KB Speicherkapazität eine typische Zugriffslatenz
von vier Taktzyklen. Bei einem Cache-Fehlzugriff müssen zum Nachladen aus
dem SDRAM-Hauptspeicher weitere ca. 100 Taktzyklen veranschlagt werden.
Diese große und stetig wachsende Geschwindigkeitslücke zwischen Prozessor
und Speicher wird als memory wall bezeichnet. Sie hat die Entwicklungen in memory wall
Richtung Superpipeling immer stärker gebremst und dazu geführt, dass man
sich zur Steigerung der Leistungsfähigkeit verstärkt auf die Vergrößerung des
IPC-Wertes konzentriert.
Da meist auch bei einer skalaren RISC-Mikroarchitektur verschiedene Funk- Befehlsebenentionseinheiten für Ganz- und Gleitkommazahlen oder Speicherzugriffe vorhanden parallelität
sind, liegt es nahe, diese gleichzeitig durch zwei oder mehrere voneinander unabhängige Befehle zu nutzen. Damit erreichen wir eine Parallelverarbeitung auf
der Befehlsebene und man spricht von Befehlsebenenparallelität (Instruction
Level Parallelism, ILP).
Beispiel: Zweifache Parallelität
Wie wir bereits gesehen haben, wird bei einem skalaren RISC-Prozessor in der
Ausführungsphase (EX) immer nur ein einziger Befehl bearbeitet. Damit wird
152
Kapitel 4. Weiterführende Prozessortechniken
bei konfliktfreiem Pipelining ein IPC von 1 erreicht. Um den IPC und damit
die Leistungsfähigkeit zu erhöhen, kann man die skalare Pipeline so erweitern,
dass zwei Befehle gleichzeitig geholt, dekodiert und zwei parallel vorhandenen
Ausführungseinheiten zugeordnet werden. Dies ist in Abbildung 4.2 dargestellt.
1
2
3
4
add
sub
or
add
$t1 ,
$t4 ,
$t3 ,
$t1 ,
$t1 ,
$a1 ,
$t2 ,
$t1 ,
3
4
8
-1
Listing 4.1: Programmausschnitt mit zweifacher Befehlsebenenparallelität
Abbildung 4.2: Befehlsverarbeitung bei einem einfachen superskalaren RISCProzessor mit zwei Ausführungseinheiten
Da nun pro Takt mehr Befehle und Daten zwischen den Stufen übertragen
werden müssen, steigen die Anforderungen an die Speicherbandbreite. Im Idealfall erreicht man hier einen IPC von 2. Um den ILP zu nutzen, muss man
im Befehlsstrom nach voneinander unabhängigen Befehlen suchen und diese
den Ausführungseinheiten zuordnen. Die Zuordnung kann durch den Compiler, durch die Prozessor-Hardware oder durch eine Kombination dieser beiden
Möglichkeiten erfolgen.
Die Zuordnung durch den Compiler erfordert immer eine Neuübersetzung
des Programms und kann daher nicht angewandt werden, wenn statt des Quellprogramms nur ein binäres Maschinenprogramm für eine bestimmte Befehlssatzarchitektur vorliegt.
In dem Beispiel nach Listing 4.1 muss der Compiler Paare unabhängiger
Befehle bilden, die gleichzeitig von der Hardware dekodiert und dann den
Ausführungseinheiten zugeordnet werden. Im Folgenden wollen wir dieses einfache Beispielprogramm betrachten, das durch die in Abbildung 4.2 gezeigte
superskalare Verarbeitung mit zwei ALUs doppelt so schnell ausgeführt werden
kann.
Da sowohl zwischen den Befehlspaaren 1 und 2 als auch zwischen 3 und 4
keine RAW-Datenabhängigkeiten bestehen, können diese gleichzeitig ausgeführt
werden. Hierzu müssen jeweils zwei Befehle geholt, dekodiert und dann zwei
ALUs zugeordnet werden. Die Zuordnung (issue) kann in der Programmreihenfolge erfolgen (in-order issue). Man beachte dass die beiden Registeroperanden
gleichzeitig aus dem Registerblock gelesen werden müssen. Wir benötigen dazu
zwei Lesekanäle (Ports) zum Zugriff auf die Register $t1 und $a1. Danach
werden zunächst die beiden Befehle 1 und 2 parallel ausgeführt. Da diese in zwei
identische Ausführungseinheiten mit einer Latenzzeit von 1 verarbeitet werden,
liegen die Ergebnisse nach einem Takt vor und müssen nun gleichzeitig in die
4.2. Parallelität auf Befehlsebene
153
beiden Architekturregister $t1 und $t4 geschrieben werden. Um dies in einem
Takt zu erreichen, benötigen wir auch zwei Schreibkanäle für den Registerblock.
Anschließend wird mit dem zweiten Befehlspaar in Listing 4.1 analog verfahren.
Sofern die zweifach superskalare Pipeline bereits befüllt ist, kann der dargestellte
Programmausschnitt in zwei Takten insgesamt vier Befehle ausführen. Der IPC
und damit die Leistungsfähigkeit wird durch die superskalare Pipeline von 1 auf
2 erhöht.
1
2
3
4
5
6
7
8
9
sll
add
or
add
and
add
div
add
or
$t0 ,
$t0 ,
$t1 ,
$t1 ,
$t1 ,
$t2 ,
$t3 ,
$t4 ,
$t4 ,
$a0 ,
$t0 ,
$a1 ,
$t1 ,
$t1 ,
$t0 ,
$t2 ,
$t1 ,
$t4 ,
2
4
8
-1
8
$t1
6
1
8
Listing 4.2: Programmausschnitt zur Übung 1
Selbsttestaufgabe 4.1
Wieviele Taktzyklen werden für die Ausführung des Programms nach Listing
4.2 benötigt, wenn die Befehle in Programmreihenfolge zugeordnet werden und
stets zwei passende Ausführungseinheiten zur Verfügung stehen?
In Abbildung 4.3 wird gezeigt, wie die parallele Befehlsausführung implementiert werden kann und welche Prozessorarten daraus entstanden sind. Diese
werden wir im Folgenden genauer betrachten.
4.2.2
Very Long Instruction Word
Wenn statt zwei eine deutlich größere Anzahl von Ausführungseinheiten zur
Verfügung stehen, kann man diese durch ein sehr langes Befehlswort ansprechen. Man bezeichnet Prozessoren mit dieser Technik als VLIW-Prozessoren
(Very Long Instruction Word ). Ordnet man jedem Teilabschnitt dieses Befehlswortes eine Ausführungseinheit zu, so kann dieses auch sehr schnell von der
Hardware decodiert werden. Wie im obigen Beispiel der zweifachen Parallelität
wird dann explizit im Befehlswort festgelegt, welche Ausführungseinheiten im
jeweiligen Takt eine bestimmte Operation ausführen sollen. Der Compiler muss
also beim Übersetzen die Mikroarchitektur des Prozessors genau kennen und
kann keine binäre Maschinenprogramme erzeugen, die auf unterschiedlichen
Mikroarchitekturen ablauffähig sind.
Man spricht von einer statischen Befehlszuordnung (static instruction scheduling), da die Zuordnung von Befehlen zu den Ausführungseinheiten nicht statisches
während der Laufzeit geändert werden kann. So können z. B. Cache-Fehlzugriffe Scheduling
nachfolgende Befehlsgruppen blockieren, weil die Hardware keinen direkten
154
Kapitel 4. Weiterführende Prozessortechniken
Parallele
Befehlsausführung
Dy
Sc nam
he is
du ch
lin es
g
s
he ng
c
s
i
ati ul
St hed
Sc
Compiler
In-order issue
In-order completion
Very Long
Instruction Word
(VLIW-Prozessor)
Unterstützung
In-order issue
Out-of-order
completion
In-Order
Execution
(IOE-Prozessor)
Hardware
Out-of-order issue
Out-of-order
completion
Out-of-Order
Execution
(OOE-Prozessor)
Abbildung 4.3: Nutzung der Parallelität auf Befehlsebene
kein BefehlsScheduler nötig
Einfluss auf die Zuordnung der Teilfunktionen zu den Ausführungseinheiten hat.
Im Gegensatz dazu gibt es in den beiden nachfolgend beschriebenen Varianten
so genannter Superskalarprozessoren jeweils einen Befehls-Scheduler, der einzelne Befehle einer bestimmten Befehlssatzarchitektur entgegennimmt und deren
Ausführung auf parallel vorhandenen Ausführungseinheiten verplant.
VLIW-Prozessoren benötigen keinen Befehls-Scheduler und haben somit
mehr Platz für weitere Funktionseinheiten. Da in ihrer Befehlssatzarchitektur mit
einem sehr langen Befehlswort (z. B. 128 Bit) in jedem Befehl zahlreiche Mikrooperationen ablaufen, lässt sie sich nur schlecht vereinheitlichen und nicht – wie
bei Befehlssatzarchitekturen üblich – durch verschiedene Mikroarchitekturen realisieren. Wie oben bereits erwähnt, muss der Compiler viele Opcode-Fragmente
auf die Mikroarchitektur abstimmen. Da VLIW-Prozessoren keinen BefehlsScheduler haben, werden ihre Befehle stets in der vom Compiler vorgegebenen
Reihenfolge an die Ausführungseinheiten zugeordnet (in-order issue) und die
wenig komplexen Teiloperationen können meist in einem einzigen Taktzyklus
abgeschlossen werden. Daher erfolgt die Fertigstellung der Befehle in der gleichen
Reihenfolge (in-order completion) und es wird keine besondere Rückordnungseinheit zur Herstellung der sequentiellen Programmreihenfolge benötigt. Falls pro
Taktzyklus nicht alle Funktionseinheiten genutzt oder Laufzeitkorrekturen nötig
werden, müssen NOOP-Codes (No operation) eingefügt oder die Pipeline muss
angehalten werden (Abbildung 4.4). Da VLIW-Prozessoren einen sehr geringen
Hardware-Aufwand für die Steuerlogik benötigen, sind sie sehr Energie-effizient,
4.2. Parallelität auf Befehlsebene
Abbildung 4.4: Befehlszuordnung bei einem VLIW-Prozessor
d. h. ihr Energieverbrauch pro Operation ist gering.
Bisherige VLIW-Prozessoren waren trotzdem am Markt nicht besonders
erfolgreich. So hat Intel kürzlich die Produktion der Prozessoren mit ItaniumIA-64-Architektur eingestellt. Diese basierte im Wesentlichen auf einer VLIWMikroarchitektur mit Compiler-Unterstützung und Prädikation zur Reduktion
von Sprungbefehlen (vgl. Abschnitt 4.2.5).
An dem obigen Beispiel eines Prozessors mit zweifacher Parallelität haben
wir die grundlegende Arbeitsweise eines Superskalarprozessors kennengelernt.
Da die Latenz der beiden Ausführungseinheiten eins ist, gelingt es nur mit dem
Compiler, unabhängige Paare von Befehlen zu bilden. Für komplexere Operationen haben die Ausführungseinheiten der Mikroarchitekturen jedoch größere
und unterschiedliche Latenzen. Diese müssten bei der Übersetzung des Quellprogramms bekannt sein, damit vom Compiler entsprechende Befehlsgruppen
gebildet werden können. Es ist daher problematisch, das Befehls-Scheduling nur
mit Hilfe eines Compilers auszuführen. Wir werden in den nächsten Abschnitt
sehen, dass dazu ein so genannter Befehls-Scheduler (Instruction scheduler, IS)
vorgesehen wird.
Trotzdem kann der Compiler die Ausführung in der superskalaren Pipeline
unterstützen, indem er datenabhängige Befehle soweit wie möglich voneinander trennt. So wird die Zahl der Datenkonflikte reduziert, die später durch
entsprechende Hardware zum Befehls-Scheduling gelöst werden müssen. Der
Vorteil des Compilers liegt darin, dass zur Optimierung der Befehlsanordnung
das gesamte Programm einbezogen werden kann. Ein Befehls-Scheduler kann
dagegen nur einen kleinen Ausschnitt der Befehle einbeziehen, die auf den
aktuellen Programmzähler (Programm Counter, PC) folgen.
155
156
Kapitel 4. Weiterführende Prozessortechniken
Abbildung 4.5: Aufbau eines Superskalarprozessors mit in-order execution (IOE)
4.2.3
In-Order-Execution-Pipeline
In Abbildung 4.5 ist der Aufbau eines Superkalarprozessors mit In-Order Execution (IOE) dargestellt. Die Befehlsholeeinheit (Instruction Fetch, IF) holt
ständig Befehle aus dem Befehls-Cache (I-Cache), speichert sie in einem internen
Befehls-Puffer und leitet sie an eine parallel arbeitende Dekodiereinheit weiter
(Instruction Decode, ID). Die dekodierten Befehle werden in ein so genanntes
Befehlsfenster (Warteschlange) eingespeist, das der Befehls-Scheduler auswertet
und ablaufbereite Befehle den Ausführungseinheiten zuordnet.
Die Ausführung der Befehle erfolgt hier in der Programmreihenfolge. Von
der aktuellen PC-Adresse werden die n (hier n = 4) nachfolgenden Befehle
betrachtet. Falls keine Datenkonflikte bestehen, werden sie den passenden
Ausführungseinheiten zugeordnet. Dieser Idealfall wird jedoch in der Regel
selten vorliegen, da aufgrund von Daten- und Strukturkonflikten meist nur
weniger als n Befehle ausgeführt werden können.
Trace Cache
Da in der IOE-Pipeline mehrere Befehle gleichzeitig ausgeführt werden, müssen
pro Takt soviele Befehle geholt werden, wie der superskalare Prozessor maximal parallel ausführen könnte. Dies stellt – insbesondere wegen der häufigen
Sprungbefehle (jeder fünfte bis siebte Befehl ist ein Sprungbefehl) – erhöhte
Anforderungen an die Cache-Organisation.
Im Befehls-Cache können die Befehle über mehrere Cache-Blöcke verteilt
sein (vgl. Abbildung 4.6). Dies kann dazu führen, dass zum Holen der Befehle,
entlang eines bestimmten Befehlspfades, viele Cache-Zugriffe erfolgen und daher
die Ausführungseinheiten nicht immer mit genügend Befehlen versorgt werden.
Um eine ausreichende Zuordnungsbandbreite zu erreichen, verwendet man
einen so genannten Trace Cache zum Speichern dynamischer Befehlssequenzen.
Dieser kann parallel zu den Zugriffen auf den I-Cache befüllt werden und
speichert einen gewissen Vorrat an (aufeinander folgenden) Befehlsblöcken mit
4.2. Parallelität auf Befehlsebene
linearen Befehlssequenzen1 , die durch Sprünge miteinander verbunden sind.
Die Cache-Blöcke des Befehls-Caches werden dabei so zerlegt, dass die Befehle
häufig genutzter Befehlspfade hintereinander im Trace Cache stehen. Wird die
entsprechende Befehlssequenz später erneut ausgeführt, so müssen nicht länger
viele einzelne Cache-Blöcke aus dem I-Cache geladen werden. Stattdessen steht
im Trace Cache bereits die komplette Befehlssequenz und diese kann sehr viel
schneller in den Prefetch-Puffer des Prozessors geholt werden.
Im Folgenden gehen wir davon aus, dass die Befehlsbereitstellung durch den
I-Cache stets durch einen Trace Cache beschleunigt werden kann. Da dieser die
kompletten Befehlssequenzen speichert, wird hiermit auch die Verarbeitung von
Sprungbefehlen unterstützt.
Befehls-Scheduler
Der Befehls-Scheduler ist für die Koordination der Befehlsausführung zuständig.
Dazu muss er prüfen, ob es im Befehlsfenster ablaufbereite Befehle gibt und ob
für diese auch Ausführungseinheiten bereitstehen:
Abbildung 4.6: Trace Cache speichert häufig genutzte Befehlspfade
• Die Ablaufbereitschaft ist immer dann gegeben, wenn es zwischen dem
gerade untersuchten Befehl und seinen Vorgängern keine echte Datenabhängigkeiten gibt. Wenn wir nur die aktuell im Befehlsfenster vorliegenden
Befehle betrachten, ist beim IOE-Prozessor der vorderste Befehl des Befehlsfensters stets ablaufbereit. Alle nachfolgenden Befehle, bei denen
wenigstens ein Quellregister gleich dem Ergebnisregister eines VorgängerBefehls ist, sind wegen der echten Datenabhängigkeit nicht ablaufbereit.
Der erste nicht ablaufbereite Befehl blockiert sowohl seine eigene Zuordnung als auch die seiner Nachfolger.
• Für alle ablaufbereiten Befehle ist vom Befehls-Scheduler weiter zu prüfen,
ob für sie eine passende Ausführungseinheit verfügbar ist. Wenn diese
Prüfung ebenfalls erfolgreich verläuft, werden die betreffenden Befehle im
Befehlsfenster an die entsprechenden Ausführungseinheiten ausgegeben.
Im Idealfall sind alle Befehle im Befehlsfenster ablaufbereit und können
einer der n Ausführungseinheiten zugeordnet werden. In diesem Fall wird
ein IPC von n erreicht. Im Mittel wird der IPC-Wert jedoch deutlich
1
Diese nennt man auch basic blocks.
157
158
Kapitel 4. Weiterführende Prozessortechniken
Abbildung 4.7: Erweiterung der IOE-Pipeline um einen Rückordnungspuffer
unter dem Optimum liegen, da wegen der In-Order-Ausführung bereits
ein Datenkonflikt genügt, um auch alle nachfolgenden Befehle an der
Ausführung zu hindern.
Selbsttestaufgabe 4.2
Es sei angenommen, dass n = 4 ist und dass im Befehlsfenster nur Registerbefehle
mit je zwei Quell- und einem Ergebnisregister stehen. Wieviele Vergleiche sind
nötig, um die ablaufbereiten Befehle zu bestimmen?
Out-of-Order-Fertigstellung
Rückordnungspuffer
Obwohl der Befehls-Scheduler die Befehle in-order startet, werden sie in der
Regel nicht in der gleichen Reihenfolge fertiggestellt. Der Grund dafür ist, dass
einige Ausführungseinheiten selbst auch als Pipeline implementiert sind und
daher mehrere Takte (Latenz > 1) zur Fertigstellung (completion) benötigen.
Da die Programmausführung auf einer superskalaren Mikroarchitektur die
gleichen Ergebnisse wie auf einer beliebigen anderen Mikroarchitektur liefern
muss, wird eine zusätzliche Funktionseinheit benötigt. Diese hat die Aufgabe,
die Ergebnisse der Ausführungseinheiten in der korrekten Programmreihenfolge in den Architekturregistern gültig zu machen. Der so genannte Rückordnungspuffer (Reorder Buffer, RoB) besteht aus einer Warteschlange (First-InFirst-Out-Speicher, FIFO), in die die Befehle bei der Zuordnung (issue) in
Programmreihenfolge eingereiht werden.
In Abbildung 4.7 wurde die IOE-Pipeline um einen solchen Rückordnungspuffer erweitert. Jeder zugeordnete Befehl wird dort durch zwei Einträge beschrieben:
4.2. Parallelität auf Befehlsebene
159
• ein Befehlsetikett (Instruction Tag, IT) und der
• Ergebnisadresse (Result Address, RA).
Als Befehlsetikett kann die Befehlsadresse im Speicher oder eine äquivalente,
kompaktere Kodierung dienen. Als Ergebnisadresse wird das Zielregister oder
eine Speicheradresse im Hauptspeicher verwendet. Bei der Ausgabe des Befehls an eine Ausführungseinheit (dispatch) wird das Befehlsetikett auch an
die Hardware weitergeleitet. Es durchläuft die einzelnen Pipeline-Stufen und
kommt schließlich zusammen mit dem Ergebnis im letzten Pipeline-Register
der jeweiligen Ausführungseinheit an. Dies signalisiert die Fertigstellung des
Befehls.
Am vorderen Ende des Rückordnungspuffers befindet sich ein Vergleicher,
der die Befehlsetiketten der n Befehle am Kopf der Warteschlange ständig mit
allen Befehlsetiketten aus den letzten Pipeline-Stufen der Ausführungseinheiten
vergleicht. Sobald eine Übereinstimmung mit den vorderen n Befehlsetiketten
im Rückordnungspuffers vorliegt, gilt der betreffende Befehl als fertiggestellt
und er kann gültig gemacht werden (commitment). Dazu muss er entweder am
Kopf der Warteschlange stehen oder er folgt unmittelbar auf einem anderen
ebenfalls gültig zu machenden Befehl. Der erste nicht gültig zu machende Befehl
blockiert sich selbst und alle nachfolgenden Befehle im Rückordnungspuffer.
Das Gültigmachen besteht darin, dass die Ergebnisse der betreffenden Befehle
in die jeweiligen Architekturregister zurückgeschrieben werden (write back, WB).
Danach werden die gültig gemachten Befehle vom Kopf des Rückordnungspuffers
entfernt. Diesen Schritt bezeichnet man als retirement, was soviel bedeutet wie
in den Ruhestand versetzen.
Mit der Einführung des Rückordnungspuffers, zur Sicherstellung der sequentiellen Programmordnung, muss auch der Befehls-Scheduler erweitert werden.
So kann es vorkommen, dass Ergebnisadressen im RA-Teil der Einträge des
Rückordnungspuffers auch Quelloperanden der gerade zuzuordnenden Befehle im
Befehlsfenster sind. Wegen des dabei vorliegenden RAW-Datenkonflikts dürfen
davon betroffene Befehle nicht zugeordnet werden. Wenn der vorderste Befehl
im Befehlsfenster einen RAW-Datenkonflikt mit einem RoB-Eintrag hat, muss
die Befehlszuordnung solange angehalten werden, bis der den Datenkonflikt
erzeugende Vorgänger-Befehl gültig gemacht wurde.
Um eine hohe Befehlsebenenparallelität (hoher IPC-Wert) zu erreichen, muss
der Compiler den Maschinencode so optimieren, dass er auf die konkrete Struktur
der Ausführungseinheiten abgestimmt ist. Bereits vorhandener Maschinencode
ist für die Ausführung auf einem IOE-Prozessor nicht geeignet, weil er aufgrund
fehlender Optimierung die vorhandenen Ausführungseinheiten nicht effizient
nutzen kann. Im Extremfall wird in jedem Taktzyklus nur der vorderste Befehl
im Befehlsfenster ausgegeben und so wird der IOE-Prozessor zu einem skalaren
RISC degradiert.
completion
Fertiggestellte
Befehle
commitment
retirement
Datenkonflikte
durch out-of-order
completion
Nachteil der IOE
160
Kapitel 4. Weiterführende Prozessortechniken
Abbildung 4.8: Aufbau der Pipeline eines Superskalarprozessors mit Out-ofOrder Execution und Rückordnungspuffer
Selbsttestaufgabe 4.3
Wie muss das Programm nach Listing 4.2 umgeordnet werden, damit es für die
Ausführung auf einem IOE-Prozessor optimiert ist?
Um die oben angesprochene Problematik zu lösen, konstruiert man Out-ofOrder-Execution-(OOE)-Prozessoren, die statt eines statischen ein dynamisches
Befehls-Scheduling durch die Hardware vornehmen. Dabei werden die Befehle so
an die Ausführungseinheiten weitergeleitet (dispatch), dass sie auch außerhalb
der sequentiellen Programmreihenfolge ausgeführt werden können. Dadurch
wird eine bessere Auslastung der Ausführungseinheiten und somit eine hohe
Befehlsebenenparallelität erreicht.
4.2.4
Out-of-Order-Execution-Pipeline
In Abbildung 4.8 ist der Aufbau eines Superskalarprozessors mit Out-of-Order
Execution (OOE) dargestellt. Auch hier wird ein Rückordnungspuffer zur
Sicherstellung der sequentiellen Programmreihenfolge eingesetzt. Wie beim
IOE-Prozessor müssen die Befehle in Programmreihenfolge am Ende der RoBWarteschlange zugeordnet werden. Im Gegensatz zur IOE-Pipeline werden nun
die Befehle vom Befehls-Scheduler außerhalb der Programmreihenfolge an die
Ausführungseinheiten weitergeleitet (dispatch).
Dies führt zu einem schwerwiegenden Problem, das es so bei IOE-Mikroarchitekturen nicht gibt: Durch eine vertauschte Reihenfolge der Befehle können
scheinbare zu echten Datenabhängigkeiten werden (und umgekehrt). Während
4.2. Parallelität auf Befehlsebene
die Ausführungseinheiten bei IOE nur die Architekturregister verwenden, ist dies
bei der OOE-Variante nicht mehr möglich. Um die Äquivalenz zum sequentiellen
Programm sicherzustellen, müssen daher bei OOE zusätzliche Schattenregister
verwendet werden.
Es gibt zwei Möglichkeiten, die Architekturregister temporär durch Schattenregister zu ersetzen:
• Registerumbennung (renaming) oder
• Registerumordnung (buffering).
Bei der Registerumbennung gibt es eine Sammlung freier Register, die über Registereine Tabelle beliebig umbenannt werden können. Je nach Eintrag verhalten umbennung
sich die Register entweder als nur intern verwendbare Schattenregister oder als
Architekturregister. Obwohl die Schattenregister nicht aus einem Programm
ansprechbar sind, erhöhen sie die Zahl der Prozessor-internen Speichermöglichkeiten und unterstützen so die out-of-order-Verarbeitung von Befehlen. Um
Konflikte durch scheinbare Datenabhängigkeiten zu vermeiden, werden die Architekturregister zeitweise in Schattenregister umbenannt, indem ihre Zuordnung
über die Umbennungstabelle geändert wird.
1
2
3
add $t1 , $t2 , $t3
sub $t3 , $t0 , $t2
add $t1 , $t0 , $t2
Listing 4.3: Beispiel zur Registerumbennung
Betrachten wir den Programmausschnitt aus Listing 4.3 so erkennen wir,
dass zwischen Befehl 1 und 2 ein WAR- und zwischen Befehl 1 und 3 ein
WAW-Datenkonflikt besteht. Der WAR-Datenkonflikt wird bei Vertauschung
der Ausführungsreihenfolge zu einem RAW-Datenkonflikt werden. Durch Umbenennung von Register $t3 in Befehl 2 zu einem Schattenregister SRi kann
diese echte Datenabhängigkeit aufgrund der out-of-order execution behoben
werden. Ähnlich verhält es sich mit der WAW-Datenabhängigkeit, die durch
Umbennenung von $t1 in Befehl 3 zu einem Schattenregister SRj behoben wird.
Wir erhalten also folgende Umbenennungen:
$t3 −→ SRi
$t1 −→ SRj
Mit dem Gültigmachen der Befehle 2 und 3 müssen natürlich die Schattenregister wieder in die ursprünglichen Architekturregister umbenannt werden:
SRi −→ $t3
SRj −→ $t1
Die Umbennungen in Schattenregister müssen beide gleichzeitig in der
ID-Phase des (gleichzeitig geholten) Befehlsblocks erfolgen und werden beim
Gültigmachen der betroffenen Befehle wieder rückgängig gemacht.
161
162
Registerumordnung
Kapitel 4. Weiterführende Prozessortechniken
Bei der zweiten Variante werden die Architekturregister in die Schattenregister kopiert. Man bezeichnet dies als Registerumordnung. Das Verfahren
basiert auf einem Algorithmus , den Tomasulo im Jahre 1967 für die IBM 360/91
vorgeschlagen hat. Jeder Ausführungseinheit wird eine so genannte Reservierungsstation (reservation station) zugeordnet. Diese verfügt eingangsseitig über
eine Warteschlange mit zwei bis vier Einträgen, in die alle zur Ausführung eines
Befehls benötigten Informationen eingeschrieben werden:
• Kopien der Quelloperanden sowie die
• gemäß dekodiertem Opcode auszuführende Operation.
Die Quelloperanden können entweder aus dem Registerblock oder als Ergebnis aus einer noch laufenden Berechnung einer anderen Ausführungseinheit
stammen. Für die zweite Möglichkeit werden die Ergebnisse aller Funktionseinheiten sofort bei Fertigstellung über den so genannten Common Data Bus
(CDB) an alle anderen Reservierungsstationen versandt (broadcast). Sobald
eine Reservierungsstation feststellt, dass alle Operanden für eine in ihrer Warteschlange stehende Berechnung vorliegen, beginnt sie die Ausführung der im
dekodierten Opcode spezifizierten Operation. Die Operanden können entweder
Kopien von Registerinhalten aus dem Registerblock sein oder bei Fertigstellung
eines vorangehenden Befehls vom CDB in die Warteschlange kopiert werden.
Die Technik der Reservierungsstationen bietet eine elegante Möglichkeit, alle
Arten von Datenkonflikten ohne eine zentrale Steuerlogik zu lösen. Mit Hilfe des
CDBs können sogar Befehle mit echten Datenabhängigkeiten sofort zugeordnet
werden. Der datenabhängige Befehl wird solange in der Reservierungsstation
verweilen, bis der benötigte Quelloperand als Ergebnis einer Ausführungseinheit
auf dem CDB „rundgestrahlt“ wird. Er kann seine Ausführung auch sofort starten,
noch bevor der vorangehende Befehl gültig gemacht und das Ergebnis in das
zugehörige Architekturregister übertragen wurde. Durch den CDB erreicht man
also einen ähnlichen Effekt wie mit dem in Kapitel 2 beschriebenen Forwarding.
Die Einbeziehung der Reservierungsstationen erfordert, dass die RA-Einträge
im Rückordnungspuffer der OOE-Pipeline in Abbildung 4.8 um die Angabe
der Reservierungsstation erweitert wird. Wenn der Befehls-Scheduler einer
Reservierungsstation einen Befehl zuordnet, wird neben der Angabe des Architekturregisters (bzw. bei Speicher-Befehlen der Speicheradresse) für das Ergebnis
auch die Reservierungsstation der Ausführungseinheit angegeben, in die der
Befehl ausgegeben wurde.
Wenn der Befehls-Scheduler nach startbaren Befehlen sucht, erkennt er anhand der Architekturregister (oder Speicheradressen) Datenkonflikte zwischen
Befehlen im Befehlsfenster und bereits zugeordneten aber noch nicht fertiggestellten Befehlen. Falls es in den Reservierungsstationen freie Plätze für diese
abhängigen Befehle gibt, kann der Befehls-Scheduler sie zuordnen, indem er
als Quelloperanden entweder die Architekturregister (bzw. Speicheradressen)
oder im Falle eines Datenkonflikts die Reservierungsstation der noch laufenden
Befehle einträgt. Über den CDB erkennt die Reservierungsstation, dass ein von
4.2. Parallelität auf Befehlsebene
163
Abbildung 4.9: Befehlszuordnung bei IOE- und OOE-Prozessoren
ihr benötigter Operand berechnet wurde. Sie kann diesen dann kopieren und
unmittelbar danach die Ausführung starten.
Wie bei der IOE-Pipeline wird parallel zur Eintragung der Operation auch
das Befehlsetikett in die Reservierungsstation übernommen und nach dem
Abschluss der Ausführung zusammen mit dem Ergebnis (in der letzten PipelineStufe der Ausführungseinheit) ausgegeben. Auch hier sorgt der Rückordnungspuffer für die Einhaltung der sequentiellen Programmordnung, indem die Befehle
am Ende der Warteschlange in der Programmreihenfolge zugeordnet und später am Kopf der Warteschlange in der gleichen Reihenfolge gültig gemacht
werden. Die Ergebnisse der Ausführungseinheiten werden zusammen mit dem
Ergebnis sowie zugehörigen Befehlsetikett in einem Pipelineregister vor der
Commit-Einheit vorgehalten und parallel mit den Befehlsetiketten am Kopf
des Rückordnungspuffers verglichen. Ein n-fach-Vergleicher überprüft, welche
Befehlsetiketten übereinstimmen und macht die zugehörigen Befehle in Programmreihenfolge gültig. Dies erfolgt genauso wie schon beim IOE-Prozessor
beschrieben.
Selbsttestaufgabe 4.4
Welcher strukturelle Konflikt kann verhindern, dass nicht alle fertiggestellten
Befehle gleichzeitig gültig gemacht werden können?
Abbildung 4.9 stellt die Befehlszuordnung der beiden betrachten Varianten
von Superskalarprozessoren gegenüber. Wir erkennen, dass der IOE-Prozessor
immer nur eine bestimmte Anzahl an direkt aufeinander folgenden Befehlen
parallel in den Ausführunsgeinheiten verarbeitet.
Dagegen kann der OOE-Prozessor beliebig im Befehlsfenster verteilte Befehle
164
Kapitel 4. Weiterführende Prozessortechniken
an die Ausführungseinheiten ausgeben. Dadurch erreicht er einen höheren IPCWert und eine entsprechend höhere Leistungsfähigkeit.
Selbsttestaufgabe 4.5
Bestimmen Sie die IPC-Werte für die beiden Prozessoren aus Abbildung 4.9!
Größe des Befehlsfensters
Die Größe des Befehlsfensters hat einen entscheidenden Einfluss auf das Scheduling der Befehle. Das verfügbare Befehlsfenster ist beim statischen Scheduling
durch einen Compiler am größten, da dieser das ganze Programm berücksichtigen kann. Beim OOE-Prozessor ist das Befehlsfenster zwar deutlich kleiner,
aber größer als die Zuordnungsbreite n (Superskalarität), um möglichst viel
Befehlsparallelität entdecken zu können. Da die Größe des Befehlsfensters den
Hardwareaufwand für die Logik des Befehls-Schedulers bestimmt, bleibt sie
jedoch gegenüber dem statischen Scheduling mittels Compiler beschränkt (z. B.
50 Einträge beim Intel Core i7 Prozessor (Coffee Lake) aus 2017). Beim IOEProzessor muss das Befehlsfenster nur maximal so groß wie die Zuordnungsbreite
n sein. Dies erklärt den deutlich geringeren Hardwareaufwand gegenüber der
OOE-Variante weil – neben dem Wegfall der Reservierungsstationen – weniger
Vergleiche zur Prüfung möglicher Datenkonflikte ausgeführt werden müssen.
Vergleich von IOE- und OOE-Prozessor
Brainiac
Speed demon
Aus Sicht des Nutzers bietet ein OOE-Prozessor nur Vorteile. So kann er binäre
Maschinenprogramme ohne erneute Compilierung verarbeiten und erreicht dabei
einen höheren IPC als ein IOE-Prozessor. Nachteilig ist allerdings, dass der
OOE-Prozessor eine sehr komplexe Steuerlogik für den Befehls-Scheduler und
die Reservierungsstationen benötigt. Wegen dieser Eigenschaft werden OOEProzessoren häufig auch als Brainiacs bezeichnet2 . Aufgrund der komplexen
Steuerlogik steigt der Bedarf an Chipfläche und der Energieverbrauch ist beim
OOE- deutlich höher als beim IOE-Prozessor. Daher kann die OOE-Variante
auch nicht so hoch wie dieser getaktet werden.
Der IOE-Prozessor benötigt aufgrund seiner vergleichsweise einfachen Steuerlogik weniger Chipfläche, weniger Energie und kann mit höheren Taktfrequenzen
als der OOE-Prozessor betrieben werden. Somit können auf einem einzigen
Chip Prozessoren mit mehr und schnelleren IOE-Kernen implementiert werden.
Wegen der letztgenannten Eigenschaft werden IOE-Prozessoren auch als Speed
demons bezeichnet. Nachteilig bei IOE ist, dass mit den einzelnen Kernen nur ein
geringerer Grad an Befehlsebenenparallelität erreicht wird und dass wegen des
statischen Befehls-Schedulings eine Compiler-Unterstützung unverzichtbar ist.
2
Brainiac ist eine Comicfigur aus dem Jahre 1958, die unter anderem als denkendes
Computerprogramm mit eigener Persönlichkeit dargestellt wurde.
4.2. Parallelität auf Befehlsebene
Hierzu müssen auch die Quellprogramme vorliegen, was bei manchen klassischen
Anwendungen nicht gegeben ist.
Im Laufe der Entwicklungsgeschichte der Prozessor-Technik haben die Halbleiterhersteller häufiger zwischen dem Brainiac- und Speed-demon-Ansatz gewechselt. Wir werden in Abschnitt 4.5 Beispiele dafür kennenlernen. Generell
kann man feststellen, dass heute die Brainiac-Variante vorwiegend bei leistungsstarken Multicore-Prozessoren für universelle Anwendungen (general purpose
computing) zu finden ist. Die Speed-demon-Variante findet man bei ManycoreProzessoren, die über mehr als 16 Kerne verfügen und bei GPUs (Graphic
Processing Units), die außer bei Grafikanwendungen auch zur Lösung von
datenparallelen Problemstellungen eingesetzt werden.
Wie das Zitat am Beginn dieses Kapitels andeutet, ist die Effektivität der
OOE-Prozessoren nicht überzeugend. Untersuchungen haben gezeigt, dass sie
nur 20 bis 40% besser sind als IOE-Prozessoren. Außerdem bringt es selbst bei
ausgefeilten OOE-Designs deutliche Verbesserungen, wenn die Quellprogramme
mit spezieller Prozessor-Optimierung neu übersetzt werden und so binärer Maschinencode erzeugt wird, der besser auf die Mikroarchitektur zugeschnitten ist.
Der häufig genannte Vorteil der „recompile independence“ von OOE-Prozessoren
ist also eine Legende.
Die Befehlsebenenparallelität (ILP) der Anwenderprogramme ist leider begrenzt. Ursachen sind
• geringer Anteil an feinkörniger Parallelität,
• Cache-Fehlzugriffe und hohe Speicher-Latenzen,
• Datenabhängigkeiten und
• Steuerflusskonflikte.
Aufgrund dieser Ursachen entsteht eine Hürde, die als ILP Wall bezeichnet
wird. Der IPC erreicht z. B. bei Ausführung einer Sammlung von typischen
Programmen mit Ganzzahlarithmetik (SPECint Benchmark) selbst bei OOEProzessoren mit 8-facher Superskalarität gerade mal einen IPC-Wert von 2.
Da reale Anwendungen deutlich schwieriger zu parallelisieren sind, kann man
diesen Wert als Obergrenze für die Leistungsfähigkeit von OOE-Prozessoren
betrachten.
Im nächsten Unterabschnitt werden wir die Sprungvorhersage als weitere
Maßnahme (neben dem Trace Cache) kennenlernen, um Leistungsverluste durch
die relativ häufig vorkommenden Steuerflusskonflikte gering zu halten.
4.2.5
Sprungvorhersage und spekulative Ausführung
Leider wird der kontinuierliche Programmfluss durch die Pipeline immer wieder
durch unbedingte und bedingte Sprungbefehle gestört. So ist im Schnitt etwa
jeder fünfte bis siebte Befehl ein bedingter Sprungbefehl. Ohne weitere Maßnahmen entstehen aufgrund von Steuerflusskonflikten Leistungsverluste bei der
165
166
SprungzieladressCache
Kapitel 4. Weiterführende Prozessortechniken
Pipeline-Verarbeitung, da sowohl die Sprungrichtung als auch das Sprungziel
erst am Ende der Ausführungsphase des Sprungbefehls vorliegen. Wenn der
Sprung genommen wird, müssen die beiden bis dahin geholten bzw. dekodierten
Befehle verworfen werden. Obwohl bei unbedingten Sprüngen die Sprungrichtung fest ist, muss ebenfalls das Sprungziel berechnet werden und es gehen
auch hier zwei Taktzyklen verloren. Da bei heutigen Prozessoren die Anzahl
der Pipeline-Stufen ca. drei bis viermal mal größer ist als beim MIPS, ist der
durch Sprungbefehle entstehende Leistungsverlust entsprechend hoch.
In Kapitel 2 wurde schon eingeführt, dass zur Verringerung dieser Verluste
die statische oder dynamische Sprungvorhersage verwendet werden. Ziel beider
Ansätze ist es, Sprungrichtung und Sprungziel schon während der IF-Phase
vorherzusagen und ohne Verzögerung ab der darauffolgenden IF-Phase spekulativ auszuführen. Dabei muss die Sprungrichtung nur für bedingte Sprünge
vorhergesagt werden.
Um eine schnelle Vorhersage innerhalb eines Taktes zu erreichen, wird sowohl
die neue Befehlszähleradresse als auch die Sprungrichtung aus einem schnellen
Assoziativspeicher gewonnen. Dieser Sprungzieladress-Cache (Branch Target
Address Cache, BTAC) oder Sprungzielpuffer (Branch Target Buffer, BTB)
ist ein kleiner Cache-Speicher, auf den in der IF-Stufe der Pipeline zugegriffen
wird. Der Sprungzieladress-Cache umfasst eine Menge von Tupeln, von denen
jedes die folgenden Elemente enthält (siehe Abbildung 4.10):
• die Befehlsadresse eines Sprungbefehls, der in der Vergangenheit ausgeführt
wurde,
• die Zieladresse dieses Sprungs,
• (optional): Vorhersagebits, die steuern, ob im Falle eines bedingten
Sprungs dieser als „genommen“ oder „nicht genommen“ vorhergesagt wird,
oder ob es sich um einen unbedingten Sprung handelt.
Abbildung 4.10: Erweiterung der IF-Stufe durch einen Sprungzieladress-Cache
(BTAC).
4.2. Parallelität auf Befehlsebene
167
Während die Vorhersagebits bei statischer Vorhersage vom Compiler fest
vorgegeben sind, werden sie bei dynamischer Sprungvorhersage über eine Sprungverlaufstabelle ermittelt (vgl. Unterabschnitt 4.2.5).
Der Sprungzieladress-Cache funktioniert wie folgt: Die IF-Stufe vergleicht den
Inhalt des Befehlszählerregisters (PC) mit den Befehlsadressen der Sprungbefehle
im Sprungzieladress-Cache. Falls ein passender Eintrag gefunden wird3 , wird
die zugehörige Zieladresse als neuer PC benutzt und der Befehl am Sprungziel
als nächstes geholt.
Falls kein Eintrag vorhanden ist, so wird ein Eintrag erzeugt, sobald das
Sprungziel berechnet ist, und dafür eventuell ein anderer Eintrag überschrieben.
Der Sprungzieladress-Cache ist meist als vollassoziativer Cache-Speicher organisiert, d. h. der Vergleich des aktuellen Befehlszähler-Inhalts geschieht simultan
in einem Takt mit allen im Cache gespeicherten Adressen von Sprungbefehlen
(siehe Kapitel 3). Er benötigt wie alle Cache-Speicher eine gewisse „Aufwärmzeit“, in der beim erstmaligen Ausführen der Sprungbefehle die Einträge erzeugt
werden.
Der Sprungzieladress-Cache speichert die Adressen von bedingten wie auch
von unbedingten Sprüngen. Bei bedingten Sprüngen handelt es sich daher immer
um eine spekulierte Sprungzieladresse, wohingegen bei unbedingten Sprüngen
die Sprungrichtung fest ist.
Wenn der geholte Befehl ein bedingter Sprung ist, wird eine Vorhersage, ob
der Sprung genommen wird oder nicht, auf Basis der Vorhersagebits getroffen.
Wird er als „genommen“ vorhergesagt, so wird die zugehörige Sprungzieladresse
in den PC übertragen und mit der Befehl von dort geholt.
Natürlich kann eine falsche Vorhersage auftreten. Dann muss der Sprung- Dynamische
zieladress-Cache die Vorhersagebits korrigieren, sobald die Sprungrichtung in Sprungvorhersage
der MEM-Stufe bekannt ist. Da die Hardware die vorhergesagte Sprungrichtung
aufgrund der Sprungverläufe während der Programmausführung modifiziert,
ist diese Art der Sprungvorhersage ein Beispiel einer einfachen dynamischen
Sprungvorhersage. Um die Größe des Sprungzieladress-Cache klein zu halten,
kann die Implementierung so geschehen, dass von den bedingten Sprüngen nur
diejenigen gespeichert werden, die als „genommen“ vorhergesagt werden.
Das Laden der Befehle ab einer Zieladresse geschieht ohne Verzögerung,
wenn die Befehle schon im I-Cache vorhanden sind. Eine Variante des Sprungzieladress-Cache war in älteren Prozessoren ohne On-Chip-Cache verbreitet:
Der so genannte Sprungziel-Cache (Branch Target Cache, BTC) speicherte
pro Eintrag noch zusätzlich ein paar Befehle am Sprungziel ab.
Statische Sprungvorhersagetechniken
Die statische Sprungvorhersage ist eine sehr einfache Vorhersagetechnik, bei Statische vs.
der die Hardware jeden bedingten Sprung nach einem festen Muster vorhersagt. dynamische
In manchen Architekturen kann der Compiler durch ein Bit die Spekulations- Sprungvorhersage
richtung für alle bedingten Sprungbefehle gemeinsam festlegen, indem er die
3
Dies wird in Abbildung 4.10 vorausgesetzt.
168
Kapitel 4. Weiterführende Prozessortechniken
hardwarebasierte Vorhersage umkehrt. In beiden Fällen kann sich die Vorhersage für einen speziellen Sprungbefehl nie ändern. Eine solche „dynamische“
Änderung der Vorhersagerichtung ist jedoch bei den o. g. dynamischen Sprungvorhersagetechniken der Fall, welche die Vorgeschichte des Sprungbefehls zur
Sprungvorhersage nutzen.
Nach einer statischen Sprungvorhersage werden die Befehle auf dem spekulativen Pfad vom Prozessor in die Pipeline eingefüttert, bis die Sprungrichtung
entschieden ist. Falls richtig spekuliert wurde, können die Ergebnisse der Befehle
gültig gemacht werden und es treten keine Verzögerungen auf. Falls die Spekulation fehlgeschlagen ist, so müssen diese Befehle wieder aus der Pipeline gelöscht
werden. Sobald der Sprungbefehl am Kopf des Rückordnungspuffers angelangt
ist, werden hierbei alle nachfolgenden Befehle bis zum Ende der Warteschlange
gelöscht und anschließend mit den Befehlen ab dem korrekten Sprungziel gefüllt.
Einfache Muster für die statische Vorhersage in Hardware sind:
• Predict always not taken: Dies ist das einfachste Schema, bei dem angenommen wird, dass kein bedingter Sprung genommen wird. Dies entspricht
dem schon weiter vorne beschriebenen geradlinigen Durchlaufen eines Programms. Da die meisten Programme auf Schleifen basieren, wird nach
jedem Schleifendurchlauf eine Fehlspekulation stattfinden. Diese Technik
ist also nicht sehr effizient, weil alle Ergebnisse der Befehle nach einem
falsch spekulierten Sprung verworfen werden. (Diese Technik nicht mit
der verzögerten Sprungtechnik verwechseln, bei der die Befehle in den
Verzögerungszeitschlitzen immer ausgeführt werden.)
• Predict always taken: Hier wird angenommen, dass jeder Sprung genommen
wird. Damit werden alle Rücksprünge4 bei Schleifen richtig vorhergesagt.
Ein Sprungzieladress-Cache ist für ein verzögerungsfreies Befehlsladen
nötig.
• Predict backward taken, forward not taken: Alle Sprünge, die in der Programmreihenfolge rückwärts springen, werden als „genommen“ vorhergesagt und alle Vorwärtssprünge als „nicht genommen“. Dahinter steckt
die Idee, dass die Rückwärtssprünge am Ende einer Schleife fast immer genommen und alle anderen vorzugsweise nicht genommen werden.
Die Umsetzung erfolgt wieder in der der IF-Stufe und auch hier ist ein
Sprungzieladress-Cache erforderlich. Die Sprungzieladresse muss mit dem
Wert PC+4 verglichen werden und der Sprung wird nur genommen, wenn
die Zieladresse kleiner als PC+4 ist.
Manche Befehlssätze ermöglichen es dem Compiler, mit einem Bit im Befehlscode der Sprunganweisung die Vorhersage direkt zu steuern (z. B. ,Bit gesetzt‘
bedeutet predict taken, ,Bit nicht gesetzt‘ bedeutet predict not taken) oder,
bei Anwendung einer der obigen statischen Vorhersagetechniken, die statische
Vorhersage für den betreffenden Sprungbefehl umzukehren.
4
außer beim letzten Durchlauf
4.2. Parallelität auf Befehlsebene
Um eine gute Vorhersage zu erzielen, kann der Compiler folgende Techniken
anwenden:
• Analyse der Programmstrukturen hinsichtlich der Vorhersage (Sprünge
zurück zum Anfang einer Schleife sollten als „genommen“ vorhergesagt
werden, Sprünge aus if-then-else-Konstrukten dagegen nicht).
• Der Programmierer kann über Compiler-Direktiven sein Wissen über
bestimmte Abläufe in die Sprungvorhersage einbringen.
• Durch eine Analyse des Verhaltens von früheren Programmabläufen (Profiling) kann das voraussichtliche Verhalten jedes Sprungs ermittelt werden.
Obwohl die Technik des Profilings sehr aufwendig ist, liefert sie fast immer
die besten Ergebnisse.
Dynamische Sprungvorhersagetechniken
In den einzelnen Stufen eines Superskalarprozessors befinden sich viele Befehle
in verschiedenen Ausführungszuständen. Der Ausführungsteil eines Prozessors
arbeitet am besten, wenn der Zuordnungseinheit sehr viele Befehle zur Auswahl
stehen, d. h. das Befehlsfenster groß ist. Unter Berücksichtigung der spekulativen
Ausführung von Befehlen befinden sich bei heutigen Superskalarprozessoren in
der Regel mehrere Sprungbefehle in der Pipeline.
Um eine hohe Leistung bei der Sprungbehandlung zu erzielen, sind folgende
Bedingungen zu erfüllen:
• Die Sprungrichtung muss möglichst rasch festgestellt werden.
• Die Sprungzieladresse muss in einem Sprungzieladress-Cache (BTAC)
nach ihrem erstmaligen Berechnen gespeichert und bei Bedarf, d. h., falls
die Sprungvorhersage einen genommenen Sprung unterstellt, sofort in den
Befehlszähler geholt werden, sodass ohne Verzögerung die Befehle ab der
Sprungzieladresse spekulativ in die Pipeline geladen werden können.
• Die Sprungvorhersage sollte sich durch eine sehr hohe Genauigkeit und
die Möglichkeit auszeichnen, Befehle spekulativ auszuführen.
• Häufig muss ein weiterer Sprung vorhergesagt werden, obwohl der Test der
Bedingung des vorhergehenden Sprungs noch nicht ausgeführt wurde. Der
Prozessor muss daher mehrere Ebenen der Spekulation verwalten können.
• Wenn ein Sprung falsch vorhergesagt wurde, ist außerdem ein schneller
Rückrollmechanismus mit geringem Fehlspekulationsaufwand (Misprediction Penalty) wichtig.
169
170
Kapitel 4. Weiterführende Prozessortechniken
Eine möglichst frühe Bestimmung des Sprungausgangs wird durch die direkte
Auswertung (Forwarding) des Ergebnisses vom vorangegangenen Vergleichsbefehl erzielt, ohne das dieses Ergebnis zunächst im Registersatz abgelegt werden
muss. Der Test der Sprungbedingung kann dadurch bis in die Dekodierstufe
vorgezogen werden.
Die Gesamtleistung einer Sprungvorhersage hängt zum einen von der Genauigkeit der Vorhersage und zum anderen von den Kosten für das Rückrollen bei
einer Fehlspekulation ab. Die Genauigkeit kann durch eine qualitativ bessere
Sprungvorhersagetechnik erhöht werden. Gegenüber den im vorigen Abschnitt
beschriebenen statischen Sprungvorhersagetechniken haben sich bei heutigen
Superskalarprozessoren die wesentlich genaueren dynamischen Sprungvorhersagetechniken durchgesetzt. Der Einsatz größerer Informationstabellen über die
bisherigen Sprungverläufe führt bei diesen Techniken auch zu weniger Fehlspekulationen. Mit den „bisherigen Sprungverläufen“, der so genannten „Historie“ der
Sprünge, sind die Sprungrichtungen der bedingten Sprünge gemeint, die vom
Programmstart bis zum augenblicklichen Ausführungszustand in den Sprungverlaufstabellen gesammelt worden sind.
Die Kosten für das Rückrollen einer Fehlspekulation liegen selbst bei einfachen RISC-Pipelines meist bei zwei oder mehr Takten. Diese Kosten hängen
jedoch von vielen organisatorischen Faktoren einer Pipeline ab. Eine tiefe Pipeline verursacht mehr Kosten als eine kurze Pipeline.
Bei manchen Prozessoren ist es nicht möglich, Befehle aus internen Puffern
zu löschen, d. h. auch wenn Befehle als ungültig erkannt werden, müssen diese
ausgeführt und dann mittels Rückordnungspuffer verworfen werden. Weiterhin
gibt es noch dynamische Einflüsse auf die Kosten für das Rückrollen, wie z. B. die
Anzahl der spekulativen Befehle im Befehlsfenster oder dem Rückordnungspuffer.
Typischerweise können aus diesen meist recht großen Puffern immer nur eine
kleine Anzahl Befehle pro Takt gelöscht werden. Dies führt dazu, dass die
Kosten im Allgemeinen hoch sind, wie z. B. 15 Takte bei Cache-Treffer (bzw. 18
bis 20 Takte bei einem Cache-Fehlzugriff) beim Intel Haswell. Für eine gute
Gesamtleistung bei der Sprungvorhersage ist es daher sehr wichtig, dass die
Genauigkeit der Vorhersage hoch ist.
dynamische SprungBei der dynamischen Sprungvorhersagetechnik wird die Entscheidung über
vorhersagetechnik
die Spekulationsrichtung eines Sprungs in Abhängigkeit vom bisherigen Programmablauf getroffen. Die Sprungverläufe werden beim Programmablauf in
Sprungverlaufstabellen gesammelt und auf der Grundlage der Tabelleneinträge
werden aktuelle Sprungvorhersagen getroffen. Bei Fehlspekulationen werden die
Tabellen per Hardware abgeändert.
Im Gegensatz dazu steht die Sprungvorhersage bei den statischen Sprungvorhersagetechniken aus Unterabschnitt 4.2.5 immer fest und kann sich im Verlauf
einer Programmausführung nie ändern. Auch wenn ein Sprung ständig falsch
vorhergesagt wird, kann die Hardware darauf nicht reagieren.
Natürlich wird wie bei allen Prädiktoren zusätzlich die Sprungzieladresse
benötigt, was einen Sprungzieladress-Cache notwendig macht, der die Sprungadressen und die Sprungzieladressen speichert. Eine mögliche Implementie-
4.2. Parallelität auf Befehlsebene
171
rungstechnik ist deshalb die Erweiterung des Sprungzieladress-Caches um die
zusätzlichen Bits der Sprungverlaufstabelle (vgl. die Vorhersagebits in Abbildung
4.10).
Im Normalfall bleiben der Sprungzieladress-Cache und die Sprungverlaufstabelle getrennte Hardware-Einrichtungen. Man beachte dabei die unterschiedliche Organisation: der Sprungzieladress-Cache ist ein meist vollassoziativ organisierter Cache-Speicher (siehe Kapitel 3), der die gesamte Sprungbefehlsadresse
zur Identifikation eines Eintrags speichert. Eine Sprungverlaufstabelle wird über
einen Teil der Sprungbefehlsadresse adressiert und enthält dann nur das oder
die Vorhersagebit(s) als Einträge. Üblicherweise werden die niederwertigen Bits
der Befehlsadresse genommen, um einen Eintrag zu adressieren.
Zu einer Fehlspekulation kann es aus drei Gründen kommen:
Gründe für
Fehlspekulationen
• Zunächst erfolgt nach dem Programmstart eine Warmlaufphase, während
derer die ersten Informationen über die Sprungverläufe gesammelt werden.
In dieser Phase wird die Qualität der dynamischen Sprungvorhersage
zunehmend genauer. Zu Beginn ist eine statische Vorhersage oft besser.
Im Allgemeinen sind jedoch nach der Warmlaufphase die dynamischen
Techniken den statischen überlegen, wobei dies mit erhöhter Hardwarekomplexität erkauft wird.
• Die Spekulation für den Sprung wird falsch vorhergesagt, da der Sprung
eine unvorhergesehene Richtung nimmt. Beispielsweise führt der Austritt
aus einer Schleife nach vielen durchlaufenen Iterationen immer zu einer
falschen Vorhersage.
• Der Index zur Adressierung eines Tabelleneintrags der Sprungverlaufstabelle besteht aus einem Teil der Sprungbefehlsadresse (in der Regel
sind dies die niederwertigen Adressbits). Wenn zwei Sprungbefehle in
dem betreffenden Adressteil dasselbe Bitmuster aufweisen, werden sie bei
den einfachen Verfahren auf den gleichen Eintrag abgebildet. Durch die
Indizierung der im Speicherplatz beschränkten Sprungverlaufstabelle wird
also die Verlaufsgeschichte eines anderen Sprungbefehls mit erfasst. Eine
solche Wechselwirkung oder Interferenz (Branch Interference, Aliasing) zwischen zwei Sprungbefehlen ist unerwünscht. Sie führt besonders
bei kleinen Sprungverlaufstabellen häufig zu einer falschen Vorhersage.
Die Anzahl der Interferenzen lässt sich verringern und damit die Spekulationsgenauigkeit signifikant verbessern, indem die Sprungverlaufstabelle
vergrößert wird. Die Interferenzen würden sogar ganz vermieden, wenn
jeder Sprungbefehl einen eigenen Eintrag in der Sprungverlaufstabelle
erhält, dies ist für beliebig große Programme wegen des beschränkten
Platzes auf dem Prozessor-Chip nicht möglich.
Als dynamische Sprungvorhersagetechniken kommen neben den einfachen
Ein- und Zwei-Bit-Prädiktoren auch Korrelations-Prädiktoren zum Einsatz.
Diese drei Techniken werden in den nachfolgenden Unterabschnitten beschrieben.
Wichtig:
Wechselwirkung oder
Interferenz zwischen
zwei Sprungbefehlen
172
Kapitel 4. Weiterführende Prozessortechniken
Ein- und Zwei-Bit-Prädiktoren
Ein-Bit-Prädiktor
Die einfachste dynamische Sprungvorhersagetechnik ist der Ein-Bit-Prädiktor, der für jeden Sprungbefehl die zwei Zustände „genommen“ oder „nicht
genommen“ in einem Bit speichert. Diese Zustände beziehen sich dabei immer
auf die zeitlich letzte Ausführung des Sprungbefehls. Die Zustände eines Ein-BitPrädiktors sind in Abbildung 4.11 dargestellt. Dabei steht T für „genommen“
(taken) und NT für „nicht genommen“ (not taken).
Abbildung 4.11: Die Zustände des Ein-Bit-Prädiktors.
Zwei-Bit-Prädiktor
Der Ein-Bit-Prädiktor wird in der Befehlsholestufe implementiert und benötigt eine Sprungverlaufstabelle (Branch History Table, BHT, auch Branch
Prediction Buffer genannt), die mit einem Teil der Sprungbefehlsadresse adressiert wird und pro Eintrag ein Bit enthält, das die Sprungvorhersage bestimmt.
Ist dieses Bit gesetzt, wird der Sprung als „genommen“ vorhergesagt, wenn es
gelöscht ist, als „nicht genommen“. Im Falle einer Fehlspekulation wird das Bit
invertiert und damit die Richtung der Vorhersage umgekehrt.
Eine Implementierungsvariante des Ein-Bit-Prädiktors besteht darin, nur
die als „genommen“ vorhergesagten Sprünge in den Sprungzieladress-Cache
einzutragen und auf die Vorhersagebits zu verzichten.
Der Ein-Bit-Prädiktor sagt jeden Sprung am Ende einer Schleifeniteration
richtig voraus, solange die Schleife iteriert wird. Der Prädiktor des Sprungbefehls steht auf „genommen“ (T). Wird die Schleife verlassen, so ergibt sich
eine falsche Vorhersage und damit eine Invertierung des Vorhersagebits des
Sprungbefehls, auf „nicht genommen“ (NT). Damit kommt es in geschachtelten
Schleifen jedoch in der inneren Schleife zu einer weiteren falschen Vorhersage.
Beim Wiedereintritt in die innere Schleife steht am Ende der ersten Iteration der
inneren Schleife die Vorhersage noch auf NT. Die zweite Iteration wird damit
falsch vorhergesagt, denn erst ab dieser zweiten Iteration steht der Prädiktor
des Sprungbefehls wieder auf „genommen“ (T). Mit einem Zwei-Bit-Prädiktor
wird bei geschachtelten Schleifen eine dieser zwei Fehlvorhersagen vermieden.
Beim Zwei-Bit-Prädiktor werden für die Zustandskodierungen der bedingten Sprungbefehle zwei Bits pro Eintrag in der Sprungverlaufstabelle verwendet.
Damit ergeben sich die vier Zustände „sicher genommen“ (strongly taken), „vielleicht genommen“ (weakly taken), „vielleicht nicht genommen“ (weakly not taken)
und „sicher nicht genommen“ (strongly not taken). Befindet sich ein Sprungbefehl in einem „sicheren“ Vorhersagezustand, so sind zwei aufeinander folgende
4.2. Parallelität auf Befehlsebene
Fehlspekulationen nötig, um die Vorhersagerichtung umzudrehen. Damit kommt
es bei inneren Schleifen einer Schleifenschachtelung nur beim Austritt aus der
Schleife zu einer falschen Vorhersage.
Es gibt zwei Ausprägungen des Zwei-Bit-Prädiktors
indexPrädiktor!Zwei-Bit-∼, die sich in der Definition der Zustandsübergänge
unterscheiden. Das Schema mit einem Sättigungszähler (Saturation Up-down
Counter ) ist in Abbildung 4.12 dargestellt. Die zweite, Hysteresezähler genannte Variante verdeutlicht Abbildung 4.13.
Abbildung 4.12: Die Zustände des Zwei-Bit-Prädiktors mit Sättigungszähler
Abbildung 4.13: Die Zustände des Zwei-Bit-Prädiktors mit Hysteresezähler
Der Zwei-Bit-Prädiktor mit Sättigungszähler erhöht jedes Mal, wenn der
Sprung genommen wurde, den Zähler und erniedrigt ihn, falls er nicht genommenen wurde. Durch die Sättigungsarithmetik fällt der Zähler nie unter null
(00) oder wird nie größer als drei (11). Das höchstwertige Bit gibt die Richtung
der Vorhersage an.
Die zweite Variante, die Hysteresemethode, unterscheidet sich von der des
Sättigungszählers dadurch, dass direkt von einem „unsicheren“ (weakly) Zustand
in den „sicheren“ (strongly) Zustand der entgegengesetzten Richtung gewechselt
wird. Damit kommt man von einem sicheren Zustand in den anderen sicheren Zustand durch zwei Fehlspekulationen. Der Zustand von noch nicht aufgetretenen
Sprüngen wird dabei auf „vielleicht nicht genommen“ initialisiert.
Die Technik der Zwei-Bit-Prädiktoren lässt sich leicht auf n Bits erweitern.
Es zeigte sich jedoch, dass dabei so gut wie keine Verbesserungen mehr erzielbar
sind.
173
174
Kapitel 4. Weiterführende Prozessortechniken
Ein Zwei-Bit-Prädiktor kann ebenfalls in einem Sprungzieladress-Cache
implementiert werden, wobei jeder Eintrag um zwei Vorhersagebits erweitert
wird. Eine weitere Möglichkeit besteht darin, den Sprungzieladress-Cache für
die Zieladressen und eine separate Sprungverlaufstabelle für die Vorhersage zu
nutzen.
Im Allgemeinen arbeitet ein Zwei-Bit-Prädiktor sehr gut bei numerischen
Programmen, die Schleifen mit vielen Iterationen enthalten. Die Anzahl der
Fehlspekulationen steigt jedoch bei allgemeinen, ganzzahlintensiven Programmen stark an, wenn die Schleifen häufig nur wenige Iterationen aufweisen, dafür
aber viele if-then- und if-then-else-Konstrukte vorkommen.
Zwei-Bit-Prädiktoren ziehen für eine Vorhersage immer nur den Verlauf des
Sprungs selbst in Betracht. Die Beziehungen zwischen verschiedenen Sprüngen
werden nicht berücksichtigt. Durch Auswertung dieser Beziehungen kann jedoch
selbst mit einfachen Ein-Bit-Prädiktoren eine gute Sprungvorhersage erreicht
werden. Diesen Ansatz werden wir im folgenden Abschnitt genauer betrachten.
Korrelations-Prädikatoren
Aufeinander folgende Sprünge sind oft in der Art des folgenden in C-Code
geschriebenen Programmstücks voneinander abhängig (correlated ):
if (d == 0) // Sprung s1
d=1;
if (d == 1) // Sprung s2
...
Dazu betrachten wir eine mögliche Übersetzung der if-Konstrukte in MIPSMaschinensprache (die Variable d sei im Register $t1 abgelegt):
bne $t1,$zero,L1
addi $t1,$zero,1
L1: addi $t2,$zero,1
bne $t1,$t2,L2
...
L2: ...
;
;
;
;
Sprung s1 zu L1 falls (d != 0)
d = 1
$t2=1 zum Vergleichen nötig
Sprung s2 zu L2 falls (d!=1)
Wenn der erste Sprung genommen wird, ist d!=0 erfüllt. Wird er nicht
genommen, so wird wegen d==1 auch der zweite Sprung nicht genommen. Die
beiden Sprünge sind also miteinander korreliert.
Wir betrachten nun den Fall, dass der Wert für d zwischen 2 und 0 wechselt
und für jeden Sprung je ein Ein-Bit Prädiktor vorhergesehen wird, der jeweils
mit NT initialisiert wird. Die Vorhersagen und das Verhalten der Sprünge s1
und s2 sind in Tabelle 4.1 zusammengefasst. Der Tabelle kann man entnehmen,
dass mit zwei separaten Ein-Bit-Prädiktoren alle Sprünge falsch vorhergesagt
werden.
Daher wollen wir im Folgenden einen Korrelations-Prädiktor betrachten,
der aus jeweils zwei Ein-Bit-Prädiktoren pro Sprung besteht und der stets
4.2. Parallelität auf Befehlsebene
175
Tabelle 4.1: Vorhersage mit zwei separaten Ein-Bit-Prädiktoren. AV steht für
aktuelle Vorhersage, SR für Sprungrichtung und NV für neue Vorhersage.
d=
AV s1
SR s1
NV s1
AV s2
SR s2
NV s2
2
0
2
0
NT
T
NT
T
T
NT
T
NT
T
NT
T
NT
NT
T
NT
T
T
NT
T
NT
T
NT
NT
NT
die letzte genommene Sprungrichtung mit in Betracht zieht. Die Korrelation
kommt dadurch zustande, dass die genommene Sprungrichtung von Sprung
s1 die Auswahl des Prädiktors für Sprung s2 steuert. Ebenso wählt die von
Sprung s2 genommene Sprungrichtung dann auch den Prädiktor für Sprung s1
aus. Abhängig von der Vorgeschichte wird dann (pro Sprung) der zuständige
Prädiktor ausgewählt.
Für die aktuellen Vorhersagen (AV) und die neuen Vorhersagen (NV) müssen
dann jeweils zwei Prädiktionsbits angegeben werden, die durch einen Schrägstrich
getrennt werden. Zuerst wird die Vorhersage für den Fall angegeben, dass
der letzte Sprung nicht genommen wurde. Nach dem Schrägstrich wird die
Vorhersage für den Fall angegeben, dass der Sprung genommen wurde. In der
Tabelle 4.2 sind die aktuellen Vorhersagen fett gedruckt. Die Ein-Bit-Prädiktoren
wurden jeweils mit NT initialisiert.
Tabelle 4.2: Vorhersage der Sprünge s1 und s2 mit einem Korrelations-Prädiktor
auf Basis von Ein-Bit-Prädiktoren.
d=
AV s1
SR s1
NV s1
AV s2
SR s2
NV s2
2
0
2
0
NT/NT
T/NT
T/NT
T/NT
T
NT
T
NT
T/NT
T/NT
T/NT
T/NT
NT/NT
NT/T
NT/T
NT/T
T
NT
T
NT
NT/T
NT/T
NT/T
NT/T
Wir können der Tabelle 4.2 entnehmen, dass aufgrund der Korrelation
nur noch der erste Sprung s1 falsch vorhergesagt wird. Alle nachfolgenden
Vorhersagen sind korrekt. Man beachte, dass die globale Geschichte der Sprünge
genutzt wird. Dies bedeutet, dass die jeweiligen Ein-Bit-Prädiktoren nicht von
einem fest zugeordneten Sprung, sondern nur von der Sprungrichtung des letzten
Sprungs gesteuert werden.
Der in Tabelle 4.2 beschriebene Korrelations-Prädiktor wird als ein (1, 1)Prädiktor bezeichnet. Natürlich kann die Verknüpfung auch auf die Sprungver-
176
Kapitel 4. Weiterführende Prozessortechniken
laufsmuster der letzten m Sprünge erweitert werden. Dann spricht man von
einem (m, 1)-Prädiktor und muss 2m verschiedene Ein-Bit-Prädiktoren für jede
mögliche Kombination bereithalten. Wenn für jedes Verlaufsmuster jeweils ein
n-Bit-Prädiktor vorgesehen wird, bezeichnet man diese Anordnung als (m, n)Prädiktor. Ein Zwei-Bit-Prädiktor wird in dieser Notation als (0, 2)-Prädiktor
bezeichnet.
Selbsttestaufgabe 4.6
Wieviele falsche Vorhersagen erhält man mit dem Korrelations-Prädiktor nach
Tabelle 4.2, wenn die Ein-Bit-Prädiktoren für die Sprünge jeweils mit „genommen“ (T) initialisiert werden?
Die Kernidee von Korrelations-Prädiktoren besteht also darin, dass man
für jeden Sprung mehrere Prädiktoren bereitstellt. Sie können recht einfach
mit einem RAM realisiert werden. In der Abbildung 4.14 ist eine mögliche
Hardware-Implementierung dargestellt. Die Sprungverlaufsgeschichte wird in
einem Schieberegister aufgezeichnet, das hier zwei Bit umfasst und als Branch
History Register (BHR) bezeichnet wird. Nicht genommene Sprünge (NT)
werden durch eine 0 und genommene Sprünge (T) durch eine 1 kodiert. Der
Inhalt des BHR wird nach jedem Sprungbefehl um ein Bit nach rechts geschoben
und die Sprungrichtung des letzten Sprungs wird von links nachgeschoben.
Im RAM ist für alle Sprünge eines Sprungverlaufstabelle (Pattern History
Table, PHT) vorhanden, die für die vier möglichen Sprungverlaufsmuster die
entsprechenden Vorhersagebits bereithält. Üblicherweise werden in der PHT
Zwei-Bit-Prädiktoren eingesetzt.
Die Basisadresse dieser PHT ergibt sich hier aus der Konkatenation der
Sprungbefehlsadresse (Branch Address, BA) und den zwei BHR-Bits. Um die
Speicherkapazität des RAM klein zu halten, werden anstatt der vollen Adresse
nur die niederwertigen zehn Bits (BA[9:0]) verwendet. Obwohl es mehrere
Sprungbefehle geben kann, die in diesen zehn Adressbits übereinstimmen, nimmt
man eine mögliche Wechselwirkung zwischen diesen Sprüngen in Kauf. Die
unter der Adresse BA[9:0]+BHR[1:0] gespeicherten Vorhersagebits liefern die
Sprungvorhersage für den aktuellen Sprungbefehl. Nachdem seine tatsächliche
Sprungrichtung ermittelt wurde, werden sie entsprechend des jeweils verwendeten
Prädiktormodells aktualisiert.
Selbsttestaufgabe 4.7
Kann in Abbildung 4.14 die Sprungrichtung des letzten Sprungs auch von rechts
ins BHR eingetragen werden?
4.2. Parallelität auf Befehlsebene
177
Abbildung 4.14: Implementierung eines (2,2)-Prädiktors
Prädikation
Eine andere Technik, mit Sprüngen umzugehen, ist die so genannte Prädikation
(Predication), die so genannte prädikative (predicated oder conditional ) Befehle
benutzt, um einen bedingten Sprungbefehl durch Ausführung beider Programmpfade nach der Bedingung zu ersetzen und spätestens bei der Rückordnung die
fälschlicherweise ausgeführten Befehle zu verwerfen. Die Prädikationstechnik
ist besonders effektiv, wenn der Sprungausgang völlig irregulär wechselt und
damit nicht vorhersagbar ist. In diesem Fall spart man sich die Kosten für
das Rückrollen falsch vorhergesagter Sprungpfade, die meist höher sind als
die unnötige Ausführung einer der beiden Programmpfade bei Anwendung der
Prädikation.
VLIW-Prozessoren verwenden häufig statt einer Sprungvorhersage mit spe- Prädikation als
kulativer Ausführung die Prädikation, die es ermöglicht, statt einer Sprungspe- Alternative zur
kulation die Befehle beider Sprungrichtungen auszuführen und im Nachhinein Prädiktion
die fälschlicherweise ausgeführten Befehle zu verwerfen.
Um Prädikation anwenden zu können, sind zwei Architekturerweiterungen
notwendig. Es müssen ein oder mehrere 1-Bit breite Prädikatsregister vorhanden
sein und möglichst alle Befehle des Befehlssatzes müssen diese Prädikatsregister im Befehlsformat ansprechen können. Ein Prädikatsregister wird von
einem Vergleichsbefehl gesetzt. Im einfachsten Fall genügt ein einzelnes EinBit-Prädikatsregister und ein zusätzliches Bit im Befehlsformat, das es dem
Compiler erlaubt, die gültige Befehlsausführung von der Übereinstimmung
des Prädikatsbits im Befehl mit dem Bit im Prädikationsregister abhängig zu
machen.
Falls alle Befehle eines Befehlssatzes prädikativ sind, so spricht man von
178
Kapitel 4. Weiterführende Prozessortechniken
einem voll prädikativen Befehlssatz.
Das folgende Programmstück in C wird zur Demonstration einer Implementierung mit Prädikation verwendet:
if (x==0)
a=b+c;
d=e-f;
g=h*i;
{// branch b1
}
// instruction independent of branch b1 ...
Falls das Programmstück in Maschinencode mit einem bedingten Sprungbefehl übersetzt wird, so erfolgt zur Laufzeit eine Sprungspekulation. Im Falle
einer Fehlspekulation werden nicht nur die spekulativ ausgeführten Befehle
a=b+c und d=e-f, sondern alle spekulativen Ausführungsresultate nachfolgender Befehle gelöscht. Das gilt auch für den von dem Sprungbefehl und seinen
beiden Nachfolgebefehlen unabhängigen Befehl g=h*i.
Das lässt sich jedoch vermeiden, wenn der Quellcode in eine Codefolge
mit prädikativen Befehlen übersetzt wird (jede Zeile steht für einen einzelnen
Maschinenbefehl in Pseudo-C-Code):
(Pred =
// Pred
if Pred
if Pred
g=h*i;
(x==0))
// replaces branch b1:
is set to true if x==0
then a=b+c; // The operations are only performed
then d=e-f; // if Pred is set to true
Die Anweisung if Pred then a=b+c steht für einen prädikativen Maschinenbefehl. Im Beispiel werden der zweite und der dritte Befehl nur dann ausgeführt
(bzw. die Resultate gültig gemacht), wenn das Prädikat Pred zu true ausgewertet
wurde. Falls Pred zu false ausgewertet wird, so werden nur die betreffenden beiden Befehlsausführungen gelöscht und alle Ausführungen nachfolgender Befehle
werden weiterverwendet. Damit sind zwei Befehle überflüssigerweise ausgeführt
worden, deren Ausführung im Falle einer Implementierung mit einem bedingten
Sprungbefehl und korrekter Spekulation nicht erfolgt wäre.
Wie man sehen kann, ermöglicht es die Prädikation, bedingte Sprungbefehle
aus dem Maschinencode zu eliminieren. Eine Fehlspekulation ist nicht mehr
möglich, die Kosten für das Rückrollen und Neuaufsetzen der Befehlsausführung
werden eingespart. Diese sind meist höher als die unnötige Ausführung einer
der beiden Sprungpfade. Weiterhin wird die Grundblocklänge (die Anzahl der
Befehle zwischen zwei Sprungbefehlen) vergrößert und damit dem Compiler
mehr Spielraum für eine Optimierung der Befehlsanordnung gegeben.
Prädikation hat folgende Eigenschaften:
• Sie lässt sich besonders gut für if-then-Anweisungen mit kleinem then-Teil
einsetzen, wie es in der obigen Codefolge der Fall ist. Sie ist besonders
effizient, wenn der Sprungausgang irregulär wechselt und damit nicht vorhersagbar ist. Bei sehr gut vorhersagbaren Sprüngen führt die Prädikation
ständig zu unnötigen Befehlsausführungen und belastet die Prozessorressourcen.
4.2. Parallelität auf Befehlsebene
• Sie kann außerdem nicht immer sinnvoll angewandt werden. Falls sehr
lange Befehlsfolgen von einer Verzweigung abhängen, so müssten eventuell
sehr viele Befehle prädikativ ausgeführt werden. Eine Sprungspekulation
ist dann meist effizienter.
• Sie beeinflusst nicht nur die Architektur durch zusätzliche Befehle im
Befehlssatz und zusätzlich nötige Bits im Befehlsformat, sondern führt
auch zu einer komplexeren Mikroarchitektur des Prozessors.
Prädikative Befehle durchlaufen wie die nicht-prädikativen Befehle die Befehlsbereitstellung bzw. -dekodierung und werden wie diese im Befehlsfenster
zwischengespeichert. Nun hängt es vom Prozessor ab, wie weit die prädikativen
Befehle in der Prozessor-Pipeline voranschreiten können, bevor die Prädikation
entschieden ist.
Falls das Prädikationsregister als zusätzliches Operandenregister des prädikativen Befehls betrachtet wird, so verbleibt der Befehl im Befehlsfenster,
bis alle Operanden (also auch der Wert des Prädikatsregisters) vorhanden sind.
In diesem Fall erreicht ein prädikativer Befehl erst dann die Ausführungsstufe,
wenn die Prädikation entschieden ist, also der vorangehende Vergleichsbefehl
das Prädikatsregister gesetzt hat. Anschließend wird der Befehl nur dann weiter
ausgeführt, wenn das Prädikat zu true ausgewertet wurde.
Eine alternative Implementierung führt den prädikativen Befehl spekulativ aus und macht das Resultat nur dann gültig, wenn das Prädikat zu true
ausgewertet wurde (z. B. bei der Intel-IA-64-Architektur).
Bei heutigen superskalaren Prozessoren wird die Prädikation noch selten
eingesetzt, jedoch findet sich die Prädikation bei den Digitalen Signalprozessoren und bei leistungsstarken Mikroprozessoren und Mikrocontrollern, die
im Bereich der eingebetteten Systeme eingesetzt werden. Der Befehlssatz der
ARM-Prozessoren ist ab 32-Bit-Versionen voll prädikativ. Weiterhin sind im
IA-64-Befehlssatz alle Befehle prädikativ, wobei der Itanium-Prozessor Prädikation und Sprungspekulation implementiert. Die MIPS-, PowerPC- und
SPARC-Befehlssätze enthalten nur prädikative Ladebefehle.
Mehrpfadausführung
Bei der Mehrpfadausführung (Multipath) oder Eager Execution werden nach
einem bedingten Sprung beide möglichen Ausführungspfade in die Pipeline
geladen und ausgeführt. Eventuelle Datenabhängigkeiten zwischen den beiden
Ausführungspfaden (insbesondere das Schreiben auf das gleiche Register) können
durch die Technik der Registerumbenennung oder -umordnung gelöst werden.
Sobald die Sprungrichtung entschieden ist, werden alle Befehlsresultate und noch
in der Pipeline befindlichen Befehle des nicht genommenen Ausführungspfads
wieder gelöscht. Die Mehrpfadausführung vermeidet Fehlspekulationen und
erhöht die Ausführungsgeschwindigkeit, da der Fehlspekulationsaufwand entfällt.
Die Mehrpfadausführung ist eine Mikroarchitekturtechnik, die ohne Einfluss
auf die Architektur implementiert werden kann.
179
180
Kapitel 4. Weiterführende Prozessortechniken
Das Problem beim Einsatz einer Mehrpfadausführung ist der dafür nötige
Ressourcenverbrauch. Wenn man eine Mehrpfadausführung mit unbegrenzten
Prozessorressourcen annimmt, so erhält man denselben (theoretischen) Geschwindigkeitsgewinn für die Programmausführung wie die Annahme einer
perfekten Sprungvorhersage, bei der kein einziger Sprung falsch vorhergesagt
würde. Bei den in der Realität begrenzten Ressourcen muss bei der Mehrpfadausführung jedoch sehr sorgfältig ausgewählt werden, wann diese eingesetzt
wird. Es werden deshalb Mechanismen benötigt, um auszuwählen, wann die
Mehrpfadausführung oder eine Sprungspekulation angewandt werden sollte.
Ein Entscheidungsmechanismus kann die Anwendung einer Zuverlässigkeitsschätzung der Sprungvorhersage sein, die immer dann, wenn für die Vorhersage
eines Sprunges eine hohe Zuverlässigkeit ermittelt wird, eine Sprungspekulation durchführt und bei niedriger Zuverlässigkeit eine Mehrpfadausführung
vorschlägt.
Wie schon weiter oben erwähnt wurde, ist die Befehlsebenenparallelität
horizontale und ver- von Anwendungen begrenzt. Dies ist dadurch zu erklären, dass weder bei IOEtikale Verluste
noch bei OOE-Prozessoren aufgrund von Daten- und Strukturkonflikten alle
parallel vorhandenen Ausführungseinheiten voll ausgenutzt werden können.
Außerdem kommen im Verlauf der Befehlsbearbeitung in der Pipeline noch
Verzögerungen durch Cache-Fehlzugriffe und Steuerflusskonflikte hinzu. Die
sowohl räumlich als auch zeitlich verteilten Verluste können in einem Diagramm
mit Befehlsfächern visualisiert werden (Abbildung 4.15). Bzgl. der Zeitachse
spricht man von vertikalem und bei der Raumachse von horizontalem Verlust.
Abbildung 4.15: Verluste durch nicht ausgefüllte Befehlsfächer
4.3
Latenzzeit
Mehrfädige Prozessoren
Eine wichtige Aufgabe beim Prozessorentwurf ist die Reduzierung von Verzögerungszeiten, den sog. Latenzzeiten (Latencies). Diese entstehen insbesondere bei
Speicherzugriffen. Die Speicherlatenz, d. h. die Zeit, die vergeht, bis ein benötigtes Datum von einem Speicher geliefert wird, kann durch die Verwendung von
Cache-Speichern, Speicherpuffern und breiten Verbindungswegen im Mittel stark
4.3. Mehrfädige Prozessoren
verringert werden. Jedoch bleibt eine solche Latenzzeit bei Cache-Fehlzugriffen
bestehen.
Im Fall eines Zugriffs auf einen nicht lokalen Speicher bei einem speichergekoppelten Multiprozessorsystem wird die Speicherlatenz jedoch um die Übertragungszeit durch das Kommunikationsnetz oder über den Multiprozessorbus
erhöht. Diese Übertragungszeit dauert bis zu einigen Zehnerpotenzen länger als
die Zeit für die Ausführung eines Maschinenbefehls. Derart hohe Speicherlatenzen können durch Cache-Techniken nicht mehr überbrückt werden.
Latenzzeiten entstehen weiterhin bei der Synchronisation von parallelen
Kontrollfäden, wenn eine Synchronisationsbedingung nicht erfüllt ist und einer
der Kontrollfäden warten muss.
Die Zeit, die der Prozessor auf einen nicht lokalen Speicherzugriff oder auf
das Eintreten einer Synchronisationsbedingung warten muss, führt entweder
zum Leerlauf (Idle Time) des Prozessors oder der Prozessor muss auf einen
anderen Kontrollfaden umschalten. Durch Leerlauf verliert man bei längeren
Wartezeiten wertvolle Ressourcen. Ein Wechsel des Kontrollfadens (Thread Wechsel) führt durch den hohen Verwaltungsaufwand, den die meisten heute
verfügbaren Prozessoren dafür benötigen, zu einem Effizienzverlust. Eine Lösung
dafür bietet die mehrfädige Prozessor-Technik.
Ein mehrfädiger (multithreaded ) Prozessor speichert die Kontexte mehrerer Kontrollfäden in separaten Registersätzen auf dem Prozessor-Chip und
kann Befehle verschiedener Kontrollfäden gleichzeitig (oder zumindest überlappt
parallel) in der Prozessor-Pipeline ausführen. Die Registersätze können durch
die Adresslogik innerhalb eines Taktes umgeschaltet werden. Daher erfolgt der
Thread-Wechsel extrem schnell. Dem Nutzer stehen wie bei einem Mehrkernprozessor mehrere Prozessoren zur Verfügung, die allerdings nicht physisch
sondern nur logisch vorhanden sind. Latenzzeiten, die durch Cache-Fehlzugriffe,
lang laufende Operationen oder sonstige Daten- oder Steuerflussabhängigkeiten
entstehen, können durch Befehle eines anderen Kontrollfadens überbrückt werden. Die Prozessorauslastung steigt und der Durchsatz einer Last aus mehreren
Kontrollfäden wird erhöht.
Derartige mehrfädige Prozessoren, bei denen ein Kontrollfaden einem Betriebssystem-Thread oder Prozess entspricht, werden im Folgenden als explizit
mehrfädig bezeichnet. Explizit mehrfädige Prozessoren sind für die Ausführung
einer Last paralleler Threads oder Prozesse entworfen.
Als implizit mehrfädig werden Prozessorentwürfe für zukünftige Prozessoren bezeichnet, deren Ziel es ist, spekulativ parallele Kontrollfäden aus einem
einzigen sequenziellen Programm zu erzeugen (Thread Level Speculation, TLS)
und diese simultan zueinander auszuführen. Ähnlich wie bei der Spekulation
von Sprüngen werden die Ergebnisse nur dann im Hauptspeicher gültig gemacht,
nachdem sich die Spekulation als korrekt erwiesen hat.
In den nächsten Unterabschnitten werden die explizit mehrfädigen ProzessorTechniken vorgestellt. Auf die implizit mehrfädigen Architekturansätze aus der
Forschung kann im Rahmen dieses Kurses nicht eingegangen werden.
181
182
Kapitel 4. Weiterführende Prozessortechniken
4.3.1
Grundtechniken der Mehrfädigkeit
Nach der Art, wie Befehle, die aus mehreren Kontrollfäden stammen, auf einem
Prozessor zur Ausführung ausgewählt werden, können bei der mehrfädigen
Prozessor-Technik zwei Grundtechniken unterschieden werden:
• Bei der Cycle-by-Cycle-Interleaving-Technik, die oft auch als FineGrain Multithreading bezeichnet wird, wechselt der Kontext (Context
Switch) mit jedem Prozessortakt. Eine Anzahl von Kontrollfäden ist auf
dem Prozessor geladen, ein Hardware-Scheduler wählt in jedem Takt
einen der „ausführbereiten“ Kontrollfäden aus. Von diesem wird der in
der Programmreihenfolge nächste Befehl in die Pipeline eingefüttert. In
aufeinander folgenden Takten wird jeweils ein Befehl aus einem anderen
Kontrollfaden ausgewählt. Ein Kontrollfaden ist erst dann wieder „ausführbereit“, wenn keiner seiner Befehle sich noch in der Pipeline (bzw. im
RoB) befindet.
• Bei der Block-Interleaving -Technik, die auch als Coarse-Grain Multithreading bezeichnet wird, werden die Befehle eines Kontrollfadens so lange
wie möglich direkt aufeinander folgend ausgeführt, bis ein Ereignis eintritt,
das zum Warten des Prozesses bzw. Kontrollfadens zwingt. Ausgelöst
durch ein solches Kontextwechselereignis (z. B. einen Cache-Fehlzugriff)
wird ein Thread-Wechsel durchgeführt.
Abbildung 4.16 zeigt die grundlegenden Unterschiede beider Techniken bei
der Ausführung von vier Kontrollfäden auf einem vierfädigen skalaren Prozessor.
Die Abbildung zeigt die Befehlsfächer der Zuordnungsstufe einer hypothetischen Pipeline. Die Zahlen in den Klammern bezeichnen Befehle verschiedener
Kontrollfäden. Leere Fächer, d. h. leere Kästchen in der Abbildung 4.16, entsprechen Leertakte in der Pipeline. Wie man sieht, können Leertakte, die bei
einem konventionellen Prozessor auftreten, bei mehrfädigen Prozessoren durch
Ausführung von Befehlen anderer Kontrollfäden genutzt werden. Bei der BlockInterleaving-Technik ergibt sich jedoch ein kleiner Kontextwechselaufwand von
in der Regel ein bis mehreren Takten, der in der Abbildung durch zwei leere
Kästchen verdeutlicht ist.
vertikaler
Verlust
Die beiden Grundtechniken der Mehrfädigkeit sind jedoch nicht nur für skalare Prozessoren, sondern insbesondere auch für VLIW-Prozessoren anwendbar.
Abbildung 4.17 zeigt die Cycle-by-cycle-Interleaving-Technik unter Annahme
eines vierfach superskalaren Prozessors bzw. eines vierfachen VLIW-Prozessors.
Falls in einem Prozessortakt kein Befehl zugeordnet werden kann, so bleiben die
vier horizontal angeordneten Befehlsfächer frei. Dies wird als vertikaler Verlust
bezeichnet. Allerdings kann es auch passieren, dass wegen eines Mangels an
gleichzeitig ausführbaren Befehlen nicht die gesamte Zuordnungsbreite genutzt
werden kann. In diesem Fall bleiben beim Superskalarprozessor die zugehörigen
Befehlsfächer frei oder werden beim VLIW-Prozessor durch Leerbefehle (N
4.3. Mehrfädige Prozessoren
183
Abbildung 4.16: (a) konventioneller, einfädiger und skalarer Prozessor, (b) skalarer Prozessor mit Cycle-by-cycle-Interleaving-Technik, (c) skalarer Prozessor
mit Block-Interleaving-Technik
für Noop-Befehle in der Abbildung) aufgefüllt. Dies wird horizontaler Verlust
genannt . Wie man sieht, kann die Cycle-by-cycle-Interleaving-Technik (wie horizontaler Verlust
auch die Block-Interleaving-Technik) zwar die vertikalen, jedoch nicht die horizontalen Verluste ausgleichen. Zur zusätzlichen Vermeidung der horizontalen
Verluste müsste der Prozessor pro Takt auch Befehle aus mehreren Kontrollfäden zuweisen können. Dies ist bei der im nächsten Unterabschnitt vorgestellten
simultan mehrfädigen Prozessor-Technik der Fall.
Block Interleaving benötigt für einen Thread-Wechsel meist einige Takte
mehr als Cycle-by-cycle-Interleaving. Der Vorteil der ersten Technik gegenüber
der zweiten ist die höhere Leistung bei der Ausführung eines einzelnen Kontrollfadens, da dessen Befehle direkt aufeinander folgend ausgeführt werden, während
beim Cycle-by-cycle-Interleaving bei einer Befehls-Pipeline von n Stufen nur in
jedem n-ten Takt ein Befehl desselben Kontrollfadens zur Ausführung kommt.
Zur Überbrückung von Speicherlatenzen sollten demnach möglichst genauso
viele Kontrollfäden wie die für den Speicherzugriff benötigte Taktzahl auf dem
Prozessor zur Verfügung stehen.
Falls jedoch nicht genügend Kontrollfäden als Last vorhanden sind, bleibt
ein Teil der Befehlsfächer eines Cycle-by-cycle-Interleaving-Prozessors leer. Falls
überhaupt nur ein einziger Kontrollfaden als Last zur Verfügung steht, so entspricht die Ausführungszeit dieses Kontrollfadens derjenigen eines Prozessors
ohne Pipelining. Eine Explicit-Dependence Lookahead genannte Technik erlaubt es jedoch bei bestimmten Cycle-by-cycle-Interleaving-Prozessoren, eine
begrenzte Anzahl von Befehlen desselben Kontrollfadens überlappend auszuführen. Dabei kann der Compiler im Opcode jedes Befehls die Anzahl der von
diesem Befehl unabhängigen Folgebefehle angeben. Diese Information wird
vom Scheduler der Befehlsbereitstellung genutzt, um bei zu geringer Last auch
184
Kapitel 4. Weiterführende Prozessortechniken
Abbildung 4.17: (a) vierfach Superskalarprozessor, (b) vierfach VLIW-Prozessor,
(c) vierfach Superskalarprozessor mit Cycle-by-cycle-Interleaving-Technik, (d)
vierfach VLIW-Prozessor mit Cycle-by-cycle-Interleaving-Technik.
unabhängige Befehle desselben Kontrollfadens in die Pipeline einzufüttern, ohne
die Beendigung der Ausführung eines vorherigen Befehls des Kontrollfadens
abwarten zu müssen.
Ein Vorteil des Cycle-by-Cycle-Interleaving gegenüber dem Block-Interleaving
ist der Thread-Wechsel nach jedem Takt. Ein Leeren der Pipeline entfällt beim
Thread-Wechsel. So kann auch eine Prozessor-Pipeline mit hoher Stufenzahl
effizient genutzt werden. Da ein Nachfolgebefehl eines Kontrollfadens erst nach
Beendigung seines Vorgängers in die Pipeline eingefüttert wird, wirken sich die
Kontroll- und Datenabhängigkeiten einer Befehlsfolge nicht aus. Die komplexe Hardware-Logik zur Überwachung dieser Abhängigkeiten kann eingespart
werden.
Vergleichende Untersuchungen ergaben, dass Block-Interleaving dem Cycleby-cycle-Interleaving meist überlegen ist. Simulationen der Block-InterleavingTechnik zeigten, dass eine erhebliche Steigerung der Prozessorauslastung bei
zwei bis vier Kontrollfäden pro Prozessor erreicht wird. Eine weitere Erhöhung
der Anzahl der Kontrollfäden bringt nur noch wenig Gewinn oder gar eine
Verschlechterung der Prozessorauslastung.
Mehrfädige Prozessoren mit Block-Interleaving-Technik lassen sich gemäß
der Auslösebedingung des Kontextwechsels wie in Abbildung 4.18 dargestellt
klassifizieren. Danach unterscheidet man:
• Statisches Block-Interleaving: Ein Kontextwechsel wird in Abhängigkeit
von bestimmten Befehlen bei jeder Ausführung eines solchen Befehls
ausgelöst. Hier steht der Kontextwechsel bereits beim Übersetzen fest und
ist im Befehl kodiert. Dabei gibt es noch folgende Unterscheidungen:
– Explicit Switch: Es existiert ein expliziter Kontextwechselbefehl, der
den Kontextwechsel auslöst.
4.3. Mehrfädige Prozessoren
Abbildung 4.18: Klassifizierung von Block-Interleaving-Techniken
– Implicit Switch: Die Zugehörigkeit zu einer Befehlsklasse löst den
Kontextwechsel aus. In Abhängigkeit von der Befehlsart kann noch
weiter in Switch-on-Load, Switch-on-Branch etc. unterschieden werden.
• Dynamisches Block Interleaving: Hier hängt das den Kontextwechsel auslösende Ereignis vom dynamischen Programmablauf ab. Man unterscheidet:
– Switch-on-Cache-Miss: Ein Kontextwechsel wird von einem CacheFehlzugriff ausgelöst.
– Switch-on-Signal : Ein Kontextwechsel wird von einem externen oder
internen Signal ausgelöst, d. h. von einem Interrupt oder Trap.
– Switch-on-Use: Der Kontextwechsel wird nicht sofort ausgelöst, sondern erst, wenn der Operand vom Befehl benötigt wird. Dies entspricht der Switch-on-Cache-Miss-Strategie mit verzögerter Ausführung.
– Conditional-Switch: Verknüpfung der Explicit-Switch-Strategie mit
einer Bedingung.
Der Vorteil der statischen Block-Interleaving-Technik ist, dass der Kontextwechsel bereits in der Befehlsbereitstellungsstufe der Pipeline erkannt und
durchgeführt werden kann. Durch eine geschickte Kodierung, die es erlaubt,
kontextwechselauslösenden Befehle bereits in der Befehlsbereitstellungsstufe
als solche zu erkennen, kann der Kontextwechselaufwand verringert werden.
Der Kontextwechselaufwand entspricht somit einem einzigen Takt, falls der
Befehl, der den Kontextwechsel auslöst, selbst nicht weiter ausgeführt wird
(z. B. bei Explicit-Switch-Befehlen), und null Takten, falls der auslösende Befehl
weiterverwendet wird (Implicit-Switch-Befehle). Die Anwendung eines Kontextwechselpuffers, der die Adressen von kontextwechselauslösenden Befehlen
zur Laufzeit speichert, kann den Aufwand auch im ersten Fall auf nahezu null
drücken.
185
186
Kapitel 4. Weiterführende Prozessortechniken
Bei der dynamischen Block-Interleaving-Technik müssen alle bereits in der
Pipeline befindlichen Befehle nach dem kontextwechselauslösenden Befehl gelöscht werden. Im Falle der Switch-on-Cache-Miss-Technik sind dies alle Befehle
zwischen der Befehlsbereitstellungsstufe und der Speicherzugriffsstufe. Daraus
ergibt sich ein Kontextwechselaufwand von mehreren Takten, also ein höherer
Aufwand als bei den statischen Techniken. Allerdings wird bei den dynamischen
Techniken ein Kontextwechsel immer nur dann ausgelöst, wenn dieser notwendig
ist, während bei den statischen Techniken Kontextwechsel prinzipiell ausgelöst
werden.
Die beiden Multithreading-Techniken wurden für skalare Prozessoren entwickelt. Sie sind im Prinzip mit der Superskalar- oder der VLIW-Technik kombinierbar. Beispielsweise könnten durch den Hardware-Scheduler beim Cycleby-Cycle-Interleaving pro Takt auch mehrere Befehle eines Kontrollfadens
oder sogar mehrere Befehle aus mehreren Kontrollfäden ausgewählt und in
die Befehls-Pipeline eingefüttert werden. Auch beim Block Interleaving könnten den Ausführungseinheiten Befehle aus mehreren Kontrollfäden gleichzeitig
zugewiesen werden.
4.3.2
Simultan mehrfädige Prozessor-Technik
Die Kombination der Mehrfädigkeit mit der Superskalartechnik stellt eine
dritte mehrfädige Prozessor-Technik dar, die wohl als die zukunftsträchtigste
betrachtet werden kann. Ein simultan mehrfädiger Prozessor (Simultaneous
Multithreading, SMT) kann Befehle mehrerer Kontrollfäden in einem Taktzyklus
den Ausführungseinheiten zuordnen. Wie bei der mehrfädigen Prozessor-Technik
üblich, ist jedem Befehlsstrom ein eigener Registersatz zugeordnet.
Vergleicht man die simultan mehrfädige Prozessor-Technik mit den Mehrkernprozessoren (siehe Abbildung 4.19) so zeigt sich, dass die simultan mehrfädige
Technik im Idealfall alle Latenzen durch Befehle anderer Kontrollfäden füllen
kann, im Gegensatz zum Mehrkernprozessor, der in jedem Prozessor Latenzzeiten
aufweist. Nicht nur die vertikalen, sondern auch die horizontalen Verluste können
verringert, im Idealfall sogar vollständig durch sinnvolle Befehle ausgeglichen
werden.
Bei der simultan mehrfädigen Prozessor-Technik werden alle Pipeline-Ressourcen – bis auf die Registersätze und die Befehlszähler – von den Kontrollfäden gemeinsam genutzt. Die einzelnen Befehle werden in der Pipeline mit
Tags versehen, um ihre Zugehörigkeit zu den verschiedenen Kontrollfäden zu
bezeichnen. Dadurch wird eine sehr geringe zusätzliche Hardware-Komplexität
gegenüber einem Superskalarprozessor benötigt. Zusätzliche Komplexität ergibt
sich insbesondere in der Befehlsbereitstellungsstufe, die fähig sein muss, Befehle
verschiedener Kontrollfäden bereitzustellen, und in der Rückordnungsstufe, die
eine den Kontrollfäden entsprechende selektive Rückordnung durchführen muss.
Die Befehlsbereitstellungseinheit kann ihre Bandbreite auf zwei oder mehr
Kontrollfäden aufteilen und z. B. zweimal vier Befehle verschiedener Kontrollfäden aus dem Code-Cache holen oder sie kann mit jedem Takt Befehle eines
4.4. Mehrkernprozessoren
187
Abbildung 4.19: Befehlszuordnungen (Issue Slots) eines vierfädigen, achtfach
superskalaren SMT-Prozessors (links) und eines Mehrkernprozessors aus vier
je zweifach superskalaren Prozessoren (rechts); die Nummern stehen für die
Kontrollfäden, leere Kästchen für ungenutzte Befehlsfächer der Zuordnungsbandbreite.
anderen Kontrollfadens in die Pipeline laden. Es wurden mehrere selektive
Befehlsholestrategien entwickelt, wie z. B. diejenigen, die bevorzugt Befehle von
Kontrollfäden laden, die keine Cache-Fehlzugriffe aufweisen oder deren Befehle
nur eine geringe Spekulationstiefe aufweisen.
4.4
Mehrkernprozessoren
Wir haben im letzten Abschnitt gesehen, dass mehrere unabhängige Befehlsfäden den ILP erhöhen. Dadurch wird bei einem Superskalarprozessor ein höherer
IPC erreicht, der bei ausreichender Zahl von Befehlsfäden nah an seine Zuordnungsbreite (Superskalarität) herankommt. So erzeugt die Mehrfädigkeit
logische Prozessoren, die die vorhandenen Ausführungseinheiten fast perfekt
ausnutzen. Eine weitere Steigerung der Leistungsfähigkeit ist dann nur noch
durch parallele Hardware möglich. Würde man zusätzliche Ausführungseinheiten
vorsehen, steigt die Komplexität der Steuerlogik (für den Befehls-Scheduler und
die Reservierungsstationen) stark an. Dies reduziert gleichzeitig die Taktfrequenz
und erhöht den Energiebedarf bzw. die Wärmeentwicklung.
Daher ist es naheliegend, einfach mehrere Prozessoren gleichzeitig zu betreiben und über einen gemeinsamen Speicher zu koppeln. Würde man einzelne
Prozessoren verwenden, so müssten diese aufwendig verdrahtet werden und es
entstünde ein höherer Stromverbrauch wegen getrennter Treiberbausteine und
Busschnittstellen. Außerdem käme es aufgrund höherer Signallaufzeiten wieder
zu Beschränkungen der Taktfrequenz. Daher ist günstiger, die Prozessoren auf
einem Chip zu integrieren. Vorteilhaft ist dabei, dass die einzelnen Prozessoren
Mehrfädigkeit: logische Prozessoren
Kopplung über globalen Speicher
188
Cache-Hierarchie
Kapitel 4. Weiterführende Prozessortechniken
nicht vollständig ausgebaut werden müssen, da sie sich die Schnittstelle zu einem
gemeinsamen Speicher teilen können. Stattdessen wird nur der Kern (core) des
Prozessors mehrfach kopiert und durch eine geeignete Cache-Hierarchie mit
dem globalen Hauptspeicher verbunden. Diese Vorgehensweise erfordert deutlich
weniger Entwicklungsaufwand als zusätzliche Ausführungseinheiten oder eine
Erweiterung des Prozessors auf eine größere Wortbreite.
Pro Prozessorkern wird ein separater Befehls- und Daten-Cache benötigt,
da sonst beim Pipelining ein permanenter Strukturkonflikt besteht. Die sich
anschließenden höheren Cache-Ebenen werden für Befehls und Daten vereinheitlichte Caches realisiert. Hierbei gibt es verschiedene Möglichkeiten. Eine Variante
mit privaten L2-Cache und für alle anderen Kerne gemeinsamen L3-Cache ist
Abbildung 4.20 dargestellt.
Abbildung 4.20: Cache-Hierarchie eines Mehrkernprozessors mit privaten L2Caches
Eine andere Variante ordnet jeweils zwei Kernen einen gemeinsamen L2Cache zu und vereint diese in einem großen gemeinsamen L3-Cache (vgl.
Abbildung 4.21). Dieser L3-Cache stellt auch die Verbindung zum globalen
Hauptspeicher dar. Wie aus Kapitel 3 bekannt steigt die Speicherkapazität mit
zunehmender Entfernung vom Prozessorkern. Da die Prozessorkerne gleichzeitig
arbeiten, muss die Cache-Kohärenz auf allen Ebenen sichergestellt werden.
Die einzelnen Kerne können verschieden ausgestaltet sein. Im einfachsten Fall
haben alle Kerne die gleiche Befehlssatz- und Mikroarchitektur. Man bezeichnet
diese Variante als einen symmetrischen oder homogenen Mehrkernprozessor. In
letzter Zeit tauchen jedoch häufiger asymmetrische (oder heterogene) Mehrsymmetrische und kernprozessoren auf. Diese besitzen zwar die gleiche Befehlssatzarchitektur, verasymmetrische
wenden aber Kerne mit verschiedenen Mikroarchitekturen. Ein Beispiel hierfür
Mehrkernprozesso- sind die big.LITTLE-ARM-Prozessoren, die OOE- und IOE-Kerne mit unterren
schiedlicher Anzahl miteinander mischen. Hiermit kann der Energieverbrauch
bedarfsweise an die gerade benötigte Leistungsfähigkeit angepasst werden. Wenn
eine geringe Leistungsfähigkeit ausreicht, nutzt man IOE-Kerne mit niedrigem
4.4. Mehrkernprozessoren
189
Abbildung 4.21: Cache-Hierarchie eines Mehrkernprozessors mit gemeinsamen
L2-Caches für jeweils zwei Kerne
Energieverbrauch. Dies ist vor allem bei batteriegetriebenen Geräten vorteilhaft.
Umgekehrt kann man bei Netzbetrieb die OOE-Kerne mit deutlich höherer
Leistungsfähigkeit bei ebenfalls höherem Energieverbrauch nutzen. Eine weitere
Variante sind Mehrkernprozessoren mit verschiedenen Befehlssatzarchitekturen,
die für bestimmte Anwendungen (z. B. Signalverarbeitung) optimiert sind.
Software für Mehrkernprozessoren muss auf die Nutzung der Parallelität auf
Prozessebene ausgelegt sein. Gibt es viele unabhängige Prozesse, so können diese Parallelität auf Provom Betriebssystem den verschiedenen Kernen zugeordnet werden. Erhöhter zessebene
Entwicklungsaufwand entsteht jedoch für eine explizite Parallelisierung einzelner
Programme. Am Einfachsten ist hierbei die Datenparallelität nutzbar, da bei
den so genannten Symmetric Multicore Processors (SMP) alle Daten im gemeinsamen Speicher abgelegt sind. Alle Prozesse führen dann das gleiche Programm
auf unterschiedlichen Teilmengen der Daten aus. Im Gegensatz dazu ist die
Funktionsparallelität weitaus schwieriger zu implementieren. Das parallele Programm besteht hier aus unterschiedliche Teil-Programmen, die Teilergebnisse
untereinander austauschen müssen. Zur bestmöglichen Auslastung der Kerne
müssen zusätzlich die zeitlichen Abläufe der einzelnen Prozesse koordiniert werden (Scheduling). Da alle Kerne mehr oder weniger gleichzeitig auf den globalen
Speicher zugreifen, kommt es hier bei vielen parallelen Prozessen schnell zu
Zugriffsengpässen. Abhilfe schafft dann nur ein Mehrcomputersystem (cluster
computer ) bei dem vollständige Computer an der Lösung eines aufwendigen
Problems zusammenarbeiten und ihre Teilergebnisse über ein schnelles Netzwerk
austauschen. Größere Mehrcomputersysteme können mehrere Hunderttausend
einzelner Computer enthalten und werden häufig als Supercomputer bezeichnet.
Wenn Sie mehr über die Programmierung solcher parallel arbeitender Computersysteme erfahren möchten, empfehlen wir Ihnen den Kurs 01727 „Parallele
Programmierung und Grid Computing“, der jeweils im Wintersemester angeboten wird.
190
Kapitel 4. Weiterführende Prozessortechniken
4.5
Entwicklung der Prozessor-Technik
Wie in der Abbildung 4.22 zu sehen ist, hat sich die Prozessor-Technik in den
letzten 46 Jahren rasant entwickelt und es ist bis heute nicht abzusehen, wie
lange sich das Moore’sche Gesetz noch weiter bestätigen wird. Rechnet man
mit den in der Abbildung angegebenen Zahlen, so ergibt sich eine Steigerung
der Transistorzahl von ca. 35 % pro Jahr. Damit ergibt sich eine Steigerung um
einen Faktor von ca. 1,82 in zwei Jahren. Damit wird das Moore’sche Gesetz
recht gut bestätigt.
Während der erste Mikroprozessor-Chip, Intels 4004, gerade mal 2300 Transistoren hatte, werden heute Prozessorchips mit mehreren Milliarden Transistoren
produziert. Obwohl die Leistungsfähigkeit der Prozessoren hauptsächlich von
den Mikroarchitekturmerkmalen, der Speicherorganisation und der Taktfrequenz
abhängen, vermittelt die Darstellung der Transistoranzahl über der Zeit ein
brauchbares Maß für die Entwicklungsgeschwindigkeit.
Im Folgenden werden verschiedene Beispiele für die in diesem Kurs behandelten Mikroarchitekturen bzw. Mikroarchitektur-Konzepte aufgelistet (vgl. auch
Tabelle 4.3):
• Multizyklus (ab 1971, typ. Vertreter z.B. Intel 4004)
• CISC (ab 1981, z.B. Intel 8086, 80486)
• Skalarer RISC (ab 1985, z.B. RISC I, MIPS)
• IOE-RISC (ab 1993, DEC Alpha 21064, IBM POWER)
• OOE-RISC (ab 1993, Intel Pentium, Coffe Lake)
• VLIW (ab 2000, z.B. Transmeta Crusoe, Intel Itanium)
• Multithreading (ab 2005, z.B. Intel Core Familie)
• Multicore (ab 2006, z.B. Core 2 Duo, IBM 714)
• Manycore (ab 2015, z.B. AMD Epyc, Intel Broadwell)
• SoC (ab 2010, z.B. Apple A4, Apple, A11 Bionic)
• GPU (ab 2005, z.B. Nvidia GV Volta)
Während bis Ende der siebziger Jahre verschiedene 8-Bit-Prozessoren mit
Multizyklus-Mikroarchitekturen vorherrschten, waren die achtziger Jahre durch
CISC-Prozessoren geprägt. Auf den 80486 folgten ab den frühen neunzigern die
ersten CISC-Superkalarprozessoren wie Pentium und Pentium Pro. Parallel dazu
entstanden (reine) skalare und superskalare RISC-Prozessoren wie der MIPS
R4000, SPARC und die ARM-Prozessoren. Viele der ersten Superskalarprozessoren waren IOE-Typen. Beispiele sind der ursprüngliche Pentium, SuperSPARC
sowie die Alpha 21064 und 21164. Zu den ersten OOE-Prozessoren zählen der
4.5. Entwicklung der Prozessor-Technik
191
1010
109
Anzahl
108
107
106
105
104
1970
1980
1990
Datum der Einführung
2000
2010
2017
Abbildung 4.22: Entwicklung der Prozessor-Technik von 1971 bis 2017. Quelle:
www.ourworldindata.org
MIPS R1000, ALPHA 21264 sowie IBMs POWER bzw. PowerPC. Auch AMD
brachte etliche zur x86-kompatible superskalare Mikroarchitekturen auf den
Markt. Um die Jahrtausendwende wurden die ersten VLIW-/EPIC-Designs
mit dem Itanium-Prozessor entwickelt und schließlich wurden ab ca. 2005 die
ersten Mehrkernprozessoren eingeführt. Durch den Verzicht auf OOE erreicht
man beim 61-core-Xeon-Phi durch gleichzeitiges vierfaches Multithreading bis
zu 244-fache Thread-Level-Parallelität (TLP). Außerdem kann der Xeon Phi
mit mehr als fünf GHz getaktet werden (speed demon). Dagegen kann der
32-core-OOE-SPARC-M7-Prozessor bei vergleichbarem TLP nur mit ca. 4,1
GHz getaktet werden (brainiac).
Aus der Abbildung kann man entnehmen, dass die Entwicklung bis zur
Jahrtausendwende auf etwa 50 Millionen Transistoren begrenzt war und dass
die Dichte der neuen Prozessoren pro Zeiteinheit ab dem Jahr 2000 deutlich
zugenommen hat. Die Halbleiterhersteller bringen in den letzten Jahren ständig
neue Mikroarchitekturen mit immer skurrileren Codenamen auf den Markt.
Diese Codenamen bezeichnen meist Variationen einer bestimmten Mikroarchitektur, die zur Realisierung von Mehrkernprozessoren mit verschiedener Zahl
von Kernen oder zusätzlichen GPUs (Graphic Processing Units) implementiert
wurden. Insgesamt ist der Trend feststellbar, dass künftig komplette Computersysteme mit teilweise heterogenen Prozessoren auf einem einzigen Chip integriert
werden. Apple verwendet in mobilen Geräten wie Smartphones und Tablets
solche Systems on a Chip (SoC), die beim neusten A11-Prozessor aus vier bis
192
Kapitel 4. Weiterführende Prozessortechniken
sechs ARM-Kernen und drei selbst entwickelten GPU-Kernen bestehen. Beim
A11 wird die ARM-v8-Befehlssatzarchitektur verwendet. Zwei Kerne sind als
leistungsstarke Prozessoren ausgelegt und bei vier Kernen verwendet man energiesparende Mikroarchitekturen. Solche heterogene Anordnungen sind besonders
geeignet, um den Energiebedarf an die momentan benötigte Leistungsfähigkeit
anzupassen und so Computersysteme mit hoher Energie-Effizienz zu realisieren.
Hier werden leistungsstarke aber energiehungrige mit leistungsschwächeren und
damit energiesparenden Kernen auf einem Chip integriert. Je nach momentaner Leistungsanforderung des Benutzers werden die passenden Kerngruppen
aktiviert. Obwohl Apple keine Details zu den jeweiligen Mikroarchitekturen
herausgibt, wird es sich bei den leistungsstarken um OOE- und bei den Energieeffizienten um IOE-Prozessoren handeln. Auch bei Prozessoren für den Desktopund Server-Bereich ist eine stetige Zunahme der Zahl der Kerne festzustellen.
Dort werden mittlerweile bis zu 32 OOE-Kerne integriert (z. B. beim AMD
Epyc). Insbesondere bei GPUs findet man eine extrem hohe Anzahl leichtgewichtiger IOE-Prozessoren, die nach dem SIMD-Schema (Single Instruction
Multiple Data) datenparallele Anwendungen beschleunigen. So verfügt z. B.
die Nvidia Volta-Mikroarchitektur über mehr als 5000 Kerne, die gleichzeitig
Gleitkommaoperationen auf großen Datenmengen ausführen können.
Aufgrund der großen Vielfalt unterschiedlicher Kombinationen fällt die
Bewertung und Auswahl eine Prozessors nicht leicht. Zudem muss man bedenken,
dass auch die Speicherorganisation (vgl. voriges Kapitel) einen erheblichen
Einfluss auf die erzielbare Leistungsfähigkeit eines Computersystems hat.
In der Tabelle 4.3 ist die zeitliche Entwicklung der Strukturgrößen bei
drei ausgewählten klassischen und fünf aktuellen Prozessoren dargestellt. Wir
erkennen, dass die Strukturgrößen stetig gesunken sind und dass die Zahl der
Transistoren seit 1971 etwa um den Faktor 106 gestiegen ist. Dagegen sind die
Taktfrequenzen nach einem starkem Anstieg Ende der 90er-Jahre bis etwa 2005
(aufgrund der Einführung von Pipelining und Superpipelining) in den letzten
Jahren nur noch leicht gestiegen (Abbildung 4.23).
4.5. Entwicklung der Prozessor-Technik
193
Tabelle 4.3: Vergleich klassischer und aktueller Prozessoren
Jahr
Name
Kerne
Transistoren
Strukturgröße
Chipfläche
1971
1978
1993
2006
2016
2017
2017
2017
2017
Intel 4004
Intel 8086
Intel Pentium
Intel Core 2 Duo
Xeon Broadwell
IBM 714
Apple A11 Bionic
AMD Epyc
Nvidia GV Volta
1
1
1
2
22
7-10
6+3
32
5120
2.300
29.000
3,1 Millionen
291 Millionen
7,2 Milliarden
6,1 Milliarden
4,3 Milliarden
19,2 Milliarden
21,1 Milliarden
10.000 nm
3.000 nm
800 nm
65 nm
14 nm
22 nm
10 nm
14 nm
10 nm
12 mm2
33 mm2
294 mm2
143 mm2
456 mm2
678 mm2
89 mm2
4x192 mm2
815 mm2
Abbildung 4.23: Entwicklung von Strukturgrößen und Taktfrequenz
194
Kapitel 4. Weiterführende Prozessortechniken
4.6
Zusammenfassung
In diesem Kapitel wurden weiterführende Prozessor-Techniken vorgestellt, die
man in aktuellen Computersystemen findet. Zunächst wird die Superskalarund VLIW-Technik eingeführt. Dabei erweitert man eine (skalare) Pipeline um
mehrere, parallel arbeitende Ausführungseinheiten, so dass pro Takt mehrere
Befehle ausgeführt werden können. Um Zeitverluste bei Sprüngen zu vermeiden,
werden verschiedene Strategien zur Sprungvorhersage eingesetzt. Da mehrere
Befehle gleichzeitig geholt und verarbeitet werden müssen, sind getrennte Befehlsund Daten-Caches mit hohen Speicherbandbreiten erforderlich.
Cache-Fehlzugriffe führen jedoch zu Verzögerungen (Latenzen), die mit
Hilfe der mehrfädigen Ausführung (Multithreading) behoben werden können.
Hierzu verwendet man Mikroarchitekturen mit zwei oder mehreren Sätzen
von Architekturregistern, zwischen denen bei Cache-Fehlzugriffen umgeschaltet
werden kann. Durch den schnellen Kontextwechsel wird dann die Latenzzeit
zum Nachladen des Caches verdeckt.
Aufgrund der enormen Fortschritte der Halbleitertechnologie bei der Verringerung der Strukturgrößen nahm die Wärmedichte auf den Chips ständig zu.
Die pro Quadratzentimeter abzuführende Wärmeleistung ist daher schon genauso groß wie bei einer Herdplatte und kann in Zukunft nicht weiter gesteigert
werden. Um immer mehr Transistoren auf einem Chip integrieren zu können,
müssen die Taktfrequenzen reduziert werden. Die Rechenleistung kann künftig
nur noch durch Parallelverarbeitung auf Prozessebene weiter gesteigert werden.
Man integriert dazu auf den Chips mehrere Prozessorkerne (Multicores), die
langsamer getaktet werden. Diese führen gleichzeitig Programme aus, die jeweils
Teile der Problemstellung lösen und Zwischenergebnisse über einen gemeinsam
genutzten Speicher austauschen. Um Blockierungen durch Speicherzugriffskonflikte zu vermeiden, verwendet man eine meist mehrstufige Cache-Organisation
mit Mechanismen zur Cache-Kohärenz.
Der Aufwand zur Programmierung paralleler Anwendungen ist hoch und
stellt die Programmierer insbesondere dann vor große Problemen, wenn verschiedene Teilprozesse miteinander zusammen arbeiten sollen. Dies ist dadurch
begründet, dass Parallelität auf der Funktionsebene vorliegt und entsprechend
kodiert werden muss. Einfacher wird es, wenn mehrere gleiche Teilprozesse
parallel ablaufen können. Dies ist immer dann der Fall, wenn Parallelität auf
der Datenebene vorliegt.
Am Ende des Kapitels wurde ein grober Überblick über die zeitliche Entwicklung der Prozessor-Technik seit der Einführung des ersten Mikroprozessors
gegeben. Wir finden hier Beispiele für das gesamte Spektrum der in diesem
Kurs vorgestellten Mikroarchitekturen.
4.6. Zusammenfassung
195
Lösungen zu den Selbsttestaufgaben
Lösung Selbsttestaufgabe 4.1
Es können folgende sieben Befehlspakete gebildet werden, die entsprechend in
sieben Taktzyklen ausgeführt werden: (1), (2, 3), (4), (5), (6), (7, 8) und (9).
Lösung Selbsttestaufgabe 4.2
Es gibt acht Quellregister und vier Ergebnisregister. Das Ergebnisregister des
ersten Befehls darf kein Quellregister der nachfolgenden drei Befehle sein. Also
müssen sechs Vergleiche für die Prüfung der Befehle zwei bis vier durchgeführt
werden. Entsprechend sind vier Vergleiche für die Prüfung einer möglichen
Abhängigkeit der Befehle drei bis vier von Befehl zwei und zwei Vergleiche für
den Befehl vier in Bezug auf Befehl drei nötig. Insgesamt sind also 12 Vergleiche
durchzuführen.
Lösung Selbsttestaufgabe 4.3
Durch Vertauschen der Befehle 3 mit 2 und 7 mit 8 kann die Zahl der Befehlspakete (bzw. Taktzyklen) von 7 auf 5 reduziert werden.
Lösung Selbsttestaufgabe 4.4
Der Registerblock muss über genügend Schreibkanäle (Ports) verfügen, um alle
neu berechneten Architekturregister in einem Taktzyklus zu schreiben. Ist dies
nicht möglich, so muss das Gültigmachen einiger Register verzögert werden.
Lösung Selbsttestaufgabe 4.5
IPCIOE =2,5 und IPCOOE =3.
Lösung Selbsttestaufgabe 4.6
Wir passen die Tabelle entsprechend an, indem T/T als Startzustand beider
Ein-Bit-Prädiktoren angenommen wird:
d=
AV s1
SR s1
NV s1
AV s2
SR s2
NV s2
2
0
2
0
T/T
T/T
T/NT
T/NT
T
NT
T
NT
T/T
T/NT
T/NT
T/NT
T/T
T/T
NT/T
NT/T
T
NT
T
NT
T/T
NT/T
NT/T
NT/T
Es ergeben sich mit dieser Initialisierung zwei falsche Vorhersagen (in der zweiten
Zeile). Danach ist der Korrelations-Prädiktor eingeschwungen und verhält sich
wie in Tabelle 4.2.
Lösung Selbsttestaufgabe 4.7
Ja. Dies führt nur zu einer Vertauschung der Einträge für BHR=01 und 10.
196
Kapitel 4. Weiterführende Prozessortechniken
Literatur
[1] Uwe Brinkschulte und Theo Ungerer. Mikrocontroller und Mikroprozessoren.
3. Aufl. Springer-Verlag, 2010. isbn: 978-3-642-05397-9.
[2] David Harris und Sarah Harris. Digital Design and Computer Architecture.
2. Aufl. Morgan Kaufmann Ltd., 2012. isbn: 978-0-123-94424-5.
[3] John L. Hennessy und David A. Patterson. Computer Architecture - A
Quantitative Approach. 5. Aufl. Morgan Kaufmann Ltd., 2011. isbn: 978-0123-83872-8.
[4] John L. Hennessy und David A. Patterson. Computer Organization and
Design. 5. Aufl. Morgan Kaufmann Ltd., 2013. isbn: 978-0-080-88613-8.
[5] Wolfram Schiffmann. Technische Informatik 1 - Grundlagen der Digitalen
Elektronik. Springer-Verlag, 2004. isbn: 978-3-540-40418-7.
[6] Wolfram Schiffmann. Technische Informatik 2 - Grundlagen der Computertechnik. Springer-Verlag, 2005. isbn: 978-3-540-22271-2.
[7] Dominic Sweetman. See MIPS Run - Linux. Morgan Kaufmann Ltd., 2006.
isbn: 978-0-120-88421-6.
197
Index
Adressierung
absolute, 22
Befehls-∼, 24
befehlszählerindirekte, 25
befehlszählerrelative, 24
Daten-∼, 21
direkte, 22
indizierte, 24
Register-∼, 22
registerindirekte, 23
unmittelbare, 22
Akkumulator-Architektur, 18
∼-Speicher, 50
∼-Zähler, 49
∼-Zyklen, 60, 63
∼ Dekodierer, 56
Befehlsebenen-Parallelität, 149
Befehlsumordnung
dynamische, 80
statische, 77, 83
BHT, 170
Big-Endian-Format, 10
Bit Banding, 138
Brainiac, 162
Befehl
arithmetisch-logischer, 15
Gleitkomma-∼, 17
Multimedia-∼, 16
prädikativer, 176
Programmsteuer-∼, 17
Schiebe-Rotations-∼, 15
Synchronisations-∼, 17
Systemsteuer-∼, 17
Befehls
∼-Ausführung
Out-of-Order, 87, 156, 158
∼-Fächer, 180
∼-Fenster, 162
∼-Format, 17
Dreiadressformat, 17
Einadressformat, 18
Nulladressformat, 18
Zweiadressformat, 18
∼-Holestrategien, 185
∼-Länge, 19
∼-Satz, 15
∼-Architektur, 2, 7
voll prädikativer, 176
∼-Scheduler, 155
Cache, 104
∼-Block, 107
∼-Fehlzugriff, 110
∼-Flattern, 109, 116
∼-Treffer, 110
∼-Zeile, 108
örtliche Lokalität, 107
Assoziativität, 105
Bus-Schnüffeln, 123
Byte-Auswahl, 111, 114
Code-∼, 67
Daten-∼, 68
direkt abgebildeter, 114
Durchschreibeverfahren, 121
Einlagerung, 108
Ersetzungsstrategie, 119
Fehlzugriffsrate, 110
FIFO, 120, 156
Index, 114
Inhaltsadressierung, 107
Kohährenzprotokolle, 122
Kohärenz, 122
Komplexität, 116
Konflikt, 111
Konsistenz, 122
198
Index
Level-n-∼, 97
LFU, 120
LRU, 120
MESI-Protokoll, 122
mit Bereitstellung, 121
n-Wege-satzassoziativer, 116
ohne Bereitstellung, 121
Organisation, 105
Ortsadressierung, 107
physikalischer, 118
Rückschreibeverfahren, 121
RND, 120
Satz, 108
schreibender Zugriff, 120
Tag, 107, 111, 114
Tag-RAM, 112
Trace-∼, 154
Trefferrate, 107, 110
Umschreibeverfahren, 121
unified, 97
Verdrängung, 108
Verdränungsstrategie, 119
virtueller, 118
vollassoziativer, 111
Write-invalidate-Protokoll, 122
Write-update-Protokoll, 122
zeitliche Lokalität, 107
Chipsatz, 135
CISC-Prinzip, 5, 27
Commitment, 157
Completion, 157
CPI-Wert, 148
Datenabhängigkeiten, 74
Ausgabeabhängigkeit, 74
echte, 74
Gegenabhängigkeit, 74
scheinbare, falsche, 75
Datenformate, 11
BCD-∼, 12
Ganzzahl-∼, 11
Gleitkomma-∼, 12
Multimedia-∼, 14
Datenlängen, 9
Byte, 9
199
Doppelwort, 9
Halbwort, 9
Quadwort, 9
Wort, 9
Datenpfad, 50
DIMM, 131
DRAM, 102
∼-Feld, 132
∼-Zeile, 131
asynchroner, 133
Auffrischung, 98, 103
CAS, 132
Defektelektronen, 102
Double Data Rate, 134
Kondensator, 98
Latenz, 134
Lokalität, 134
RAS, 132
SDRAM, 131
Single Data Rate, 134
Speicherbank, 133
Speicherzelle, 131
Strobe-Signal, 132
synchroner, 133
dynamischer Speicher, siehe DRAM
effektive Adresse, 20
Feldeffekttransistor, siehe MOSFET
Flash-Speicher, 103
Floating Gate, 104
NAND, 103
negative Vorspannung, 104
NOR, 103
Harvard-Architektur, 95
Hysteresezähler, 171
I/O Controller Hub, 135
IEEE-754, 13
Interleaving
Block-∼, 180
dynamisches, 183
statisches, 182
Cycle-by-Cycle-∼, 180
IPC-Wert, 148
200
Index
Kondensator, 100
Kontextwechsel
conditional, 183
explizit, 182
implizit, 183
on-Cache-Miss, 183
on-Signal, 183
on-Use, 183
Lade-Speicher-Architektur, 18
Little-Endian-Format, 10
Mehrpfadausführung, 177
Memory Controller Hub, 135
Memory Mapped I/O, 135
Memory Wall, 149
Mikroarchitektur, 3, 7
Einzyklus-∼, 49
Mehrzyklen-∼, 60
Pipeline-∼, 64
Mikroprogrammierung, 27
MIPS
∼-Architektur, 29
∼-Befehle, 32
∼-Befehlsformate, 31
∼-Befehlssatz, 30
∼-Codeanalyse, 39
∼-Pipeline, 68
∼-Register, 30
∼-Simulator (MARS), 36
Mooresches Gesetz, 3
MOSFET, 98
CMOS, 100
Floating Gate, 98
n-Kanal-∼, 100
p-Kanal-∼, 100
Schwellenspannung, 100
NOP-Befehl, 77
Op-Code, 17
Pipelining, 64
∼-Register, 65
∼-Stufen, 65, 67
Arbitrierung, 85
Beschleunigung, 66
Datenkonflikte, 73, 157
Durchsatz, 66
EX-Phase, 69
Forwarding, 78
ID-Phase, 69
IF-Phase, 69
Interlocking, 78, 85
Konflikte, 72
Latenz, 66
Leerlauf, 84
MEM-Phase, 69
MIPS, 68
Ressourcenreplizierung, 86
Speedup, 66
Steuerflusskonflikte, 81
Strukturkonflikte, 85
WB-Phase, 69
Port Mapped I/O, 137
Prädikation, 175
Prädikationsregister, 177
Prädiktor
Ein-Bit-∼, 170
Korrelations-∼, 174
Zwei-Bit-∼, 170
Programmiermodell, 8
Prozessor
∼-Architektur, 7
∼-Familie, 8
mehrfädiger, 179, 184
explizit, 179
implizit, 179
Mehrkern-∼, 185
n-Bit-∼, 9
simultan mehrfädiger, 184
skalarer, 29
superskalarer, 29, 147
Rückordnungspuffer, 156
Register, 8
∼-Satz, 50
∼-Umbenennung, 159
allgemeine, 8
Buffering, 159
Gleitkomma-∼, 8
Multimedia-∼, 8
Index
Renaming, 159
Spezial-∼, 8
Universal-∼, 8
Register-Register-Architektur, 18
Register-Speicher-Architektur, 18
Retirement, 157
RISC-Prinzip, 5, 27
201
Deskriptortabelle, 128
externe Fragmentierung, 127
Hintergrundspeicher, 126
interne Fragmentierung, 130
lineare Adresse, 129
Offset, 128
Paging, 129
physikalische Adresse, 126, 129
Segmentdeskriptor, 129
Segmentierung, 127
Segmentregister, 129
Seite, 130
Seitentabellenverzeichnis, 129
Seitenverzeichnis, 130
Seitenwechselverfahren, 129
Selektor, 128
Translation Lookaside Buffer, 131
virtuelle Adresse, 126, 128
VLIW-Technik, 151, 180
Von-Neumann-Architektur, 95
Von-Neumann-Flaschenhals, 95
Sättigungszähler, 171
Schaltung
integrierte, 3
synchron-sequentielle, 50
Speed Demon, 162
Speicher
∼-Anbindung, 131
∼-Controller, 135
∼-Hierarchie, 96
∼-Latenz, 178
byteadressierbarer, 10
flüchtiger, 98
nicht flüchtiger, 103
wortadressierbarer, 10
Zustandselemente, 49
spekulative Ausführung, 163
Sprung
∼-Interferenz, 169
∼-Fehlspekulation, 169
∼-Technik, verzögerte, 83, 166
∼-Verlaufstabelle, 168–170
∼-Vorhersage, 163, 167
dynamische, 85, 165, 168
perfekte, 178
statische, 83, 165, 168
∼-Ziel-Puffer, 164
∼-Zieladress-Cache, 164, 165, 167,
168
SRAM, 101
bistabiles Kippglied, 101
Flip-Flop, 98, 101
Six-Device-Zelle, 101
Stack-Architektur, 19
statischer Speicher, siehe SRAM
Steuereinheit, 56
Superpipelining, 149
Virtuelle Speicherverwaltung, 20, 126
Adressraum, 9, 126
202
Index
Download