專案 GitHub:antirez/c64-hw-simulator
重點
- 模擬的是電路板:C64(ASSY 250407)和 ZX Spectrum 48K(Issue 3B)的主機板依 KiCad 復刻板的電路連線表轉成資料,每支接腳、每條銅箔都在模擬裡。C64 那片有 216 個零件、249 條 net。
- 故障症狀不寫死:拔掉晶片、抬起接腳、短路到地,畫面變成什麼樣子都由電路和晶片模型算出來,程式裡沒有任何針對特定症狀的特例。
- 電氣模型很簡化,但有形狀:五級驅動強度決定一條 net 的電位;沒人驅動的 net 會保留電荷、慢慢漂移,靠近門檻時讀值會亂跳。
- 代價是慢:專案自述在 Apple M3 Max 上只有實機 31% 的速度,Spectrum 更慢。
- 很新:2026 年 10 月 15 日才建立,GitHub 上只有一個 commit。
以 2026 年 10 月 20 日從 GitHub API 查到的資料,專案在 10 月 15 日 15
(台北時間)建立,有 392 顆星、16 個 fork、2 個開啟中的 issue,授權是 zlib 風格。作者是 Redis 的作者 Salvatore Sanfilippo(antirez)。Hacker News 上的發表串拿到 237 分。內容根據 README、PLAN.md,以及 src/sim.h、src/sim.c、src/c64.c、src/parts.c、tests/faults.sh 等原始碼。
它解決什麼問題
修過老電腦的人都知道,一台 C64 開機黑畫面,可能是 CPU、PLA、KERNAL ROM、某顆 DRAM 或彩色 RAM 出問題,每種故障的症狀都不一樣。一般模擬器(例如 VICE)在晶片暫存器與記憶體映射的層級工作,沒有「這支腳斷了」的概念,想模擬故障就得另外寫死每種症狀。
c64-hw-simulator 把抽象層降到主機板本身。README 舉的例子是抬起 C64 彩色 RAM 的第 17 腳(A7)再開機:機器看起來完全正常,因為 CPU 寫入和 VIC-II 讀取用的是同一個錯的位址,RAM 只是發生位址重疊。把腳放回去,從沒寫過的那一半就變成亂色;再抬起來,畫面又乾淨了。真正的 2114 也是如此。
怎麼運作
flowchart LR
kicad["bwack 的 KiCad 復刻板"] --> conv["tools/kicad2c.py"]
conv --> data["c64_board_data.c / zx48_board_data.c"]
data --> sim["sim.c:net 引擎"]
chips["floooh chips:6510、VIC-II、CIA、SID、Z80"] --> socket["只看得到自己接腳的插座"]
socket --> sim
parts["parts.c:膠合邏輯、ROM、DRAM、PLA"] --> sim
sim --> ui["畫面、閃燈碼、UI 標示"]
電路連線表是資料
tools/kicad2c.py 把 bwack 的 C64 KiCad 復刻專案轉成 C 表格。零件、接腳和 net 都是資料,程式只負責模擬電氣行為。
驅動強度與 contention
每支接腳以某個強度驅動它所在的 net。sim.h 定義了五級:
| 強度 | 代表 |
|---|---|
| SUPPLY | 電源軌,以及短路到 GND/+5V |
| STRONG | 一般晶片輸出 |
| RES | 經過串聯電阻的輸出(Spectrum 的 CPU 與 ULA 匯流排之間那排 470Ω) |
| PULL | 上拉電阻、較弱的 NMOS 輸出(CIA 的埠) |
| Z | 沒人驅動,net 浮接 |
combine() 從最強的一級往下找,有人驅動就由那一級決定電位。同一級裡高低互打就是 contention,結果取低電位,因為 NMOS/TTL 輸出往下拉比往上推更用力;這條 net 也會被標記,讓 UI 顯示出來。
每條 net 不存驅動者清單,只存「每種強度與電位各有幾個驅動」的計數陣列(count[10]),一支接腳改變驅動時只要一減一加。
浮接的 net 會漂移
沒人驅動的 net 保留最後的電荷,接著按指數漂移,常數都寫在 sim.c:
- 有 TTL 輸入的 net:時間常數 2 個週期,漂到約 1.4V。TTL 輸入讀浮接 net 一律讀成 1。
- 只有 MOS 輸入的 net:時間常數 30 萬個週期(約 0.3 秒),漏到 0V。
- 被抬起的 MOS 接腳自成一個小節點:時間常數 5000 個週期(約 5 毫秒),漂到約 1.4V,還會吃到封裝上同一側左右鄰腳的串音(
K_CROSSTALK = 0.12)。
MOS 輸入拿「電壓加雜訊」去比自己的門檻。門檻是 1.4V 加上一個由接腳編號雜湊出來、介於 ±0.25V 的偏差,所以每支腳都不同。離門檻遠的時候讀值穩定,靠近時就會亂跳,跟實機一樣。雜訊來自帶種子的 xorshift,同樣的設定每次執行結果都能重現。
為了不讓每次讀取都算指數,compute_hold_times() 在初始化時先算好「浮接之後多久內還一定讀成原值」,這段時間內的讀取直接回傳。
事件驅動的 settle 與時脈邊緣
只有輸入 net 變了的零件會進佇列重新評估,sim_settle() 一直跑到整片板子穩定。有故障的板子可能自我振盪(輸出經由短路接回自己的輸入),所以每次 settle 最多評估 20000 次,超過就丟掉剩下的工作。閂鎖、RAM 這類有記憶的零件在板子穩定後另跑一次 commit,模擬時脈邊緣取樣。
磁珠、串聯電阻和按下的鍵盤按鍵會把幾條 net 連在一起,用 union-find(含 path halving)合併。這些連線很少變,一變就整個重建。
C64 每半個週期分成三步:STEP_ROW(/RAS、/CAS 都高,列位址上線)、STEP_RAS(/RAS 下降,DRAM 和 74LS373 閂住列位址)、STEP_CAS(/CAS 下降,送出行位址),每一步之後整片板子都要 settle。4164 DRAM 的模型也照這個時序,在 /RAS 下降時閂住 row、/CAS 下降時閂住 column。
晶片怎麼接上去
6510、VIC-II、CIA、SID 用的是 floooh 的 chips 週期精準核心,外面包一層「插座」,晶片只看得到自己的接腳。Spectrum 的 Z80 也取自 chips,改成在每個時脈邊緣步進。
一個加速技巧:主匯流排的每條線在一個 64 位元的 bitmap 裡各佔一個 bit,晶片可以用位移加遮罩一次讀整條匯流排(sim_read_group())。只要匯流排上有一條線有故障,就退回逐腳讀取,所以模擬結果不受這個捷徑影響。
Spectrum 的 ULA
ULA 以 14 MHz 振盪器的半週期為解析度建模,CPU 和 ULA 搶匯流排的時序直接從模擬裡出現,沒有查延遲表。tools/ulacheck.c 拿 emu-russia 從 ULA 晶片 die 還原的電路連線表(661 個 NOR 閘),對同樣的輸入逐個半週期比對所有輸出和像素。README 說這樣量到第一個被搶的 T-state 在 /INT 下降後第 14337 個,一般模擬器寫 14335 或 14336,差異來自起算點不同。
故障測試的做法
| 測試 | 內容 |
|---|---|
tests/faults.sh | 31 個 C64 常見故障情境(含健康板),每個開機 4 秒後存下畫面與文字 |
tests/zx-faults.sh | 13 個 Spectrum 情境 |
tests/diag.sh | 跑 Commodore Dead Test 與 586220 診斷卡帶,對照手冊上的閃燈碼和結果 |
faults.sh 每一行都寫著「預期症狀」,例如拔掉 CIA1 是「READY 畫面但游標不閃」,拔掉 VIC-II 是「完全沒有畫面(沒有同步)」。檔頭註明這一欄依修機經驗、在跑模擬之前就寫好,模擬器裡也沒有任何程式知道這些症狀。
跟常見做法比
| 一般模擬器(如 VICE) | c64-hw-simulator | |
|---|---|---|
| 模擬層級 | 晶片暫存器、記憶體映射 | 接腳、net、驅動強度 |
| 故障 | 要模擬就得特別寫 | 從電路自然產生 |
| 速度 | 輕鬆超過實機 | 專案自述低於實機 |
晶片內部仍用週期精準的行為模型,所以這是「行為級晶片加電氣級主機板」的混合。專案自述的速度如下:
| 主機 | C64 | ZX Spectrum |
|---|---|---|
| Apple M3 Max | 31% | 23% |
| AMD Ryzen AI Max+ PRO 495 | 39% | 28% |
| Apple M5 Max | 48% | 34% |
Spectrum 比較慢,是因為 ULA 以 14 MHz 半週期為解析度,整片板子每模擬一秒要 settle 2800 萬次,C64 約 600 萬次(專案自述)。
限制與成熟度
- 電氣常數是猜的:
sim.c的註解直說,門檻、漂移時間、串音這些數字都是有根據的猜測,重點在行為的形狀。 - 沒有類比部分:視訊輸出、SID 濾波器、電源、dot clock PLL 都沒有模擬;晶片內部的故障(例如壞掉的 DRAM cell)和兩條 net 之間的橋接也還沒做。
- 規劃文件比程式超前:
PLAN.md的目標包括把邏輯分析儀擷取存成 VCD,以及透過 c64-llm 的 LLM socket 協定讓代理人操作,src/裡都還找不到對應程式。PLAN.md也寫明第一版用「驅動強度加低電位贏」取代原本規劃的電導模型,速度寫的是約 35%,跟 README 的數字不同。 - 歷史很短:只有一個 commit,看起來是整包推上來。
- 本文沒有實測:以上根據文件與原始碼整理,沒有編譯或執行。