おそらく、多くのプログラマーは、もし夢を見ていなければ、少なくともプロセッサーの独自のエミュレーターを書くことを考えていました。 おそらく、Z80のようなものを試してみた人もいたでしょう。 しかし、エミュレータの最終的な実装に達した人は多くありませんでした。
この記事では、遠い70年代のCHIP-8ゲームプラットフォームの簡単なエミュレーターの作成についてお話したいと思います。 第一に、歴史に触れ、第二に、そのプラットフォームがシンプルであるため、このプラットフォームにより、初心者プログラマーでも完全に機能するエミュレーターを作成できます。
終わり
どんなに奇妙であっても、最後から始めます。 ここにそのようなプログラムがあります
オプションバイナリ; HP48ファイルではなく、バイナリファイルが必要です。
ALIGN OFF ; また、自動調整も必要ありません。
; データはワードではなくバイトで構成できます。
LD V0、0
LD V1,0
ループ :
LD I 、左; 乱数としてデフォルトで左線を描画します
; 0または1です。1になると仮定すると、
; 左の線を描きます。 0であれば、レジスタを変更します
; 正しい線を引きます。
RND V2、1 ; V2 a 0 ... 1の乱数を読み込む
SE V2、1 ; 1ですか? はいの場合、私はまだ左の行を指します
; ビットマップ。
LD I 、右; そうでない場合は、正しい行を参照するように変更します
; ビットマップ。
DRW V0 、 V1、4 ; そして、ビットマップをV0、V1に描画します。
ADD V0、4 ; 次のビットマップは4ピクセル右です。 更新します
; そうするためにV0。
SE V0、64 ; V0 == 64の場合、完全な線の描画が終了したため、
; V1も更新する必要があるため、LOOPへのジャンプをスキップします。
JP LOOP ; 完全な線を引きませんでしたか? 続けます!
LD V0、0 ; 各行の最初のビットマップは0、V1にあります。
ADD V1、4 ; V1を更新します。 次の行は、4ピクセルdoanです。
SE V1、32 ; すべての線を描画しましたか? はいの場合、V1 == 32。
JP LOOP ; いや? 続けます!
FIN : JP FIN ; 無限ループ...
右:; 左行の4 * 4ビットマップ
DB $ 1 .......
Db $ 。 1 ......
DB $ .. 1 .....
DB $ ... 1 ....
左:; 右行の4 * 4ビットマップ
; そして、はい、それはそのようなものです...
DB $ .. 1 .....
Db $ 。 1 ......
DB $ 1 .......
DB $ ... 1 ....
38バイトを占有し、このようにコンパイルされた形式で
最終的にエミュレータで実行され、およそ次の図が表示されます。
これで終わりです。少し退屈ですが必要な理論に戻ります。
建築
CHIP-8ゲームプラットフォームとは何ですか? 英語を話す人は
ウィキペディアの詳細な記事を読むことができますが、私は自分の言葉で要点をもう一度話そうとします。
CHIP-8は、
COSMAC VIPおよびTelmac 1800ゲームコンソール用に70年代半ばに作成されたインタープリタ型プログラミング言語で、CHIP-8用に作成およびコンパイルされたプログラムは、仮想マシンのコンソール自体で実行されます。 まあ、現代の類推で、それはJavaバイトコードのビットです。 一般的に、エミュレーターの作成時には、それがインタープリター言語であることを忘れて、鉄のプラットフォーム、つまり独自の命令セットを持つプロセッサーをエミュレートすることをお勧めします。 さらに、「プレフィックス」と言うときは、CHIP-8を意味します。
プレフィックスには、メモリ、プロセッサ、ビデオ出力デバイス、サウンド、そしてもちろん入力デバイスがあります。 すべてのコンポーネントをより詳細に検討してください。
記憶
プレフィックスには4Kbのメインメモリ(RAM)があります。 メモリはそれぞれオフセット200hで始まり、オフセットFFFhで終わります。 プログラムメモリが200hで始まるのはなぜですか? すべてが非常に簡単です-元のコンソールの最初の512バイトのメモリは、プレフィックスが構築されているプロセッサのマシンコードのCHIP-8言語のインタプリタによって占有されています。
登録
CHIP-8には、V0 ... VFという名前の16個の8ビットデータレジスタがあります。 VFレジスタは、加算/減算演算中のキャリーフラグを担当します。 プレフィックスには16ビットアドレスレジスタIもあります。
スタック
スタックは、ルーチンの完了時に戻りアドレスを保存するために使用されます。 セットトップボックスの元のバージョンのスタックサイズは48バイトで、これは12のサブルーチンアタッチメントレベルに対応しています。 リソースに制限がないため、16レベルの投資を使用します。 ほとんどのCHIP-8エミュレーターも同様です。
タイマー
プレフィックスには2つの8ビットタイマーが含まれ、両方ともゼロに達するまで60 Hzの周波数で減少します。
遅延タイマー:このタイマーはゲームのさまざまな遅延に使用され、その値はコマンドを使用して読み取り/変更できます。
サウンドタイマー:タイマー値がゼロ以外の場合、ビープ音が出力されます。
入力機器
入力は16キーを使用して行われます。 元のプレフィックスでは、キーには0hからFhのコードがあります。コンピューターでエミュレートする場合、キーボードの右側のNumPad部分(数字の0-9とNumLockが配置されている部分)を使用するのが最も便利です。 キー「8」、「4」、「6」、および「2」は通常、移動に使用されますが、常にそうではありません。 それはゲームに依存します。
グラフィックスとサウンド
コンソールでは、画面の解像度は64x32ピクセル、1色(モノクロ)です。 出力はスプライトを使用して実装されます。スプライトの幅は常に8ピクセルで、長さは1〜15ピクセルです。 描画中にスプライトが別のスプライトに重ねられると、オーバーレイポイントで色が反転し、VF(キャリーフラグ)レジスタの値が1になります。それ以外の場合、値は0になります。
上記のように、サウンドタイマーの値がゼロ以外の場合、不快なきしみ音が再生されます。 音がまったく気付かないと思います。これらのビープ音は好きではありません。
チーム
プロセッサ(実際にはCHIP-8)には35個の命令があり、各命令の長さは常に2バイトです。 ここでは、
Wikipediaにあるコマンドの表を再入力しません。 そこからいくつかの例を解析できます。例えば:
00E0画面をクリアします。 -コード00E0で会ったら、画面をクリアしてください。
6XNN VXをNNに設定します。 -VXレジスタをNNに設定します。 たとえば、635Aコマンドに適合した場合、値5AhをV3レジスタに書き込む必要があります。
練習する
上記から、このプラットフォームはエミュレーターの原理の研究を開始するのに最適であることがわかります。 ここでは、トリッキーなマスクおよび非マスク割り込み、I / Oポートを備えた周辺機器のヒープ、複雑なタイマーなどはありません。 ファイルから2バイトのコマンドを自分で読み、それらをopcodes yesと比較して、必要なことを実行します。 はい、そしてチームはすべて何もない-35個。 落とし穴はありますが、どこに落とし穴がありますか? それでは、始めましょう。 そして、記憶から始めましょう。
エミュレータを起動するときに最初に行うことは、仮想マシンの初期化であることは明らかです。 つまり、メモリ、スタック、レジスタ、およびビデオメモリをクリアします。 上で書いたように、エミュレートされたプログラムをロードするオフセットは200hです。 これに先立ち、つまり、オフセット000hから1FFhまで、元のインタープリターを見つけなければなりません。 その中には、オフセット000hから050hまでで始まり、80バイトかかる小さなフォントがあります。 それは私のエミュレータのソースコードで見ることができます。 はい、
フランスのデルファイについて謝罪しますが、私はそれをプログラムします、私を責めないでください。 簡単にするために、この構造を作成しました。
表示: バイトの 配列 [ 0..64 * 32-1 ] 。 //ビデオメモリ
メモリ: バイトの 配列 [ 0..4095 ] 。 // RAMメモリ
スタック: Wordの 配列 [ 0..15 ] 。 //スタック
レジスタ: バイトの 配列 [ 0..15 ] 。 //登録
rI : 単語 = 200ドル。 //登録します
SP : バイト = 0 ; //スタックカウンター
PC : ワード = 200ドル。 //メモリオフセットカウンター
delay_timer : バイト = 255 ; //遅延タイマー;
sound_timer : バイト = 255 ; //サウンドタイマー;
そのため、最初にすべての配列をゼロで埋めてから、フォント(フォント:バイトのFont:array [1..80])をゼロから始まるMemory配列にコピーし、すべての値を初期化します。
FillChar (メモリ、 4096、0 ) ; //メインメモリをクリアします
移動(フォント、メモリ、 80 ) ; //オフセット000hでフォントをコピーします
FillChar (スタック、 16、0 ) ; //スタックをクリアします
FillChar (レジスタ、 16、0 ) ; //レジスタをゼロにリセットします
rI := 200ドル。 //プログラムの開始時にアドレスレジスタI
PC := $ 200 ; //配列オフセット
SP := 0 ; //スタックカウンター
delay_timer := 0 ; //タイマーをゼロに
sound_timer := 0 ;
これですべてが準備され、エミュレートされたプログラムをオフセット200hでメモリに読み込み、コードの解釈を開始できます。 ここでは、ビットが誰であるか、バイトと単語(単語)からそれらを抽出する方法を少し覚えておく必要があります。 簡単にするために、2バイトのオペコードが渡され、解釈され、実行されるExecuteOpcode(opcode:word)プロシージャを作成しました。 意味を理解するために、
Wikipediaのコマンドの表を確認できます。
プロシージャ ExecuteOpcode ( opcode : word ) ;
始める
case ( op_code and $ F000 ) shr 12 of //オペコードから最初の4ビットを選択
$ 00 : 開始//オペコードをゼロから開始
ケース op_code と $ 00FF の
//これはオペコード00E0です-画面をクリアします
$ E0 :開始
//物事を行います。つまり、画面を愚かにクリアします。
出る
終了 ;
//そして、これは00EEです-プロシージャを終了します
$ EE :開始
//スタックからアドレスを復元し、ジャンプします
出る
終了 ;
終了 ;
//そして、オペコードがゼロから開始されたが、E0もEEも終了しなかった場合、ここに到達します
//したがって、無効なオペコードメッセージを取得するか表示する
出る
終了 ; // nullオペコードのチェックを終了します
$ 01 : Begin //オペコードの最初の4ビットは1です(オペコードは1で始まります)
//これはJMP、ジャンプです。 目的のアドレスにジャンプします
PC := op_code および $ 0FFF;
出る
終了 ;
$ 02 :開始 //オペコードの最初の4ビットは2です(オペコードは2で始まります)
//サブルーチンを呼び出します。
//スタックポインタを増やします
//現在のアドレスをスタックにプッシュします
//サブルーチンを突く
終了 ;
//
//これは、オペコードが7で始まるまで続きます。
//
$ 08 : Begin //オペコードは8から始まります。ここでは、最後の4ビットを見る必要があります
case op_code および $ 000F of //最後の4ビットのopcode
// mov vx、vy
$ 00 :開始
//値VYをレジスタVXに入れます
出る
終了 ;
//またはvx、vy
$ 01 :開始
// VX = VXまたはVY
出る
終了 ;
//
//これは0Eまで続きます
//
終了 ; //オペコードの最後の4ビットのチェックの終了
//無効なオペコードの場合、ここに到達します
出る
終了 ; //オペコードが8で開始したかどうかのチェックの終わり
など、アイデアは多かれ少なかれ明確でなければなりません。 インタープリターの作成中に、一部のコマンドにスタブを使用できます。 さて、基本的なプロセッサ命令を実装するとき、画面に出力を描画し、入力デバイスを実装することが残っています。 DXYNコマンドは、画面に表示する役割を果たします。 レジスタVXは座標X、レジスタVYは座標Yであり、そこからスプライトの描画を開始する必要があります。 このときのアドレスレジスタIは、スプライトのビットマップイメージを示します。 私はグラフィックスの描画の実装を適用しません。特にこの投稿の最後のソースで常に見ることができるので、特に問題はないはずです。 キーボードも同様です。
おわりに
もちろん、この記事では実装のすべての詳細に言及することはできませんでした。 目標は、単に考えを考え出し、オペコードの分析を示すことです。 興味のある方は、Delphiでのエミュレータの実装を確認するか、インターネットでエミュレータの他の実装を見つけることができます。 何千と言ってもファッショナブルです。 Visual Basicから始まり、鉄のソリューションで終わる。
私は自分のコードを事前に謝罪します。整理しませんでした-そのまま流し込みました。 主な興味深いファイルはhchip.pasで、すべてのエミュレーションを実装しています。
また、英語を話す優れたフォーラムEmuTalkもあります。このフォーラムでは、Chip-8のエミュレート専用の
スレッドが特に強調されています。
おそらく最高のチップ8エミュレーターとゲームの1つをダウンロードできる
ページ 。
とにかく、Google「chip-8」のリクエストに応じて、必要なものをすべて見つけることができます。
他に何ができますか? エミュレータをわずかに変更して、Super chip-8命令とスプライトをサポートできます。 はい、できます。
みなさん、良い一日を。