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 :)
BRA
("branche toujours") n'introduit pas de branche dans le flux de contrôle, ne devrait-elle pas être appelée JMP
?
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 de JMP
l'adressage absolu. Donc, je viens de suivre ces conceptions - en effet, cela ne semble pas si logique;)