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 código de golf , 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,0
basada)Una lista de bytes con una longitud máxima de
256
entradas, 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
A
y - 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ón0
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 A
y X
son 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
HLT
alcanza 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.
0
es 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 1
o 2
).
Todos los códigos de operación que se muestran aquí están en hexadecimal.
LDA
- cargar operando enA
- Códigos de operación: inmediato:,
00
absoluto:,02
indexado:04
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
STA
- almacenarA
en operando- Códigos de operación: inmediato:,
08
absoluto:,0a
indexado:0c
- Códigos de operación: inmediato:,
LDX
- cargar operando enX
- Códigos de operación: inmediato:,
10
absoluto:,12
indexado:14
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
STX
- almacenarX
en operando- Códigos de operación: inmediato:,
18
absoluto:,1a
indexado:1c
- Códigos de operación: inmediato:,
AND
- bit a bit y deA
y operando enA
- Códigos de operación: inmediato:,
30
absoluto:,32
indexado:34
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
ORA
- bit a bit o deA
y operando enA
- Códigos de operación: inmediato:,
38
absoluto:,3a
indexado:3c
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
EOR
- bitor xor (exclusivo o) deA
y operando enA
- Códigos de operación: inmediato:,
40
absoluto:,42
indexado:44
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
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:,
48
absoluto:,4a
indexado:4c
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
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:,
50
absoluto:,52
indexado:54
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
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:,
58
absoluto:,5a
indexado:5c
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
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:,
60
absoluto:,62
indexado:64
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
ADC
- agregar con carry, operand plus carry se agrega aA
, carry se establece en desbordamiento- Códigos de operación: inmediato:,
68
absoluto:,6a
indexado:6c
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
INC
- incrementa el operando en uno- Códigos de operación: inmediato:,
78
absoluto:,7a
indexado:7c
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
DEC
- decremento operando por uno- Códigos de operación: inmediato:,
80
absoluto:,82
indexado:84
- Banderas:
Z
,N
- Códigos de operación: inmediato:,
CMP
- compararA
con operando restando operando deA
, olvidar resultado. El transporte se borra por debajo del flujo, configurado de otra manera- Códigos de operación: inmediato:,
88
absoluto:,8a
indexado:8c
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
CPX
- compararX
- igual queCMP
paraX
- Códigos de operación: inmediato:,
90
absoluto:,92
indexado:94
- Banderas:
Z
,N
,C
- Códigos de operación: inmediato:,
HLT
-- Terminar- Códigos de operación: implícito:
c0
- Códigos de operación: implícito:
INX
- incrementarX
en uno- Códigos de operación: implícito:
c8
- Banderas:
Z
,N
- Códigos de operación: implícito:
DEX
- decrementoX
por uno- Códigos de operación: implícito:
c9
- Banderas:
Z
,N
- Códigos de operación: implícito:
SEC
- establecer la bandera de transporte- Códigos de operación: implícito:
d0
- Banderas
C
- Códigos de operación: implícito:
CLC
- claro llevar la bandera- Códigos de operación: implícito:
d1
- Banderas
C
- Códigos de operación: implícito:
BRA
- rama siempre- Códigos de operación: relativo:
f2
- Códigos de operación: relativo:
BNE
- bifurcar si seZ
borró la bandera- Códigos de operación: relativo:
f4
- Códigos de operación: relativo:
BEQ
- rama si seZ
establece la bandera- Códigos de operación: relativo:
f6
- Códigos de operación: relativo:
BPL
- bifurcar si seN
borró la bandera- Códigos de operación: relativo:
f8
- Códigos de operación: relativo:
BMI
- rama si seN
establece la bandera- Códigos de operación: relativo:
fa
- Códigos de operación: relativo:
BCC
- bifurcar si seC
borró la bandera- Códigos de operación: relativo:
fc
- Códigos de operación: relativo:
BCS
- rama si seC
establece la bandera- Códigos de operación: relativo:
fe
- Códigos de operación: relativo:
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) stderr
y 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 challenge
realice un pago en la rama y realice una git submodule update --init --recursive
clonación posterior para obtener mi sistema de compilación personalizado.
Construya la herramienta con GNU make (solo escriba make
, o gmake
si 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 defecto0
-h
- la entrada está en hexadecimal (de lo contrario binaria)-t
- seguimiento de ejecución astderr
-c convfile
- convertir códigos de operación de acuerdo con una asignación dada enconvfile
-d
- volcar la memoria resultante como datos binarios-x
- volcar la memoria resultante como hexadecimalinitial_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 :)
fuente
BRA
("rama siempre") no introduce una rama en el flujo de control, ¿no debería llamarseJMP
?BRA
existe en diseños de chips posteriores (el 6502 no tiene esa instrucción) como el 65C02 y el MC 68000. tambiénJMP
existe. La diferencia es queBRA
usa direccionamiento relativo yJMP
usa direccionamiento absoluto. Entonces, solo seguí estos diseños; de hecho, no suena tan lógico;)Respuestas:
C (gcc) ,
1381133812551073 bytesGran mejora gracias a ceilingcat y Rogem.
Pruébalo en línea!
Muchas definiciones se movieron a las banderas del compilador.
Explicación (MUY sin golf):
fuente
00
aunque 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 :)APL (Dyalog Classic) ,
397332330 bytesgracias @ Adám por -8 bytes
Pruébalo en línea!
fuente
f←
?C (gcc) ,
487,480,463,452,447, 438 bytesUtiliza 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).Pruébalo en línea!
Preprocesador
Estos dos proporcionan una forma más corta de desreferenciar los punteros.
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.
Instrucciones
Las instrucciones están estructuradas de la siguiente manera:
Los bits 6-7 indican la aridad de la instrucción (
00
Nular, Unario01
,10
Binario,11
Binario)Los bits 0-2 determinan los operandos:
R=0
seleccionaA
yR=1
seleccionaX
.OP=00
usa el registro como un operando,OP=01
selecciona el operando inmediato,OP=10
selecciona el operando absoluto yOP=11
selecciona el operando indexado.X
) incluso cuando normalmente no se pueden usar por especificación. Por ejemploINC A
,ADC X, 10
yASL X
todo 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,
110
pruebas para llevar sin establecer y001
para llevar con conjunto.111
y000
rama 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=01
se comporta como una rama de especificación.fuente
JavaScript (ES6), 361 bytes
Toma entrada como
(memory)(program_counter)
, dondememory
es unUint8Array
. Salidas modificando esta matriz.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
Rendimiento esperado:
Caso de prueba n. ° 2
Rendimiento esperado:
Caso de prueba n. ° 3
Rendimiento esperado:
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.fuente
o = ...
línea se ejecuta para cada instrucción, ¿podría tener "códigos de operación no deseados"?c = M[A] >> 7 & 1
<- ¿es&1
realmente necesario aquí?Uint8Array
hecho 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?PHP,
581 585 555532 bytes (no compitiendo)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:
nota al margen: el
código
24
da como resultado unaBNV
(rama nunca = 2 bytesNOP
);04
,08
,0C
Son los alias deINX
,CLC
ySEC
y algo por encima
3F
es o bien un dos bytesNOP
o un alias para las instrucciones de modo único.fuente