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


31

Фон

Мені подобається мій старий 8-бітний чіп 6502. Це навіть цікаво вирішити деякі завдання тут на PPCG в машинному коді 6502. Але деякі речі, які повинні бути простими (наприклад, читання даних або вихід у stdout), надмірно громіздко робити в машинному коді. Тож у мене на думці є груба ідея: Винайдіть власну 8-бітну віртуальну машину, яка надихається 6502, але з модифікованим дизайном, щоб бути більш придатним для вирішення проблем. Почавши щось реалізовувати, я зрозумів, що це може бути приємним завданням, якщо дизайн VM буде зведений до мінімуму :)

Завдання

Реалізуйте 8-бітну віртуальну машину відповідно до наведених нижче специфікацій. Це , тому реалізація з найменшими байтами виграє.

Вхідні дані

Ваша реалізація повинна мати такі входи:

  • Єдиний непідписаний байт pc, це початковий лічильник програми (адреса в пам'яті, де VM починає виконання, на 0базі)

  • Список байтів з максимальною довжиною 256записів, це ОЗУ для віртуальної машини (з її початковим вмістом)

Ви можете взяти цей ввід у будь-якому розумному форматі.

Вихід

Список байтів, які є кінцевим вмістом оперативної пам’яті після завершення роботи VM (див. Нижче). Можна припустити, що ви отримуєте лише вхід, який призводить до припинення в кінцевому рахунку. Дозволений будь-який розумний формат.

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

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

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

Є три прапори стану:

  • Z - нульовий прапор встановлюється після деякої операції в 0
  • N - від'ємний прапор встановлюється після деякої операції, яка призводить до від'ємного числа (встановлено iow біт 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

Опкоди

Поведінка VM не визначена, якщо знайдено який-небудь опкод, який не відповідає дійсній інструкції із наведеного вище списку.

Відповідно до запиту Джонатана Аллана , ви можете вибрати власний набір кодів замість опкодів, показаних у розділі Інструкції . Якщо ви це зробите, ви повинні додати повне відображення до описаних вище кодів у своїй відповіді.

Відображення має бути шістнадцятковим файлом з парами <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 btw моє міркування про те, що це дозволило зменшити кількість спеціальних випадків, тому воно повинно бути краще "гольфуючим" - кожен опкод або неявний, відносна гілка або дозволяє всі три інші режими адресації :)
Фелікс Палмен,

1
якщо BRA("гілка завжди") не вводить гілку в контрольний потік, чи не слід її називати JMP?
ngn

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 під "пошкодженням" Я мав на увазі несправедливе ставлення до вашого рішення, тому що ви, можливо, "витратили байти" на те, щоб переконатися, що незаконне кодування скасовує vm. Я радий, що ви вітаєте зміну правила як можливість :) І ще раз, вітаю, я просто люблю бачити рішення на мові C, яка є моєю улюбленою мовою програмування за весь час. Шкода, що ти рідко виграєш виклик з гольф-кодом у С - все-таки "гольф" С - це просто класно :)
Фелікс Палмен

Чи можете ви додати частину прапорів для публікації?
l4m2

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


Чи є в цьому рішенні ненавмисні опкоди, і якщо ні, чи витрачали ви байти, уникаючи їх? Дивіться цей коментар з тієї причини , що я прошу ...
Фелікс Palmen

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

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

Вам потрібно f←?
Ерік Аутгольфер

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 вказують на 00суворість інструкції ( Nullary, 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(_)}

Примітка: Код стискається з 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 fb

Тест №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 2а 02 02 00 2е 02 06 00 02 01 2е 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 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.