fundo
Eu gosto do meu antigo chip 6502 de 8 bits. É até divertido resolver alguns dos desafios aqui no PPCG no código de máquina 6502. Mas algumas coisas que devem ser simples (como ler dados ou enviar para stdout) são desnecessariamente complicadas de se fazer no código da máquina. Portanto, tenho uma ideia aproximada: invente minha própria máquina virtual de 8 bits inspirada no 6502, mas com o design modificado para ser mais utilizável para desafios. Começando a implementar algo, percebi que isso poderia ser um bom desafio se o design da VM fosse reduzido a um mínimo. :)
Tarefa
Implemente uma máquina virtual de 8 bits em conformidade com a seguinte especificação. Isso é código-golfe , então a implementação com o menor número de bytes vence.
Entrada
Sua implementação deve receber as seguintes entradas:
Um único byte não assinado
pc
, esse é o contador inicial do programa (o endereço na memória em que a VM inicia a execução,0
com base)Uma lista de bytes com um comprimento máximo de
256
entradas, é a RAM da máquina virtual (com seu conteúdo inicial)
Você pode receber esta entrada em qualquer formato adequado.
Saída
Uma lista de bytes que são o conteúdo final da RAM após o término da VM (veja abaixo). Você pode presumir que você só recebe informações que levem à finalização eventualmente. Qualquer formato sensível é permitido.
CPU virtual
A CPU virtual possui
- um contador de programa de 8 bits,
- um registro acumulador de 8 bits chamado
A
e - um registro de índice de 8 bits chamado
X
.
Existem três sinalizadores de status:
Z
- o sinalizador zero é definido após algumas operações resultar em0
N
- o sinalizador negativo é definido após algumas operações resultar em um número negativo (o bit 7 do resultado é definido)C
- o sinalizador de transporte é definido por adições e mudanças para o bit "ausente" do resultado
Ao iniciar, todos os sinalizadores são limpos, o contador do programa é definido para um determinado valor e o conteúdo A
e X
são indeterminados.
Os valores de 8 bits representam
- um não assinado número inteiro no intervalo
[0..255]
- um inteiro assinado , complemento de 2, no intervalo
[-128..127]
dependendo do contexto. Se uma operação exceder ou exceder o limite, o valor envolve (e no caso de uma adição, a marcação de transporte é afectada).
Terminação
A máquina virtual termina quando
- UMA
HLT
instrução é alcançada - Um endereço de memória não existente é acessado
- O contador do programa é executado fora da memória (observe que ele não é distribuído, mesmo que a VM receba 256 bytes de memória completos)
Modos de endereçamento
- implícito - a instrução não tem argumento, o operando está implícito
- imediato - o operando é o byte diretamente após a instrução
- relativo - (apenas para ramificação) o byte após a assinatura da instrução (complemento de 2) e determina o deslocamento a ser adicionado ao contador de programa se a ramificação for obtida.
0
é o local da seguinte instrução - absoluto - o byte após a instrução é o endereço do operando
- indexado - o byte após a instrução mais
X
(o registro) é o endereço do operando
Instruções
Cada instrução consiste em um código de operação (um byte) e, nos modos de endereçamento imediato , relativo , absoluto e indexado, um segundo byte de argumento. Quando a CPU virtual executa uma instrução, incrementa o contador do programa de acordo (por 1
ou2
).
Todos os códigos de operação mostrados aqui estão em hexadecimal.
LDA
- carregar operando noA
- Opcodes: imediato:,
00
absoluto :,02
indexado:04
- Bandeiras:
Z
,N
- Opcodes: imediato:,
STA
- armazenarA
no operando- Opcodes: imediato:,
08
absoluto :,0a
indexado:0c
- Opcodes: imediato:,
LDX
- carregar operando noX
- Opcodes: imediato:,
10
absoluto :,12
indexado:14
- Bandeiras:
Z
,N
- Opcodes: imediato:,
STX
- armazenarX
no operando- Opcodes: imediato:,
18
absoluto :,1a
indexado:1c
- Opcodes: imediato:,
AND
- bit a bit e deA
e operando emA
- Opcodes: imediato:,
30
absoluto :,32
indexado:34
- Bandeiras:
Z
,N
- Opcodes: imediato:,
ORA
- bit a bit ou deA
e operando emA
- Opcodes: imediato:,
38
absoluto :,3a
indexado:3c
- Bandeiras:
Z
,N
- Opcodes: imediato:,
EOR
- bit a bit xor (exclusivo ou) deA
e operando emA
- Opcodes: imediato:,
40
absoluto :,42
indexado:44
- Bandeiras:
Z
,N
- Opcodes: imediato:,
LSR
- deslocamento lógico para a direita, desloque todos os bits do operando um lugar para a direita, o bit 0 vai carregar- Opcodes: imediato:,
48
absoluto :,4a
indexado:4c
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
ASL
- deslocamento aritmético para a esquerda, desloque todos os bits do operando um lugar para a esquerda, o bit 7 vai carregar- Opcodes: imediato:,
50
absoluto :,52
indexado:54
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
ROR
- gire para a direita, mova todos os bits do operando um lugar para a direita, leve para o bit 7, leve 0 para carregar- Opcodes: imediato:,
58
absoluto :,5a
indexado:5c
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
ROL
- gire para a esquerda, mova todos os bits do operando um lugar para a esquerda, leve para o bit 0, leve 7 para carregar- Opcodes: imediato:,
60
absoluto :,62
indexado:64
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
ADC
- add with carry, operando mais carry é adicionadoA
, carry está definido no estouro- Opcodes: imediato:,
68
absoluto :,6a
indexado:6c
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
INC
- incremento de operando em um- Opcodes: imediato:,
78
absoluto :,7a
indexado:7c
- Bandeiras:
Z
,N
- Opcodes: imediato:,
DEC
- decremento de operando em um- Opcodes: imediato:,
80
absoluto :,82
indexado:84
- Bandeiras:
Z
,N
- Opcodes: imediato:,
CMP
- compareA
com o operando subtraindo o operando deA
, esqueça o resultado. O transporte é liberado quando houver fluxo insuficiente,- Opcodes: imediato:,
88
absoluto :,8a
indexado:8c
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
CPX
- compareX
- o mesmo queCMP
paraX
- Opcodes: imediato:,
90
absoluto :,92
indexado:94
- Bandeiras:
Z
,N
,C
- Opcodes: imediato:,
HLT
- terminar- Opcodes: implícito:
c0
- Opcodes: implícito:
INX
- incrementoX
de um- Opcodes: implícito:
c8
- Bandeiras:
Z
,N
- Opcodes: implícito:
DEX
- decréscimoX
de um- Opcodes: implícito:
c9
- Bandeiras:
Z
,N
- Opcodes: implícito:
SEC
- definir bandeira de transporte- Opcodes: implícito:
d0
- Bandeiras:
C
- Opcodes: implícito:
CLC
- bandeira de transporte clara- Opcodes: implícito:
d1
- Bandeiras:
C
- Opcodes: implícito:
BRA
- ramo sempre- Opcodes: relativos:
f2
- Opcodes: relativos:
BNE
- ramifique se aZ
bandeira for limpa- Opcodes: relativos:
f4
- Opcodes: relativos:
BEQ
- ramificar seZ
sinalizador definido- Opcodes: relativos:
f6
- Opcodes: relativos:
BPL
- ramifique se aN
bandeira for limpa- Opcodes: relativos:
f8
- Opcodes: relativos:
BMI
- ramificar seN
sinalizador definido- Opcodes: relativos:
fa
- Opcodes: relativos:
BCC
- ramifique se aC
bandeira for limpa- Opcodes: relativos:
fc
- Opcodes: relativos:
BCS
- ramificar seC
sinalizador definido- Opcodes: relativos:
fe
- Opcodes: relativos:
Opcodes
O comportamento da VM é indefinido se for encontrado algum código de operação que não seja mapeado para uma instrução válida da lista acima.
Conforme a solicitação de Jonathan Allan , você pode escolher seu próprio conjunto de códigos de operação em vez dos códigos de operação mostrados na seção Instruções . Se você fizer isso, você deve adicionar um mapeamento completo aos códigos de operação usados acima em sua resposta.
O mapeamento deve ser um arquivo hexadecimal com pares <official opcode> <your opcode>
, por exemplo, se você substituiu dois opcodes:
f4 f5
10 11
As novas linhas não importam aqui.
Casos de teste (códigos oficiais)
// 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
Eu posso adicionar mais casos de teste mais tarde.
Referência e teste
Para ajudar com os próprios experimentos, aqui está uma implementação de referência (totalmente sem golfe) - ela pode gerar informações de rastreamento (incluindo instruções desmontadas) stderr
e converter opcodes durante a execução.
Maneira recomendada de obter a fonte:
git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules
Ou faça o checkout challenge
e faça umagit submodule update --init --recursive
clonagem após, para obter meu sistema de compilação personalizado.
Crie a ferramenta com o GNU make (apenas digite make
ou, gmake
no seu sistema, o make padrão não é o GNU make).
Uso :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram
-s startpc
- o contador inicial do programa, o padrão é0
-h
- a entrada está em hexadecimal (caso contrário, binário)-t
- rastrear a execução parastderr
-c convfile
- converta códigos de operação de acordo com um mapeamento fornecido emconvfile
-d
- despejar a memória resultante como dados binários-x
- despejar a memória resultante como hexadecimalinitial_ram
- o conteúdo inicial da RAM, hexadecimal ou binário
Observe que o recurso de conversão falhará em programas que modificam códigos de operação durante a execução.
Isenção de responsabilidade: As regras e especificações acima são autorizadas para o desafio, não esta ferramenta. Isso se aplica especialmente ao recurso de conversão de opcode. Se você acha que a ferramenta apresentada aqui possui um erro, especifique as especificações, por favor relate em um comentário :)
BRA
("ramificar sempre") não introduzir uma ramificação no fluxo de controle, não deveria ser chamado JMP
?
BRA
existe em projetos posteriores de chips (o 6502 não tem essa instrução) como o 65C02 e o MC 68000. também JMP
existe. A diferença é que BRA
usa o endereço relativo e JMP
o endereço absoluto. Então, eu apenas segui estes projetos - na verdade, não soa tudo o que lógica;)