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 code-golf , 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,0
basée sur)Une liste d'octets avec une longueur maximale d'
256
entré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é
A
et - 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 par0
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 A
et X
est 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
HLT
instruction 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.
0
est 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 1
ou 2
).
Tous les opcodes montrés ici sont en hexadécimal.
LDA
- charger l'opérande dansA
- Opcodes: immédiat:,
00
absolu02
:, indexé:04
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
STA
- stockerA
dans l'opérande- Opcodes: immédiat:,
08
absolu0a
:, indexé:0c
- Opcodes: immédiat:,
LDX
- charger l'opérande dansX
- Opcodes: immédiat:,
10
absolu12
:, indexé:14
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
STX
- stockerX
dans l'opérande- Opcodes: immédiat:,
18
absolu1a
:, indexé:1c
- Opcodes: immédiat:,
AND
- au niveau du bit et de l'A
opérande dansA
- Opcodes: immédiat:,
30
absolu32
:, indexé:34
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
ORA
- au niveau du bit ou deA
et l'opérande dansA
- Opcodes: immédiat:,
38
absolu3a
:, indexé:3c
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
EOR
- xor au niveau du bit (exclusif ou) deA
et opérande dansA
- Opcodes: immédiat:,
40
absolu42
:, indexé:44
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
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:,
48
absolu4a
:, indexé:4c
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
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:,
50
absolu52
:, indexé:54
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
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:,
58
absolu5a
:, indexé:5c
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
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:,
60
absolu62
:, indexé:64
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
ADC
- ajouter avec report, l'opérande et le report sont ajoutésA
, le report est réglé sur le débordement- Opcodes: immédiat:,
68
absolu6a
:, indexé:6c
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
INC
- incrémenter l'opérande d'une unité- Opcodes: immédiat:,
78
absolu7a
:, indexé:7c
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
DEC
- décrémenter l'opérande d'une unité- Opcodes: immédiat:,
80
absolu82
:, indexé:84
- Drapeaux:
Z
,N
- Opcodes: immédiat:,
CMP
- comparerA
avec l'opérande en soustrayant l'opérande deA
, oublier le résultat. Le transport est effacé lors du sous-dépassement, réglé autrement- Opcodes: immédiat:,
88
absolu8a
:, indexé:8c
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
CPX
- comparerX
- commeCMP
pourX
- Opcodes: immédiat:,
90
absolu92
:, indexé:94
- Drapeaux:
Z
,N
,C
- Opcodes: immédiat:,
HLT
- terminer- Opcodes: implicites:
c0
- Opcodes: implicites:
INX
- incrémenterX
d'un- Opcodes: implicites:
c8
- Drapeaux:
Z
,N
- Opcodes: implicites:
DEX
- décrémenterX
de un- Opcodes: implicites:
c9
- Drapeaux:
Z
,N
- Opcodes: implicites:
SEC
- définir le drapeau de portage- Opcodes: implicites:
d0
- Drapeaux:
C
- Opcodes: implicites:
CLC
- drapeau de transport transparent- Opcodes: implicites:
d1
- Drapeaux:
C
- Opcodes: implicites:
BRA
- branche toujours- Opcodes: relatifs:
f2
- Opcodes: relatifs:
BNE
- branche siZ
drapeau effacé- Opcodes: relatifs:
f4
- Opcodes: relatifs:
BEQ
- branche siZ
drapeau défini- Opcodes: relatifs:
f6
- Opcodes: relatifs:
BPL
- branche siN
drapeau effacé- Opcodes: relatifs:
f8
- Opcodes: relatifs:
BMI
- branche siN
drapeau défini- Opcodes: relatifs:
fa
- Opcodes: relatifs:
BCC
- branche siC
drapeau effacé- Opcodes: relatifs:
fc
- Opcodes: relatifs:
BCS
- branche siC
drapeau défini- Opcodes: relatifs:
fe
- Opcodes: relatifs:
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) stderr
et 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 challenge
et faites un git submodule update --init --recursive
après le clonage, pour obtenir mon système de construction personnalisé.
Construisez l'outil avec GNU make (tapez simplement make
, ou gmake
si 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é dansconvfile
-d
- vider la mémoire résultante sous forme de données binaires-x
- vider la mémoire résultante sous forme hexadécimaleinitial_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 :)
la source
BRA
("branche toujours") n'introduit pas de branche dans le flux de contrôle, ne devrait-elle pas être appeléeJMP
?BRA
existe bien dans les conceptions de puces ultérieures (le 6502 n'a pas une telle instruction) comme le 65C02 et le MC 68000.JMP
existe également. La différence est que l'BRA
utilisation de l'adressage relatif et deJMP
l'adressage absolu. Donc, je viens de suivre ces conceptions - en effet, cela ne semble pas si logique;)Réponses:
C (gcc) ,
1381133812551073 octetsAmélioration considérable grâce à plafondcat et Rogem.
Essayez-le en ligne!
Beaucoup de définitions sont déplacées vers les drapeaux du compilateur.
Explication (TRÈS non golfée):
la source
00
octets 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 :)APL (Dyalog Classic) ,
397332330 octetsmerci @ Adám pour -8 octets
Essayez-le en ligne!
la source
f←
?C (gcc) ,
487,480,463,452,447, 438 octetsUtilise 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).Essayez-le en ligne!
Préprocesseur
Ces deux fournissent un moyen plus court de déréférencer les pointeurs.
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é.
Instructions
Les instructions sont structurées comme suit:
Les bits 6-7 indiquent l'arité de l'instruction (
00
nullaire,01
unaire,10
binaire,11
binaire)Les bits 0-2 déterminent l'opérande (s):
R=0
sélectionneA
etR=1
sélectionneX
.OP=00
utilise le registre comme opérande,OP=01
sélectionne l'opérande immédiat,OP=10
sélectionne l'opérande absolu etOP=11
sélectionne l'opérande indexé.X
), même si elles ne peuvent normalement pas être utilisées par spécification. Par exempleINC A
,ADC X, 10
etASL X
tout 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 exemple
110
tests pour un report non001
défini et pour un report défini.111
et000
branche 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=01
se comporte comme une branche de spécification.la source
JavaScript (ES6), 361 octets
Prend l'entrée comme
(memory)(program_counter)
, oùmemory
est unUint8Array
. Sorties en modifiant ce tableau.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
Production attendue:
Cas de test # 2
Production attendue:
Cas de test # 3
Production attendue:
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.la source
o = ...
ligne est exécutée pour chaque instruction, cela pourrait avoir des "opcodes involontaires"?c = M[A] >> 7 & 1
<- est-ce&1
vraiment nécessaire ici?Uint8Array
effet, 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 ...PHP,
581 585 555532 octets (pas encore en concurrence)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:
note latérale: le
code
24
donne unBNV
(branche jamais = 2 octetsNOP
);04
,08
,0C
Sont des alias pourINX
,CLC
etSEC
et tout ce qui précède
3F
est soit un deux octetsNOP
ou un alias pour les instructions en mode simple.la source