8-битная виртуальная машина


31

Задний план

Мне нравится мой старый 8-битный чип 6502. Здесь даже забавно решить некоторые проблемы на PPCG в машинном коде 6502. Но некоторые вещи, которые должны быть простыми (например, чтение данных или вывод в stdout), излишне громоздки в машинном коде. Так что у меня в голове есть грубая идея: изобрести собственную 8-битную виртуальную машину, которая вдохновлена ​​6502, но с измененным дизайном, чтобы он был более пригодным для решения задач. Начав что-то реализовывать, я понял, что это может быть хорошей задачей, если дизайн виртуальной машины сведен к минимуму :)

задача

Реализуйте 8-битную виртуальную машину, соответствующую следующей спецификации. Это , поэтому выигрывает реализация с наименьшим количеством байтов.

вход

Ваша реализация должна принимать следующие входные данные:

  • Один беззнаковый байт pc, это начальный счетчик программы (адрес в памяти, где виртуальная машина начинает выполнение, на 0основе)

  • Список байтов с максимальной длиной 256записей, это оперативная память для виртуальной машины (с ее начальным содержимым)

Вы можете принять этот вклад в любом разумном формате.

Выход

Список байтов, которые являются окончательным содержимым оперативной памяти после завершения работы виртуальной машины (см. Ниже). Вы можете предположить, что вы получаете только вход, который в конечном итоге приводит к завершению. Разрешен любой разумный формат.

Виртуальный процессор

Виртуальный процессор имеет

  • 8-битный программный счетчик,
  • 8-битный регистр аккумулятора называется Aи
  • вызывается 8-битный индексный регистр X.

Есть три флага состояния:

  • Z - нулевой флаг устанавливается после того, как какая-то операция приводит к 0
  • N - отрицательный флаг устанавливается после того, как некоторые операции приводят к отрицательному числу (установлен бит 7 результата)
  • C - флаг переноса устанавливается добавками и сдвигами для «пропущенного» бита результата

При запуске все флаги сбрасываются, счетчик программ устанавливается на заданное значение, а содержимое Aи Xявляется неопределенным.

8-битные значения представляют либо

  • беззнаковое целое число в диапазоне[0..255]
  • подписал целое число, 2 в дополнение, в диапазоне[-128..127]

в зависимости от контекста. Если операция переполняет или теряет значение, значение оборачивается (и в случае добавления затрагивается флаг переноса).

прекращение

Виртуальная машина завершает работу, когда

  • HLTИнструкция достигается
  • Доступ к несуществующему адресу памяти
  • Счетчик программы работает за пределами памяти (обратите внимание, что он не оборачивается, даже если виртуальной машине предоставлено все 256 байтов памяти)

Режимы адресации

  • неявный - инструкция не имеет аргумента, операнд подразумевается
  • немедленный - операнд является байтом непосредственно после инструкции
  • относительный - (только для ветвления) байт после подписания инструкции (дополнение 2) и определяет смещение, добавляемое к счетчику программы, если берется ветвление. 0расположение следующей инструкции
  • абсолютный - байт после инструкции является адресом операнда
  • индексируется - байт после инструкции плюс X(регистр) является адресом операнда

инструкции

Каждая инструкция состоит из кода операции (один байт) и, в режимах адресации, непосредственного , относительного , абсолютного и индексируемого второго байта аргумента. Когда виртуальный ЦП выполняет инструкцию, он соответственно увеличивает счетчик программы (на 1или 2).

Все коды операций, показанные здесь, в шестнадцатеричном формате.

  • LDA - загрузить операнд в A

    • Коды операций: немедленный:, 00абсолютный:, 02проиндексированный:04
    • Флаги: Z,N
  • STA- сохранить Aв операнде

    • Коды операций: немедленный:, 08абсолютный:, 0aпроиндексированный:0c
  • LDX - загрузить операнд в X

    • Коды операций: немедленный:, 10абсолютный:, 12проиндексированный:14
    • Флаги: Z,N
  • STX- сохранить Xв операнде

    • Коды операций: немедленный:, 18абсолютный:, 1aпроиндексированный:1c
  • AND- побитовое и из Aи операнда вA

    • Коды операций: немедленный:, 30абсолютный:, 32проиндексированный:34
    • Флаги: Z,N
  • ORA- поразрядно или из Aи операнд вA

    • Коды операций: немедленный:, 38абсолютный:, 3aпроиндексированный:3c
    • Флаги: Z,N
  • EOR- побитовый xor (исключая или) Aи операнд вA

    • Коды операций: немедленный:, 40абсолютный:, 42проиндексированный:44
    • Флаги: Z,N
  • LSR - логическое смещение вправо, сдвиг всех битов операнда на одно место вправо, бит 0 переносится

    • Коды операций: немедленный:, 48абсолютный:, 4aпроиндексированный:4c
    • Флаги: Z, N,C
  • ASL - сдвиг арифметики влево, сдвиг всех битов операнда на одну позицию влево, бит 7 идет на перенос

    • Коды операций: немедленный:, 50абсолютный:, 52проиндексированный:54
    • Флаги: Z, N,C
  • ROR - повернуть вправо, сдвинуть все биты операнда на одно место вправо, перенос идет до бита 7, бит 0 - переносить

    • Коды операций: немедленный:, 58абсолютный:, 5aпроиндексированный:5c
    • Флаги: Z, N,C
  • ROL - поверните влево, сдвиньте все биты операнда на одну позицию влево, перенос идет в бит 0, бит 7 идет в перенос

    • Коды операций: немедленный:, 60абсолютный:, 62проиндексированный:64
    • Флаги: Z, N,C
  • ADC- добавить с переносом, добавлен операнд плюс перенос A, перенос установлен на переполнение

    • Коды операций: немедленный:, 68абсолютный:, 6aпроиндексированный:6c
    • Флаги: Z, N,C
  • INC - увеличить операнд на единицу

    • Коды операций: немедленный:, 78абсолютный:, 7aпроиндексированный:7c
    • Флаги: Z,N
  • DEC - уменьшить операнд на единицу

    • Коды операций: немедленный:, 80абсолютный:, 82проиндексированный:84
    • Флаги: Z,N
  • CMP- сравните Aс операндом, вычитая операнд из A, забудьте результат. Перенос очищается при потере, устанавливается иначе

    • Коды операций: немедленный:, 88абсолютный:, 8aпроиндексированный:8c
    • Флаги: Z, N,C
  • CPX- сравнить X- так же, как CMPдляX

    • Коды операций: немедленный:, 90абсолютный:, 92проиндексированный:94
    • Флаги: Z, N,C
  • HLT - прекратить

    • Коды операций: неявный: c0
  • INX- увеличение Xна единицу

    • Коды операций: неявный: c8
    • Флаги: Z,N
  • DEX- уменьшение Xна единицу

    • Коды операций: неявный: c9
    • Флаги: Z,N
  • SEC - установить флаг переноса

    • Коды операций: неявный: d0
    • Флаги: C
  • CLC - снять флажок

    • Коды операций: неявный: d1
    • Флаги: C
  • BRA - ветка всегда

    • Коды операций: относительный: f2
  • BNE- ветвь, если Zфлаг снят

    • Коды операций: относительный: f4
  • BEQ- ветвь, если Zустановлен флаг

    • Коды операций: относительный: f6
  • BPL- ветвь, если Nфлаг снят

    • Коды операций: относительный: f8
  • BMI- ветвь, если Nустановлен флаг

    • Коды операций: относительный: fa
  • BCC- ветвь, если Cфлаг снят

    • Коды операций: относительный: fc
  • BCS- ветвь, если Cустановлен флаг

    • Коды операций: относительный: fe

Opcodes

Поведение виртуальной машины не определено, если найден какой-либо код операции, который не соответствует действительной инструкции из приведенного выше списка.

Согласно запросу Джонатана Аллана , вы можете выбрать свой собственный набор кодов операций вместо кодов операций, показанных в разделе « Инструкции ». Если вы это сделаете, вы должны добавить полное сопоставление к кодам операций, использованным выше в вашем ответе.

Сопоставление должно быть шестнадцатеричным файлом с парами <official opcode> <your opcode>, например, если вы заменили два кода операции:

f4 f5
10 11

Новые строки здесь не имеют значения.

Тестовые случаи (официальные коды операций)

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

Я мог бы добавить больше тестовых случаев позже.

Справка и тестирование

Чтобы помочь с собственными экспериментами, вот некоторая (полностью не игровая) эталонная реализация - она ​​может выводить информацию трассировки (включая разобранные инструкции) stderrи конвертировать коды операций во время работы.

Рекомендуемый способ получения источника:

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

Или оформите ветку challengeи сделайте git submodule update --init --recursiveпосле клонирования, чтобы получить мою собственную систему сборки.

Создайте инструмент с помощью GNU make (просто введите makeили, gmakeесли в вашей системе, по умолчанию make не GNU make).

Использование :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc - начальный счетчик программы, по умолчанию 0
  • -h - ввод в шестнадцатеричном (иначе двоичный)
  • -t - отслеживать выполнение до stderr
  • -c convfile - конвертировать коды операций в соответствии с отображением, приведенным в convfile
  • -d - сбросить результирующую память в виде двоичных данных
  • -x - дамп результирующей памяти в виде шестнадцатеричного
  • initial_ram - начальное содержимое ОЗУ, либо шестнадцатеричное, либо двоичное

Обратите внимание, что функция преобразования не будет работать в программах, которые изменяют коды операций во время работы.

Отказ от ответственности: приведенные выше правила и спецификации являются авторскими для вызова, а не для этого инструмента. Это особенно относится к функции преобразования кода операции. Если вы считаете, что инструмент, представленный здесь, имеет ошибку в спецификации, сообщите об этом в комментарии :)


1
Я предположил бы, что, вероятно, будет множество возможностей для игры в гольф, если выбрать различные коды операций для инструкций, но кажется, что коды операций были исправлены (хотя набор команд определяет машину). Может быть, стоит подумать о том, чтобы реализации имели собственную кодовую страницу?
Джонатан Аллан

1
@JonathanAllan дважды подумал об этом, я позволю это сейчас и мог бы добавить инструмент «преобразования», чтобы сделать решения, использующие другие наборы кодов операций, легко тестируемыми.
Феликс Пальмен,

1
@Arnauld, между прочим, я решил, что это должно было уменьшить количество особых случаев, поэтому он должен быть лучше "пригодным для игры в гольф" - каждый код операции является либо неявным, либо относительным переходом, либо разрешает все три других режима адресации :)
Феликс Палмен,

1
если BRA("ветвь всегда") не вводит ветвь в потоке управления, не должна ли она быть вызвана JMP?
СПП

1
@ngn, BRAсуществует в более поздних конструкциях чипов (у 6502 такой инструкции нет), таких как 65C02 и MC 68000. JMPСуществует также. Разница в том, что BRAиспользуется относительная адресация и JMPабсолютная адресация. Итак, я просто следовал этим проектам - действительно, это не звучит так логично;)
Феликс Палмен

Ответы:


16

C (gcc) , 1381 1338, 1255, 1073 байта

Огромное улучшение благодаря потолку и 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]();}

Попробуйте онлайн!

Много определений перенесено во флаги компилятора.

Пояснение (ОЧЕНЬ без присмотра):

#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] ();
}

Отличная работа, мгновенный +1, действительно не ожидал решения C первым :) Ваше добавление 00байтов может немного нарушать правила ... Я признаю, что не пытался анализировать этот код ... не могли бы вы сохранить байты, делая ввод / вывод в двоичном формате вместо шестнадцатеричного? Будет разрешено по правилам :)
Феликс Пальмен

Я хотел бы заменить правило, согласно которому незаконные коды операций приводят к прекращению, просто сказав, что поведение незаконных кодов операций не определено ... это повредит вашему ответу или вас это устраивает?
Феликс Палмен

@FelixPalmen хорошо, поскольку прекращение вполне допустимое «неопределенное» поведение, оно не повредит (вместо этого он открывает новую возможность для игры в гольф!)
Макс Ехлаков

@MaxYekhlakov под "обидой" Я имел в виду несправедливость по отношению к вашему решению, потому что вы, возможно, "потратили байты" на то, чтобы убедиться, что незаконный код операции завершает работу виртуальной машины. Я рад, что вы приветствуете изменение правила как возможность :) И снова, поздравляю, я просто люблю видеть решение в C, который является моим самым любимым языком программирования на все времена. Жаль, что вы редко выигрываете соревнование по коду для гольфа в C - тем не менее, «игра в гольф» - это просто круто :)
Феликс Палмен,

Не могли бы вы добавить часть флагов для публикации?
14 м2 18

8

APL (Dyalog Classic) , 397 332 330 байтов

спасибо @ Adám за -8 байт

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

Попробуйте онлайн!

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


Есть ли в этом решении непреднамеренные коды операций, и если нет, то потратили ли вы байты, избегая их? Смотрите этот комментарий по той причине, о которой я спрашиваю ...
Феликс Пальмен,

@FelixPalmen Теперь, когда вы упомянули об этом, да :( Изначально я соблюдал это правило, но когда я играл в гольф, я случайно сделал 4, 5 и, возможно, другие, действительные коды операций. Поэтому решение сделать их поведение неопределенным было бы очень кстати :)
нгн

2
Сделано сейчас, я понимаю, что это было не самое лучшее решение, и @MaxYekhlakov, к сожалению, не пришлось ничего говорить об изменении правила.
Феликс Пальмен,

Вам нужно f←?
Эрик Outgolfer

8

C (gcc) , 487 , 480 , 463 , 452 , 447 , 438 байтов

Использует это отображение инструкций . Обновление инструкций сбило 9 байтов и, возможно, больше в будущем. Возвращает путем изменения памяти, на которую указывает первый аргумент ( M). Спасибо @ceilingcat за то, что сбрил несколько байтов.

Должен быть скомпилирован с флагами -DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(уже включены в байты).

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

Попробуйте онлайн!

препроцессор

-DO=*o -DD=*d

Эти два предоставляют более короткий способ разыменования указателей.

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

Уменьшите количество байтов, необходимых для if-elses и объявлений типов.

Код

Ниже приведена удобочитаемая версия кода с расширенными директивами препроцессора и переименованными переменными для удобства чтения.

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

инструкции

Инструкции структурированы следующим образом:

  • Биты 6-7 указывают на арность инструкции ( 00Nullary, Unary 01, 10Binary, 11Binary)

  • Биты 0-2 определяют операнд (ы): R=0выбирает AиR=1 выбирает X. OP=00использует регистр в качестве операнда, OP=01выбирает непосредственный операнд, OP=10выбирает абсолютный операнд и OP=11выбирает индексированный операнд.

    • Как вы могли заметить, это позволяет выполнять любые операции с любым регистром (хотя вы по-прежнему можете индексировать только из X ), даже если они обычно не могут использоваться согласно спецификации. Например INC A, ADC X, 10и ASL Xвсе работает.
  • Биты 3-5 определяют условие ветвления: наличие одного из битов указывает, какой флаг проверять (бит 3-> C, бит 4-> N, бит 5-> Z). Если установлен только один бит, инструкция проверяет наличие установленного флага. Чтобы проверить наличие неустановленного флага, возьмите дополнение битов. Например, 110тесты для неснятого переноса и 001для установленного переноса. 111и 000ветка всегда.

  • Вы также можете переходить к смещению адреса, хранящегося в регистре, что позволяет вам писать функции, или вы можете использовать стандартные режимы индексации. OP=01ведет себя как ветвь спецификации.

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

7

JavaScript (ES6), 361 байт

Принимает как (memory)(program_counter), где memoryнаходится Uint8Array.Выходы путем изменения этого массива.

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: код сжат RegPack и содержит много непечатаемых символов, которые экранируются в приведенном выше представлении источника.

Попробуйте онлайн!

Отображение кода операции и контрольные примеры

Виртуальная машина использует это отображение кода операции .

Ниже приведены переведенные тесты и ожидаемые результаты.

Тестовый пример № 1

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

Ожидаемый результат:

09 20 32 01 44 55 фб

Тестовый пример № 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

Ожидаемый результат:

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

Тестовый пример № 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

Ожидаемый результат:

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

Распакован и отформатирован

Поскольку код сжимается с помощью алгоритма, который заменяет часто повторяющиеся строки одним символом, более эффективно использовать одни и те же блоки кода снова и снова, чем определять и вызывать вспомогательные функции или сохранять промежуточные результаты (например, M[A]) в дополнительных переменных.

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]
  )
}

Впечатляет :) Не профессионал JS, так что - это индексирование в некоторый «массив кода» по значению кода операции? выглядит хорошо. Но если эта o = ...строка выполняется для каждой инструкции, это может иметь «непреднамеренные коды операций»?
Феликс Пальмен

2
Я, вероятно, должен добавить тестовый пример: o Теперь я думаю, что было бы лучше разрешить непреднамеренные коды операций ... проверка достоверности просто тратит здесь байты, но, вероятно, слишком поздно, чтобы изменить правила :(
Феликс Пальмен

Что ж, я собирался предложить именно это, так как это не сильно усложняет задачу, и теперь это сложно проверить с помощью пользовательских сопоставлений. Но вы, вероятно, должны сначала спросить / предупредить @MaxYekhlakov, поскольку они могли правильно реализовать правило.
Арнаулд

c = M[A] >> 7 & 1<- &1действительно нужно здесь?
Феликс Пальмен

2
Я почти уверен, что так как ваше представление в любом случае является функцией, моя формулировка была «список байтов [...] любого разумного формата» и Uint8Arrayдействительно просто заключает в себе такой список байтов. Поэтому, если размещение байтов в шестнадцатеричной строке является приемлемым способом представления входных данных, почему запрещается помещать их в контейнерный объект ...
Феликс Пальмен,

2

PHP, 581 585 555 532 байта (не конкурирует)

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

принимает коды ПК и OP в качестве целых 10-ти целых чисел из аргументов командной строки,
печатает память в виде списка [base 10 address] => base 10 value.

Это еще не проверено полностью ; но есть поломка .

Вот карта кодов и обзор моего отображения:

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)

примечание:
код 24приводит к BNV(ветвь никогда не = 2 байта NOP);
04, 08, 0CЯвляются псевдонимами для INX, CLCи SEC
и все , что выше 3Fявляется либо два байта NOPили псевдоним для инструкции одномодовых.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.