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 è code-golf , 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,0
basato su)Un elenco di byte con una lunghezza massima di
256
voci, 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
A
e - un registro indice a 8 bit chiamato
X
.
Esistono tre flag di stato:
Z
- il flag zero viene impostato dopo il risultato di alcune operazioni0
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 A
e X
sono 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
HLT
raggiunta 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 1
o 2
).
Tutti i codici operativi mostrati qui sono in esadecimale.
LDA
- carica l'operando inA
- Codici operativi: immediato:,
00
assoluto :,02
indicizzato:04
- Flags:
Z
,N
- Codici operativi: immediato:,
STA
- memorizzaA
in operando- Codici operativi: immediato:,
08
assoluto :,0a
indicizzato:0c
- Codici operativi: immediato:,
LDX
- carica l'operando inX
- Codici operativi: immediato:,
10
assoluto :,12
indicizzato:14
- Flags:
Z
,N
- Codici operativi: immediato:,
STX
- memorizzaX
in operando- Codici operativi: immediato:,
18
assoluto :,1a
indicizzato:1c
- Codici operativi: immediato:,
AND
- bit per bit e diA
e operando inA
- Codici operativi: immediato:,
30
assoluto :,32
indicizzato:34
- Flags:
Z
,N
- Codici operativi: immediato:,
ORA
- bit per bit o diA
e operando inA
- Codici operativi: immediato:,
38
assoluto :,3a
indicizzato:3c
- Flags:
Z
,N
- Codici operativi: immediato:,
EOR
- bit per bit XOR (OR esclusivo) diA
e operando inA
- Codici operativi: immediato:,
40
assoluto :,42
indicizzato:44
- Flags:
Z
,N
- Codici operativi: immediato:,
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:,
48
assoluto :,4a
indicizzato:4c
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
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:,
50
assoluto :,52
indicizzato:54
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
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:,
58
assoluto :,5a
indicizzato:5c
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
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:,
60
assoluto :,62
indicizzato:64
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
ADC
- aggiungi con carry, operando più carry viene aggiuntoA
, carry viene impostato su overflow- Codici operativi: immediato:,
68
assoluto :,6a
indicizzato:6c
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
INC
- incrementa l'operando di uno- Codici operativi: immediato:,
78
assoluto :,7a
indicizzato:7c
- Flags:
Z
,N
- Codici operativi: immediato:,
DEC
- decrementa l'operando di uno- Codici operativi: immediato:,
80
assoluto :,82
indicizzato:84
- Flags:
Z
,N
- Codici operativi: immediato:,
CMP
- confrontaA
con l'operando sottraendo l'operando daA
, dimentica il risultato. Il trasporto viene cancellato su underflow, impostato diversamente- Codici operativi: immediato:,
88
assoluto :,8a
indicizzato:8c
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
CPX
- confrontaX
- comeCMP
perX
- Codici operativi: immediato:,
90
assoluto :,92
indicizzato:94
- Bandiere:
Z
,N
,C
- Codici operativi: immediato:,
HLT
-- terminare- Codici operativi: implicito:
c0
- Codici operativi: implicito:
INX
- incrementoX
di uno- Codici operativi: implicito:
c8
- Flags:
Z
,N
- Codici operativi: implicito:
DEX
- decrementaX
di uno- Codici operativi: implicito:
c9
- Flags:
Z
,N
- Codici operativi: implicito:
SEC
- set carry flag- Codici operativi: implicito:
d0
- Flags:
C
- Codici operativi: implicito:
CLC
- cancella bandiera di trasporto- Codici operativi: implicito:
d1
- Flags:
C
- Codici operativi: implicito:
BRA
- ramo sempre- Codici operativi: relativi:
f2
- Codici operativi: relativi:
BNE
- ramo seZ
bandiera cancellata- Codici operativi: relativi:
f4
- Codici operativi: relativi:
BEQ
- ramo seZ
flag impostato- Codici operativi: relativi:
f6
- Codici operativi: relativi:
BPL
- ramo seN
bandiera cancellata- Codici operativi: relativi:
f8
- Codici operativi: relativi:
BMI
- ramo seN
flag impostato- Codici operativi: relativi:
fa
- Codici operativi: relativi:
BCC
- ramo seC
bandiera cancellata- Codici operativi: relativi:
fc
- Codici operativi: relativi:
BCS
- ramo seC
flag impostato- Codici operativi: relativi:
fe
- Codici operativi: relativi:
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) stderr
e 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 challenge
fai il checkout della filiale ed esegui una git submodule update --init --recursive
clonazione successiva per ottenere il mio sistema di generazione personalizzato.
Costruisci lo strumento con GNU make (basta digitare make
, o gmake
se 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 astderr
-c convfile
- convertire i codici operativi secondo una mappatura fornita inconvfile
-d
- dump della memoria risultante come dati binari-x
- scarica la memoria risultante come esadecimaleinitial_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 :)
BRA
("ramo sempre") non introduce un ramo nel flusso di controllo, non dovrebbe essere chiamato JMP
?
BRA
esiste nei successivi progetti di chip (il 6502 non ha tale istruzione) come il 65C02 e l'MC 68000. JMP
esiste anche. La differenza è che BRA
utilizza l'indirizzamento relativo e JMP
utilizza l'indirizzamento assoluto. Quindi, ho appena seguito questi progetti - in effetti, non sembra così logico;)