Hintergrund
Ich mag meinen alten 8-Bit-6502-Chip. Es macht sogar Spaß, einige der Herausforderungen hier bei PPCG im 6502-Maschinencode zu lösen. Einige Dinge, die einfach sein sollten (wie Daten einlesen oder auf stdout ausgeben), sind im Maschinencode unnötig umständlich. Ich habe also eine grobe Idee: Erfinde meine eigene virtuelle 8-Bit-Maschine, die vom 6502 inspiriert ist, aber deren Design geändert wurde, um für Herausforderungen besser geeignet zu sein. Als ich anfing, etwas zu implementieren, wurde mir klar, dass dies eine nette Herausforderung sein könnte, wenn das Design der VM auf ein Minimum reduziert wird :)
Aufgabe
Implementieren Sie eine virtuelle 8-Bit-Maschine, die der folgenden Spezifikation entspricht. Das ist Code-Golf , also gewinnt die Implementierung mit den wenigsten Bytes.
Eingang
Ihre Implementierung sollte die folgenden Eingaben übernehmen:
Ein einzelnes Byte ohne Vorzeichen
pc
, dies ist der anfängliche Programmzähler (die Adresse im Speicher, an der die VM mit der Ausführung beginnt,0
basierend auf)Eine Liste von Bytes mit einer maximalen Länge von
256
Einträgen. Dies ist der RAM für die virtuelle Maschine (mit ihrem anfänglichen Inhalt).
Sie können diese Eingabe in jedem vernünftigen Format vornehmen.
Ausgabe
Eine Liste von Bytes, die den endgültigen Inhalt des Arbeitsspeichers nach dem Beenden der VM darstellen (siehe unten). Sie können davon ausgehen, dass Sie nur Eingaben erhalten, die schließlich zum Abbruch führen. Jedes sinnvolle Format ist erlaubt.
Virtuelle CPU
Die virtuelle CPU hat
- ein 8-Bit-Programmzähler,
- ein 8-Bit-Akkumulatorregister aufgerufen
A
und - ein 8-Bit-Indexregister aufgerufen
X
.
Es gibt drei Statusflags:
Z
- Das Null-Flag wird gesetzt, nachdem eine Operation zu einem Ergebnis geführt hat0
N
- das negative Flag wird gesetzt, nachdem eine Operation zu einer negativen Zahl geführt hat (iow Bit 7 des Ergebnisses wird gesetzt)C
- Das Übertragsflag wird durch Additionen und Verschiebungen für das "fehlende" Bit des Ergebnisses gesetzt
Beim Start werden alle Flags gelöscht, der Programmzähler wird auf einen bestimmten Wert gesetzt und der Inhalt von A
und X
ist unbestimmt.
Die 8-Bit-Werte repräsentieren entweder
- Eine ganze Zahl ohne Vorzeichen im Bereich
[0..255]
- eine vorzeichenbehaftete ganze Zahl, das Zweierkomplement im Bereich
[-128..127]
abhängig vom Kontext. Wenn eine Operation über- oder unterläuft, wird der Wert umbrochen (und im Falle einer Addition wird das Übertragsflag beeinflusst).
Beendigung
Die virtuelle Maschine wird beendet, wenn
- Eine
HLT
Anweisung ist erreicht - Auf eine nicht vorhandene Speicheradresse wird zugegriffen
- Der Programmzähler wird außerhalb des Arbeitsspeichers ausgeführt. (Beachten Sie, dass er nicht umgangen wird, selbst wenn der VM die vollen 256 Byte Arbeitsspeicher zugewiesen werden.)
Adressierungsmodi
- implizit - die Anweisung hat kein Argument, der Operand ist impliziert
- sofort - der Operand ist das Byte direkt nach der Anweisung
- relative - (nur zum Verzweigen) das Byte nach dem Signieren des Befehls (Zweierkomplement) und bestimmt den Offset, der dem Programmzähler hinzugefügt werden soll, wenn die Verzweigung ausgeführt wird.
0
ist der Ort der folgenden Anweisung - absolut - das Byte nach dem Befehl ist die Adresse des Operanden
- indexiert - das Byte nach dem Anweisungsplus
X
(dem Register) ist die Adresse des Operanden
Anleitung
Jeder Befehl besteht aus einem Operationscode (ein Byte) und in den Adressierungsmodi unmittelbar , relativ , absolut und indiziert einem zweiten Argumentbyte. Wenn die virtuelle CPU eine Anweisung ausführt, erhöht sie den Programmzähler entsprechend (durch 1
oder)2
).
Alle hier gezeigten Opcodes sind hexadezimal.
LDA
- Laden Sie den Operanden inA
- Opcodes: sofort:,
00
absolut :,02
indiziert:04
- Flags:
Z
,N
- Opcodes: sofort:,
STA
-A
In Operand speichern- Opcodes: sofort:,
08
absolut :,0a
indiziert:0c
- Opcodes: sofort:,
LDX
- Laden Sie den Operanden inX
- Opcodes: sofort:,
10
absolut :,12
indiziert:14
- Flags:
Z
,N
- Opcodes: sofort:,
STX
-X
In Operand speichern- Opcodes: sofort:,
18
absolut :,1a
indiziert:1c
- Opcodes: sofort:,
AND
- bitweise und vonA
und Operand inA
- Opcodes: sofort:,
30
absolut :,32
indiziert:34
- Flags:
Z
,N
- Opcodes: sofort:,
ORA
- bitweise oder vonA
und Operand inA
- Opcodes: sofort:,
38
absolut :,3a
indiziert:3c
- Flags:
Z
,N
- Opcodes: sofort:,
EOR
- Bitweises xor (exklusive oder) vonA
und Operand inA
- Opcodes: sofort:,
40
absolut :,42
indiziert:44
- Flags:
Z
,N
- Opcodes: sofort:,
LSR
- logische Verschiebung nach rechts, alle Bits des Operanden um eine Stelle nach rechts verschieben, Bit 0 wird übertragen- Opcodes: sofort:,
48
absolut :,4a
indiziert:4c
- Flags:
Z
,N
,C
- Opcodes: sofort:,
ASL
- Arithmetische Verschiebung nach links, Verschiebung aller Bits des Operanden um eine Stelle nach links, Bit 7 wird übertragen- Opcodes: sofort:,
50
absolut :,52
indiziert:54
- Flags:
Z
,N
,C
- Opcodes: sofort:,
ROR
- nach rechts drehen, alle Bits des Operanden um eine Stelle nach rechts verschieben, Übertrag geht zu Bit 7, Bit 0 geht zu Übertragen- Opcodes: sofort:,
58
absolut :,5a
indiziert:5c
- Flags:
Z
,N
,C
- Opcodes: sofort:,
ROL
- nach links drehen, alle Bits des Operanden um eine Stelle nach links verschieben, Übertrag geht zu Bit 0, Bit 7 geht zu Übertragen- Opcodes: sofort:,
60
absolut :,62
indiziert:64
- Flags:
Z
,N
,C
- Opcodes: sofort:,
ADC
- Addieren mit Carry, Operand plus Carry wird hinzugefügtA
, Carry wird auf Überlauf gesetzt- Opcodes: sofort:,
68
absolut :,6a
indiziert:6c
- Flags:
Z
,N
,C
- Opcodes: sofort:,
INC
- Inkrementiere den Operanden um eins- Opcodes: sofort:,
78
absolut :,7a
indiziert:7c
- Flags:
Z
,N
- Opcodes: sofort:,
DEC
- Dekrementiere den Operanden um eins- Opcodes: sofort:,
80
absolut :,82
indiziert:84
- Flags:
Z
,N
- Opcodes: sofort:,
CMP
- Vergleichen SieA
mit dem Operanden, indem Sie den Operanden von subtrahierenA
und das Ergebnis vergessen. Carry wird bei Unterlauf gelöscht, sonst eingestellt- Opcodes: sofort:,
88
absolut :,8a
indiziert:8c
- Flags:
Z
,N
,C
- Opcodes: sofort:,
CPX
- vergleicheX
- wieCMP
fürX
- Opcodes: sofort:,
90
absolut :,92
indiziert:94
- Flags:
Z
,N
,C
- Opcodes: sofort:,
HLT
-- kündigen- Opcodes: implizit:
c0
- Opcodes: implizit:
INX
-X
um eins erhöhen- Opcodes: implizit:
c8
- Flags:
Z
,N
- Opcodes: implizit:
DEX
- AbnahmeX
um eins- Opcodes: implizit:
c9
- Flags:
Z
,N
- Opcodes: implizit:
SEC
- Carry Flag setzen- Opcodes: implizit:
d0
- Flaggen:
C
- Opcodes: implizit:
CLC
- klare Tragefahne- Opcodes: implizit:
d1
- Flaggen:
C
- Opcodes: implizit:
BRA
- immer verzweigen- Opcodes: relativ:
f2
- Opcodes: relativ:
BNE
- Verzweigen, wenn dasZ
Flag gelöscht ist- Opcodes: relativ:
f4
- Opcodes: relativ:
BEQ
- Branch wennZ
Flag gesetzt- Opcodes: relativ:
f6
- Opcodes: relativ:
BPL
- Verzweigen, wenn dasN
Flag gelöscht ist- Opcodes: relativ:
f8
- Opcodes: relativ:
BMI
- Branch wennN
Flag gesetzt- Opcodes: relativ:
fa
- Opcodes: relativ:
BCC
- Verzweigen, wenn dasC
Flag gelöscht ist- Opcodes: relativ:
fc
- Opcodes: relativ:
BCS
- Branch wennC
Flag gesetzt- Opcodes: relativ:
fe
- Opcodes: relativ:
Opcodes
Das Verhalten der VM ist undefiniert, wenn ein Opcode gefunden wird, der keinem gültigen Befehl aus der obigen Liste zugeordnet ist.
Laut Jonathan Allan Wunsch , Sie können Ihren eigenen Satz von Opcodes anstelle der in der gezeigten Opcodes wählen Anleitung Abschnitt. Wenn Sie dies tun, müssen Sie eine vollständige Abbildung auf die oben in Ihrer Antwort verwendet Opcodes hinzufügen.
Das Mapping sollte eine Hex-Datei mit Paaren sein <official opcode> <your opcode>
, zB wenn Sie zwei Opcodes ersetzt haben:
f4 f5
10 11
Zeilenumbrüche spielen hier keine Rolle.
Testfälle (offizielle Opcodes)
// some increments and decrements
pc: 0
ram: 10 10 7a 01 c9 f4 fb
output: 10 20 7a 01 c9 f4 fb
// a 16bit addition
pc: 4
ram: e0 08 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
output: 0a 0b 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
// a 16bit multiplication
pc: 4
ram: 5e 01 28 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
02 62 03 c9 f8 e6 c0 00 00
output: 00 00 00 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
02 62 03 c9 f8 e6 c0 b0 36
Ich könnte später weitere Testfälle hinzufügen.
Referenz und Prüfung
Als Hilfe für eigene Experimente finden Sie hier eine Referenzimplementierung (völlig ohne Golf) : Sie kann Ablaufverfolgungsinformationen (einschließlich zerlegter Anweisungen) ausgeben stderr
und Opcodes konvertieren, während sie ausgeführt werden.
Empfohlener Weg, um die Quelle zu bekommen:
git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules
Oder zur Kasse gehen challenge
und agit submodule update --init --recursive
nach dem Klonen ein, um mein Custom Build System zu bekommen.
Erstellen Sie das Tool mit GNU make (geben Sie einfach Folgendes ein make
, oder gmake
wenn auf Ihrem System das Standard-make nicht GNU make ist).
Verwendung :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram
-s startpc
- Der anfängliche Programmzähler ist standardmäßig auf0
-h
- Eingabe erfolgt in hex (sonst binär)-t
- Ablaufverfolgung bisstderr
-c convfile
- Konvertieren von Opcodes gemäß einer Zuordnung inconvfile
-d
- Ergebnisspeicher als Binärdaten sichern-x
- Speicher als Hex sicherninitial_ram
- Der anfängliche RAM-Inhalt, entweder hexadezimal oder binär
Beachten Sie, dass die Konvertierungsfunktion bei Programmen fehlschlägt, die während der Ausführung Opcodes ändern.
Haftungsausschluss: Die oben genannten Regeln und Spezifikationen sind maßgeblich für die Herausforderung, nicht für dieses Tool. Dies gilt insbesondere für die Opcode-Konvertierungsfunktion. Wenn Sie der Meinung sind, dass das hier vorgestellte Tool einen Fehler in den technischen Daten aufweist, melden Sie dies bitte in einem Kommentar :)
BRA
("branch always") keine Verzweigung in den Kontrollfluss einführt, sollte sie nicht aufgerufen werden JMP
?
BRA
gibt es in späteren Chip-Designs (der 6502 hat keine solche Anweisung) wie der 65C02 und der MC 68000. JMP
gibt es auch. Der Unterschied besteht darin, dass BRA
die relative Adressierung und JMP
die absolute Adressierung verwendet werden. Also habe ich diese Entwürfe befolgt - das klingt in der Tat nicht ganz so logisch;)