6502tinymachines

2C02 の難しい隅

P2 の報告。作業の後に書かれた。

P2 は、エミュレータの言い伝えが言い争ってきた問いを閉じる。どれも、 スイッチレベルのチップの上で走らせた台本付きのレジスタプログラムで押さえ、 リファレンスのシミュレータと照らし合わせたものだ。一つのスケジュール (tools/golden-trace/p2-schedule.json。このエンジンでの p2-schedule の 空走行が計算したもの) が、一本の軌道に沿って三つの場面を駆動する。 リファレンスは同じスケジュールを何も知らずに実行し (gen-p2.js)、六つの窓の 中の全ノードを吐き出す。テスト (tests/p2.rs) が再生して比べる。 REQUIRE_GOLDEN_P2=1 を付けた cargo test -p v2c02-sim --test p2。 MUTATE=1 は赤にならなければならない。

スプライト 0: ヒットはシリコンが置く場所に着く

スプライト 0 (y=90、x=180、べた塗りのタイルをべた塗りの背景の上に) は vpos 91、hpos 182 でヒットする: スプライトに書き下ろした x に 2 ドットの パイプライン遅延を足した位置で、押さえる前に実測した。二つのスプライトの窓、 つまりフェッチ行でのカウンタのロードと表示行でのヒットは、リファレンスに 対してノード単位でビット厳密に、600 状態を免除一つ無しで再生する。

そこへ至るまでがこのマイルストーンの本当の仕事で、一族で初めて証明された エンジンのダイバージェンスを生んだ。最初の走行は、スプライト 0 を左端で ヒットさせた: H 位置のカウンタ spr0_p が 0 と読めていて、x のバイトが そこへ届いていなかったからだ。spr_d の OAM データ線は、グループが両方の レールを抱えたときにリファレンス自身の chipsim.js が特別扱いする 8 ノード であり、その特別扱いを持たないこのエンジンは、そうしたグループを Vss 勝ちで 解決してバイトをゼロへ潰していた。リファレンス自身がこの場面を走らせて (gen-p2-probe.js) それを確かめた: あちらのカウンタは 180 をロードし、 スプライトの x でのヒットまで数え下げる。

直したのは halfphi 0.1.5 の レール衝突のホールドだ: チップに依らない仕組みに、チップの側が一覧を 渡す。印を付けたグループが両方のレールにつながっている場合、レールは打ち 消し合い、代わりの規則はリファレンス自身のもの、まずプル、次に面積で 重みを付けた電荷の投票になる。面積の投票に行き当たる前に、数え上げ式の 代替が二つ、初期化時の P0 のゴールデンで死んだ。計器を付けたリファレンス (gen-hold-probe.js) がその理由を示した。充電された構成員 11 個が、大きな 2 個に投票で負けていたのだ。一覧はピン留めした chipsim.js からビルド時に 抽出し、計数のテストの中で spr_d の名前と突き合わせる。面積は wires.js が計算するのと同じやり方で計算する。このホールドを入れると、P0 と P1 は緑の ままで、スプライトの場面はどの検査点でもリファレンスと一致する。

VBL 読み出しの競合: 取り逃がしの窓、実測

set_vbl_flag は vpos 241、hpos 1 で立つ (wiki の言う「走査線 241 の ドット 1」を、シリコンから)。そのセットをまたいでアクセスを掃かせながら $2002 を読むと、古典的な三つの結末が出て、スケジュールはそのそれぞれに読み出しを 一つ押さえる:

ずれ (読み出しの開始からセットを引いたもの)ビット 7 の戻り直後のフラグNMI
-30 から -240生き残るセットのところで普通に発火
-18、-12 (競合)0消える一度も発火しない
-6 から +121読み出しが消費するクリアされる

有名な vblank 取り逃がしの窓は、幅およそ 1.5 ドットと実測され、両側をきれいな 振る舞いに挟まれている。スケジュールした三つの読み出し (取り逃がし、抑止、 消費) は、このエンジンでビット 7 に [0, 0, 1] を返す。

競合の窓はノードのゴールデンではなく振る舞いで確かめてあり、その区別こそが 肝だ。スプライトの窓 (純粋なチップの振る舞い) にはノードのゴールデンが正しい 計器だった。しかし競合は、わざと作った準安定の境界だ: 読み出しは内部のフラグの セットから髪一本の距離に着地し、忠実な二つのエンジンは、観測できるものすべてで 一致しながら、内部ノードの別々の軌道に沿ってその境界を縫うことができる。 これが結果ではなく軌道の話だという証拠: リファレンス自身が三つの競合の 読み出しで標本した D7 を、そのゴールデンからちょうど標本の縁で読み返すと、 やはり [0, 0, 1] であり、どの整列でもこのエンジンと一致する。つまり、 観測できるもの、アーキテクチャ上の結果、標本した読み出しは、どれも両方の エンジンで一致する。競合を通して刻々の状態が違い、ノードで判定していないのは、 I/O 経路の内部ノード 54 個から 68 個 (read_2002_output_vblank_flag、 vbl_flag、int、CPU のバスピン) だ。このテストが検査するために存在して いる、まさにその vblank 読み出しのノードにノードマスクをかけては、テストを 打ち負かしてしまう。ここで正直な計器は、リファレンス自身の標本した結果と 突き合わせた振る舞いの表明だ。

OAM: 書き込み、属性のマスク、そして破壊は無い

手順についての事実が二つ落ちてきて、テストがそれを押さえている:

  • $2004 の書き込みは、アクセスの解放スロットで完了する。 直後に続けて アクセスすると、保留中の OAM 書き込みは取り消される。各書き込みの後に アクセス 1 幅分の空きを置くと (ちょうど DMA の間合いであり、cpucmd.js 自身の律動でもある)、どの書き込みも着地する。実在のバスマスタがこれより 速く書くことは無いので、これはチップのバグではなくハーネスの現実味の 制約であり、ハーネスはその空きを空けたままにしてある。
  • OAM のバイト 2 は 0xE3 でマスクされて読み返る、どの行でも。属性バイトの 未実装のビット 2..4 で、シリコンどおりだ。

文書化されている二つの引き金 (描画中の $2003 書き込み、描画が始まる時に OAMADDR が $28 に立っていること) のどちらでも、破壊は観測されなかった: 描画を一フレーム通した後で 64 バイトすべてが同一で、スケジュールした 16 回の 読み返しは、バイト 2 のマスクを効かせた恒等の詰め物 (各バイトに自分の番号を 入れたもの) を返す。

破壊の窓には、それ自身の発見が付いていて、それがこの窓も振る舞いで見る理由だ。 OAM のバイト 2 の未実装の属性ビットは、どんな $2004 の書き込みでも駆動でき ない物理的な DRAM のセルだ。その電源投入時の状態は、二つのエンジンが違うふうに 投げるコインであり、OAMADDR を停めたフレームは、スプライトの評価にそのセルが 乗っている行を読ませる。そのフレームにかけるノードのゴールデンは、作りからして コイン投げで、それは両方のエンジンで同じだ。最初のスケジュールは OAM の行を まるごと書かずに残しており、そのコインが描画パイプライン全体を分岐させた (破壊の窓までに 248 ノード)。今のスケジュールはまず 64 バイトの OAM をすべて 初期化し、それによって未定義の状態は書き込めない属性ビットに閉じ込められる。 だから破壊についての主張は、ノードではなくアーキテクチャ上の読み返しに拠る。

ゲート

  • スプライトの窓: 600 状態がノード単位でビット厳密、免除なし。
  • 競合と破壊の窓: アーキテクチャ上の結果を表明 ([0, 0, 1]、91,182 での ヒット、OAM の恒等と 0xE3 のマスク)。競合はリファレンス自身の標本した D7 と突き合わせた。
  • MUTATE=1 はワールドを、背景のタイルが透明なものに入れ替える: テストが 要求するスプライト 0 のヒットは一度も来ず、再生は spr0_/h0 で半ステップ 962572 に赤になる。妨害された版を祝福する分岐は一つも無い。

持ち越し

競合の窓の内部ノードの軌道は、忠実な二つのエンジンの間で違っていて、しかし 両方が観測できるものすべてと、標本した読み出しの結果で一致している。それが 準安定の境界における狭いアルゴリズムの違いなのか、二つのバスアクセスの再生の 非対称なのかは、まだ押さえていない。P2 にとって荷重を受けてはいないし、 観測できるものは両方のエンジンで確かめてある。コンソールの N5 のゲート 1 (VBL の競合をコンソール全体で再生する) が、この競合をピンではなくノードの 粒度で決着させる必要が出てきたときに、最初に引くべき糸がこれだ。

ビルド時に 2c02/docs/p2-report.md から取り込まれる。写しはリポジトリの一つだけだ。報告は独自の作業用の言葉をいくつか使う: 報告書で使われる言葉。