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 :)
fonte
BRA
("ramificar sempre") não introduzir uma ramificação no fluxo de controle, não deveria ser chamadoJMP
?BRA
existe em projetos posteriores de chips (o 6502 não tem essa instrução) como o 65C02 e o MC 68000. tambémJMP
existe. A diferença é queBRA
usa o endereço relativo eJMP
o endereço absoluto. Então, eu apenas segui estes projetos - na verdade, não soa tudo o que lógica;)Respostas:
C (gcc) ,
1381133812551073 bytesEnorme melhoria graças ao roofcat e ao Rogem.
Experimente online!
Muitas definições foram movidas para sinalizadores do compilador.
Explicação (MUITO ungolfed):
fonte
00
bytes podem estar um pouco distorcendo as regras ... Admito que não tentei analisar esse código ... você poderia salvar bytes fazendo E / S em binário em vez de hexadecimal? Seria permitido pelas regras :)APL (Dyalog clássico) ,
397332330 bytesthanks @ Adám por -8 bytes
Experimente online!
fonte
f←
?C (gcc) ,
487,480,463,452,447, 438 bytesUsa esse mapeamento de instruções . A atualização das instruções reduziu 9 bytes e potencialmente mais no futuro. Retorna modificando a memória apontada pelo primeiro argumento (
M
). Obrigado ao @ceilingcat por remover alguns bytes.Deve ser compilado com sinalizadores
-DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"
(já incluídos em bytes).Experimente online!
Pré-processador
Esses dois fornecem uma maneira mais curta de desreferenciar os ponteiros.
Reduza o número de bytes necessários para if-elses e digite declarações.
Código
A seguir, uma versão do código legível por humanos, com as diretivas de pré-processador expandidas e as variáveis renomeadas para facilitar a leitura.
Instruções
As instruções estão estruturadas da seguinte maneira:
Os bits 6-7 indicam a aridade da instrução (
00
Nulo, Unário01
,10
Binário,11
Binário)Os bits 0-2 determinam o (s) operando (s):
R=0
selecionaA
eR=1
selecionaX
.OP=00
usa o registrador como um operando,OP=01
seleciona operando imediato,OP=10
seleciona operando absoluto eOP=11
seleciona operando indexado.X
) mesmo quando eles normalmente não puderam ser usados por especificação. Por exemploINC A
,ADC X, 10
eASL X
todo o trabalho.Os bits 3-5 determinam a condição para ramificação: ter um dos bits indica qual sinalizador testar (bit 3-> C, bit 4-> N, bit 5-> Z). Se apenas um bit estiver definido, a instrução testará um sinalizador definido. Para testar um sinalizador não definido, use o complemento dos bits. Por exemplo,
110
testes para transporte não001
definido e para transporte definido.111
e000
ramifique sempre.Você também pode ramificar para um deslocamento de endereço armazenado em um registro, permitindo escrever funções ou usar os modos de indexação padrão.
OP=01
comporta-se como ramo de especificação.fonte
JavaScript (ES6), 361 bytes
Recebe entrada como
(memory)(program_counter)
, ondememory
é umUint8Array
. Saídas modificando essa matriz.Nota: o código é compactado com o RegPack e contém muitos caracteres não imprimíveis, todos escapados na representação acima da fonte.
Experimente online!
Mapeamento de opcode e casos de teste
A máquina virtual usa esse mapeamento de código de operação .
Abaixo estão os casos de teste traduzidos, juntamente com as saídas esperadas.
Caso de teste nº 1
Saída esperada:
Caso de teste nº 2
Saída esperada:
Caso de teste nº 3
Saída esperada:
Descompactado e formatado
Como o código é compactado com um algoritmo que substitui seqüências frequentemente repetidas por um único caractere, é mais eficiente usar os mesmos blocos de código repetidamente do que definir e chamar funções auxiliares ou armazenar resultados intermediários (como
M[A]
) em variáveis adicionais.fonte
o = ...
linha for executada para todas as instruções, isso pode ter "códigos de operação não intencionais"?c = M[A] >> 7 & 1
<- é&1
realmente necessário aqui?Uint8Array
fato, apenas encapsula essa lista de bytes. Então, se colocar os bytes em uma string hexadecimal é uma forma aceitável de representar a entrada, por que colocá-los em um objeto de recipiente ser proibido ...PHP,
581 585 555532 bytes (ainda não competindo)recebe códigos PC e OP como base 10 inteiros dos argumentos da linha de comando,
imprime a memória como uma lista de
[base 10 address] => base 10 value
.Isso ainda não foi testado completamente ; mas há um colapso .
Há o mapa de código e aqui está uma visão geral do meu mapeamento:
nota lateral: o
código
24
resulta em umBNV
(ramo nunca = 2 bytesNOP
);04
,08
,0C
São aliás paraINX
,CLC
eSEC
e qualquer coisa acima
3F
ou é um de dois bytesNOP
ou um nome alternativo para as instruções de modo único.fonte