Slide 1

Slide 1 text

Lean 4をRTL開発の中核にする — Sparkle における JIT、 検証、Reverse Synthesis(逆合成) 2026/07/11 Junji Hashimoto Rev0.1 1

Slide 2

Slide 2 text

目次 • Sparkle / Lean / RTL • アーキテクチャ: Elab → IR → SystemVerilog / JIT • ベンチマーク: Verilator との勝負 • 証明の活⽤: 等価検証‧スキップ‧逆合成 • エコシステムと JupyterLite • FPGA デモ: Ethereum 署名機(Macで動かない) • 「結局 AI で作ってるんじゃない?」 FPGA: Tang Nano 20K 2

Slide 3

Slide 3 text

プロフィール • グリー: シニアリードテック (インフラ 13 年⽬) • 前職はハードウェアエンジニア (10 年) • KVS / Web3 (Sui, Avalanche, Polygon, Oasys) • Haskell: Hasktorch 開発 • ML: アナザーエデンの AI (2017〜) • WebGPU: gpu.cpp 開発、rustを使ったプロダクトに導⼊中 3

Slide 4

Slide 4 text

Sparkle はなに? • Lean 4 の DSL で回路を書くシステム • RTL → SystemVerilog を⽣成 • シミュレーションが⾼速 (C ⽣成 + JIT) • 証明が使える: 等価検証‧スキップ‧逆合成 • ブラウザで試せる: verilean.github.io/sparkle 4

Slide 5

Slide 5 text

Lean —「ないものリスト」が半年で埋まった • 証明アシスタント + ⾼速コンパイルの関数型⾔語 • Web フレームワーク → lean-tea (Elm + Yesod + Persistent + MCP) • Jupyter カーネル → xeus-lean (Native + WASM) • GPGPU → hesper (WebGPU + Bitnet + Gemma4) • Flare → k8s operator (時相論理 + 安定性) : 開発中 証明するものを決めるのが⼤変。 • Game → TRPG?? • 「ないもの」は AI 時代には空き地 5

Slide 6

Slide 6 text

RTL (Register Transfer Level) • ゲートレベル • RTL ← SystemVerilog / Sparkle はここ • ビヘイビアレベル ← 逆合成でここに登る • クロック + DFF + 組み合わせ回路の抽象 6

Slide 7

Slide 7 text

Sparkle DSL — 10 行で回路 def grayCtr {dom : DomainConfig } : Signal dom (BitVec 4) := circuit do let bin ← Signal.reg 0#4 let binSig := bin. 1 let shifted := (fun x => x >>> 1) <$> binSig bin <~ binSig + 1#4 return (binSig ^^^ shifted) #synthesizeVerilog grayCtr -- これだけで SV が出る • Signal dom α = ドメイン上の時系列 / circuit do = レジスタ DSL 7

Slide 8

Slide 8 text

依存型と RTL • 回路は常にビット幅を指定する世界 • ソフトの型に「4bit」はない → Int に詰めて祈る • Lean: BitVec (n : Nat) — 型に数が⼊る • 幅ミスマッチ = コンパイルエラー • Lean を使う理由の⼀丁⽬⼀番地 8

Slide 9

Slide 9 text

circuit do は「後付けの文法」 — syntax 宣言 -- Lean に新しい文法を足している (本体の改造ゼロ ) syntax "let " ident " ← " "Signal.reg " term : cdoStmt syntax ident " <~ " term : cdoStmt syntax "circuit" "do" ... : term • やりたいこと: 回路を命令型っぽく書きたい • でも Lean に <~ なんて⽂法はない • syntax = ユーザーが⽂法⾃体を追加できる Lean の標準機能 • <~ も Signal.reg も私たちが定義した「新しい構⽂」 9

Slide 10

Slide 10 text

展開先: 状態はただのタプル -- macro が circuit do を純関数に翻訳する -- レジスタ 2 本なら状態 = タプル (Prod): step : (BitVec 4 × BitVec 8) → (BitVec 4 × BitVec 8) -~~~~~~~~ 今の状態 ~~~~~~~~ ~~~ 次の状態 ~~~ • 命令型に⾒えるが、実体は「状態を受けて次状態を返す純関 数」 • モナドと⾔っても魔法なし — タプルの詰め替えだけ • 純関数 + タプルなら elaborator が構造を追える • → そのまま回路 (レジスタ + 配線) に落ちる 10

Slide 11

Slide 11 text

苦労した話 (1): 出力は何でもよくない class HasDomain (ρ : Type) (dom : outParam DomainConfig ) instance : HasDomain (Signal dom τ) dom instance [HasDomain α dom] [HasDomain β dom] : HasDomain (α × β) dom • AI は任意の値を出⼒にしようとして失敗し続けた • ⼈間の解決: HList + typeclass で「可能な形」を閉じる • dom 不⼀致 = インスタンス探索失敗 = コンパイルエラー • AI が⾃発的に出さない設計判断だった 11

Slide 12

Slide 12 text

苦労した話 (2): 合成が N² に遅くなる • FSM の各レジスタが同じ部分式 (次状態ロジック) を参照 • elaborator は同じ⽊を毎回「新品の Expr」として再⽣成 • 重複に気づけないと同じ回路を N 回合成 → 時間も回路も N² 12

Slide 13

Slide 13 text

解決: content-addressed な AST キャッシュ -- キャッシュのキーを変えた : -旧: Std.HashMap Lean.Expr _ -BEq Expr ≈ ポインタ等価 → ヒット率 < 10% -新: Lean.ExprStructMap _ -Expr.equal (構造等価 ) + Expr.hash exprCache : IO.Ref (Lean.ExprStructMap String) • ポインタが違っても構造が同じなら同じワイヤに collapse • 合成時間が線形に戻り、重複回路も消えた • 教訓:「同じ式」には 3 つの同⼀性がある ‒ ポインタ / 構造 / 意味 — どれで持つかが性能を決める 13

Slide 14

Slide 14 text

アーキテクチャ : Elab がフロントエンド • Haskell の Clash と同じ発想 • Lean はメタプログラミングが第⼀級市⺠ 14

Slide 15

Slide 15 text

#synthesizeVerilog の実装 (実物) elab "#synthesizeVerilog" id:ident : command => do let declName ← liftCoreM (resolveGlobalConstNoOverload id) liftTermElabM do let (module, _) ← synthesizeCombinational declName for w in DRC.checkRegisteredOutputs module do logWarning m! "{w}" let optimized := IR.Optimize.optimizeModule module IO.println (toVerilog optimized) • ユーザー定義コマンドが 10 ⾏ — 解決→合成→DRC→最適化→ 出⼒ 15

Slide 16

Slide 16 text

Expr → IR 変換 let constInfo ← getConstInfo declName -- 定義の Expr let resultWire ← translateExprToWire body "result" • (· + ·) <$> a <*> b → add ノード • Signal.mux c t e → mux ノード • Signal.register init next → DFF • 補助 def は unfoldDefinition? で 1 段ずつ inline ‒ whnf で⼀括正規化すると深いタプルで指数爆発した 16

Slide 17

Slide 17 text

IR 最適化 • 定数畳み込み: mux(true,a,b) → a など • 単⼀使⽤ワイヤのインライン化 • 到達可能性 DCE (Dead Code Elimination): 出⼒から BFS • 効果: JIT 2.0x (6.3M → 12.6M cyc/s) • 信号名ハードコードなし — 全部汎⽤ 17

Slide 18

Slide 18 text

SystemVerilog 出力 module grayCtr ( input logic clk, input logic rst, output logic [3:0] out); always_ff @(posedge clk) begin if (rst) _tmp_a_6 <= 4'd0; else _tmp_a_6 <= _tmp_reg_input_5; end assign out = (_gen_binSig ^ (_gen_binSig >> endmodule 4'd1)); • let の名前が _gen_ として SV に⽣存 • yosys / Vivado / Gowin にそのまま⼊る 18

Slide 19

Slide 19 text

JIT: IR → C → dlopen @[extern "sparkle_jit_load" ] opaque JIT.load (path : @& String) : IO JITHandle @[extern "sparkle_jit_eval_tick" ] opaque JIT.evalTick (h : @& JITHandle) : IO Unit • clang -shared -O2 → ハッシュキャッシュ → dlopen • 回路変更 → 数秒で再実⾏ • このループの速さが後の AI 話に効く 19

Slide 20

Slide 20 text

Q: Verilator に勝てるの ? • Verilator のプロファイルを取った • 良: 変数削除‧キャッシュ最適化 • 悪: シングルクロックでも Mutex 多⽤ • 作戦: Mutex なし + スタックローカル化 + DCE 20

Slide 21

Slide 21 text

ベンチ (1): RV32I SoC (10M cycles) バックエンド cyc/s vs Sparkle JIT (ピーク 時) 14.2M 1.63x Verilator 5.040 8.73M 1.00x • Sparkle 製フル SoC (RV32IMA, Linux ブート) • ※現 main は開発中で退⾏ (2.6M) — 回復予定 21

Slide 22

Slide 22 text

ベンチ (2): LiteX + PicoRV32 (1730 行の実SV) バックエンド cyc/s vs Sparkle JIT (ピーク 時) 11.7M 1.13x Verilator 5.040 (-O2) 10.5M 1.00x • 他⼈の Verilog を SVParser で取り込んで JIT • ※現 main は退⾏中 22

Slide 23

Slide 23 text

ベンチ (3): マルチコア (8 コア共有バス ) 構成 Verilator Sparkle 比 1 コア 10.5M 11.7M 1.13x 8 コア並列 5.07M 1.06M 4.78x • Sparkle は命令共有で劣化が緩い 23

Slide 24

Slide 24 text

なぜ勝てるのか • 1. ワイヤ全ローカル化 (L1 に載る)<- Verilatorもやっている • 2. 汎⽤ガード検出 (_valid/_enable)<- Verilatorもやっている • 3. DCE + 定数伝播 + fused evalTick<- Verilatorもやっている • 4. 異なるクロック間のやり取りはロックフリー&投機実⾏ • 5. 証明つき逆合成: FSM → 直接計算 24

Slide 25

Slide 25 text

証明の活用 — 独自の 3 本柱 • 1. 回路の等価検証 • 2. 不変条件でシミュレーションをスキップ • 3. 逆合成 — RTL をビヘイビアに登る • 証明が安全のためだけでなく速度のためにある 25

Slide 26

Slide 26 text

等価検証 example : ∀ op a b, aluPlain op a b = aluSpec op a b := by decide • ⼩空間: decide / native_decide で全数 • BitVec: bv_decide (SAT に委譲) • SATでできない場合は帰納法で証明(⾃動の証明は難しい) • リファクタ前後の同⼀性を CI で証明 • 各種コマンド(完全に等価、サイクル精度を跨いで等価、) #verify_eq, #verify_eq_at, #verify_eq_git 26

Slide 27

Slide 27 text

Verilog を読み込んで証明する verilog! " module sum_reg(...); always @( posedge clk) if (rst) result <= 1'b0; else result <= a ^ b; endmodule" theorem spec (s : State) (i : Input) : (nextState s i)._reg_result = (if i.rst = 0 then i.a ^^^ i.b else 0) := by simp [nextState]; bv_decide • 既存 SV 資産にあとから証明を貼れる 27

Slide 28

Slide 28 text

不変条件によるサイクルスキップ • 「状態が変わらない」区間は回さなくていい • 例: CPU がタイマー待ちで空転 • 証明があるから結果不変が保証される • ヒューリスティックではない 28

Slide 29

Slide 29 text

逆合成 — OracleReduction class OracleReduction (tag State Input Output) where step : State → State -- FSM の 1 サイクル compute : Input → Output -- 直接計算 numCycles : Nat /-- MANDATORY PROOF -/ equiv : ∀ input, extractResult (iterateN step numCycles (initState input)) = compute input • 「N サイクル = 直接計算」の証明が必須フィールド • 証明が書けなければインスタンスが作れない 29

Slide 30

Slide 30 text

逆合成の実例 : 乗算器 • pcpi_mul: 38 assign のマルチサイクル FSM • a * b = fold (+ a) 0 [1..b] <- 掛け算は⾜し算の畳み込み • 回路では畳み込みで掛け算を表現 • 計算機では掛け算は掛け算 • 回路の表現は畳み込みだが計算は掛け算にする。 • 合成の逆向きだから「逆合成」 30

Slide 31

Slide 31 text

Timer Oracle: 48.9 GHz 相当 モード 実効速度 倍率 通常シミュレーション 5.04M cyc/s 1x Timer oracle 48.9 GHz 相当 9,707x • CPU アイドル検出 → タイマー分を⼀括スキップ • 実クロックをシミュレーションが追い越す 31

Slide 32

Slide 32 text

IP カタログ — 全部 Lean 製 • RV32: RISC-V SoC (Linux ブート) • Crypto: secp256k1 / Ed25519 / BLS12-381 / AES-GCM • BitNet: 1-bit LLM / YOLOv8 / H.264 • Bus: AXI4 / PCI-E • 完成度はまちまち — が同じ証明インフラに乗る 32

Slide 33

Slide 33 text

ドキュメント & チュートリアル • チュートリアル 12 章 (Ch0〜Ch11) https://verilean.github.io/sparkle • 組み合わせ → 順序 → 証明 → FPGA → Web3 実機 • API リファレンス⾃動⽣成 (doc-gen4) • IP ごとの仕様書 (docs/ip-catalog/) 33

Slide 34

Slide 34 text

JupyterLite — ブラウザだけで試せる • xlean カーネルを WASM ビルド • verilean.github.io/sparkle/lab/ を開くだけ • インストールゼロ • #synthesizeVerilog も #eval も動く 34

Slide 35

Slide 35 text

Jupyter + MCP: AI と同じ画面で開発 • xeus-lean (native kernel) + jupyter-collaboration (RTC) • AI がノートブックをライブ編集 → 実⾏ • 波形 SVG がブラウザに即出る • notebook_run_cell 1 コールで完結 35

Slide 36

Slide 36 text

開発の TAT 実測 操作 実測 ファイル / md⇄ipynb 変換 1–8 ms カーネル実行 150–240 ms 回路変更 → JIT 再シム 数秒 • LLM 推論 (~3s) が⽀配的、ツール側はゼロ • この数字が最終章の伏線 36

Slide 37

Slide 37 text

FPGA デモ: 鍵がチップから出ない Ethereum 署名機 • Tang Nano 20K (約5,000円) 実機 • 全部 Sparkle (Lean) 製 • 危殆化ホストでも d も k も漏れない 37

Slide 38

Slide 38 text

Lean → 実機 → 実チェーン lake env lean GenSignZDemoSynth .lean yosys + nextpnr-gowin openFPGALoader → Tang Nano 20K nix-shell -p foundry –-run anvil python3 sign_tx.py --send → anvil # Lean → SV # 合成+P&R # 書き込み # anvil を起動 # 実チェーン • 実機で 2 回の実転送に成功 • ecrecover がチップのアドレスに⼀致 • だが、Linuxでは動いたがMacだと動かない。(デバッグ中) 38

Slide 39

Slide 39 text

デモタイム • sign_tx.py → LED → anvil の残⾼変化 • バックアップ: JupyterLite で波形デモ 39

Slide 40

Slide 40 text

「結局 AI で作ってるんじゃない ?」 • そうです。半年で「ないもの」が全部埋まった • ただし: 何を作るか‧証明を速度に使う着想は⼈間 • AI が埋めたのは実装の膨⼤な⼿数 • 実話: AI は「Python で作れば?」と⾔ってきた • AI は踏まれた道を勧める。道を決めるのは⼈間 • 本題: 律速はモデルの賢さでなく TAT 40

Slide 41

Slide 41 text

E2Bのカーネルチューニング: webgpu + js • 30分程度で 254.8 tok/s — llama.cpp (220, 240?) 超え https://x.com/xenovacom/status/2065656427117437213 だが、leanのフレームワークだと数⽇かけても50%も難しい 41

Slide 42

Slide 42 text

実証: E4B エンジンをスクラッチで一朝 フェーズ 時間 リポ作成 + 資料読み 24 分 ベースライン計測 30 分 ハーネス + golden 値 35 分 初回実行で token 一致 50 分 合計 1 時間 46 分 • 同⽇中に 124.2 tok/s — llama.cpp (102.4) 超え 42

Slide 43

Slide 43 text

Sparkle は「ハードウェア版の同じ賭け」 観点 e4b-webgpu Sparkle 実行まで 秒 (no build) 秒 (elab + JIT) 正しさ golden 一致 Lean 証明 観測 tok/s 波形が即出る • HW は本来 TAT 最悪 → 秒の世界に持ち込む • 証明は AI の柵: sorry はバレる、等価性とか型でコンパイルで落 ちるのだが、それが⽣産性を下げる場合があるかも。 43

Slide 44

Slide 44 text

まとめ • Lean の elab がコンパイラフロントエンド • JIT: Verilator ⽐ 1.6‒1.7x / オラクルで 9,707x • 証明を「速度」に変える 3 本柱 • Tang Nano 20K で実チェーンに送⾦ • verilean.github.io/sparkle で今すぐ • Q&A 44