8-bitowa maszyna wirtualna

31

tło

Lubię mój stary 8-bitowy układ 6502. Zabawne jest nawet rozwiązywanie niektórych problemów tutaj na PPCG w kodzie maszynowym 6502. Ale niektóre rzeczy, które powinny być proste (jak wczytywanie danych lub wysyłanie do standardowego wyjścia) są niepotrzebnie kłopotliwe w kodzie maszynowym. Mam więc w głowie nieokreślony pomysł: wynajdź własną 8-bitową maszynę wirtualną zainspirowaną 6502, ale ze zmienioną konstrukcją, aby była bardziej użyteczna w przypadku wyzwań. Zaczynając coś wdrażać, zdałem sobie sprawę, że może to być miłym wyzwaniem, jeśli konstrukcja maszyny wirtualnej zostanie zredukowana do absolutnego minimum :)

Zadanie

Zaimplementuj 8-bitową maszynę wirtualną zgodną z następującą specyfikacją. To jest , więc wygrywa implementacja z najmniejszą liczbą bajtów.

Wkład

Twoje wdrożenie powinno obejmować następujące dane wejściowe:

  • Pojedynczy bajt bez znaku pc, jest to licznik programu początkowego (adres w pamięci, w którym maszyna wirtualna rozpoczyna wykonywanie, 0na podstawie)

  • Lista bajtów z maksymalną długością 256wpisów, jest to pamięć RAM dla maszyny wirtualnej (z jej początkową zawartością)

Możesz wziąć to wejście w dowolnym rozsądnym formacie.

Wydajność

Lista bajtów, które są końcową zawartością pamięci RAM po zakończeniu maszyny wirtualnej (patrz poniżej). Możesz założyć, że otrzymujesz tylko dane wejściowe, które ostatecznie prowadzą do zakończenia. Dowolny rozsądny format jest dozwolony.

Wirtualny procesor

Wirtualny procesor ma

  • 8-bitowy licznik programów,
  • 8-bitowy rejestr akumulatorów o nazwie Ai
  • 8-bitowy rejestr indeksu o nazwie X.

Istnieją trzy flagi stanu:

  • Z - flaga zerowa jest ustawiana po niektórych wynikach operacji 0
  • N - flaga ujemna jest ustawiana po niektórych operacjach skutkujących liczbą ujemną (ustawiony jest iow bit 7 wyniku)
  • C - flaga przeniesienia jest ustawiana poprzez dodawanie i przesuwanie „brakującego” bitu wyniku

Po uruchomieniu wszystkie flagi są kasowane, licznik programu jest ustawiany na określoną wartość, a zawartość Ai Xjest nieokreślona.

Wartości 8-bitowe reprezentują albo

  • unsigned całkowitą w zakresie[0..255]
  • podpisane całkowitą, 2 jest uzupełnieniem w zakresie[-128..127]

w zależności od kontekstu. Jeśli operacja jest przepełniona lub niedopełniona, wartość jest zawijana (aw przypadku dodania wpływa na flagę carry).

Zakończenie

Maszyna wirtualna kończy się, kiedy

  • HLTInstrukcja zostanie osiągnięta
  • Dostęp do nieistniejącego adresu pamięci jest uzyskiwany
  • Licznik programu działa poza pamięcią (pamiętaj, że nie zawija się, nawet jeśli maszyna wirtualna ma pełne 256 bajtów pamięci)

Tryby adresowania

  • niejawne - instrukcja nie ma argumentu, argument jest domyślny
  • natychmiastowy - operand jest bajtem bezpośrednio po instrukcji
  • względne - (tylko dla rozgałęzienia) bajt po podpisaniu instrukcji (uzupełnienie 2) i określa przesunięcie, które należy dodać do licznika programu, jeśli brana jest gałąź. 0to lokalizacja następującej instrukcji
  • bezwzględny - bajt po instrukcji jest adresem argumentu
  • zindeksowany - bajt po instrukcji plus X(rejestr) to adres argumentu

Instrukcje

Każda instrukcja składa się z kodu operacji (jeden bajt), aw trybach adresowania natychmiastowego , względnego , bezwzględnego i zindeksowanego drugiego bajtu argumentu. Gdy wirtualny procesor wykonuje instrukcję, odpowiednio zwiększa licznik programu (o 1lub 2).

Wszystkie pokazane tu kody operacyjne są szesnastkowe.

  • LDA - załaduj operand do A

    • Kody: natychmiastowe: 00bezwzględne: 02indeksowane:04
    • Flagi: Z,N
  • STA- zapisz Aw operand

    • Kody: natychmiastowe: 08bezwzględne: 0aindeksowane:0c
  • LDX - załaduj operand do X

    • Kody: natychmiastowe: 10bezwzględne: 12indeksowane:14
    • Flagi: Z,N
  • STX- zapisz Xw operand

    • Kody: natychmiastowe: 18bezwzględne: 1aindeksowane:1c
  • AND- bitowe oraz z Ai operand naA

    • Kody: natychmiastowe: 30bezwzględne: 32indeksowane:34
    • Flagi: Z,N
  • ORA- bitowe lub z Ai operand naA

    • Kody: natychmiastowe: 38bezwzględne: 3aindeksowane:3c
    • Flagi: Z,N
  • EOR- bitowe xor (wyłączne lub) Ai operand naA

    • Kody: natychmiastowe: 40bezwzględne: 42indeksowane:44
    • Flagi: Z,N
  • LSR - logiczne przesunięcie w prawo, przesunięcie wszystkich bitów argumentu o jedno miejsce w prawo, bit 0 przenosi

    • Kody: natychmiastowe: 48bezwzględne:4aindeksowane:4c
    • Flagi: Z, N,C
  • ASL - przesunięcie arytmetyczne w lewo, przesunięcie wszystkich bitów argumentu o jedno miejsce w lewo, bit 7 przenosi

    • Kody: natychmiastowe: 50bezwzględne:52indeksowane:54
    • Flagi: Z, N,C
  • ROR - obróć w prawo, przesuń wszystkie bity argumentu o jedno miejsce w prawo, przeniesienie przechodzi do bitu 7, bit 0 przechodzi do przeniesienia

    • Kody: natychmiastowe: 58bezwzględne:5aindeksowane:5c
    • Flagi: Z, N,C
  • ROL - obrót w lewo, przesunięcie wszystkich bitów argumentu o jedno miejsce w lewo, przeniesienie przechodzi do bitu 0, bit 7 przechodzi do przeniesienia

    • Kody: natychmiastowe: 60bezwzględne:62indeksowane:64
    • Flagi: Z, N,C
  • ADC- dodaj z przeniesieniem, dodawany jest operand plus przeniesienie A, przeniesienie jest ustawione na przepełnienie

    • Kody: natychmiastowe: 68bezwzględne:6aindeksowane:6c
    • Flagi: Z, N,C
  • INC - operand inkrementacji o jeden

    • Kody: natychmiastowe: 78bezwzględne:7aindeksowane:7c
    • Flagi: Z,N
  • DEC - operand dekrementacji o jeden

    • Kody: natychmiastowe: 80bezwzględne:82indeksowane:84
    • Flagi: Z,N
  • CMP- porównaj Az operandem, odejmując operand od A, zapomnij wynik. Przenoszenie jest kasowane przy niedopełnieniu, w przeciwnym razie ustawione

    • Kody: natychmiastowe: 88bezwzględne: 8aindeksowane:8c
    • Flagi: Z, N,C
  • CPX- porównaj X- tak samo jak CMPdlaX

    • Kody: natychmiastowe: 90bezwzględne: 92indeksowane:94
    • Flagi: Z, N,C
  • HLT - zakończyć

    • Opcodes: domyślnie: c0
  • INX- przyrost Xo jeden

    • Opcodes: domyślnie: c8
    • Flagi: Z,N
  • DEX- zmniejszenie Xo jeden

    • Opcodes: domyślnie: c9
    • Flagi: Z,N
  • SEC - ustaw flagę carry

    • Opcodes: domyślnie: d0
    • Flagi: C
  • CLC - wyczyść flagę carry

    • Opcodes: domyślnie: d1
    • Flagi: C
  • BRA - oddział zawsze

    • Kody: względne: f2
  • BNE- rozgałęzienie, jeśli Zflaga jest wyczyszczona

    • Kody: względne: f4
  • BEQ- rozgałęzienie, jeśli Zustawiona jest flaga

    • Kody: względne: f6
  • BPL- rozgałęzienie, jeśli Nflaga jest wyczyszczona

    • Kody: względne: f8
  • BMI- rozgałęzienie, jeśli Nustawiona jest flaga

    • Kody: względne: fa
  • BCC- rozgałęzienie, jeśli Cflaga jest wyczyszczona

    • Kody: względne: fc
  • BCS- rozgałęzienie, jeśli Custawiona jest flaga

    • Kody: względne: fe

Kody

Zachowanie maszyny wirtualnej jest niezdefiniowane, jeśli zostanie znaleziony jakikolwiek kod operacji, który nie jest odwzorowany na prawidłową instrukcję z powyższej listy.

Jak na zamówienie Jonathana Allana , to może wybrać swój własny zestaw rozkazy zamiast opcodes podanych w Instrukcji sekcji. Jeśli to zrobisz, musisz dodać pełne mapowanie do kodów operacyjnych użytych powyżej w odpowiedzi.

Mapowanie powinno być plikiem szesnastkowym z parami <official opcode> <your opcode>, np. Jeśli zastąpisz dwa kody operacyjne:

f4 f5
10 11

Newlines nie mają tutaj znaczenia.

Przypadki testowe (oficjalne kody operacyjne)

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

Mogę później dodać więcej przypadków testowych.

Referencje i testy

Aby pomóc w przeprowadzaniu własnych eksperymentów, oto niektóre (całkowicie nie golfa) implementacje referencyjne - mogą one wyświetlać informacje o śledzeniu (w tym zdemontowane instrukcje) stderri konwertować kody operacyjne podczas działania.

Zalecany sposób uzyskania źródła:

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

Lub gałąź kasy challengei wykonajgit submodule update --init --recursive odejdź do po klonowaniu, aby uzyskać niestandardowy system kompilacji.

Zbuduj narzędzie z GNU make (po prostu wpisz make, lub gmakejeśli w twoim systemie domyślnym make nie jest GNU make).

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

  • -s startpc - początkowy licznik programu, domyślnie 0
  • -h - dane wejściowe są szesnastkowe (inaczej binarne)
  • -t - śledzenie wykonania do stderr
  • -c convfile - konwertuj kody operacyjne zgodnie z mapowaniem podanym w convfile
  • -d - zrzuć wynikową pamięć jako dane binarne
  • -x - zrzuć wynikową pamięć jako hex
  • initial_ram - początkowa zawartość pamięci RAM, szesnastkowa lub binarna

Uwaga: funkcja konwersji nie powiedzie się w programach, które modyfikują kody operacyjne podczas działania.

Oświadczenie: powyższe zasady i specyfikacje są autorytatywne dla wyzwania, a nie to narzędzie. Dotyczy to w szczególności funkcji konwersji kodu operacyjnego. Jeśli uważasz, że narzędzie przedstawione tutaj ma błąd w specyfikacji, zgłoś to w komentarzu :)

Felix Palmen
źródło
1
Wyobrażam sobie, że prawdopodobnie będzie wiele możliwości gry w golfa, wybierając różne instrukcje dla instrukcji, ale wygląda na to, że zostały one naprawione (mimo że zestaw instrukcji definiuje maszynę). Może warto rozważyć zezwolenie implementacjom na posiadanie własnej strony kodowej?
Jonathan Allan
1
@JonathanAllan zastanowił się dwa razy, pozwalam na to teraz i mogę dodać narzędzie do konwersji, aby ułatwić testowanie rozwiązań wykorzystujących inne zestawy kodów.
Felix Palmen
1
@Arnauld przy okazji moim uzasadnieniem tego było zmniejszenie liczby specjalnych przypadków, więc powinno być lepiej „grać w golfa” - każdy kod operacji jest albo niejawny, względny lub umożliwia wszystkie trzy inne tryby adresowania :)
Felix Palmen
1
jeśli BRA(„gałąź zawsze”) nie wprowadza gałęzi w przepływie sterowania, czy nie powinno się jej wywoływać JMP?
ngn
1
@ cóż, BRAistnieje w późniejszych projektach układów (6502 nie ma takiej instrukcji), takich jak 65C02 i MC 68000. JMPistnieje również. Różnica polega na tym, że BRAwykorzystuje adresowanie względne i JMPwykorzystuje adresowanie bezwzględne. Po prostu zastosowałem się do tych projektów - nie brzmi to wcale logicznie;)
Felix Palmen,

Odpowiedzi:

16

C (gcc) , 1381 1338 1255 1073 bajtów

Ogromna poprawa dzięki CeCatcat i 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]();}

Wypróbuj online!

Wiele definicji przeniesiono do flag kompilatora.

Objaśnienie (BARDZO niepoddane golfowi):

#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
źródło
Świetna robota, natychmiastowe +1, naprawdę nie spodziewałem się najpierw rozwiązania w języku C :) Twoje dodawanie 00bajtów może jednak nieco naginać reguły ... Przyznaję, że nie próbowałem analizować tego kodu ... czy mógłbyś zapisać bajty robiące operacje we / wy w postaci binarnej zamiast szesnastkowej? Pozwolą na to przepisy :)
Felix Palmen
Chciałbym zastąpić zasadę, że nielegalne kody prowadzą do wypowiedzenia, mówiąc tylko, że zachowanie nielegalnych kodów jest niezdefiniowane ... czy to zaszkodzi twojej odpowiedzi, czy wszystko w porządku?
Felix Palmen,
@ FelixPalmen również, o ile wypowiedzenie jest dość ważnym „niezdefiniowanym” zachowaniem, nie zaszkodzi (otwiera to nową możliwość gry w golfa w dół!)
Max Yekhlakov
@MaxYekhlakov przez „hurt” Miałem na myśli niesprawiedliwość wobec twojego rozwiązania, ponieważ być może „wydałeś bajty” za upewnienie się, że nielegalny kod operacyjny kończy działanie vm. Cieszę się, że witasz zmianę reguły jako okazję :) I znowu, gratulacje, po prostu uwielbiam widzieć rozwiązanie w C, który jest moim ulubionym językiem programowania. Szkoda, że ​​rzadko wygrywasz wyzwanie w golfa C w golfie - niemniej „golfista” C jest po prostu fajny :)
Felix Palmen
Czy możesz dodać część flagi do postu?
l4m2
8

APL (Dyalog Classic) , 397 332 330 bajtów

dzięki @ Adám za -8 bajtów

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

Wypróbuj online!

p  program counter
m  memory
a  accumulator register
x  index register
q  flags z (zero) and n (negative) as a length-2 vector
c  flag for carry
  function to update z and n
b  current instruction
u  highest 5 bits of b
o  operand
i  target address in memory
ngn
źródło
Czy to rozwiązanie ma niezamierzone kody operacyjne, a jeśli nie, czy spędziłeś bajty na ich unikaniu? Zobacz ten komentarz z tego powodu, dla którego pytam ...
Felix Palmen
@FelixPalmen Teraz, kiedy o tym wspomniałeś, tak :( Początkowo przestrzegałem tej zasady, ale kiedy grałem w golfa, przypadkowo wprowadziłem 4, 5 i ewentualnie inne ważne kody. Dlatego decyzja o niezdefiniowaniu ich zachowania byłaby bardzo mile widziana :)
ngn
2
zrobione teraz, zdaję sobie sprawę, że to nie była najlepsza decyzja, a @MaxYekhlakov niestety nie musiał nic mówić o zmianie reguły.
Felix Palmen
Czy trzeba f←?
Erik the Outgolfer
8

C (gcc) , 487 , 480 , 463 , 452 , 447 , 438 bajtów

Wykorzystuje to mapowanie instrukcji . Aktualizacja instrukcji straciła 9 bajtów, a potencjalnie więcej w przyszłości. Zwraca przez modyfikację pamięci wskazanej przez pierwszy argument ( M). Dzięki @ceilingcat za golenie niektórych bajtów.

Musi być skompilowany z flagami -DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(już zawartymi w bajtach).

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

Wypróbuj online!

Preprocesor

-DO=*o -DD=*d

Te dwa stanowią krótszy sposób na usunięcie odniesienia do wskaźników.

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

Zmniejsz liczbę bajtów potrzebną do if-els i deklaracji typu.

Kod

Poniżej znajduje się czytelna dla człowieka wersja kodu, z rozszerzonymi dyrektywami preprocesora i zmienionymi nazwami zmiennych w celu zapewnienia czytelności.

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

Instrukcje

Instrukcje mają następującą strukturę:

  • Bity 6-7 wskazują na istnienie instrukcji ( 00zerowy, 01jednoargumentowy, 10binarny, 11binarny)

  • Bity 0-2 określają operand (y): R=0wybiera Ai R=1wybiera X. OP=00wykorzystuje rejestr jako operand, OP=01wybiera operand bezpośredni, OP=10wybiera operand bezwzględny i OP=11wybiera indeksowany indeks.

    • Jak zapewne zauważyłeś, pozwala to na wykonywanie dowolnych operacji na obu rejestrach (chociaż nadal możesz indeksować tylko z X), nawet jeśli normalnie nie można ich użyć według specyfikacji. Na przykład INC A, ADC X, 10a ASL Xwszystkie prace.
  • Bity 3-5 określają warunek rozgałęzienia: posiadanie jednego z bitów wskazuje, którą flagę przetestować (bit 3-> C, bit 4-> N, bit 5-> Z). Jeśli ustawiony jest tylko jeden bit, instrukcja sprawdza flagę zestawu. Aby przetestować flagę nieustawioną, weź uzupełnienie bitów. Na przykład 110testy dla nieuzbrojonego przenoszenia i 001dla ustawionego przenoszenia. 111i 000gałąź zawsze.

  • Możesz także rozgałęzić się do przesunięcia adresu zapisanego w rejestrze, umożliwiając pisanie funkcji, lub możesz użyć standardowych trybów indeksowania. OP=01zachowuje się jak gałąź specyfikacji.

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

źródło
7

JavaScript (ES6), 361 bajtów

Pobiera dane wejściowe jako (memory)(program_counter), gdzie memoryjest Uint8Array. Wyjście poprzez modyfikację tej tablicy.

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

Uwaga: Kod jest skompresowany za pomocą RegPack i zawiera wiele znaków niedrukowalnych, z których wszystkie są zastępowane w powyższej reprezentacji źródła.

Wypróbuj online!

Mapowanie kodów operacyjnych i przypadki testowe

Maszyna wirtualna używa tego mapowania opcode .

Poniżej znajdują się przetłumaczone przypadki testowe wraz z oczekiwanymi wynikami.

Przypadek testowy nr 1

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

Oczekiwany wynik:

09 20 32 01 44 55 fb

Przypadek testowy nr 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

Oczekiwany wynik:

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

Przypadek testowy nr 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

Oczekiwany wynik:

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

Rozpakowane i sformatowane

Ponieważ kod jest kompresowany za pomocą algorytmu, który zastępuje często powtarzane ciągi pojedynczym znakiem, bardziej efektywne jest stosowanie tych samych bloków kodu w kółko niż definiowanie i wywoływanie funkcji pomocniczych lub przechowywanie wyników pośrednich (takich jak M[A]) w dodatkowych zmiennych.

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
źródło
Imponujące :) Nie jest to JS pro, więc - czy to indeksowanie do jakiejś „tablicy kodów” według wartości opcode? Wygląda dobrze. Ale jeśli ten o = ...wiersz jest wykonywany dla każdej instrukcji, może to mieć „niezamierzone kody operacyjne”?
Felix Palmen
2
Powinienem chyba dodać przypadek testowy: o Teraz myślę, że lepiej byłoby pozwolić na niezamierzone kody operacyjne ... kontrole ważności po prostu marnują bajty tutaj, ale prawdopodobnie za późno, aby zmienić reguły :(
Felix Palmen
Cóż, miałem właśnie to zasugerować, ponieważ nie stanowi to większego wyzwania i jest teraz trudne do sprawdzenia za pomocą niestandardowych mapowań. Ale prawdopodobnie powinieneś najpierw zapytać / ostrzec @MaxYekhlakov, ponieważ mogli oni poprawnie wdrożyć regułę.
Arnauld
c = M[A] >> 7 & 1<- czy &1naprawdę jest tu potrzebny?
Felix Palmen,
2
Jestem prawie pewien, że tak czy inaczej, ponieważ przesłanie jest funkcją, moje sformułowanie brzmiało: „lista bajtów [...] dowolnego rozsądnego formatu” i Uint8Arrayrzeczywiście po prostu zawiera taką listę bajtów. Jeśli więc umieszczenie bajtów w ciągu szesnastkowym jest akceptowalnym sposobem reprezentowania danych wejściowych, dlaczego umieszczanie ich w obiekcie kontenerowym powinno być zabronione ...
Felix Palmen
2

PHP, 581585 555 532 bajtów (nie-konkurencyjne)

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

pobiera kody PC i OP jako 10 liczb całkowitych bazowych z argumentów wiersza poleceń,
drukuje pamięć jako listę [base 10 address] => base 10 value.

Nie zostało to jeszcze dokładnie przetestowane ; ale jest awaria .

There's mapie kodu i here's przegląd dla mojego mapowania:

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)

uwaga dodatkowa:
kod 24skutkuje BNV(gałąź nigdy = 2 bajty NOP);
04, 08, 0CSą aliasy INX, CLCa SEC
przede wszystko 3Fjest albo dwa bajt NOPlub aliasem instrukcji jednomodowych.

Tytus
źródło