Berkeley-Entwicklungsprojekt führt zu neuem Rechnersystem:

RISC-Maschine mit 32000 Registern von Celerity

07.11.1986

Nach den Designgrundsätzen des Berkeley-RISC-Projektes wurde die im nachfolgenden Bericht* beschriebene Maschine entwickelt. Das Berkeley-Konzept benutzt einen stack-artig aufgebauten Registersatz mit überlappenden Fenstern. Das Betriebssystem ist Unix 4.2.

Vor etwa zwei Jahren wurde von der Celerity Computing Inc., San Diego/USA, eine neue Maschine für den Einsatz im technisch-wissenschaftlichen Bereich vorgestellt. Dieser Rechner sollte folgende Anforderungen erfüllen: hohe Verarbeitungsgeschwindigkeit, die Unterstützung sehr großer Programme, ein schnelles I/O sowie eine 64-Bit-Fließkomma-Arithmetik.

Das Grundkonzept der Maschine basiert auf den RISC-Untersuchungen der University of California in Berkeley. Das Berkeley-RISC-Projekt erweitert das herkömmliche Konzept der 16 Universalregister um einen großen Vorrat an Stack-artig aufgebauten Registersätzen mit überlappenden -Fenstern. Nach dem Celerity-Konzept wird auch für den Fließkomma-Prozessor ein großer Stack-Register-Vorrat zur Verfügung gestellt.

Ein herkömmlicher Datencache existiert nicht. An dessen Stelle tritt die Berkeley-Register-Architektur und ein schnelles Load/Store-Memory-Interface. Dieser Bericht beschränkt sich im wesentlichen auf diese Systemkomponenten, da deren Effizienz den Erfolg einer Realisierung des Berkeley-RISC-Konzepts bestimmt.

Über die Register-Architektur

Die Registerarchitektur ist einer der wesentlichen Schlüssel zur Gesamtleistung der Maschine. Die Celerity-Maschine enthält drei Arten von Registern: ein Universalregister, ein Stack-Register-Cache und schließlich ein Floating-Point-Register-Cache. Jedem Integer-Prozessor sind 16 Universalregister zugeordnet. Diese Register können Operanden, Ergebnisse oder Adressen enthalten. Alle Integeroperationen werden in den Universalregistern ausgeführt. Alle Daten, die von oder zu anderen Registertypen oder vom beziehungsweise zum Hauptspeicher bewegt werden, müssen durch die Universalregister geführt werden. Die Funktionalität der Universalregister entspricht im wesentlichen der Funktionalität der Register auf einer herkömmlichen Maschine.

Der Floating-Point-Register-Chache und der Stack-Register-Cache sind in sogenannten Banks und Frames organisiert. Ein Frame ist eine adressierbare Einheit einer Registerbank. Einer Prozedur wird stets ein Frame von Registern zugewiesen. Dieser Frame ist nun für die Prozedur als privater temporärer Speicher nutzbar. Eine Bank ist ein Satz von Frames, der einem bestimmen Prozeß zugeordnet ist. Jedem Prozeß im System wird eine solche Registerbank zugeordnet. Falls ein Prozeß Fließkomma-Operationen durchführt, wird ihm außerdem eine Bank im Floating-Point-Register-Cache zugeordnet.

Push- und Pop-Operationen erlauben dem Prozeß den Zugang zu neuen Frames innerhalb der ihm zugewiesenen Registerbank. Bei einem Unterprogramm-Aufruf erzeugen die Compiler einen Push-Befehl, um einen neuen Frame von Registern für die aufgerufene Prozedur zu erhalten. Bei der Rückkehr aus der aufgerufenen Prozedur wird durch einen Pop-Befehl der neue Frame wieder aufgegeben.

Ein ebenfalls integrierter Floating-Point-Register-Cache umfaßt 1800 Doubleprecision-Register (64 Bit). Dieser Registervorrat ist in acht Banks unterteilt, wobei jede Bank über 15 Frames mit je 15 Registern verfügt. Alle Fließkomma-Operationen werden in diesen Registern ausgeführt; die Register des Cache werden dabei durch die Universalregister mit Daten versorgt. Auch der Transport der Daten aus dem Floating-Point-Register-Cache zum Hauptspeicher erfolgt durch diese Universalregister. Der Floating-Point-Register-Cache ist so aufgebaut, daß ein Datenaustausch mit ihm auch dann stattfinden kann, wenn eine Fließkomma-Operation aktiv ist. Es wird dadurch Zeit gespart, weil mit der Vorbereitung der nächsten Operationen begonnen werden kann, ohne die laufende abwarten zu müssen.

Ein Push-Befehl stellt einen neuen Frame von 15 Registern zur Verfügung. Eine Pop-Operation restauriert den Frame, der vor dem letzten Push-Befehl aktiv war. Diese Stack-artige Bereitstellung neuer Registerframes erlaubt einen extrem schnellen Unterprogrammaufruf.

Der Stack-Register-Chache arbeitet wie ein extrem schneller temporärer Zwischenspeicher. Ein Frame besteht aus 32 Registern mit je 32 Bit. Zusätzlich zu den 32 Registern des aktuellen Frame kann ein Programm die unteren 16 Register des folgenden Frame adressieren. Ein Programm hat damit Zugriff auf den Steck-Register-Cache durch ein Fenster von 48 Registern. Durch den sich überlappenden Bereich zweier benachbarter Frames können extrem schnell Parameter an aufgerufene Unterprogramme übergeben werden. Am Anfang eines Frame stehen damit die Parameter, mit denen die laufende Prozedur von der rufenden versorgt wurde. Die folgenden Registerplätze können von der aktuellen Prozedur als sogenanntes ScratchMemory verwendet werden.

In den oberen Registern eines Frame rettet eine Prozedur den Status der rufenden Prozedur. Dieser gerettete Status umfaßt die Return-Adresse, einen Stackmaker und den Inhalt von bis zu sechs der Universalregister der rufenden Prozedur. Die restlichen Registerplätze werden von den Compilern vergeben: Hier werden bei Bedarf Zwischenergebnisse komplizierter Berechnungen abgelegt. Die Parameter einer (von der aktuellen Prozedur) gerufenen Prozedur werden in den Registern 32 bis 47 abgelegt. Falls mehr als 16 Parameter an die gerufene Prozedur übergeben werden müssen, wird die Versorgung der überschüssigen Parameter über den Unix-Data-Stack durchgeführt.

Die 16384 Register des Stack-Register-Cache verbinden die Zugriffsgeschwindigkeit von Registern mit der Größe eine Datenchaches. Die Größe des Stack-Register-Cache ist für ein Applikationsprogramm völlig transparent, weil Überlaufsituationen durch einen In-memory-Stack aufgefangen werden.

In-Memory-Stack

Das Betriebssystem verwaltet für jeden Prozeß drei Stacks im Hauptspeicher, und zwar einen Memory-Stack für Fließkomma-Register, einen Memory-Stack für Stack-Register-Cache sowie einen normalen Unix-Datencache. Ein eigenes Segment im Adreßraum eines Prozessors nimmt diese Stacks auf. Ein Push- oder Pop-Befehl erzeugt einen sogenannten Trap, falls keine Frames mehr in der zugewiesenen Bank enthalten sind. Es finden dann folgende Datenbewegungen statt:

Falls ein Programm einen Overflow seiner Register-Bank erzeugt, wird durch Softwareeingriff der älteste Satz von Frames von dieser Register-Bank in den Hauptspeicher also auf den In-memory-Stack - ausgelagert. Damit werden diese Register-Frames wieder frei für die Benutzung durch dieses Programm. Ein Underflow löst den umgekehrten Prozeß aus: Die Anzahl der beim Auftreten einer Trap bewegten Frames wurden im Rahmen einer Untersuchung über das tatsächliche Verhalten von Programmen im angestrebten Zielmarkt ermittelt.

Das Ergebnis sind unterschiedliche Algorithmen für die Anzahl zu bewegender Frames beim Auslagern in den Hauptspeicher ober beim Wiedereinlagern aus dem Hauptspeicher. Bei diesen Berechnungen wird außerdem zwischen Anwendungsprozessen und Systemprozessen unterschieden. Falls ein Prozeß zur Zeit intakt ist, werden seine Register-Frames auf den Hauptspeicher-Stack geschoben, um die Registerbänke für aktive Prozesse frei zu machen.

Während bei einem konventionellen Datencache stets volle 32-Bit-Adressen Gebildet und dekodiert werden müssen, um an Daten zu kommen die (vielleicht) im Datencache stehen, erfolgt die Adreßbildung auf den Celerity-Maßchinen kurz und einfach - nämlich typisch vier oder sechs Bit zur Adressierung eines Registers relativ zum aktuellen Frame.

Bei einem normalen Datencache zieht jede Modifikation des Cache-Inhaltes eine Modifikation des Hauptspeicherinhaltes nach sich. Auf diesen Rechnern können die Compiler-Register als Scratchmemory vorgesehen werden und zur Compiler-Zeit entscheiden, welche Daten überhaupt im Hauptspeicher abgelegt werden müssen. Dadurch wird der Datenverkehr mit dem Hauptspeicher entsprechend reduziert.

Neunzig Prozent des Befehlssatzes des Integer-Prozessors (insgesamt 142 Befehle) bestehen aus Halbwort-Befehlen mit 16 Bit, die in einem Zyklus ausgeführt werden. Ausführung der Befehle innerhalb eines Zyklus wird durch ein dreistufiges Pipelining erreicht. In jedem Zyklus wird eine Instruktion geholt, eine wird interpretiert und eine wird ausgeführt. Daher steht in jedem Zyklus eine Instruktion zur Ausführung bereit. Da fast alle Befehle in einem Zyklus ausgeführt werden, können bei einer Zykluszeit von 100 Nanosekunden zehn Millionen Halbwortbefehle pro Sekunde (10 Mips) ausgeführt werden. Zu diesen Single-cycles gehören arithmetische Anweisungen, logische Anweisungen, Vergleichsoperationen und Register-Register-Transfers.

Einige Instruktionen bestehen aus zwei Halbworten, wobei jedes Halbwort getrennt durch die Pipeline wandert. Diese Instruktionen erfordern zwei Zyklen. Instruktionen aus den Gruppen, Jumps und Skips, Literal loads und Memory-Zugriffe erfordern unter Umständen mehr als einen Maschinenzyklus. Im Befehlsvorrat der Celerity-Prozessoren sind sofortige und sogenannte Delayed-Sprünge enthalten. Ein "delayed jump" wird zwei Halbwortbefehle vor dem gewünschten Sprungpunkt in den Befehlsstrom eingebaut. Die beiden Befehle hinter dem "delayed jump" werde noch ausgeführt, bevor das Ziel des Sprungs erreicht wird. Dadurch wird die Zeit zum Laden und Dekodieren des angesprungenen Befehls eingespart.

Der Befehlsvorrat des Floating-Point-Prozessors umfaßt neben den Grundrechenarten auch mathematische Funktionen, wie Quadratwurzel, Potenzierung, Logarithmen, Sinus, Cosinus, Arcus-Tangens. Insgesamt stehen 126 Floating-Point-Befehle zur Verfügung.

Der Floating-Point-Koprozessor untersucht den Instruktionsstrom nach Befehlen. Falls er einen findet, beginnt er mit der entsprechenden Fließkomma-Operation, ohne auf den Integer-Prozessor zu warten. Wenn der Integer-Prozessor den Befehl bestätigt, muß der Floating-Point-Koprozessor nur noch die Ergebnisse in ein Register schreiben beziehungsweise die Operation zuvor vollenden, falls diese noch nicht abgeschlossen war. Der Floating-Point-Koprozdssor arbeitet mit einer Floating-Point-Darstellung nach der IEEE-Norm.

Eine wesentliche Eigenschaft dieser Rechnerarchitektur ist. die Unabhängigkeit zwischen Integer-Prozessor und Floating-Point-Koprozersor. Dadurch, daß beide Prozessoren ihren eigenen Registersatz -haben, ist es möglich, daß der Koprozessor die Fließkomma-Befehle unabhängig vom Integer-Prozessor bearbeitet. Der einzige Kontrollmechanismus auf Hardwareebene besteht darin, daß der Integer-Prozessor den Floating-Point-Koprozessor nicht freigibt, bevor der Integer-Prozessor die erforderlichen Fließkomma-Parameter geladen hat. Außerdem wird der Integer-Prozessor daran gehindert, ein Ergebnis einer Floating-Point-Berechnung anzufordern, bevor diese abgeschlossen wurde. Durch diese Architektur wird eine überlappende Arbeitsweise der beiden Prozessoren möglich, Die Compiler nutzen diese Hardwareeigenschaft durch Umgruppieren von Codesequenzen aus.

Die Celerity-Maschinen weisen außerdem ein virtuelles Memory-Subsystem mit Demand-paging auf. In der Hardware des Address-Translation-Cache ist eine große Anzahl an Page-Einträgen vorgesehen. Durch die große Anzahl der Page-Einträge in der Hardware ist die Häufigkeit der Ausnahmesituationen, die eine Software-Intervention erfordern, gering. In der Regel treten solche Ausnahmesituationen nur dann auf, wenn sowieso ein Plattenzugriff erforderlich ist.

Der Address-Translation-Cache (ATC) ist als vierfacher Assoziativspeicher zur Übersetzung der virtuellen Adressen organisiert. Bei einer Pagegröße von 4096 Byte und einer ATC-Gesamtgröße von 32 KB können die Pageeinträge für bis zu 16 MB Realspeicher gehalten werden. Über die Standardausrüstung mit 32 KB ATC-Größe hinaus können ATCs mit bis zu 128 KB vorgesehen werden, die dann bis zu 64 MB Hauptspeicher ohne Überlauf unterstützen können.

Die Adressierung des Memory-Subsystems erfolgt stets mit 40-Bit-Adressen. Ein 8-Bit-Prozeß-Identifikator wird stets der virtuellen 32-Bit-Adresse vorangestellt. Diese Prozeß-Identifikatoren werden auch im ATC gehalten. Bei einem Prozeßwechsel sind keine Umlade- oder Reorganisationsvorgänge im ATC erforderlich, da sich beim Prozeßwechsel auch der Prozeß-Identifikator ändert und folglich nur dessen ATC-Einträge adressiert werden können. ein Prozeßwechsel erfordert keinen Overhead im Memory-Subsystem.

Einfachworte werden in 400 Nanosekunden geholt, Doppelworte in 525 Nanosekunden und Vierfachworte in 800 Nanosekunden.

Adreß-Übersetzung und Memory-Zugriff-Überlappung

Der Gesamtdurchsatz des Memory-Subsystems wird durch die überlappende Ausführung der Adreß-Übersetzung und des Zugriffs auf den Realspeicher verbessert, Wenn der ATC mit einer 40-Bit-Adresse beaufschlagt wird, nimmt das Memory-Subsystem die Page-offset-Adresse (4 bis 12 Bits) und leitet diese Feinadresse unmittelbar zu den Hauptspeicherkarten; diese beginnen alle bereits mit der Dekodierung der Feinadresse, obwohl nur eine der Karten die verlangten Daten enthält.

Die Pagenummer (13 bis 40 Bits) wird nun einem Hashingalgorithmus im ATC unterworfen, um dort vier Einträge zu finden. Diese Einträge werden mit der verlangten Pageadresse verglichen. Falls es eine Übereinstimmung gibt, wird die zugehörige 16-Bit-Pagenummer des Realspeichers an die Hauptspeicherkarten geschickt. Falls es keine Übereinstimmung gibt, liegt ein Pagefault vor, der eine Intervention durch Software erfordert.

Die höherwertigen Bits der Realadresse selektieren die Hauptspeicherkarte; die niedrigwertigen Bits selektieren ein oder zwei 39-Bit-Worte, die dann der ECC-Logik zugeführt und schließlich als 32-Bit-Worte dem anfordernden Subsystem zur Verfügung gestellt werden.

*GEI-Rechnersysteme, Aachen.