RISC-V Nachteile

Ursprünglich habe ich dieses Dokument vor einigen Jahren als Ausführungskern-Verifizierungsingenieur in ARM geschrieben. Natürlich wurde meine Meinung durch die eingehende Arbeit mit den Executive Cores verschiedener Prozessoren beeinflusst. Machen Sie es also bitte für einen Rabatt: Vielleicht bin ich zu kategorisch.

Ich glaube jedoch immer noch, dass die Entwickler von RISC-V es viel besser machen könnten. Wenn ich heute einen 32-Bit- oder 64-Bit-Prozessor entworfen hätte, hätte ich wahrscheinlich eine solche Architektur implementiert, um die vorhandenen Tools zu nutzen.

Der Artikel beschrieb ursprünglich den RISC-V 2.0-Befehlssatz. Für Version 2.2 wurden einige Aktualisierungen vorgenommen.

Ursprüngliches Vorwort: Einige persönliche Meinungen


Der RISC-V-Befehlssatz wurde auf ein absolutes Minimum reduziert. Viel Aufmerksamkeit wird der Minimierung der Anzahl von Anweisungen, der Normalisierung der Codierung usw. gewidmet. Dieser Wunsch nach Minimalismus hat zu falscher Orthogonalität (wie der Wiederverwendung derselben Anweisung für Übergänge, Aufrufe und Rückgaben) und obligatorischer Ausführlichkeit geführt, was sowohl Größe als auch Menge erhöht Anweisungen.

Hier ist zum Beispiel der C-Code:

int readidx(int *p, size_t idx) { return p[idx]; } 

Dies ist ein einfacher Fall der Indizierung eines Arrays, eine sehr häufige Operation. Dies ist die Kompilierung für x86_64:

 mov eax, [rdi+rsi*4] ret 

oder ARM:

 ldr r0, [r0, r1, lsl #2] bx lr // return 

Für RISC-V ist jedoch der folgende Code erforderlich:

 slli a1, a1, 2 add a0, a1, a1 lw a0, a0, 0 jalr r0, r1, 0 // return 

Vereinfachung RISC-V vereinfacht den Decoder (d. H. Das CPU-Front-End) durch Ausführen weiterer Anweisungen. Das Skalieren der Breite der Pipeline ist jedoch ein schwieriges Problem, während das Decodieren von leicht (oder stark) unregelmäßigen Befehlen gut implementiert ist (die Hauptschwierigkeit tritt auf, wenn es schwierig ist, die Länge des Befehls zu bestimmen: Dies wird besonders im x86-Befehlssatz mit zahlreichen Präfixen deutlich).

Die Vereinfachung der Anweisungen sollte nicht an ihre Grenzen gebracht werden. Register und Registeraddition mit einer Verschiebung des Registerspeichers ist eine einfache und sehr häufige Anweisung in Programmen, und es ist für den Prozessor sehr einfach, sie effektiv zu implementieren. Wenn der Prozessor den Befehl nicht direkt implementieren kann, kann es relativ einfach sein, ihn in seine Komponenten zu zerlegen. Dies ist ein viel einfacheres Problem als das Zusammenführen von Sequenzen einfacher Operationen.

Wir müssen zwischen „komplexen“ spezifischen Anweisungen von CISC-Prozessoren - komplizierten, selten verwendeten und ineffizienten Anweisungen - und „funktionalen“ Anweisungen unterscheiden, die CISC- und RISC-Prozessoren gemeinsam sind und eine kleine Folge von Operationen kombinieren. Letztere werden häufig und mit hoher Leistung eingesetzt.

Mittelmäßige Umsetzung


  • Fast unbegrenzte Erweiterbarkeit. Obwohl dies das Ziel von RISC-V ist, entsteht ein fragmentiertes, inkompatibles Ökosystem, das mit äußerster Vorsicht verwaltet werden muss.
  • Der gleiche Befehl ( JALR ) wird für Aufrufe und für Rückgaben sowie für registerindirekte Verzweigungen verwendet, bei denen eine zusätzliche Decodierung für die Verzweigungsvorhersage erforderlich ist
    • Rufen Sie an: Rd = R1
    • Rückgabe: Rd = R0 , Rs = R1
    • Indirekter Übergang: Rd = R0 , RsR1
    • (Seltsamer Übergang: RdR0 , RdR1 )
  • Die Codierung mit variabler Länge des Aufzeichnungsfelds synchronisiert sich nicht selbst (dies ist häufig - beispielsweise ein ähnliches Problem mit x86 und Thumb-2 -, verursacht jedoch verschiedene Probleme sowohl bei der Implementierung als auch bei der Sicherheit, z. B. bei der rückwärtsorientierten Programmierung, d. H. ROP-Angriffen )
  • RV64I erfordert eine Zeichenerweiterung für alle 32-Bit-Werte. Dies führt dazu, dass die obere Hälfte von 64-Bit-Registern nicht mehr zum Speichern von Zwischenergebnissen verwendet werden kann, was zu einer unnötigen speziellen Platzierung der oberen Hälfte der Register führt. Es ist optimaler, die Erweiterung mit Nullen zu verwenden (da sie die Anzahl der Schalter verringert und normalerweise optimiert werden kann, indem das "Null" -Bit verfolgt wird, wenn bekannt ist, dass die obere Hälfte Null ist).
  • Die Multiplikation ist optional. Obwohl schnelle Multiplikationsblöcke auf winzigen Kristallen einen beträchtlichen Bereich einnehmen können, können Sie immer etwas langsamere Schaltkreise verwenden, die die vorhandene ALU für mehrere Multiplikationszyklen aktiv nutzen.
  • LR / SC strenge Fortschrittsanforderungen für eine begrenzte Teilmenge von Anwendungen. Obwohl diese Einschränkung recht streng ist, führt sie möglicherweise zu Problemen bei kleinen Implementierungen (insbesondere ohne Cache).
    • Dies scheint ein Ersatz für die CAS-Anweisung zu sein, siehe Kommentar unten
  • Die Speicher-Sticky-Bits FP und der Rundungsmodus befinden sich im selben Register. Dies erfordert eine Serialisierung des FP-Kanals, wenn die RMW-Operation ausgeführt wird, um den Rundungsmodus zu ändern.
  • FP Befehle sind für eine Genauigkeit von 32, 64 und 128 Bit codiert, jedoch nicht für 16 Bit (was in der Hardware weitaus häufiger vorkommt als 128 Bit).
    • Dies kann leicht behoben werden: Code der Dimension 0b10 frei.
    • Update: In Version 2.2 wurde ein Dezimalplatzhalter angezeigt, es gibt jedoch keinen Platzhalter mit halber Genauigkeit. Der Geist ist unverständlich.
  • Die Art und Weise, wie FP-Werte in der FP-Registerdatei dargestellt werden, ist nicht definiert, aber beobachtbar (über Laden / Speichern).
    • Emulatorautoren werden dich hassen
    • Die Migration virtueller Maschinen kann unmöglich werden
    • Update: Version 2.2 erfordert breitere NaN-Boxing-Werte

Schlecht


  • Es gibt keine Bedingungscodes, stattdessen werden die Anweisungen zum Vergleichen und Verzweigen verwendet. Dies ist an sich kein Problem, aber die Folgen sind unangenehm:
    • Reduzierter Codierungsraum in bedingten Verzweigungen aufgrund der Notwendigkeit, einen oder zwei Registerspezifizierer zu codieren
    • Keine bedingte Auswahl (nützlich für sehr unvorhersehbare Übergänge)
    • Keine Übertragung / Subtraktion mit Übertragung oder Ausleihe
    • (Beachten Sie, dass dies immer noch besser ist als Befehlssätze, die Flags in das allgemeine Register schreiben und dann zu den empfangenen Flags wechseln.)
  • Es scheint, dass in einem nicht privilegierten ISA hochpräzise Zähler (Hardware-Zyklen) erforderlich sind . In der Praxis ist die Bereitstellung von Anwendungen ein hervorragender Vektor für Angriffe auf Kanäle von Drittanbietern
  • Multiplikation und Division sind Teil derselben Erweiterung, und es scheint, dass, wenn eine implementiert ist, die andere ebenfalls implementiert werden sollte. Die Multiplikation ist viel einfacher als die Division und bei den meisten Prozessoren üblich, die Division jedoch nicht.
  • Die grundlegende Befehlssatzarchitektur enthält keine atomaren Anweisungen. Multi-Core-Mikrocontroller werden immer häufiger eingesetzt, sodass atomare Anweisungen wie LL / SC kostengünstig sind (für eine minimale Implementierung in einem einzelnen [Multi-Core] -Prozessor wird nur 1 Bit Prozessorstatus benötigt).
  • LR / SC befinden sich in derselben Erweiterung wie komplexere atomare Anweisungen, was die Flexibilität für kleine Implementierungen einschränkt
  • Allgemeine atomare Anweisungen (nicht LR / SC ) enthalten kein CAS Grundelement
    • Der CmpHi:CmpLo darin, die Notwendigkeit eines Befehls zu vermeiden, der fünf Register liest ( Addr , CmpHi:CmpLo , SwapHi:SwapLo ), aber dies wird wahrscheinlich weniger Implementierungsaufwand verursachen als der garantierte Vorwärts- LR / SC , der als bereitgestellt wird Ersatz
  • Es werden atomare Anweisungen angeboten, die mit 32-Bit- und 64-Bit-Werten arbeiten, jedoch nicht mit 8-Bit- oder 16-Bit-Werten
  • Für RV32I gibt es keine Möglichkeit, den DP-FP-Wert zwischen einer Ganzzahl- und einer FP-Registerdatei zu übertragen, außer über den Speicher, dh aus 32-Bit-Ganzzahlregistern ist es unmöglich, eine 64-Bit-Gleitkommazahl mit doppelter Genauigkeit zu erstellen. Sie müssen zuerst den Zwischenwert in den Speicher schreiben und laden ihn von dort in die Registerdatei
  • Beispielsweise haben der 32-Bit- ADD Befehl in RV32I und der 64-Bit- ADD Befehl in RVI64 die gleichen Codierungen, und in RVI64 wird eine weitere ADD.W Codierung ADD.W . Dies ist eine unnötige Komplikation für einen Prozessor, der beide Anweisungen implementiert. Es wäre vorzuziehen, stattdessen eine neue 64-Bit-Codierung hinzuzufügen.
  • Keine MOV Anweisungen. Der Mnemonikcode des MV Befehls wird vom Assembler in den Befehl MV rD, rS -> ADDI rD, rS, 0 . Hochleistungsprozessoren optimieren in der Regel MOV Anweisungen, während sie Anweisungen umfassend neu anordnen. Als kanonische Form des MV Befehls in RISC-V wurde ein Befehl mit einem direkten 12-Bit-Operanden gewählt.
    • In Abwesenheit von MOV der ADD rD, rS, r0 Befehl ADD rD, rS, r0 dem kanonischen MOV tatsächlich vorzuziehen, da es einfacher zu decodieren ist und Operationen mit dem Nullregister (r0) in der CPU normalerweise optimiert werden

Schrecklich


  • JAL 5 Bits für die Codierung des Kommunikationsregisters aus, was immer gleich R1 (oder R0 für Übergänge).
    • Dies bedeutet, dass der RV32I eine 21-Bit-Verzweigungsverschiebung verwendet. Dies reicht für große Anwendungen - beispielsweise Webbrowser - nicht aus, ohne mehrere Befehlssequenzen und / oder „Zweiginseln“ zu verwenden.
    • Dies ist eine Verschlechterung gegenüber Version 1.0 der Befehlsarchitektur!
  • Trotz des großen Aufwands für eine einheitliche Codierung werden Lade- / Speicheranweisungen unterschiedlich codiert (Groß- und Kleinschreibung und unmittelbare Felder ändern sich).
    • Offensichtlich war die Codierungsorthogonalität des Ausgangsregisters der Codierungsorthogonalität von zwei stark verwandten Befehlen vorzuziehen. Diese Wahl erscheint etwas seltsam, da die Adressgenerierung zeitkritischer ist.
  • Es gibt keine Anweisungen zum Laden des Speichers mit Registerversätzen ( Rbase + Roffset ) oder Indizes ( Rbase + Rindex << Scale ).
  • FENCE.I impliziert eine vollständige Synchronisation des Anweisungscaches mit allen vorherigen Repositorys, mit oder ohne eingezäunt. Implementierungen müssen entweder alle I $ auf dem Zaun löschen oder nach D $ und dem Speicherpuffer suchen
  • In RV32I erfordert das Lesen von 64-Bit-Zählern das zweimalige Lesen der oberen Hälfte, das Vergleichen und Verzweigen im Fall einer Übertragung zwischen der unteren und oberen Hälfte während eines Lesevorgangs
    • Typischerweise enthalten 32-Bit-ISAs einen Befehl zum Lesen eines speziellen Paarregisters, um dieses Problem zu vermeiden.
  • Es gibt keinen architektonisch definierten Speicherplatz für die Hinweiscodierung, sodass Anweisungen aus diesem Speicherplatz auf älteren Prozessoren (die als NOP verarbeitet werden) keinen Fehler verursachen, sondern auf den modernsten CPUs
    • Typische Beispiele für reine NOP-Hinweise sind Dinge wie die Spinlock-Ausbeute
    • Neuere Prozessoren haben auch komplexere Hinweise (mit sichtbaren Nebenwirkungen auf den neueren Prozessoren; z. B. werden Anweisungen zur x86-Rahmenprüfung im Hinweisbereich codiert, damit die Binärdateien abwärtskompatibel bleiben).

Source: https://habr.com/ru/post/de461785/


All Articles