Leerzeichen entfernt

master
Robert Jacob 2022-08-27 21:12:37 +02:00
parent 5b21cd91c9
commit 1d049b200e
1 changed files with 41 additions and 47 deletions

View File

@ -1,40 +1,38 @@
---
title: HE32, Rust & "the NAND2TETRIS experience"
author: kaqu
status: Aktiv
difficulty: no
time: ~1h
date: 2022-08-26
date: 2022-08-27
image: HE32_Overview.png
keywords: NAND2TETRIS, jack, hack
keywords: nand2tetris, jack, hack
---
{{< image alt="Showtime" src="HE32_Overview.png" size="1920x1080 q90">}}
## Motivation ##
Vor einem Vierteljahr habe ich das Buch "The Elements of Computing Systems" (Nisan/Schocken,
wichtig: 2.Auflage!) geschenkt bekommen und die ca. 300 Seiten gleich in einem Rutsch durchgelesen.
wichtig: 2.Auflage!) geschenkt bekommen und die ca. 300 Seiten gleich in einem Rutsch durchgelesen.
Faszinierend, insbesondere wenn ich mich an mein eher rudimentäres Verständnis meines alten
Dragon-Books erinnere (dort werden im Wesentlichen die Grundlagen für die Nutzung von
lex & yacc gelegt - heute: flex & bison). Ich habe damals nach ca. 3/4 des Buches aufgehört ...
Dragon-Books erinnere (dort werden im Wesentlichen die Grundlagen für die Nutzung von
lex & yacc gelegt - heute: flex & bison). Ich habe damals nach ca. 3/4 des Buches aufgehört ...
<div class="pure-g">
{{<image alt="Cover1" src="BookCover.jpg" size="400x240 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{<image alt="Cover2" src="BookCoverOld.jpg" size="400x240 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Cover1" src="BookCover.jpg" size="400x240 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Cover2" src="BookCoverOld.jpg" size="400x240 q90" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Das neuere Buch ist ursprünglich aus dem - in der angelsächsischen Welt - recht bekannten
"Nand-to-Tetris"-Kurs entstanden.
"Nand-to-Tetris"-Kurs entstanden.
Weitere Infos & Unterlagen können auf der zugehörigen [Website](https://www.nand2tetris.org/) abgerufen werden.
## 1. Der Kurs ##
Inhaltlich geht es in 12 Kapiteln vom NAND-Gatter bis zur einfachen 'Betriebssystem'-Erstellung
(die auf der [Website](https://www.nand2tetris.org/course) unter 'Projects' abgerufen werden).
- Die ersten fünf Kapitel befassen sich mit dem Aufbau einer 'eigenen' CPU, Speicherelementen
etc. aus einfachsten NAND-Gattern und deren Zusammenschaltung zum 'Hack'-Computer!
- Kapitel 6 behandelt die Erstellung eines passenden Assemblers,
@ -48,44 +46,44 @@ es hier eigentlich um eine Kombination von BIOS & Standard-Library geht.
Das Geniale: Der Schwierigkeitsgrad bleibt immer akzeptabel, gerade für das Selbststudium!
Man merkt an vielen Stellen, wie die Rückfragen der Studenten im Laufe der Jahre in den Text
zurückgeflossen sind. Praktisch alle Problemstellen (für das Verständnis) werden sehr
detailliert erläutert.
detailliert erläutert.
Technisch werden im Software-Teil ab Kapitel 6 immer recht enge API-Vorgaben gemacht,
jedoch ist man frei in der Wahl der Programmiersprache zur Realisierung (*ich habe meine Lösung zeitsparend
in Python erstellt - natürlich hier nicht veröffentlicht, schließlich soll sich jeder seine
Lösung selbst erarbeiten!*).
Lösung selbst erarbeiten!*).
Mitgeliefert werden alle notwendigen Emulatoren und Hilfsmittel um die Ergebnisse mit den korrekten
Lösungen automatisiert zu vergleichen (auf der [Website](https://www.nand2tetris.org/software) unter 'Software').
Ich habe zwei Monate für den ganzen Kurs gebraucht. Für die ersten 10 Kapitel einen Monat, für die
restlichen zwei noch einmal einen ganzen Monat (der Schwierigkeitsgrad steigt zwar kaum an, jedoch
baut sich immer mehr Wissen auf, d.h. die Komplexität nimmt zu).
baut sich immer mehr Wissen auf, d.h. die Komplexität nimmt zu).
Meine Empfehlung: Pädagogisch wertvoll!
## 2. Ausführung ##
### 2.1 Hardware & Assembler ##
Zunächst wird im Hardware-Teil eine [ISA](https://de.wikipedia.org/wiki/Befehlssatzarchitektur) definiert,
Zunächst wird im Hardware-Teil eine [ISA](https://de.wikipedia.org/wiki/Befehlssatzarchitektur) definiert,
d.h. man 'baut' analog zu einer echten CPU eine Theoretische, die 'Hack'-CPU mit dem 'Hack'-Befehlssatz
(in einer Kurs-spezifischen [HDL](https://de.wikipedia.org/wiki/Hardwarebeschreibungssprache)). Diese wird
im beigestellten Simulator mittels Testscript verifiziert.
Das sieht dann ungefähr so aus:
<div class="pure-g">
{{<image alt="HDL Code Sample" src="hdl_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{<image alt="HDL Simulator" src="hdl_simulator.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="HDL Code Sample" src="hdl_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="HDL Simulator" src="hdl_simulator.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Das IS sieht wie folgt aus:
{{<image alt="IS Overview" src="hack_is.png" size="1024x640 q90">}}
{{< image alt="IS Overview" src="hack_is.png" size="1024x640 q90">}}
Hierfür ist ein Assembler zu erstellen, der als Output 16-Bit Code (als 'Binär-Text') generiert, z.Bsp. wie folgt:
<div class="pure-g">
{{<image alt="Assembly Sample" src="asm_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{<image alt="Machine Code Sample" src="machine_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Assembly Sample" src="asm_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Machine Code Sample" src="machine_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
</div>
### 2.2 VM / IL ###
@ -94,8 +92,8 @@ Zunächst macht man sich mit einer vordefinierten IL (Intermediate language) fü
vertraut. Hierzu kann der beigestellte VM-Emulator genutzt werden:
<div class="pure-g">
{{<image alt="VM Code Sample" src="vm_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{<image alt="VM Emulator" src="vm_emulator.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="VM Code Sample" src="vm_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="VM Emulator" src="vm_emulator.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Als nächstes wird ein Übersetzer erstellt, von VM-Code zu Assembly Language. Wenn man diesen mit dem unter
@ -109,17 +107,17 @@ Sogar Objekte können bereits angelegt werden!
Hier mal die formale Definition:
{{<image alt="Jack Grammar" src="jack_grammar.png" size="1920x1080 q90">}}
{{< image alt="Jack Grammar" src="jack_grammar.png" size="1920x1080 q90">}}
Praktisch sieht der Code z.Bsp. so aus:
{{<image alt="Jack Code Sample" src="jack_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Jack Code Sample" src="jack_code.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
Auch hierfür ist - unter Anleitung - ein Compiler zu erstellen. Dieser erzeugt aus Jack dann VM-Code:
<div class="pure-g">
{{<image alt="Jack Code" src="jack_code_main.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{<image alt="VM Code" src="vm_code_main.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Jack Code" src="jack_code_main.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="VM Code" src="vm_code_main.png" size="640x480 q90" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Sieht doch gar nicht so kompliziert aus!
@ -137,7 +135,6 @@ Garbage Collection Strategie - wirklich lehrreich!
Bei Screen- und Output-Modul befasst man sich mit Bildschirmspeicher und Character-Generierung, wie zu C64-Zeiten ...
## 3. Ein kleines Defizit und dessen Beseitigung ##
### 3.1 Von 16-Bit zu 32-Bit Worten ###
@ -166,26 +163,26 @@ Mit **Kresse** hab' ich eine Wette laufen: Jedes Jahr mindestens eine neue Sprac
verkündet hat, das auch [Rust](https://en.wikipedia.org/wiki/Rust_(programming_language)) im Linux Kernel genutzt
werden darf, fand ich, das es Zeit ist hier mal nachzurüsten.
**Xperimental**: *Mach lieber [Go](https://en.wikipedia.org/wiki/Go_(programming_language)),
**Xperimental**: *Mach lieber [Go](https://en.wikipedia.org/wiki/Go_(programming_language)),
Rust ist wie C++ (reichlich überladen ...). Hätte ich mal auf ihn gehört!*
Was mir auch nicht gleich klar war: Rust ist eine funktionale Sprache (wie Scheme, Lisp, Haskell u.ä.), es sieht
nur optisch wie eine Prozedurale aus.
Bis Kapitel 17 (inkl., von insgesamt 21) habe ich mich durch das [Rust-Manual](https://doc.rust-lang.org/stable/book/)
Bis Kapitel 17 (inkl., von insgesamt 21) habe ich mich durch das [Rust-Manual](https://doc.rust-lang.org/stable/book/)
vorgearbeitet, dann habe ich die Arbeit am HE32-Projekt in Rust aufgenommen ('Hack'-Emulator 32-Bit) - und bin
gleich am indirekten Vector-Zugriff gescheitert (ein solideres Verständnis von 'RefCell' hätte geholfen -
Danke **Vespasian**)!
Auch hatte ich zunächst für parallele Threads geplant und vorsichtshalber eine Menge mit Arc & Mutex hantiert -
gleich am indirekten Vector-Zugriff gescheitert (ein solideres Verständnis von 'RefCell' hätte geholfen -
Danke **Vespasian**)!
Auch hatte ich zunächst für parallele Threads geplant und vorsichtshalber eine Menge mit Arc & Mutex hantiert -
überflüssigerweise & eine echte Performance-Bremse.
Weil eine neue Sprache lernen ja immer eine Unterforderung ist (💀!), habe ich mich zwecks Komplexitätssteigerung
auch gleich für eine GUI-Lösung entschieden 🏋 (hier sieht man noch, das die GUI-Landschaft für Rust im Fluß
auch gleich für eine GUI-Lösung entschieden 🏋 (hier sieht man noch, das die GUI-Landschaft für Rust im Fluß
ist - nach einiger Web-Recherche habe ich mich für [Qt5 mit ritual](https://rust-qt.github.io/) entschieden).
Eigentlich soll Rust ja 'sicher' im Hinblick auf Speicherzugriffe sein, schade auch, das für Qt5 dann große Teile
wieder auf 'unsafe' gedreht werden (müssen) 🖕 ...
Wenigstens läßt sich der Timer-Event von Qt gut nutzen!
wieder auf 'unsafe' gedreht werden (müssen) 🖕 ...
Wenigstens lässt sich der Timer-Event von Qt gut nutzen!
Nicht so gut funktioniert das Einklinken in die Event-Schleife zwecks Abfangen der Tastatur und Weiterreichung
an die Emulation (das Symbol wird nicht richtig aufgelöst - ist zumindest nicht auffindbar?!).
Daher habe ich das Modul ('crate' in Rust-Terminologie) device_query dafür genutzt.
@ -195,20 +192,19 @@ Nach einigen Performance-Optimierungen läuft der HE32 jetzt ganz passabel. Nur
System-Bibliotheken anzupassen: In Math.multiply musste die Bitzahl erhöht werden und Sys.wait musste an
die neue Abarbeitungsgeschwindigkeit angepasst werden.
{{<image alt="HE32 running Pong" src="he32_run_pong.png" size="1920x1080 q90">}}
{{< image alt="HE32 running Pong" src="he32_run_pong.png" size="1920x1080 q90">}}
Die [Quellen zu HE32](https://git.hacknology.de/projekte/HE32) finden sich wie gehabt auf dem hacKNology git-server.
## 4. Was kann man - ganz praktisch - an Erkenntnissen gewinnen? ##
- Der Kurs empfiehlt sich unbedingt für Ingenieure oder Naturwissenschaftler. Wer im Informatik-Studium nur die
'Fertigprodukte' Flex und Bison kennengelernt hat, jedoch interessiert ist am Eigenbau, ist hier ebenfalls richtig.
Ist schon ein tolles Gefühl, wenn man eine 'eigene' Compiler-Toolchain gebaut hat!
Ist schon ein tolles Gefühl, wenn man eine 'eigene' Compiler-Toolchain gebaut hat!
- Der Compilerbau hat seit den 80ern Fortschritte gemacht: Die Erfindung einer zwischengeschalteten VM/IL erlaubt
eine deutliche Komplexitätsreduzierung, man muß eben nicht mehr direkt aus der Hochsprache Assembly-Code für die
Zielplattform generieren.
Ok, schneller wird's nicht - Performance ist allerdings heutzutage nicht mehr so das Problem.
Zielplattform generieren.
Ok, schneller wird's nicht - Performance ist allerdings heutzutage nicht mehr so das Problem.
Außerdem erlaubt das Konzept die Aufteilung in Frontend/VM/Backend - und als Frontend
können dann bei Bedarf verschiedene Hochsprachen Verwendung finden, als Backend sind unterschiedliche Targets
mit vertretbarem Aufwand machbar (siehe hierzu z.Bsp.: [LLVM](https://llvm.org/)).
@ -216,18 +212,16 @@ mit vertretbarem Aufwand machbar (siehe hierzu z.Bsp.: [LLVM](https://llvm.org/)
- 💡Eine CPU mit nur zwei Registern (ohne PC) muss extrem viel über den Stack abwickeln. Sowohl in der Emulation wie
auch in der Realität (externer Speicherzugriff!) ist das SEHR langsam. Hier gilt also: Viel(e Register) hilft viel!
## 5. Ausblick ##
Man könnte z.Bsp. mit einer CPU mit mehr Registern (oder auch einem CPU-lokalen Teil-Stack?) experimentieren ... 🤔
Ebenfalls denkbar: Höherwertige CPU-Befehle (womit wir bei der alten RISC/CISC Diskussion angekommen wären!).
Man könnte z.Bsp. mit einer CPU mit mehr Registern (oder auch einem CPU-lokalen Teil-Stack?) experimentieren ... 🤔
Ebenfalls denkbar: Höherwertige CPU-Befehle (womit wir bei der alten RISC/CISC Diskussion angekommen wären!).
Man sieht: Der Kurs macht Lust auf mehr ...
VM: Ein VM-Emulator in Rust wird schneller als die derzeitige Java-Variante sein. Wenigstens ist der mitgelieferte
VM-Emulator langsamer als HE32, er müsste aber eigentlich schneller sein (wg. der enorm reduzierten Anzahl
VM-Emulator langsamer als HE32, er müsste aber eigentlich schneller sein (wg. der enorm reduzierten Anzahl
höherwertiger Befehle die sich aber immer noch gut/schnell emulieren lassen).
🗣 Wenn ich ihn richtig verstanden habe, will **Vespasian** eine verbesserte HE32-Version gestützt auf das Tokio-Framework
entwickeln (oder eine qemu-Version implementieren, die den Assembler-Code wieder auf VM-Code wandelt - oder so ähnlich?).
entwickeln (oder eine qemu-Version implementieren, die den Assembler-Code wieder auf VM-Code wandelt - oder so ähnlich?).
Beides um die Performance nochmal richtig zu steigern. Ich bin gespannt ...