8ビット仮想マシン


31

バックグラウンド

私は古い8ビット6502チップが好きです。6502マシンコードでPPCGの問題を解決するのは楽しいことです。しかし、単純であるべきいくつかのこと(データの読み取りやstdoutへの出力など)は、マシンコードで行うのは不必要に面倒です。だから、私の頭の中には大まかなアイデアがあります。6502に触発された独自の8ビット仮想マシンを発明します。何かを実装し始めて、VMの設計が最小限に抑えられている場合、これ自体が素晴らしい挑戦であるかもしれないことに気付きました:)

仕事

次の仕様に準拠する8ビット仮想マシンを実装します。これはであるため、バイト数が最も少ない実装が優先されます。

入力

実装には、次の入力が必要です。

  • 単一の符号なしバイトpc、これは初期プログラムカウンター(VMが実行を開始するメモリ内のアドレス、0ベース)

  • 256エントリの最大長を持つバイトのリスト。これは仮想マシンのRAM(初期コンテンツを含む)

この入力は、適切な形式で入力できます。

出力

VMが終了した後のRAMの最終的な内容であるバイトのリスト(以下を参照)。最終的に終了することにつながる入力のみを取得すると仮定できます。賢明な形式はすべて許可されます。

仮想CPU

仮想CPUには

  • 8ビットプログラムカウンター、
  • 呼ばれる8ビットアキュムレータレジスタA
  • という8ビットのインデックスレジスタX

3つのステータスフラグがあります。

  • Z -何らかの操作の結果、ゼロフラグが設定された 0
  • N -負のフラグは、何らかの操作の結果が負の数になった後に設定されます(結果のビット7が設定されます)
  • C -キャリーフラグは、結果の「欠落」ビットの加算とシフトによって設定されます

スタート時には、フラグが全てクリアされ、プログラムカウンタは、与えられた値との内容に設定されているAX不定です。

8ビット値は、

  • 符号なしの範囲の整数[0..255]
  • 範囲の符号付き整数、2の補数[-128..127]

コンテキストに応じて。操作がオーバーフローまたはアンダーフローすると、値は折り返されます(また、追加の場合、キャリーフラグが影響を受けます)。

終了

仮想マシンは次の場合に終了します

  • HLT命令に到達します
  • 存在しないメモリアドレスがアクセスされた
  • プログラムカウンターはメモリ外で実行されます(VMに256バイトのメモリがすべて与えられてもラップアラウンドしないことに注意してください)

ドレッシングモード

  • 暗黙的 -命令には引数がありません。オペランドは暗黙的です
  • 即時 -オペランドは命令の直後のバイトです
  • relative-(分岐のみ)命令が署名された後のバイト(2の補数)。分岐が行われた場合にプログラムカウンターに追加するオフセットを決定します。0次の命令の場所です
  • absolute-命令の後のバイトは、オペランドのアドレスです
  • インデックス付き -命令プラスX(レジスタ)の後のバイトは、オペランドのアドレスです

説明書

各命令はオペコード(1バイト)で構成され、アドレッシングモードでimmediaterelativeabsolute、およびindexedの2番目の引数バイトです。仮想CPUが命令を実行すると、それに応じて(1または2)プログラムカウンターをインクリメントします。

ここに示されているすべてのオペコードは16進数です。

  • LDA -オペランドをロードする A

    • オペコード:即時:00、絶対:02、インデックス付き:04
    • フラグ:ZN
  • STA- Aオペランドに格納

    • オペコード:即時:08、絶対:0a、インデックス付き:0c
  • LDX -オペランドをロードする X

    • オペコード:即時:10、絶対:12、インデックス付き:14
    • フラグ:ZN
  • STX- Xオペランドに格納

    • オペコード:即時:18、絶対:1a、インデックス付き:1c
  • AND-ビットAオペランドにA

    • オペコード:即時:30、絶対:32、インデックス付き:34
    • フラグ:ZN
  • ORA-ビットまたはAオペランドにA

    • オペコード:即時:38、絶対:3a、インデックス付き:3c
    • フラグ:ZN
  • EOR-ビット単位のXOR(排他的ORAとオペランドA

    • オペコード:即時:40、絶対:42、インデックス付き:44
    • フラグ:ZN
  • LSR -論理右シフト、オペランドのすべてのビットを右に1桁シフトします。ビット0はキャリーになります

    • オペコード:即時:48、絶対:4a、インデックス付き:4c
    • フラグ:ZNC
  • ASL -算術左シフト、オペランドのすべてのビットを1桁左にシフト、ビット7はキャリーに移動

    • オペコード:即時:50、絶対:52、インデックス付き:54
    • フラグ:ZNC
  • ROR -右に回転し、オペランドのすべてのビットを右に1桁シフトし、キャリーがビット7になり、ビット0がキャリーになります

    • オペコード:即時:58、絶対:5a、インデックス付き:5c
    • フラグ:ZNC
  • ROL -左に回転し、オペランドのすべてのビットを左に1桁シフトし、キャリーがビット0になり、ビット7がキャリーになります

    • オペコード:即時:60、絶対:62、インデックス付き:64
    • フラグ:ZNC
  • ADC-キャリー付き加算、オペランドとキャリーが加算されA、オーバーフロー時にキャリーが設定されます

    • オペコード:即時:68、絶対:6a、インデックス付き:6c
    • フラグ:ZNC
  • INC -オペランドを1インクリメント

    • オペコード:即時:78、絶対:7a、インデックス付き:7c
    • フラグ:ZN
  • DEC -オペランドを1減らす

    • オペコード:即時:80、絶対:82、インデックス付き:84
    • フラグ:ZN
  • CMP- Aからオペランドを減算してオペランドと比較しA、結果を忘れます。キャリーはアンダーフローでクリアされ、そうでなければ設定されます

    • オペコード:即時:88、絶対:8a、インデックス付き:8c
    • フラグ:ZNC
  • CPX-比較X-同じCMPのためのX

    • オペコード:即時:90、絶対:92、インデックス付き:94
    • フラグ:ZNC
  • HLT -終了

    • オペコード:暗黙的: c0
  • INX-増分Xずつ

    • オペコード:暗黙的: c8
    • フラグ:ZN
  • DEX-1 Xずつ減少

    • オペコード:暗黙的: c9
    • フラグ:ZN
  • 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の動作は未定義です。

あたりとしてジョナサン・アランの要求、あなたはありかわりに示したオペコードのオペコードの独自のセットを選択命令セクションを。その場合、上記の回答で使用したオペコードに完全なマッピングを追加する必要あります。

マッピングは、ペアを持つ16進ファイルである必要があります<official opcode> <your opcode>。たとえば、2つのオペコードを置き換えた場合:

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 -入力は16進数です(それ以外の場合はバイナリ)
  • -t -実行をトレースする stderr
  • -c convfile -で指定されたマッピングに従ってオペコードを変換する convfile
  • -d -結果のメモリをバイナリデータとしてダンプする
  • -x -結果のメモリを16進数としてダンプします
  • initial_ram -16進数またはバイナリの初期RAM内容

実行中にオペコードを変更するプログラムでは、変換機能が失敗することに注意してください

免責事項:上記のルールと仕様は、このツールではなく、チャレンジに対して信頼できるものです。これは、特にオペコード変換機能に適用されます。ここに示されているツールに仕様に関するバグがあると思われる場合は、コメントで報告してください:)


1
命令に異なるオペコードを選択することにより、おそらく多くのゴルフの機会があると想像しますが、オペコードは修正されているようです(命令セットがマシンを定義するものであっても)。実装に独自のコードページを許可することを検討する価値があるのでしょうか?
ジョナサンアラン

1
@JonathanAllanはそれについて再考しました。私は今それを許可し、他のオペコードのセットを使用したソリューションを簡単にテストできる「変換」ツールを追加するかもしれません。
フェリックスパルメン

1
@Arnauld btwこれを許可する理由は、特別なケースの量を減らすことでした。したがって、「ゴルフ可能」にする必要があります-各オペコードは暗黙的、相対的な分岐、または他の3つのアドレス指定モードすべてを許可します:)
Felix Palmen

1
場合BRA(「常にブランチ」)は、制御フローにブランチを導入しない、それが呼び出されるべきではありませんかJMP
ngn

1
@ngn BRAは、65C02やMC 68000のような後期のチップ設計(6502にはこのような命令はありません)にJMPも存在します。違いは、BRA相対アドレス指定とJMP絶対アドレス指定を使用することです。だから、私はこれらのデザインにしたがった-実際、それはすべての論理的ではないように聞こえる;)
フェリックスパルメン

回答:


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

オンラインでお試しください!

多くの定義がコンパイラフラグに移動しました。

説明(非常に手放された):

#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進数ではなくバイナリでI / Oを行うバイトを節約しますか?規則によって許可されます:)
フェリックスパルメン

違法なオペコードの動作が未定義であると言うだけで、違法なオペコードが終了につながるというルールを置き換えたいと思います...これはあなたの答えを傷つけますか、それとも大丈夫ですか?
フェリックスパルメン

@FelixPalmenよく、終了が非常に有効な「未定義」動作である限り、それは傷つきません(代わりにそれをゴルフで
打つ

@MaxYekhlakov by "hurt"違法なオペコードがvmを終了させることを確認するために「バイトを使う」可能性があるため、ソリューションに対して不公平であることを意味しました。ルールの変更を機会として歓迎していることをうれしく思います:)そして、おめでとうございます、私はCでの解決策を見るのが大好きです。これは私の大好きなプログラミング言語です。Cでコードゴルフチャレンジに勝つことはめったにありませんが、残念ながら「ゴルフ」されたCはクールです:)
Felix Palmen

フラグ部分を追加して投稿できますか?
l4m2

8

APL(Dyalog Classic)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


このソリューションには意図しないオペコードがありますか?このコメントを参照してください ...私が求めている理由のために
フェリックスPalmen

@FelixPalmenさて、そうですね:(最初はそのルールを守っていましたが、ゴルフをしているときに、誤って4、5、そして場合によっては他の有効なオペコードを作成しました。
ngn

2
今やっていることは、そもそもそれが最良の決定ではなかったことに気づき、残念ながら@MaxYekhlakovはルールの変更について何も言う必要がなかった。
フェリックスパルメン

必要f←ですか?
エリックアウトゴルファー

8

C(GCC) 487480463452447、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

これら2つは、ポインターを間接参照するためのより短い方法を提供します。

-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 0110Binary、11Binary)

  • ビット0から2はオペランドを決定します:R=0を選択AしてR=1選択しXます。OP=00レジスタをオペランドとして使用し、OP=01即値オペランドをOP=10選択し、絶対オペランドをOP=11選択し、インデックス付きオペランドを選択します。

    • お気づきかもしれませんが、これにより、X通常は仕様ごとに使用できない場合でも、いずれかのレジスタで操作を実行できます(ただし、からのインデックスしか作成できません)。例えばINC AADC X, 10およびASL Xすべての作業。
  • ビット3〜5は、分岐の条件を決定します。いずれかのビットがテストするフラグを示します(ビット3-> C、ビット4-> N、ビット5-> Z)。1ビットのみが設定されている場合、命令はセットフラグをテストします。未設定フラグをテストするには、ビットの補数を取ります。たとえば110、設定解除キャリーと設定キャリーのテスト001111そして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 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 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さて、意図しないオペコードを許可する方が良かったと思います...ここで有効性チェックはバイトを無駄にしますが、おそらくルールを変更するには遅すぎます:(
Felix Palmen

まあ、私はまさにそれを提案しようとしていました、それはそれが挑戦に多くを加えず、そして今カスタムマッピングでチェックするのが難しいからです。ただし、ルールを正しく実装している可能性があるため、おそらく最初に@MaxYekhlakovに尋ねる/警告する必要があります。
アーナウルド

c = M[A] >> 7 & 1<- &1ここで本当に必要ですか?
フェリックスパルメン

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

コマンドライン引数から10の整数としてPCおよびOPコードを取得し、
メモリをリストとして出力します[base 10 address] => base 10 value

これはまだ完全にはテストされていません。しかし、故障がありますます。

There's コードマップ と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)

サイドノート:
コード24BNV(branch never = 2 byte NOP)になります;
04080Cの別名ですINXCLCそしてSEC
、何が上記3Fのいずれか2バイトであるNOPか、シングルモード命令の別名。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.