4

私はエミュレーションの紹介としてChip8エミュレーターを書いていますが、ちょっと迷っています。基本的に、私はChip 8 ROMを読み取り、それをメモリ内のchar配列に格納しました。次に、ガイドに従って、次のコードを使用して、現在のプログラムカウンター(pc)でオペコードを取得します。

// Fetch opcode
opcode = memory[pc] << 8 | memory[pc + 1];

チップ8オペコードはそれぞれ2バイトです。これは、memory [pc]に8ビットスペースを追加し(<< 8を使用)、次にmemory [pc + 1]をそれにマージして(|を使用)、結果をオペコード変数に格納することとして漠然と理解しているガイドのコードです。 。

しかし、オペコードが分離されたので、どうしたらよいかわかりません。私はこのオペコードテーブルを使用していますが、読み取った16進オペコードをそのテーブルのオペコード識別子と照合することに関して基本的に迷っています。また、私が読んでいるオペコードの多くにはオペランドも含まれていることを認識しています(後者のバイトを想定していますか?)。これはおそらく私の状況をさらに複雑にします。

ヘルプ?!

4

4 に答える 4

10

基本的に、命令を取得したら、それをデコードする必要があります。たとえば、オペコードテーブルから:

if ((inst&0xF000)==0x1000)
{
  write_register(pc,(inst&0x0FFF)<<1);
}

そして、命令ごとに2バイトのROMにアクセスしているので、アドレスはおそらくバイトアドレスではなく(16ビット)ワードアドレスであると推測して、1つ左にシフトしました(これらの命令がどのようにエンコードされているかを調べる必要があります。提供したオペコードテーブルそれには不十分であり、仮定を立てる必要はありません)。

やらなければならないことがもっとたくさんあり、githubサンプルにそれについて何か書いたかどうかはわかりません。アドレスで命令をフェッチするためのフェッチ関数、読み取りメモリ関数、書き込みメモリ関数、読み取りレジスタ関数、書き込みレジスタ関数を作成することをお勧めします。デコードおよび実行関数は、一度に1つの命令のみをデコードして実行することをお勧めします。通常の実行では、ループで呼び出すだけで、多くの余分な作業を行うことなく、割り込みなどを実行できます。また、ソリューションをモジュール化します。fetch()read_mem_byte()read_mem_word()などの関数を作成する。コードをモジュール化すると(パフォーマンスがわずかに低下します)、レジスタまたはメモリアクセスを監視し、何が起こっているか、何が起こっていないかを把握できる単一の場所があるため、デバッグがはるかに簡単になります。

あなたの質問に基づいて、そしてあなたがこのプロセスのどこにいるのか、私はあなたがエミュレーターを書く前にあなたが最初にする必要があることは逆アセンブラーを書くことだと思います。固定命令長の命令セット(16ビット)であるため、はるかに簡単です。ROM内の興味深いポイントから開始することも、必要に応じて最初から開始して、表示されるすべてをデコードすることもできます。例えば:

if ((inst&0xF000)==0x1000)
{
  printf("jmp 0x%04X\n",(inst&0x0FFF)<<1);
}

たった35の命令で、午後、おそらく土曜日全体が、命令をデコードするのは初めてです(私はあなたの質問に基づいていると思います)。逆アセンブラは、エミュレータのコアデコーダになります。printf()をエミュレーションに置き換えます。さらに、printfsをそのままにして、命令の実行をエミュレートするコードを追加するだけで、実行を追跡できます。(同じ取引では、単一の命令関数を逆アセンブルし、命令ごとに呼び出します。これがエミュレーターの基盤になります)。

このタスクを実行するには、ビット操作を十分に理解している必要があります。コードのフェッチ行が何をしているのかについて、理解が曖昧である必要があります。

また、私はあなたが提供したそのコード行をバグがあるか、少なくとも危険だと呼びます。memory []がバイトの配列である場合、コンパイラはバイトサイズの計算を使用して左シフトを実行するとゼロになり、2番目のバイトでゼロまたはredを実行すると、2番目のバイトのみになります。

基本的に、コンパイラはこれを有効にする権利の範囲内にあります。

opcode = memory[pc] << 8) | memory[pc + 1];

これに:

opcode = memory[pc + 1];

これはまったく機能しません。非常に簡単な修正です。

opcode = memory[pc + 0];
opcode <<= 8;
opcode |= memory[pc + 1];

あなたにいくつかの頭痛を救うでしょう。最小限の最適化により、コンパイラーは、操作ごとに中間結果をRAMに保存する必要がなくなり、同じ(望ましい)出力/パフォーマンスが得られます。

私が上で書いた命令セットシミュレーターは、パフォーマンスを目的としたものではなく、読みやすさ、可視性、そしてできれば教育的なものを目的としています。私はそのようなものから始めます、そして例えばパフォーマンスが興味があるならあなたはそれを書き直さなければならないでしょう。このchip8エミュレーターは、一度経験すると、最初から午後のタスクになるため、これを初めて実行すると、週末に3〜4回書き直すことができますが、記念碑的なタスクではありません(書き直さなければなりません)。 )。(サムレーターは週末に大部分を費やしました。msp430はおそらく夕方か2分の仕事のようなものでした。オーバーフローフラグを正しく取得することが最大のタスクでしたが、それは後で起こりました。 )。とにかく、ポイントは、mameソースのようなものを見て、これらの命令セットシミュレータのすべてではないにしても、ほとんどが実行速度のために設計されており、多くはかなりの量の研究なしではほとんど読めません。多くの場合、テーブル駆動型であり、Cプログラミングのトリックが多い場合もあります。管理しやすいものから始めて、適切に機能させてから、速度、サイズ、移植性などを改善することを心配してください。このchip8はグラフィックベースのように見えるので、ビットマップ/画面/どこでも多くの線画やその他のビット操作を処理する必要があります。または、APIまたはオペレーティングシステムの関数を呼び出すこともできます。基本的に、このchip8は、レジスタとアドレッシングモードとalu演算のランドリーリストを備えた従来の命令セットではありません。時には多くのCプログラミングのトリックなどがあります。管理しやすいものから始めて、適切に機能させてから、速度、サイズ、移植性などを改善することを心配してください。このchip8はグラフィックベースのように見えるので、ビットマップ/画面/どこでも多くの線画やその他のビット操作を処理する必要があります。または、APIまたはオペレーティングシステムの関数を呼び出すこともできます。基本的に、このchip8は、レジスタとアドレッシングモードとalu演算のランドリーリストを備えた従来の命令セットではありません。時には多くのCプログラミングのトリックなどがあります。管理しやすいものから始めて、適切に機能させてから、速度、サイズ、移植性などを改善することを心配してください。このchip8はグラフィックベースのように見えるので、ビットマップ/画面/どこでも多くの線画やその他のビット操作を処理する必要があります。または、APIまたはオペレーティングシステムの関数を呼び出すこともできます。基本的に、このchip8は、レジスタとアドレッシングモードとalu演算のランドリーリストを備えた従来の命令セットではありません。このchip8はグラフィックベースのように見えるので、ビットマップ/画面/どこでも多くの線画やその他のビット操作を処理する必要があります。または、APIまたはオペレーティングシステムの関数を呼び出すこともできます。基本的に、このchip8は、レジスタとアドレッシングモードとalu演算のランドリーリストを備えた従来の命令セットではありません。このchip8はグラフィックベースのように見えるので、ビットマップ/画面/どこでも多くの線画やその他のビット操作を処理する必要があります。または、APIまたはオペレーティングシステムの関数を呼び出すこともできます。基本的に、このchip8は、レジスタとアドレッシングモードとalu演算のランドリーリストを備えた従来の命令セットではありません。

于 2011-07-08T05:56:48.483 に答える
4

基本的に-オペコードの可変部分をマスクして、一致するものを探します。次に、可変部分を使用します。

たとえば、1NNNはジャンプです。それで:

int a = opcode & 0xF000;
int b = opcode & 0x0FFF;
if(a == 0x1000)
   doJump(b);

次に、ゲームは、必要に応じて、そのコードを高速または小型、あるいはエレガントにすることです。きれいに楽しく!

于 2011-07-08T04:17:14.900 に答える
3

CPUが異なれば、メモリへの値の格納方法も異なります。ビッグエンディアンのマシンは、$ FFCCのような番号をFF、CCの順序でメモリに格納します。リトルエンディアンのマシンは、CC、FFの逆順で(つまり、「リトルエンド」が最初に)バイトを格納します。

CHIP-8アーキテクチャはビッグエンディアンであるため、実行するコードには、ビッグエンディアンで記述された命令とデータが含まれています。

「opcode=memory [pc] << 8 | memory [pc + 1];」というステートメントでは、ホストCPU(コンピューターのCPU)がリトルエンディアンであるかビッグエンディアンであるかは関係ありません。常に16ビットのビッグエンディアン値を正しい順序で整数に入れます。

役立つ可能性のあるリソースがいくつかあります。http : //www.emulator101.comには、いくつかの一般的なエミュレーター手法とともに、CHIP-8エミュレーターのチュートリアルがあります。これも良いです:http ://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/

于 2012-01-13T05:50:47.673 に答える
2

これらのオペコードを解釈するには、16ビットワードから実際のオペコードを有限状態マシンと組み合わせて取得するために、さまざまなビットマスクを設定する必要があります。これは、オペコードの方法にいくつかの問題があるように見えるためです。エンコードされます(つまり、特定のオペコードにはレジスタ識別子などがありますが、他のオペコードは単一の識別子でかなり簡単です)。

有限状態マシンは基本的に次のことを実行できます。

  1. `0xF000のようなマスクを使用して、オペコードの最初のニブルを取得します。これにより、オペコードを「分類」できるようになります
  2. 手順1の関数カテゴリに基づいて、さらにマスクを適用して、オペコードからレジスタ値を取得するか、または他の変数をオペコードでエンコードして、呼び出す必要のある実際の関数を絞り込みます。引数。
  3. オペコードと変数の情報を取得したら、オペコードの機能とオペコードに付随する変数と一致する適切なハンドラーを持つ関数の固定長テーブルを検索します。ステートマシンでは、適切な機能を分離した後、各オペコードに対応する関数の名前をハードコーディングできますが、各オペコードの関数ポインターを使用して初期化するテーブルは、より柔軟なアプローチです。コード機能を簡単に変更できるようになります(つまり、デバッグハンドラーと「通常の」ハンドラーなどを簡単に切り替えることができます)。
于 2011-07-08T04:32:57.350 に答える