AprVisual.S1 Switch-Level Test Report

transistor/switch-level NES simulation (Visual2A03 + Visual2C02 netlists) — every result below is computed by propagating individual transistor state changes, not by a behavioral emulator.電晶體/開關級 NES 模擬(Visual2A03 + Visual2C02 netlist)—— 以下每一筆結果都是逐一傳播電晶體狀態變化算出來的,不是行為層模擬器。
0
Passed通過
0
Failed失敗
0
Timeout逾時
0
Pending待跑
0
Total總數
Campaign: started 2026-07-09 21:26, finished 2026-07-10 03:38 — total 6 h 12 m wall clock. Throughput: weighted mean per-test speed 111.1 khc/s over 147 timed runs (latest contiguous run of 147 results; 14.5 G half-cycles in 36.1 core-hours). Campaign aggregate 647 khc/s over the 6.2 h span; steady-state ≈ 654 khc/s at 7 lanes (per-lane sustained rate × peak lanes — excludes the 10 s/worker startup stagger, inter-test gaps and tail drain; lane utilization 99%, mean 6.9 lanes active). Estimates from result timestamps.本輪回歸:2026-07-09 21:26 開跑,2026-07-10 03:38 完成,總歷時 6 小時 12 分吞吐量:單測加權平均 111.1 khc/s(147 筆計時,取 147 筆中最新連續段;共 14.5G 半週期 / 36.1 核時)。戰役實測聚合 647 khc/s(跨度 6.2 小時);穩態吞吐 ≈ 654 khc/s(單 lane 持續速率 × 峰值 7 lanes — 已排除每 worker 20 秒錯開起跑、測試間空檔與尾段收工;lane 使用率 99%,平均 6.9 lanes 活躍)。皆由結果檔時間戳估算。
Scope: 141 NROM/CNROM NTSC test ROMs. Detection runs once per simulated frame (a single test takes minutes-to-an-hour of wall time at switch level); the class badge on each card says how the verdict is detected:範圍:141 個 NROM/CNROM NTSC 測試 ROM。判定每模擬幀執行一次 (開關級下單一測試需數分鐘到一小時的牆鐘時間);每張卡片上的類別徽章標示其判定方式:
A blargg $6000 protocol — the ROM writes its result to $6000 (0 = pass, else fail code); the engine reads one byte per frame and stops the moment the result appears.blargg $6000 協定 —— ROM 把結果寫到 $6000(0=通過,其他=失敗碼);引擎每幀讀一個 byte,結果一出現立即停止。 A-r same $6000 protocol, plus the ROM requests soft resets: status $6000=$81 asks the runner to press Reset — the engine waits 6 frames, pulses the console's res line for 192 half-cycles (WireCore.SoftReset), and the test continues across the reset (up to 10 times). Used by the apu_reset / cpu_reset suites, which verify post-reset hardware state.同樣的 $6000 協定,但 ROM 會主動要求軟重設:狀態 $6000=$81 表示「請按 Reset」—— 引擎等 6 幀後把主機 res 線拉 192 個半週期(WireCore.SoftReset),測試跨越重設繼續(最多 10 次)。apu_reset / cpu_reset 套件用它驗證重設後的硬體狀態。 B screen text — no $6000; the engine decodes nametable 0 every frame (blargg CHR maps tile = ASCII) and stops at a terminal Passed/Failed/$0X marker (2-frame confirm; no 90-frame stability wait).畫面文字 —— 無 $6000;引擎每幀解碼 nametable 0(blargg 的 CHR 對映 tile=ASCII),遇到終端 Passed/Failed/$0X 標記即停(連續 2 幀確認;不等 90 幀穩定)。 C on-screen CRC — the ROM prints a CRC32; it is compared against the per-console accept set (dmc_dma visual tests).畫面 CRC —— ROM 印出 CRC32,與依機種而異的合法集合比對(dmc_dma 視覺測試)。
apu_mixer's $6000 pass only certifies sequence completion (its real verdict is auditory) — treat those 4 as smoke tests.apu_mixer 的 $6000 通過只代表「音頻序列播完沒當機」(真正的判定是聽覺的)—— 那 4 個請視為 smoke test。
📚 Knowledge base — the living master document of every investigation: FAIL taxonomy, the engine's semantic limits vs real silicon, the full fix table (root cause → fix), instruments and method: Test-Fix Knowledge Base (EN).知識庫 —— 所有調查的總綱 living document:FAIL 三分類、引擎語意極限、修復總表(根因→修法)、工具鏈與方法論: 測試修復知識庫(繁中)
🧰 Reproduce it yourself — a hands-on tutorial for the whole test-ROM toolchain (one-click .bat, manual run_tests.py options, reading the results, verifying a single test); the test ROMs are bundled in the repo: Test-ROM Toolchain Tutorial (EN).自己重現 —— 整套測試 ROM 工具鏈的上手教學(一鍵 .bat、手動 run_tests.py、讀結果、驗證單測),測試 ROM 已內建於 repo: 測試 ROM 工具鏈完整教學(繁中)
How we test & how the performance numbers are computed測試方式與效能計算方法
Harness. Every ROM runs in its own headless engine process, one test per physical core (affinity-pinned, 7 lanes), launched from a catalog of all tests with per-test budgets. The power-on CPU–PPU clock alignment is pinned to one reproducible phase (the same trade-off a real console makes at power-on — see the dossiers), so verdicts are deterministic run-to-run. Each result JSON records start/finish timestamps, simulated half-cycles and engine wall time.測試框架。每個 ROM 都在獨立的 headless 引擎行程中執行, 一測一實體核心(affinity 綁定,7 lanes),由完整測試目錄排程,各測有自己的幀數/時間預算。上電 CPU–PPU 時脈對齊固定在單一可重現相位 (與真機上電時的取捨相同 —— 見卷宗),同一輪跑幾次判定都一致。每筆結果 JSON 記錄開始/結束時間戳、模擬半週期數與引擎牆鐘時間。
Verdicts. Three detection channels, matching each test's design: the blargg $6000 protocol (signature DE B0 61; status < $80 = done, 0 = pass; $81 = the ROM requests a soft reset, honored automatically), screen CRC32 against the author-documented accept sets (alignment-dependent tests list every real-hardware pattern), and on-screen pass-marker text. Tests that need controller input use a scripted behavioral joypad (button:frame schedules).判定。依各測試的設計走三種偵測通道:blargg $6000 協定 (簽章 DE B0 61;狀態 < $80 = 結束、0 = 通過;$81 = ROM 要求軟重置,自動照辦)、畫面 CRC32 對照作者記載的合法集合 (對齊相關的測試會列出每一種真機圖樣)、以及畫面通過字樣掃描。需要手把的測試用腳本化行為層手把(按鍵:幀數排程)。
Clean campaigns. A full regression starts from an empty results directory, and the aggregator only uses the newest contiguous run (results separated by >30 min of idle are treated as a different campaign) — numbers on this page never mix runs.乾淨戰役。全量回歸從清空的 結果目錄開始,統計也只取最新的連續一段(間隔超過 30 分鐘視為另一輪)—— 本頁數字不會混到不同輪的紀錄。
Performance metrics. One half-cycle (hc) is half a period of the NES 21.477 MHz master clock — the engine's atomic settle step; a real console advances ≈42.95 M hc/s. Per-test speed = half-cycles ÷ engine wall seconds (khc/s). Weighted mean = total hc ÷ total core-seconds (per-core efficiency). Campaign aggregate = total hc ÷ wall-clock span from first start to last finish (includes lane idle: staggered starts, inter-test gaps, tail drain). Steady-state = per-busy-second rate × peak concurrent lanes, from an interval sweep over every test's start/finish events — what the machine sustains while all lanes are busy, with idle excluded. All three are estimates derived from the recorded timestamps. 效能計算。一個半週期(hc)= NES 21.477 MHz 主時脈的半個週期,是引擎的最小穩定步;真機速度 ≈42.95M hc/s。 單測速度 = 半週期數 ÷ 引擎牆鐘秒數(khc/s)。加權平均 = 總 hc ÷ 總核心秒數(單核效率)。 戰役聚合 = 總 hc ÷ 從第一測開跑到最後一測完成的牆鐘跨度(含 lane 閒置:錯開起跑、測試間空檔、尾段收工)。 穩態吞吐 = 忙碌秒速率 × 峰值並行 lane 數,由所有測試的開始/結束事件做區間掃描求得 —— 即「所有 lane 都在忙」時 機器實際維持的速度,已排除閒置。三者皆由結果檔時間戳推估。
Integrity. The engine's default (benchmark) path is never touched by test instrumentation — its state checksum is bit-identical with and without the test harness. Every deviation handling is a documented test-mode shim (see the knowledge base and the in-depth Q&A linked above).誠信。 引擎預設(基準測試)路徑不受任何測試儀器影響 —— 掛不掛測試框架,狀態 checksum 逐位元相同。所有偏差處理都是文件化的測試模式 shim (見上方知識庫與深入 Q&A)。
Hardware model — what is netlist, what is behavioral硬體模型 —— 哪些是 netlist、哪些是行為層
Part部件Device器件Simulation level模擬層級
CPURicoh RP2A03G (NTSC) Transistor netlist — Quietust's Visual2A03 die tracing: the whole die (6502 core with BCD disabled, APU, OAM-DMA, controller I/O)電晶體 netlist —— Quietust 的 Visual2A03 晶粒描繪:整顆晶片(BCD 停用的 6502 核心、APU、OAM-DMA、手把 I/O)
PPURicoh RP2C02G (NTSC) Transistor netlist — Quietust's Visual2C02 die tracing, including palette RAM and OAM as physical storage cells (not hoisted to handlers)電晶體 netlist —— Quietust 的 Visual2C02 晶粒描繪,palette RAM 與 OAM 保持為物理儲存 cell(未抽成 handler)
Board glueNES-001: 74LS373 (PPU AD-bus latch), 74LS139 (address decoder), 2×74LS368, 74HC04, CIC, controller ports Gate-level transistor modules (hand-authored netlist defs, MetalNES lineage)閘級電晶體模組(手寫 netlist 定義,MetalNES 血緣)
Memories2 KB CPU RAM (u1), 2 KB CIRAM (u4), cart PRG/CHR ROM, 8 KB cart WRAM (test ROMs) Behavioral byte arrays behind physical tri-state pass-gates (chip-select wiring is netlist, so open-bus hold emerges physically; missing: charge decay, access time)行為層位元組陣列,接在物理三態 pass-gate 後面(晶片選取接線是 netlist,open-bus 保持是物理湧現;缺:電荷衰減、存取時間)
Controllers手把2× NES pad (CD4021 shift register) Behavioral 4021 protocol handler driving the pad's serial line through the board's real LS368 path (test mode; scripted input via --input). The gate-level 4021 cannot express a released button under quiescent-settle semantics — its latch pass-gates backdrive the button nodes and in-group GND beats any external drive.行為層 4021 協定 handler,經電路板真實的 LS368 路徑驅動手把序列線(測試模式;--input 腳本輸入)。閘級 4021 在穩態解析語意下無法表達「放開的按鍵」—— 閂鎖 pass-gate 反向驅動按鍵節點,群組內 GND 勝過任何外部驅動。
MapperNROM / CNROM NROM = pure wiring; CNROM = behavioral CHR bank latch on the PRG busNROM = 純接線;CNROM = PRG 匯流排上的行為層 CHR bank latch
Clock21.477 MHz master Behavioral half-cycle toggle; the ÷12 CPU / ÷4 PPU dividers are inside the dies行為層半週期翻轉;÷12 CPU / ÷4 PPU 除頻器在晶粒內
Video outframebuffer Measurement tap: palette-RAM cells read at each pclk1 edge (does not affect the sim)量測 tap:每個 pclk1 邊沿讀 palette-RAM cell(不影響模擬)
Composed system: 14,723 nodes / 26,775 transistors (after connection-merge lowering; raw 15,164 / 27,305). The RP2A03G + RP2C02G pair is the same revision AccuracyCoin targets.組成後的系統:14,723 節點 / 26,775 電晶體(connection-merge lowering 後;原始 15,164 / 27,305)。RP2A03G + RP2C02G 正是 AccuracyCoin 鎖定的版次。
Lineage: the engine is AprVisual.S1's C# re-implementation of MetalNES's wire / group-resolution core (itself descended from visual6502.org's chipsim); the chip netlists are Quietust's Visual2A03 / Visual2C02 die tracings and the board/system module definitions follow MetalNES's system-def format — see the lineage page for full credits.血緣:引擎是 AprVisual.S1 對 MetalNES wire/群組解析核心的 C# 重寫(其前身為 visual6502.org 的 chipsim);晶片 netlist 為 Quietust 的 Visual2A03 / Visual2C02 晶粒描繪,主機板/系統模組定義沿用 MetalNES 的 system-def 格式 —— 完整致謝見血緣頁
Why do test ROMs still fail when we run the real chip's netlist?為什麼拿「真晶片的 netlist」來跑,測試 ROM 還是會錯?
A fair question — if the transistors are the real chip's transistors, shouldn't every test pass? No, and the reasons are instructive:很合理的疑問 —— 電晶體都是真晶片的電晶體了,不是每個測試都該過嗎?並不是,而且原因本身就很有教育價值:
1. The two dies are not the whole console.1. 兩顆晶粒不等於一整台主機。
The netlists cover exactly two silicon dies — the CPU (RP2A03) and the PPU (RP2C02). A working NES also contains work RAM, video RAM, the cartridge (ROM chips plus mapper circuitry), the clock crystal, controllers, and all the board wiring between them. Everything outside the two dies has to be re-created as a behavioral layer: ordinary program code that must answer the bus exactly the way the real part would. That is where errors have room to live — usually not in what value is returned, but in which fraction of a cycle it is returned, whether the bus floats afterwards, and how long a stale value lingers. Several bugs we fixed during this campaign lived precisely on that seam (power-up palette state, PPU open-bus decay), and the investigations still open are probing the same seam.netlist 只涵蓋兩顆矽晶粒 —— CPU(RP2A03)與 PPU(RP2C02)。一台能動的 NES 還有工作 RAM、顯示 RAM、卡帶(ROM 晶片加 mapper 電路)、石英振盪器、手把,以及把這些全部接起來的電路板走線。晶粒以外的一切都得用行為層重建:用普通程式碼扮演那顆零件,對匯流排做出跟實品一模一樣的回應。錯誤的空間就在這裡 —— 通常不是「回傳什麼值」錯了,而是「在一個 cycle 的哪個瞬間回傳」、「回完之後匯流排有沒有浮接」、「殘值會殘留多久」這種細節。這次戰役修掉的幾個 bug 正好都住在這條接縫上(上電 palette 狀態、PPU open-bus 衰減),還在追查的問題也都在探同一條縫。
2. "The NES" is not one machine.2. 「NES」不是一台機器,是一個家族。
Nintendo shipped multiple CPU revisions (RP2A03E / G / H …), multiple PPU revisions, and multiple board designs (front-loader NES-001, top-loader NES-101, the Famicom, licensed clones) — and the revisions differ in measurable, test-visible ways. A test ROM is calibrated against the author's own console; a different console can honestly produce a different result. That is why this project pins a single reference machine — NES-001 with RP2A03G + RP2C02G, the exact revisions the Visual2A03/2C02 dies were photographed from — and implements the behavioral layer to that machine's personality. Matching some other console's quirk would be wrong for ours.任天堂出過多個 CPU 版次(RP2A03E / G / H …)、多個 PPU 版次、多種電路板(前插式 NES-001、上插式 NES-101、Famicom、授權相容機)—— 而且版次之間的差異是量得到、測試看得到的。測試 ROM 是拿作者自己那台主機校準的;換一台主機,誠實地跑出不同結果是完全可能的。所以本專案釘死一台參考機 —— NES-001 配 RP2A03G + RP2C02G,正是 Visual2A03/2C02 晶粒照片的來源版次 —— 行為層就照這台的個性實作。去遷就別台主機的怪癖,對我們這台反而是錯的。
3. Part of the chip is analog, and a switch-level model is digital.3. 晶片有一部分是類比電路,而開關級模型是數位的。
The netlist abstraction treats each transistor as an on/off switch and each node as 0/1 with a few strength classes. The real chip is analog NMOS silicon: OAM is dynamic RAM whose charge leaks away, floating buses hold their last value for a temperature-dependent fraction of a second, power-on drops every latch into a random analog equilibrium, and the APU's sound output is literally an analog mixing network. Tests that measure these phenomena (oam_read's decay patterns, ppu_open_bus's ~600 ms decay time) are measuring physics, not logic — a digital model can only approximate them with explicitly documented shims, or honestly fail.netlist 抽象把每顆電晶體當成通/斷的開關、每個節點當成 0/1 加上幾級強度。真晶片卻是類比的 NMOS 矽:OAM 是會漏電的動態記憶體、浮接的匯流排會把上一個值保持零點幾秒(時間還隨溫度變)、上電瞬間每個閂鎖掉進隨機的類比平衡點、APU 的聲音輸出根本就是一張類比混音網路。量測這些現象的測試(oam_read 的衰減圖樣、ppu_open_bus 約 600 ms 的衰減時間)量的是物理,不是邏輯 —— 數位模型只能用明文記載的 shim 去近似,或者誠實地失敗。
4. The netlist itself is a hand-made transcription of die photographs.4. netlist 本身是人工從晶粒照片描繪出來的。
Visual2A03/2C02 were produced by tracing polygons off die micrographs by hand (Quietust's work — heroic and remarkably accurate, as our own zero-diff verification against the upstream data confirms). But it is a lumped model: no parasitic capacitance network, no analog transistor sizing, no continuous propagation delays. Behavior that hinges on a race between two signals inside a single clock phase can legitimately resolve differently in the model than in silicon — the model is faithful to the connectivity, not to every picosecond of the electrodynamics.Visual2A03/2C02 是人工對著晶粒顯微照片一筆一筆描出多邊形做成的(Quietust 的工作 —— 壯舉級的準確,我們對上游資料做過雙向零差異驗證)。但它是一個集總模型:沒有寄生電容網路、沒有類比的電晶體尺寸、沒有連續的傳播延遲。凡是取決於「同一個時脈相位內兩條信號誰先到」的行為,模型和矽晶就有可能合法地解出不同答案 —— 模型忠實的是連接關係,不是電磁動力學的每一皮秒。
5. Real power-up state is undefined — a simulator must pick one.5. 真機的上電狀態是未定義的 —— 模擬器卻必須挑一個。
Real silicon wakes into random latch states: blargg's own readmes document the same console giving different oam_read patterns on different power-ons, and the CPU÷12 / PPU÷4 clock alignment lottery (four possible fine alignments, some tests mutually exclusive across them — see the dossier below). A deterministic simulator has to choose one reproducible power-up; whichever it chooses, some test somewhere is calibrated to a different roll of the dice.真矽晶醒來時每個閂鎖都是隨機的:blargg 自己的 readme 就記載同一台主機不同次開機會給出不同的 oam_read 圖樣,還有 CPU÷12 / PPU÷4 時脈對齊的抽籤(四種細對齊,某些測試在不同對齊間互斥 —— 見下方卷宗)。一個確定性的模擬器必須挑一種可重現的上電狀態;不管挑哪種,總有某個測試是照別的骰子點數校準的。
So a FAIL on this page means one of three things: a behavioral-layer integration bug (we find it and fix it — the score's climb is exactly that process), a machine-profile difference (documented against our pinned NES-001 / G-revision target), or physics beyond a digital model, or a gap the behavioral layer must fill (both documented with evidence in the two sections below). The netlist gives us something no behavioral emulator has: when a test fails, we can put a probe on the actual named transistor nodes and watch the failure happen cycle by cycle.所以本頁的一個 FAIL,只會是三種情況之一:行為層整合 bug(找到就修 —— 分數一路爬升就是這個過程)、機型差異(對照我們釘死的 NES-001 / G 版次目標據實記載),或是數位模型構不到的物理、又或是行為層必須補上的缺口(兩者都在下方兩節據實記載並附證據)。而 netlist 給了我們行為層模擬器都沒有的東西:測試失敗時,可以把探針直接搭上有名字的電晶體節點,逐 cycle 看著失敗發生。
Where the CPU+PPU netlist alone isn't enough — the behavioral layer supplies the missing spec (these PASS)光靠 CPU+PPU 兩顆晶粒的 netlist 還算不出的正確行為 —— 行為層補上缺漏的規格(這些測試已通過)
The netlist computes only the two dies. A few correct behaviors live outside what pure switch-level propagation of the CPU+PPU can express — analog physics (charge decay, power-on latch settling), a same-half-cycle race a two-state model must resolve the wrong way, or a ~1-dot delay in the two-die board-level integration. For each, the real hardware has a well-defined answer, so we supply that spec through a documented, test-mode behavioral shim and the test PASSES. These are not faithful deviations — the failure was a gap in our model, and the shim fills it with the correct behavior. Every shim is test-mode only: the engine's default (benchmark) path is bit-identical with and without it, and each is backed by the test author's own words, NESdev references, and how reference emulators (Mesen2 / TriCNES) model the same thing.netlist 只算兩顆晶粒。有少數正確行為,單靠 CPU+PPU 的開關級傳播根本表達不出來 —— 類比物理(電荷衰減、上電閂鎖沉降)、二值模型只能解錯邊的同半週期賽跑、或雙晶片板級整合裡約 1 dot 的延遲。這些真機都有明確答案,所以我們用文件化的測試模式行為層 shim 把那份規格補上,測試就通過。這些不是忠實偏差 —— 失敗是我們模型的缺口,shim 用正確行為把它補齊。每個 shim 都只在測試模式作用:引擎預設(benchmark)路徑掛不掛都逐位元相同;每一條都有測試作者原文、NESdev 文獻、以及參考級模擬器(Mesen2 / TriCNES)對同一件事的建模佐證。
🔬 How these shims are built without breaking other tests — a teaching write-up on the probe effect (why a zero-fire graph edit flipped an unrelated test) and Gemini's instrument-grade force/release principle: Don't Touch the DUT — probe effect & instrument-grade shims (EN).這些 shim 怎麼掛才不會弄壞別的測試 —— 一篇教學:探針效應(一個「零開火」的圖變更為何弄壞無關測試)與 Gemini 的儀器級 force/release 通則: 別動被測物 —— 探針效應與儀器級 shim(繁中)
1. CPU÷12 / PPU÷4 alignment — 10-even_odd_timing (fixed via a narrow write-delay shim)1. CPU÷12 / PPU÷4 上電對齊 —— 10-even_odd_timing(已用窄窗寫延遲 shim 修復)
A real console powers up in one of several CPU-PPU fine alignments (NESdev forum: "CPU-PPU clock alignment", with blargg's own analysis in-thread; the odd-frame dot skip is documented at NESdev: PPU frame timing). Reference emulators: Mesen2 ships RandomizeCpuPpuAlignment (Core/NES/NesCpu.cpp) and logs the drawn alignment per power-on — the variability is standard knowledge.真實主機上電時會落入數種 CPU-PPU 細對齊之一 (NESdev 論壇:「CPU-PPU clock alignment」,串內有 blargg 本人的分析;奇數幀 dot-skip 行為見 NESdev: PPU frame timing)。 參考級模擬器:Mesen2 內建 RandomizeCpuPpuAlignment(Core/NES/NesCpu.cpp)並在每次上電記錄抽到的對齊 —— 這種變異性是社群標準知識。
We deterministically enumerated all four alignments (engine flag --reset-hold-extra; the probe also revealed the CPU divider free-runs from power-on while the PPU divider restarts at /res release) and measured:我們確定性地列舉了全部四種對齊(引擎旗標 --reset-hold-extra;探針同時發現 CPU 除頻器自上電起自由運轉、PPU 除頻器在 /res 釋放時重啟),實測如下:
Alignment (K)對齊(K) NMI-edge family (8 tests)NMI 邊沿家族(8 個)10-even_odd_timing
1 (K=0)FAILPASS
7 (K=1) ← chosen7(K=1)← 定案PASSFAIL
5 (K=3)PASSFAIL
3 (K=5)FAILPASS
A perfect complementary 2+2 split — zero intersection on this model. Community knowledge points the other way for real hardware: blargg developed and validated the suite on a real console, and a “golden alignment” passing all ten in a single power-on is understood to exist. We therefore read the zero intersection not as a property of the real machine but as a known limitation of our two-netlist board-level integration: an unarbitrated absolute-phase offset of roughly one dot (candidate sources: the idealized zero-delay $2002 read path between the two dies, the reset-release timing that starts the PPU divider, unmodeled clock-pad buffer delays). We fix alignment 7 (the phase blargg's NMI-edge tests were calibrated on), and the remaining ~1-dot offset is then supplied by a documented test-mode behavioral shim — a narrow clamp on the $2001 render-enable transition around the pre-render skip dot — so 10-even_odd_timing now PASSES (output 08 08 09 07) on the same alignment as the whole NMI-edge family. (A PPUSim cross-check of the $2002 read's absolute master-clock latency remains a planned follow-up to arbitrate the offset at its source rather than compensate for it.)完美的 2+2 互補分裂 —— 在本模型上零交集。但社群知識指向另一邊:blargg 是在真機上開發並驗證整套測試的,公認存在能單次上電十項全過的「golden alignment」。因此我們把零交集解讀為我們雙 netlist 板級整合的已知限制,而非真機的性質:一個約 1 dot、尚未仲裁的絕對相位偏移(候選來源:兩顆晶片間被理想化為零延遲的 $2002 讀取路徑、啟動 PPU 除頻器的 reset 釋放時序、未建模的時鐘 pad 緩衝延遲)。我們固定在對齊 7(blargg NMI 邊沿測試校準的相位),剩下那 ~1 dot 偏移則由一個文件化的測試模式行為層 shim 補上 —— 在 pre-render skip dot 附近對 $2001 渲染開啟的轉態做窄窗夾制 —— 因此 10-even_odd_timing 現在通過(輸出 08 08 09 07),與整個 NMI 邊沿家族在同一對齊下同時通過。(用 PPUSim 交叉比對 $2002 讀取絕對 master-clock 延遲、從源頭仲裁這個偏移而非補償它,仍列為後續計畫。)
Completeness check: the four alignments above are the entire reachable space, not a sample. Intermediate reset-release offsets (K=2, K=4) were also run and quantize onto the same classes (K=2 ≡ alignment 7, K=4 ≡ alignment 5 — identical verdicts, per-test fail codes and frame counts; the divider pair restarts on whole clk0 periods, so only these four relative phases physically exist). For contrast, TriCNES — a reference emulator that passes all ten — reaches that result by hand-tuning its power-on offset until the suite passed; its own source comments the choice with "Shouldn't this be 0? I don't know why, but this passes all the tests if this is 7, so...?" (Emulator.cs, power-on init). Read together with the golden-alignment consensus, that hand-tuned success is consistent with the pass intervals genuinely overlapping on real silicon — which is exactly why we localize our zero-intersection to an integration offset on our side rather than to the tests.完備性檢查:上表四種對齊是全部可達空間,不是抽樣。 中間的 reset 釋放偏移(K=2、K=4)也實測過,量化塌縮回同樣的類別 (K=2 ≡ 對齊 7、K=4 ≡ 對齊 5 —— 判定、失敗碼、幀數完全一致; 除頻器對以整個 clk0 週期重啟,所以物理上就只存在這四種相對相位)。 對照:全過這十項的參考模擬器 TriCNES,是把上電偏移當參數手調到整套通過為止 —— 其原始碼對這個選擇的註解是 「Shouldn't this be 0? I don't know why, but this passes all the tests if this is 7, so...?」 (Emulator.cs 上電初始化)。把這個手調成功與 golden-alignment 共識放在一起看,恰好說明真矽晶上兩組通過區間確實有交集 —— 這也正是我們把零交集定位在自己這側的整合偏移、而不是測試本身的原因。
2. PPU open-bus decay — ppu_open_bus (fixed via documented shim)2. PPU open-bus 衰減 —— ppu_open_bus(已用文件化 shim 修復)
Author's readme作者的 readme (ppu_open_bus/readme.txt):
"If a bit isn't refreshed with a 1 for about 600 milliseconds, it will decay to 0 (some decay sooner, depending on the NES and temperature)."
Temperature-dependent charge leakage is an analog phenomenon the switch-level model cannot express (floating nodes hold indefinitely). Fixed (test mode only): a behavioral timer zeroes the PPU's io-bus latch nodes (_io_db) after ~600 ms without a value change — the same modelling used by the AccuracyCoin author's own emulator TriCNES (per-bit decay timers, constant measured on his console).依溫度而異的電荷洩漏是開關級模型無法表達的類比現象(浮空節點永久保持)。已修復(僅測試模式):行為計時器在值 ~600 ms 未變後把 PPU 的 io 匯流排閂鎖節點(_io_db)歸零 —— 與 AccuracyCoin 作者自己的模擬器 TriCNES 相同的建模方式(per-bit 衰減計時器,常數為其主機實測值)。
3. Power-up state — power_up_palette, registers (fixed via documented shim)3. 上電狀態 —— power_up_paletteregisters(已用文件化 shim 修復)
Author's own source comment作者原始碼開頭的註解 (blargg_ppu_tests_2005.09.15b/source/power_up_palette.asm, line 1):
"Reports whether initial values in palette at power-up match those that my NES has. These values are probably unique to my NES."
NESdev: PPU power-up state lists palette contents as "unspecified" at power on. Test mode injects the consensus table (and clears the Z flag to the real power-on P=$34) into the netlist cells via a drive→settle→release sequence; the benchmark path is untouched.NESdev: PPU power-up state 將 palette 上電內容列為「未定義」。測試模式以驅動→settle→釋放的程序把共識表(並把 Z flag 清為真實上電的 P=$34)注入 netlist cell;benchmark 路徑不受影響。
4. The DMC IRQ latch race — an analog behavior two independent silicon models both lose (fixed via documented shim)4. DMC IRQ 閂鎖賽跑 —— 兩個獨立矽晶模型都輸掉的類比行為(已用文件化 shim 修復)
blargg's 7-dmc_basics test 19 (apu_test/source/7-dmc_basics.s: "There should be a one-byte buffer that's filled immediately if empty") enables a 1-byte DMC sample and immediately reads $4015, expecting $80 (IRQ flag set) — it passes on real hardware. Half-cycle probing of the netlist located the failure in a single pass-transistor latch (Visual2A03 t14402): the latch's input falls in the same half-cycle its clock (apu_clk1) closes. Real NMOS silicon resolves this race "data wins" — the gate keeps conducting while the clock decays through threshold — but a discrete two-state simulation must evaluate the settled state, where the gate is simply off, so the IRQ flag lands one APU cycle late. Decisively, emu-russia's APUSim — an independent schematic-level model reverse-engineered from the same die — fails the same test the same way (we ran it: it reads $10 where hardware reads $80), while behavioral emulators pass by construction. This is a limit of the digital abstraction, not a transcription or engine bug (our netlist has a verified zero diff against the upstream Visual2A03 data). Test mode arms a documented micro-shim that applies the latch's intended edge semantics — capture the input value at the clock's falling edge — inert except in the race case. One shim flipped four tests to PASS (7-dmc_basics, sprdma_and_dmc_dma ×2, dma_2007_read) with zero regressions. References: NESdev: DMA (cycle-level DMC DMA structure), Breaking NES wiki (2A03 DPCM circuit).blargg 的 7-dmc_basics 測試 19(apu_test/source/7-dmc_basics.s:「There should be a one-byte buffer that's filled immediately if empty」)啟用 1-byte DMC 取樣後立即讀 $4015,期望 $80(IRQ 旗標已設)—— 真機通過。對 netlist 做半週期探測後,失敗點鎖定在單一一個 pass-transistor 閂鎖(Visual2A03 t14402):閂鎖的輸入 在其時脈(apu_clk1)關門的同一個半週期下降。真 NMOS 矽晶把這場賽跑解成「資料贏」—— 時脈電壓衰減穿越門檻期間 pass gate 仍導通 —— 但離散二值模擬只能取穩定後的狀態,門就是關的,IRQ 旗標因此晚一個 APU cycle。決定性的是: emu-russia 的 APUSim —— 從同一晶粒獨立逆向的電路級模型 —— 以同樣方式輸掉同一個測試(我們實跑:真機讀 $80 之處它讀 $10), 而行為層模擬器則建構上直接通過。這是數位抽象的極限,不是轉錄或引擎 bug(我們的 netlist 對上游 Visual2A03 資料 有雙向零差異驗證)。測試模式武裝一個文件化 micro-shim,實作閂鎖本意的邊沿語意 —— 在時脈下降沿捕捉輸入值 —— 賽跑情況以外完全不作用。一個 shim 翻正四個測試(7-dmc_basicssprdma_and_dmc_dma ×2、 dma_2007_read),零回歸。參考: NESdev: DMA(cycle 級 DMC DMA 結構)、 Breaking NES wiki(2A03 DPCM 電路)。
Faithful deviations — where failing IS the faithful result (evidence dossier)忠實偏差 —— 失敗本身就是忠實的結果(證據卷宗)
These are the opposite case: the switch-level model reproduces a real-silicon behavior that the test itself documents as hardware-dependent, so a FAIL (or a power-on lottery) IS the faithful result — forcing a PASS would move the simulation away from our pinned NES-001 / RP2C02G target. Each claim is backed three ways: (a) the test author's own words, quoted with the file path in the nes-test-roms collection; (b) NESdev references; (c) how reference-grade emulators model the same variability.這是相反的情況:開關級模型重現了測試自己都記載為「依硬體而異」的真實矽晶行為,所以 FAIL(或上電抽籤)本身就是忠實的結果 —— 硬要它通過,反而讓模擬偏離我們釘死的 NES-001 / RP2C02G 目標。每條主張都有三重佐證:(a) 測試作者原文逐字引用,附 nes-test-roms 合集內檔案路徑;(b) NESdev 文獻;(c) 參考級模擬器對同一變異性的建模方式。
📖 In-depth Q&A: Faithful Deviations — In-Depth Q&A (EN).深入解說 Q&A: 忠實偏差深入解說 Q&A(繁中)
1. OAM is dynamic RAM — oam_read, cpu_dummy_writes_oam1. OAM 是動態記憶體(DRAM)—— oam_readcpu_dummy_writes_oam
Our 2C02 keeps OAM as physical DRAM cells in the netlist (not a plain array), so oam_read's verdict is a power-on-pattern lottery, exactly as on hardware.我們的 2C02 把 OAM 保持為 netlist 中的物理 DRAM cell(不是普通陣列),所以 oam_read 的判定是一場上電圖樣抽籤 —— 和真機完全一樣。
Author's own readme作者自己的 readme (oam_read/readme.txt):
"On my NTSC front-loader NES, I get the following four general patterns at random after power/reset"
— and of blargg's four documented real-hardware patterns, three end in "Failed" (CRCs 694ADBE0, E9E8E60F, 44551956); only one passes. Earlier engine states produced a failing member of that family (*-patterned dump, CRC E03E03AD); since the 2026-07 shim set the deterministic power-on lands on the passing pattern — still the same documented lottery, and future engine changes may legitimately shift it again. The entry stays because the variability itself is the faithful behavior.—— blargg 記錄的四種真實硬體圖樣中,三種以「Failed」收場(CRC 694ADBE0、E9E8E60F、44551956),只有一種通過。較早的引擎狀態落在失敗家族的一員(帶 * 圖樣的 dump,CRC E03E03AD);自 2026-07 的 shim 組合後,確定性上電落在通過的圖樣 —— 仍是同一場已記載的抽籤,未來引擎變動也可能再合法地換邊。本條目保留,因為「會變」本身就是忠實行為。
The other test declares its own limitation on screen另一個測試在畫面上自述其限制 (cpu_dummy_writes_oam):
"Requirement: OAM memory reads MUST be reliable. This is often the case on emulators, but NOT on the real NES."
Community: NESdev: PPU OAM ("OAM uses dynamic memory (which will slowly decay if the PPU is not rendering)"); NESdev: PPU power-up state ("The contents of OAM are unspecified both at power on and at reset due to DRAM decay"). Reference emulators: Mesen2 ships an opt-in EnablePpuOamRowCorruption setting (Core/Shared/SettingTypes.h) precisely because real OAM misbehaves.社群文獻:NESdev: PPU OAM(「OAM 使用動態記憶體(PPU 未渲染時會慢慢衰減)」);NESdev: PPU power-up state(「OAM 內容在上電與重設時均為未定義,因 DRAM 衰減」)。參考級模擬器:Mesen2 內建可選的 EnablePpuOamRowCorruption 設定(Core/Shared/SettingTypes.h)—— 正因為真實 OAM 會出錯。