NEStinymachines

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)から直接来る。妨害としてテストハーネスがそのバイトを偽って供給すると、二つの検査が両方落ちる: 再生はバイトが最初にバスを渡る瞬間に、台地の数は偽のバイトが予言する数そのもので。

揃えた二本のトレース: 方形波チャネルの 4 ビット出力コードが 0 と 15 の間を規則的な台地で振れ、同じ走行がミキサーを通って AD1 ピンのレベルになる。
実測したトレース: 走行中のチップから CPU ハーフステップごとに読んだ sq0_out と、同じ走行を転記済みミキサーに通した AD1 ピンのレベル(上端は 0.1494)。ミキサー定数は nesdev wiki のもので、まだ自分たちのベンチには載せていない。

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.034 ms で描き、フレーム周期 16.639 ms の 16.1 倍内側、最悪フレーム 1.206 ms、200 フレームで計測。

PPU の 1 走査線のタイミング図: ネームテーブル、属性、パターンのフェッチと、アドレス増分、コピー、スプライト評価の行に、スイッチレベルのチップがそれを発火させる各ドットの目盛り。
チップ内部のスケジュールを走査線一本ぶん、スイッチそのものから読み取った。各行は高速 PPU の材料になる制御信号で、目盛りは実チップがそれを発火させたドット: 青がフェッチ、赤がアドレスとスプライトのイベント。図解を写したものではなく、実測だ。
スイッチレベルの PPU が描いたスプライトワールドを一族の NTSC 経路に通したもの: XOR 模様の背景の上に 64 個のスプライト、模擬ブラウン管上。スイッチレベルの PPU が描いたスクロールワールド: スクロールした XOR 模様の背景に、フレーム途中のレジスタ書き込みがスクロールを変えた切れ目が見える。
高速 PPU が再現しなければならないテスト画像が二つ。スイッチレベルのチップが描き、テレビが映すのと同じように、コンポジットにエンコードして模擬ブラウン管にデコードして示した。わざとノイズのように見せている: どのタイルも自分の位置から計算されるので、一つの間違ったドットにも隠れる場所がない。スクロールワールドを横切る二本の切れ目もわざとで、フレーム途中に書き込まれたスクロールの変更だ。高速 PPU はどちらの絵もドット単位で正しく描く。

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 のラング 3。二つのつまみは 6502 リポジトリに着地し、そこのゴールデンに押さえられている。スイッチレベルの 2A03 に対して全プログラムと全スクリプトで: 285 プログラム、65561 ハーフサイクル、残る差は write-phi1 のバイトだけ。

表としての APU、ハーフステップごとにチップに押さえる

音の側は PPU の型に従う: ビルド時にスイッチレベルのチップから測り出した表、ヘッドレスなプローブから書き下ろした周りの機構、そして全体をチップ自身の出力に押さえる。先にプローブ、計測器として残した十一の実測(両モードのフレームシーケンサ、長さ表、デューティ列、エンベロープとスイープのクロック、三角波、ノイズと DMC の周期表、スプライト DMA、コントローラのストローブ)が、公開モデルの言わないことを二つ見つけた。ノイズと DMC のタイマはカウンタではなく線形帰還シフトレジスタで、電源投入から自走し、終端に達すると記録済み十六状態の一つを再装填する: ダイの周期 ROM で、ノイズ ROM のインデックス 12 は公開表がすべて 762 と言うところで 964 サイクルに落ちる。ダイデータの転記欠陥か部品の癖か、名指しして持ち越す。ユニット内部のタイミングはすべて、検査の符号列が最初に分かれた時にプローブで測って留めた定数だ: 下位バイトの周期書き込みは次のティックを再装填にし、方形波の符号はステップから 2 ハーフステップ遅れ、DMC の出力ユニットは電源投入から八回の完了を数えてから鳴る。検査は両フレームモード下の二つのレジスタプログラム、4 ワールド各 80001 ハーフステップで、五つの出力符号とフレーム IRQ フラグが全ハーフステップでスイッチレベルのチップと同一。次にストール: ピンに現れたチップ全体、DMA ユニットがバスを取る様子を、RDY も他のフィールド同様に比べながらフレーム単位でスイッチレベルのチップと照らす。両方の書き込み整列でのスプライト DMA(各 1201 フレーム、RDY 低は 10291027)と DMC のサンプル取り込み(4201 フレーム、RDY 低は 19)。すべてを付けたチップは毎秒 32,067,032 ハーフサイクル、実時間の 9.0 倍で走る。段階ごとの記録はN3 報告

2A03 時間 22 ミリ秒にわたる五段のトレース: 二つの方形波、三角波、ノイズ、DMC の出力符号とフレーム IRQ の印
検査の長音ワールド 80001 ハーフステップにわたる五つの出力符号: 書き下ろした APU の列で、検査はそのすべてでスイッチレベルのチップと同一だと押さえた。方形波 1 はミュートまでスイープし、方形波 0 のエンベロープは最初の四分フレームで始まり、ノイズの LFSR はタイマの電源投入周回を待ち、DMC は 33 バイトのサンプルを歩く。縦線はフレーム IRQ。

二つのチップが一つのクロックに乗り、実 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 本中 10 本が合格、残る一本は名指しで拒む(縦長スプライトは模していない)。vblank と NMI のタイミング検査は 10 本中 5 本が合格、残りは文書化された実機から一、二ドットのずれで、すべて一つの問いに帰する: 実機の NMI は、それぞれ自身の実測タイミングに押さえた二つのチップが許すより約二ドット遅れて CPU に届く。決めるのは実基板に当てるスコープだ。APU 検査は 8 本中 2 本が合格、残りはすべて書き込み後のフレームシーケンサの位置で、名指しで 2A03 へ持ち越す。ROM が見つけたものこそ走らせる意味だ: CPU の高速ラングは記録済みの全トレースを正確に再生しながら、トレースが覆っていなかった四つの見落としを抱えていた(駆動されないバス線に乗って次の命令へ渡る桁上げ、誤った捕捉から読んだシフトの桁上げ、結果がバスの取り合いでありスイッチモデルが独自に決着させる三命令、そして割り込み入力を標本化するハーフサイクル。これは位相の検査が捕まえた)。高速 PPU にも四つ。それぞれ、スイッチレベルチップと高速ラングを ROM 上で食い違うまで並走させて位置を特定し、チップで測り、書き下ろし、無ければ赤になる固定具で押さえた。記録はN5 報告。遊びの検査はカートリッジ待ち。

ここの数字は、テストを走らせ直した実測から来ている

下の数字は 2026-09-05 に、記録されたコミットで二つのチップリポジトリ自身のスイート(ネットリストと記録済みのリファレンス走行をすべて必須にして)と MUTATE=1 をもう一度走らせて得たもので、このページはその走行が書いたものだけを読む。

スイート: 10 テスト緑MUTATE=1: 8 テスト赤halfphi: 0.1.6コミット: 0e6fc4e
2c02 スイート: 14 テスト緑2c02 MUTATE=1: 9 テスト赤2c02 コミット: 474b7e7
APU 検査: 4 ワールド、各 80001 ハーフステップ同一ストール検査: 6603 フレーム同一、RDY 込みAPU 込みで: 実時間の 9.0
nes スイート: 22 テスト緑命令検査: 16 本中 16 本合格コンソール: 実時間の 2.1 から 2.3nes コミット: f402c8a

ここから起動するコンソールまでのマイルストーン

計画は書かれ、合意済みだ: 規約リポジトリのエンドツーエンドのスケッチに、マイルストーンごとの検査がある。PPU の隅と高速ラング、ピン検査の両半分、2A03 の梯子(コア、APU、DMA ユニット)、糊、そしてコンソールの位相検査は済み、コンソールは標準スイートを走らせている。そこで開いているもの: 実基板での NMI 到達タイミング、APU の書き込みタイミング、そしてカートリッジ待ちの遊びの検査。それからエンコーダを通る絵と、音。信号の側はすでに実在する: ntsc のページには実機からデコードしたフレームが載り、コンポジット深掘りはその実機の映像をスコープからレベルごとに読む。

リポジトリは公開されている: github.com/tinymachines/2a03 とその兄弟たち。チップクレートは visual6502 系の画像に由来するダイデータを埋め込むため、NonCommercial と ShareAlike が付いて回る。規約クレートは MIT で、何も埋め込まない。