HN 日本語サマリー

← 一覧へ戻る
プログラミング

CHIP-8の芸術

The Art of Chip-8 (beyondloom.com)

12 pointsby surprisetalk0 コメント

要約

CHIP-8は、1977年のCOSMAC VIPキットコンピュータ用に開発されたプログラミング言語であり、その命令セットとエミュレータの原理について解説しています。多くのCHIP-8実装が存在するものの、互換性の問題が指摘されており、本記事ではOctoアセンブラの記法を用いながら、移植性の高いプログラミングアプローチに焦点を当てて解説します。

全文翻訳

CHIP-8の芸術 CHIP-8は、1977年のCOSMAC VIPキットコンピュータ用に元々開発されたプログラミング言語です。 CHIP-8プログラムは、単純な仮想命令セットアーキテクチャのマシンコードに似た一連の2バイト命令で構成されているため、CHIP-8インタプリタは「エミュレータ」とも呼ばれます。 実際、CHIP-8インタプリタを書くことは、古いコンピュータやゲームコンソールのエミュレータの基本原則を学ぶのに最適であり、その結果、ほぼすべての考えられるプラットフォームで数千ものCHIP-8ランタイムが利用可能になっています。 歴史的なプラットフォームが生き残るためには、それらのために新しいソフトウェアを書く必要があります。 CHIP-8実装の増加は、半世紀にわたる伝言ゲームがインタプリタ間で広範な動作のばらつきを生み出したため、多くの混乱を引き起こしています。 私の高水準CHIP-8アセンブラであるOctoを開発する過程で、私は現存するCHIP-8の様々なフレーバー間の一般的なばらつきを捉える「クワークスフラグ」を普及させ標準化するのを助け、影響力のあるインタプリタの多くの暗く、仕様外の領域を調査しました。 現在、CHIP-8インタプリタとそのバリアントのための成熟したテストスイートが利用可能であるため、現代のインタプリタが詳細を間違える言い訳はありません。 それでも、実際のCHIP-8の現実は断片的です。多くのプラットフォームのインタプリタは、より広範なホビイストコミュニティを認識していない初心者が書き、PONG.CH8を(正しく)実行できるようになったらすぐに放棄されます。 この記事では、命令セットとしてのCHIP-8と、新しいプログラムを書く上での実際的な意味について考察し、私が過去に書いた多くの散在するチュートリアル、例、FAQを蒸留します。 特に、バグが最も少なく、最も不完全な既存のCHIP-8インタプリタを除くすべてで移植可能なアプローチを指摘します。 例コードはOctoの記法を使用します。このドキュメントはOctoアセンブリ言語の完全なリファレンスマニュアルを意図したものではありませんが、新しいアイデアに出会ったときに説明するよう努めます。 インデックス システム概要 算術命令 メモリ サブルーチン 制御フロー 入力 乱数 タイミング 出力 次へ進む場所 さらなる読書 システム概要 CHIP-8は12ビットのアドレス空間で動作します。 元のCHIP-8インタプリタは、この空間の最初の512バイトに常駐し、プログラムはアドレス0x200から開始します。 また、スタック、フレームバッファ、およびいくつかのスクラッチパッドのために、アドレス空間の上部領域の一部を予約していました。 その結果、コードとデータのために最大3232バイトが残されています。 現代のCHIP-8インタプリタはしばしばより寛大で、ユーザープログラムのために最大3584バイトを残し、しばしば低位512バイトをメモリに格納するために使用します。これは、それらの16進フォント、または何も格納しないため、プログラムが操作できる状態にします。 コードとデータは、最大限の移植性のために3232バイト以内に収まる必要があります。 v0-vfという名前の16個の汎用8ビットレジスタのファイルがあり、プラットフォームに心地よいRISC風の感触を与えています。 12ビットの「インデックスレジスタ」iは、メモリを参照または操作するすべての操作に使用されます。 サブルーチンのリターンアドレスをスレッド化するための内部スタックがありますが、それは不透明です。命令セットは、プログラムが一時値を自由にプッシュまたはポップしたり、スタックの内容を検査したりすることを許可しません。 プログラムは、16進キーパッドから入力を受け取ります。 出力のために、64x32ピクセルの1ビットビットマップディスプレイと、ノイズを出すためのシンプルなピエゾブザーがあります。 また、割り込みのない遅延タイマーと乱数ジェネレータも利用できます。 CHIP-8には34個の基本的な命令があります。 以下の説明では、vxとvyは任意のvレジスタであり、NNN、NN、Nはそれぞれ即値の12ビット、8ビット、または4ビット値です。 マシンコード Octo構文 ノート 00E0 clear ディスプレイをクリアします。 00EE ; または return サブルーチンを終了します。 1NNN jump NNN 2NNN NNN または :call NNN サブルーチンを呼び出します。 3XNN if vx != NN then 条件付きスキップ。 4XNN if vx == NN then 条件付きスキップ。 5XY0 if vx != vy then 条件付きスキップ。 6XNN vx := NN 7XNN vx += NN 8XY0 vx := vy 8XY1 vx |= vy ビットワイズ OR。 8XY2 vx &= vy ビットワイズ AND。 8XY3 vx ^= vy ビットワイズ XOR。 8XY4 vx += vy vf はキャリー時に1、それ以外の場合は0。 8XY5 vx -= vy vf はボロー時に0、それ以外の場合は1。 8XY6 vx >>= vy vf は古い最下位ビットを取得します。 8XY7 vx =- vy vf はボロー時に0、それ以外の場合は1。 8XYE vx <<= vy vf は古い最上位ビットを取得します。 9XY0 if vx == vy then 条件付きスキップ。 ANNN i := NNN BNNN jump0 NNN v0 + NNN のアドレスにジャンプします。 CXNN vx := random NN NN とビットワイズ AND されたランダムバイト。 DXYN sprite vx vy N ディスプレイに描画します。vf は衝突時に1、それ以外の場合は0。 EX9E if vx -key then キーが押されていないか? EXA1 if vx key then キーが押されているか? FX07 vx := delay FX0A vx := key キープレスを待ちます。 FX15 delay := vx FX18 buzzer := vx FX1E i += vx FX29 i := hex vx vx の16進文字スプライトに i を設定します。 FX33 bcd vx vx をBCD (Binary-Coded Decimal) にデコードします。 FX55 save vx v0-vx をアドレス i から i+x のメモリに保存します。 FX65 load vx v0-vx をアドレス i から i+x のメモリからロードします。 すべての命令は2バイト幅であり、コンポーネントフィールドはニブル(4ビット)単位で整列されています。これらの特性は、ペンと紙で手作業でアセンブルするプログラムを容易にし、一部の形式の自己変更コードの作成を簡素化するのに役立ちます。 次のセクションでは、これらの命令を機能的な目的別にグループ化して、より詳細に説明します。 算術命令 2つの算術命令は即値引数を取ります。 マシンコード Octo構文 6XNN vx := NN 7XNN vx += NN 残りは2つのレジスタを操作し、vxが結果を格納します。 マシンコード Octo構文 8XY0 vx := vy 8XY1 vx |= vy 8XY2 vx &= vy 8XY3 vx ^= vy 8XY4 vx += vy 8XY5 vx -= vy 8XY6 vx >>= vy 8XY7 vx =- vy 8XYE vx <<= vy Octoは、これらの操作のためにCファミリー言語に似た構文を使用し、対称性のためにPascalスタイルの := 代入演算子を使用します。 ^=、=-、<<=、>>= 命令は元のCHIP-8インタプリタに存在しましたが、文書化されていませんでした。それらはRCA-1802命令エンコーディングの自然な結果として生まれました。 =- 命令は -= と似ていますが、vyからvxを引くのではなく、vxからvyを引きます。常に結果はvxに格納されます。 シフト命令は、vxをvyを1ビット左または右にシフトした値に設定し、シフトアウトされたビットをvfに格納することを意図しています。 多くの現代のインタプリタは、vyを無視してvxをインプレースでシフトするように、これらの命令を誤って実装しています。 vx <<= vx および vx >>= vx の形式のシフト命令のみを使用してください。 両方の引数に同じレジスタを使用すると、「シフトクワークス」の有無にかかわらず一貫した動作が得られます。 一部のインタプリタでは、ビットワイズ操作 |=、&=、および ^= は副作用としてvfを変更します。 この問題は「シフトクワークス」よりもあまり知られておらず、エミュレートされたCOSMAC VIPでプログラムを実行しようとすると、かなりの驚きとなる可能性があります。 ビットワイズ命令 vx |= vy 、 vx &= vy、および vx ^= vy は vf の内容を破壊すると仮定してください。 ビットワイズ NOT を行う必要がある場合は、レジスタ内の適切な定数とのXORを使用できます。 vf レジスタは、定数が一度だけ必要な場合に理想的な選択肢です。 vf := 0xFF v0 ^= vf # v0 のビットを反転します 多くのインタプリタは、算術命令のメイン結果の前または後にキャリーフラグの結果を書き込むかどうかに一貫性がなく、宛先レジスタ(vx)がvfである場合に曖昧さを生じさせます。 vf := NN、vf += NN、または vf := vx 以外の場合、算術命令の宛先として vf レジスタを使用しないでください。 「ノープ(no-op)」命令が必要な場合は、vx := vx の形式の命令、例えば v0 := v0 が最良の選択肢です。 vx += 0 の形式の命令も機能します。これは重要な潜在的な「落とし穴」につながります。 16ビットカウンタをインクリメントしようとしているとします。 v0 += 1 # 低位バイトをインクリメントします v1 += vf # 高位バイトへのキャリー このスニペットの動作は未定義です。なぜなら、vレジスタに即値を加算してもキャリーフラグ vf は変更されないからです。 元のバージョンが、vfにすでにあったものに依存して、時々機能するように見えるという事実は、さらに腹立たしいだけです。 あなたはこれを行うつもりでした。