8 비트 가상 머신


31

배경

오래된 8 비트 6502 칩이 마음에 듭니다. 6502 기계 코드에서 PPCG의 몇 가지 문제를 해결하는 것도 재미 있습니다. 그러나 데이터를 읽거나 표준 출력으로 출력하는 것과 같이 단순해야하는 일부 작업은 기계 코드에서 불필요하게 번거 롭습니다. 6502에서 영감을 얻었지만 문제에보다 유용하게 사용할 수 있도록 디자인 된 8 비트 가상 머신을 발명하십시오. 무언가를 구현하기 시작하면서 VM의 디자인이 최소한으로 줄어들면 이것이 큰 도전이 될 수 있음을 깨달았습니다. :)

태스크

다음 사양을 준수하는 8 비트 가상 머신을 구현하십시오. 이것은 이므로 바이트 수가 가장 적은 구현이 승리합니다.

입력

구현에는 다음 입력이 필요합니다.

  • 단일 부호없는 바이트 pc, 이것은 초기 프로그램 카운터 (VM이 실행을 시작하는 메모리의 주소 0)

  • 최대 길이의 256항목이 있는 바이트 목록으로 , 가상 머신의 RAM입니다 (초기 내용 포함).

이 입력은 적절한 형식으로 가져갈 수 있습니다.

산출

VM이 종료 된 후 RAM의 최종 내용 인 바이트 목록 (아래 참조). 결국 종료로 이어지는 입력 만받는다고 가정 할 수 있습니다. 모든 합리적인 형식이 허용됩니다.

가상 CPU

가상 CPU는

  • 8 비트 프로그램 카운터
  • 8 비트 누산기 레지스터 호출 A
  • 8 비트 지수가 호출 레지스터 X.

세 가지 상태 플래그가 있습니다.

  • Z -제로 플래그는 일부 작업 결과로 설정된 후 0
  • N -일부 연산 결과가 음수로 표시된 후 음수 플래그가 설정 됨 (결과의 비트 7이 설정 됨)
  • C -캐리 플래그는 결과의 "누락"비트에 대한 추가 및 시프트로 설정됩니다.

시작되면, 플래그는 모든 프로그램 카운터는 주어진 값의 내용으로 설정, 클리어 A하고 X불확정이다.

8 비트 값은 다음 중 하나를 나타냅니다.

  • 부호 범위의 정수[0..255]
  • 범위 내의 부호있는 정수, 2의 보수[-128..127]

상황에 따라. 작업이 오버플로 또는 언더 플로 인 경우 값이 줄 바꿈됩니다 (추가의 경우 캐리 플래그가 영향을받습니다).

종료

가상 머신이 종료되면

  • HLT명령에 도달
  • 존재하지 않는 메모리 주소에 액세스
  • 프로그램 카운터는 메모리 외부에서 실행됩니다 (VM에 256 바이트의 전체 메모리가 제공 되어도 랩되지 않습니다)

어드레싱 모드

  • 암시 적 -명령어에는 인수가 없으며 피연산자가 암시됩니다.
  • 즉치 -피연산자는 명령어 바로 다음의 바이트입니다.
  • relative- (분기 전용) 명령어가 서명 된 후 바이트 (2의 보수)이며 분기가 수행 될 경우 프로그램 카운터에 추가 할 오프셋을 결정합니다. 0다음 명령의 위치입니다
  • absolute- 명령어 뒤의 바이트는 피연산자의 주소입니다.
  • indexed- 명령어 뒤의 바이트 더하기 X(레지스터)는 피연산자의 주소입니다.

명령

각 명령어는 opcode (1 바이트)로 구성되며 주소 지정 모드에서 즉치 , 상대 , 절대 및 두 번째 인수 바이트를 인덱싱 합니다. 가상 CPU는 명령을 실행할 때 그에 따라 프로그램 카운터를 증가시킵니다 ( 1또는 2).

여기에 표시된 모든 opcode는 16 진입니다.

  • 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 -피연산자를 1 씩 증가

    • 오피 코드 : 즉시 : 78, 절대 : 7a, 색인 :7c
    • 플래그 : Z,N
  • DEC -피연산자를 1 씩 감소

    • 오피 코드 : 즉시 : 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

오피 코드

위 목록에서 유효한 명령어로 매핑되지 않은 opcode가 발견되면 VM의 동작이 정의되지 않습니다.

조나단 앨런의 요청 , 당신은 할 수 있습니다 대신에 표시된 옵 코드의 옵 코드의 자신의 세트를 선택 지침 섹션을 참조하십시오. 그렇게하면 위에서 답변에 사용 된 opcode에 전체 매핑을 추가 해야합니다 .

매핑은 쌍이있는 16 진 파일이어야합니다 ( <official opcode> <your opcode>예 : 두 개의 opcode를 교체 한 경우).

f4 f5
10 11

개행은 여기서 중요하지 않습니다.

테스트 사례 (공식 opcode)

// 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하고 실행 중에 opcode를 변환 할 수 있습니다.

소스를 얻는 권장 방법 :

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

또는 체크 아웃 브랜치 challengegit submodule update --init --recursive복제 후 사용자 정의 빌드 시스템을 얻으십시오.

GNU make를 사용하여 도구를 빌드하십시오 (type make또는 gmake시스템에서 기본 make가 GNU make가 아닌 경우).

사용법 :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc -초기 프로그램 카운터, 기본값은 0
  • -h -입력이 16 진입니다 (그렇지 않으면 이진).
  • -t -추적 실행 stderr
  • -c convfile -주어진 매핑에 따라 opcode를 변환 convfile
  • -d -결과 메모리를 이진 데이터로 덤프
  • -x -결과 메모리를 16 진수로 덤프
  • initial_ram -초기 RAM 내용 (16 진 또는 2 진)

참고 실행 수정 옵 코드 동안 프로그램에 실패 변환 기능을.

면책 조항 : 위의 규칙과 사양은이 도구가 아닌 도전에 대한 권한입니다. 이것은 특히 opcode 변환 기능에 적용됩니다. 여기에 제시된 도구에 사양에 대한 버그가 있다고 생각되면 의견을 보내주십시오. :)


1
명령에 대해 다른 opcode를 선택하면 많은 골프 기회가있을 것이라고 생각하지만 opcode가 수정 된 것처럼 보입니다 (명령 세트가 기계를 정의하는 것이더라도). 구현에 자체 코드 페이지를 허용하는 것을 고려해 볼 가치가 있습니까?
Jonathan Allan

1
@JonathanAllan은 그것에 대해 두 번 생각했습니다. 지금 허용하고 있으며 다른 opcode 세트를 사용하는 솔루션을 쉽게 테스트 할 수 있도록 "변환"도구를 추가 할 수 있습니다.
Felix Palmen

1
@Arnauld btw는 이것이 특별한 경우의 양을 줄이는 것이었기 때문에 더 나은 "골프 가능"하는 것이 좋을 것입니다. 각 opcode는 암시 적이거나 상대적 브랜치이거나 세 가지 다른 주소 지정 모드를 모두 허용합니다.
Felix Palmen

1
경우 BRA( "항상 지점") 제어 흐름에 지점을 소개하지 않습니다, 그것은 호출 할 수 없습니다 JMP?
ngn

1
@ngn BRA은 65C02 및 MC 68000과 같은 최신 칩 설계에 존재합니다 (6502에는 이러한 명령어가 없음) JMP. 차이점은 BRA상대 주소 지정과 JMP절대 주소 지정을 사용 한다는 것 입니다. 방금이 디자인을 따랐습니다. 실제로 그렇게 논리적으로 들리는 것은 아닙니다.)
Felix Palmen

답변:


16

C (gcc) , 1381 1338 1255 1073 바이트

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

온라인으로 사용해보십시오!

많은 정의가 컴파일러 플래그로 이동되었습니다.

설명 (매우 ungolfed) :

#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바이트를 추가 하면 규칙이 약간 구부러 질 수 있습니다. 16 진수 대신 바이너리로 입출력을 수행하는 바이트를 저장합니까? 규칙에 의해 허용됩니다 :)
펠릭스 Palmen

불법 오피 코드의 동작이 정의되어 있지 않다고 말함으로써 불법 오피 코드가 종료되는 규칙을 대체하고 싶습니다.
Felix Palmen

잘 @FelixPalmen, 한 종단은 매우 유효로 동작, 그것은 (! 대신 그것을 아래로 골프에 대한 새로운 가능성을 열어) 해치지 않을 것 "정의되지 않은"
최대 Yekhlakov

@MaxYekhlakov의 "hurt"는 잘못된 opcode가 vm을 종료하는지 확인하기 위해 "사용 된 바이트"일 수 있으므로 솔루션에 불공평하다는 의미입니다. 나는 규칙 변경을 기회로 환영합니다 :) 그리고 다시, 축하합니다. 저는 C에서 가장 좋아하는 프로그래밍 언어 인 솔루션을보고 싶습니다. 그럼에도 불구하고, :) C 그냥 멋지다 "golfed"- 그것은 당신이 거의 C에서 코드 골프 도전을 이길 수없는거야 유감
펠릭스 Palmen을

게시 할 플래그 부분을 추가 할 수 있습니까?
l4m2

8

APL (Dyalog 기본) , 397 (332) 330 바이트

-8 바이트를위한 @ Adám에게 감사합니다

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


이 솔루션에는 의도하지 않은 opcode가 있으며 그렇지 않은 경우 opcode를 피하면서 바이트를 소비 했습니까? 내가 묻는 이유에 대해이 의견보십시오 ...
Felix Palmen

@FelixPalmen 이제 언급했듯이, yes :( 처음 나는 그 규칙을 지켜 봤지만, 골프를하면서 실수로 4, 5 등의 유효한 opcode를 만들었습니다.
ngn

2
이제는 그것이 처음에는 최선의 결정이 아니며 @MaxYekhlakov가 불행히도 규칙 변경에 대해 아무 말도 할 필요가 없다는 것을 알고 있습니다.
Felix Palmen

당신은 필요 f←합니까?
Outgolfer Erik

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-els 및 type 선언에 필요한 바이트 수를 줄이십시오.

암호

아래는 사람이 읽을 수있는 코드 버전으로, 전 처리기 지시문이 확장되고 변수 이름이 읽기 쉽게 변경되었습니다.

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은 명령어의 00Arity ( Nullary, 01Unary, 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

자바 스크립트 (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 으로 압축 되어 있으며 인쇄 할 수없는 많은 문자를 포함하고 있습니다.

온라인으로 사용해보십시오!

Opcode 매핑 및 테스트 사례

가상 머신은 이 opcode 매핑을 사용 합니다 .

다음은 예상 결과와 함께 번역 된 테스트 사례입니다.

테스트 사례 # 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 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 1e 01  44 5 일 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 전문가가 아니므로 opcode의 값에 의해 일부 "코드 배열"로 색인이 생성됩니까? 좋아 보인다. 그러나이 o = ...줄이 모든 명령에 대해 실행 되면 "의도하지 않은 opcode"가있을 수 있습니까?
Felix Palmen

2
아마도 테스트 케이스를 추가해야 할 것입니다 : o 이제 의도하지 않은 opcode를 허용하는 것이 좋을 것이라고 생각합니다 ... 유효성 검사는 여기에서 바이트를 낭비하지만 규칙을 변경하기에는 너무 늦습니다 :(
Felix Palmen

글쎄, 나는 그것이 도전에 많은 도움이되지 않고 이제 사용자 정의 매핑으로 확인하기가 어렵 기 때문에 정확하게 제안하려고했습니다. 그러나 규칙을 올바르게 구현했을 수 있으므로 @MaxYekhlakov를 먼저 요청 / 경고해야합니다.
Arnauld

c = M[A] >> 7 & 1<- &1정말 필요한가요?
Felix Palmen

2
나는 당신의 제출이 어쨌든 함수 일 것이라고 확신합니다. 제 말은 "바이트 목록 [...] 모든 합리적인 형식"이며 Uint8Array실제로 그러한 바이트 목록을 캡슐화합니다. 따라서 16 진 문자열에 바이트를 넣는 것이 입력을 나타내는 적절한 방법 인 경우 컨테이너 오브젝트에 넣는 것을 금지해야하는 이유는 무엇입니까?
Felix Palmen

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

PC 및 OP 코드를 명령 행 인수에서 기본 10 개의 정수로 가져와
메모리를 목록으로 인쇄합니다 [base 10 address] => base 10 value.

이되어 아직 완전히 테스트되지 ; 그러나 고장이 있습니다.

놈의 코드지도 및 here's 내 매핑을위한 개요 :

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앨리어스 (alias) INX, CLC그리고 SEC
아무것도 위 3F중 두 바이트 NOP또는 단일 모드 지침에 대한 별칭.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.