next up previous contents
Next: Simulationsaufbau Up: Evaluierung und Simulation Previous: Evaluierung und Simulation   Inhalt

Übersicht

In einem mehrfädigen RISC-Prozessor mit Cycle-by-Cycle-Interleaving wird in jedem Takt ein Befehl von einem anderen Thread aus dem Speicher geladen. Dieser durchläuft die gesamte Pipeline. Das ist möglich, da jeder RISC-Befehl die gleiche Länge hat. Java-Bytecodes sind im Durchschnitt 1,8 Bytes lang. Es gibt Befehle mit einem Byte, zwei Bytes und drei Bytes Länge, die der Prozessor direkt ausführt[*].

Das bedeutet, daß mit jedem 32bit-Wort mehrere Befehle aus dem Speicher geladen werden. Da man in einem skalaren Prozessor nur einen davon an die nachfolgende Pipeline-Stufe weitergeben kann, werden die restlichen in Befehlsfenstern zwischengespeichert. Dieser Überschuß bewirkt, daß für jeden Thread ein Vorrat an Bytecodes in den Fenstern zur Verfügung steht. Dieser kann genutzt werden, wenn gerade keine Befehle nachgeladen werden können, weil der einzige Speicherbus gerade von der Execute-Stufe zum Laden von Anwendungsdaten belegt ist.

Da die Befehle unterschiedlich lang sind, kann es auch vorkommen, daß Befehle über zwei 32bit-Worte verteilt sind. Nach dem ersten Lesevorgang steht dann ein unvollständiger Bytecode im Fenster. Dieser Befehl kann nicht ausgeführt werden, solange der Rest nicht geladen ist. Die Decode-Stufe muß solche Fälle erkennen und die Pipeline für einen Takt anhalten.

Da der Prioritäten-Manager sowieso eine Entscheidung über den nächsten auszuführenden Thread treffen muß, kann er den Füllstand der Fenster auch schon beim Scheduling berücksichtigen und somit die Decode-Stufe entlasten. Er wird keinen Thread auswählen, der ein leeres Befehlsfenster hat. Da die Länge der Bytecodes und die Anzahl der Operanden erst nach dem Dekodieren bekannt ist, kann der Scheduler nicht feststellen, ob ein Befehl vollständig ist oder nicht. Wenn er sicher sein will, daß der ausgewählte Befehl auch ausgeführt werden kann, darf er daher nur Threads wählen, die mindestens drei Bytes in ihrem Fenster haben. Dann ist auch der längste mögliche Befehl vollständig darin enthalten. Die genauen Auswirkungen dieser Technik werden in Abschnitt 3.3.4 diskutiert.

Eine weitere Folge der Befehlsfenster ist, daß das Nachladen der Befehle aus dem Speicher nicht mehr die Ausführungsreihenfolge bestimmt. Die Fetch-Stufe muß also nur dafür sorgen, daß immer in jedem Fenster möglichst viele Befehle stehen.

Die Fetch-Strategie bestimmt, von welchem Thread als nächstes Daten aus dem Speicher in die Befehlsfenster geladen werden. Für diese Entscheidung sind zunächst die Füllstände der Befehlsfenster wichtig. Da diese möglichst gleichmäßig gefüllt sein sollen, ist es sinnvoll, bevorzugt das Fenster mit dem geringsten Füllstand zu bedienen. Eine andere Strategie wählt zuerst die Threads mit der höchsten Priorität, damit deren Fenster immer möglichst voll sind.

Zusätzlich kann man beim Nachladen von Befehlen die Threads bevorzugen, die gerade einen Sprung ausgeführt haben, da deren Fenster leer ist und sie somit aufgehalten werden. Man kann Threads zurückstellen, die gerade nicht aktiv sind, da diese im Moment keine Befehle benötigen.

Im Komodo-Mikrocontroller gibt die Speicheranbindung die Randbedingungen für die Zusammenarbeit zwischen Fetch-Stufe und Decode-Stufe vor. Die Fetch-Stufe entscheidet, für welchen Thread ein 32bit-Wort nachgeladen werden soll und legt die entsprechende Adresse an den Speicher an. Im folgenden Takt liefert der Speicher die Daten zurück, so daß die Decode-Stufe sie in die Befehlsfenster übertragen kann. Danach wird entschieden, welcher Befehl ausgeführt wird. Dieser wird dekodiert und aus dem Fenster entfernt. Dazu werden alle Daten in dem Fenster um die Länge des Bytecode-Befehls verschoben. Die Decode-Stufe wird dadurch sehr komplex. Wie in Abbildung 3.1 zu sehen ist, besteht sie aus vier Teilen.

Abbildung 3.1: Aufbau der Pipeline des Komodo-Mikrocontrollers.

Durch die Integration des Scheduling in den Pipeline-Takt der Decode-Stufe erreicht man eine Scheduling-Entscheidung in jedem Takt, also keinen Takt Overhead.

Um die Laufzeit der Decode-Stufe zu verkürzen, kann das Scheduling für den nächsten Takt parallel zum Dekodieren des aktuellen Befehls stattfinden. Dann kann es aber passieren, daß der Scheduler denselben Thread nochmals auswählt, der gerade dekodiert wird, obwohl der im folgenden Takt eine Latenz haben wird. Das steht erst nach dem Dekodieren fest. Der Scheduler bestimmt daher zwei Threads und wählt zu Beginn des folgenden Taktes den zweiten, wenn der höchstpriorisierte eine Latenz hat.


next up previous contents
Next: Simulationsaufbau Up: Evaluierung und Simulation Previous: Evaluierung und Simulation   Inhalt
Alexander Schulz
2000-06-18