0 · Where the last article left off0 · 上一篇停在哪裡
The companion piece, the semantic ceiling, ended on a map: a 141-test hardware suite pushed our binary switch-level engine to its edge, and the deviations sorted into a handful of analog behaviours the model quantizes away — ratioed drive fights, timing-window overlaps, transparent-latch tracking, propagation latency, board-level parts, power-on state. During that campaign each was patched by a small, targeted shim: a test-mode override at the one measured site. Roughly twenty of them accumulated across two campaigns.
姊妹篇語意天花板停在一張地圖上:一套 141 顆的硬體測試把我們的二值開關級引擎逼到邊緣,而偏差歸類成少數幾種被模型量化掉的類比行為 —— 比例式驅動打架、時窗重疊、透明閂鎖追隨、傳播延遲、板級零件、上電狀態。戰役期間,每一種都用一顆小而精準的 shim 補上:在那個量測過的站點做測試模式覆蓋。兩場戰役下來,累積了大約二十顆。
Shims work, and — as EDA practice confirms — keeping them is the correct engineering trade for a performance-critical emulator. But a shim is a black box: it says what to override, not why the silicon does that. This article is the plan to replace the black boxes with white boxes — a small set of mechanisms, each a direct model of one piece of device physics — and, because the physics is the whole point, to teach that physics as we go. The target is not "zero shims" (some behaviours are continuous-time and have no static equivalent) but zero hand-written special cases: not one line of code that names a site or a test.
Shim 是有效的,而且 —— 如 EDA 實務所證實 —— 對一個效能吃緊的模擬器而言,保留它們是正確的工程取捨。但 shim 是黑箱:它只說要覆蓋什麼,不說矽為什麼那樣。這篇文章是把黑箱換成白箱的計畫 —— 一小組機制,每個都是某一塊元件物理的直接模型 —— 而且既然物理才是重點,我們就邊做邊把物理教清楚。目標不是「零 shim」(有些行為是連續時間的,沒有靜態等價物),而是零手寫特例:沒有一行代碼認站點、認測試。
The governing idea — mixed-signal co-simulation. Do not turn the whole chip into SPICE; at 30,000 transistors that runs millions of times slower and a single test sweep would take years. Instead keep the fast binary core running ~99% of the activity, and attach cheap analog models only to the few "islands" where the digital abstraction breaks. This is exactly how production EDA handles mixed-signal blocks (Verilog-AMS). The whole plan below is islands, not oceans.
統領全局的想法 —— 混合訊號協同模擬。不要把整顆晶片變成 SPICE;三萬顆電晶體那樣跑會慢上百萬倍,單一測試掃描要跑好幾年。改成:讓快速的二值核心繼續跑掉約 99% 的活動,只在少數「島嶼」—— 數位抽象失效的那幾處 —— 掛上便宜的類比模型。這正是業界 EDA 處理混合訊號區塊的方式(Verilog-AMS)。下面整份計畫講的都是島,不是海。
1 · Ratioed logic — when "both drivers are on" is the design1 · 比例式邏輯 —— 當「兩個驅動源都開著」就是設計本身M1
The physics. A MOSFET is a voltage-controlled resistor. Its on-resistance is set by geometry: R ∝ L/W, the channel's length over its width. NMOS logic of the 1970s had no p-type pull-ups, so a gate output was a permanent ratio: a weak, always-on depletion pull-up (long, narrow — high resistance) fighting a strong pull-down (short, wide — low resistance) whenever the input was high. The output voltage is just the resistor-divider midpoint. "Logic 0" is not "connected to ground"; it is "the pull-down wins the divider by design". Make the pull-up too strong or the pull-down too weak and the 0 floats up into forbidden territory — which is exactly what unofficial opcodes like LAE/LXA expose, where an accumulator's weak drive fights a latch's drive on one merged bus and the hardware answer ($FF) is the ratio's verdict.
物理。一顆 MOSFET 是電壓控制的電阻。它的導通電阻由幾何決定:R ∝ L/W,通道長度除以寬度。七〇年代的 NMOS 邏輯沒有 p 型上拉,所以閘的輸出永遠是一個比例:一個微弱、恆開的耗盡型上拉(細長 —— 高電阻)對抗一個強力的下拉(短寬 —— 低電阻),只要輸入為高。輸出電壓就是這組電阻分壓的中點。「邏輯 0」不是「接到地」;而是「下拉照設計贏了這場分壓」。把上拉做太強或下拉做太弱,0 就會浮上禁區 —— 這正是 LAE/LXA 這種非官方指令暴露的:累加器的弱驅動在一條合併匯流排上對抗閂鎖的驅動,而硬體答案($FF)就是比例的判決。
Our engine has no strength inside a priority class: GND simply wins, so it quantizes the ratio to $00 and needs a shim to force the real $FF. The white-box fix is the classic one — MOSSIM II strength classes (Bryant, 1984): give each transistor a discrete conductance level and resolve a group by comparing total pull-up conductance against total pull-down conductance, not by a flat priority OR.
我們的引擎在優先級類別內部沒有強度概念:GND 直接全勝,於是把比例量化成 $00,得靠 shim 硬灌真正的 $FF。白箱解法是經典的那個 —— MOSSIM II 強度類別(Bryant,1984):給每顆電晶體一個離散電導等級,群組決議改成「比較上拉總電導 vs 下拉總電導」,而不是平的優先級 OR。
The plan. We have no RC extraction data, but we have the polygon geometry (segdefs/transdefs). Compute a crude W/L proxy per transistor at load time, quantize to 2 bits of strength, and widen the group-resolution table from 256 to ~1024 entries — still small enough to live entirely in L1 cache, so the hot loop stays a single table lookup. A load-time census flags the <1% of nodes where two different drive cones can conduct at once; only those groups take the strength path, everyone else keeps the O(1) OR. Retires: the LXA-magic and ALU-fight shims — LAE/LXA resolve to the ratio's real answer with no site knowledge at all.
計畫。我們沒有 RC 萃取資料,但有多邊形幾何(segdefs/transdefs)。載入期對每顆電晶體算一個粗略的 W/L 代理、量化成 2 bit 強度,把群組決議表從 256 格加寬到約 1024 格 —— 仍小到能整個住進 L1,所以熱迴圈依舊是一次查表。載入期普查標出「兩個不同驅動錐可同時導通」的那 <1% 節點;只有那些群走強度路徑,其餘照舊 O(1) OR。退休:LXA-magic 與 ALU-fight 兩顆 shim —— LAE/LXA 直接解出比例的真答案,完全不需認站點。
2 · Charge storage — information parked on a capacitor2 · 電荷儲存 —— 資訊寄放在電容上M2
The physics. Every wire has parasitic capacitance to ground — the plates are the metal/poly area over the substrate, C = C_ox · W · L, strictly proportional to polygon area. That capacitance is not a nuisance here; it is storage. Disconnect a wire from every driver and the charge stays put, holding its last logic level for a while — this is dynamic RAM with no refresh, and the NES uses it everywhere: OAM sprite memory is cross-coupled cells with no pull-ups at all, kept alive only by being read (the read's bit-line precharge senses and restores the charge, DRAM-style), and the CPU/PPU data buses hold their last driven byte on capacitance — the famous open bus that game code can read back. But charge leaks: over milliseconds a floating node's voltage decays through sub-threshold and junction leakage until it crosses the logic threshold and reads as 0. "Open bus decays to zero after ~600 ms" and "an OAM cell corrupts if the rendering that refreshes it is switched off at the wrong edge" are the same physics seen from two sides.
物理。每條線對地都有寄生電容 —— 極板就是金屬/poly 蓋在基板上的面積,C = C_ox · W · L,嚴格正比於多邊形面積。這裡的電容不是麻煩,而是儲存。把一條線從所有驅動源斷開,電荷會留在原地,把最後的邏輯電平撐住一陣子 —— 這就是不刷新的 DRAM,而 NES 到處在用:OAM 精靈記憶體是交叉耦合的 cell、完全沒有上拉,只靠被讀取續命(讀取的位元線預充電會 sense 並回寫,DRAM 式),而 CPU/PPU 資料匯流排把最後被驅動的位元組保持在電容上 —— 就是遊戲程式讀得回來的知名 open bus。但電荷會漏:數毫秒間,浮接節點的電壓會經次臨界與接面漏電衰退,直到跨過邏輯閾值、被讀成 0。「open bus 約 600 毫秒後衰減到零」和「OAM cell 在錯誤的邊沿關掉刷新它的渲染就毀損」是同一套物理的兩面。
The plan — timestamp lazy decay. Do not evaluate V(t) = V₀·e^(−t/RC) in the hot loop; a floating-point exp per half-cycle would drop the engine below 1 kHz. Instead, at load time, solve the exponential once for the only thing we need — the moment it crosses threshold — and store it as an integer cycle count: Δt = −RC · ln(V_th/V₀). At run time: when the settle notices a storage node going floating, stamp node.disconnectedAt = now (one integer write). When that node is next read or shares charge, check now − stamp > decayCycles (one integer subtract + compare) and collapse to 0 if so. Node capacitance comes from the polygon-area tool already validated in the pipeline. Cost estimate: 0% to −2%, because it replaces the existing behavioural if-else counters rather than adding to them, with no floating point in sight. Retires: the open-bus and OAM-blank-edge shims — both become emergent behaviour of one decay model.
計畫 —— 時戳惰性衰減。不要在熱迴圈算 V(t) = V₀·e^(−t/RC);每半週期一次浮點 exp 會把引擎壓到 1 kHz 以下。改成:載入期把指數式解一次,只解我們唯一需要的東西 —— 它何時跨過閾值 —— 存成整數週期數:Δt = −RC · ln(V_th/V₀)。執行期:當 settle 發現某儲存節點進入浮接,蓋一個時戳 node.disconnectedAt = now(一次整數寫入)。那節點下次被讀取或參與電荷分享時,檢查 now − stamp > decayCycles(一次整數減法+比較),超過就塌成 0。節點電容來自管線裡已驗證的多邊形面積工具。成本估計:0% 到 −2%,因為它是替換既有的行為層 if-else 計數器,不是疊加,而且全程無浮點。退休:open-bus 與 OAM-blank-edge 兩顆 shim —— 兩者都變成同一個衰減模型的湧現行為。
3 · Propagation delay — the wire is not instant3 · 傳播延遲 —— 線不是瞬間的M3 highest risk最高風險
The physics. Charging that same parasitic capacitance through the driving transistor's on-resistance takes time — the RC time constant. A signal crossing the chip does not arrive everywhere at once; it lags by ~R·C at each stage, and in NMOS the lag is asymmetric because a strong pull-down discharges faster than a weak pull-up charges (electron mobility, transistor sizing). This is why one test needs the PPU's rendering-enable to reach a decision point about three dots late, with different rise and fall delays — and why our zero-delay settle, which propagates every change to quiescence within one half-cycle, reads the decision at the wrong dot. The most extreme case, the boing2k7 $2007 read that corrupts a background fetch, is this same skew at the whole-instruction scale: a CPU read's cross-chip effect lands one CPU cycle early because the write path was idealized to zero delay.
物理。透過驅動電晶體的導通電阻去充那同一顆寄生電容,是要時間的 —— 就是 RC 時間常數。一個訊號穿越晶片不會同時到達每一處;它在每一級都落後約 ~R·C,而在 NMOS 裡這個落後是非對稱的,因為強下拉放電比弱上拉充電快(電子遷移率、電晶體尺寸)。這就是為什麼有一顆測試需要 PPU 的渲染致能晚約三個 dot 才到某個決策點、而且上升與下降延遲不同 —— 也是為什麼我們零延遲的 settle(把每個變化在一個半週期內傳到靜止)會在錯的 dot 讀到那個決策。最極端的案例 —— boing2k7 那個污染背景取樣的 $2007 讀取 —— 是同一個偏移放大到整條指令的尺度:一次 CPU 讀取的跨晶片效應早了一個 CPU 週期到達,因為寫入路徑被理想化成零延遲。
The danger. This is the mechanism EDA history warns loudest about. IRSIM (1980s) added exactly this — linear RC delay on every net — and it costs the hot path dearly; tracking continuous timing across a whole chip is categorically slower. A settle-to-quiescence engine has no notion of "later"; adding one is the deepest architectural change on this list.
危險。這是 EDA 歷史警告得最大聲的機制。IRSIM(1980 年代)加的正是這個 —— 每條線都上線性 RC 延遲 —— 而它讓熱路徑付出慘重代價;在整顆晶片上追蹤連續時序,本質上就是慢。一個 settle-到-靜止的引擎沒有「稍後」的概念;加進去是這份清單上最深的架構改動。
The plan — bounded islands, or fall back to a data file. Never a global timing layer. Instead: a load-time pass flags only the handful of genuinely cross-chip, high-fan-out nets (the CPU→PPU control lines, the AD bus). Only those nets route their downstream transitions through a small delay queue (fire at t + delay instead of same-settle); everyone else stays instant. If even that proves too costly or too hard to auto-calibrate, the honest fallback is a generic delay primitive whose per-net constants live in a timing-calibrations.json data file — the framework is code, the numbers are data. Retires: the dot-339 and $2001-write-delay shims — and, if the unified phase hypothesis below holds, the ALERead heuristic too. (⟳ Since confirmed: ALERead fell to a hand-built prototype of exactly this mechanism — a 24-half-cycle access replay at the CPU↔PPU seam. See Breaking the ceiling.)
計畫 —— 有界的島,否則退回資料檔。絕不做全域時序層。改成:載入期只標出那少數真正跨晶片、高扇出的線(CPU→PPU 控制線、AD 匯流排)。只有那些線把下游轉態走一條小延遲佇列(在 t + delay 開火,而非同一 settle);其餘全部維持瞬間。若連這都太貴或太難自動校準,誠實的退路是一個通用延遲原語,它的 per-net 常數放在 timing-calibrations.json 資料檔 —— 框架是代碼、數字是資料。退休:dot-339 與 $2001-寫延遲兩顆 shim —— 而且若下面的統一相位假說成立,ALERead 的啟發式也一併退休。(⟳ 其後已證實:ALERead 正是倒在這個機制的手工原型之下 —— 在 CPU↔PPU 接縫上做 24 半週期的存取重播。見打破天花板。)
4 · Transparent latches — a switch that stays open too long4 · 透明閂鎖 —— 一個開太久的開關M4 most shims最大宗
The physics. The cheapest memory in NMOS is a single pass transistor plus the wire's own capacitance. While the clock (its gate) is high the transistor is on and the output transparently follows the input; when the clock falls the transistor turns off and the last value is trapped as charge. A real transparent latch tracks its input for the entire clock phase — tens of nanoseconds — so a brief glitch during that window is overwritten before it matters. Our engine settles once per half-cycle: whatever intermediate value the latch's input node happened to hold at that single snapshot is what gets captured. If a bus buffer is still turning on and the group momentarily reads $00, the latch keeps the $00. Worse, when a pass transistor feeds back — output through a read path back to its own input, as the cartridge's octal latch does during an ALE+read overlap — the loop locks on whatever value survives, a genuine analog fixed point.
物理。NMOS 裡最便宜的記憶體,是一顆 pass 電晶體加上線本身的電容。當時鐘(它的閘)為高,電晶體導通,輸出透明地跟隨輸入;時鐘一落,電晶體關斷,最後的值被當成電荷困住。真正的透明閂鎖在整段時鐘相位裡追隨輸入 —— 數十奈秒 —— 所以那段窗內的短暫毛刺會在它造成影響前就被蓋掉。我們的引擎每半週期只 settle 一次:閂鎖的輸入節點在那唯一快照剎那剛好持有什麼中間值,就捕捉什麼。如果某匯流排緩衝器還在導通、群組瞬間讀成 $00,閂鎖就留下這個 $00。更糟的是,當一顆 pass 電晶體形成回授 —— 輸出經讀取路徑繞回自己的輸入,像卡帶八位閂鎖在 ALE+讀取重疊時那樣 —— 迴圈會鎖在能存活下來的那個值上,一個貨真價實的類比不動點。
This is the single largest shim family — six of them: the DL φ2-transparency, the DMC and ALU input latches, the frame-IRQ transient, the double-$2007 merge, the OAM-DMA bus hold. They are all the same bug: an intermediate settle value captured where silicon would have tracked to the final one.
這是最大的一個 shim 家族 —— 六顆:DL 的 φ2 透明、DMC 與 ALU 輸入閂鎖、frame-IRQ 暫態、雙 $2007 合併、OAM-DMA 匯流排保持。它們全是同一個 bug:一個 settle 中間值被捕捉在矽本該追隨到終值的地方。
The plan — auto-detect and restate, not hand-patch. The pattern is purely structural and enumerable at load time: a storage node whose only drive path runs through a pass transistor whose gate is a clock phase. One scan lists every dynamic transparent latch on the chip. For each, the generic rule is: while the clock is in its transparent phase, after each settle completes, if the latch node disagrees with its actual input node, restate it to the input. This is a post-settle correction (the boundary tests confirm it is a no-op on correct cycles), and — critically — it derives its restate value from the latch's real input, curing the very bug that sank an earlier global attempt. Feedback loops resolve by a small relaxation to the fixed point (we already prototype this for the octal latch). Retires: all six, converging to one auto-derived mechanism that also covers latch sites no test has hit yet.
計畫 —— 自動偵測並重述,不手動 patch。這個 pattern 是純結構的、載入期可枚舉:一個儲存節點,其唯一驅動路徑經過一顆 pass 電晶體,而該電晶體的閘是一個時鐘相位。一次掃描就列出全晶片每一個動態透明閂鎖。對每一個,通用規則是:當時鐘處於透明相位、每次 settle 完成後,若閂鎖節點與它真正的輸入節點不一致,就重述回輸入值。這是一個 post-settle 校正(邊界測試證實它在正確週期上是 no-op),而且 —— 關鍵在此 —— 它從閂鎖的真實輸入取重述值,正好治好當年一次全域嘗試失敗的那個 bug。回授迴圈用一次小型 relaxation 解到不動點(我們已經為八位閂鎖做了雛形)。退休:全部六顆,收斂成一個自動衍生的機制,還順便覆蓋測試還沒打到的閂鎖站點。
5 · The board is an analog circuit too5 · 主機板也是一塊類比電路M5
The physics. The netlist covers two dies. The console and cartridge around them are a real circuit board full of parts the die photos never saw: the 74LS373 octal latch that de-multiplexes the PPU's shared address/data pins (it is the transparent latch of §4, but a physical TTL chip on the AD bus — and its analog feedback under a well-timed $2007 read is exactly what boing2k7 weaponizes), the CD4021 shift register inside the controller, address decoders, and passive pull/tie resistors. These parts have their own pin semantics (latch-enable, output-enable), their own propagation delays, and — for the CD4021 especially — a CMOS structure that dies instantly if you transliterate it into the netlist's NMOS-only, GND-wins vocabulary (a released button can never be written back). That is why the joypad already runs as a behavioural model rather than as transistors.
物理。網表涵蓋兩顆 die。圍繞它們的主機與卡帶,是一塊真實的電路板,滿是 die 照片從沒看過的零件:把 PPU 共用的位址/資料腳解多工的 74LS373 八位閂鎖(它就是 §4 的透明閂鎖,只是一顆掛在 AD 匯流排上的實體 TTL —— 而它在一個抓準時機的 $2007 讀取下的類比回授,正是 boing2k7 拿來當武器的東西)、手把裡的 CD4021 移位暫存器、位址解碼器、以及被動的 pull/tie 電阻。這些零件有自己的 pin 語意(latch-enable、output-enable)、自己的傳播延遲,而且 —— CD4021 尤其 —— 有一種 CMOS 結構,一旦你把它翻譯成網表那套只有 NMOS、GND-wins 的語彙就立刻死掉(放開的按鍵永遠寫不回去)。這就是為什麼手把早已用行為層模型跑,而不是用電晶體跑。
The plan — a board-level component library. Model the external parts as first-class components sitting on the inter-chip buses, not as code hacks inside a memory callback: an Ls373OctalLatch, a Cd4021ShiftRegister, decoders and ties, each with datasheet pin behaviour and updated at the half-cycle grain of §4. MetalNES's system-def board descriptions are a starting reference for the topology. Once the 74LS373 is a real component, boing2k7's octal-latch feedback is the component behaving normally — not a special case in the CHR read path. Retires: the board-octal-latch (ALERead) shim, the exec-space tie hack, and the joypad's bespoke wiring — folded into one component library. (⟳ Correction: the 74LS373 turned out to be a switch-level component already — module u2, loaded and working all along — so ALERead was retired by the M3/M6 phase fix instead, and the octal-latch shim was never adopted. M5's case now rests on the CD4021, the decoders and the ties. See Breaking the ceiling.)
計畫 —— 板級元件庫。把外部零件建成一級元件、掛在晶片間的匯流排上,而不是塞在記憶體 callback 裡的代碼 hack:一個 Ls373OctalLatch、一個 Cd4021ShiftRegister、解碼器與 tie,每個都有 datasheet 級的 pin 行為,並在 §4 的半週期粒度更新。MetalNES 的 system-def 板級描述是拓撲的起點參考。一旦 74LS373 是一個真元件,boing2k7 的八位閂鎖回授就是元件的正常行為 —— 不是 CHR 讀取路徑裡的特例。退休:board-octal-latch(ALERead)shim、exec-space tie hack、以及手把的客製接線 —— 全部收進一個元件庫。(⟳ 更正:74LS373 原來早就是開關級元件 —— 模組 u2,一直都載入且運作 —— 所以 ALERead 是被 M3/M6 相位修法退休的,八位閂鎖 shim 從未被採用。M5 的論據現在押在 CD4021、解碼器與 tie 上。見打破天花板。)
6 · Power-on and phase — where the simulation starts from6 · 上電與相位 —— 模擬從哪裡起跑M6
The physics. Two facts about the first microsecond. First, power-on state is genuinely random: when the rails come up, every capacitor holds whatever charge the noise gave it, so palette RAM, registers and flags start undefined — real consoles differ unit to unit, and blargg's tests encode the community-consensus power-up values. Second, relative clock phase is a discrete lottery: the CPU divides the master clock by 12 and free-runs from power-on; the PPU divides by 4 and restarts when reset releases. Their relative alignment can only land in one of four sub-cycle phases (because 1 CPU cycle = 3 dots and the divider quantizes to whole master-clock periods). A cycle-counted test aimed at a specific dot depends on which phase you booted into — and this is very likely the same root cause as the ALERead skew: our boot phase lands one CPU cycle off the phase the test was written for. (⟳ Since confirmed and fixed — operationally, without touching the boot lottery: re-timing the $2007 access itself by 24 half-cycles at the interface was sufficient. See Breaking the ceiling.)
物理。關於最初那一微秒的兩個事實。第一,上電狀態是真的隨機:電源軌一上來,每顆電容持有雜訊給它的任何電荷,所以 palette RAM、暫存器、旗標全都從未定義起跑 —— 真機一台一台不同,而 blargg 的測試把社群共識的上電值編碼進去。第二,相對時鐘相位是離散抽籤:CPU 把主時鐘除以 12、從上電就自由運轉;PPU 除以 4、在 reset 釋放時重啟。它們的相對對齊只可能落在四種 sub-cycle 相位之一(因為 1 CPU 週期 = 3 dot,而除頻器量化到整數個主時鐘週期)。一顆精算週期、瞄準特定 dot 的測試,取決於你開機開進哪個相位 —— 而這很可能就是 ALERead 偏移的同一個根因:我們的開機相位,比測試撰寫時所依據的相位差了一個 CPU 週期。(⟳ 其後已證實並修復 —— 而且操作上不必動開機抽籤:只要在介面上把 $2007 存取本身重新定時 24 個半週期就足夠。見打破天花板。)
The plan — make initial state and phase explicit parameters. Replace the scattered power-up injection shims with a single power-on state table (node → initial charge, applied at load time), and lift the CPU/PPU divider alignment out of implicit reset timing into an explicit phase selector (one of the four). Sweep the four, pick the one matching the pinned reference machine — which is exactly blargg's "golden alignment". The unified hypothesis: even_odd, dot-339, and ALERead may all be the same un-arbitrated cross-chip phase offset. If the phase selector plus the M3 delay on the $2002/$2007 read path arbitrate that one-cycle skew, ALERead's fetch overlap lands on the correct dot by physics — retiring the heuristic window it uses today. Cost is near zero (load-time configuration). Retires: the reset-hold-extra alignment, power_up_palette, and register-init shims.
計畫 —— 把初態與相位變成明確參數。把散落的上電注入 shim 換成單一的上電狀態表(節點 → 初始電荷,載入期套用),並把 CPU/PPU 除頻器對齊從隱含的 reset 時序,提升成一個明確的相位選擇器(四選一)。掃四種、挑符合釘死參考機的那個 —— 這正是 blargg 的「golden alignment」。統一假說:even_odd、dot-339、ALERead 可能全是同一個未仲裁的跨晶片相位偏移。若相位選擇器加上 M3 在 $2002/$2007 讀取路徑上的延遲,把那個一週期偏移仲裁掉,ALERead 的取樣重疊就會靠物理落在正確的 dot 上 —— 退休它今天用的啟發式窗。成本近乎零(載入期設定)。退休:reset-hold-extra 對齊、power_up_palette、暫存器初始化三顆 shim。
7 · Determinism — a numbering artifact, not physics7 · 決定論 —— 一個編號的假影,不是物理M7
Not physics — an artifact. This one is different: the failure is not something the chip does, it is something our engine does. When two events settle at the "same" instant, the engine processes them in queue order, which is node-numbering order, which is the order nodes happened to be parsed from the file — pure arbitrary label. Silicon arbitrates that race by continuous-time physics; we arbitrate it by array index. The tell is brutal: two structurally identical sibling circuits (the twin '368 buffers u7/u8) resolve oppositely, for no reason but their numbers. And any graph change — patching a transistor, attaching a probe — renumbers everything and re-rolls the whole lottery.
不是物理 —— 是假影。這一個不一樣:失敗不是晶片做的事,而是我們引擎做的事。當兩個事件在「同一」剎那 settle,引擎按佇列順序處理它們,而那是節點編號順序,也就是節點剛好從檔案被解析出來的順序 —— 純粹任意的標籤。矽用連續時間物理仲裁那場競速;我們用陣列索引仲裁。徵兆很殘酷:兩個結構完全相同的孿生電路(孿生 '368 緩衝器 u7/u8)解出相反結果,理由只是它們的編號。而任何圖變更 —— 補一顆管、掛一個探針 —— 都會重新編號、把整個彩票重抽一次。
The plan — canonical renumbering. Replace "load order" with a deterministic physical key computed at load time: sort nodes by (class, layered polygon area, structural hash of the neighbourhood, degree) before assigning integer ids — a Nauty-style canonical labeling. The engine's existing id-order arbitration then becomes canonical arbitration with zero hot-path change (it is a one-time load sort, foldable into the cache-locality renumber we already ship). Two payoffs: identical sub-circuits get identical local ordering, so twins share a verdict — the lottery is gone at the root; and because ids no longer depend on file order, a netlist patch or a probe perturbs only its neighbourhood instead of re-rolling everything, which makes graph edits safe operations for the first time. The honest caveat: consistent ≠ correct — canonicalization removes the arbitrariness, but whether the shared verdict matches silicon depends on how well the key correlates with the real physics (area can't see path resistance). The residue drops from "a lottery" to "a known, calibratable bias".
計畫 —— 正準重編號。把「載入順序」換成載入期算出的確定性物理鍵:在指派整數 id 之前,按 (class、分層多邊形面積、鄰域結構雜湊、degree) 排序節點 —— 一種 Nauty 式的正準標記。引擎既有的 id 順序仲裁就變成正準仲裁,熱路徑零改動(它是一次性的載入排序,可併進我們已出貨的快取局部性重編號)。兩個回報:結構相同的子電路得到相同的局部順序,孿生共享判決 —— 樂透從根消失;而且因為 id 不再依賴檔案順序,一個網表補丁或一個探針只擾動它的鄰域、不再重抽全部,這讓圖編輯第一次成為安全操作。誠實的但書:一致 ≠ 正確 —— 正準化拿掉的是任意性,但共享判決是否符合矽,取決於這把鍵與真實物理的相關度(面積看不到路徑電阻)。殘差從「一場樂透」降格為「一個已知、可校準的偏差」。
8 · The honest edge — what stays a shim, and why that's fine8 · 誠實的邊界 —— 什麼會留成 shim,以及為何無妨
Two of the analog behaviours — window-overlap durations (A2) and propagation latency (A4) — are continuous-time by nature. No static, load-time analysis can conjure a nanosecond out of a model that has no time axis; the only true cures are per-gate timing annotation or SPICE, both categorically too slow. So the honest end state is not a shim-free engine. It is an engine where the mechanism is general code and only the constants stay hand-supplied — moved out of the source and into three data files: netlist-patch.json (the missing transistors), analog-sidecar.json (capacitances and decay constants), timing-calibrations.json (the residual delays). EDA practice endorses exactly this: keep the shim architecture, because mixed-signal override is the correct trade for a performance-critical simulator; just stop letting any shim name a site.
這些類比行為裡有兩種 —— 時窗重疊長短(A2)與傳播延遲(A4) —— 本質上是連續時間的。沒有任何靜態的載入期分析,能從一個沒有時間軸的模型裡變出一個奈秒;唯一的真解是 per-gate 時序標註或 SPICE,兩者本質上都太慢。所以誠實的終態不是一顆零 shim 的引擎。而是一顆這樣的引擎:機制是通用代碼,只有常數還是手工供給 —— 從原始碼移出,搬進三個資料檔:netlist-patch.json(缺失的電晶體)、analog-sidecar.json(電容與衰減常數)、timing-calibrations.json(殘餘延遲)。EDA 實務正是背書這個:保留 shim 架構,因為對一個效能吃緊的模擬器而言,混合訊號覆蓋是正確的取捨;只是別再讓任何一顆 shim 認站點。
Why a fork, not an upgrade in place. Several of these mechanisms — strength classes, canonical renumbering, the decay layer — change the golden checksum, which the golden engine treats as sacred. On the fast engine you would have to package them into one "big bang" re-baseline to make it worthwhile. A separate fork (AprVisual.S1a, "with analog") has its own baseline, so the same work becomes a sequence of small, individually verifiable steps — and the golden speed engine stays untouched as the performance reference. Accuracy and speed stop competing for the same artifact.
為什麼是 fork,不是原地升級。這些機制有幾個 —— 強度類別、正準重編號、衰減層 —— 會改動金 checksum,而黃金引擎視它為神聖。在快引擎上,你得把它們打包成一次「大爆炸」重定基準才划算。一個獨立的 fork(AprVisual.S1a,「with analog」)有自己的 baseline,所以同樣的工作變成一連串小的、可個別驗證的步驟 —— 而黃金速度引擎原封不動,留作效能基準。準確與速度,不再爭奪同一個產物。
9 · The vision, in one line9 · 一句話的願景
Seven mechanisms — five modelling analog device physics (drive strength, charge, RC delay, transparent latches, board parts), one modelling the physical start state and phase, one removing a pure numbering artifact — replace roughly twenty hand-written behavioural patches accumulated across two hardware-test campaigns. None of them is exotic; every one is a textbook piece of device physics or EDA method, chosen because a specific test proved we needed exactly that physics and no more. The measure of success is not a score; it is a curve: for each mechanism, how much analog reality it buys against how much of the 99%-fast core it disturbs. That number turns the project's central finding — "~1% of the chip is genuinely analog" — from a statistic into an itemized bill of materials.
七個機制 —— 五個模擬類比元件物理(驅動強度、電荷、RC 延遲、透明閂鎖、板級零件),一個模擬物理的起始狀態與相位,一個移除純粹的編號假影 —— 取代兩場硬體測試戰役累積的大約二十顆手寫行為層補丁。它們沒有一個是奇門遁甲;每一個都是教科書等級的元件物理或 EDA 方法,之所以被選中,是因為某一顆測試證明了我們正好需要那塊物理、不多不少。成功的量尺不是一個分數;是一條曲線:每個機制,買到多少類比真實 vs 擾動了多少那個 99% 的快核。那個數字,把專案的核心發現 —— 「晶片約 1% 是真正的類比」 —— 從一個統計數字,變成一張逐項列明的物料清單。
And if some mechanism proves infeasible — if RC islands cannot stay bounded, if the phase hypothesis does not unify — that is a result too, in the same spirit as this project's other findings: a falsifiable boundary, mapped with data, left as a signpost for whoever tries next.
而如果某個機制被證明不可行 —— 如果 RC 島無法維持有界、如果相位假說無法統一 —— 那也是一個結果,和本專案其他發現同一個精神:一條可證偽的邊界,用資料畫出來,留成路標給下一個嘗試的人。
Design notes: MD/S1a/00 (the mechanism plan, M1–M7) and four 2026-07-14 consult write-ups under MD/suggest/. This is a roadmap, not a shipped feature — the fast S1 engine remains the golden artifact.設計筆記:MD/S1a/00(機制計畫,M1–M7)與 MD/suggest/ 下四份 2026-07-14 諮詢整理。這是一份路線圖,不是已出貨的功能 —— 快速的 S1 引擎仍是黃金產物。