An trägen Winterabenden, als die Sonne träge durch den Schleier der Tage ging, fand ich die Kraft, die Verwirklichung eines lang gehegten Traums in Angriff zu nehmen: herauszufinden, wie die Prozessoren angeordnet sind. Dieser Traum veranlasste mich, eine formale RISC-V-Prozessorspezifikation zu schreiben. Github-Projekt

Wie war es
Ich hatte vor langer Zeit einen solchen Wunsch, als ich vor 20 Jahren anfing, mich mit meinen ersten Projekten zu beschäftigen. Zum größten Teil handelte es sich dabei um wissenschaftliche Forschung, mathematische Modellierung im Rahmen von Hausarbeiten und wissenschaftlichen Artikeln. Dies waren die Tage von Pascal und Delphi. Doch schon damals haben Haskell und die funktionale Programmierung mein Interesse geweckt. Mit der Zeit änderten sich die Sprachen der Projekte und Technologien, an denen ich beteiligt war. Aber seitdem ist das Interesse an funktionalen Programmiersprachen ein roter Faden und sie sind geworden: Haskell, Idris, Agda. Vor kurzem waren meine Projekte jedoch in Rust. Ein tieferes Eintauchen in Rust führte mich dazu, eingebettete Geräte zu studieren.
Von Rost zu eingebettet
Die Fähigkeiten von Rust sind so breit und die Community ist so aktiv, dass die Embedded-Entwicklung begonnen hat, eine breite Palette von Geräten zu unterstützen. Und dies war mein erster Schritt in ein untergeordnetes Verständnis von Prozessoren.
Mein erstes Board war: STM32F407VET6 . Es war ein Eintauchen in die Welt der Mikrocontroller, von der ich damals sehr weit entfernt war und ungefähr genug verstand, wie die Arbeit auf niedrigem Niveau erledigt wurde.
Allmählich wurden hier esp32 , ATmega328- Boards (vertreten durch das Ukraino UNO- Board) hinzugefügt. Das Eintauchen in stm32 erwies sich als ziemlich schmerzhaft - die Informationen sind reichlich und oft nicht die, die ich brauche. Und es stellte sich heraus, dass das Entwickeln von Assembler zum Beispiel eine eher routinemäßige und undankbare Aufgabe ist, mit einer Teilmenge von mehr als 1000 Anweisungen. Rust kam jedoch fröhlich damit klar, obwohl es manchmal Schwierigkeiten bei der Integration für bestimmte chinesische Gremien gab.
Die AVR-Architektur erwies sich als deutlich einfacher und transparenter. Die reichlich vorhandenen Handbücher gaben mir ein ausreichendes Verständnis dafür, wie man mit solch einem ziemlich begrenzten Satz von Anweisungen arbeitet und trotzdem in der Lage ist, sehr interessante Lösungen zu erstellen. Trotzdem gefiel mir der Arduino-Pfad überhaupt nicht, aber das Schreiben in Asm / C / Rust erwies sich als viel interessanter.
Wo ist das RISC-V?
Und in diesem Moment stellt sich eine logische Frage: Wo ist die RISC-V-CPU ?
Es ist der minimalistische Charakter von AVR und seine ausreichende Dokumentation, die mich zu meinem vorherigen Traum zurückgebracht hat, um herauszufinden, wie der Prozessor funktioniert. Zu diesem Zeitpunkt hatte ich eine FPGA-Karte und die ersten Implementierungen dafür in Form von Interaktion mit VGA-Geräten, Grafikausgabe, Interaktion mit Peripheriegeräten.
Bücher waren meine Leitfäden zur Prozessorarchitektur:
- John L. Hennessy und David A. Patterson - Computerarchitektur: Ein quantitativer Ansatz (Die Morgan Kaufmann-Reihe in Computerarchitektur und -design)
- John L. Hennessy und David A. Patterson - Computerorganisation und -design. Die Hardware- / Software-Schnittstelle: RISC-V Edition
- David M. Harris und Sarah L. Harris - Digitale Schaltkreise und Computerarchitektur
- Das RISC-V Bedienungsanleitung
Warum ist es notwendig?
Es scheint - alles wurde schon lange geschrieben und implementiert.
verschiedene Implementierungen in HDL und Programmiersprachen. Übrigens eine ziemlich interessante Implementierung von RISC-V auf Rust .
Was könnte jedoch interessanter sein, als es selbst herauszufinden und selbst zu kreieren. Dein Fahrrad ? Oder zum Fahrradbau beitragen? Neben dem persönlichen tiefen Interesse hatte ich eine Idee - wie man versucht, zu popularisieren, zu interessieren. Und finden Sie Ihre Form, Ihren Ansatz. Und das bedeutet, die ziemlich langweilige RISC-V ISA- Dokumentation in Form einer offiziellen Spezifikation in einer anderen Form zu präsentieren. Und es scheint mir, dass der Weg der Formalisierung in diesem Sinne ziemlich interessant ist.
Was meine ich mit Formalisierung? Ein ziemlich breites Konzept. Darstellung eines bestimmten Datensatzes in formaler Form. In diesem Fall durch eine Beschreibung der Strukturen und eine Funktionsbeschreibung. Und in diesem Sinne haben funktionale Programmiersprachen ihren eigenen Charme. Die Aufgabe besteht auch darin, dass eine Person, die nicht sehr in die Programmierung vertieft ist, den Code als Spezifikation lesen kann, wenn möglich, die Besonderheiten der Sprache, in der er beschrieben wird, nur minimal versteht.
Sozusagen ein deklarativer Ansatz. Es gibt eine Aussage, aber wie genau es funktioniert, ist nicht mehr wesentlich. Die Hauptsache ist Lesbarkeit, Sichtbarkeit und natürlich Korrektheit. Entsprechung formaler Aussagen zu der darin eingebetteten Bedeutung.

Total - Ich bin wirklich neugierig, mein Interesse an andere weiterzugeben. Es gibt eine gewisse Illusion, dass das Interesse die treibende Kraft für Aktionen ist. Durch die Individualität wird und sich manifestiert. Und dies ist Teil der Selbstverwirklichung, der Verkörperung von Kreativität.
Ehrgeizig und ein bisschen Text. Was kommt als nächstes?
Bestehende Implementierungen
Sie existieren und werden derzeit vom Projekt aggregiert: RISC-V Formal Verification .
Liste der formalen Spezifikationen (einschließlich meiner Arbeit): https://github.com/SymbioticEDA/riscv-formal/blob/master/docs/references.md
Wie Sie sehen können, handelt es sich größtenteils um Formalisierungen in der Sprache Haskell. Dies war der Ausgangspunkt für die Auswahl einer anderen funktionalen Sprache. Und meine Wahl fiel auf F # .
Warum F#
So kam es, dass ich schon lange über F # Bescheid wusste, aber irgendwie hatte ich in der Hektik des Alltags nicht die Gelegenheit, mich besser kennenzulernen. Ein weiterer Faktor war die .NET- Plattform. Angesichts der Tatsache, dass ich unter Linux bin, war ich lange Zeit nicht zufrieden mit der IDE und mono
sah roh genug aus. Die Rückkehr zu Windows nur für MS Visual Studio ist eine eher zweifelhafte Idee.
Die Zeit steht jedoch nicht still und die Sterne am Himmel haben es nicht eilig, sich zu ändern. Zu diesem Zeitpunkt hatte sich Jetbrains Rider jedoch zu einem vollständigen und praktischen Tool entwickelt, und .NET Core
für Linux bereitet keine Probleme .
Die Frage war - welche funktionale Sprache zu wählen. Die Tatsache, dass es nur eine funktionale Sprache sein sollte - in einer etwas erbärmlichen Form, habe ich oben argumentiert.
Haskell, Idris, Agda
? F#
- Ich kenne ihn nicht. Eine großartige Gelegenheit, neue Farben der Welt der funktionalen Sprachen zu lernen.
Ja, F#
nicht rein funktional. Aber was hindert daran, an " Reinheit " festzuhalten ? Und dann stellte sich heraus, dass die F # -Dokumentation sehr detailliert und vollständig ist. Lesbar, und ich würde sogar interessant sagen.
Was ist F#
für mich jetzt? Eine ziemlich flexible Sprache mit sehr praktischen IDEs (Rider, Visual Studio). Vollständig entwickelte Typen (obwohl Idris
natürlich sehr weit weg ist). Und insgesamt ziemlich süß in Bezug auf die Lesbarkeit. Wie sich jedoch herausstellte, kann seine funktionale „ Nicht-Reinheit “ den Code in Bezug auf Lesbarkeit und Logik zu einer völlig verrückten Form führen. Die Analyse von Paketen in Nuget zeigt dies.
Ein weiteres interessantes und mysteriöses Merkmal für mich war die Entdeckung, dass niemand daran interessiert war, die RISC-V ISA- Formalisierung zuvor in F # (offiziell oder in durchsuchbarer Form) zu schreiben. Und das bedeutet, dass ich die Möglichkeit habe, einen neuen Stream in diese Community, Sprache und dieses „ Ökosystem “ einzuführen.
Die Fallstricke, denen ich begegnet bin
Der schwierigste Teil war die Implementierung des Ausführungsflusses. Es stellte sich oft heraus, dass nicht ganz klar war, wie die Anweisung funktionieren sollte. Leider konnte ich keinen zuverlässigen Kameraden fragen, der um 3 Uhr morgens mit besorgter, aspirierter Stimme anrufen konnte: "Weißt du, die BLTU
Anweisung unterscheidet sich wahrscheinlich in der Bedeutung ..." In diesem Sinne ist es sehr willkommen, qualifizierte Kameraden zu haben, die mit einem freundlichen Wort und angemessener qualifizierter Beratung helfen.
Was waren die Schwierigkeiten und Fallstricke? Ich werde These versuchen:
- ELF - eine merkwürdige Aufgabe war es, herauszufinden, wie man damit arbeitet, Abschnitte und Anweisungen zu lesen. Höchstwahrscheinlich ist diese Geschichte im Rahmen des aktuellen Projekts noch nicht abgeschlossen.
- Anweisungen ohne Vorzeichen führten regelmäßig zu Fehlern, die ich beim Testen der Einheit entdeckte
- Die Implementierung der Arbeit mit dem Speicher ist erforderlich, um über schöne und lesbare Algorithmen für die Byte-Komposition nachzudenken.
- Es gab kein geeignetes Paket für die Arbeit mit Bits unter
int32, int64
. Es hat einige Zeit gedauert, mein Paket zu schreiben und es zu testen.
Hier möchte ich darauf hinweisen, dass das Arbeiten mit Bits in F # viel bequemer ist als in Haskell mit seinen Data.Bits - richtige Unterstützung für Registerbits mit der Fähigkeit,
x32
und x64
gleichzeitig zu unterstützen. Unaufmerksamkeit führte dazu, dass int64
an einigen Stellen int64
. Unit-Tests haben mir dabei geholfen, dies zu identifizieren. Aber es hat eine Weile gedauert. - Ich habe kein einfaches, präzises und persönlich praktisches CLI F # -Paket für mich persönlich gefunden. Ein Nebeneffekt war das Schreiben einer minimalistischen Version in einem funktionalen Stil.
- Im Moment bleibt es ein Rätsel, wie die Systemanweisungen korrekt implementiert werden: FENCE, ECALL, BREAK
- weit entfernt von der ganzen Reihe von Erweiterungen (ISA-Erweiterungen) aus der Liste:
[A, M, C, F, D]
derzeit offensichtlich. Insbesondere erfolgt die Implementierung von [F,D]
nicht über soft float
. - Im Moment gibt es kein klares Verständnis von Privileged Instructions, User Mod, Arbeit mit Peripheriegeräten - leider. Und das ist der Weg der Forschung, des Versuchs und des Irrtums.
- Es gibt keinen schwarzen Gürtel zum Schreiben von Assembler-Programmen unter RISC-V. Vielleicht ist dies nicht unbedingt erforderlich, da bereits viele Sprachen für das Schreiben unter RISC-V portiert wurden.
- Der Zeitfaktor war ebenfalls signifikant - er ist im Strudel der Grundarbeit, der alltäglichen Bedürfnisse und des Ozeans des Lebens ziemlich klein. Und es gibt viel Arbeit, und das meiste davon ist nicht so sehr das " Codieren " - das Schreiben des Codes selbst, sondern das Lernen, das Beherrschen der Materialien.
Wie es funktioniert und welche Funktionen
Jetzt vielleicht der technischste Teil. Was sind die Funktionen im Moment:
rv32i
Befehlssatzes rv32i
- die Fähigkeit, das Programm als RISC-V-Simulator auszuführen - die Ausführung von ELF-Dateien.
- Befehlszeilenunterstützung (CLI) - Auswahl der ausführbaren Architektur, des Befehlssatzes, der ausführbaren ELF-Dateien, des Protokollierungsmodus und der Befehlszeilenhilfe.
- Die Möglichkeit, das Protokoll der ausführbaren Anweisungen in der Nähe der
objdump
Ansicht beim Zerlegen anzuzeigen. - die Fähigkeit, Tests durchzuführen, die den gesamten implementierten Befehlssatz abdecken.
Das Programm ist in folgende Phasen und Zyklen unterteilt:
- Befehlszeile lesen
- Lesen von Anweisungen aus einer ELF-Datei
- Lesen einer bestimmten Anweisung gemäß dem aktuellen PC-Zähler (Programmzähler)
- Dekodierungsanweisungen
- Befehlsausführung
- In unvorhergesehenen Situationen werden Traps eingerichtet, mit denen Sie den Ausführungsprozess abschließen, ein Problem signalisieren und die erforderlichen Daten bereitstellen können
- Befindet sich das Programm nicht in einer Endlosschleife, zeigen Sie den Status der Register an und beenden Sie das Simulationsprogramm
Was ist in den Plänen enthalten:
- Standard Base 64i (fast vollständig)
- Standarderweiterung M (Ganzzahl multiplizieren / dividieren)
- Standarderweiterung A (Atomic Memory Ops)
- Standarderweiterung C (komprimierte 16-Bit-Anweisungen)
- Standarderweiterung F (Gleitkomma mit einfacher Genauigkeit)
- Standarderweiterung D (Gleitkomma mit doppelter Genauigkeit * Berechtigungsstufe M (Maschine)
- Berechtigungsstufe U (Benutzer)
- Berechtigungsstufe S (Supervisor)
- Virtuelle Speicherschemata SV32, SV39 und SV48
- Host-Programme
- GPIO - Arbeiten mit Peripheriegeräten
Wie man läuft
Um das Programm ausführen zu können, benötigen Sie .NET Core
. Wenn Sie es nicht installiert haben, müssen Sie beispielsweise unter Ubuntu 16.04
die folgenden Befehle ausführen:
$ wget -q https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb $ sudo dpkg -i packages-microsoft-prod.deb $ sudo apt-get update $ sudo apt-get install apt-transport-https $ sudo apt-get update $ sudo apt-get install dotnet-sdk-3.0
Führen Sie Folgendes aus, um zu überprüfen, ob sich etwas im Leben geändert hat:
$ dotnet --version
Und das Leben sollte mit neuen Farben funkeln!
Versuchen Sie nun zu rennen. Besorgen Sie sich dazu Ihren Lieblingstee oder -kaffee, Schokolade mit Brötchen, schalten Sie Ihre Lieblingsmusik ein und befolgen Sie die folgenden Befehle:
$ cd some/my/dev/dir $ git clone https://github.com/mrLSD/riscv-fs $ cd riscv-fs $ dotnet build $ dotnet run -- --help
und Ihre Konsole sollte Ihnen spielerisch mit einer Hilfemeldung zuzwinkern.
Der Start ist:
$ dotnet run
In einem strengen Ton wird er sagen, dass Parameter benötigt werden. Führen Sie daher Folgendes aus:
$ dotnet run -- -A rv32i -v myapp.elf
Dies ist der gleiche unangenehme Moment, in dem sich herausstellt, dass wir noch ein fertiges Ausführungsprogramm für RISC-V benötigen. Und ich kann Ihnen dabei helfen. Sie benötigen jedoch die GNU-Toolchain für RISC-V . Lassen Sie es Hausaufgaben installieren - die Beschreibung des Repositorys beschreibt ausreichend detailliert, wie dies zu tun ist.
Um die begehrte Test-ELF-Datei zu erhalten, führen Sie die folgenden Aktionen aus:
$ cd Tests/asm/ $ make build32
Wenn Sie eine RISC-V-Toolchain haben, sollte alles reibungslos funktionieren. Und die Dateien sollten im Verzeichnis angezeigt werden:
$ ls Tests/asm/build/ add32 alu32 alui32 br32 j32 mem32 sys32 ui32
und jetzt versuchen wir mutig, ohne zurückzublicken, den Befehl:
$ dotnet run -- -A rv32i -v Tests/asm/build/ui32
Es ist wichtig zu beachten, dass Tests/asm
kein Testprogramm ist, aber ihr Hauptzweck sind Testanweisungen und ihre Codes zum Schreiben von Tests. Wenn Sie also etwas Größeres und Heldenhafteres mögen, müssen Sie die Welt in Ihrem Willen rv32i
indem Sie eine unabhängig ausführbare 32-Bit-ELF-Datei finden, die nur rv32i
Anweisungen unterstützt.
Die Anweisungen und Erweiterungen werden jedoch wieder aufgefüllt, gewinnen an Dynamik und nehmen an Gewicht zu.
Zusammenfassung und Links
Es stellte sich heraus, dass es sich um eine kleine erbärmliche Erzählung handelte, die von der persönlichen Geschichte geprägt war. Mal technisch, mal subjektiv. Jedoch enthusiastisch und mit einem Hauch von Begeisterung.
Ich für meinen Teil bin daran interessiert, von Ihnen zu hören: Bewertungen, Eindrücke, gute Abschiedswörter. Und für die Wagemutigsten - Hilfe bei der Unterstützung des Projekts.
Interessieren Sie sich für ein solches Erzählformat und möchten Sie fortfahren?