Technik Wiki
Registrieren
Advertisement
Gruppe: Grid Redstone-Komparator
Schaltwerke

Grid Roter Sand blass mit Mechaniken
                Hier:
Grid Redstone mit Redstone

Grid Befehlsblock blass mit Befehlen
Verfügbar in:
Wiki Redstone-Welt Redstone-Welt

Dank Redstone sind der Kreativität keine Grenzen gesetzt, deshalb kann man auch Rechner (engl. Computer) in Minecraft bauen.

Grundlegendes[]

Rechner (Redstone) Bild 1.1 Rechner (Redstone) Bild 1.2 Rechner (Redstone) Bild 1.3 Rechner (Redstone) Bild 1.4

Heutige moderne Computer sind extrem komplex. Minecraft-Computer sind dagegen eher abstrakte Modelle, die sehr wenig mit der Realität gemeinsam haben (Zum Vergleich: Zuses Z3, die als erster Computer überhaupt angesehen wird, konnte deutlich mehr). Trotzdem kann man mit Minecraft viel über Computer lernen, außerdem ist dieser Computer turingmächtig. Er ist sehr groß und langsam, obwohl er genauso auch viel effizienter gebaut werden könnte. Dabei wird das ganze aber leicht unübersichtlich, was es erschwert, daraus zu lernen.

Das Modell ist vom Aufbau der Harvard-Architektur ähnlich und hat einen sehr kleinen Befehlssatz. In einem Taktzyklus wird genau ein Befehl ausgeführt (IPC=1). Ein Befehl ist 16 bit lang, Datensätze 8 bit. Es gibt 8 Register. Ein Stack ist nicht vorhanden; Subroutinen sind daher nicht (einfach) möglich. Im Befehlssatz sind relativ viele Lücken, etwa dass 8 Register mit 4 bit adressiert werden. Mit dem ungenutzten Bit lassen sich 8 weitere Register anschließen.

Register[]

Die acht eingebauten Register R0 bis R7 sind General Purpose Register (GPR). Das heißt, sie sind alle gleichwertig und alle Operationen können mit ihnen durchgeführt werden. Kein Register hat irgendeine Sonderbedeutung, sodass das Programm alle zum Speichern von Variablen verwenden kann. Es gibt noch acht weitere Register R8 bis RF, die nicht eingebaut, aber reserviert sind. Sie können nach Einbau also ohne weitere Anpassungen verwendet werden.

Busse[]

Es gibt drei Datenbusse, D0, D1 und D2. Alle sind 8 bit breit. D0 ist der Datenbus, über den Werte in Register geschrieben werden. D1 und D2 enthalten die Werte von zwei ausgewählten Registern. Sie führen benötigte Daten an weitere Komponenten.

Es gibt drei wichtige Adressbusse, die 4 bit breit sind. Sie heißen A0, A1 und A2. A1 wählt das Register aus, dessen Wert auf D1 kommt; A2 und D2 verhalten sich analog dazu. A0 gibt das Register an, in das der Wert von D0 geschrieben wird.

Maschinencode und Befehlssatz[]

Von den 16 Bit Opcode geben die ersten 4 den Befehl an. Die die restlichen 12 Bit sind frei und werden je nach Befehl unterschiedlich verwendet. Die Tabelle gibt an, welche Bedeutung ihnen bei bestimmten Befehlen beigemessen wird. Es können entweder Daten (Direktwerte genannt) entgegengenommen werden oder die Nummer der Registers, in dem die Daten stehen. Letzteres ist Standard; Felder, wo ersteres gilt, sind grün unterlegt.

Befehl (Bit 15 - 11) Mnemonik A0 (Bit 11 - 8) A1 (Bit 7 - 4) A2 (Bit 3 - 0) Beschreibung
0x0 MOV Ziel Quelle -- Kopiert Register Quelle in Register Ziel
0x1 RIN Ziel Daten Bit 7-4 Daten Bit 3-0 Schreibt eine Zahl in Ziel
0x2 COMP -- Zahl A Zahl B Vergleicht Zahl A und Zahl B. Das Ergebnis des Vergleichs wird in einem internen Register gespeichert. Bedingte Sprungbefehle treffen ihre Entscheidung basierend auf dem letzten Ergebnis einer Vergleichsoperation.
0x3 SHIFT Ziel Zahl A Optionen Bitweise Verschiebeoperationen an Zahl A. Bit der Optionen Beschreibung
7-5 --
4 Modus (Aus: Zykliche Verschiebung, An: Logische Verschiebung)
3 Richtung (Aus: nach rechts, An: nach links)
2-0 Entfernung der Operation.
0x4 IN Ziel Daten Port Liest aus dem IO-Port nach Ziel. Dabei erhält der IO-Port Daten als zusätzliches Argument (etwa für eventuelle Adressen). Wie der Port Daten behandelt bleibt ihm überlassen.
0x5 OUT Port Daten 1 Daten 2 Sender Daten 1 und Daten 2 in den IO-Port
0x6 JMP1 Bedingung Ziel wenn wahr Ziel wenn falsch Sprung je nach Bedingung an unterschiedliche Ziele. Für die Bedeutung von Bedingung siehe Tabelle rechts. Mit A und B sind die beiden Operanden des letzten Aufrufs von COMP gemeint. Option Beschreibung
0 FALSCH (Unbedingt nie Springen)
1 WAHR (Unbedingt Springen)
2 IST GLEICH (Bedingter Sprung wenn A=B)
0x7 JMP2 Bedingung AdresseBit 7-4 AdresseBit 3-0 Sprung nach Adresse bei wahrer Bedingung. Für die Bedeutung von Bedingung siehe Tabelle rechts. Mit A und B sind die beiden Operanden des letzten Aufrufs von COMP gemeint. 3 UNGLEICH (Bedingter Sprung wenn nicht A=B)
4 GRÖSSER ALS (Bedingter Sprung wenn A>B)
5 KLEINER ALS (Bedingter Sprung wenn A<B)
6 GRÖSSER GLEICH (Bedingter Sprung wenn nicht A<B)
7 KLEINER GLEICH (Bedingter Sprung wenn nicht A>B)
0x8 - 0xF ALU Ziel Zahl A Zahl B Verrechnet Zahl A und Zahl B und speichert in Ziel
Befehl Beschreibung
0x8 Addition (ADD)
0x9 Subtraktion (SUB)
0xA Bitweises UND (AND)
0xB Bitweises ODER (OR)
0xC Bitweises exklusives ODER (XOR)
0xD Bitweises UND, invertiert (NAND)
0xE Bitweises ODER, invertiert (NOR)
0xF Bitweises NICHT (NOT) Nur A wird verwendet.

Kernkomponenten[]

Hier weden einige zentrale Komponenten des Prozessorkerns näher erläutert.

ROM[]

Im ROM wird das ausgeführte Programm gespeichert. Vorteilhaft ist es, wenn jede beliebige Zeile direkt angesprochen und gelesen werden kann (wahlfreier Zugriff). Das können vor allem die Kolben-ROMs nicht, welche nur das aktuelle Element und das darauf folgende ohne größere Zeitverzögerung auslesen können. Für Sprünge, insbesondere an eine vorherige Stelle im Programm, müsste der ROM fast einmal komplett durchlaufen, was viel zu langsam ist. In diesem Artikel wird ein Daten-Abruf-Speicher mit Fackeln verwendet.

Zähler[]

Bei jedem Befehl im Programm muss die nächste Zeile im ROM gelesen werden. Zusätzlich muss die aktuelle Zeile per Sprungbefehl auch gesetzt werden können. Dabei gibt es zwei Möglichkeiten:

  • Einen binären Zähler (mit Sprungfunktion) zu nehmen und dessen Ausgang mit einem Kodierer passend an den ROM weiterzugeben
  • Einen unären Zähler (auch mit Sprungfunktion) zu verwenden, der direkt an den ROM gebunden ist. Die Sprungadresse muss dann also kodiert werden, bevor der Wert an den Zähler darf.

Die zweite Variante ist eher zu empfehlen, da Sprünge relativ selten sind. Außerdem lässt es sich so herum kompakter bauen. Die hier verwendete Variante entspricht einem Ringzähler mit der zusätzlichen Eigenschaft, dass ein beliebiges Bit gesetzt und alle Bits gleichzeitig zurückgesetzt werden können. Die Sprungfunktion erfolgt dann dadurch, dass die dekodierte Adresse den aktuellen Zustand des Zählers überschreibt.

Register[]

Register sind extrem schnelle Speicher, wo alle Berechnungen der CPU zwischengespeichert werden. In Minecraft schaltet man dazu mehrere D-Flip-Flops zu einer größeren Speichereinheit zusammen. Wichtig ist, dass sie nur 1 Block breit sind, damit sie leichter verwendet werden können. Empfehlenswert ist dieser hier. Es gehören also 8 von diesen Flip-Flops pro Register nebeneinander. Dies ist zwar ziemlich groß, sie zu stapeln macht das Ganze aber zu unübersichtlich.

Damit die Register schreiben (die Kolben sind unten), muss das Register aktiviert sein und es muss die steigende Taktflanke sein. Dazu führen die beiden Verbindungskabel (enable und Takt) links und rechts darüber vorbei, während mithilfe von Fackeln ein senkrechtes UND-Gatter angeschlossen ist (siehe Bild). Die Aktivierungskabel kommen aus einem 3 bit Dekodierer (8 Ausgänge), der von A0 gespeist wird (1 Bit bleibt ungenutzt, um später Register zu ergänzen). Dieser kann mit einer Zusatzfunktion (siehe Bild) (de)aktiviert werden, sodass kein Register aktiviert ist.

D1 und D2[]

Die Ausgänge der Register werden zweigeteilt, einmal geradeaus und einmal nach oben. Auf beiden Ebenen passiert das selbe. Jeder Registerausgang wird mit einer Kolbenschranke versehen, sodass der Wert nur bei deren Aktivierung durchkommt. Alle Ausgänge hinter den Schranken einer Etage kommen auf einen gemeinsamen Datenbus. Alle Aktivierungskabel für die Schranken einer Etage kommen aus einem 3-Bit Dekodierer. Der untere Bus heißt D1, der obere D2. Die Dekodierer werden dementsprechend von A1 und A2 gespeist (und wieder bleibt ein Bit ungenutzt).

Steuerwerk[]

Das Steuerwerk steuert sämtliche Komponenten des Kerns. Das wird üblicherweise dadurch bewerkstelligt, dass jede Komponente über verschiedene Steuerleitungen ansprechbar ist. Das Steuerwerk aktiviert und deaktiviert die passenden Steuerleitungen, sodass die Komponenten sich so verhalten, dass der aktuelle Befehl korrekt ausgeführt wird.

Da immer genau ein Befehl pro Takt ausgeführt wird, ist das Steuerwerk in diesem Rechner nahezu primitiv. Indem man den Befehl in mehreren Takten ausführt oder mehrere Teilbefehle pro Takt ausführt ([[de.wikipedia:Pipeline_(Prozessor)|Pipelining]), kann die Leistung eines Prozessors stark vergrößert werden. Dies erfordert gleichzeitig aber auch ein ungleich komplexeres Steuerwerk.

Befehlsdekodierer[]

Aufgrund des kleinen Befehlssatzes ist auch der Befehlsdekodierer relativ einfach gehalten. Er besteht aus einem weiteren kleinen ROM mit einer Adresse pro Befehl. Jedes Bit ist einer Steuerleitung zugewiesen. Je nach Befehl werden dann also die entsprechend benötigten Steuerleitungen aktiviert, damit der ausgeführte Befehl die passende Funktionalität erhält.

Steuerleitungen[]

Hier kommt noch eine Tabelle hin, welche Steuerleitung was macht und bei welchem Befehl aktiviert wird.

Name Beschreibung Aktiviert bei folgenden Befehlen
A1+A2 --> D0 Leitet das zweite Byte des Opcodes als Direktwert auf den Datenbus, anstatt es auf die Adressbusse A0 und A1 aufzuteilen RIN, JMP2
D1 --> D0 Schreibt den Inhalt von D1 auf D0 MOV
D2 --> D0 Schreibt den Inhalt von D2 auf D0 -- (Implizit wenn die Jump always-Steuerleitung aktiviert ist)
Write registers Schreibt den Wert von D0 in das von A0 ausgewählte Register MOV, RIN, SHIFT, IN, ALU
IO-RW Steuert, ob der IO-Port schreibend (Wert 0) oder lesend (Wert 1) angesteuert werden soll. IN
Enable IO Aktiviert den IO-Controller. IN, OUT
Jump IF Schreibt D0 als Adresse in das Sprungregister, wenn die Sprungbedingung wahr ist JMP2
Jump always Schreibt immer D0 in das Sprungregister. Je nachdem ob die Sprungbedingung wahr oder falsch ist, wird entweder D1 oder D2 auf D0 geleitet. JMP1
Compare Schreibt das Ergebnis des Komparators auf D0 COMP
Shift Schreibt das Ergebnis des Bitschiebers auf D0 SHIFT
ALU Schreibt das Ergebnis der ALU auf D0 ADD, SUB, AND, OR, XOR, NAND, NOR, INV
Continue Wenn diese Steuerleitung nicht gesetzt ist, fährt der Taktgeber nicht mit dem nächsten Takt fort, bis dieser auf anderem Wege ein Signal dazu erhalten hat. MOV, RIN, COMP, SHIFT, JUMP1, JUMP2, ADD, SUB, AND, OR, XOR, NAND, NOR, INV (Alle außer IN und OUT)

Taktgeber[]

Der Taktgeber -- hier auf Basis eines Komparators -- ist so langsam geschaltet, dass zwischen zwei Taktsignalen auch der langsamste Befehl ausgeführt werden kann. Das entspricht ungefähr der Zeit, die ein Signal benötigt, um von den Registern aus zu der verarbeitenden Komponente und wieder zurück zu gelangen. Ist der Taktgeber schneller, kommt es zu Fehlern in der Berechnung. Deshalb sollte die Taktung deutlich langsamer geschaltet sein als der längste Befehl im schlimmsten Fall brauchen würde, weil diese Zeitspanne (zumindest in Minecraft) nicht einfach präzise messbar ist.

In diesem Rechner wird in jedem Takt genau ein Befehl ausgeführt. Um Funktionalität wie Befehle mit längerer Latenz, Nutzereingaben ohne Warteschleife oder das Anhalten der Maschine einfach zu ermöglichen, wird der nächste Takt nur dann gestartet, wenn eine bestimmte Steuerleitung gesetzt ist. Die meisten Befehle setzen sie automatisch, was dazu führt, dass der Taktgeber ohne Unterbrechung arbeitet. Manche aber -- etwa diejenigen, die Nutzereingaben erfordern -- geben den nächsten Takt erst dann frei, wenn der Nutzer eine Eingabe getätigt und bestätigt hat. Bis dahin hält das Programm an und wartet. Dies kann aber auch genutzt werden, um die Maschine zu stoppen.

Äußere Komponenten[]

Diese Komponenten gehören zwar immer noch fest zum Prozessorkern, allerdings sind sie eigenständige Bauteile und nicht so fest mit dem Rest des Kerns verbunden, d.h. sie sind als abgetrenntes Bauteil immer noch funktionsfähig. Die meisten sind über die drei Datenbusse angebunden und führen ständig ihre Berechnungen anhand der Daten von D1 und D2 aus. Das Ergebnis wird nur auf D0 weitergeleitet, wenn das jeweilige Bauteil aktiviert ist

ALU[]

Die verwendete Arithmetisch-logische Einheit wird wird hier genauer beschrieben (Variante 1).

Komparator[]

Das verwendete Vergleichwerkk wird hier genauer beschrieben (Variante 1).

Bitverschieber[]

Zum verschieben von Bits wird ein erweiterter Barrel-Shifter verwendet, wie er im Artikel Bitschieber beschrieben wird.

IO-Controller[]

Über die IO-Ports können beliebige weitere Bauteile angeschlossen werden, mit denen der Prozessor dann kommunizieren kann. Es sind 16 Ports reserviert, von denen nur Port 1 mit der Datenein- und Ausgabe für den Nutzer verbunden ist. Weitere denkbare Bauteile könnten verschiedene Anzeigen oder ein Arbeitspeicher sein.

Port 0: Nutzerinteraktion[]

Über Port 0 kann mit dem Nutzer interagiert werden. Das Schreiben eines Wertes an diesen Port führt dazu, dass dieser Wert auf einer binären Anzeige dargestellt wird. Wird eine Zahl aus diesem Port gelesen, muss der Nutzer diese an einer binären Eingabetafel mithilfe von Hebeln eingeben. In beiden Fällen hält das Programm an, bis der Nutzer die Eingabe beziehungsweise die Annahme des Wertes per Knopfdruck bestätigt. Zusätzlich leuchtet eine Lampe auf, die den Nutzer darauf hinweist.

Programmierung[]

Automatisierung[]

Der Rechner wird programmiert, indem in dessen Speicher Fackeln für jedes Bit gesetzt werden. Das macht es sehr aufwendig, Programme einzugeben, zu verwalten oder zu wechseln. Deshalb gibt es ein Tool, welches das Programm als eine Folge von 1 und 0 annimmt und einen komprimierten Befehl ausgibt, welcher die Fackeln nach Bedarf setzt oder entfernt, sodass sie das gewünschte Programm ergeben.

Assemblersprache[]

Weil niemand auf den ersten Blick erkennen kann, was ein Wust aus Einsen und Nullen mit dem Rechner macht, werden Programme in der Regel in Assemblersprache geschrieben. (Komplexere Programme werden in einer abstrakteren Programmiersprache geschrieben und dann in Assemblersprache übersetzt, allerdings ist dies bei so primitiver Hardware nicht nötig). Assemblerprogramme werden dann von einem Assembler in binären Mashinencode übersetzt. In diesem Fall übernimmt der Programmierer selbst die Rolle des Assemblers.

Alle Befehle sind 4x4bit groß, weshalb man sie besonders gut hexadezimal aufschreiben kann. Das erste Nibble (Ja, so heißen 4 bits tatsächlich) gibt immer den Befehl an, deshalb wird es zur besseren Lesbarkeit durch dessen Mnemonik ersetzt. Bei den restlichen Werten wird die Notation geändert, je nachdem, was sie darstellen: Direktwerte werden durch das Präfix 0x gekennzeichnet, Register durch R, Ports mit P und so weiter. So lässt sich die Lesbarkeit des Codes weiter verbessern.

Da die hier gezeigten Beispielprogramme so wenig Variablen benötigen, dass jeder Variable ein Register zugewiesen werden kann, wird deren Name austauschbar mit ihrer Registernummer verwendet.

Fibonacci[]

Berechnet die Fibonacci-Folge und pausiert nach jeder Zahl. Dafür müssen lediglich zwei Werte gespeichert werden, der aktuelle un der vorherige. Nach jeder Berechnung gibt das Programm den Wert über Port 0 an den Benutzer aus und wartet bis er dies per Knopfdruck bestätigt.

Register Variable Erklärung
R0 A Die vorherige Zahl der Fibonacci-Reihe
R1 B Die kommende Zahl der Fibonacci-Reihe


Adresse Maschinencode Assembler Pseudocode
0x00 0001 0000 0000 0000 RIN R0 0x00 A = 0
0x01 0001 0001 0000 0001 RIN R1 0x01 B = 1
do {
0x02 0101 0001 0000 0000 OUT P1 R0 print(A)
0x03 1000 0000 0000 0001 ADD R0 R0 R1 A = A + B
0x04 1001 0001 0000 0001 SUB R1 R0 R1 B = A - B
0x05 0111 0001 0000 0010 JMP2 true 0x02 } while (true)

Multiplizierer[]

Multipliziert zwei gegebene Zahlen mit dem Shift-and-add-Algorithmus. Das Programm frag zunächst nach den Faktoren A und B, die multipliziert werden sollen. Die Multiplikation entspricht der schriftlichen Multiplikation wie man sie aus der Schule, kennt, nur zur Basis 2. Am Ende wird das Ergebnis ausgegeben und das Programm fängt wieder von vorne an.

Register Variable Erklärung
R0 TMP0 Variable 1 zum Zwischenspeichern von Berechnungsergebnissen
R1 TMP1 Variable 2 zum Zwischenspeichern von Berechnungsergebnissen
R2 T Temporäre Variable der Hauptschleife.
R3 1 Konstante eins. Da der Befehlssatz nur selten die Verwendung von Direktwerten erlaubt, ist es nützlich, häufigen Konstanten ein eigenes Register zuzuweisen um nicht ansonsten jedes Mal einen Takt dafür zu verschwenden.
R4 i Die Indexvariable der Hauptschleife.
R5 E Die Ergebnisvariable.
R6 A Faktor A.
R7 B Faktor B.


Adresse Maschinencode Assembler Pseudocode
0x00 0001 0011 0000 0001 RIN R3 0x01 1 = 1
do {
0x01 0001 0100 0000 0000 RIN R4 0x00 i = 0
0x02 0001 0101 0000 0000 RIN R5 0x00 E = 0
0x03 0100 0110 0000 0001 IN R6 R0 P1 A = read()
0x04 0100 0111 0000 0001 IN R7 R0 P1 B = read()
do {
0x05 0001 0001 0001 0000 RIN R1 0x10 // Konstante für logischen Rechtsshift
0x06 1011 0001 0001 0100 OR R1 R1 R4 // Überlagert man die Konstante mit i, erhält man den vollen Parameter zum Schieben von i bits
0x07 0011 0010 0110 0001 SHIFT R2 R6 R1 T = A >> i
0x08 1010 0010 0010 0011 AND R2 R2 R3 T &= 1
0x09 0010 0000 0010 0011 COMP R2 R3
0x0A 0111 0011 0000 1111 JMP2 != 0x0F if (T == 1) {
0x0B 0001 0001 0001 1000 RIN R1 0x18 // Konstante für logischen Linksshift
0x0C 1011 0001 0001 0100 OR R1 R1 R4 // Logischer linksshift um i bits
0x0D 0011 0010 0111 0001 SHIFT R2 R7 R1 T = B << i
0x0E 1000 0101 0101 0010 ADD R5 R5 R2 E += T
}
0x0F 1000 0100 0100 0011 ADD R4 R4 R3 i++
0x10 0001 0001 0000 0111 RIN R1 0x07
0x11 0010 0000 0100 0001 COMP R4 R1
0x12 0111 0111 0000 0101 JMP2 <= 0x05 } while (i <= 7)
0x13 0101 0001 0101 0000 OUT P1 R5 print(E)
0x14 0111 0001 0000 0001 JMP2 true 0x01 } while (true)

Die Programme in den PC übertragen[]

Um ein geschriebenes Programm in den eigentlichen Rechner zu übertragen, müssen überall dort, wo im Binärcode eine 1 ist, eine Fackel gesetzt werden. Das ist Zeitaufwendig und fehleranfällig. Deshalb sollte man das setzen der Fackeln Befehlsblöcken überlassen. Dieses Tool (Quellcode hier) generiert einen komprimierten Befehl, der sämtliche Fackeln automatisch setzt. Es muss dafür lediglich mit dem Programm sowie Koordinaten und Richtung versehen werden. Zusätzlich kann es den Befehl auf mehrere Befehlsblöcke aufteilen, um die Bauhöhenbeschränkungen bei besonders komplexen Programmen umgehen zu können.


Disambig color
Advertisement