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 :)
quelle
BRA
("branch always") keine Verzweigung in den Kontrollfluss einführt, sollte sie nicht aufgerufen werdenJMP
?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, dassBRA
die relative Adressierung undJMP
die absolute Adressierung verwendet werden. Also habe ich diese Entwürfe befolgt - das klingt in der Tat nicht ganz so logisch;)Antworten:
C (gcc) ,
1381 -1338 -1255 -1073 BytesRiesige Verbesserung dank Ceilingcat und Rogem.
Probieren Sie es online!
Viele Definitionen wurden in Compiler-Flags verschoben.
Erklärung (SEHR ungolfed):
quelle
00
ich habe wirklich nicht mit einer C-Lösung gerechnet :) Ihr Anhängen von Bytes könnte die Regeln allerdings ein wenig verbiegen ... Ich gebe zu, ich habe nicht versucht, diesen Code zu analysieren ... könnten Sie vielleicht Bytes speichern, die E / A in Binär statt in Hex ausführen? Wäre nach den Regeln erlaubt :)APL (Dyalog Classic) ,
397332330 Bytesdanke @ Adám für -8 Bytes
Probieren Sie es online!
quelle
f←
?C (gcc) ,
487,480,463,452,447, 438 BytesVerwendet diese Anweisungszuordnung . Das Update der Anweisungen hat 9 Byte und möglicherweise mehr in der Zukunft gespart. Gibt zurück, indem der Speicher geändert wird, auf den das erste Argument (
M
) zeigt. Vielen Dank an @ceilingcat für das Abschneiden einiger Bytes.Muss mit Flags kompiliert werden
-DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"
(bereits in Bytes enthalten).Probieren Sie es online!
Präprozessor
Diese beiden bieten eine kürzere Möglichkeit, die Zeiger zu dereferenzieren.
Reduzieren Sie die Anzahl der für if-elses und Typdeklarationen benötigten Bytes.
Code
Das Folgende ist eine für Menschen lesbare Version des Codes, wobei die Präprozessoranweisungen erweitert und die Variablen aus Gründen der Lesbarkeit umbenannt wurden.
Anleitung
Die Anweisungen sind wie folgt aufgebaut:
Die Bits 6-7 geben die Arität des Befehls an (
00
Nullary, Unary01
,10
Binary,11
Binary).Die Bits 0-2 bestimmen den oder die Operanden:
R=0
wählt ausA
undR=1
wählt ausX
.OP=00
Verwendet das Register als Operanden,OP=01
wählt den unmittelbaren Operanden aus,OP=10
wählt den absoluten Operanden aus undOP=11
wählt den indizierten Operanden aus.X
), auch wenn sie normalerweise nicht gemäß der Spezifikation verwendet werden können. Zum BeispielINC A
,ADC X, 10
undASL X
alle arbeiten.Die Bits 3-5 bestimmen die Verzweigungsbedingung: Wenn eines der Bits vorhanden ist, wird angezeigt, welches Flag geprüft werden soll (Bit 3-> C, Bit 4-> N, Bit 5-> Z). Wenn nur ein Bit gesetzt ist, prüft der Befehl auf ein gesetztes Flag. Um auf ein nicht gesetztes Flag zu testen, nehmen Sie das Komplement der Bits. Zum Beispiel
110
Tests für nicht001
gesetzten Übertrag und für gesetzten Übertrag.111
und000
immer verzweigen.Sie können auch zu einem in einem Register gespeicherten Adressoffset verzweigen, um Funktionen zu schreiben, oder Sie können die Standardindizierungsmodi verwenden.
OP=01
verhält sich wie Spezifikationszweig.quelle
JavaScript (ES6), 361 Byte
Nimmt Eingaben wie
(memory)(program_counter)
, womemory
ist einUint8Array
. Ausgabe durch Ändern dieses Arrays.NB: Der Code wird mit RegPack komprimiert und enthält viele nicht druckbare Zeichen, die alle in der obigen Darstellung der Quelle maskiert sind.
Probieren Sie es online!
Opcode-Mapping und Testfälle
Die virtuelle Maschine verwendet diese Opcode-Zuordnung .
Nachfolgend sind die übersetzten Testfälle zusammen mit den erwarteten Ausgaben aufgeführt.
Testfall Nr. 1
Erwartete Ausgabe:
Testfall Nr. 2
Erwartete Ausgabe:
Testfall Nr. 3
Erwartete Ausgabe:
Entpackt und formatiert
Da der Code mit einem Algorithmus komprimiert wird, der häufig wiederholte Zeichenfolgen durch ein einzelnes Zeichen ersetzt, ist es effizienter, dieselben Codeblöcke immer wieder zu verwenden, als Hilfsfunktionen zu definieren und aufzurufen oder Zwischenergebnisse (z. B.
M[A]
) in zusätzlichen Variablen zu speichern .quelle
o = ...
Zeile für jeden Befehl ausgeführt wird, könnte dies "unbeabsichtigte Opcodes" haben?c = M[A] >> 7 & 1
<- wird das hier&1
wirklich gebraucht?Uint8Array
kapselt tatsächlich nur eine solche Liste von Bytes. Wenn das Einfügen der Bytes in eine Hex-Zeichenfolge eine akzeptable Methode zur Darstellung der Eingabe ist, warum sollte es dann verboten sein, sie in ein Containerobjekt einzufügen ...PHP,
581 585 555532 Bytes (noch nicht konkurrierend)Verwendet PC- und OP-Codes als Basis-10-Ganzzahlen aus Befehlszeilenargumenten und
druckt den Speicher als Liste von
[base 10 address] => base 10 value
.Dies ist noch nicht gründlich getestet ; aber es gibt eine Panne .
Es gibt die Codekarte und hier eine Übersicht für mein Mapping:
Randnotiz:
Code
24
ergibt aBNV
(Zweig nie = 2 ByteNOP
);04
,08
,0C
Sind Aliasnamen fürINX
,CLC
und ,SEC
und alles , was über
3F
ist entweder ein Zwei - ByteNOP
oder ein Alias für die Single - Mode - Anweisungen.quelle