the NES console
一族の部品から組み上がる動く NES: チップはスイッチのレベルで、チップ間の規約は記録済みのリファレンストレースで証明され、信号経路はすでに実在する。
6502 の仕事はチップをスイッチのレベルで模擬し、ntsc-crt はコンソールとブラウン管の間の信号を模擬する。このプロジェクトは、その二つが隣人であることをやめて一台の機械になる場所だ: 動く NES をチップごとに組み上げ、チップ間の規約は文書の約束ではなく、記録済みのリファレンストレースで証明する。
チップは一つの規約を共有し、ピンについての嘘はテストで落ちる
nes-bus は、すべてのチップクレートが話すフレーム型とピン表を依存ゼロで持つ。これは単なるコンパイル検査ではない: PPU(2c02)の記録済みリファレンス走行はいま規約のピンフレームを通して再生され、一本のピンの極性について嘘をつく仕込みの妨害は、再生を失敗させなければならない。
五つ目のチップは、例外リストなしにリファレンスと厳密に一致する
2a03 は NES の CPU: 6502 コア、クロック分周器、音源ユニット、合わせて 10,946 個のトランジスタと 5,577 個の定義済みノードで、二つの独立したパーサが同じ数を数えた。記録済みの走行はリファレンスシミュレータに対し 601 状態をビット単位で、例外リストを一切持たずに再生する。一族で最初のチップだ。そこへ至る途中で、エンジンが初版から抱えていた問いも決着した: 2A03 は電源投入時に、レイアウトのプルと外部駆動が争うグループを 3 個作る(どのチップでも初のゼロでない数)。halfphi 0.1.6 はそれをシリコンと同じ向きに解決し、他のどのチップでも観測不能であることが証明されている。
最初の音。そして音程はプログラムそのもの
うちの小さなプログラムがメモリハーネス越しにチップ上で走り、方形波チャネルを歌わせる。同じプログラムをリファレンス自身が走らせた結果は、ハーネスを通して 2,001 状態をビット単位で再生する: コアと音源ユニットとバスの糊を、一つの比較の下で。そして音程は仮定ではなく、こちらで実測した: チャネルの出力はちょうど 144 ハーフステップの台地で振れ、ten 個を数えた。144 はプログラム自身のタイマーバイト(8)から直接来る。妨害としてテストハーネスがそのバイトを偽って供給すると、二つの検査が両方落ちる: 再生はバイトが最初にバスを渡る瞬間に、台地の数は偽のバイトが予言する数そのもので。

PPU の厄介な隅を、専用トレースで留める
エミュレータの世界で言い争われてきた問いは、スイッチレベルの PPU(2c02)上でスクリプト化したレジスタプログラムによって一つずつ答えられた。リファレンスシミュレータは同じスクリプトを何も知らずに走らせ、重要な窓の中で全ノードをダンプする。スプライト 0 はライン 91、ドット 182 で当たる。スプライト自身の x に 2 ドットのパイプラインを足した位置で、二つのスプライト窓は 600 状態を例外なしにノード単位で再生する。有名な vblank 取りこぼしの窓は約 1.5 ドット幅と測れ、フラグの立ち上がりをまたぐ三回の読み出しはビット 7 を [0, 0, 1](取りこぼし、抑止、消費)と返し、リファレンス自身がサンプルしたデータビットと照合済み。OAM はどちらの既知のトリガでも破損を見せなかった。
エンジンの相違が二つ、どちらもチップが見つけ、エンジンで直った
スプライト 0 をそもそも当てることが最初の相違を暴いた: グループが両方のレールを含むときにリファレンスが特別扱いする OAM データ線を、エンジンはゼロに潰していた。halfphi 0.1.5 はその修正を汎用のホールドとして持ち、面積で重み付けした電荷投票、つまりリファレンス自身の規則で決める。二つ目はパレット書き込みを実 CPU と同じ間隔で行うまで隠れていた: バイトはうちのエンジンではアドレス下位バイトと OR されて着地し、リファレンスでは書いたとおりに着地した。原因は駆動されていないグループの解決のしかただった。2C02 のリファレンスはメンバーの面積を量り、visual6502 は電荷を持つメンバー一つで勝たせる。halfphi 0.1.6 はネットリストにどちらかを宣言させ、投票を宣言すると PPU の二つの記録済みリファレンス走行は例外を一切持たずに再生する: 電源投入からの 601 状態と、バスハーネス越しの 4008 状態、10,906 ノードの一つ残らず。それらの走行が未定義の電源投入状態として隠していた 9 個、次いで 27 個のラッチは、シリコンではなく電荷規則だった。いまは検査が宣言を押さえ、古い規則でチップを組むとその検査が赤になる。
高速 PPU はドット単位でチップと一致し、フレーム周期の内側に収まる
高速 PPU はチップの二つ目のモデルではない。そのシーケンサはビルド時にスイッチレベルのチップから測り出した表で、フレームの各ドットに一語: チップがどのフェッチをラッチしたか、いつアドレスを進めたか、いつスクロールをコピーしたか、いつフラグが立ったか。書き下ろしたのはデータパスだけで、それはチップ自身のフレームに押さえられている: 最初のワールドで 62160 個の可視ドットがスイッチレベルの描画と一致し、64 スプライトのワールド(反転、優先度、一行に九つ、スプライト 0 の当たりは (92, 183)、チップ自身のフラグはドット 185 で立った)で 61440 個すべて、そしてフレーム途中に五つのレジスタ書き込みが着地するスクロールワールドで 61440 個すべて。書き込みはそれぞれ自分のバスアクセスの中で効く(アクセス開始から 1, 2, 3 ドットの台地)。1 フレームを 1.092 ms で描き、フレーム周期 16.639 ms の 15.2 倍内側、最悪フレーム 1.772 ms、200 フレームで計測。



6502 のピンに現れた 2A03 のコア、チップ対チップ
コンソールのスケッチが言う新種の検査、規約を介したチップ対チップの比較は、両半分がそろった。2A03 の 6502 コアを 6502 プロジェクト自身の規約クレートのピンフレームとしてクロック位相ごとに提示し、6502 の記録済みピン走行の全トレース(289 本: 七つのプログラム、リファレンスのプログラム、割り込みと RDY のスクリプト走行、三本の 10 進モード連鎖、全 256 命令)を通す。相手のチップは記録されたテキストとして入り、エンジンとしては決して入らない。285 本が比較され(4 本は 2A03 にないピンを駆動するので名指しで拒む)、うち 131 本は全ハーフサイクルの全フィールドで一致。残りは名前と境界を持つ四つのクラスの内側でだけ異なる: スタックページ(二つのダイの模擬電源投入スタックポインタは $40 違い、両コア自身のレジスタから導いた)、書き込みの phi1 半分のデータバイト(そこでは何も供給されない)、10 進連鎖(6502 が補正するところを 2A03 は二進の和と二進のフラグを書く、9 バイトを算術と共に列挙)、そして走行中のリセット(2A03 は RES の間コアを止め、6502 は走り続け、どちらも同じハーフサイクルでベクタを読む)。この一覧が 2A03 自身の高速コアの形を決めた: 10 進補正を切り離し、スタックポインタをこのチップから種付けした 6502 の高速コア。二つのつまみは 6502 リポジトリに着地し、そこの記録済み走行に押さえられている。スイッチレベルの 2A03 に対して全プログラムと全スクリプトで: 285 プログラム、65561 ハーフサイクル、残る差は write-phi1 のバイトだけ。
表としての APU、ハーフステップごとにチップに押さえる
音の側は PPU の型に従う: ビルド時にスイッチレベルのチップから測り出した表、ヘッドレスなプローブから書き下ろした周りの機構、そして全体をチップ自身の出力に押さえる。先にプローブ、計測器として残した十一の実測(両モードのフレームシーケンサ、長さ表、デューティ列、エンベロープとスイープのクロック、三角波、ノイズと DMC の周期表、スプライト DMA、コントローラのストローブ)が、公開モデルの言わないことを二つ見つけた。ノイズと DMC のタイマはカウンタではなく線形帰還シフトレジスタで、電源投入から自走し、終端に達すると記録済み十六状態の一つを再装填する: ダイの周期 ROM で、ノイズ ROM のインデックス 12 は公開表がすべて 762 と言うところで 964 サイクルに落ちる。ダイデータの転記欠陥か部品の癖か、名指しして持ち越す。ユニット内部のタイミングはすべて、検査の符号列が最初に分かれた時にプローブで測って留めた定数だ: 下位バイトの周期書き込みは次のティックを再装填にし、方形波の符号はステップから 2 ハーフステップ遅れ、DMC の出力ユニットは電源投入から八回の完了を数えてから鳴る。検査は両フレームモード下の二つのレジスタプログラム、10 ワールド各 80001 ハーフステップで、五つの出力符号とフレーム IRQ フラグが全ハーフステップでスイッチレベルのチップと同一。次にストール: ピンに現れたチップ全体、DMA ユニットがバスを取る様子を、RDY も他のフィールド同様に比べながらフレーム単位でスイッチレベルのチップと照らす。両方の書き込み整列でのスプライト DMA(各 1201 フレーム、RDY 低は 1029 と 1027)と DMC のサンプル取り込み(4201 フレーム、RDY 低は 19)。すべてを付けたチップは毎秒 32,067,032 ハーフサイクル、実時間の 9.0 倍で走る。段階ごとの記録はN3 報告。

二つのチップが一つのクロックに乗り、実 CPU を付けて標準テスト ROM が走る
まず糊。NES-001 基板の少数の部品、アドレスデコーダ、PPU のアドレスラッチ、二つの RAM、コントローラポートのバッファとその先のコントローラ、リセット連鎖を、それぞれ数行で自前の検査によりデータシートに押さえ、書き下ろしと札を付けた。そこにはネットリストを通るものがないからだ。二つは最初の書き下ろしが間違っていて、検査がそう言った。次にコンソール: 2A03 の高速コアと高速 PPU を一つのマスタハーフステップ計数器に乗せ、CPU は十二ごと、PPU は八ごとに進み、位相は二つのスイッチレベルチップ自身のクロック分周器から測った(cpu_phase 4、ppu_phase 3。分周器が電源投入で取り得る 24 通りの一つで、走行がスタンプに記す)。配管の検査はテストカートリッジを 6 フレーム走らせ、マスタ計数器がドットあたり八であること、奇数フレームが一ドット短いこと、絵が単体 PPU のものと同じこと、RAM の NMI 計数が一フレーム一回であることを押さえる。一コアで毎秒 125 から 140 フレーム、実時間の 2.1 から 2.3 倍。位相の検査は、この弧全体の要である継ぎ目を二通りに押さえる。PPU の実 NMI を BRK の周りに一サイクル刻みで 8 通りの位置に落とし、コンソールの CPU を同じエッジで駆動したスイッチレベルの 6502 とハーフサイクルごとに比べる: 960 ハーフサイクルが一致、取ったベクタも、プッシュも、タイミングも。そして $2002 読み出しの競合をコンソール自身のアクセス形でスイッチレベル PPU 上に全ハーフステップで測り、コンソールの読み出しをその表に 24 通りすべての位相で押さえる: フラグのセット周りで 145 回、クリア周りで 185 回、両窓の全ハーフステップに届き、結果はすべてチップのもの。 そして、この弧全体が目指してきた、本物のプログラムでの検査: blargg のテスト ROM をコンソール全体に通す。これらの高速チップが何百万サイクルも実プログラムを走らせた最初の機会だ。CPU タイミング検査は合格。命令検査は 16 本中 16 本が合格、公式・非公式の全命令。スプライトヒット検査は 11 本中 11 本が合格。縦長の一本は、高速 PPU の縦長スプライト規則をスイッチレベルチップで測ってドット単位で押さえてからの合格だ。vblank と NMI のタイミング検査は 10 本中 5 本が合格、残りは文書化された実機から一、二ドットのずれで、すべて一つの問いに帰する: 実機の NMI は、それぞれ自身の実測タイミングに押さえた二つのチップが許すより約二ドット遅れて CPU に届く。決めるのは実基板に当てるスコープだ。APU 検査は 8 本中 8 本が合格。うち六本は、不合格をスイッチレベルの 2A03 で測って書き下ろしてからの合格だ: $4017 書き込みの位相ジッタと即時クロック、バスに尋ねた半ステップ後にラッチされるステータス、三サイクル続けて立つ IRQ フラグ、読み出しが着地する所で数え落とされる DMC のバイト。ROM が見つけたものこそ走らせる意味だ: 高速 CPU は記録済みの全トレースを正確に再生しながら、トレースが覆っていなかった五つの見落としを抱えていた(駆動されないバス線に乗って次の命令へ渡る桁上げ、誤った捕捉から読んだシフトの桁上げ、結果がバスの取り合いでありスイッチモデルが独自に決着させる三命令、割り込み入力を標本化するハーフサイクル(位相の検査が捕まえた)、そしてバスに尋ねるより遅くラッチされるバイト)。高速 PPU にも四つ。それぞれ、スイッチレベルチップと高速チップを ROM 上で食い違うまで並走させて位置を特定し、チップで測り、書き下ろし、無ければ赤になる固定具で押さえた。記録はN5 報告。遊びの検査はカートリッジ待ち。
コンソールのフレームがテレビ模型を通り、その捕捉が採点される
絵は ntsc-crt の連鎖(v0.2.8、タグで固定)で、コンソールが足すのは二つだけ: フレームの順序と、一つのフレームから次へ持ち越す副搬送波の位相だ。奇数フレームの短い一行が位相を動かすので、コンソールはドットだけでなくパリティを渡す。各フレームは NES ソースで符号化され、三ラインコムで復号され、書き下ろしの定数で CRT の各段を通る。押さえは二つ。コンソールのフレームをこの連鎖に通したものは、同じ世界から単体の高速 PPU が出したフレームを同じ連鎖に通したものと、復号された全標本で一致する(1474560 成分が一致、OddShort フレーム、画面上 768 × 720)。そして 12 フレーム(うち 4 が短い)後の位相は、その列にグリッドの算術が与えるもの(位相 4。全フレームを偶数に強制すると違う値になり、検査が赤になる)。実機比較が欲しがるカラーバーのカートリッジは PPU の描画を切って塗るが、高速 PPU はそれを訊かれたことがなかった: スイッチレベルのチップで測ると、描画を切った絵はアドレスレジスタが指すパレット項目で、行途中の書き込みに対するタイミングは今そこに固定具として在る。 次に捕捉経路、実機が加わる比較の機械側半分: カラーバーのカートリッジをコンソールに通し、そのフレームを ntsc-crt の捕捉カード模型に通して実記録とまったく同じ手順で復元し、合成は模型自身のフロントエンドを通して両側が同じ帯域制限を持つようにし、平坦な領域すべてを同一の復号器を通したその合成に対して、縁から 5 ドット内側で採点する。この距離は選んだのではなく復号器のクロマフィルタから導いた。許容は走らせる前に書いた: 輝度 0.01 以内、色相 1 度以内、彩度 5 パーセント以内。カートリッジはリポジトリ自身のもの。blargg のバーは十六ドット幅で復号器は五ドットで落ち着くからだ: 三十二ドットのセルに十二の色相を一つの輝度行で並べ、背景色を加え、行は二秒ごとに進み、行ごとに一走行で採点する。輝度行 1: 13 領域中 13 が三つとも成立(最悪で輝度 0.0001、色相 0.3 度、彩度 0.0011、レートは +0.7 ppm)。輝度行 2: 13 領域中 13 が三つとも成立(最悪で輝度 0.0001、色相 0.3 度、彩度 0.0012、レートは +0.7 ppm)。輝度行 3: 13 領域中 13 が三つとも成立(最悪で輝度 0.0001、色相 0.3 度、彩度 0.0005、レートは +0.7 ppm)。輝度行 0: 13 領域中 13 が三つとも成立(最悪で輝度 0.0001、色相 0.3 度、彩度 0.0008、レートは +0.7 ppm)。往復は閉じる。最初の走行群は採点自身の幾何の前に計器を三度見つけた: ヒストグラム一区画分粗いレベル基準合わせ、消去期間と取り違えられた暗い絵、同期エッジと取り違えられた最も暗い色のクロマの谷。それぞれ ntsc-crt 側で直して固定し直した。同じカートリッジの実機記録がベンチ項目で、そのためのカートリッジはいま在る。記録はN6 報告。
コンソールの音が基板の音声段を通り、blargg のミキサー検査に押さえられ、彼の録音と並ぶ
2A03 の五つの出力コードは CPU ハーフサイクルごとにチップを出て、二つの DAC を通る。一族が初音以来抱えてきた nesdev の表で、書き下ろしと札が付く。その先は NES-001 の回路図にあり、直接読んだ: 各音声ピンは 100 オームで引き下げられ(表自身の「プラス 100」)、二つのピンは 20K と 12K で加算され(表の二つの定数がすでに抱える比)、結合コンデンサを経て、47K の帰還抵抗と並列の 220 pF で線形に保たれた 74HC04 インバータに入る。つまりジャックまでは、時定数 7.50 ms のハイパス、インバータの符号付きの利得 -2.350、10.34 マイクロ秒のローパス、そして厳密な有理時刻での窓付き sinc による 48 kHz への再標本化。段はその算術に押さえられる(ステップは時定数ごとに 0.3679 に減衰し、10 kHz の音は 200 Hz に対して 0.8401 で通り、値が与えるのは 0.8433)。模していないと明記するもの: インバータの有限な開ループ利得とその電源レール、そして表の絶対電圧。後者はスコープ記録が与える一つの倍率だ。 実機に対する検査は blargg のもの: 四つのミキサー ROM で、それぞれ一チャンネルを鳴らしながら DMC がその逆波形を鳴らすので、正しいミキサーは二つの参照ビープの間でほぼ無音に打ち消す。それぞれをコンソール全体に通し、各検査の最悪の 100 ms 窓はビープに対する割合で 5 パーセント未満でなければならない(DMC の一段だけで約二)。ノイズ ROM は設計上ノイズがフェードするので純音成分で押さえる。コンソール、次いで同じ ROM を実機で録った blargg の録音を同じコードで測ったもの: square は 2.4 パーセント対 6.1、triangle は 2.7 パーセント対 3.0、noise は 21.1 パーセント対 19.0、dmc は 3.1 パーセント対 5.6。三角波とノイズは実機とコンマ数パーセントで一致する。矩形波と dmc は実機でコンソールの二倍の残差を抱え、その残差は純音だ: 実物のパルス DAC と DMC DAC は、表が blargg の逆波形から離れる以上に表の曲線から離れている。これはスコープへの問いで、記録し、押さえない。代わりに wiki の線形近似で混ぜると検査は赤になり、ビープの三分の一に達する。記録はN7 報告。AUDIO_OUT の記録がベンチ項目。
窓の中のコンソール。絵は GPU に置かれて CPU の連鎖に押さえられ、二つ目の標的はブラウザ
一フレームが一コアで要する時間は何かを書く前に測られ、仕事がどこへ行くべきかを告げた: コンソールと符号化器は一コアに収まり、コム復号と五段の CRT は CPU のどこにも収まらない。その二つはいま GPU 上の八つの計算パスで、すべての定数は打ち込まれるのではなく復号器と CRT のパラメータから送られ、連続する三フレーム(最後は残光が見えるよう黒)の全画素の全成分で信号経路自身の CPU 連鎖に押さえられる。書き下ろしのパラメータでは最悪の成分差が 4.8e-7、平均が 1.2e-8。マスクと幾何を入れると 2.8e-5 と 1.9e-7。先に述べた許容は 1e-3 と 1e-5。一フレームは NVIDIA GeForce RTX 3070 で転送込み 0.94 ms。残光を飛ばすと検査が赤になる。 窓は Linux のバイナリ: 音付きのコンソールが自分のスレッドで、信号経路のドリフト方針が壁時計から決める分だけ周期ごとにフレーム単位で進み、重複と欠落を数え、時間方向には決して再標本化しない。表示は新しいフレームごとに符号化して GPU の絵を走らせ、音は音声デバイスへ、キーボードはコントローラ 1。ループは合成クロックで押さえる: ちょうど周期では 60 拍で新フレーム 59、重複 1、欠落 0。半周期では 60 拍のうち 31 が前のフレームを再提示。二倍では 20 拍で 19 欠落。この機械では仮想ディスプレイで走った。本物の画面、スピーカー、手が机の項目。 二つ目の標的はブラウザ: 音付きのコンソールを wasm-bindgen の後ろに置き、node で full_palette.nes を測った: 300 フレームを 3.56 秒、毎秒 84.3 フレーム、実時間の 1.40 倍、一フレームあたり 798.7 音声標本。その標的はいまページを持つ: コンソールはここで走る。自分のディスクのカートリッジで、ntsc ベンチと同じ信号経路を通り、ドリフトカウンタを生で表示する。記録はN8 報告。
ベンチ: 実機と模型を同じ入力履歴の下に置く
機械側のマイルストーンがすべて閉じたいま、コンソールがまだ実機について知らないことはベンチを待っており、次に組むのはそのベンチだ。コンソールのコントローラポートと純正パッドの間にブリッジを置く: ブリッジ上のシフトレジスタがコンソールにクロックされるパッドそのものになり、マイクロコントローラはポーリングの合間にその入力を書き、コンソールのラッチとクロックのパルスをハードウェアで数え、LAN 上の Raspberry Pi がスクリプトを受け取り、リセットと電源のリレーを駆動し、スコープをトリガする。スクリプトはラッチ番号ごとのバイトなので、模型と実機は同じ履歴を見る。ブリッジのラッチごとのログと模型のログをラッチ単位で突き合わせ、トリガした取り込みは絵のマイルストーンが閉じたのと同じ往復で採点する。計画はファームウェアより先に四つのマイルストーンと検査を名指しし、最初の「盗み聞き」はすでに模型を動かした: DMC フェッチがパッド読み出しに何をするか問うと、スイッチレベルの 2A03 が実機より先に答え、コアと高速チップとコンソールがそれに合わせて変わり、それぞれの変更は、無ければ赤になる妨害テストが押さえる。いまや各マイルストーンの道具はハードウェアに先んじて揃っている: スクリプトをブリッジとリレーとスコープに流すヘッドから、実機と模型が食い違う最初のポーリングを見つける二分探索まで、それぞれ代役に対して検証され、無ければ赤になる妨害テストが押さえる。下の図は配線表からスクリプトが導いたもので、表と食い違うことはできない。計画、配線、スクリプト、進行中の報告はノートブックに、リポジトリは github.com/tinymachines/nes-bench。
ここの数字は、テストを走らせ直した実測から来ている
下の数字は 2026-09-07 に、記録されたコミットで二つのチップリポジトリ自身のスイート(ネットリストと記録済みのリファレンス走行をすべて必須にして)と MUTATE=1 をもう一度走らせて得たもので、このページはその走行が書いたものだけを読む。
スケッチが求めたもの、そして実機を待つもの
計画は先に書かれた: 規約リポジトリのエンドツーエンドのスケッチに、マイルストーンごとの検査がある。そこに名のあるマイルストーンはすべてこの機械の上で組まれ、検査を通っている: 規約、スイッチレベルの二つのチップとそこから組んだ高速チップ、丸ごと組み上がった高速 2A03、糊、一つのクロックで標準スイートを走らせるコンソール、エンコーダを通って取り込み経路を戻る絵、基板自身の段を通る音、そして GPU の絵とゲームパッドとブラウザ標的を持つシェル。残るのは、スイッチレベルの模型だけでは決められないものそのものであり、各項目は報告に名指しされている: 実基板での NMI 到達タイミング、遊びの検査に入れる実カートリッジ、絵のマイルストーンが求めた終端付きの取り込み、実スピーカーの下での音の段、コンソールが電源投入時に取る位相、そして模型と実機が食い違うと分かっていてロジックアナライザが決めるダイ自身の発見。上のベンチはそれらを閉じる手段だ。信号の側はすでに実在する: ntsc のページには実機からデコードしたフレームが載り、コンポジット深掘りはその実機の映像をスコープからレベルごとに読む。
リポジトリは公開されている: github.com/tinymachines/2a03 とその兄弟たち。チップクレートは visual6502 系の画像に由来するダイデータを埋め込むため、NonCommercial と ShareAlike が付いて回る。規約クレートは MIT で、何も埋め込まない。