Zeit für RISC-6 RISC-V für Embedded-Anwendungen? Eher nicht!

Generell gefällt mir die RISC-V-Idee:Ein freier, nicht-patentierter Befehlssatz, mehrere Anbieter und freie Implementierungen sind verfügbar, auch kommerzielle Implementierungen sind von verschiedenen Anbietern erhältlich. Leider ist RISC-V für Embedded-Anwendungen jedoch nur sehr bedingt brauchbar.

Es war an der Zeit für eine solche Idee. Auch wenn ich ganz ehrlich gesagt die Firma Arm mag, ihren Gründer und Ex-CTO, ihren CEO, große Teile ihres Managements und die meisten ihrer Produkte (aber sicher nicht den Cortex-A15, der Energie verbrennt ohne Ende): Die Welt sollte nicht nur aus Arm bestehen. Im RISC-V-Bereich gibt es viele Aktivitäten. Viele Entwicklungssysteme diverser Hersteller sind auf RISC-V portiert worden, ebenso Debugger. Alles gut? Leider nein. Aus der Sicht der Embedded-Systeme könnte RISC-V besser sein. Betrachten wir die guten Seiten und die Dinge, die besser sein könnten.

Vorteile von RISC-V

RISC-V hat viele Vorteile gegenüber konkurrierenden Architekturen, wie zum Beispiel Arm. Sie sind im Grunde alle das Ergebnis der Tatsache, dass die ISA quelloffen und frei ist:
➔ Benutzerdefinierte Anweisungen können hinzugefügt werden
➔ Kostenlose Implementierungen sind verfügbar
➔ Kommerzielle Implementierungen gibt es von mehreren Anbietern und das Design   kann geändert werden

Dies sind bedeutende Vorteile, und sie werden RISC-V wahrscheinlich sehr populär machen. Es bestand Bedarf an einem offenen Standard.

Unzulänglichkeiten von RISC-V

Die Kernfrage ist: Wie gut kann RISC-V in Embedded-Systemen eingesetzt werden? Dazu ein bisschen zur CPU-Geschichte und zum CPU-Design. CPUs haben sich in den letzten Jahrzehnten stark weiterentwickelt. In den 70er- und frühen 80er-Jahren waren die meisten CPUs 8-bit- und CISC-Designs. Sie hatten Instruktionen unterschiedlicher Größe, wobei die einfachen, die häufig verwendet wurden, typischerweise Einzelbyte-Instruktionen waren.

Die Register waren uneinheitlich, und es gab typischerweise einen Akkumulator, die Bezeichnung A. 8051, 8080 und 6502 waren typisch für diesen CPU-Typ. Dann stiegen die Wortgrößen an, auf 16 und 32 bit. Es entwickelte sich das RISC-Konzept, bei dem alle Register gleich waren, typischerweise als R0..R15 bezeichnet (im Falle von 16 Allzweckregistern).

Alle Befehle auf dem ersten Arm-Prozessor waren 32 bit breit. Dadurch blieb die CPU einfach, aber die Programme wurden riesig, da viele Befehle benötigt wurden und jede Anweisung verwendete 4 Bytes. Nehmen wir als Beispiel die folgende Codezeile:

Cnt++;

Ihr einziger Zweck ist es, eine globale Variable zu inkrementieren. Arm-Prozessoren benötigen vier Anweisungen, um diese Aktion auszuführen:

LDR R0, =Cnt // Adresse in R0 laden

Beachten Sie, dass die Adresse von Cnt relativ zum Programmzähler gespeichert wird und zusätzlich 4 Bytes benötigt.

LDR R1, [R0]
ADD R1, R1, #1
STR R1, [R0]

Auf einem Arm-Core im Arm-Modus waren dafür 20 Bytes erforderlich. Ein 8051 konnte dies in 2 Bytes im IRAM und 6 Bytes in XRAM bewerkstelligen. Beachten Sie, dass dies bereits 1 bzw. 2 Bytes für die Adresse beinhaltet.

Arm verstand das Problem und verbesserte die Code-Dichte durch die Einführung des THUMB-Modus, bei dem alle Befehle außer einem BL (Subroutinen-Aufruf) 2-Byte-Anweisungen sind. Nicht auf alle Register konnte leicht zugegriffen werden, und einige Befehle sind im THUMB-Modus einfach nicht verfügbar. In vielen Fällen waren mehr Befehle erforderlich, im Durchschnitt vielleicht 40 % mehr, um die gleiche Funktionalität im THUMB-Modus zu implementieren.

Aber da die Befehle nur halb so groß waren, führte dies dennoch zu einem kleineren Programm: 140 % / 2 = 70 %, also 30 % kleiner ohne Berücksichtigung der Literals. Im Falle der oben genannten Anweisungen sind sie alle im THUMB-Modus verfügbar, sodass die Code-Größe von 4*4 + 4 = 20 Bytes auf 4*2 + 4 = 12 Bytes sinken würde. Arm ergänzte auch einige weitere Befehle wie z.B. PUSHM, der die Fähigkeit besitzt, mehrere Registerinhalte mit einem einzigen Befehl auf den Stack zu schieben. Dasselbe gilt für PopM, das die Register vom Stapel nehmen kann. Die Registerliste ist flexibel und kann den Programmzähler einschließen, sodass die Funktionsrückgabe oft ein einziger PopM-Befehl ist. Ein weiteres Beispiel ist die Einführung eines BLX genannten Calls (Branch Link with Mode Exchange). Die Kehrseite des Thumb-Modus war, dass der Prozessor den Modus wechseln und im Thumb-Modus nur Thumb-Befehle ausführen konnte. Zum Umschalten der Modi war ein spezieller Befehl (BX, Branch mit Modusschalter) erforderlich.

Arm erkannte das Verbesserungspotenzial und führte THUMB-2 ein, eine Mischung aus 2- und 4-Byte-Befehlen. Es waren keine Modi-Schalter mehr erforderlich. Die Code-Dichte von THUMB-2 ist annehmbar, auch wenn es Prozessoren mit noch höherer Code-Dichte gibt, zum Beispiel Renesas RX.

Wie sieht es aber mit der Code-Dichte in RISC-V aus? Ich nehme die Antwort vorweg: Katastrophal. Das RISC-V-Design ist dem MIPS-CPU-Design sehr ähnlich: 32 Register, das erste immer 0. Es gibt keine Flags und alle Befehle sind 32 bit breit. Leider ist RISC-V ähnlich wie ein 20 bis 30 Jahre altes Design. Daher werden in den meisten Fällen mehr Instruktionen benötigt als bei konkurrierenden Architekturen. Dies führt zu ziemlich großem Code, der für kleinere Devices nicht wirklich geeignet ist, also für Mikrocontroller, die in eingebetteten Systemen verwendet werden. Betrachten wir nur den Preis dafür, dass es keine Flags wie Carry gibt.

Keine Flags

RISC-V hat keine Flags: Kein Übertrag, keine Null- und keine Vorzeichen-Flags. Sogar alte 8-bit-Prozessoren hatten sie. Das bedeutet, dass für viele Operationen mehr Anweisungen erforderlich sind. Werfen wir einen Blick auf ein Stück Code, das einfach zwei 64-Bit-Werte hinzufügt.

int unsigned long long sum (unsigned long long x,
unsigned long long y) {
return x + y;
}

Aus einem Arm Cortex-M benötigen wir zwei Anweisungen, die in Summe 6 Bytes im Speicher belegen:

// R1:R0: x
// R3:R2: y
// Rückgabewert: R1:R0
adds r0, r0, r2 // Addiert die unteren 32 Bits und setzt den Übertrag entsprechend
adc.w r1, r3, r1 // Addiere obere 32 Bit, inkl. Übertrag
bx lr // Arm-Version von RET

In RISC-V werden fünf Anweisungen benötigt, die in Summe 12 Bytes belegen:

// A1:A0: x
// A3:A2: y
// Rückgabewert: A1:A0
mv a5, a0 // a5 = a0
add a0, a0, a2 // Addiere untere 32 Bits
sltu a5, a0, a5 // Weniger als vorzeichenlos setzen: a5 = a0 < a5 ? 1 : 0. a5 wirkt als Carry-Flag
add a1, a1, a3 // addiere die oberen 32 Bits
add a1, a1, a5 // Übertrag hinzufügen
ret

Der RISC-V-Code benötigt also fünf Anweisungen und 12 Bytes, während der Thumb-2-Code nur zwei Anweisungen und 6 Bytes benötigt. Was kann getan werden, um die Code-Dichte in RISC-V zu verbessern?

➔ Ein Flag-Register einführen und sicherstellen, dass die Flags von allen relevanten Operationen gesetzt werden
➔ Operationen einführen, die die Flags verwenden, wie zum Beispiel ADC (Add with Carry)
➔ PUSHM / POPM einführen
➔ Laden und Speichern mit Post-Inkrement einführen (wie zum Beispiel LDR Rx, [Ry++])
➔ Einfügen eines Schieberegisterfenster, sodass bei der Funktionsein- und -ausgabe kein „Push and Pop“ erforderlich ist.

RV32E zur Rettung? Leider auch nicht

RV32E ist die Embedded-Version der RISC-V-CPU. Der Hauptunterschied besteht darin, dass die Anzahl der Allzweckregister von 32 auf 16 reduziert wurde. Dadurch wird die Komplexität der CPU etwas reduziert, was zu einer etwas kleineren CPU führt. Dies mag zwar für extreme Fälle gut sein, in denen die CPU nur eine sehr einfache Aufgabe zu erfüllen hat oder für Softcores in einem FPGA, aber das Hauptziel, die Code-Größe zu reduzieren, wird damit nicht erreicht, da die Befehle gleich sind und der ungenutzte Teil des Befehlsraums (weil Bit 4 der Registerauswahl jetzt nicht mehr benötigt wird) für die Implementierung von »kundenspezifischen Befehlen« offen gelassen wird.

Die Spezifikation besagt (4.3 RV32E-Befehlssatz) Folgendes: »RV32E verwendet dieselbe Befehlssatzcodierung wie RV32I, mit der Ausnahme, dass nur die Register x0 – x15 zur Verfügung stehen. Zukünftige Standarderweiterungen werden die durch die reduzierten Registerspezifikationsfelder freigewordenen Befehlsbits nicht nutzen, sodass diese für kundenspezifische Erweiterungen zur Verfügung stehen.«

Ist RV32E also eine Option für eingebettete Systeme? Wahrscheinlich nicht, da weder die Anzahl der Befehle noch die Code-Dichte verbessert wird. Da die Anzahl der Register verringert wird und die Befehlscodierung unverändert bleibt, ist es sogar wahrscheinlich, dass noch mehr Code benötigt wird.

Kompression – die C-Erweiterung

Die Komprimierungserweiterung nutzt einen Teil des ungenutzten Anweisungsraums, um komprimierte Versionen (d.h. 2 Byte statt 4 Byte) häufig verwendeter Anweisungen zu erhalten. Das ist eine ähnliche Idee wie das Hinzufügen von THUMB-Befehlen zum Arm-Befehlssatz. Sie hilft, die Code-Größe zu verringern, da sie jedoch nicht auch die Anzahl der Anweisungen reduziert, ist der resultierende Code immer noch deutlich größer als der THUMB-2-Code.

Schlussfolgerung

RV32 ist ein großartiger Prozessor mit großem Potenzial, vor allem, weil er offen und frei ist. Leider basiert das Design auf einem 30 Jahre alten Schema, das zu viele Anweisungen erfordert.

Dies ist auf jedem System ein Problem, sowohl auf kleinen eingebetteten CPUs als auch auf großen 64-Bit-Systemen. Bei großen Systemen ist die Codegröße nicht so relevant, aber die Anzahl der Instruktionen macht es schwieriger, den Code schnell und effizient bei minimalem Energieverbrauch auszuführen. Die Komprimierungserweiterung ermöglicht in vielen Fällen die Verwendung von 2-Byte-Befehlen und verbessert die Code-Dichte, liegt aber immer noch weit hinter dem THUMB-2 von Arm. Für die Zukunft kann sie auf das gleiche oder zumindest ein ähnliches Niveau wie THUMB-2 kommen, indem (kleine, 2-Byte-) Befehle wie oben gezeigt sowie ein Flagregister u.ä. eingeführt werden.

Um Code zu erzeugen, der kleiner als THUMB-2 ist, wäre somit eine RISC-6-Architektur erforderlich. Solange diese nicht verfügbar ist, bleibt es dabei: Für speichersensitive Embedded-Systeme ist RISC-V nur sehr bedingt brauchbar. Arm wird es freuen.

Anmerkung: Zum Generieren der Codebeispiele wurde vom Autor die Entwicklungsumgebung »Embedded Studio« der Firma Segger verwendet.