Macchina virtuale a 8 bit


31

sfondo

Mi piace il mio vecchio chip 6502 a 8 bit. È anche divertente risolvere alcune delle sfide qui su PPCG nel codice macchina 6502. Ma alcune cose che dovrebbero essere semplici (come, leggere in dati o in output su stdout) sono inutilmente ingombranti da fare nel codice macchina. Quindi ho un'idea approssimativa nella mia mente: inventare la mia macchina virtuale a 8 bit ispirata al 6502, ma con il design modificato per essere più utilizzabile per le sfide. Iniziando a implementare qualcosa, mi sono reso conto che questa potrebbe essere una bella sfida se il design della VM fosse ridotto al minimo :)

Compito

Implementare una macchina virtuale a 8 bit conforme alle seguenti specifiche. Questo è , quindi vince l'implementazione con il minor numero di byte.

Ingresso

L'implementazione dovrebbe assumere i seguenti input:

  • Un singolo byte senza segno pc, questo è il contatore del programma iniziale (l'indirizzo in memoria in cui la VM inizia l'esecuzione, 0basato su)

  • Un elenco di byte con una lunghezza massima di 256voci, questa è la RAM per la macchina virtuale (con i suoi contenuti iniziali)

Puoi prendere questo input in qualsiasi formato ragionevole.

Produzione

Un elenco di byte che rappresentano il contenuto finale della RAM al termine della VM (vedere di seguito). Puoi presumere che alla fine ricevi solo input che portano alla conclusione. È consentito qualsiasi formato sensibile.

CPU virtuale

La CPU virtuale ha

  • un contatore di programmi a 8 bit,
  • un registro accumulatore a 8 bit chiamato Ae
  • un registro indice a 8 bit chiamato X.

Esistono tre flag di stato:

  • Z - il flag zero viene impostato dopo il risultato di alcune operazioni 0
  • N - il flag negativo viene impostato dopo che alcune operazioni generano un numero negativo (viene impostato il bit 7 del risultato)
  • C - il flag carry è impostato da aggiunte e turni per il bit "mancante" del risultato

All'avvio, i flag vengono tutti cancellati, il contatore del programma è impostato su un determinato valore e il contenuto di Ae Xsono indeterminati.

I valori a 8 bit rappresentano entrambi

  • un numero intero senza segno nell'intervallo[0..255]
  • una firma intero, complemento a 2, nella gamma[-128..127]

a seconda del contesto. Se un'operazione trabocca o trabocca, il valore si avvolge (e in caso di aggiunta, il flag carry è interessato).

fine

La macchina virtuale termina quando

  • È stata HLTraggiunta un'istruzione
  • Si accede a un indirizzo di memoria inesistente
  • Il contatore del programma viene eseguito al di fuori della memoria (si noti che non si avvolge anche se alla VM vengono assegnati 256 byte completi di memoria)

Modalità di indirizzo

  • implicito : l'istruzione non ha argomenti, l'operando è implicito
  • immediato : l'operando è il byte direttamente dopo l'istruzione
  • relativo - (solo per la diramazione) il byte dopo la firma dell'istruzione (complemento a 2) e determina l'offset da aggiungere al contatore del programma se viene presa la diramazione. 0è la posizione delle seguenti istruzioni
  • assoluto : il byte dopo l'istruzione è l'indirizzo dell'operando
  • indicizzato - il byte dopo l'istruzione plus X(il registro) è l'indirizzo dell'operando

Istruzioni

Ogni istruzione è composta da un opcode (un byte) e, nelle modalità di indirizzamento immediato , relativo , assoluto e indicizzato un secondo byte argomento. Quando la CPU virtuale esegue un'istruzione, incrementa il contatore del programma di conseguenza (di 1o 2).

Tutti i codici operativi mostrati qui sono in esadecimale.

  • LDA - carica l'operando in A

    • Codici operativi: immediato:, 00assoluto :, 02indicizzato:04
    • Flags: Z,N
  • STA- memorizza Ain operando

    • Codici operativi: immediato:, 08assoluto :, 0aindicizzato:0c
  • LDX - carica l'operando in X

    • Codici operativi: immediato:, 10assoluto :, 12indicizzato:14
    • Flags: Z,N
  • STX- memorizza Xin operando

    • Codici operativi: immediato:, 18assoluto :, 1aindicizzato:1c
  • AND- bit per bit e di Ae operando inA

    • Codici operativi: immediato:, 30assoluto :, 32indicizzato:34
    • Flags: Z,N
  • ORA- bit per bit o di Ae operando inA

    • Codici operativi: immediato:, 38assoluto :, 3aindicizzato:3c
    • Flags: Z,N
  • EOR- bit per bit XOR (OR esclusivo) di Ae operando inA

    • Codici operativi: immediato:, 40assoluto :, 42indicizzato:44
    • Flags: Z,N
  • LSR - spostamento logico a destra, sposta tutti i bit dell'operando di un posto a destra, il bit 0 va a portare

    • Codici operativi: immediato:, 48assoluto :, 4aindicizzato:4c
    • Bandiere: Z, N,C
  • ASL - spostamento aritmetico a sinistra, sposta tutti i bit dell'operando di un posto a sinistra, il bit 7 va a trasportare

    • Codici operativi: immediato:, 50assoluto :, 52indicizzato:54
    • Bandiere: Z, N,C
  • ROR - ruota a destra, sposta tutti i bit dell'operando di un posto a destra, il carry passa al bit 7, il bit 0 va al carry

    • Codici operativi: immediato:, 58assoluto :, 5aindicizzato:5c
    • Bandiere: Z, N,C
  • ROL - ruota a sinistra, sposta tutti i bit dell'operando di un posto a sinistra, carry passa a bit 0, bit 7 va a carry

    • Codici operativi: immediato:, 60assoluto :, 62indicizzato:64
    • Bandiere: Z, N,C
  • ADC- aggiungi con carry, operando più carry viene aggiunto A, carry viene impostato su overflow

    • Codici operativi: immediato:, 68assoluto :, 6aindicizzato:6c
    • Bandiere: Z, N,C
  • INC - incrementa l'operando di uno

    • Codici operativi: immediato:, 78assoluto :, 7aindicizzato:7c
    • Flags: Z,N
  • DEC - decrementa l'operando di uno

    • Codici operativi: immediato:, 80assoluto :, 82indicizzato:84
    • Flags: Z,N
  • CMP- confronta Acon l'operando sottraendo l'operando da A, dimentica il risultato. Il trasporto viene cancellato su underflow, impostato diversamente

    • Codici operativi: immediato:, 88assoluto :, 8aindicizzato:8c
    • Bandiere: Z, N,C
  • CPX- confronta X- come CMPperX

    • Codici operativi: immediato:, 90assoluto :, 92indicizzato:94
    • Bandiere: Z, N,C
  • HLT -- terminare

    • Codici operativi: implicito: c0
  • INX- incremento Xdi uno

    • Codici operativi: implicito: c8
    • Flags: Z,N
  • DEX- decrementa Xdi uno

    • Codici operativi: implicito: c9
    • Flags: Z,N
  • SEC - set carry flag

    • Codici operativi: implicito: d0
    • Flags: C
  • CLC - cancella bandiera di trasporto

    • Codici operativi: implicito: d1
    • Flags: C
  • BRA - ramo sempre

    • Codici operativi: relativi: f2
  • BNE- ramo se Zbandiera cancellata

    • Codici operativi: relativi: f4
  • BEQ- ramo se Zflag impostato

    • Codici operativi: relativi: f6
  • BPL- ramo se Nbandiera cancellata

    • Codici operativi: relativi: f8
  • BMI- ramo se Nflag impostato

    • Codici operativi: relativi: fa
  • BCC- ramo se Cbandiera cancellata

    • Codici operativi: relativi: fc
  • BCS- ramo se Cflag impostato

    • Codici operativi: relativi: fe

opcodes

Il comportamento della VM non è definito se viene trovato un codice operativo che non è associato a un'istruzione valida dall'elenco precedente.

Secondo la richiesta di Jonathan Allan , è possibile scegliere il proprio set di codici operativi anziché i codici operativi mostrati nella sezione Istruzioni . In tal caso, è necessario aggiungere una mappatura completa agli opcode utilizzati sopra nella risposta.

La mappatura dovrebbe essere un file esadecimale con coppie <official opcode> <your opcode>, ad esempio se hai sostituito due codici operativi:

f4 f5
10 11

Le newline non contano qui.

Casi di test (codici operativi ufficiali)

// 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

Potrei aggiungere altri test più tardi.

Riferimenti e prove

Per aiutare con i propri esperimenti, ecco alcune implementazioni di riferimento (totalmente non giocate a golf) : può generare informazioni di tracciamento (comprese istruzioni disassemblate) stderre convertire codici op durante l'esecuzione.

Modo consigliato per ottenere la fonte:

git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules

Oppure challengefai il checkout della filiale ed esegui una git submodule update --init --recursiveclonazione successiva per ottenere il mio sistema di generazione personalizzato.

Costruisci lo strumento con GNU make (basta digitare make, o gmakese sul tuo sistema, il make predefinito non è GNU make).

Utilizzo :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc - il contatore del programma iniziale, il valore predefinito è 0
  • -h - input è in esadecimale (altrimenti binario)
  • -t - traccia l'esecuzione a stderr
  • -c convfile - convertire i codici operativi secondo una mappatura fornita in convfile
  • -d - dump della memoria risultante come dati binari
  • -x - scarica la memoria risultante come esadecimale
  • initial_ram - il contenuto iniziale della RAM, esadecimale o binario

Nota che la funzione di conversione fallirà sui programmi che modificano i codici operativi durante l'esecuzione.

Dichiarazione di non responsabilità: le regole e le specifiche sopra riportate sono autorevoli per la sfida, non per questo strumento. Ciò vale soprattutto per la funzione di conversione del codice operativo. Se ritieni che lo strumento presentato qui contenga un bug relativo alle specifiche, ti preghiamo di segnalarlo in un commento :)


1
Immagino che probabilmente ci sarebbero numerose opportunità di golf da scegliere scegliendo diversi codici operativi per le istruzioni, ma sembra che i codici operativi siano stati corretti (anche se il set di istruzioni è ciò che definisce la macchina). Forse vale la pena considerare la possibilità di consentire alle implementazioni di avere una propria tabella codici?
Jonathan Allan,

1
@JonathanAllan ci ha pensato due volte, lo sto concedendo ora e potrei aggiungere uno strumento di "conversione" per rendere facilmente testabili le soluzioni utilizzando altri set di codici operativi.
Felix Palmen,

1
@Arnauld tra il mio ragionamento per consentire questo era di ridurre la quantità di casi speciali, quindi dovrebbe essere meglio "golfabile" - ogni codice operativo è implicito, un ramo relativo o consente tutte e tre le altre modalità di indirizzamento :)
Felix Palmen

1
se BRA("ramo sempre") non introduce un ramo nel flusso di controllo, non dovrebbe essere chiamato JMP?
ngn,

1
@ngn bene, BRAesiste nei successivi progetti di chip (il 6502 non ha tale istruzione) come il 65C02 e l'MC 68000. JMPesiste anche. La differenza è che BRAutilizza l'indirizzamento relativo e JMPutilizza l'indirizzamento assoluto. Quindi, ho appena seguito questi progetti - in effetti, non sembra così logico;)
Felix Palmen,

Risposte:


16

C (gcc) , 1381 1338 1255 1073 byte

Enorme miglioramento grazie a ceilingcat e 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]();}

Provalo online!

Molte definizioni sono state spostate nei flag del compilatore.

Spiegazione (MOLTO 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] ();
}

Ottimo lavoro, istantaneo +1, non mi aspettavo davvero una soluzione C prima :) la tua aggiunta di 00byte potrebbe un po 'piegare le regole però ... Ammetto di non aver provato ad analizzare questo codice ... potresti forse salvare byte facendo I / O in binario anziché esadecimale? Sarebbe permesso dalle regole :)
Felix Palmen,

Vorrei sostituire la regola secondo cui i codici operativi illegali portano alla risoluzione semplicemente dicendo che il comportamento dei codici operativi illegali è indefinito ... ciò danneggerebbe la tua risposta o ti va bene?
Felix Palmen,

@FelixPalmen bene, fintanto che la terminazione è un comportamento "indefinito" abbastanza valido, non farà male (apre invece una nuova possibilità per giocare a golf!)
Max Yekhlakov,

@MaxYekhlakov per "male" volevo dire essere ingiusto nei confronti della tua soluzione perché forse hai "speso byte" per assicurarti che un codice operativo illegale termini la VM. Sono contento che tu accolga favorevolmente il cambio di regola come un'opportunità :) E ancora, complimenti, adoro vedere una soluzione in C, che è il mio linguaggio di programmazione preferito di tutti i tempi. È un peccato che raramente vincerai una sfida di code-golf in C - tuttavia, il "golfing" C è semplicemente fantastico :)
Felix Palmen,

Potresti aggiungere la parte delle bandiere da pubblicare?
l4m2

8

APL (Dyalog Classic) , 397 332 330 byte

grazie @ Adám per -8 byte

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←⍵}

Provalo 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


Questa soluzione ha codici operativi non intenzionali e, in caso contrario, hai speso byte per evitarli? Vedi questo commento per il motivo che sto chiedendo ...
Felix Palmen,

@FelixPalmen Ora che me lo dici, sì :( Inizialmente ho osservato quella regola, ma mentre stavo giocando a golf ho accidentalmente fatto 4, 5, e forse altri, codici operativi validi. Quindi una decisione di rendere indefinito il loro comportamento sarebbe molto gradita :)
ngn,

2
fatto ora, mi rendo conto che non è stata la decisione migliore in primo luogo e @MaxYekhlakov purtroppo non ha dovuto dire nulla sul cambio di regola.
Felix Palmen,

Ti serve f←?
Erik the Outgolfer,

8

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

Utilizza questa mappatura delle istruzioni . L'aggiornamento alle istruzioni si è rasato di 9 byte e potenzialmente più in futuro. Restituisce modificando la memoria indicata dal primo argomento ( M). Grazie a @ceilingcat per aver rasato alcuni byte.

Deve essere compilato con flag -DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(già incluso nei byte).

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)}}

Provalo online!

preprocessore

-DO=*o -DD=*d

Questi due forniscono un modo più breve per dereferenziare i puntatori.

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

Ridurre il numero di byte necessari per if-elses e dichiarazioni di tipo.

Codice

Di seguito è una versione del codice leggibile dall'uomo, con le direttive del preprocessore espanse e le variabili rinominate per essere leggibili.

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;
    }
  }
}

Istruzioni

Le istruzioni sono strutturate come segue:

  • I bit 6-7 indicano l'arità dell'istruzione ( 00Nullary, Unary 01, 10Binary, 11Binary)

  • I bit 0-2 determinano l'operando (i): R=0seleziona Ae R=1seleziona X. OP=00utilizza il registro come operando, OP=01seleziona l'operando immediato, OP=10seleziona l'operando assoluto e OP=11seleziona l'operando indicizzato.

    • Come avrai notato, ciò consente di eseguire qualsiasi operazione su entrambi i registri (anche se puoi ancora indicizzare da X) anche quando normalmente non possono essere utilizzati per specifica. Ad esempio INC A, ADC X, 10e ASL Xtutto funziona.
  • I bit 3-5 determinano la condizione per la ramificazione: avere uno dei bit indica quale flag testare (bit 3-> C, bit 4-> N, bit 5-> Z). Se è impostato solo un bit, l'istruzione verifica un flag impostato. Per verificare la presenza di un flag non impostato, prendere il complemento dei bit. Ad esempio 110test per carry non impostato e 001set carry. 111e 000ramo sempre.

  • È inoltre possibile diramare verso un offset di indirizzo memorizzato in un registro, consentendo di scrivere funzioni o utilizzare le modalità di indicizzazione standard. OP=01si comporta come il ramo delle specifiche.

+-----+----------+-------+-----------------------------------------------+
| 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.                               |
+-----+----------+-------+-----------------------------------------------+

7

JavaScript (ES6), 361 byte

Accetta input come (memory)(program_counter), dove memoryè un Uint8Array. Emette modificando questo array.

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: il codice è compresso con RegPack e contiene molti caratteri non stampabili, che sono tutti sfuggiti alla rappresentazione sopra della fonte.

Provalo online!

Mappatura Opcode e casi di test

La macchina virtuale utilizza questa mappatura del codice operativo .

Di seguito sono riportati i casi di test tradotti, insieme agli output previsti.

Caso di test n. 1

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

Uscita prevista:

09 20 32 01 44 55 fb

Caso di test n. 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

Uscita prevista:

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

Caso di test n. 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

Uscita prevista:

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

Disimballato e formattato

Poiché il codice è compresso con un algoritmo che sostituisce le stringhe ripetute frequentemente con un singolo carattere, è più efficiente utilizzare gli stessi blocchi di codice più volte che definire e invocare funzioni di supporto o archiviare risultati intermedi (come M[A]) in variabili aggiuntive.

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]
  )
}

Impressionante :) Non è un professionista JS, quindi - questa indicizzazione in qualche "array di codici" è in base al valore dell'opcode? sembra bello. Ma se questa o = ...riga viene eseguita per ogni istruzione, potrebbe avere "codici operativi non intenzionali"?
Felix Palmen,

2
Dovrei probabilmente aggiungere un caso di prova: o Ora, penso che sarebbe stato meglio consentire codici operativi non intenzionali ... i controlli di validità sprecano solo byte qui, ma probabilmente sono troppo tardi per cambiare le regole :(
Felix Palmen,

Bene, stavo per suggerire esattamente questo, poiché non aggiunge molto alla sfida ed è ora difficile controllare con i mapping personalizzati. Ma probabilmente dovresti chiedere / avvisare @MaxYekhlakov prima, poiché potrebbero aver implementato la regola correttamente.
Arnauld,

c = M[A] >> 7 & 1<- è &1davvero necessario qui?
Felix Palmen,

2
Sono abbastanza sicuro che la tua presentazione sia comunque una funzione, la mia formulazione era "un elenco di byte [...] qualsiasi formato sensibile" e in Uint8Arrayeffetti racchiude un tale elenco di byte. Quindi, se mettere i byte in una stringa esadecimale è un modo accettabile di rappresentare l'input, perché vietarli metterli in un oggetto contenitore ...
Felix Palmen,

2

PHP, 581 585 555 532 byte (non -yet- concorrente)

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);

prende i codici PC e OP come numeri interi di base 10 dagli argomenti della riga di comando,
stampa la memoria come un elenco di [base 10 address] => base 10 value.

Questo non è ancora stato testato a fondo ; ma c'è un guasto .

C'è la mappa del codice ed ecco una panoramica della mia mappatura:

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)

nota a margine: il
codice 24risulta in un BNV(ramo mai = 2 byte NOP);
04, 08, 0CSono alias per INX, CLCe SEC
e qualsiasi cosa sopra 3Fo è una di due byte NOPo un alias per le istruzioni per la modalità singola.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.