The capacitor nobody placed沒有人放的電容

Deep dive #2 · Open bus. Read an address on the NES that no chip answers, and you do not get a fixed value — you get the last byte that was on the pins, fading. That memory is not stored in any register; it is charge sitting on the parasitic capacitance of the motherboard's data bus, a node on neither die. This article explains the analog phenomenon, derives its decay from first principles (and finds it is linear, not the RC curve you'd expect), shows how it rots bit-by-bit, and lays out exactly how we implement it — and how we will replace the last tuned number with a computed one.

深入專文 #2 · Open bus。在 NES 上讀一個沒有晶片回應的位址,你拿到的不是固定值 —— 而是接腳上最後那個位元組,正在褪色。那份記憶不存在任何暫存器裡;它是坐在主機板資料匯流排寄生電容上的電荷,一個不在任何一顆晶粒的節點。這篇解釋這個類比現象、從第一性原理推導它的衰減(並發現它是線性的,不是你以為的 RC 曲線)、說明它怎麼一位一位地爛,並鋪陳我們到底怎麼實作 —— 以及我們將如何把最後那個手調數字換成算出來的。

S1a · deep dive #2Open bus · M5e2026-07-20

The phenomenon現象A bus that remembers會記憶的匯流排

When the 6502 executes a read, it puts an address on the bus and expects something to drive the eight data lines back. For most addresses a chip answers — RAM, ROM, a PPU register. But some addresses map to nothing. On a chip with pull-up resistors an unanswered read would return all 1s; the NES has no such resistors, so the data lines are simply left floating. And a floating wire is not empty: it is a tiny capacitor, still holding whatever voltage was last forced onto it. So the CPU reads back the last byte that any device drove — the "open bus" value.

當 6502 執行一次讀取,它把位址放上匯流排,期待某個東西把八條資料線驅動回來。大多數位址有晶片回應 —— RAM、ROM、PPU 暫存器。但有些位址對應不到任何東西。在有上拉電阻的晶片上,沒被回應的讀取會回全 1;NES 沒有那種電阻,所以資料線就這樣被留成浮接。而一條浮接的線並不是空的:它是一顆微小的電容,還保持著最後被強壓上去的電壓。所以 CPU 讀回的是最後任何裝置驅動過的位元組 —— 「open bus」值。

Two facts make this a hard case for a switch-level simulator, and they are the whole reason this shim exists:

兩個事實讓這對開關級模擬器成為困難個案,而它們正是這個 shim 存在的全部理由:

The first fact is why this shim is the shim ledger's one permanent CEILING: no on-die mechanism (charge arbitration, latch verdicts, delay) can reach a board node. It is chartered to M5e, the board-level bus-hold. This article is about the second fact — the decay — because that we can compute.

第一個事實正是為什麼這個 shim 是 shim 總帳裡唯一永久的 CEILING:沒有晶粒內機制(電荷裁決、閂鎖判決、延遲)碰得到板子節點。它被立案到 M5e,板級 bus-hold。這篇講的是第二個事實 —— 衰減 —— 因為那個我們算得出來。

The byte is held on a node that is on neither die 2A03 CPU die segdefs = these polygons 2C02 PPU die board data bus AD[7:0] (package leads · PCB traces · cart edge · ROM inputs) C ≈ 20 pF holds the last byte I_leak ≈ 80 pA → GND no chip drives it → it floats → it slowly discharges to 0
The holding node lives on the board between the dies. Our on-die geometry cannot see it — hence the CEILING — but the charge on it and the leakage off it are ordinary physics we can put numbers to.保持節點活在兩顆晶粒之間的板子上。我方晶粒內幾何看不到它 —— 所以是天花板 —— 但它上面的電荷、和從它漏走的電流,是我們放得上數字的普通物理。

The decay is linear, not RC衰減是線性,不是 RCWhy the curve is a straight ramp為什麼曲線是直斜坡

The instinct is to model a decaying capacitor as an RC discharge — the familiar e−t/RC exponential. For this node that instinct is wrong, and the reason is worth stating precisely. A floating MOS node loses its charge not through a resistor but through reverse-biased PN-junction leakage into the grounded substrate (plus a little subthreshold leakage, also to ground). And reverse-bias junction leakage is, to first order, constant current — it barely depends on the voltage across it. A constant current draining a fixed capacitance gives a constant slope:

直覺會把衰減的電容當成 RC 放電 —— 那個熟悉的 e−t/RC 指數。對這個節點,那個直覺是錯的,而原因值得講精確。一個浮接的 MOS 節點失去電荷不是透過電阻,而是透過反偏 PN junction 漏電流進接地的基板(加一點 subthreshold 漏電,也是流到地)。而反偏 junction 漏電,一階近似下是定電流 —— 幾乎不依賴它兩端的電壓。定電流放掉固定電容,給出固定斜率:

// constant-current discharge — NOT exponential
dV/dt = −Ileak / Cbus (constant slope)
V(t) = VOH − (Ileak / Cbus) · t (a straight ramp down)

The switch-level model does not carry a voltage; it carries a 0 or a 1. So all we need from this ramp is the single instant it crosses the input-low threshold and the digital bit flips 1 → 0. (It only ever goes 1→0: leakage is to ground, there is no pull-up, so a 0 never rises to a 1.) That crossing time is the whole model:

開關級模型不帶電壓;它帶 0 或 1。所以我們從這個斜坡只需要一個東西:它越過輸入低閾值、數位位元翻 1 → 0 的那一瞬間。(它永遠只 1→0:漏電到地、沒有上拉,所以 0 不會升成 1。)那個穿越時間就是整個模型:

Computing the constant算那個常數From picofarads to half-cycles從皮法到半週期

Here is the whole derivation, every step, with the numbers we can defend from first principles.

這是完整推導,每一步,附上我們能從第一性原理辯護的數字。

Step 1 — the bus capacitance第一步 —— 匯流排電容

contribution來源≈ pF
DIP-40 package lead4
PCB trace (~4 in × 1.5 pF/in)PCB 走線(~4吋 × 1.5pF/吋)6
cartridge edge connector卡帶邊緣連接器3
die pad + ROM input gate晶粒 pad + ROM 輸入閘7
Cbus total≈ 20 pF
Step 2 — the parameters
Cbus = 20 pF Ileak ≈ 80 pA (reverse-bias diode leak, 3µm NMOS @ 25 °C, summed over the pins on the bus)
VOH ≈ 4.5 V → VIL ≈ 1.5 V ⇒ ΔV ≈ 3.0 – 3.5 V

Step 3 — the decay time (constant-current, so t = C·ΔV / I)
tdecay = Cbus · ΔV / Ileak
= (20 pF · 3.5 V) / 80 pA
= (70 × 10−12) / (80 × 10−12) s = 0.875 s (with 3.0 V → 0.75 s; the band is ~0.6–0.9 s)

Step 4 — convert to half-cycles (1 hc = 23.28 ns)
threshold_hc = tdecay / thc = 0.7 s / 23.28 ns ≈ 30 million hc

So the hand-set "~600 ms" that our timestamp-decay used is not a magic number — it is C·ΔV / I with defensible board values, landing in the 0.6–0.9 s band. The empirical value we tuned to sits right inside the first-principles range. The constant is grounded; only its exact position within the band is calibration (and, as we'll see, that position is not even fixed).

所以我方時戳衰減用的手調「~600 ms」不是魔術數字 —— 它是 C·ΔV / I 帶上有依據的板級數值,落在 0.6–0.9 秒帶。我們調到的經驗值正好坐在第一性原理範圍內。這個常數是有依據的;只有它在帶內的精確位置是校準(而且我們接著會看到,那個位置甚至不是固定的)。

It rots bit by bit它一位一位地爛Eight lines, eight clocks八條線,八個時鐘

A crucial subtlety: the byte does not decay all at once. The eight data lines are eight independent capacitors, each with a slightly different trace length, via count, and — most of all — a slightly different junction-leakage current from manufacturing spread. So each line has its own decay clock, and a held 0xFF does not snap to 0x00; it rots one bit at a time — something like 0xFF → 0xEF → 0xEE → 0x40 → 0x00 over a window of tens of milliseconds. The hardware open-bus decay tests watch exactly this waterfall, and a ROM can check the order the bits drop. So a correct model needs a per-bit timestamp and threshold, not one for the whole byte.

一個關鍵細節:位元組不是一次全衰減。八條資料線是八顆獨立的電容,各自走線長度、via 數略有不同,而最重要的是製程散佈讓 junction 漏電流略有不同。所以每條線有自己的衰減時鐘,一個保持的 0xFF 不會瞬間變 0x00;它一次爛一個位元 —— 像 0xFF → 0xEF → 0xEE → 0x40 → 0x00 這樣,在幾十毫秒的窗裡。硬體 open-bus 衰減測試看的正是這道瀑布,而 ROM 可以檢查位元掉落的順序。所以正確模型需要逐位元的時戳與閾值,不是整個位元組一個。

Linear decay, and the per-bit waterfall time (ms, after last drive) line voltage V_OH 4.5 V_IL 1.5 0 250 500 750 1000 ms RC exponential (what it is NOT) 8 lines: constant-slope ramps, ±10–20% spread bit 2 drops bit 7 0xFF → 0xEF → 0x40 → 0x00
Each data line is a constant-slope ramp (straight, because leakage is constant-current), and the eight cross the threshold at slightly different times — so the held byte rots one bit at a time. The dashed curve is the RC exponential the physics is not.每條資料線是一條定斜率斜坡(直的,因為漏電是定電流),八條在略微不同的時間穿越閾值 —— 所以保持的位元組一次爛一位。虛線是物理不是的那條 RC 指數。

How we implement it我們怎麼實作Lazy timestamps, off the hot path懶惰時戳,離開熱路徑

The engine has no analog voltage and no future-event scheduler, so we do not simulate the ramp. We use lazy-evaluated timestamps: only ever compute the decay the moment a floating line is looked at. This is correct here precisely because the NES samples its data bus synchronously (on φ2 / read-enable) — the value is only observed at clock edges, so evaluating it there matches the hardware without scheduling a wake-up 30 million cycles in the future (which an event wheel would need, then have to cancel every time the bus is driven again).

引擎沒有類比電壓、也沒有未來事件排程器,所以我們不模擬那條斜坡。我們用懶惰求值的時戳:只在一條浮接線被的那一刻才算衰減。這裡這樣做是對的,正因為 NES 是同步取樣它的資料匯流排(在 φ2 / read-enable)—— 值只在時鐘邊沿被觀察,所以在那裡求值就對上硬體,不用排一個 3000 萬週期後的喚醒(event wheel 要那樣,而且每次匯流排被重新驅動都得取消它)。

It is a tiny post-pass over just the eight external data-bus nodes, run after the switch-level network has settled for the half-cycle — never inside the hot inner solver, so the golden benchmark is untouched:

它是一個小小的 post-pass,只跑八條外部資料匯流排節點,在開關級網路 settle 完該半週期之後 —— 絕不進熱內迴圈,所以金 benchmark 不受影響:

// after wire_compute() has settled this half-cycle — test-mode only
struct OpenBusBit { uint64_t last_driven_hc; uint32_t threshold_hc; uint8_t last_val; } bus[8];

for (i = 0; i < 8; i++) {
    if (node[DB0+i].is_driven) {          // had a path to VDD/GND this cycle → refresh
        bus[i].last_driven_hc = now_hc;
        bus[i].last_val       = node[DB0+i].state;
    } else if (bus[i].last_val == 1) {     // floating, was high → is it still holding?
        uint64_t age = now_hc - bus[i].last_driven_hc;
        node[DB0+i].state = (age > bus[i].threshold_hc) ? 0 : 1;   // the flip, 1→0 only
    } else {
        node[DB0+i].state = 0;             // was driven low → stays low
    }
}

The refresh rule is the load-bearing detail: a line's clock resets iff there was an active path to VDD or GND during the settled half-cycle. A write drives it → refresh. A read of mapped RAM/ROM/PPU drives it → refresh. A read of open bus does not drive it → no refresh (the pass-transistors do briefly charge-share the huge board cap against the tiny internal bus, but that drops the voltage by microvolts and never recharges it, so a discrete model ignores it). And one hazard to keep straight: these eight nodes are the board bus. The 2C02 has its own internal open-bus latch (reading $2002 returns the last value on the PPU's internal data bus) — a different capacitance, modelled separately; do not let the two leak into each other.

refresh 規則是承重的細節:一條線的時鐘重置當且僅當該 settle 半週期裡有到 VDD 或 GND 的 active path。寫入驅動它 → refresh。讀已映射的 RAM/ROM/PPU 驅動它 → refresh。讀 open bus 驅動它 → 不 refresh(pass-transistor 確實會短暫讓巨大板電容對微小內部匯流排 charge-share,但那只掉幾微伏、且從不充回,所以離散模型忽略它)。還有一個要分清楚的雷:這八個節點是匯流排。2C02 有它自己的內部 open-bus latch(讀 $2002 回傳 PPU 內部資料匯流排的最後值)—— 是不同的電容、分開模型;別讓兩者互相滲漏。

From tuned to computed — our plan從調到算 —— 我們的計畫And the floor we cannot cross以及我們跨不過的地板

The implementation above still seeds threshold_hc from a hand-set number. Replacing it with a computed one is a short, concrete plan:

上面的實作仍然用手設的數字種 threshold_hc。把它換成算出來的,是一個短而具體的計畫:

The honest floor. There is one thing we can never compute to a single universal number, and it is worth being clear about: reverse-bias leakage is exponentially temperature-dependent, Ileak ∝ T³ · exp(−Eg/kT). A real NES in a 30 °C room decays its open bus almost twice as fast as one at 20 °C. So there is no single "true" threshold — the hardware itself does not have one. The honest model computes the base constant, documents the derivation, and exposes a temperature-scaling parameter for the leakage; the ±10% per-bit spread does the rest. That is not a defeat of the model — it is the model correctly refusing to pretend a temperature-dependent analog quantity is a fixed digital constant. This is what the CEILING fate means in the ledger: an off-die, physically-real behaviour we can characterise and bound, but not reduce to one on-die number.誠實的地板。有一件事我們永遠算不出單一普世數字,而它值得講清楚:反偏漏電是溫度指數相關的,Ileak ∝ T³ · exp(−Eg/kT)。30 °C 房裡的真 NES 衰減它的 open bus 比 20 °C 的快近兩倍。所以沒有單一「真」閾值 —— 硬體自己就沒有。誠實的模型算出基準常數、把推導寫成文件、對漏電開一個溫度縮放參數;±10% 逐位元散佈做剩下的。這不是模型的失敗 —— 是模型正確地拒絕假裝一個溫度相關的類比量是固定的數位常數。這就是總帳裡 CEILING 命運的意思:一個晶粒外、物理真實、我們能刻畫並界定、但無法化約成一個晶粒內數字的行為。

Interactive — the same byte, at different room temperatures互動 —— 同一個位元組,在不同室溫下

Slide the temperature. This is the whole reason there is no single "true" decay time — the leakage current, and so the decay, moves by more than 100× across a normal range of ambient temperatures. Warm room: the byte is gone in a tenth of a second. Freezer: it lingers for minutes.拖動溫度。這就是為什麼沒有單一「真」衰減時間 —— 漏電流、進而衰減,在正常室溫範圍內就變動超過 100 倍。溫暖的房間:位元組十分之一秒就沒了。冷凍庫:它逗留好幾分鐘。

−50 °C0 °C50 °C
25 °C  →  decay ≈  600 ms
(1.0× vs a 25 °C room)

Model: t(T) = t₀ · exp( Ea/k · (1/T − 1/298 K) ), generation-limited junction leakage Ea ≈ 0.56 eV (≈ halves every 10 °C), anchored at t₀ = 600 ms at 25 °C. Illustrative — the numbers move; the point is that they must.模型:t(T) = t₀ · exp( Ea/k · (1/T − 1/298 K) ),generation-limited junction 漏電 Ea ≈ 0.56 eV(≈ 每 10 °C 減半),錨定在 25 °C 時 t₀ = 600 ms。示意用 —— 數字會動;重點是它們必須動。

← back to deep dives · the M5e charter · shim ledger← 回深入專文 · M5e 立案 · shim 總帳