Máquina virtual de 8 bits

31

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 é , 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, 0com base)

  • Uma lista de bytes com um comprimento máximo de 256entradas, é 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 Ae
  • 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 em 0
  • 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 Ae Xsã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 1ou2 ).

Todos os códigos de operação mostrados aqui estão em hexadecimal.

  • LDA - carregar operando no A

    • Opcodes: imediato:, 00absoluto :, 02indexado:04
    • Bandeiras: Z,N
  • STA- armazenar Ano operando

    • Opcodes: imediato:, 08absoluto :, 0aindexado:0c
  • LDX - carregar operando no X

    • Opcodes: imediato:, 10absoluto :, 12indexado:14
    • Bandeiras: Z,N
  • STX- armazenar Xno operando

    • Opcodes: imediato:, 18absoluto :, 1aindexado:1c
  • AND- bit a bit e de Ae operando emA

    • Opcodes: imediato:, 30absoluto :, 32indexado:34
    • Bandeiras: Z,N
  • ORA- bit a bit ou de Ae operando emA

    • Opcodes: imediato:, 38absoluto :, 3aindexado:3c
    • Bandeiras: Z,N
  • EOR- bit a bit xor (exclusivo ou) de Ae operando emA

    • Opcodes: imediato:, 40absoluto :, 42indexado:44
    • Bandeiras: Z,N
  • 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:, 48absoluto :, 4aindexado:4c
    • Bandeiras: Z, N,C
  • 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:, 50absoluto :, 52indexado:54
    • Bandeiras: Z, N,C
  • 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:, 58absoluto :, 5aindexado:5c
    • Bandeiras: Z, N,C
  • 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:, 60absoluto :, 62indexado:64
    • Bandeiras: Z, N,C
  • ADC- add with carry, operando mais carry é adicionado A, carry está definido no estouro

    • Opcodes: imediato:, 68absoluto :, 6aindexado:6c
    • Bandeiras: Z, N,C
  • INC - incremento de operando em um

    • Opcodes: imediato:, 78absoluto :, 7aindexado:7c
    • Bandeiras: Z,N
  • DEC - decremento de operando em um

    • Opcodes: imediato:, 80absoluto :, 82indexado:84
    • Bandeiras: Z,N
  • CMP- compare Acom o operando subtraindo o operando de A, esqueça o resultado. O transporte é liberado quando houver fluxo insuficiente,

    • Opcodes: imediato:, 88absoluto :, 8aindexado:8c
    • Bandeiras: Z, N,C
  • CPX- compare X- o mesmo que CMPparaX

    • Opcodes: imediato:, 90absoluto :, 92indexado:94
    • Bandeiras: Z, N,C
  • HLT - terminar

    • Opcodes: implícito: c0
  • INX- incremento Xde um

    • Opcodes: implícito: c8
    • Bandeiras: Z,N
  • DEX- decréscimo Xde um

    • Opcodes: implícito: c9
    • Bandeiras: Z,N
  • SEC - definir bandeira de transporte

    • Opcodes: implícito: d0
    • Bandeiras: C
  • CLC - bandeira de transporte clara

    • Opcodes: implícito: d1
    • Bandeiras: C
  • BRA - ramo sempre

    • Opcodes: relativos: f2
  • BNE- ramifique se a Zbandeira for limpa

    • Opcodes: relativos: f4
  • BEQ- ramificar se Zsinalizador definido

    • Opcodes: relativos: f6
  • BPL- ramifique se a Nbandeira for limpa

    • Opcodes: relativos: f8
  • BMI- ramificar se Nsinalizador definido

    • Opcodes: relativos: fa
  • BCC- ramifique se a Cbandeira for limpa

    • Opcodes: relativos: fc
  • BCS- ramificar se Csinalizador definido

    • Opcodes: relativos: fe

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) stderre 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 challengee 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 makeou, gmakeno 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 para stderr
  • -c convfile - converta códigos de operação de acordo com um mapeamento fornecido em convfile
  • -d - despejar a memória resultante como dados binários
  • -x - despejar a memória resultante como hexadecimal
  • initial_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 :)

Felix Palmen
fonte
1
Eu imagino que provavelmente haveria inúmeras oportunidades de golfe ao escolher opcodes diferentes para as instruções, mas parece que os opcodes foram corrigidos (mesmo que o conjunto de instruções seja o que define a máquina). Talvez valha a pena considerar permitir que as implementações tenham sua própria página de código?
Jonathan Allan
1
@ JonathanAllan pensou duas vezes sobre isso, eu estou permitindo isso agora e pode adicionar uma ferramenta de "conversão" para tornar soluções que usam outros conjuntos de opcodes facilmente testáveis.
Felix Palmen
1
@Arnauld: meu raciocínio para permitir isso era reduzir a quantidade de casos especiais, por isso deveria ser melhor "golfable" - cada código de operação é implícito, um ramo relativo ou permite todos os outros três modos de endereçamento :)
Felix Palmen
1
se BRA("ramificar sempre") não introduzir uma ramificação no fluxo de controle, não deveria ser chamado JMP?
NGN
1
Bem, BRAexiste em projetos posteriores de chips (o 6502 não tem essa instrução) como o 65C02 e o MC 68000. também JMPexiste. A diferença é que BRAusa o endereço relativo e JMPo endereço absoluto. Então, eu apenas segui estes projetos - na verdade, não soa tudo o que lógica;)
Felix Palmen

Respostas:

16

C (gcc) , 1381 1338 1255 1073 bytes

Enorme melhoria graças ao roofcat e ao Rogem.

#include<stdio.h>
C*F="%02hhx ";m[256],p,a,x,z,n,c,e;g;*I(){R++p+m;}*A(){R*I()+m;}*X(){R*I()+m+x;}C*Q(){W(printf,m[g],1)exit(a);}C*(*L[])()={I,Q,A,Q,X,Q,Q,Q};l(C*i){R 254/p?*i=*L[m[p]&7]():*Q();}s(i){R 254/p?*L[m[p]&7]()=i:*Q();}q(){p>254?Q():++p;}C*y(){p+=e;}B(U,z)B(V,!z)B(_,n)B(BM,!n)B(BC,c)B(BS,!c)C*(*b[])()={Q,Q,y,Q,U,Q,V,Q,_,Q,BM,Q,BC,Q,BS,Q};j(){z=!l(&a);v}o(){s(a);}t(){z=!l(&x);n=x&H;}D(){s(x);}f(K(&=)h(K(|=)i(K(^=)J(E;c=e&1;z=!(e/=2);s(e);w}k(E;c=w;z=!e;s(e*=2);}T(E;g=e&1;z=!(e=e/2|H*!!c);c=g;s(e);w}M(E;g=w z=!(e=e*2|H*!!c);c=g;s(e);}N(E;z=!(a=g=a+e+!!c);c=g>>8%2;G}P(E;z=!~e;--p;s(g=e+1);G}u(E;g=e-1;z=!g;--p;s(g);G}r(E;g=a-e;z=!g;c=G}S(E;g=x-e;z=!g;c=G}Y(){z=!(x=g=1-m[p]%2*2);n=x&H;}Z(){c=~m[p]&1;}d(){p<255||Q();e=m[++p];b[m[p-1]&15]();}(*O[])()={j,o,t,D,Q,Q,f,h,i,J,k,T,M,N,Q,P,u,r,S,Q,Q,Q,Q,Q,Q,Y,Z,Q,Q,Q,d,d};main(){scanf(F,&p);W(scanf,&m[g],0)for(;;q())O[m[p]/8]();}

Experimente online!

Muitas definições foram movidas para sinalizadores do compilador.

Explicação (MUITO ungolfed):

#include<stdio.h>

// useful defines
#define C unsigned char
#define H 128 // highest bit
#define R return

// code generator for I/O
#define W(o,p,q)for(g=-1;++g<256&&((q)||!feof(stdin));)(o)(F,(p));

// code generator for branching instruction handlers
#define BB(q)(){(q)||Y();}

// frequent pieces of code
#define NA n=a&H;
#define NE n=e&H;
#define NG n=g&H;
#define E l(&e)

// printf/scanf template
C * F = "%02hhx ";

// global state: m=memory, pax=registers, znc=flags
// e and g are for temporaries and type conversions
C m[256],p,a,x,z,n,c,e;g;

// get the pointer to a memory location:
C * I() {R &m[++p];} // immediate
C * A() {R &m[m[++p]];} // absolute
C * X() {R &m[m[++p]+x];} // indexed

// terminate the VM (and dump memory contents)
C * Q() { W(printf,m[g],1) exit(a);}

// an array of functions for accessing the memory
// They either return the pointer to memory location
// or terminate the program.
C * (*L[])()={I,Q,A,Q,X,Q,Q,Q};

// load a byte from the memory into the variable pointed by i
// terminate the program if we cannot access the address byte
l (C * i) {return 254 / p ? *i = *L[m[p]&7] () : *Q ();}

// save a byte i to the memory
// terminate the program if we cannot access the address byte
s (C i) {return 254 / p ? *L[m[p]&7]() = i : *Q ();}

// advance the instruction pointer (or fail if we fall outside the memory)
q () {p > 254 ? Q () : ++p;}

// branch
C * Y() {p += e;}

// generated functions for conditional branches
C * BN BB(z)
C * BZ BB(!z)
C * BP BB(n)
C * BM BB(!n)
C * BC BB(c)
C * BS BB(!c)

// a list of branch functions
C * (*B[])() = {Q,Q,Y,Q,BN,Q,BZ,Q,BP,Q,BM,Q,BC,Q,BS,Q};

// Instruction handling functions

OA () {z = !l (&a); NA} // lda
OB () {s (a);} // sta
OC () {z = !l (&x); n = x & H;} // ldx
OD () {s (x);} // stx
OG () {E; z = !(a &= e); NA} // and
OH () {E; z = !(a |= e); NA} // ora
OI () {E; z = !(a ^= e); NA} // eor
OJ () {E; c = e & 1; z = !(e /= 2); s (e); NE} // lsr
OK () {E; c = NE; z = !e; s (e *= 2);} // asl
OL () {E; g = e & 1; z = !(e = e / 2 | H * !!c); c = g; s (e); NE} // ror
OM () {E; g = e & H; z = !(e = e * 2 | H * !!c); c = g; s (e); NE} // rol
ON () {E; z = !(a = g = a + e + !!c); c = !!(g & 256); NG} // adc
OP () {E; z = !~e; --p; s (g = e + 1); NG} // inc
OQ () {E; g = e - 1; z = !g; --p; s (g); NG} // dec
OR () {E; g = a - e; z = !g; c = NG} // cmp
OS () {E; g = x - e; z = !g; c = NG} // cpx
OY () {z = !(x = g = ~m[p] & 1 * 2 - 1); n = x & H;} // inx/dex
OZ () {c = ~m[p] & 1;} // sec/clc
Od () {p < 255 || Q (); e = m[++p]; B[m[p-1]&15] ();} // branching

// list of opcode handlers
(*O[]) () = {OA,OB,OC,OD,Q,Q,OG,OH,OI,OJ,OK,OL,OM,ON,Q,OP,OQ,OR,OS,Q,Q,Q,Q,Q,Q,OY,OZ,Q,Q,Q,Od,Od};

// main function
main ()
{
    // read the instruction pointer
    scanf (F, &p);

    // read memory contents
    W(scanf, &m[g], 0)

    // repeatedly invoke instruction handlers until we eventually terminate
    for (;; q())
        O[m[p]/8] ();
}
Max Yekhlakov
fonte
Bom trabalho, instantâneo +1, realmente não esperava uma solução C primeiro :) seus anexos de 00bytes 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 :)
Felix Palmen
Gostaria de substituir a regra de que códigos de opção ilegais levam ao encerramento apenas dizendo que o comportamento de códigos de opção ilegais é indefinido ... isso prejudicaria sua resposta ou você está bem com isso?
Felix Palmen
@FelixPalmen bem, enquanto rescisão é bastante válido "indefinido" comportamento, não vai machucar (ele abre uma nova possibilidade para jogar golfe-lo para baixo em vez!)
Max Yekhlakov
@MaxYekhlakov por "machucado", quis dizer ser injusto com a sua solução, porque você talvez "gastou bytes" para garantir que um código de operação ilegal encerre a VM. Fico feliz que você tenha acolhido a mudança de regra como uma oportunidade :) E, novamente, parabéns, adoro ver uma solução em C, que é minha linguagem de programação favorita de todos os tempos. É uma pena que você raramente vença um desafio de código-golfe em C - no entanto, "jogar golfe" é legal :) #
Felix Palmen
Você poderia adicionar a parte dos sinalizadores para postar?
L4m2 20/1018
8

APL (Dyalog clássico) , 397 332 330 bytes

thanks @ Adám por -8 bytes

f←{q2a x c←≡B256⋄{0::m
⍺←(∇q∘←)0∘=,B≤+⍨
30u←⌊8÷⍨bpm:∇p+←129-B|127-1pm×⊃b2/(,~,⍪)1,q,c
p+←1
u=25:⍺x⊢←B|x1*b
u=26:∇c⊢←~2|b
p+←≢om⊃⍨i←⍎'p⊃m+x'↑⍨1+8|b
u⊃(,⍉'⍺ax⊢←o' '∇m[i]←ax'∘.~'xa'),5 4 2 3 2/'⍺⌽⊃'∘,¨'a⊢←2⊥(⍕⊃u⌽''∧∨≠'')/o a⊤⍨8⍴2' 'c(i⊃m)←u⌽d⊤(⌽d←u⌽2B)⊥u⌽o,c×u>10' 'c a⊢←2B⊤a+o+c' 'm[i]←B|o-¯1*u' 'c⊢←⊃2B⊤o-⍨⊃u⌽x a'}p m←⍵}

Experimente online!

p  program counter
m  memory
a  accumulator register
x  index register
q  flags z (zero) and n (negative) as a length-2 vector
c  flag for carry
  function to update z and n
b  current instruction
u  highest 5 bits of b
o  operand
i  target address in memory
ngn
fonte
Esta solução possui códigos de operação não intencionais e, se não, você gastou bytes evitando-os? Veja este comentário para a razão pela qual eu estou perguntando ...
Felix Palmen
@FelixPalmen Agora que você mencionou, sim :( Inicialmente observei essa regra, mas como eu estava jogando golfe acidentalmente fiz 4, 5 e possivelmente outros códigos de operação válidos. Portanto, uma decisão de deixar o comportamento deles indefinido seria muito bem-vinda :)
NGN
2
feito agora, percebo que não foi a melhor decisão em primeiro lugar e @MaxYekhlakov infelizmente não precisou dizer nada sobre a mudança de regra.
Felix Palmen
Você precisa f←?
Erik the Outgolfer
8

C (gcc) , 487 , 480 , 463 , 452 , 447 , 438 bytes

Usa 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).

e(M,Z,C)u*M,C;{for(u r[2],S=0,D,O,c,t;o=C<Z?M+C++:0;){I(c=O,d=r+!(c&4),break)I(o=c&3?C<Z&&C?M+C++:0:d,o=c&2?O+c%2**r+M:o,break)t=(c/=8)&7;I(c<24&c>4&&t,t&=3;I(c&8,I(c&4,c&=S&1;S=O>>7*!(t/=2);O=t=O<<!t>>t|c<<7*t,t=O+=t%2*2-1),I(c&4,D=t=t?t&2?t&1?O^D:O|D:O&D:O,I(c&1,S=D>(t=D+=O+S%2),t=D-O;S=t>D)))S=S&1|t>>6&2|4*!t,I(c&8,C+=!(t&~-t?~t&S:t&~S)*O,I(t,S=S&6|c%2,O=D)))I(C,,Z=0)}}

Experimente online!

Pré-processador

-DO=*o -DD=*d

Esses dois fornecem uma maneira mais curta de desreferenciar os ponteiros.

-DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"

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.

exec_8bit(unsigned char *ram, int ramSize, unsigned char PC)
{  
  for(unsigned char reg[2], SR=0, // The registers. 
                                  // reg[0] is X, reg[1] is A. 
                                  // SR contains the flags.
      *dst, *op, opCode, tmp;
      // Load the next instruction as long as we haven't gone out of ram.
      op = PC < ramSize ? ram + PC++ : 0;)
  { // Check for HLT.
    if(opCode=*op)
    { // Take a pointer to the register selected by complement of bit 3.
      dst = reg+!(opCode&4);
    } else break;
    // Load operand as indicated by bits 0 and 1. Also check that we don't
    // go out of bounds and that the PC doesn't overflow.
    if(op = opCode&3 ? PC<ramSize && PC ? ram + PC++ : 0 : dst)
    {
      op = opCode&2 ? *op + opCode%2 * *reg + ram: op
    } else break;

    // Store the bits 3-5 in tmp.
    tmp = (opCode/=8) & 7;
    if(opCode<24 & opCode>4 && tmp)
    { // If not HLT, CLC, SEC or ST, enter this block.
      tmp &= 3; // We only care about bits 3&4 here.
      if(opCode&8) // Determine whether the operation is binary or unary.
      { // Unary
        if(opCode&4)
        { // Bitshift
          opCode &= SR&1; // Extract carry flag and AND it with bit 3 in opCode.
          SR=*op >> 7*!(tmp/=2);// Update carry flag.
          // Shift to left if bit 4 unset, to right if set. Inclusive-OR 
          // the carry/bit 3 onto the correct end.
          *op = tmp = *op << !tmp >> tmp | opCode << 7*tmp;
        } else tmp=*o+=tmp%2*2-1;
      } else if(opCode&4) {
        // Bitwise operations and LD.
        // Ternary conditional to determine which operation.
        *dst = tmp = tmp? tmp&2? tmp&1? *op^*dst: *op|*dst: *op&*dst: *op
      } else if(opCode&1) {
        // ADC. Abuse precedence to do this in fewer bytes.
        // Updates carry flag.
        SR = *dst > (tmp = *dst += *op + SR%2);
      } else tmp=*dst-*op; SR = tmp > *dst; // Comparison.
      SR = SR&1 | tmp >> 6&2 | 4*!tmp; // Update Z and N flags, leaving C as it is.
    } else if(opCode&8) {
      // Branch.
      // tmp&~-tmp returns a truthy value when tmp has more than one bit set
      // We use this to detect the "unset" and "always" conditions.
      // Then, we bitwise-AND either ~tmp&SR or tmp&~SR to get a falsy value
      // when the condition is fulfilled. Finally, we take logical complement,
      // and multiply the resulting value (`1` or `0`) with the operand,
      // and add the result to program counter to perform the jump.
      PC += !(tmp & ~-tmp? ~tmp&SR : tmp&~SR) * *op;
    } else if (tmp) { // SEC, CLC
      SR = SR&6 | opCode % 2;
    } else {
      *op = *dst; // ST
    }
    if(!PC){ // If program counter looped around, null out ramSize to stop.
           // There's likely a bug here that will kill the program when it
           // branches back to address 0x00
      ramSize=0;
    }
  }
}

Instruções

As instruções estão estruturadas da seguinte maneira:

  • Os bits 6-7 indicam a aridade da instrução ( 00Nulo, Unário 01, 10Binário, 11Binário)

  • Os bits 0-2 determinam o (s) operando (s): R=0seleciona Ae R=1seleciona X. OP=00usa o registrador como um operando, OP=01seleciona operando imediato, OP=10seleciona operando absoluto e OP=11seleciona operando indexado.

    • Como você deve ter notado, isso permite que qualquer operação seja executada em qualquer um dos registradores (embora você ainda possa indexar apenas X) mesmo quando eles normalmente não puderam ser usados ​​por especificação. Por exemplo INC A, ADC X, 10e ASL Xtodo 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, 110testes para transporte não 001definido e para transporte definido. 111e 000ramifique 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=01comporta-se como ramo de especificação.

+-----+----------+-------+-----------------------------------------------+
| OP  | BINARY   | FLAGS | INFO                                          |
+-----+----------+-------+-----------------------------------------------+
| ST  | 10000ROP |       | Register -> Operand                           |
| LD  | 10100ROP | Z N   | Operand -> Register                           |
| AND | 10101ROP | Z N   | Register &= Operand                           |
| XOR | 10111ROP | Z N   | Register ^= Operand                           |
| IOR | 10110ROP | Z N   | Register |= Operand                           |
| ADC | 10011ROP | Z N C | Register += Operand + Carry                   |
| INC | 01011ROP | Z N   | Operand += 1                                  |
| DEC | 01010ROP | Z N   | Operand -= 1                                  |
| ASL | 01100ROP | Z N C | Operand <<= 1                                 |
| LSR | 01110ROP | Z N C | Operand >>= 1                                 |
| ROL | 01101ROP | Z N C | Operand = Operand << 1 | Carry                |
| ROR | 01111ROP | Z N C | Operand = Operand >> 1 | Carry << 7           |
| CMP | 10010ROP | Z N C | Update ZNC based on Register - Operand        |
| BR  | 11CNDROP |       | PC += Condition ? Operand : 0      |
| SEC | 00011000 |     C | Set carry                                     |
| CLC | 00010000 |     C | Clear carry                                   |
| HLT | 00000000 |       | Halt execution.                               |
+-----+----------+-------+-----------------------------------------------+

fonte
7

JavaScript (ES6), 361 bytes

Recebe entrada como (memory)(program_counter), onde memoryé um Uint8Array. Saídas modificando essa matriz.

M=>p=>{for(_='M[\u0011\u0011A]\u0010\u0010=\u000fc=\u000e,\u0011p]\f(n=\u000b128)\t=\u0010\b&=255\u0007,z=!(n\u0007),n&=\t;\u0006\u0006\u000b\u0005-\u0010,\u000en>=0\u0005\u0004\u0011c\b>>7,A]*2\u0005\u0003\u0011c\b&1,A]/2\u0005\u000f\u0002&&(p+=(\u0010^\t-\t;\u0001for(a=n=z=\u000ex=0;a\u0007,x\u0007,A=[i=\u0011p++],p\f\f+x][i&3],i&3&&p++,i&&A<256;)eval(`\u000ba\b\u0006\u000fa;\u000bx\b\u0006\u000fx;\u000ba&\b\u0005a|\b\u0005a^\b\u0005\u000f\u0002\u0003\u000fc*128|\u0002c|\u0003a+\b+c,\u000ea>>8\u0005++\u0010\u0005--\u0010\u0005a\u0004x\u0004++x\u0005--x\u0006\u000e1;\u000e0;1\u0001!z\u0001z\u0001!n\u0001n\u0001!c\u0001c\u0001`.split`;`[i>>2])';G=/[\u0001-\u0011]/.exec(_);)with(_.split(G))_=join(shift());eval(_)}

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

00 - LDX #$10  09 10
02 - INC $01   32 01
04 - DEX       44
05 - BNE $fb   55 fb

Saída esperada:

09 20 32 01 44 55 fb

Caso de teste nº 2

00 - (DATA)    e0 08 2a 02
04 - LDA $00   02 00
06 - ADC $02   2e 02
08 - STA $00   06 00
0a - LDA $01   02 01
0c - ADC $03   2e 03
0e - STA $01   06 01

Saída esperada:

0a 0b 2a 02 02 00 2e 02 06 00 02 01 2e 03 06 01

Caso de teste nº 3

00 - (DATA)    5e 01 28 00
04 - LDX #$10  09 10
06 - LSR $01   1e 01
08 - ROR $00   26 00
0a - BCC $0d   65 0d
0c - LDA $02   02 02
0e - CLC       4c
0f - ADC $21   2e 21
11 - STA $21   06 21
13 - LDA $03   02 03
15 - ADC $22   2e 22
17 - STA $22   06 22
19 - ASL $02   22 02
1b - ROL $03   2a 03
1d - DEX       44
1e - BPL $e6   5d e6
20 - HLT       00
21 - (DATA)    00 00

Saída esperada:

00 00 00 00 09 10 1e 01 ... 44 5d e6 00 b0 36

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.

M => p => {
  for(
    a = n = z = c = x = 0;
    a &= 255, x &= 255,
    A = [i = M[p++], p, M[p], M[p] + x][i & 3],
    i & 3 && p++,
    i && A < 256;
  ) eval((
    '(n = a = M[A], z = !(n &= 255), n &= 128);'                                + // LDA
    'M[A] = a;'                                                                 + // STA
    '(n = x = M[A], z = !(n &= 255), n &= 128);'                                + // LDX
    'M[A] = x;'                                                                 + // STX
    '(n = a &= M[A], z = !(n &= 255), n &= 128);'                               + // AND
    '(n = a |= M[A], z = !(n &= 255), n &= 128);'                               + // ORA
    '(n = a ^= M[A], z = !(n &= 255), n &= 128);'                               + // EOR
    '(n = M[A] = M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);'           + // LSR
    '(n = M[A] = M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'          + // ASL
    '(n = M[A] = c * 128 | M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);' + // ROR
    '(n = M[A] = c | M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'      + // ROL
    '(n = a += M[A] + c, c = a >> 8, z = !(n &= 255), n &= 128);'               + // ADC
    '(n = ++M[A], z = !(n &= 255), n &= 128);'                                  + // INC
    '(n = --M[A], z = !(n &= 255), n &= 128);'                                  + // DEC
    '(n = a - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CMP
    '(n = x - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CPX
    '(n = ++x, z = !(n &= 255), n &= 128);'                                     + // INX
    '(n = --x, z = !(n &= 255), n &= 128);'                                     + // DEX
    'c = 1;'                                                                    + // SEC
    'c = 0;'                                                                    + // CLC
    ' 1 && (p += (M[A] ^ 128) - 128);'                                          + // BRA
    '!z && (p += (M[A] ^ 128) - 128);'                                          + // BNE
    ' z && (p += (M[A] ^ 128) - 128);'                                          + // BEQ
    '!n && (p += (M[A] ^ 128) - 128);'                                          + // BPL
    ' n && (p += (M[A] ^ 128) - 128);'                                          + // BMI
    '!c && (p += (M[A] ^ 128) - 128);'                                          + // BCC
    ' c && (p += (M[A] ^ 128) - 128);')                                           // BCS
    .split`;`[i >> 2]
  )
}
Arnauld
fonte
Impressionante :) Não é um profissional de JS, então - isso está indexando em alguma "matriz de código" pelo valor do opcode? Parece legal. Mas se essa o = ...linha for executada para todas as instruções, isso pode ter "códigos de operação não intencionais"?
Felix Palmen
2
Eu provavelmente deve adicionar um caso de teste: o Agora, eu acho que teria sido melhor para permitir opcodes não intencionais ... validade verifica apenas desperdiçar bytes aqui, mas provavelmente demasiado tarde para mudar as regras :(
Felix Palmen
Bem, eu estava prestes a sugerir exatamente isso, pois ele não adiciona muito ao desafio e agora é difícil verificar com mapeamentos personalizados. Mas você provavelmente deve perguntar / avisar @MaxYekhlakov primeiro, pois eles podem ter implementado a regra corretamente.
Arnauld
c = M[A] >> 7 & 1<- é &1realmente necessário aqui?
Felix Palmen
2
Eu tenho certeza que, como sua submissão é uma função, meu texto era "uma lista de bytes em qualquer formato [...] sensível" e um 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 ...
Felix Palmen
2

PHP, 581 585 555 532 bytes (ainda não competindo)

function t($v){global$f,$c;$f=8*$c|4&$v>>5|2*!$v;}$m=array_slice($argv,2);for($f=0;$p>-1&&$p<$argc-1&&$i=$m[$p=&$argv[1]];$p++)$i&3?eval(explode(_,'$a=$y_$a&=$y_$a|=$y_$a^=$y_$a+=$y+$c;$c=$a>>8_$x=$y_$c=$y&1;$y>>=1_$c=($y*=2)>>8_$y+=$y+$c;$c=$y>>8_$y+=$c<<8;$c=$y&1;$y>>=1_$y--_$y++_$z=$a-$y,$c=$a<$y_$z=$x-$y,$c=$x<$y_$y=$a_$y=$x_'.$y=&$m[[0,++$p,$g=$m[$p],$g+$x][$i&3]])[$i>>=2].'$i<14&&t(${[aaaaaxyyyyyyzz][$i]}&=255);'):($i&32?$p+=($f>>$i/8-4^$i)&1?($y=$m[++$p])-($y>>7<<8):1:($i&8?$f=$f&7|8*$c=$i/4:t($x+=$i/2-9)));print_r($m);

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 .

o mapa de código e aqui está uma visão geral do meu mapeamento:

3-mode instructions:
00: LDA     04: AND     08: ORA     0C: EOR
10: ADC     14: LDX     18: LSR     1C: ASL
20: ROL     24: ROR     28: DEC     2C: INC
30: CMP     34: CPX     38: STA     3C: STX

+1: immediate
+2: absolute
+3: relative

implicit:
00: HLT
10: DEX 14: INX
18: CLC 1C: SEC

relative:
20: BRA         (0)
28: BNE 2C: BEQ (Z)
30: BPL 34: BMI (N)
38: BCC 3C: BCS (C)

nota lateral: o
código 24resulta em um BNV(ramo nunca = 2 bytes NOP);
04, 08,0C São aliás para INX, CLCe SEC
e qualquer coisa acima 3Fou é um de dois bytes NOPou um nome alternativo para as instruções de modo único.

Titus
fonte