8bit virtuelle Maschine

31

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 , 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, 0basierend auf)

  • Eine Liste von Bytes mit einer maximalen Länge von 256Einträ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 hat 0
  • 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 Aund Xist 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 HLTAnweisung 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. 0ist 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 1oder)2 ).

Alle hier gezeigten Opcodes sind hexadezimal.

  • LDA - Laden Sie den Operanden in A

    • Opcodes: sofort:, 00absolut :, 02indiziert:04
    • Flags: Z,N
  • STA- AIn Operand speichern

    • Opcodes: sofort:, 08absolut :, 0aindiziert:0c
  • LDX - Laden Sie den Operanden in X

    • Opcodes: sofort:, 10absolut :, 12indiziert:14
    • Flags: Z,N
  • STX- XIn Operand speichern

    • Opcodes: sofort:, 18absolut :, 1aindiziert:1c
  • AND- bitweise und von Aund Operand inA

    • Opcodes: sofort:, 30absolut :, 32indiziert:34
    • Flags: Z,N
  • ORA- bitweise oder von Aund Operand inA

    • Opcodes: sofort:, 38absolut :, 3aindiziert:3c
    • Flags: Z,N
  • EOR- Bitweises xor (exklusive oder) von Aund Operand inA

    • Opcodes: sofort:, 40absolut :, 42indiziert:44
    • Flags: Z,N
  • LSR - logische Verschiebung nach rechts, alle Bits des Operanden um eine Stelle nach rechts verschieben, Bit 0 wird übertragen

    • Opcodes: sofort:, 48absolut :, 4aindiziert:4c
    • Flags: Z, N,C
  • ASL - Arithmetische Verschiebung nach links, Verschiebung aller Bits des Operanden um eine Stelle nach links, Bit 7 wird übertragen

    • Opcodes: sofort:, 50absolut :, 52indiziert:54
    • Flags: Z, N,C
  • 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:, 58absolut :, 5aindiziert:5c
    • Flags: Z, N,C
  • 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:, 60absolut :, 62indiziert:64
    • Flags: Z, N,C
  • ADC- Addieren mit Carry, Operand plus Carry wird hinzugefügt A, Carry wird auf Überlauf gesetzt

    • Opcodes: sofort:, 68absolut :, 6aindiziert:6c
    • Flags: Z, N,C
  • INC - Inkrementiere den Operanden um eins

    • Opcodes: sofort:, 78absolut :, 7aindiziert:7c
    • Flags: Z,N
  • DEC - Dekrementiere den Operanden um eins

    • Opcodes: sofort:, 80absolut :, 82indiziert:84
    • Flags: Z,N
  • CMP- Vergleichen Sie Amit dem Operanden, indem Sie den Operanden von subtrahierenA und das Ergebnis vergessen. Carry wird bei Unterlauf gelöscht, sonst eingestellt

    • Opcodes: sofort:, 88absolut :, 8aindiziert:8c
    • Flags: Z, N,C
  • CPX- vergleiche X- wie CMPfürX

    • Opcodes: sofort:, 90absolut :, 92indiziert:94
    • Flags: Z, N,C
  • HLT -- kündigen

    • Opcodes: implizit: c0
  • INX- Xum eins erhöhen

    • Opcodes: implizit: c8
    • Flags: Z,N
  • DEX- Abnahme Xum eins

    • Opcodes: implizit: c9
    • Flags: Z,N
  • SEC - Carry Flag setzen

    • Opcodes: implizit: d0
    • Flaggen: C
  • CLC - klare Tragefahne

    • Opcodes: implizit: d1
    • Flaggen: C
  • BRA - immer verzweigen

    • Opcodes: relativ: f2
  • BNE- Verzweigen, wenn das ZFlag gelöscht ist

    • Opcodes: relativ: f4
  • BEQ- Branch wenn ZFlag gesetzt

    • Opcodes: relativ: f6
  • BPL- Verzweigen, wenn das NFlag gelöscht ist

    • Opcodes: relativ: f8
  • BMI- Branch wenn NFlag gesetzt

    • Opcodes: relativ: fa
  • BCC- Verzweigen, wenn das CFlag gelöscht ist

    • Opcodes: relativ: fc
  • BCS- Branch wenn CFlag gesetzt

    • Opcodes: relativ: fe

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 stderrund 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 challengeund 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 gmakewenn 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 auf 0
  • -h - Eingabe erfolgt in hex (sonst binär)
  • -t - Ablaufverfolgung bis stderr
  • -c convfile - Konvertieren von Opcodes gemäß einer Zuordnung in convfile
  • -d - Ergebnisspeicher als Binärdaten sichern
  • -x - Speicher als Hex sichern
  • initial_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 :)

Felix Palmen
quelle
1
Ich würde mir vorstellen, dass es wahrscheinlich zahlreiche Möglichkeiten zum Golfen geben würde, wenn verschiedene Opcodes für die Anweisungen ausgewählt würden, aber es scheint, dass die Opcodes behoben wurden (obwohl der Befehlssatz die Maschine definiert). Vielleicht lohnt es sich zu überlegen, Implementierungen eine eigene Codepage zu erlauben?
Jonathan Allan
1
@JonathanAllan dachte zweimal darüber nach, ich erlaube es jetzt und füge möglicherweise ein "Konvertierungs" -Tool hinzu, um Lösungen mit anderen Opcode-Sätzen einfach testbar zu machen.
Felix Palmen
1
@Arnauld Übrigens war es meine Überlegung, dies zuzulassen, um die Anzahl der Sonderfälle zu verringern, daher sollte es besser "golfbar" sein - jeder Opcode ist entweder implizit, eine relative Verzweigung oder lässt alle drei anderen Adressierungsmodi zu :)
Felix Palmen
1
Wenn BRA("branch always") keine Verzweigung in den Kontrollfluss einführt, sollte sie nicht aufgerufen werden JMP?
ngn
1
@ngn gut, BRAgibt es in späteren Chip-Designs (der 6502 hat keine solche Anweisung) wie der 65C02 und der MC 68000. JMPgibt es auch. Der Unterschied besteht darin, dass BRAdie relative Adressierung und JMPdie absolute Adressierung verwendet werden. Also habe ich diese Entwürfe befolgt - das klingt in der Tat nicht ganz so logisch;)
Felix Palmen

Antworten:

16

C (gcc) , 1381 - 1338 - 1255 - 1073 Bytes

Riesige Verbesserung dank Ceilingcat und Rogem.

#include<stdio.h>
C*F="%02hhx ";m[256],p,a,x,z,n,c,e;g;*I(){R++p+m;}*A(){R*I()+m;}*X(){R*I()+m+x;}C*Q(){W(printf,m[g],1)exit(a);}C*(*L[])()={I,Q,A,Q,X,Q,Q,Q};l(C*i){R 254/p?*i=*L[m[p]&7]():*Q();}s(i){R 254/p?*L[m[p]&7]()=i:*Q();}q(){p>254?Q():++p;}C*y(){p+=e;}B(U,z)B(V,!z)B(_,n)B(BM,!n)B(BC,c)B(BS,!c)C*(*b[])()={Q,Q,y,Q,U,Q,V,Q,_,Q,BM,Q,BC,Q,BS,Q};j(){z=!l(&a);v}o(){s(a);}t(){z=!l(&x);n=x&H;}D(){s(x);}f(K(&=)h(K(|=)i(K(^=)J(E;c=e&1;z=!(e/=2);s(e);w}k(E;c=w;z=!e;s(e*=2);}T(E;g=e&1;z=!(e=e/2|H*!!c);c=g;s(e);w}M(E;g=w z=!(e=e*2|H*!!c);c=g;s(e);}N(E;z=!(a=g=a+e+!!c);c=g>>8%2;G}P(E;z=!~e;--p;s(g=e+1);G}u(E;g=e-1;z=!g;--p;s(g);G}r(E;g=a-e;z=!g;c=G}S(E;g=x-e;z=!g;c=G}Y(){z=!(x=g=1-m[p]%2*2);n=x&H;}Z(){c=~m[p]&1;}d(){p<255||Q();e=m[++p];b[m[p-1]&15]();}(*O[])()={j,o,t,D,Q,Q,f,h,i,J,k,T,M,N,Q,P,u,r,S,Q,Q,Q,Q,Q,Q,Y,Z,Q,Q,Q,d,d};main(){scanf(F,&p);W(scanf,&m[g],0)for(;;q())O[m[p]/8]();}

Probieren Sie es online!

Viele Definitionen wurden in Compiler-Flags verschoben.

Erklärung (SEHR ungolfed):

#include<stdio.h>

// useful defines
#define C unsigned char
#define H 128 // highest bit
#define R return

// code generator for I/O
#define W(o,p,q)for(g=-1;++g<256&&((q)||!feof(stdin));)(o)(F,(p));

// code generator for branching instruction handlers
#define BB(q)(){(q)||Y();}

// frequent pieces of code
#define NA n=a&H;
#define NE n=e&H;
#define NG n=g&H;
#define E l(&e)

// printf/scanf template
C * F = "%02hhx ";

// global state: m=memory, pax=registers, znc=flags
// e and g are for temporaries and type conversions
C m[256],p,a,x,z,n,c,e;g;

// get the pointer to a memory location:
C * I() {R &m[++p];} // immediate
C * A() {R &m[m[++p]];} // absolute
C * X() {R &m[m[++p]+x];} // indexed

// terminate the VM (and dump memory contents)
C * Q() { W(printf,m[g],1) exit(a);}

// an array of functions for accessing the memory
// They either return the pointer to memory location
// or terminate the program.
C * (*L[])()={I,Q,A,Q,X,Q,Q,Q};

// load a byte from the memory into the variable pointed by i
// terminate the program if we cannot access the address byte
l (C * i) {return 254 / p ? *i = *L[m[p]&7] () : *Q ();}

// save a byte i to the memory
// terminate the program if we cannot access the address byte
s (C i) {return 254 / p ? *L[m[p]&7]() = i : *Q ();}

// advance the instruction pointer (or fail if we fall outside the memory)
q () {p > 254 ? Q () : ++p;}

// branch
C * Y() {p += e;}

// generated functions for conditional branches
C * BN BB(z)
C * BZ BB(!z)
C * BP BB(n)
C * BM BB(!n)
C * BC BB(c)
C * BS BB(!c)

// a list of branch functions
C * (*B[])() = {Q,Q,Y,Q,BN,Q,BZ,Q,BP,Q,BM,Q,BC,Q,BS,Q};

// Instruction handling functions

OA () {z = !l (&a); NA} // lda
OB () {s (a);} // sta
OC () {z = !l (&x); n = x & H;} // ldx
OD () {s (x);} // stx
OG () {E; z = !(a &= e); NA} // and
OH () {E; z = !(a |= e); NA} // ora
OI () {E; z = !(a ^= e); NA} // eor
OJ () {E; c = e & 1; z = !(e /= 2); s (e); NE} // lsr
OK () {E; c = NE; z = !e; s (e *= 2);} // asl
OL () {E; g = e & 1; z = !(e = e / 2 | H * !!c); c = g; s (e); NE} // ror
OM () {E; g = e & H; z = !(e = e * 2 | H * !!c); c = g; s (e); NE} // rol
ON () {E; z = !(a = g = a + e + !!c); c = !!(g & 256); NG} // adc
OP () {E; z = !~e; --p; s (g = e + 1); NG} // inc
OQ () {E; g = e - 1; z = !g; --p; s (g); NG} // dec
OR () {E; g = a - e; z = !g; c = NG} // cmp
OS () {E; g = x - e; z = !g; c = NG} // cpx
OY () {z = !(x = g = ~m[p] & 1 * 2 - 1); n = x & H;} // inx/dex
OZ () {c = ~m[p] & 1;} // sec/clc
Od () {p < 255 || Q (); e = m[++p]; B[m[p-1]&15] ();} // branching

// list of opcode handlers
(*O[]) () = {OA,OB,OC,OD,Q,Q,OG,OH,OI,OJ,OK,OL,OM,ON,Q,OP,OQ,OR,OS,Q,Q,Q,Q,Q,Q,OY,OZ,Q,Q,Q,Od,Od};

// main function
main ()
{
    // read the instruction pointer
    scanf (F, &p);

    // read memory contents
    W(scanf, &m[g], 0)

    // repeatedly invoke instruction handlers until we eventually terminate
    for (;; q())
        O[m[p]/8] ();
}
Max Yekhlakov
quelle
Tolle Arbeit, sofort +1, 00ich 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 :)
Felix Palmen
Ich möchte die Regel, dass illegale Opcodes zur Kündigung führen, ersetzen, indem ich sage, dass das Verhalten von illegalen Opcodes undefiniert ist. Würde dies Ihrer Antwort schaden oder geht es Ihnen gut damit?
Felix Palmen
@FelixPalmen Nun, solange die Kündigung ein ziemlich gültiges "undefiniertes" Verhalten ist, wird es nicht schaden (es eröffnet eine neue Möglichkeit, es stattdessen abzuspielen!)
abzuspielen
@MaxYekhlakov von "verletzt" Ich habe gemeint, Ihrer Lösung gegenüber unfair zu sein, weil Sie vielleicht "Bytes ausgegeben" haben, um sicherzustellen, dass ein illegaler Opcode das VM beendet. Ich bin froh, dass Sie die Regeländerung als Chance begrüßen :) Und noch einmal, herzlichen Glückwunsch, ich freue mich sehr über eine Lösung in C, meiner Lieblingsprogrammiersprache aller Zeiten. Es ist schade, dass Sie selten eine Code-Golf-Herausforderung in C gewinnen werden - trotzdem ist "golfed" C einfach cool :)
Felix Palmen
Könntest du das Flaggenteil zum Posten hinzufügen?
14 m²,
8

APL (Dyalog Classic) , 397 332 330 Bytes

danke @ Adám für -8 Bytes

f←{q2a x c←≡B256⋄{0::m
⍺←(∇q∘←)0∘=,B≤+⍨
30u←⌊8÷⍨bpm:∇p+←129-B|127-1pm×⊃b2/(,~,⍪)1,q,c
p+←1
u=25:⍺x⊢←B|x1*b
u=26:∇c⊢←~2|b
p+←≢om⊃⍨i←⍎'p⊃m+x'↑⍨1+8|b
u⊃(,⍉'⍺ax⊢←o' '∇m[i]←ax'∘.~'xa'),5 4 2 3 2/'⍺⌽⊃'∘,¨'a⊢←2⊥(⍕⊃u⌽''∧∨≠'')/o a⊤⍨8⍴2' 'c(i⊃m)←u⌽d⊤(⌽d←u⌽2B)⊥u⌽o,c×u>10' 'c a⊢←2B⊤a+o+c' 'm[i]←B|o-¯1*u' 'c⊢←⊃2B⊤o-⍨⊃u⌽x a'}p m←⍵}

Probieren Sie es online!

p  program counter
m  memory
a  accumulator register
x  index register
q  flags z (zero) and n (negative) as a length-2 vector
c  flag for carry
  function to update z and n
b  current instruction
u  highest 5 bits of b
o  operand
i  target address in memory
ngn
quelle
Hat diese Lösung unbeabsichtigte Opcodes und wenn nicht, haben Sie Bytes ausgegeben, um sie zu vermeiden? Siehe diesen Kommentar für den Grund, den ich frage ...
Felix Palmen
@FelixPalmen Nun, da Sie es erwähnen, ja :( Anfangs habe ich diese Regel beachtet, aber als ich Golf gespielt habe, habe ich versehentlich 4, 5 und möglicherweise andere gültige Opcodes gemacht. Daher wäre eine Entscheidung, ihr Verhalten undefiniert zu machen, sehr willkommen :)
ngn
2
Jetzt ist mir klar, dass es nicht die beste Entscheidung war und @MaxYekhlakov musste leider nichts über die Regeländerung sagen.
Felix Palmen
Sie braucht f←?
Erik der Outgolfer
8

C (gcc) , 487 , 480 , 463 , 452 , 447 , 438 Bytes

Verwendet 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).

e(M,Z,C)u*M,C;{for(u r[2],S=0,D,O,c,t;o=C<Z?M+C++:0;){I(c=O,d=r+!(c&4),break)I(o=c&3?C<Z&&C?M+C++:0:d,o=c&2?O+c%2**r+M:o,break)t=(c/=8)&7;I(c<24&c>4&&t,t&=3;I(c&8,I(c&4,c&=S&1;S=O>>7*!(t/=2);O=t=O<<!t>>t|c<<7*t,t=O+=t%2*2-1),I(c&4,D=t=t?t&2?t&1?O^D:O|D:O&D:O,I(c&1,S=D>(t=D+=O+S%2),t=D-O;S=t>D)))S=S&1|t>>6&2|4*!t,I(c&8,C+=!(t&~-t?~t&S:t&~S)*O,I(t,S=S&6|c%2,O=D)))I(C,,Z=0)}}

Probieren Sie es online!

Präprozessor

-DO=*o -DD=*d

Diese beiden bieten eine kürzere Möglichkeit, die Zeiger zu dereferenzieren.

-DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"

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.

exec_8bit(unsigned char *ram, int ramSize, unsigned char PC)
{  
  for(unsigned char reg[2], SR=0, // The registers. 
                                  // reg[0] is X, reg[1] is A. 
                                  // SR contains the flags.
      *dst, *op, opCode, tmp;
      // Load the next instruction as long as we haven't gone out of ram.
      op = PC < ramSize ? ram + PC++ : 0;)
  { // Check for HLT.
    if(opCode=*op)
    { // Take a pointer to the register selected by complement of bit 3.
      dst = reg+!(opCode&4);
    } else break;
    // Load operand as indicated by bits 0 and 1. Also check that we don't
    // go out of bounds and that the PC doesn't overflow.
    if(op = opCode&3 ? PC<ramSize && PC ? ram + PC++ : 0 : dst)
    {
      op = opCode&2 ? *op + opCode%2 * *reg + ram: op
    } else break;

    // Store the bits 3-5 in tmp.
    tmp = (opCode/=8) & 7;
    if(opCode<24 & opCode>4 && tmp)
    { // If not HLT, CLC, SEC or ST, enter this block.
      tmp &= 3; // We only care about bits 3&4 here.
      if(opCode&8) // Determine whether the operation is binary or unary.
      { // Unary
        if(opCode&4)
        { // Bitshift
          opCode &= SR&1; // Extract carry flag and AND it with bit 3 in opCode.
          SR=*op >> 7*!(tmp/=2);// Update carry flag.
          // Shift to left if bit 4 unset, to right if set. Inclusive-OR 
          // the carry/bit 3 onto the correct end.
          *op = tmp = *op << !tmp >> tmp | opCode << 7*tmp;
        } else tmp=*o+=tmp%2*2-1;
      } else if(opCode&4) {
        // Bitwise operations and LD.
        // Ternary conditional to determine which operation.
        *dst = tmp = tmp? tmp&2? tmp&1? *op^*dst: *op|*dst: *op&*dst: *op
      } else if(opCode&1) {
        // ADC. Abuse precedence to do this in fewer bytes.
        // Updates carry flag.
        SR = *dst > (tmp = *dst += *op + SR%2);
      } else tmp=*dst-*op; SR = tmp > *dst; // Comparison.
      SR = SR&1 | tmp >> 6&2 | 4*!tmp; // Update Z and N flags, leaving C as it is.
    } else if(opCode&8) {
      // Branch.
      // tmp&~-tmp returns a truthy value when tmp has more than one bit set
      // We use this to detect the "unset" and "always" conditions.
      // Then, we bitwise-AND either ~tmp&SR or tmp&~SR to get a falsy value
      // when the condition is fulfilled. Finally, we take logical complement,
      // and multiply the resulting value (`1` or `0`) with the operand,
      // and add the result to program counter to perform the jump.
      PC += !(tmp & ~-tmp? ~tmp&SR : tmp&~SR) * *op;
    } else if (tmp) { // SEC, CLC
      SR = SR&6 | opCode % 2;
    } else {
      *op = *dst; // ST
    }
    if(!PC){ // If program counter looped around, null out ramSize to stop.
           // There's likely a bug here that will kill the program when it
           // branches back to address 0x00
      ramSize=0;
    }
  }
}

Anleitung

Die Anweisungen sind wie folgt aufgebaut:

  • Die Bits 6-7 geben die Arität des Befehls an ( 00Nullary, Unary 01, 10Binary, 11Binary).

  • Die Bits 0-2 bestimmen den oder die Operanden: R=0wählt aus Aund R=1wählt aus X. OP=00Verwendet das Register als Operanden, OP=01wählt den unmittelbaren Operanden aus, OP=10wählt den absoluten Operanden aus und OP=11wählt den indizierten Operanden aus.

    • Wie Sie vielleicht bemerkt haben, können auf diese Weise Vorgänge an beiden Registern ausgeführt werden (obwohl Sie immer noch nur von diesen indexieren können X), auch wenn sie normalerweise nicht gemäß der Spezifikation verwendet werden können. Zum Beispiel INC A, ADC X, 10und ASL Xalle 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 110Tests für nicht 001gesetzten Übertrag und für gesetzten Übertrag. 111und 000immer 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=01verhält sich wie Spezifikationszweig.

+-----+----------+-------+-----------------------------------------------+
| OP  | BINARY   | FLAGS | INFO                                          |
+-----+----------+-------+-----------------------------------------------+
| ST  | 10000ROP |       | Register -> Operand                           |
| LD  | 10100ROP | Z N   | Operand -> Register                           |
| AND | 10101ROP | Z N   | Register &= Operand                           |
| XOR | 10111ROP | Z N   | Register ^= Operand                           |
| IOR | 10110ROP | Z N   | Register |= Operand                           |
| ADC | 10011ROP | Z N C | Register += Operand + Carry                   |
| INC | 01011ROP | Z N   | Operand += 1                                  |
| DEC | 01010ROP | Z N   | Operand -= 1                                  |
| ASL | 01100ROP | Z N C | Operand <<= 1                                 |
| LSR | 01110ROP | Z N C | Operand >>= 1                                 |
| ROL | 01101ROP | Z N C | Operand = Operand << 1 | Carry                |
| ROR | 01111ROP | Z N C | Operand = Operand >> 1 | Carry << 7           |
| CMP | 10010ROP | Z N C | Update ZNC based on Register - Operand        |
| BR  | 11CNDROP |       | PC += Condition ? Operand : 0      |
| SEC | 00011000 |     C | Set carry                                     |
| CLC | 00010000 |     C | Clear carry                                   |
| HLT | 00000000 |       | Halt execution.                               |
+-----+----------+-------+-----------------------------------------------+

quelle
7

JavaScript (ES6), 361 Byte

Nimmt Eingaben wie (memory)(program_counter), wo memoryist ein Uint8Array. Ausgabe durch Ändern dieses Arrays.

M=>p=>{for(_='M[\u0011\u0011A]\u0010\u0010=\u000fc=\u000e,\u0011p]\f(n=\u000b128)\t=\u0010\b&=255\u0007,z=!(n\u0007),n&=\t;\u0006\u0006\u000b\u0005-\u0010,\u000en>=0\u0005\u0004\u0011c\b>>7,A]*2\u0005\u0003\u0011c\b&1,A]/2\u0005\u000f\u0002&&(p+=(\u0010^\t-\t;\u0001for(a=n=z=\u000ex=0;a\u0007,x\u0007,A=[i=\u0011p++],p\f\f+x][i&3],i&3&&p++,i&&A<256;)eval(`\u000ba\b\u0006\u000fa;\u000bx\b\u0006\u000fx;\u000ba&\b\u0005a|\b\u0005a^\b\u0005\u000f\u0002\u0003\u000fc*128|\u0002c|\u0003a+\b+c,\u000ea>>8\u0005++\u0010\u0005--\u0010\u0005a\u0004x\u0004++x\u0005--x\u0006\u000e1;\u000e0;1\u0001!z\u0001z\u0001!n\u0001n\u0001!c\u0001c\u0001`.split`;`[i>>2])';G=/[\u0001-\u0011]/.exec(_);)with(_.split(G))_=join(shift());eval(_)}

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

00 - LDX #$10  09 10
02 - INC $01   32 01
04 - DEX       44
05 - BNE $fb   55 fb

Erwartete Ausgabe:

09 20 32 01 44 55 fb

Testfall Nr. 2

00 - (DATA)    e0 08 2a 02
04 - LDA $00   02 00
06 - ADC $02   2e 02
08 - STA $00   06 00
0a - LDA $01   02 01
0c - ADC $03   2e 03
0e - STA $01   06 01

Erwartete Ausgabe:

0a 0b 2a 02 02 00 2e 02 06 00 02 01 2e 03 06 01

Testfall Nr. 3

00 - (DATA)    5e 01 28 00
04 - LDX #$10  09 10
06 - LSR $01   1e 01
08 - ROR $00   26 00
0a - BCC $0d   65 0d
0c - LDA $02   02 02
0e - CLC       4c
0f - ADC $21   2e 21
11 - STA $21   06 21
13 - LDA $03   02 03
15 - ADC $22   2e 22
17 - STA $22   06 22
19 - ASL $02   22 02
1b - ROL $03   2a 03
1d - DEX       44
1e - BPL $e6   5d e6
20 - HLT       00
21 - (DATA)    00 00

Erwartete Ausgabe:

00 00 00 00 09 10 1e 01  44 5d e6 00 b0 36

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 .

M => p => {
  for(
    a = n = z = c = x = 0;
    a &= 255, x &= 255,
    A = [i = M[p++], p, M[p], M[p] + x][i & 3],
    i & 3 && p++,
    i && A < 256;
  ) eval((
    '(n = a = M[A], z = !(n &= 255), n &= 128);'                                + // LDA
    'M[A] = a;'                                                                 + // STA
    '(n = x = M[A], z = !(n &= 255), n &= 128);'                                + // LDX
    'M[A] = x;'                                                                 + // STX
    '(n = a &= M[A], z = !(n &= 255), n &= 128);'                               + // AND
    '(n = a |= M[A], z = !(n &= 255), n &= 128);'                               + // ORA
    '(n = a ^= M[A], z = !(n &= 255), n &= 128);'                               + // EOR
    '(n = M[A] = M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);'           + // LSR
    '(n = M[A] = M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'          + // ASL
    '(n = M[A] = c * 128 | M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);' + // ROR
    '(n = M[A] = c | M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'      + // ROL
    '(n = a += M[A] + c, c = a >> 8, z = !(n &= 255), n &= 128);'               + // ADC
    '(n = ++M[A], z = !(n &= 255), n &= 128);'                                  + // INC
    '(n = --M[A], z = !(n &= 255), n &= 128);'                                  + // DEC
    '(n = a - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CMP
    '(n = x - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CPX
    '(n = ++x, z = !(n &= 255), n &= 128);'                                     + // INX
    '(n = --x, z = !(n &= 255), n &= 128);'                                     + // DEX
    'c = 1;'                                                                    + // SEC
    'c = 0;'                                                                    + // CLC
    ' 1 && (p += (M[A] ^ 128) - 128);'                                          + // BRA
    '!z && (p += (M[A] ^ 128) - 128);'                                          + // BNE
    ' z && (p += (M[A] ^ 128) - 128);'                                          + // BEQ
    '!n && (p += (M[A] ^ 128) - 128);'                                          + // BPL
    ' n && (p += (M[A] ^ 128) - 128);'                                          + // BMI
    '!c && (p += (M[A] ^ 128) - 128);'                                          + // BCC
    ' c && (p += (M[A] ^ 128) - 128);')                                           // BCS
    .split`;`[i >> 2]
  )
}
Arnauld
quelle
Beeindruckend :) Kein JS-Profi, also - wird diese Indizierung durch den Wert des Opcodes in ein "Code-Array" unterteilt? sieht nett aus. Aber wenn diese o = ...Zeile für jeden Befehl ausgeführt wird, könnte dies "unbeabsichtigte Opcodes" haben?
Felix Palmen
2
Ich sollte wahrscheinlich einen Testfall hinzufügen: o Nun, ich denke, es wäre besser gewesen, unbeabsichtigte Opcodes zuzulassen ... Gültigkeitsprüfungen verschwenden hier nur Bytes, aber wahrscheinlich zu spät, um die Regeln zu ändern :(
Felix Palmen
Nun, ich wollte genau das vorschlagen, da dies nicht viel zur Herausforderung beiträgt und es jetzt schwierig ist, es mit benutzerdefinierten Zuordnungen zu überprüfen. Aber Sie sollten wahrscheinlich zuerst @MaxYekhlakov fragen / warnen, da sie die Regel möglicherweise korrekt implementiert haben.
Arnauld
c = M[A] >> 7 & 1<- wird das hier &1wirklich gebraucht?
Felix Palmen
2
Ich bin mir ziemlich sicher, dass dies der Fall ist, da Ihre Eingabe sowieso eine Funktion ist. Mein Wortlaut war "eine Liste von Bytes [...] in jedem vernünftigen Format" und Uint8Arraykapselt 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 ...
Felix Palmen,
2

PHP, 581 585 555 532 Bytes (noch nicht konkurrierend)

function t($v){global$f,$c;$f=8*$c|4&$v>>5|2*!$v;}$m=array_slice($argv,2);for($f=0;$p>-1&&$p<$argc-1&&$i=$m[$p=&$argv[1]];$p++)$i&3?eval(explode(_,'$a=$y_$a&=$y_$a|=$y_$a^=$y_$a+=$y+$c;$c=$a>>8_$x=$y_$c=$y&1;$y>>=1_$c=($y*=2)>>8_$y+=$y+$c;$c=$y>>8_$y+=$c<<8;$c=$y&1;$y>>=1_$y--_$y++_$z=$a-$y,$c=$a<$y_$z=$x-$y,$c=$x<$y_$y=$a_$y=$x_'.$y=&$m[[0,++$p,$g=$m[$p],$g+$x][$i&3]])[$i>>=2].'$i<14&&t(${[aaaaaxyyyyyyzz][$i]}&=255);'):($i&32?$p+=($f>>$i/8-4^$i)&1?($y=$m[++$p])-($y>>7<<8):1:($i&8?$f=$f&7|8*$c=$i/4:t($x+=$i/2-9)));print_r($m);

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:

3-mode instructions:
00: LDA     04: AND     08: ORA     0C: EOR
10: ADC     14: LDX     18: LSR     1C: ASL
20: ROL     24: ROR     28: DEC     2C: INC
30: CMP     34: CPX     38: STA     3C: STX

+1: immediate
+2: absolute
+3: relative

implicit:
00: HLT
10: DEX 14: INX
18: CLC 1C: SEC

relative:
20: BRA         (0)
28: BNE 2C: BEQ (Z)
30: BPL 34: BMI (N)
38: BCC 3C: BCS (C)

Randnotiz:
Code 24ergibt a BNV(Zweig nie = 2 Byte NOP);
04, 08, 0CSind Aliasnamen für INX, CLCund , SEC
und alles , was über 3Fist entweder ein Zwei - Byte NOPoder ein Alias für die Single - Mode - Anweisungen.

Titus
quelle