Machine virtuelle 8 bits

31

Contexte

J'aime mon ancienne puce 6502 8 bits. C'est même amusant de résoudre certains des défis ici sur PPCG dans le code machine 6502. Mais certaines choses qui devraient être simples (comme lire des données ou sortir vers stdout) sont inutilement lourdes à faire dans le code machine. Il y a donc une idée approximative dans mon esprit: inventer ma propre machine virtuelle 8 bits inspirée du 6502, mais avec une conception modifiée pour être plus utilisable pour les défis. Commençant à mettre en œuvre quelque chose, j'ai réalisé que cela pourrait être un beau défi en soi si la conception de la machine virtuelle est réduite au strict minimum :)

Tâche

Implémentez une machine virtuelle 8 bits conforme à la spécification suivante. C'est du , donc l'implémentation avec le moins d'octets est gagnante.

Contribution

Votre implémentation doit prendre les entrées suivantes:

  • Un seul octet non signé pc, c'est le compteur de programme initial (l'adresse en mémoire où la machine virtuelle commence l'exécution, 0basée sur)

  • Une liste d'octets avec une longueur maximale d' 256entrées, c'est la RAM de la machine virtuelle (avec son contenu initial)

Vous pouvez prendre cette entrée dans n'importe quel format raisonnable.

Sortie

Une liste d'octets qui sont le contenu final de la RAM après la fin de la VM (voir ci-dessous). Vous pouvez supposer que vous n'obtenez que des informations qui mènent à la fin. Tout format raisonnable est autorisé.

CPU virtuel

Le CPU virtuel a

  • un compteur de programmes 8 bits,
  • un registre d'accumulateur 8 bits appelé Aet
  • un registre d'index 8 bits appelé X.

Il existe trois indicateurs d'état:

  • Z - l'indicateur zéro est défini après que certaines opérations se traduisent par 0
  • N - le drapeau négatif est défini après qu'une opération donne un nombre négatif (le bit 7 du résultat est défini)
  • C - le drapeau de retenue est défini par des ajouts et des décalages pour le bit "manquant" du résultat

Au démarrage, les drapeaux sont tous effacés, le compteur de programme est mis à une valeur donnée et le contenu de Aet Xest indéterminé.

Les valeurs 8 bits représentent soit

  • un entier non signé dans la plage[0..255]
  • un entier signé , complément à 2, dans la plage[-128..127]

selon le contexte. En cas de dépassement ou de dépassement d'une opération, la valeur s'enroule (et en cas d'ajout, l'indicateur de report est affecté).

Résiliation

La machine virtuelle se termine lorsque

  • Une HLTinstruction est atteinte
  • Une adresse mémoire inexistante est accessible
  • Le compteur de programme s'exécute en dehors de la mémoire (notez qu'il ne s'enroule pas même si la VM reçoit les 256 octets de mémoire)

Modes d'adressage

  • implicite - l'instruction n'a pas d'argument, l'opérande est implicite
  • immédiat - l'opérande est l'octet directement après l'instruction
  • relative - (pour le branchement uniquement) l'octet après la signature de l'instruction (complément à 2) et détermine le décalage à ajouter au compteur de programme si la branche est prise. 0est l'emplacement de l'instruction suivante
  • absolu - l'octet après l'instruction est l'adresse de l'opérande
  • indexé - l'octet après l'instruction plus X(le registre) est l'adresse de l'opérande

Instructions

Chaque instruction est constituée d'un opcode (un octet) et, dans les modes d'adressage immédiat , relatif , absolu et indexé d' un deuxième octet d'argument. Lorsque la CPU virtuelle exécute une instruction, elle incrémente le compteur de programme en conséquence (par 1ou 2).

Tous les opcodes montrés ici sont en hexadécimal.

  • LDA - charger l'opérande dans A

    • Opcodes: immédiat:, 00absolu 02:, indexé:04
    • Drapeaux: Z,N
  • STA- stocker Adans l'opérande

    • Opcodes: immédiat:, 08absolu 0a:, indexé:0c
  • LDX - charger l'opérande dans X

    • Opcodes: immédiat:, 10absolu 12:, indexé:14
    • Drapeaux: Z,N
  • STX- stocker Xdans l'opérande

    • Opcodes: immédiat:, 18absolu 1a:, indexé:1c
  • AND- au niveau du bit et de l' Aopérande dansA

    • Opcodes: immédiat:, 30absolu 32:, indexé:34
    • Drapeaux: Z,N
  • ORA- au niveau du bit ou de Aet l'opérande dansA

    • Opcodes: immédiat:, 38absolu 3a:, indexé:3c
    • Drapeaux: Z,N
  • EOR- xor au niveau du bit (exclusif ou) de Aet opérande dansA

    • Opcodes: immédiat:, 40absolu 42:, indexé:44
    • Drapeaux: Z,N
  • LSR - décalage logique à droite, décaler tous les bits de l'opérande d'un endroit vers la droite, le bit 0 va porter

    • Opcodes: immédiat:, 48absolu 4a:, indexé:4c
    • Drapeaux: Z, N,C
  • ASL - décalage arithmétique vers la gauche, décaler tous les bits de l'opérande d'un endroit vers la gauche, le bit 7 va porter

    • Opcodes: immédiat:, 50absolu 52:, indexé:54
    • Drapeaux: Z, N,C
  • ROR - tourner à droite, déplacer tous les bits de l'opérande d'un endroit vers la droite, le transfert va au bit 7, le bit 0 va au transport

    • Opcodes: immédiat:, 58absolu 5a:, indexé:5c
    • Drapeaux: Z, N,C
  • ROL - tourner à gauche, déplacer tous les bits de l'opérande d'une place vers la gauche, le transfert va au bit 0, le bit 7 va au transport

    • Opcodes: immédiat:, 60absolu 62:, indexé:64
    • Drapeaux: Z, N,C
  • ADC- ajouter avec report, l'opérande et le report sont ajoutés A, le report est réglé sur le débordement

    • Opcodes: immédiat:, 68absolu 6a:, indexé:6c
    • Drapeaux: Z, N,C
  • INC - incrémenter l'opérande d'une unité

    • Opcodes: immédiat:, 78absolu 7a:, indexé:7c
    • Drapeaux: Z,N
  • DEC - décrémenter l'opérande d'une unité

    • Opcodes: immédiat:, 80absolu 82:, indexé:84
    • Drapeaux: Z,N
  • CMP- comparer Aavec l'opérande en soustrayant l'opérande de A, oublier le résultat. Le transport est effacé lors du sous-dépassement, réglé autrement

    • Opcodes: immédiat:, 88absolu 8a:, indexé:8c
    • Drapeaux: Z, N,C
  • CPX- comparer X- comme CMPpourX

    • Opcodes: immédiat:, 90absolu 92:, indexé:94
    • Drapeaux: Z, N,C
  • HLT - terminer

    • Opcodes: implicites: c0
  • INX- incrémenter Xd'un

    • Opcodes: implicites: c8
    • Drapeaux: Z,N
  • DEX- décrémenter Xde un

    • Opcodes: implicites: c9
    • Drapeaux: Z,N
  • SEC - définir le drapeau de portage

    • Opcodes: implicites: d0
    • Drapeaux: C
  • CLC - drapeau de transport transparent

    • Opcodes: implicites: d1
    • Drapeaux: C
  • BRA - branche toujours

    • Opcodes: relatifs: f2
  • BNE- branche si Zdrapeau effacé

    • Opcodes: relatifs: f4
  • BEQ- branche si Zdrapeau défini

    • Opcodes: relatifs: f6
  • BPL- branche si Ndrapeau effacé

    • Opcodes: relatifs: f8
  • BMI- branche si Ndrapeau défini

    • Opcodes: relatifs: fa
  • BCC- branche si Cdrapeau effacé

    • Opcodes: relatifs: fc
  • BCS- branche si Cdrapeau défini

    • Opcodes: relatifs: fe

Opcodes

Le comportement de la machine virtuelle n'est pas défini si un opcode est trouvé qui ne correspond pas à une instruction valide de la liste ci-dessus.

Conformément à la demande de Jonathan Allan , vous pouvez choisir votre propre ensemble d'opcodes au lieu des opcodes indiqués dans la section Instructions . Si vous le faites, vous devez ajouter un mappage complet aux opcodes utilisés ci-dessus dans votre réponse.

Le mappage doit être un fichier hexadécimal avec des paires <official opcode> <your opcode>, par exemple si vous avez remplacé deux opcodes:

f4 f5
10 11

Les sauts de ligne n'ont pas d'importance ici.

Cas de test (opcodes officiels)

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

Je pourrais ajouter plus de tests plus tard.

Référence et test

Pour vous aider avec vos propres expériences, voici une implémentation de référence (totalement sans golf) - il peut générer des informations de suivi (y compris des instructions désassemblées) stderret convertir des opcodes pendant l'exécution.

Méthode recommandée pour obtenir la source:

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

Ou branchez la caisse challengeet faites un git submodule update --init --recursiveaprès le clonage, pour obtenir mon système de construction personnalisé.

Construisez l'outil avec GNU make (tapez simplement make, ou gmakesi sur votre système, la marque par défaut n'est pas GNU make).

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

  • -s startpc - le compteur de programme initial, par défaut à 0
  • -h - l'entrée est en hexadécimal (sinon binaire)
  • -t - trace l'exécution jusqu'à stderr
  • -c convfile - convertir les opcodes selon un mappage donné dans convfile
  • -d - vider la mémoire résultante sous forme de données binaires
  • -x - vider la mémoire résultante sous forme hexadécimale
  • initial_ram - le contenu initial de la RAM, hexadécimal ou binaire

Notez que la fonction de conversion échouera sur les programmes qui modifient les opcodes lors de l'exécution.

Avertissement: Les règles et spécifications ci-dessus font autorité pour le défi, pas cet outil. Cela s'applique particulièrement à la fonction de conversion d'opcode. Si vous pensez que l'outil présenté ici a un bug par rapport aux spécifications, veuillez le signaler dans un commentaire :)

Felix Palmen
la source
1
J'imagine qu'il y aurait probablement de nombreuses opportunités de golf en choisissant différents opcodes pour les instructions, mais il semble que les opcodes aient été corrigés (même si l'ensemble d'instructions est ce qui définit la machine). Peut-être que cela vaut la peine d'envisager de permettre aux implémentations d'avoir leur propre page de code?
Jonathan Allan
1
@JonathanAllan y a réfléchi à deux fois, je l'autorise maintenant et pourrait ajouter un outil de "conversion" pour rendre les solutions utilisant d'autres ensembles d'opcodes facilement testables.
Felix Palmen
1
@Arnauld btw mon raisonnement pour autoriser cela était de réduire le nombre de cas spéciaux, donc cela devrait être mieux "jouable" - chaque opcode est soit implicite, une branche relative ou autorise les trois autres modes d'adressage :)
Felix Palmen
1
si BRA("branche toujours") n'introduit pas de branche dans le flux de contrôle, ne devrait-elle pas être appelée JMP?
ngn
1
@ngn BRAexiste bien dans les conceptions de puces ultérieures (le 6502 n'a pas une telle instruction) comme le 65C02 et le MC 68000. JMPexiste également. La différence est que l' BRAutilisation de l'adressage relatif et de JMPl'adressage absolu. Donc, je viens de suivre ces conceptions - en effet, cela ne semble pas si logique;)
Felix Palmen

Réponses:

16

C (gcc) , 1381 1338 1255 1073 octets

Amélioration considérable grâce à plafondcat et 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]();}

Essayez-le en ligne!

Beaucoup de définitions sont déplacées vers les drapeaux du compilateur.

Explication (TRÈS non golfée):

#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
la source
Excellent travail, instantané +1, ne m'attendais vraiment pas à une solution C en premier :) votre ajout d' 00octets pourrait cependant un peu contourner les règles ... J'avoue que je n'ai pas essayé d'analyser ce code ... pourriez-vous peut-être enregistrer des octets faisant des E / S en binaire au lieu d'hex? Serait autorisé par les règles :)
Felix Palmen
Je voudrais remplacer la règle selon laquelle les opcodes illégaux conduisent à la résiliation en disant simplement que le comportement des opcodes illégaux n'est pas défini ... cela nuirait-il à votre réponse ou êtes-vous d'accord avec cela?
Felix Palmen du
@FelixPalmen bien, tant que la résiliation est un comportement "indéfini" tout à fait valide, cela ne fera pas de mal (cela ouvre une nouvelle possibilité de le jouer à la place!)
Max Yekhlakov
@MaxYekhlakov par "blessé", je voulais dire être injuste envers votre solution parce que vous avez peut-être "dépensé des octets" pour vous assurer qu'un opcode illégal termine le vm. Je suis heureux que vous accueilliez le changement de règle comme une opportunité :) Et encore une fois, félicitations, j'adore voir une solution en C, qui est mon langage de programmation préféré. Dommage que vous gagniez rarement un défi de code-golf en C - néanmoins, "golfé" C est juste cool :)
Felix Palmen
Pourriez-vous ajouter la partie drapeaux à publier?
l4m2
8

APL (Dyalog Classic) , 397 332 330 octets

merci @ Adám pour -8 octets

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

Essayez-le en ligne!

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
la source
Cette solution a-t-elle des opcodes involontaires et sinon, avez-vous dépensé des octets pour les éviter? Voir ce commentaire pour la raison que je demande ...
Felix Palmen
@FelixPalmen Maintenant que vous le mentionnez, oui :( Au départ, j'ai observé cette règle, mais pendant que je jouais au golf, j'ai accidentellement pris 4, 5, et peut-être d'autres, des opcodes valides. Donc, une décision de rendre leur comportement non défini serait la bienvenue :)
ngn
2
fait maintenant, je réalise que ce n'était pas la meilleure décision en premier lieu et @MaxYekhlakov n'a malheureusement pas eu à dire quoi que ce soit sur le changement de règle.
Felix Palmen
Avez-vous besoin f←?
Erik the Outgolfer
8

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

Utilise ce mappage d'instructions . La mise à jour des instructions a réduit de 9 octets, et potentiellement plus à l'avenir. Renvoie en modifiant la mémoire pointée par le premier argument ( M). Merci à @ceilingcat d'avoir rasé certains octets.

Doit être compilé avec des drapeaux -DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(déjà inclus en octets).

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

Essayez-le en ligne!

Préprocesseur

-DO=*o -DD=*d

Ces deux fournissent un moyen plus court de déréférencer les pointeurs.

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

Réduisez le nombre d'octets nécessaires pour les if-elses et les déclarations de type.

Code

Ce qui suit est une version lisible par l'homme du code, avec les directives du préprocesseur développées et les variables renommées pour la lisibilité.

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

Instructions

Les instructions sont structurées comme suit:

  • Les bits 6-7 indiquent l'arité de l'instruction ( 00nullaire, 01unaire, 10binaire, 11binaire)

  • Les bits 0-2 déterminent l'opérande (s): R=0sélectionne Aet R=1sélectionneX . OP=00utilise le registre comme opérande, OP=01sélectionne l'opérande immédiat, OP=10sélectionne l'opérande absolu et OP=11sélectionne l'opérande indexé.

    • Comme vous l'avez peut-être remarqué, cela permet d'effectuer toutes les opérations sur l'un ou l'autre registre (bien que vous ne puissiez toujours indexer X ), même si elles ne peuvent normalement pas être utilisées par spécification. Par exemple INC A, ADC X, 10et ASL Xtout le travail.
  • Les bits 3 à 5 déterminent la condition de branchement: avoir l'un des bits indique le drapeau à tester (bit 3-> C, bit 4-> N, bit 5-> Z). Si un seul bit est défini, l'instruction teste un indicateur défini. Pour tester un indicateur non défini, prenez le complément des bits. Par exemple110 tests pour un report non 001défini et pour un report défini. 111et 000branche toujours.

  • Vous pouvez également créer une dérivation vers un décalage d'adresse stocké dans un registre, ce qui vous permet d'écrire des fonctions, ou vous pouvez utiliser les modes d'indexation standard. OP=01se comporte comme une branche de spécification.

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

la source
7

JavaScript (ES6), 361 octets

Prend l'entrée comme (memory)(program_counter), où memoryest un Uint8Array. Sorties en modifiant ce tableau.

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: Le code est compressé avec RegPack et contient beaucoup de caractères non imprimables, qui sont tous échappés dans la représentation ci-dessus de la source.

Essayez-le en ligne!

Mappage d'opcode et cas de test

La machine virtuelle utilise ce mappage d'opcode .

Vous trouverez ci-dessous les cas de test traduits, ainsi que les résultats attendus.

Cas de test # 1

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

Production attendue:

09 20 32 01 44 55 fb

Cas de test # 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

Production attendue:

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

Cas de test # 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

Production attendue:

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

Déballé et formaté

Étant donné que le code est compressé avec un algorithme qui remplace les chaînes fréquemment répétées par un seul caractère, il est plus efficace d'utiliser les mêmes blocs de code à plusieurs reprises que de définir et d'appeler des fonctions d'assistance ou de stocker des résultats intermédiaires (tels que M[A]) dans des variables supplémentaires.

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
la source
Impressionnant :) Pas un pro JS, donc - est-ce l'indexation dans un "tableau de code" par la valeur de l'opcode? à l'air cool. Mais si cette o = ...ligne est exécutée pour chaque instruction, cela pourrait avoir des "opcodes involontaires"?
Felix Palmen
2
Je devrais probablement ajouter un cas de test: o Maintenant, je pense qu'il aurait été préférable d'autoriser les opcodes involontaires ... les contrôles de validité gaspillent juste des octets ici, mais probablement trop tard pour changer les règles :(
Felix Palmen
Eh bien, j'étais sur le point de suggérer exactement cela, car cela n'ajoute pas beaucoup au défi et est maintenant difficile à vérifier avec des mappages personnalisés. Mais vous devriez probablement demander / avertir @MaxYekhlakov d'abord, car ils ont peut-être correctement mis en œuvre la règle.
Arnauld
c = M[A] >> 7 & 1<- est-ce &1vraiment nécessaire ici?
Felix Palmen
2
Je suis à peu près sûr que ce serait le cas, car votre soumission est une fonction de toute façon, ma formulation était "une liste d'octets [...] n'importe quel format sensé" et en Uint8Arrayeffet, encapsule simplement une telle liste d'octets. Donc, si mettre les octets dans une chaîne hexadécimale est une façon acceptable de représenter l'entrée, pourquoi les placer dans un objet conteneur devrait-il être interdit ...
Felix Palmen
2

PHP, 581 585 555 532 octets (pas encore en concurrence)

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

prend les codes PC et OP sous forme d'entiers de base 10 à partir des arguments de la ligne de commande,
imprime la mémoire sous forme de liste [base 10 address] => base 10 value.

Ce n'est pas encore testé à fond ; mais il y a une panne .

Il y a la carte du code et voici un aperçu de ma cartographie:

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)

note latérale: le
code 24donne un BNV(branche jamais = 2 octets NOP);
04, 08, 0CSont des alias pour INX, CLCetSEC
et tout ce qui précède 3Fest soit un deux octets NOPou un alias pour les instructions en mode simple.

Titus
la source