RISC-Pipelines im Detail

Strukturelle Hazards

Ein vollkommen anderer Konflikt entsteht, wenn zwei Einheiten in der Pipeline zeitgleich eine dritte nutzen müssen, um ihre Aufgabe zu erfüllen. Ein Beispiel ist die Bus Interface Unit: In der Pipeline existieren zwei Einheiten (Fetch und Execute), die diese gegebenenfalls gleichzeitig nutzen wollen. Diese Form von Störung wird struktureller Hazard genannt.

Der Speicherzugriff ist wirklich konkurrierend: In jedem Takt wird gemäß Pipeline-Ablaufstruktur ein Fetch (Laden einer kodierten Instruktion) durchgeführt. Der Zugriff auf den Datenspeicher ist zwar seltener, führt jedoch garantiert zu einem Konflikt.

Dieses Problem ist sehr ernst zu nehmen und tritt bei Spezialprozessoren wie DSPs (Digitalen Signalprozessoren) noch wesentlich verschärfter auf. Dort sind häufig Algorithmen (zum Beispiel Filterfunktionen, Echounterdrückung et cetera) mit sehr hohem Datenaufkommen implementiert.

Das Einfügung der beiden (bei DSPs drei oder vier) gewünschten Speicherzugriffe in eine zeitliche Sequenz innerhalb eines Takts scheidet aus, weil Speicherzugriffe zu den langsamsten Aktionen im Instruktionsablauf gehören. Wie in weiter führenden Artikeln noch zu lesen sein wird, ist es lediglich über Speicherhierarchien möglich, mit ungebremster Geschwindigkeit im Speicher zu arbeiten. Eine zeitliche Sequenz mehrerer Zugriffe pro Takt würde den Takt selbst stark limitieren.

Um dennoch Auswirkungen dieses strukturellen Hazards zu verhindern oder wenigstens zu mindern, sind RISC-Architekturen mit einer möglichst großen Anzahl von Registern ausgestattet, um externe Speicherzugriffe zu minimieren. Das Prinzip beruht darauf, lokale Variablen möglichst lange im Register zu halten. Alle Rechenoperationen wie Addition oder logische Verknüpfungen sind auf Registern als Operanden definiert, der Speicherzugriff erfolgt ausschließlich via Load- und Store-Befehlen (LD, ST, PUSH, POP).

Dieses Verfahren minimiert zumindest die Anzahl der Zugriffe. Weiter führende Architekturen (SPARC, Intel IA-64) nutzen sehr große Register Files, auch um Übergabewerte (Aufrufparameter) für Unterprogramme abzulegen (globale und lokale Registerfenster). Dies spart das umständliche Kopieren der Registerinhalte auf den Stack.