專案 GitHub:mikroverk/esp32sim
重點
- 跑真正的開機流程:從晶片自己的 mask ROM 開始,接著是二階段 bootloader、FreeRTOS,最後是沒改過的應用程式。
- 連板子一起模擬:螢幕、觸控、LED、按鈕、音訊、相機和 WiFi 都有模型,韌體可以畫圖、出聲,用 Espressif 原版 WiFi 函式庫連上虛擬基地台再連到真實網路。
- 直譯器是正確性的基準:平常以 block 為單位直譯,AArch64 主機上再把 block 編成機器碼,規定
--no-jit的結果必須完全相同。 - 四種晶片,成熟度差很多:ESP32-S3 標成 mature,C3、C6 是 draft,經典 ESP32 是 early。
- 和實體晶片比對過:S3 用 JTAG 逐步對拍,C3、C6 比對開機的序列埠輸出。
以 2026 年 10 月 20 日從 GitHub API 查到的資料,專案在 2026 年 8 月 25 日 15
(台北時間)建立,有 445 顆星、25 個 fork、21 個開啟中的 issue,授權是 MIT,最新版本是 9 月 14 日(台北時間)發布的 v0.2.0。Hacker News 的 Show HN 拿到 205 分。作者是 RISE 的 Joakim Eriksson、Alice 和 Schematik 的 Mattia Fiumara 等人。README 特別聲明:模擬器根據 ESP-IDF 暫存器標頭檔、Xtensa 核心設定、RISC-V 規格和技術參考手冊撰寫,QEMU 只用來確認指令語意,沒有複製程式碼。內容根據 README、docs/architecture.md、docs/hardware-testing.md、docs/cli.md,以及 xtensa-lx7/src/block.rs、xtensa-lx7/src/jit/mod.rs、emu-core/src/bus.rs、emu-core/src/jit_a64.rs 等原始碼。
它解決什麼問題
ESP32 韌體開發通常要燒進實體板子才能測。現有的模擬選項大多要改韌體或用替代驅動,例如 Espressif 自己維護的 QEMU 分支,官方文件列出 ESP32、ESP32-C3、ESP32-S3 都不支援 Wi-Fi 和藍牙;要連網得改用 OpenCores 乙太網路 MAC,這是只存在於模擬環境的周邊,韌體必須開啟 CONFIG_ETH_USE_OPENETH 並初始化這個驅動。esp32sim 的目標是讓同一個 .bin 不用改,從開機到連網都跑在電腦或瀏覽器分頁裡。
| 晶片 | CPU | 狀態 | 板子 |
|---|---|---|---|
| ESP32-S3 | 2 × Xtensa LX7 | mature | Atech 14-port kit、Waveshare Touch-LCD-4B 等 |
| ESP32-C3 | RISC-V RV32IMC | draft | 只有 console |
| ESP32-C6 | RISC-V RV32IMAC | draft | Waveshare ESP32-C6-LCD-1.47,含 802.15.4 |
| ESP32(經典) | 2 × Xtensa LX6 | early | LilyGO TTGO T-Display |
示範網站上可以直接跑 ESP32-S3 上的 Linux 6.11、SID 音樂播放器、LVGL 觸控面板,以及 C6 上執行 RPL/IPv6 的 Contiki-NG 節點。
怎麼運作
CPU:block 直譯器
xtensa-lx7/ 是 S3 的 CPU 核心。decode.rs 解碼,exec.rs 是單指令直譯器。單指令執行每一步都要檢查中斷、查解碼快取、累計週期,block.rs 的註解說這些開銷量起來佔執行時間三分之一,所以平常改以 block 為單位執行:
- 一個 block 最多 32 道預先解碼的指令(
MAX_LEN = 32),遇到控制轉移,或任何會改變中斷、計時器狀態的指令就結束; - 中斷檢查和計數每個 block 只做一次;
- block 不會跑過下一次
CCOMPARE命中的位置,讀寫CCOUNT/CCOMPARE的指令一定開一個新 block,所以計時器中斷仍在同一個指令邊界觸發; - register window 的 overflow 檢查仍然每道指令都做。
自我修改的程式碼
guest 記憶體每 256 bytes 一頁,每頁有一個寫入版本號(VPAGE_SHIFT = 8)。block 記住自己解碼來源那(最多兩)頁的版本,版本變了就重建。MMU 重新映射 flash 或 PSRAM 時會把版本號往上加,用舊映射解出來的指令就不會再被執行。存取記憶體走軟體 TLB,共 512 項,以 64 KiB 為索引單位。
部分 JIT,直譯器當 oracle
在 AArch64 的 macOS 或 Linux 主機上,block 會編成原生機器碼(xtensa-lx7/src/jit/)。jit/mod.rs 開宗明義寫著:編好的 block 跟直譯器跑的是同一段指令,結束規則完全相同,兩者必須產生逐 bit 相同的機器狀態,直譯器就是 oracle。--no-jit 改回直譯,結果必須一模一樣,出問題時就用它來比對。
- ALU、分支,以及 TLB 命中的記憶體存取都是原生碼;
- 呼叫、返回、
entry、特殊暫存器、FPU、PIE SIMD、MAC16 這些不常見的指令,跳回exec_insn執行那一道再回到原生碼。
機器碼由 emu-core/src/jit_a64.rs 產生,這是一個只涵蓋 block 編譯器需要的指令的小型 AArch64 編碼器,每個編碼都用測試和 clang 的組譯器比對。瀏覽器版另有一條 WebAssembly 的 JIT(wasm-jit/、jit/wasm_*.rs)。
周邊與排程
device_set! 巨集讓每顆晶片只寫一張裝置表(區塊、名稱、欄位、中斷來源編號),就能產生暫存器分派、中斷來源掃描和各時脈域的 tick。Machine 只寫一次,所有晶片共用。多核心以 quantum 輪流執行,原生版每次 64 道指令、瀏覽器版 256 道,可以設定在 64 到 4096 之間;核心閒置時會跳過時間。
虛擬 WiFi 與網路
esp-soc/src/wifi.rs:虛擬 802.11 基地台,處理 beacon、probe、認證、關聯,以及 WPA2 四向交握。交握本身是真的(PMK、PTK、MIC、AES key wrap 的 GTK);資料框以明文傳送,但照 CCMP 的格式包裝,這也是實體晶片原地加密時韌體看到的樣子。net.rs:模擬子網路 10.0.2.0/24,提供 ARP、DHCP、ICMP、DNS,以及用主機時鐘回應的 SNTP。nat.rs:每條 guest 連線由主機的 socket 轉送,TCP 和 UDP 都可以出去,所以 HTTPS 能直接用。AES、SHA、RSA 加速器都在暫存器層級模擬,TLS 走的是跟實機一樣的周邊。
從手冊產生指令表
S3 的 PIE SIMD 指令(ee.*)共 217 種編碼,由 tools/gen_pie_table.py 從技術參考手冊的指令格式轉出 pie_table.rs,再跟 ESP-IDF 5.5 的組譯器交叉比對。
怎麼確認模擬得對
docs/hardware-testing.md 列出的結果:
| ESP32-S3 | ESP32-C3 | ESP32-C6 | |
|---|---|---|---|
| 方法 | JTAG lock-step | console diff | console diff |
| 結果(專案自述) | 計時迴圈重新同步後 8000 步 | 208 行有 205 行相同 | 204 行有 203 行相同 |
| 解碼器全清單檢查(專案自述) | 97.7 萬道指令,0 不符 | 16.1 萬道,0 不符 | 12.6 萬道,0 不符 |
S3 的做法是透過 JTAG 讀出實體晶片的 efuse 和 strapping 腳位,再讓晶片和模擬器在同一份 flash 內容上逐步執行並比對。文件也寫明比對時會正規化 window 例外、重新同步延遲迴圈,所以不等於逐週期一致;這些數字是之前手動跑的結果,CI 跑的是抽樣的解碼器語料和模擬器的黃金輸出。C3、C6 的比對各找到五個模擬器 bug。
跟常見做法比
| Espressif 的 QEMU 分支 | esp32sim | |
|---|---|---|
| 語言 | C | Rust |
| WiFi | 不支援,改用 OpenCores 乙太網路(需改韌體設定) | 虛擬基地台,跑原版 WiFi 函式庫 |
| 板子周邊 | 少量 | 螢幕、觸控、音訊、相機等板子模型 |
| 瀏覽器 | 否 | WebAssembly 版 |
專案文件把 Espressif 的 QEMU 當成行為問題的參考,但不拿它的程式碼。速度方面,README 說 S3 加上 block 快取和 JIT 後,跑 hello_world 約比實機快 30 倍(專案自述)。
限制與成熟度
- 只有 S3 算成熟:C3、C6 是 draft,經典 ESP32 是 early。路線圖寫明 C6 的 PHY 基頻校正是 stub,也還沒有真正的射頻。
- 原生 JIT 只在 AArch64:x86 主機跑 block 直譯器。
- 不打算做的事:
docs/roadmap.md列出不規劃 blob 層級的 WiFi/BLE 模擬、快取時序的精確度,以及 Wokwi 或雲端整合。 - mask ROM 要另外下載:ROM 的 ELF 來自 Espressif 的 esp-rom-elfs(Apache-2.0),不放在 repo 裡,要先跑
tools/fetch-demo-assets.sh。 - 非官方:專案與 Espressif 沒有關係。
- 本文沒有實測:以上根據文件與原始碼整理,沒有編譯或執行。