Máquina virtual de 8 bits

31

Fondo

Me gusta mi viejo chip 6502 de 8 bits. Incluso es divertido resolver algunos de los desafíos aquí en PPCG en el código de máquina 6502. Pero algunas cosas que deberían ser simples (como leer datos o enviar a stdout) son innecesariamente engorrosas de hacer en el código de máquina. Así que tengo una idea aproximada: inventar mi propia máquina virtual de 8 bits inspirada en el 6502, pero con el diseño modificado para que sea más útil para los desafíos. Comenzando a implementar algo, me di cuenta de que este podría ser un buen desafío en sí mismo si el diseño de la VM se reduce al mínimo :)

Tarea

Implemente una máquina virtual de 8 bits conforme a la siguiente especificación. Este es el , por lo que gana la implementación con la menor cantidad de bytes.

Entrada

Su implementación debe tomar las siguientes entradas:

  • Un solo byte sin signo pc, este es el contador inicial del programa (la dirección en la memoria donde la VM comienza la ejecución, 0basada)

  • Una lista de bytes con una longitud máxima de 256entradas, esta es la RAM para la máquina virtual (con su contenido inicial)

Puede tomar esta entrada en cualquier formato razonable.

Salida

Una lista de bytes que son los contenidos finales de la RAM después de que la VM termina (ver más abajo). Puede suponer que solo obtiene información que conduce a la terminación eventualmente. Se permite cualquier formato sensible.

CPU virtual

La CPU virtual tiene

  • un contador de programa de 8 bits,
  • un registro acumulador de 8 bits llamado Ay
  • un registro de índice de 8 bits llamado X.

Hay tres banderas de estado:

  • Z - el indicador de cero se establece después de que algunos resultados de operación 0
  • N - el indicador negativo se establece después de que algunas operaciones den como resultado un número negativo (se establece el bit 7 del resultado)
  • C - el indicador de acarreo se establece mediante adiciones y cambios para el bit "faltante" del resultado

Al inicio, todos los indicadores se borran, el contador del programa se establece en un valor dado y los contenidos de Ay Xson indeterminados.

Los valores de 8 bits representan

  • un entero sin signo en el rango[0..255]
  • una firmada número entero, complemento a 2, en el rango[-128..127]

Dependiendo del contexto. Si una operación se desborda o se desborda, el valor se ajusta (y en caso de una adición, el indicador de acarreo se ve afectado).

Terminación

La máquina virtual termina cuando

  • Se HLTalcanza una instrucción.
  • Se accede a una dirección de memoria no existente
  • El contador del programa se ejecuta fuera de la memoria (tenga en cuenta que no se ajusta incluso si la VM recibe los 256 bytes completos de memoria)

Modos de direccionamiento

  • implícito : la instrucción no tiene argumento, el operando está implícito
  • inmediato : el operando es el byte directamente después de la instrucción
  • relativo : (solo para bifurcación) el byte después de que se firma la instrucción (complemento de 2) y determina el desplazamiento que se agregará al contador del programa si se toma la bifurcación. 0es la ubicación de las siguientes instrucciones
  • absoluto : el byte después de la instrucción es la dirección del operando
  • indexado : el byte después de la instrucción plus X(el registro) es la dirección del operando

Instrucciones

Cada instrucción consta de un código de operación (un byte) y, en los modos de direccionamiento inmediato , relativo , absoluto e indexado, un segundo byte de argumento. Cuando la CPU virtual ejecuta una instrucción, incrementa el contador del programa en consecuencia (por 1o 2).

Todos los códigos de operación que se muestran aquí están en hexadecimal.

  • LDA - cargar operando en A

    • Códigos de operación: inmediato:, 00absoluto:, 02indexado:04
    • Banderas: Z,N
  • STA- almacenar Aen operando

    • Códigos de operación: inmediato:, 08absoluto:, 0aindexado:0c
  • LDX - cargar operando en X

    • Códigos de operación: inmediato:, 10absoluto:, 12indexado:14
    • Banderas: Z,N
  • STX- almacenar Xen operando

    • Códigos de operación: inmediato:, 18absoluto:, 1aindexado:1c
  • AND- bit a bit y de Ay operando enA

    • Códigos de operación: inmediato:, 30absoluto:, 32indexado:34
    • Banderas: Z,N
  • ORA- bit a bit o de Ay operando enA

    • Códigos de operación: inmediato:, 38absoluto:, 3aindexado:3c
    • Banderas: Z,N
  • EOR- bitor xor (exclusivo o) de Ay operando enA

    • Códigos de operación: inmediato:, 40absoluto:, 42indexado:44
    • Banderas: Z,N
  • LSR - desplazamiento lógico a la derecha, desplazar todos los bits de operando un lugar a la derecha, el bit 0 va a llevar

    • Códigos de operación: inmediato:, 48absoluto:, 4aindexado:4c
    • Banderas: Z, N,C
  • ASL - desplazamiento aritmético a la izquierda, desplazar todos los bits del operando un lugar a la izquierda, el bit 7 va a llevar

    • Códigos de operación: inmediato:, 50absoluto:, 52indexado:54
    • Banderas: Z, N,C
  • ROR - rotar a la derecha, desplazar todos los bits de operando un lugar a la derecha, llevar va al bit 7, bit 0 va a llevar

    • Códigos de operación: inmediato:, 58absoluto:, 5aindexado:5c
    • Banderas: Z, N,C
  • ROL - rotar a la izquierda, desplazar todos los bits del operando un lugar a la izquierda, llevar va al bit 0, bit 7 va a llevar

    • Códigos de operación: inmediato:, 60absoluto:, 62indexado:64
    • Banderas: Z, N,C
  • ADC- agregar con carry, operand plus carry se agrega a A, carry se establece en desbordamiento

    • Códigos de operación: inmediato:, 68absoluto:, 6aindexado:6c
    • Banderas: Z, N,C
  • INC - incrementa el operando en uno

    • Códigos de operación: inmediato:, 78absoluto:, 7aindexado:7c
    • Banderas: Z,N
  • DEC - decremento operando por uno

    • Códigos de operación: inmediato:, 80absoluto:, 82indexado:84
    • Banderas: Z,N
  • CMP- comparar Acon operando restando operando de A, olvidar resultado. El transporte se borra por debajo del flujo, configurado de otra manera

    • Códigos de operación: inmediato:, 88absoluto:, 8aindexado:8c
    • Banderas: Z, N,C
  • CPX- comparar X- igual que CMPparaX

    • Códigos de operación: inmediato:, 90absoluto:, 92indexado:94
    • Banderas: Z, N,C
  • HLT -- Terminar

    • Códigos de operación: implícito: c0
  • INX- incrementar Xen uno

    • Códigos de operación: implícito: c8
    • Banderas: Z,N
  • DEX- decremento Xpor uno

    • Códigos de operación: implícito: c9
    • Banderas: Z,N
  • SEC - establecer la bandera de transporte

    • Códigos de operación: implícito: d0
    • Banderas C
  • CLC - claro llevar la bandera

    • Códigos de operación: implícito: d1
    • Banderas C
  • BRA - rama siempre

    • Códigos de operación: relativo: f2
  • BNE- bifurcar si se Zborró la bandera

    • Códigos de operación: relativo: f4
  • BEQ- rama si se Zestablece la bandera

    • Códigos de operación: relativo: f6
  • BPL- bifurcar si se Nborró la bandera

    • Códigos de operación: relativo: f8
  • BMI- rama si se Nestablece la bandera

    • Códigos de operación: relativo: fa
  • BCC- bifurcar si se Cborró la bandera

    • Códigos de operación: relativo: fc
  • BCS- rama si se Cestablece la bandera

    • Códigos de operación: relativo: fe

Opcodes

El comportamiento de la VM no está definido si se encuentra algún código de operación que no se correlacione con una instrucción válida de la lista anterior.

Según la solicitud de Jonathan Allan , puede elegir su propio conjunto de códigos de operación en lugar de los códigos de operación que se muestran en la sección de Instrucciones . Si lo hace, debe agregar una asignación completa a los códigos de operación utilizados anteriormente en su respuesta.

La asignación debe ser un archivo hexadecimal con pares <official opcode> <your opcode>, por ejemplo, si reemplazó dos códigos de operación:

f4 f5
10 11

Las nuevas líneas no importan aquí.

Casos de prueba (códigos de operación oficiales)

// 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

Podría agregar más casos de prueba más tarde.

Referencia y prueba

Para ayudar con sus propios experimentos, aquí hay alguna implementación de referencia (totalmente no desarrollada) : puede generar información de rastreo (incluidas las instrucciones desensambladas) stderry convertir códigos de operación mientras se ejecuta.

Forma recomendada de obtener la fuente:

git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules

O challengerealice un pago en la rama y realice una git submodule update --init --recursiveclonación posterior para obtener mi sistema de compilación personalizado.

Construya la herramienta con GNU make (solo escriba make, o gmakesi en su sistema, el make predeterminado no es GNU make).

Uso :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc - el contador inicial del programa, por defecto 0
  • -h - la entrada está en hexadecimal (de lo contrario binaria)
  • -t - seguimiento de ejecución a stderr
  • -c convfile - convertir códigos de operación de acuerdo con una asignación dada en convfile
  • -d - volcar la memoria resultante como datos binarios
  • -x - volcar la memoria resultante como hexadecimal
  • initial_ram - el contenido inicial de RAM, ya sea hexadecimal o binario

Tenga en cuenta que la función de conversión fallará en los programas que modifican los códigos de operación mientras se ejecutan.

Descargo de responsabilidad: las reglas y especificaciones anteriores son autorizadas para el desafío, no esta herramienta. Esto se aplica especialmente a la función de conversión de opcode. Si cree que la herramienta presentada aquí tiene un error con las especificaciones, informe en un comentario :)

Felix Palmen
fuente
1
Me imagino que probablemente habría numerosas oportunidades de golf al elegir diferentes códigos de operación para las instrucciones, pero parece que los códigos de operación se han solucionado (aunque el conjunto de instrucciones es lo que define la máquina). ¿Quizás vale la pena considerar permitir que las implementaciones tengan su propia página de códigos?
Jonathan Allan
1
@JonathanAllan lo pensó dos veces, lo estoy permitiendo ahora y podría agregar una herramienta de "conversión" para hacer que las soluciones que utilizan otros conjuntos de códigos de operación sean fácilmente comprobables.
Felix Palmen
1
@Arnauld por cierto, mi razonamiento para permitir esto era reducir la cantidad de casos especiales, por lo que debería ser mejor "golfable": cada código de operación es implícito, una rama relativa o permite los otros tres modos de direccionamiento :)
Felix Palmen
1
si BRA("rama siempre") no introduce una rama en el flujo de control, ¿no debería llamarse JMP?
ngn
1
@ngn bien, BRAexiste en diseños de chips posteriores (el 6502 no tiene esa instrucción) como el 65C02 y el MC 68000. también JMPexiste. La diferencia es que BRAusa direccionamiento relativo y JMPusa direccionamiento absoluto. Entonces, solo seguí estos diseños; de hecho, no suena tan lógico;)
Felix Palmen

Respuestas:

16

C (gcc) , 1381 1338 1255 1073 bytes

Gran mejora gracias a ceilingcat y 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]();}

Pruébalo en línea!

Muchas definiciones se movieron a las banderas del compilador.

Explicación (MUY sin golf):

#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
fuente
Gran trabajo, instantáneo +1, realmente no esperaba una solución C primero :) 00aunque su agregado de bytes podría estar un poco doblando las reglas ... Admito que no intenté analizar este código ... ¿podría guardar bytes haciendo E / S en binario en lugar de hexadecimal? Sería permitido por las reglas :)
Felix Palmen
Me gustaría reemplazar la regla de que los códigos de operación ilegales conducen a la terminación simplemente diciendo que el comportamiento de los códigos de operación ilegales no está definido ... ¿dañaría esto su respuesta o está de acuerdo con eso?
Felix Palmen
@FelixPalmen bien, siempre y cuando la terminación sea un comportamiento "indefinido" bastante válido, no dolerá (¡abre una nueva posibilidad para jugar golf en su lugar!)
Max Yekhlakov
@MaxYekhlakov por "herir" me refería a ser injusto con su solución porque quizás "gastó bytes" para asegurarse de que un código de operación ilegal termina el vm. Me alegra que acoja con satisfacción el cambio de regla como una oportunidad :) Y nuevamente, felicidades, me encanta ver una solución en C, que es mi lenguaje de programación favorito de todos los tiempos. Es una pena que rara vez ganes un desafío de código de golf en C - sin embargo, "golf" C es simplemente genial :)
Felix Palmen
¿Podría agregar la parte de banderas para publicar?
l4m2
8

APL (Dyalog Classic) , 397 332 330 bytes

gracias @ 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←⍵}

Pruébalo en línea!

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
fuente
Continuemos esta discusión en el chat .
ngn
¿Esta solución tiene códigos de operación no deseados y si no, gastó bytes evitándolos? Vea este comentario por la razón por la que pregunto ...
Felix Palmen
@FelixPalmen Ahora que lo menciona, sí :( Inicialmente observé esa regla, pero mientras jugaba al golf accidentalmente hice 4, 5 y posiblemente otros códigos de operación válidos. Por lo tanto, una decisión de hacer que su comportamiento sea indefinido sería muy bienvenida :)
ngn
2
hecho ahora, me doy cuenta de que no fue la mejor decisión en primer lugar y, lamentablemente, @MaxYekhlakov no tuvo que decir nada sobre el cambio de reglas.
Felix Palmen
¿Necesita f←?
Erik the Outgolfer
8

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

Utiliza este mapeo de instrucciones . La actualización de las instrucciones eliminó 9 bytes, y potencialmente más en el futuro. Regresa modificando la memoria apuntada por el primer argumento ( M). Gracias a @ceilingcat por eliminar algunos bytes.

Debe compilarse con banderas -DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(ya incluidas en 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)}}

Pruébalo en línea!

Preprocesador

-DO=*o -DD=*d

Estos dos proporcionan una forma más corta de desreferenciar los punteros.

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

Reduzca el número de bytes necesarios para if-elses y declaraciones de tipo.

Código

La siguiente es una versión del código legible por humanos, con las directivas del preprocesador expandidas y las variables renombradas para legibilidad.

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;
    }
  }
}

Instrucciones

Las instrucciones están estructuradas de la siguiente manera:

  • Los bits 6-7 indican la aridad de la instrucción ( 00Nular, Unario 01, 10Binario, 11Binario)

  • Los bits 0-2 determinan los operandos: R=0selecciona Ay R=1selecciona X. OP=00usa el registro como un operando, OP=01selecciona el operando inmediato, OP=10selecciona el operando absoluto y OP=11selecciona el operando indexado.

    • Como habrás notado, esto permite que cualquier operación se realice en cualquiera de los registros (aunque todavía solo puedes indexar X) incluso cuando normalmente no se pueden usar por especificación. Por ejemplo INC A, ADC X, 10y ASL Xtodo el trabajo.
  • Los bits 3-5 determinan la condición para la ramificación: tener uno de los bits indica qué bandera probar (bit 3-> C, bit 4-> N, bit 5-> Z). Si solo se establece un bit, la instrucción prueba una marca de conjunto. Para probar una bandera no establecida, tome el complemento de los bits. Por ejemplo, 110pruebas para llevar sin establecer y 001para llevar con conjunto. 111y 000rama siempre.

  • También puede ramificarse a un desplazamiento de dirección almacenado en un registro, lo que le permite escribir funciones, o puede usar los modos de indexación estándar. OP=01se comporta como una rama de especificación.

+-----+----------+-------+-----------------------------------------------+
| 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.                               |
+-----+----------+-------+-----------------------------------------------+

fuente
7

JavaScript (ES6), 361 bytes

Toma entrada como (memory)(program_counter), donde memoryes un Uint8Array. Salidas modificando esta 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(_)}

NB: El código está comprimido con RegPack y contiene muchos caracteres no imprimibles, que se escapan en la representación anterior de la fuente.

Pruébalo en línea!

Opcode mapping y casos de prueba

La máquina virtual utiliza esta asignación de código de operación .

A continuación se muestran los casos de prueba traducidos, junto con los resultados esperados.

Caso de prueba n. ° 1

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

Rendimiento esperado:

09 20 32 01 44 55 fb

Caso de prueba 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

Rendimiento esperado:

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

Caso de prueba 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

Rendimiento esperado:

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

Desempaquetado y formateado

Debido a que el código se comprime con un algoritmo que reemplaza cadenas repetidas con frecuencia con un solo carácter, es más eficiente usar los mismos bloques de código una y otra vez que definir e invocar funciones auxiliares, o almacenar resultados intermedios (como M[A]) en variables adicionales.

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
fuente
Impresionante :) No es un profesional de JS, entonces, ¿esta indexación en alguna "matriz de código" por el valor del código de operación? se ve bien. Pero si esta o = ...línea se ejecuta para cada instrucción, ¿podría tener "códigos de operación no deseados"?
Felix Palmen
2
Probablemente debería agregar un caso de prueba: o Ahora, creo que hubiera sido mejor permitir códigos de operación no deseados ... las verificaciones de validez solo desperdician bytes aquí, pero probablemente sea demasiado tarde para cambiar las reglas :(
Felix Palmen
Bueno, estaba a punto de sugerir exactamente eso, ya que no agrega mucho al desafío y ahora es difícil de verificar con mapeos personalizados. Pero probablemente debería preguntar / advertir a @MaxYekhlakov primero, ya que pueden haber implementado la regla correctamente.
Arnauld
c = M[A] >> 7 & 1<- ¿es &1realmente necesario aquí?
Felix Palmen
2
Estoy bastante seguro de que lo haría, ya que su envío es una función de todos modos, mi redacción era "una lista de bytes [...] de cualquier formato sensible" y de Uint8Arrayhecho simplemente encapsula dicha lista de bytes. Entonces, si poner los bytes en una cadena hexadecimal es una forma aceptable de representar la entrada, ¿por qué debería estar prohibido ponerlos en un objeto contenedor?
Felix Palmen
2

PHP, 581 585 555 532 bytes (no compitiendo)

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);

toma los códigos de PC y OP como enteros de base 10 de los argumentos de la línea de comandos,
imprime la memoria como una lista de [base 10 address] => base 10 value.

Esto aún no se ha probado a fondo ; Pero hay un colapso .

Ahí está el mapa de códigos y aquí hay una descripción general de mi mapeo:

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 al margen: el
código 24da como resultado una BNV(rama nunca = 2 bytes NOP);
04, 08, 0CSon los alias de INX, CLCy SEC
y algo por encima 3Fes o bien un dos bytes NOPo un alias para las instrucciones de modo único.

Tito
fuente