NEStinymachines

チップ

チップは一つの規約を共有し、ピンについての嘘はテストで落ちる

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 のいくつかの隅について長年言い争ってきた。私たちはそれぞれに、レジスタ書き込みの短いプログラムをスイッチレベルの PPU(2c02)上で走らせて答えた。リファレンスシミュレータは、こちらの期待を知らないまま同じプログラムを走らせ、肝心な瞬間の全ノードを記録する。スプライト 0 はライン 91、ドット 182 で当たる: スプライト自身の x に、パイプラインが足す 2 ドットを加えた位置だ。二つのスプライト検査を通して、うちのチップは 600 状態にわたってノード単位で例外なくリファレンスと一致する。プログラムが vblank フラグを取りこぼし得る有名な窓は、約 1.5 ドット幅と測れた。フラグが立つ瞬間をまたぐ三回の読み出しはビット 7 を [0, 0, 1](取りこぼし、抑止、消費)と返し、リファレンス自身が記録したデータビットもそれと一致する。スプライトメモリ(OAM)は、文書化された二つのトリガのどちらでも破損を見せなかった。

エンジンの相違が二つ、どちらもチップが見つけ、エンジンで直った

スプライト 0 をそもそも当てることが、最初の相違を暴いた。リファレンスは、配線のグループが両方の電源レールに触れるとき特別に扱う。スプライトメモリのデータ線で、うちのエンジンはその場合をゼロに潰していた。halfphi 0.1.5 はそれをリファレンスのやり方で直す: 元の値を保ち、メンバーの面積に電荷を投票させる。二つ目は、パレット書き込みを実 CPU と同じ間隔で行うまで隠れていた: うちのエンジンではバイトがアドレス下位バイトと OR されて着地し、リファレンスでは書いたとおりに着地した。原因は、何にも駆動されていない配線のグループがどう落ち着くかだった。2C02 のリファレンスはメンバーを面積で量り、visual6502 は電荷を持つメンバー一つで勝たせる。halfphi 0.1.6 はネットリストにどちらの規則かを宣言させ、面積の投票を宣言すると、PPU の二つの記録済みリファレンス走行は例外を一切持たずに再生する: 電源投入からの 601 状態と、バスのテスト用の土台を通した 4008 状態、10,906 ノードの一つ残らず。それらの走行が不明な電源投入状態として脇に置いていた 9 個、次いで 27 個のラッチは、シリコンではなく最初から電荷の規則だった。いまはテストが宣言を確かめ、古い規則でチップを組むとそのテストが赤になる。

高速 PPU はドット単位でチップと一致し、フレーム周期の内側に収まる

高速 PPU はチップの二つ目のモデルではない。その予定表は、組み立てる時にスイッチレベルのチップから測り出す表で、フレームの各ドットに一項目: チップがどのフェッチをラッチしたか、いつアドレスを進めたか、いつスクロールをコピーしたか、いつフラグが立ったか。手で書いたのはデータパスだけで、それはチップ自身のフレームと照らし合わせてある。最初のテスト場面では 62160 個の可視ドットがスイッチレベルの描画と一致する。64 スプライトの場面(反転、優先度、一行に九つ)では 61440 個すべてが一致し、スプライト 0 の当たりは (92, 183) に着地する。チップ自身のフラグはドット 185 で立った。フレーム途中に五つのレジスタ書き込みが着地するスクロール場面では 61440 個すべてが一致し、書き込みはそれぞれ自分のバスアクセスの中で効く(アクセス開始から 1, 2, 3 ドットの台地)。1 フレームを 1.083 ms で描き、フレーム周期 16.639 ms の 15.4 倍内側、最悪フレーム 1.903 ms、200 フレームで計測。

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

6502 のピンに現れた 2A03 のコア、チップ対チップ

コンソールのスケッチは新しい種類の検査、規約を介したチップ対チップの比較を求めていて、その両半分がそろった。2A03 の 6502 コアを 6502 プロジェクト自身のピンフレームでクロック位相ごとに示し、6502 の記録済みピントレースのすべて(289 本: 七つのプログラム、リファレンスのプログラム、割り込みと RDY のスクリプト走行、三本の 10 進モード連鎖、全 256 命令)を通す。6502 は記録されたテキストとしてだけ加わり、動くエンジンとしては決して加わらない。285 本が比較され(さらに 4 本は 2A03 にないピンを駆動するので、名指しで拒む)、うち 131 本は全ハーフサイクルの全フィールドで一致。残りは、名前と範囲のはっきりした四つの点でだけ異なる。スタックページ: 二つのダイの模擬電源投入スタックポインタは $40 違い、それは両コア自身のレジスタから導いた。書き込みの前半のデータバイト: そこでは誰も読まない。10 進連鎖: 6502 が補正するところを、2A03 は素の二進の和とフラグを書く(9 バイト、算術と共に列挙)。そして走行中のリセット: 2A03 はコアを止めたまま、6502 は走り続け、どちらも同じハーフサイクルでベクタを読む。この一覧が 2A03 自身の高速コアの形を決めた: 10 進補正を切り、スタックポインタをこのチップから種付けした 6502 の高速コア。その二つの切り替えは 6502 リポジトリに入り、そこの記録済み走行と照らし合わせてある。スイッチレベルの 2A03 に対して全プログラムと全スクリプトで(285 プログラム、65561 ハーフサイクル)、残る差は書き込み前半のそのバイトだけ。

表としての音源ユニット、ハーフステップごとにチップと一致する

音の側は 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 報告。

2A03 時間 22 ミリ秒にわたる五段のトレース: 二つの方形波、三角波、ノイズ、DMC の出力符号とフレーム IRQ の印
長音のテスト場面 80001 ハーフステップにわたる五つの出力符号: 手で組んだ音源ユニットの出力で、そのすべてでスイッチレベルのチップと一致した。方形波 1 はミュートまでスイープし、方形波 0 のエンベロープは最初の四分フレームで始まり、ノイズのシフトレジスタは電源投入からタイマの最初の一周を待ち、DMC は 33 バイトのサンプルを歩く。縦線はフレーム IRQ。

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

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

スイート: 16 テスト合格わざと壊す走行: 13 テストが赤halfphi: 0.1.6コミット: a7fa5f5
2c02 スイート: 18 テスト合格2c02 わざと壊す走行: 11 テストが赤2c02 コミット: c9fe9e8
音の検査: 10 テスト場面、各 80001 ハーフステップ同一ストール検査: 6603 フレーム同一、RDY 込み音源ユニット込みで: 実時間の 9.0 倍

チップ

すべてのチップが話す規約、スイッチのレベルの NES の二つのチップ(CPU と音の 2A03、絵の 2C02)、そしてそこから組んだ高速版。

  • The contract the chips share N0 report: The pin tables, the dot frame and the cartridge edge as one small crate with no dependencies, checked against the recorded reference runs.
  • The 2A03 at its switches, matching its reference exactly A0 report: The NES's CPU and sound chip simulated transistor by transistor, bit for bit against its reference with no list of exceptions.
  • First sound from the 2A03 A3 report: A program's note read off the chip's own output, and the mixer written down as a claim we have not yet measured.
  • Planning the fast 2A03 N3 plan: The 6502's fast core with its decimal adjust switched off, and the sound unit as tables measured out of the chip.
  • The fast 2A03, built and checked N3 report: The fast chip checked at its pins against the transistor-level one, the sound tables measured out of the chip, and the bus stalls frame for frame.
  • The 2C02 at its switches P0 report: The picture chip simulated transistor by transistor, its reference run replayed, and the transistors that only work when the supply is on found.
  • The 2C02's first picture P1 report: The picture chip driven through its simulated bus, its colour output matching the table, and the latches that power up unset named.
  • The 2C02's hard corners P2 report: Sprite 0, the vblank read race and OAM corruption, each settled by a small register program that the reference runs too, without knowing the answer.
  • Planning the fast 2C02 P3 plan: A picture chip that steps one dot at a time, driven by a schedule measured out of the transistor-level chip.
  • The fast 2C02, dot for dot with the chip P3 report: The fast picture chip agreeing with the transistor-level one on three test scenes, the register writes, and the picture with rendering switched off.

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