バックグラウンド
私は古い8ビット6502チップが好きです。6502マシンコードでPPCGの問題を解決するのは楽しいことです。しかし、単純であるべきいくつかのこと(データの読み取りやstdoutへの出力など)は、マシンコードで行うのは不必要に面倒です。だから、私の頭の中には大まかなアイデアがあります。6502に触発された独自の8ビット仮想マシンを発明します。何かを実装し始めて、VMの設計が最小限に抑えられている場合、これ自体が素晴らしい挑戦であるかもしれないことに気付きました:)
仕事
次の仕様に準拠する8ビット仮想マシンを実装します。これはcode-golfであるため、バイト数が最も少ない実装が優先されます。
入力
実装には、次の入力が必要です。
単一の符号なしバイト
pc、これは初期プログラムカウンター(VMが実行を開始するメモリ内のアドレス、0ベース)256エントリの最大長を持つバイトのリスト。これは仮想マシンのRAM(初期コンテンツを含む)
この入力は、適切な形式で入力できます。
出力
VMが終了した後のRAMの最終的な内容であるバイトのリスト(以下を参照)。最終的に終了することにつながる入力のみを取得すると仮定できます。賢明な形式はすべて許可されます。
仮想CPU
仮想CPUには
- 8ビットプログラムカウンター、
- 呼ばれる8ビットアキュムレータレジスタ
Aと - という8ビットのインデックスレジスタ
X。
3つのステータスフラグがあります。
Z-何らかの操作の結果、ゼロフラグが設定された0N-負のフラグは、何らかの操作の結果が負の数になった後に設定されます(結果のビット7が設定されます)C-キャリーフラグは、結果の「欠落」ビットの加算とシフトによって設定されます
スタート時には、フラグが全てクリアされ、プログラムカウンタは、与えられた値との内容に設定されているAとX不定です。
8ビット値は、
- 符号なしの範囲の整数
[0..255] - 範囲の符号付き整数、2の補数
[-128..127]
コンテキストに応じて。操作がオーバーフローまたはアンダーフローすると、値は折り返されます(また、追加の場合、キャリーフラグが影響を受けます)。
終了
仮想マシンは次の場合に終了します
HLT命令に到達します- 存在しないメモリアドレスがアクセスされた
- プログラムカウンターはメモリ外で実行されます(VMに256バイトのメモリがすべて与えられてもラップアラウンドしないことに注意してください)
ドレッシングモード
- 暗黙的 -命令には引数がありません。オペランドは暗黙的です
- 即時 -オペランドは命令の直後のバイトです
- relative-(分岐のみ)命令が署名された後のバイト(2の補数)。分岐が行われた場合にプログラムカウンターに追加するオフセットを決定します。
0次の命令の場所です - absolute-命令の後のバイトは、オペランドのアドレスです
- インデックス付き -命令プラス
X(レジスタ)の後のバイトは、オペランドのアドレスです
説明書
各命令はオペコード(1バイト)で構成され、アドレッシングモードでは、immediate、relative、absolute、およびindexedの2番目の引数バイトです。仮想CPUが命令を実行すると、それに応じて(1または2)プログラムカウンターをインクリメントします。
ここに示されているすべてのオペコードは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(排他的OR)AとオペランドA- オペコード:即時:
40、絶対:42、インデックス付き:44 - フラグ:
Z、N
- オペコード:即時:
LSR-論理右シフト、オペランドのすべてのビットを右に1桁シフトします。ビット0はキャリーになります- オペコード:即時:
48、絶対:4a、インデックス付き:4c - フラグ:
Z、N、C
- オペコード:即時:
ASL-算術左シフト、オペランドのすべてのビットを1桁左にシフト、ビット7はキャリーに移動- オペコード:即時:
50、絶対:52、インデックス付き:54 - フラグ:
Z、N、C
- オペコード:即時:
ROR-右に回転し、オペランドのすべてのビットを右に1桁シフトし、キャリーがビット7になり、ビット0がキャリーになります- オペコード:即時:
58、絶対:5a、インデックス付き:5c - フラグ:
Z、N、C
- オペコード:即時:
ROL-左に回転し、オペランドのすべてのビットを左に1桁シフトし、キャリーがビット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-1Xずつ減少- オペコード:暗黙的:
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の動作は未定義です。
あたりとしてジョナサン・アランの要求、あなたはありかわりに示したオペコードのオペコードの独自のセットを選択命令セクションを。その場合、上記の回答で使用したオペコードに完全なマッピングを追加する必要があります。
マッピングは、ペアを持つ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内容
実行中にオペコードを変更するプログラムでは、変換機能が失敗することに注意してください。
免責事項:上記のルールと仕様は、このツールではなく、チャレンジに対して信頼できるものです。これは、特にオペコード変換機能に適用されます。ここに示されているツールに仕様に関するバグがあると思われる場合は、コメントで報告してください:)
BRA(「常にブランチ」)は、制御フローにブランチを導入しない、それが呼び出されるべきではありませんかJMP?
BRAは、65C02やMC 68000のような後期のチップ設計(6502にはこのような命令はありません)にJMPも存在します。違いは、BRA相対アドレス指定とJMP絶対アドレス指定を使用することです。だから、私はこれらのデザインにしたがった-実際、それはすべての論理的ではないように聞こえる;)