Koreanische Lehrmaterialien für Nand2Tetris – Komplette Roadmap
12 Kapitel gleichzeitig verbinden, von Logikgattern bis hin zu Betriebssystemen
Nand2Tetris koreanische Vorlesungsmaterialien
Die in diesem Blog verfassten Artikel wurden mit dem Ziel erstellt, zu unterrichten. Außerdem können die Informationen im Artikel falsch sein, also seien Sie bitte vorsichtig.
Nand2Tetris ist der Prozess der Erstellung eines Computers, Compilers und Betriebssystems ausgehend von einem einzigen NAND-Gatter. Wenn Sie jedes Kapitel einzeln betrachten, sieht es wie eine Sammlung von HDL-Code oder Python-Code aus, aber tatsächlich werden die Ergebnisse des vorherigen Kapitels zum Material für das nächste Kapitel.
Gute Nacht. In diesem Artikel verbinden wir den vorhandenen Code aus den Kapiteln 1 bis 12 zu einem Ablauf.
voller Fluss
NAND
↓
Logikgatter
↓
Addierer und ALU
↓
Register und Speicher
↓
Hacken Sie CPU und Computer
↓
Assembler
↓
VM-Übersetzer
↓
Jack Compiler
↓
Jack-Betriebssystem
Kapitel 1–5 erstellen Hardware, In den Kapiteln 6–8 wird ein Low-Level-Übersetzer erstellt. In den Kapiteln 9–12 werden Hochsprachen, Compiler und Betriebssysteme behandelt.
Kapitel 1. Boolesche Logik und grundlegende Gatter
Ziel
- Erstellen Sie ein weiteres Logikgatter, das nur NAND verwendet.
- Schauen Sie sich die Wahrheitstabelle an und denken Sie über HDL-Zusammenhänge nach.
- Verstehen Sie, wie Mux und DMux Daten auswählen und verteilen.
NAND gibt nur dann 0 aus, wenn beide Eingänge 1 sind. Es mag einfach erscheinen, aber durch das Verbinden der Eingänge mit derselben Leitung entsteht NOT, und durch erneutes Kombinieren von NOT kann AND und OR erzeugt werden.
Not(a) = Nand(a, a)
And(a, b) = Not(Nand(a, b))
Or(a, b) = Nand(Not(a), Not(b))
Mux istselWählen Sie je nach Bedarf a oder b. DMux hingegen sendet eine Eingabe entweder an a oder b.
Verifizierungsproblem
- Warum tritt NICHT auf, wenn ich an beiden NAND-Eingängen den gleichen Wert eingebe?
- Lassen Sie uns den Unterschied zwischen Mux und DMux in Bezug auf die Datenrichtung erklären.
Mux16Was ist dasselbe wie ein 1-Bit-Mux?
Kapitel 2. Boolesche Arithmetik und ALU
Ziel
- Verstehen Sie den Unterschied zwischen Halbaddierer und Volladdierer.
- Schauen wir uns den Vorgang des Hinzufügens mehrerer Bits an.
- Verstehen Sie ALU, das mehrere Operationen mit Steuerbits ausführt.
Ein Halbaddierer addiert zwei Bits, um Summe und Übertrag zu erzeugen. Der Volladdierer erhält als Eingabe den Übertrag von der vorherigen Position.
HalfAdder: a + b
FullAdder: a + b + carry-in
Add16: 16-Bit-Addierer verbunden mit FullAdder
Der Schlüssel zu ALU besteht nicht darin, für jeden Vorgang einen separaten Schaltkreis zu erstellen. zx, nx, zy, ny, f, noEine Schaltung wird auf verschiedene Weise genutzt, indem der Ein- und Ausgang mit Signalen verändert wird.
Verifizierungsproblem
- Warum wird XOR als Additionssumme verwendet?
- Was passiert, wenn Sie den letzten Übertrag in einer 16-Bit-Addition verwerfen?
- ALUs
zr,ngFür welche Funktion wird die Ausgabe im nächsten Kapitel verwendet?
Kapitel 3. Sequentielle Logik und Gedächtnis
Ziel
- Verstehen Sie den Unterschied zwischen kombinatorischer Logik und sequentieller Logik.
- Ich verstehe, dass DFF die kleinste Einheit ist, die Zeit speichert.
- Sehen Sie, wie sich Bit, Register, RAM und PC erweitern.
In den Schaltungen der Kapitel 1 und 2 wird der Ausgang nur durch den Stromeingang bestimmt. Der Speicher muss sich jedoch den Wert der vorherigen Uhr merken.
Die Ausgabe von DFF ist nicht die aktuelle Eingabe, sondern die Eingabe zu einem früheren Zeitpunkt. Indem Sie hier einen Mux anhängen, können Sie ein Bit erstellen, das nur dann einen neuen Wert speichert, wenn die Last 1 ist.
DFF → Bit → Register → RAM8 → RAM64 → RAM512 → RAM4K → RAM16K
PC istreset, load, incPrioritäten sind wichtig. Normalerweise ist Reset am stärksten, dann Load und zuletzt Inc.
Verifizierungsproblem
- Lassen Sie uns mit Mux den Prozess erklären, durch den ein Bit seinen vorhandenen Wert beibehält, wenn die Last 0 ist.
- Warum brauchen wir DMux und Mux in RAM8?
- Welche Art von Fehlern treten auf, wenn die Steuersignalprioritäten des PCs umgekehrt werden?
Kapitel 4. Maschinensprache hacken
Ziel
- Unterscheiden Sie zwischen A-Befehlen und C-Befehlen.
- Verstehen Sie die Rollen der A-, D- und M-Register.
- Sehen Sie, wie Verzweigungen und Schleifen in Assembly ausgedrückt werden.
Der A-Befehl fügt einen Wert in das A-Register ein.
@10
C-Anweisungen drücken Berechnung, Speicherort und Sprungbedingungen in einer Zeile aus.
D=M+1;JGT
Mist kein separates Register.RAM[A]bedeutet. Wenn Sie diesen Punkt übersehen, kann es leicht zu Verwirrung kommen, ob das A-Register als Wert oder als Adresse verwendet wird.
üben
RAM[2] = RAM[0] + RAM[1]Schreiben .- Schreiben Sie eine Schleife, die 1 bis 10 addiert.
- Überprüfen Sie, warum die SCREEN-Adresse im Füllcode 16384 lautet.
Kapitel 5. Computerarchitektur hacken
Ziel
- Verbinden Sie CPU, Speicher und ROM32K in einem Computer.
- Verstehen Sie, wie die Befehlsbits die ALU und Register steuern.
- Wir sehen eine Struktur, in der Abruf und Ausführung wiederholt werden.
Die CPU interpretiert den aktuellen Befehl und entscheidet, wo das ALU-Ergebnis gespeichert werden soll: A, D oder M. Wenn die Sprungbedingung wahr ist, wird der A-Registerwert in den PC eingefügt, andernfalls wird mit dem nächsten Befehl fortgefahren.
Wählen Sie für Speicher je nach Adressbereich RAM16K, Bildschirm und Tastatur aus. Mit anderen Worten: Sie können den Bildschirm und die Tastatur einfach durch Lesen und Schreiben von Speicheradressen bedienen.
Verifizierungsproblem
instruction[15]Warum werden Klimaanlagenbefehle durch geteilt?writeMWenn dieser Wert 1 ist, an welcher Adresse wird der Wert tatsächlich gespeichert?- Warum benötigt die Tastatur kein Ladesignal?
Kapitel 6. Hack Assembler
Ziel
.asm16-Bit-Datei.hackIn Datei konvertieren.- Prozessbeschriftungen und Variablensymbole.
- Verstehen Sie, warum es notwendig ist, zweimal zu lesen.
Beschriftungen sind keine tatsächlichen Anweisungen und sollten die ROM-Adresse nicht erhöhen. Also im ersten Durchgang(LOOP)Adressen mit demselben Label werden gesammelt und die eigentlichen Anweisungen werden in einem zweiten Durchgang übersetzt.
1. Durchgang: Label → ROM-Adresse
2. Durchgang: A/C-Befehl → 16-Bit-Code
Variablensymbole: in der Reihenfolge zugewiesen, beginnend mit RAM[16]
üben
- Schreiben Sie eine Vorverarbeitungsfunktion, um Leerzeichen und Kommentare zu entfernen.
- Auch wenn dieselbe Variable mehrmals vorkommt, stellen Sie sicher, dass die Adresse nur einmal vergeben ist.
dest,comp,jumpWenn eine Eingabe auftritt, die nicht in der Tabelle enthalten ist, wird ein Fehler ausgegeben.
Kapitel 7. VM Translator I
Ziel
- Verstehen Sie stapelbasierte virtuelle Maschinen.
- Konvertieren Sie arithmetische Anweisungen und Speichersegmente in Hack-Assembly.
- Verstehen Sie, warum für Vergleichsvorgänge eindeutige Bezeichnungen erforderlich sind.
Die meisten VM-Anweisungen nehmen Werte vom Stapel, berechnen sie und geben die Ergebnisse dann wieder ein.
push constant 7
push constant 8
add
local, argument, this, thatfolgt der Basisadresse, temp, pointer, staticJeder verwendet seine eigenen festgelegten Adressregeln.
Verifizierungsproblem
- Warum ändern Push und Pop den SP in einer anderen Reihenfolge?
eq,gt,ltWas passiert, wenn Sie nicht für jeden ein eindeutiges Label erstellen?- Warum sollten statische Variablennamen den Dateinamen enthalten?
Kapitel 8. VM Translator II
Ziel
- Label, goto und if-goto werden im Rahmen der Funktion verarbeitet.
- Verstehen Sie Stapelrahmen von Funktion, Aufruf und Rückgabe.
- Verstehen Sie, was Bootstrap-Code bewirkt.
Beim Aufruf einer Funktion müssen die Rücksprungadresse und der aktuelle Frame-Status gespeichert werden.
return-address
LCL
ARG
THIS
THAT
Rückkehr istFRAME = LCL, RET = *(FRAME - 5)Speichern Sie zunächst das Segment des Anrufers und stellen Sie es dann wieder her. Da sich die LCL bei der Wiederherstellung ändert, muss zunächst RET gespeichert werden.
üben
- Stellen Sie sicher, dass sich Rückgabebezeichnungen bei rekursiven Funktionsaufrufen nicht überschneiden.
- mehrere
.vmStellen Sie sicher, dass das statische Segment der Datei keinen Konflikt verursacht. - Setzen Sie im Bootstrap SP auf 256 und
Sys.initAnruf .
Kapitel 9. Jack Hochsprache
Ziel
- Unterscheiden Sie zwischen Jacks Klasse, Konstruktor, Methode und Funktion.
- Verstehen Sie, wie Objekte und Arrays im Speicher gespeichert werden.
- Verarbeiten Sie Ein- und Ausgaben mithilfe der OS-API.
Jack sieht ähnlich aus wie Java, seine Funktionalität ist jedoch viel einfacher. Die Felder des Objekts werden im Heap gespeichert und die Adresse des aktuellen Objekts wird in der Methode gespeichert.thisEs wird.
Der Konstruktor weist dann Speicher der erforderlichen Größe zuthiskehrt zurück. Arrays ähneln außerdem eher zusammenhängenden Speicherblöcken als speziellen Datenstrukturen.
üben
- Erstellen Sie zwei Counter-Objekte und stellen Sie sicher, dass die Werte getrennt bleiben.
- Verhindert eine doppelte Verarbeitung, während die Tasteneingabe kontinuierlich gedrückt wird.
- Erklärt, warum die Entsorgung gebrauchter Objekte erforderlich ist.
Kapitel 10. Jack-Parser
Ziel
- Verstehen Sie den Prozess, durch den ein Tokenizer Quellcode in Token aufteilt.
- Die Jack-Grammatik ist als rekursiver Abstiegsparser implementiert.
- Geben Sie den Analysebaum als XML aus.
Symbole innerhalb von Zeichenfolgen und außerhalb von Kommentaren müssen unterschiedlich behandelt werden. Daher wird durch eine einfache Aufteilung nach Leerzeichen kein korrekter Tokenizer erstellt.
Der Parser schaut sich das aktuelle Token an und entscheidet, welche Grammatikfunktion aufgerufen werden soll.
class
├─ classVarDec
├─ subroutineDec
└─ statements
Verifizierungsproblem
- in der Zeichenfolge
//Warum kann ich es nicht mit einem Kommentar löschen? - Was ist praktisch an der Trennung von Ausdruck und Begriff?
- Welchen Vorteil hat es, den erwarteten Typ zu überprüfen, bevor das Token verbraucht wird?
Kapitel 11. Jack Compiler
Ziel
- Konvertieren Sie den Analysebaum in VM-Anweisungen.
- Unterscheiden Sie zwischen Klassenbereichs- und Unterprogrammbereichssymbolen.
- Verstehen Sie das verborgene Verhalten von Methodenaufrufen und Konstruktoraufrufen.
Die Symboltabelle speichert nicht nur Namen. Typ, Typ und Index werden zusammen gespeichert, um zu bestimmen, welches VM-Segment verwendet werden soll.
static → static
field → this
argument → argument
var → local
Als erstes Argument erhält die Methode das aktuelle Objekt.pointer 0Verbinden mit Der Konstruktor ist so groß wie die Anzahl der Felder.Memory.allocNach dem Aufruf lautet die Absenderadressepointer 0Setzen Sie es ein.
üben
- Überprüfen Sie, welches Symbol ausgewählt ist, wenn eine lokale Variable mit demselben Namen ein Feld verdeckt.
- Bei der Array-Zuweisung organisieren wir die Reihenfolge, in der Adressen und Werte auf dem Stapel gespeichert werden sollen.
doRückgabewert der Anfragetemp 0Erklären Sie den Grund für das Verwerfen.
Kapitel 12. Jack-Betriebssystem
Ziel
- Implementiert direkt die vom Jack-Programm verwendete Basisbibliothek.
- Trennen Sie die Verantwortlichkeiten von Mathematik, String, Array und Speicher.
- Es füllt die letzte Lücke zwischen Hardware und Hochsprache.
Ein Betriebssystem startet nicht mit einem riesigen Kernel. Der Schlüssel besteht darin, die vom Jack-Programm erwarteten Funktionen in der Jack-Sprache zu implementieren.
- Mathematik: Multiplikation, Division, Absolutwert
- Zeichenfolge: Zeichenspeicherung, Länge, Ausgabekonvertierung
- Array: zusammenhängende Speicherzuweisung
- Speicher: Heap-Zuweisung und -Freigabe
- Bildschirm: Pixel- und Formausgabe
- Tastatur: Tasteneingabe
- Ausgabe: Buchstaben und Zahlen ausgeben
- Sys: Initialisieren, Standby, Herunterfahren
Verifizierungsproblem
- Warum können Multiplikation und Division ohne CPU-Anweisungen implementiert werden?
- Warum hat ein String-Objekt eine separate Länge und Kapazität?
- Welche Probleme treten im Jack-Programm auf, wenn kein Speicher freigegeben wird?
Empfohlene Lernreihenfolge
Am besten gehen Sie die einzelnen Kapitel in der folgenden Reihenfolge durch.
- Bevor Sie sich den fertigen Code ansehen, notieren Sie sich zunächst die Ein-/Ausgabe- und Wahrheitstabelle.
- Testen Sie zuerst kleine Teile.
- Wenn der Test erfolgreich ist, wird er als Komponente der nächsten Stufe verwendet.
- Wenn dies fehlschlägt, schauen Sie sich nicht nur die Ausgabe an, sondern finden Sie heraus, auf welcher Ebene sich der Wert geändert hat.
- Vergleichen Sie am Ende den vorhandenen Blogcode mit Ihrem eigenen Code.
Wenn Sie den richtigen Code so eingeben, wie er ist, wird das Projekt schnell abgeschlossen. Aber was bei Nand2Tetris wichtig ist, ist zu verstehen, wie die Ebene eine Ebene unter der Antwort selbst die aktuelle Ebene unterstützt.
Sobald wir den Fluss von NAND sehen, der zu einem Betriebssystem führt, erscheinen Computer nicht mehr magisch.