6502tinymachines

二つのチップを一つのクロックで

N5 報告、作業の後に書かれたもの。

走行スタンプ: 2026-09-06、rustc 1.97.1。ピン: 6502 3805107 (v6502-micro、 v6502-pins、そしてゲートのオラクルとしての v6502-sim。2026-09-06 の走行では 89ae24f。2026-09-12 と 2026-09-13 に、ラング 3 の継ぎ目と分岐成立の規則、そして 記録済みバスのために、そして 2026-09-20 に二度、ページをまたいで後ろへ成立する 分岐のために、続いて同じページの中で前へ成立する分岐のために動かした。どの移動でも スイートは緑)、2a03 は 2026-09-06 の走行で dbf116b (v2a03-micro をパスで参照。 そのパスの先頭はいま f1bced7。DMA のメモの修正を得て、6502 のピンに追従した)、 2c02 はその走行で 474b7e7 (v2c02-fast をパスで参照。先頭はいま 172264a。 ステッパが飛ばしていたスプライトの枠を得て、nes-bus に追従した)、nes-bus は その走行で v0.1.1、いまは v0.1.6、nes-glue は N4 から。アラインメント (二つのクロックの位相の組み合わせ) の スタンプ: Alignment::MEASURED、cpu_phase 4、ppu_phase 3。スループット: 1 コアで 毎秒 125 から 140 フレーム、実時間の 2.1 倍から 2.3 倍、CPU はラング 3、PPU は 高速ラング。cargo test -p nes-console: 6 テスト (配管、NMI の再生、レースの再生を 両側で)。このクレートは crates/nes-console。

ゲート 1 は閉じた: どちらの再生もコンソールを通して走り、押さえられている。 ゲート 2 は以下に全部記録した。そこに残る取りこぼしは一つの問いで、ゲート 1 の 末尾で名指しした。ゲート 3 には ROM が無い。

スケジューラ

マスタ半ステップの計数器が一つ。計数器が 8 で割って ppu_phase のとき PPU が 1 ドット進み、12 で割って cpu_phase のとき CPU が半サイクル進む。PPU が先だ (その /INT と APU の IRQ は、CPU が自分の半サイクルの始まりに標本化するものだ から)。二つの位相はスイッチレベルのチップ自身の分周器から実測したもので、それぞれ リセットの最後のパルスからマスタクロックで進めてある。2A03 の clk0 はマスタ半 ステップ 4、16、28 で変わり (2a03: v2a03-sim/examples/clk-phase.rs)、2C02 の pclk0 は 3、11、19 で立ち上がる (2c02: v2c02-sim/examples/clk-phase.rs)。これは分周器が 電源投入で入りうるアラインメントの一つだ。CPU の読みは phi1 で始まり、それは マスタ半ステップ 24 ごとに再来するので、ドットの 8 に対して 24 のクラスがある。 cpu_phase は 0 から 23、ppu_phase は 3 だ (最初の草稿は 4 と数えていた。 半サイクルの格子を phi1 の格子と取り違えていた)。コンソールはこのスタンプで走った ものを記録し、run-rom は別のもので走らせるために ALIGN=cpu,ppu を取る。壁時計に 合わせた歩調合わせは作っていない。コンソールは出せるだけの速さで走り、その速度を 報告する。

ボード (board.rs) は CPU のアクセスをすべて nes-glue のデコードに通す。 TMM2115 二つ、カートリッジ、PPU のレジスタ ($2002 の読みは、CPU の半サイクルが 始まった時点の現在のドット内の半ステップを運ぶ。高速 PPU のレースのモデル用だ)、 74LS368 を通した $4016/$4017 とコントローラ、そして開放バスはバスが最後に運んだ 値だ。$6000 の 8 KiB のカートリッジ RAM はテスト ROM に差し出し (Console::with_prg_ram)、あれらのものだと札を付けている。PPU の CHR と CIRAM は PpuBus を通してカートリッジのもので、NROM のミラーリングはカートリッジ自身の CIRAM A10 による。

ゲート 1: アラインメント

三通りに押さえている。それぞれ crates/nes-console/tests の中のテストだ。

配管 (plumbing.rs): テストカートリッジ (testrom.rs: vblank を二回待ち、 4 行を塗り、NMI を RAM で数えるプログラム) の 6 フレーム。マスタの計数器は PPU の 位相からドットあたり 8 で、描画を入れた奇数フレームはどれも 1 ドット短く、それを 自分のパリティで述べる。絵は同じワールドでの高速 PPU 単体のフレームと等しく、 RAM の中の NMI の数は 1 フレームに 1 だ (ポーリングの待ちがレースに vblank を 取られた 1 回を除く。あれはレースがチップのすることをしているだけだ)。

BRK 中の NMI の再生 (gate1_nmi.rs): PPU の最初の vblank が、プログラムには 動かせないマスタ半ステップで /NMI をアサートする。長さの分かっているそり (sled) が BRK のフェッチを、そのエッジから選んだ半サイクルの位置に置く。1 サイクル刻みで 八つのオフセット、エッジの 13 半サイクル前から 1 後まで。だからエッジは BRK の どのサイクルにも、そしてその前のサイクルにも落ちる。コンソールは CPU の半サイクル ごとに入力とピンのフレームを記録し、ラング 0 (スイッチレベルの 6502、v6502-sim。 同じピンの dev-dependency) が、コンソールが見たその半サイクルから /NMI を低に 駆動して、コンソール自身の電源投入時の RAM イメージの上で同じバイト列を走らせる。 二つのピンのトレースは、BRK のフェッチの 30 前から 90 後までの全半サイクルで一致 する: 取られたベクタ、プッシュ、タイミング。スタックのページは、二つのコアの 電源投入時のスタックポインタの差を取り除いて比べる。差は、それぞれが最初に行う プッシュから導いた。データのバイトは書き込みの phi1 では飛ばす (2A03 の ロックステップが名付けたクラスだ)。960 半サイクル、八つのオフセット、厳密に一致。

これは一度目では通らなかった。そしてそれが、これを走らせた意味だった。エッジが BRK のベクタ読みの中にあると、ラング 3 はハンドラの最初のフェッチを横取りし、 ラング 0 はハンドラの最初の命令を走らせた。そこで brk-nmi-probe (6502) が、NOP と BRK の周りの全半サイクルで両方のラングを測った。エッジもレベルも、半サイクル 一つまで縮めたパルスも。そしてラング 3 は、ラング 0 のすることに合わせて書き 下ろされた: あるサイクルの phi1 が始まる時点で存在する入力は、来るフェッチで 取られる。最終サイクルの phi1 も含む。その phi2 に着いたものは待つ。NMI のエッジは 二つの phi1 の標本を比べたものなので、phi2 の中だけに収まった低はエッジではない。 五番目のサイクルの phi1 でエッジを標本化した BRK は NMI のベクタを取り、どの BRK も ポーリングをせずに終わる。台本を書いた七つのトレースが、どの継ぎ目の両側でも 6502 のゴールデンに加わった。それから 2A03 自身のパッドをマスタクロックで計った (nmi-latency-probe、2a03): 最後の phi1 が始まる 1 マスタパルス前に着いた低は 取られ、そのパルスの上に着いたものは待つ。経路の中には他に何も無い。

レースの再生 (gate1_race.rs): コンソールのアクセスの形でスイッチレベルの 2C02 の上で実測した表がオラクルだ (race-shape-probe、2c02: レジスタのアドレス、 R/W、/CS を、74LS139 がデコードするとおり CPU の phi1 でまとめて当て、バイトは 11 番目の半ステップで取る。各事象の 48 半ステップ前から 24 後までの全半ステップ、 保存した一つのチップ状態から)。セットのドット (vpos 241、hpos 1。フラグと /INT は ドットの境界で立つ) の最初の半ステップを基準にすると、8 半ステップ以上前に始まる 読みは外し、1 から 7 前に始まるものはセットを抑え、ドットの上かそれ以降に始まる ものはそれを消費する。クリアのドット (vpos 261、hpos 1) を基準にすると、その上か それ以降の読みはクリアを読み、前の読みはセットを読む。高速 PPU は自分のテストで その表を押さえ、コンソールのテストはコンソールをその表に押さえる: サイクルの パリティが交互になるループを持つポーリングのプログラムが、読みをセットをまたいで 歩かせ、NMI の後に計算した遅延を置いてフレームに一度だけ読むプログラムが、クリアの 半サイクル以内に置かれて、クリアをまたいで歩く。どちらも 24 通りのアラインメント すべてで走り、事象の 5 ドット以内に始まる読みはどれも、チップがそこで読むものを 読み、NMI をチップが残すとおりに残さなければならない。セットの周りで 145 回、 クリアの周りで 185 回の読み。どちらの窓も全半ステップを覆い、どの結果もチップの ものだ。

二つのチップが揃っても説明しないもの。 レースと NMI の両方をスイッチレベルの チップに押さえた上で、blargg の 02-vbl_set_time と 03-vbl_clear_time は実測の アラインメントで通り、それでも彼のタイミングのテスト四つが、文書化された コンソールから 1 ドットか 2 ドット離れたままだ。05-nmi_timing と 10-even_odd_timing は、ROM がレースから取っている同期で 1 ドット、 07-nmi_on_timing と 08-nmi_off_timing は 2 ドット、そして 06-suppression では 文書化されたコンソールがフラグのセットを読んで NMI を取らない 2 行だ。どれも同じ ことを言っている: 文書化されたコンソールの NMI は、フラグと一緒に /INT が落ちる PPU から、1 マスタパルスのセットアップで phi1 に標本化する CPU へ、という組が 許すよりおよそ 2 ドット遅く CPU に届く。プローブは、チップの中にそういう窓を 作るものを見つけた: アドレスがセレクトより L 半ステップ先行する読みは、フラグの セットを読み、それをクリアし、/INT は決して落ちない。セットの後 L までのセレクトに ついてそうなる (P2 のリファレンスのプロトコルは 8 先行して 8 を示した。M2 で 修飾したセレクトなら 6 先行する)。2 ドットは 16 であり、ボードは先行を一切 配線していない。だから問いはこうだ: 実機の NES-001 の上で、PPU の /INT と CPU の NMI ピンは M2 に対して何をするのか。これは 4 本の線にスコープを当てることであり、 スケッチの第 5 節の一行だ。それが取られるまではコンソールがチップを押さえ、五つの テストは、その実測に賭かっているものとしてここに名指ししておく。

ゲート 2: blargg、端から端まで

各 ROM をコンソール全体に通す。2A03 のコアはラング 3 で APU の表付き、2C02 は 高速ラング、糊、そして報告のための $6000 の 8 KiB。run-rom は報告の窓と最後の フレームを印字する。sprite-hit と cpu_timing の ROM は画面にしか報告しないので、 そこで読んだ。断りが無ければアラインメントは 4,3。

スイート結果
cpu_timing_test6合格 (画面上、公式命令)
instr_test-v5 01..1616 件中 16 件 合格
ppu_vbl_nmi 01 vbl_basics合格
ppu_vbl_nmi 02 vbl_set_time合格 (コンソールの形でレースを実測した上で)
ppu_vbl_nmi 03 vbl_clear_time合格
ppu_vbl_nmi 04 nmi_control合格 (#11 には下の NMI のエッジの規則が要った)
ppu_vbl_nmi 05 nmi_timing1 ドット差で失敗: 行 01 が 3 を読み、文書化されたコンソールは 4 を読む (NMI の問い、ゲート 1)
ppu_vbl_nmi 06 suppression失敗: 行 05 と 06 が V N を読み、文書化されたコンソールは V - を読む (NMI の問い)
ppu_vbl_nmi 07 nmi_on_timing2 ドット差で失敗: N は行 02 まで、文書化されたものは 04 まで (NMI の問い)
ppu_vbl_nmi 08 nmi_off_timing2 ドット差で失敗: N は行 05 から、文書化されたものは 07 から (NMI の問い)
ppu_vbl_nmi 09 even_odd_frames合格 (00 01 01 02)
ppu_vbl_nmi 10 even_odd_timing#2 が、ROM がレースから取る同期で 1 ドット差で失敗 (「skipped too soon」)。レースの以前の、合わせ込んだ窓の下では合格していた
sprite_hit_tests 01..07, 09..11合格 (画面上)
sprite_hit_tests 08 double_height合格 (2026-09-06。高速 PPU の背の高いスプライトの規則をラング 0 で実測して押さえるまでは名指しで拒んでいた、2c02 1dc887a)
apu_test 1..88 件中 8 件 合格 (2026-09-06。最初の走行では六つが失敗していた)

APU の行は、N3 の表が CPU 側のオラクルに初めて出会ったものだ。失敗した六つは どれも、$4017 への書き込みの後のシーケンサの位置、ステータスレジスタのタイミング、 DMC のバイトの数え方のどれかで、列そのものではなかった。それぞれ書き下ろす前に スイッチレベルの 2A03 で実測した (2a03 のリポジトリ、2026-09-06)。書き込みの ジッタ (そのリセットは、APU のパリティの一方ではストローブの 1 半ステップ後、 他方では 3 半ステップ後に着地し、以降の事象はどれも 2 ずつ離れる。記録器はいま 両方のパリティを押さえている)。モード 1 の書き込みの、その場での 1/4 と 1/2 の クロック。ステータスは読みの phi2 の終わりにラッチされ、コアが自分のバスに尋ねる 半ステップ後だ (6502 の MicroBus::read_late がそれを運び、ピンがバスのバイトを 保つ)。フレーム IRQ のフラグは連続する三サイクルで立つので、その中でそれをクリア する読みは、また立っているのを見つける。三角波とノイズの長さのビットは、ダイの 一つの半ステップに乗る。そして DMC のバイトは自分のバス読みの 6 半ステップ後に 数えられ、それは有効化の 6 (または 8) 後のリクエストの 3 後という DMA の格子に 着地するので、有効化による DMC IRQ のクリアは、フェッチが立てるフラグより先に 来る。あちらの tests/reads.rs (バスに載せたラングを、ピンでのラング 0 に対して。 ラッチされたバイトも含む) と、コードゲートの十のワールドが押さえている。

ROM がチップに教えたこと

コンソールは、ラング 3 と高速 PPU の上で実物のプログラムを数百万サイクル走らせた 最初のものだ。以下の見落としはどれも、推論せずに位置を特定し、それからスイッチ レベルのチップで実測し、それから書き下ろし、そして修正が無ければ赤になる フィクスチャで押さえた。詳細は各リポジトリのノートにある。この一覧がここにあるのは、 コンソールがそれらを見つけたからだ。

6502 では (ラング 3、docs/notes/engine.md):

  • 桁上げが立った ROR A は、ADD/SB7 を次の命令の最初の半サイクルまで下げたままに することで、桁上げをビット 7 に入れる。継ぎ目の語がいま、それをシーケンサが クリアするビットとして運ぶ。02-implied の CRC ルーチンが見つけた。全命令が 失敗していた。
  • メモリに対する左シフトの桁上げはオペランドのビット 7 であって、書き込みの サイクルが上書きしてしまう途中の ALU の捕捉ではない。
  • ASR、ARR、ATX は、ラング 0 がオラクルにならない箇所 (バスの取り合い) では blargg のチェックサムに対して書き下ろした。ATX の定数は $FF で、$EE は名指しで 失敗する。
  • 命令の最終サイクルにある NMI のエッジは、一命令待つ (04-nmi_control #11)。
  • PLP と RTI は P を三番目の読みから取り、S でのダミーからは取らない (01-basics #4)。セレクタはオペランドを、平坦なイメージではなくコンソールのバスを 通して覗く。読みのサイクルはバスに一度だけ尋ねる。
  • 記録器のコンテキスト三つ (C がクリアでの添字のまたぎ、C が立っての同じもの、 ページをまたぐ後方分岐の成立) を、ROM が当てる鍵のために。

2c02 では (高速ラング): プリレンダーラインではスプライト評価をしない (全 $ff の OAM が、どのプログラムも走る前にオーバーフローのフラグを立てていた)、アドレスの インクリメントとコピーは描画で門を開ける、奇数フレームの短いプリレンダーライン、 そして $2001 の左のクリップ。

2a03 では: $4015 に APU が答え、DMA の読みと書きが put の phi2 で届く形で、コアの バスをコンソール越しに通すこと。

ゲート 3: 遊び

Micro Mages のデモはこの機械に無く、発行元のサイトは商用ビルドしか出していない。 このゲートは、あの ROM でも代替でも走らせていない。Console::set_pad と $4016/$4017 の経路は配管のテストが押さえている (OUT0 を通したボタン一つ、八回の 読み、駆動されないビットは開放バス)。あれは配管であって、遊びではない。

持ち越し

  • ゲート 1 の末尾の NMI の問い: 実機の NES-001 の上で、/INT と CPU の NMI ピンを M2 に対して。05、06、07、08、10 を動かす唯一の実測だ。
  • ゲート 3、ROM が手に入ったとき。
  • 2A03 コアでの RES の保持と $4015 の読み、N3 から。
  • ラング 0 は、ANC #imm と A=$ff での ASR #imm で実物と違う (6502 のノートに 記録されている)。それがスイッチモデルのバスの取り合いなのかダイデータなのかは、 あのリポジトリの問いだ。
  • 壁時計での歩調合わせとドリフトの方針 (スケッチの別の層)。

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