The Visual6502 base, in one paragraph先一段講 Visual6502 的基底
Visual6502's chipsim.js is a beautifully compact switch-level core (~200 lines): recalcNode → getNodeGroup (recursive addNodeToGroup over conducting channels) → getNodeValue → write states and toggle transistors. It models one 6502 as JavaScript objects (each node has .c1c2s, .gates, .pullup, .pulldown, .state; each transistor carries an .on boolean). It's a research/visualization tool — clarity first, speed second. MetalNES keeps the exact same algorithm shape but re-engineers almost every mechanism underneath for a full NES board in compiled C++.
Visual6502 的 chipsim.js 是一個極精簡的開關級核心(約 200 行):recalcNode → getNodeGroup(遞迴 addNodeToGroup 走導通通道)→ getNodeValue → 寫入狀態並切換電晶體。它把一顆 6502 模成 JavaScript 物件(每個節點有 .c1c2s、.gates、.pullup、.pulldown、.state;每顆電晶體帶一個 .on 布林)。它是研究/視覺化工具 —— 清晰優先、速度其次。MetalNES 保留完全相同的演算法骨架,但把底下幾乎每個機制都為「整台 NES + 編譯式 C++」重新工程化。
A. Engine & acceleration changesA. 引擎與加速上的改動
| MetalNES changeMetalNES 的改動 | Visual6502 chipsim.js | MetalNES |
|---|---|---|
| Stateless transistors無狀態電晶體 | Each transistor stores t.on, explicitly toggled by turnTransistorOn/Off whenever a gate node changes.每顆電晶體存 t.on,gate 節點變動時由 turnTransistorOn/Off 顯式切換。 |
No per-transistor state at all — conduction is read inline as _node_states[gate] during the walk (L1969). The whole transistor-state array + turn-on/off bookkeeping is gone.完全沒有電晶體狀態 —— 走訪時直接讀 _node_states[gate](L1969)判斷導通。整個電晶體狀態陣列 + 開關記帳都拿掉了。 |
| Bucketed channels通道分桶 | One c1c2s list per node; GND/VCC are walked as group members and detected later via arrayContains(group, ngnd/npwr).每節點一個 c1c2s 列表;GND/VCC 當成 group 成員走訪,之後用 arrayContains(group, ngnd/npwr) 偵測。 |
Channels are pre-split into _tlist_c1c2s / _tlist_c1gnd / _tlist_c1pwr; supply channels just set a flag with early-break (L1975–1999) and are never walked as members.通道預先拆成 _tlist_c1c2s / _tlist_c1gnd / _tlist_c1pwr;supply 通道只設旗標 + early-break(L1975–1999),不當成員走訪。 |
| Flags + 256-LUT旗標 + 256 查表 | getNodeValue loops the group checking pullup → true, pulldown → false, state → true, else false. No flag set.getNodeValue 迴圈逐節點查 pullup → true、pulldown → false、state → true,否則 false。沒有旗標集合。 |
OR-accumulates each node's flags during the walk, then a precomputed 256-entry _flags_to_state LUT resolves it (L2024) — plus extra flag types: forcecompute, set_high, set_low.走訪時 OR 累加每節點旗標,再用預算的 256 項 _flags_to_state 查表解析(L2024)—— 還多了 forcecompute、set_high、set_low 旗標。 |
| Capacitance tie-break電容 tie-break | A floating group returns the first node it finds with state==true, else false — order-dependent, no capacitance concept.floating group 回傳第一個 state==true 的節點,否則 false —— 順序相關、無電容概念。 |
Tracks _max_connections / _max_state: a floating group holds the state of its largest-capacitance member (L1944–1947, L2019) — a parasitic-capacitance model.追蹤 _max_connections / _max_state:floating group 保留最大電容成員的狀態(L1944–1947、L2019)—— 寄生電容模型。 |
| Flat SoA layout扁平 SoA 佈局 | Nodes and transistors are JS objects with array fields; heavy pointer/property indirection.節點與電晶體是帶陣列欄位的 JS 物件;大量指標/屬性間接。 | Flat arrays: _transistor_list (null-terminated int sub-lists), _node_infos struct array, _node_states bytes — cache-friendly, compiled-friendly.扁平陣列:_transistor_list(null 結尾的 int 子列表)、_node_infos struct 陣列、_node_states bytes —— 對 cache 與編譯友善。 |
| Persistent settle queue常駐收斂佇列 | recalcNodeList does recalclist = new Array(); recalcHash = new Array(); every settle iteration — constant re-allocation.recalcNodeList 每一輪收斂都 recalclist = new Array(); recalcHash = new Array(); —— 不斷重新配置。 |
Swaps two persistent buffers (_recalc_list ↔ _recalc_list_next) and reuses the hash (L1552–1553) — zero per-iteration allocation.交換兩個常駐 buffer(_recalc_list ↔ _recalc_list_next)並重用 hash(L1552–1553)—— 每輪零配置。 |
| Compiled C++編譯式 C++ | Interpreted JavaScript in a browser.瀏覽器裡的直譯 JavaScript。 | Native C++ — the raw language speedup is large, but it's a substrate change, not an algorithm change.原生 C++ —— 純語言加速很大,但那是底層換掉,不是演算法改動。 |
B. The big capability extension — a whole NES, not one chipB. 最大的能力延伸 —— 整台 NES,不是一顆晶片
Visual6502's chipsim.js simulates one 6502 die, loaded from a single segdefs/transdefs/nodenames set. MetalNES adds a module / composition system: it wires the Visual 2A03 and Visual 2C02 dies plus the board's TTL glue (74LS373 / 74LS139 / 74LS368 / 74HC04), the controllers, and the cartridge into a single flat netlist (a "connection" becomes a permanently-on transistor). On top of that it adds behavioral handlers the pure switch-level core has no concept of — RAM/ROM arrays (simulated as byte arrays hung off control-pin nodes), the master clock, and composite video / audio output. That turns a single-chip viewer into a full NES-001 board simulator. (AprVisual S1 keeps this module system and the behavioral handlers, and adds the batch-settle optimization to them.)
Visual6502 的 chipsim.js 模擬一顆 6502 晶粒,由單一組 segdefs/transdefs/nodenames 載入。MetalNES 加了模組 / 組裝系統:把 Visual 2A03 與 Visual 2C02 晶粒,加上主機板 TTL glue(74LS373 / 74LS139 / 74LS368 / 74HC04)、手把、卡帶,接成一張扁平 netlist(「connection」變成一顆永遠導通的電晶體)。在這之上再加純開關級核心沒有的行為 handler —— RAM/ROM 陣列(掛在控制腳節點上、用 byte 陣列模擬)、主時鐘、composite 影像 / 音訊輸出。這把單晶片檢視器變成完整的 NES-001 主機板模擬器。(AprVisual S1 保留了這套模組系統與行為 handler,並對它們加了 batch-settle 最佳化。)
What both keep the same兩者保留相同的部分
The core algorithm is unchanged from Visual6502: settle by re-evaluating connected node groups until quiescent, with a ~100-pass loop limiter; resolve a group by priority (GND > VCC > pull). MetalNES didn't reinvent the simulation — it kept Visual6502's elegant idea and made it fast and whole-system. AprVisual S1 then took MetalNES's version and pushed the wire core further still.
核心演算法跟 Visual6502 一樣:反覆重算連通節點 group 到穩定,帶約 100 次的迴圈上限;以優先序解析 group(GND > VCC > pull)。MetalNES 沒有重新發明模擬 —— 它保留了 Visual6502 優雅的點子,把它變快、變成整機。AprVisual S1 再拿 MetalNES 的版本,把 wire 核心又往前推一步。