ただし、NES には、CPU のアドレッシング モードに応じて、2 バイトまたは 3 バイトのオペコードがあるようです。オペコードごとに読み取る必要があるバイト数を簡単に計算する方法は思いつきません。
オペコードはまだ 1 バイトです。余分なバイトは、明示的なオペランドを持つ命令のオペランドを指定します。デコードを行うには、256 ケースの -block を作成できますswitch
(一部のオペコードが不正であるため、実際には 256 ケースにはなりません)。次のようになります。
opcode = ReadByte(PC++);
switch (opcode) {
...
case 0x4C: // JMP abs
address = ReadByte(PC++);
address |= (uint16_t)ReadByte(PC) << 8;
PC = address;
cycles += 3;
break;
...
}
コンパイラは通常、ケースのジャンプ テーブルを作成するため、かなり効率的な (少し肥大化した) コードが得られます。
もう 1 つの方法は、オペコードごとに 1 つのエントリを持つ配列を作成することです。これは単純に、オペコードごとに 1 つの関数を持つ関数ポインターの配列である可能性があります。または、テーブルには、オペランドをフェッチするための 1 つの関数へのポインター、実際の操作を実行するための関数へのポインター、および命令が必要とするサイクル数に関する情報を含めることができます。このようにして、多くのコードを共有できます。例:
const Instruction INSTRUCTIONS[] =
{
...
// 0x4C: JMP abs
{&jmp, &abs_operand, 3},
...
};
また、サイクルをカウントする方法を理解するのにも問題があります。すべてが同期するようにプログラミング言語内で時計を作成するにはどうすればよいですか?
CPU サイクルのカウントは、上記のコード例で示したように、カウンターをインクリメントするだけです。
ビデオを CPU と同期させる最も簡単な方法は、1 つのスキャンラインのアクティブな表示期間に対応するサイクル数だけ CPU を実行し、次に 1 つのスキャンラインを描画してから、水平方向に対応するサイクル数だけ CPU を実行することです。ブランキング期間、最初からやり直します。
オーディオを使い始めると、どのように同期するかは、使用しているオーディオ API によって少し異なります。たとえば、一部の API はコールバックを送信し、これに応答してバッファをサンプルで満たし、生成されたサンプルの数を返します。この場合、前のコールバック以降にエミュレートされた CPU サイクル数を計算し、それに基づいて生成するサンプル数を決定できます。
無関係な補足として、NES はリトルエンディアンであるため、正しいオペコードを取得するには、programCounter + 1 を読み取ってから programCounter を読み取る必要がありますか?
オペコードは 1 バイトであり、6502 の命令は他の CPU アーキテクチャのように 1 つのワードにパックされていないため、エンディアンはあまり重要ではありません。これは16 ビット オペランドに関連しますが、一方で PC やほとんどの携帯電話もリトルエンディアンの CPU に基づいています。