Verilog → draw.io:架構圖 / 結構框圖 / 狀態機 / 控制結構
四個視角,按問題選擇:設計思想是什麼(給人講、放論文/報告)→ arch2drawio.py(第零節,預設);
閘級長怎樣(逐單元的資料路徑)→ rtl2drawio.py;怎麼走的(單個 FSM)→ fsm2drawio.py fsm;
誰指揮誰(整體控制結構)→ fsm2drawio.py ctrl。若以控制為主的模組(FSM、解碼、仲裁控制器)用框圖會亂,改用後兩者。
依賴:PyYAML;匯出 PNG/SVG 需要 drawio(搜尋順序 $DRAWIO_BIN → PATH → macOS app → ~/.local/opt/drawio;
Linux 無 $DISPLAY 時自動套用 xvfb-run,無 root 的伺服器可使用倉庫裡的 tools/install-drawio-linux.sh 安裝)。若無匯出工具,所有命令仍正常生成
.drawio,只缺 PNG,stderr 提示一行。框圖/控制結構另需 yosys、sv2v(SystemVerilog)、dot(Graphviz),以及已安裝的 drawio-skill
(預設 ~/.claude/skills/drawio-skill,可用環境變數 DRAWIO_SKILL_DIR 覆蓋;只呼叫它的 autolayout.py,不改它)。
三個生成腳本都接受 --png / --svg / --scale N(PNG 預設 scale 3,約 1 model unit = 3 px;$DRAWIO_SCALE 可改預設值),
也可以單獨匯出:python3 $S/scripts/drawio_export.py X.drawio --png --svg。放進 Markdown/Obsidian 優先給 SVG,不模糊。
$S 指本 skill 目錄。examples/ 裡有兩個對照範例:wrr_arbiter.v(單級 + 狀態暫存器)、load_pipe.v(含 replay 的多級管線)。
零、體系結構風格架構圖(arch2drawio.py)——預設
輸入是程式碼(也可以是論文 / 專利 / 使用者描述)。Yosys 網表太細、不適合架構圖,所以此處不綜合:腳本給出原始碼大綱, 由你(agent)讀程式碼、抓核心、寫出圖描述,再由腳本確定性地排版出圖。使用者不需要寫任何描述檔案;只給使用者看圖, 除非使用者想自己改描述。
python3 $S/scripts/arch2drawio.py outline src/*.sv --top gpu [--module core] # 原始碼大綱(不綜合)
python3 $S/scripts/arch2drawio.py render fig.yaml -o diagrams/arch_core.drawio --png
outline 列出:層次樹(generate 複製 ×N)、連接埠、儲存陣列(深度 × 位元寬度,packed struct 的欄位,同深度陣列提示「可能同一張表」)、
按級號命名的時序暫存器(s1_ / stage2_ / if_id_ …)、狀態機、疑似反饋信號(replay/kill/flush/stall/forward/refill…)、
實例間資料流(經共享信號,已去掉 clk/reset/迴圈變數)。論文/專利/描述輸入時跳過此步驟。
畫圖規則(寫描述時遵守)
- 只畫核心:先用一兩句話說清設計的核心思想,只保留體現它的部件;膠合邏輯、clk/reset、除錯口、零散控制都省略。 一般 8–20 個框;超過就拆成兩張(頂層一張 + 關鍵單元一張)。
- 重點突出:1–3 個關鍵設計點
hl: true(反差色:深底白字)或bold: true(加粗);關鍵路徑的線也可| hl。 需要強調一塊區域時用groups+hl: true(淺底色框)。 - 不給每個元件編號:只在需要文字對照時給核心元件
num: N,並在callouts裡寫說明。 - 單元框言簡意賅:框內只寫名字(≤ 2 行),含義靠形狀表達:運算用
adder/mult/cmp/op圓形符號或alu, 選擇用mux(選擇端x.sel從底部進),儲存用table,佇列用fifo,暫存器用reg,閘用and/or/xor/not。 - SRAM 表寫出單一項目的欄位:
table的fields寫每個欄位的含義(如[V, Tag (20b), Data (32b)]), 多路組相聯用stack: 4(疊放 + ×4);同深度、同位址的並行陣列合成一張表,每個陣列一個欄位。 - 管線:
stages定義級(id+ 功能sub,如{id: S1, sub: 命中判斷}),腳本畫虛線分界和級名; 級間暫存器 / FIFO 用between: [S0, S1],腳本把它畫在分界線上;反饋(目標在更早的級)自動沿底部走廊繞回, 也可| fb、| fb via=top(從上方繞)。多條管線用lanes,各 lane 共用級的列,時序上對應的級自動對齊。 - 不分級的方塊級圖(頂層、SoC)不寫
stages;複製的單元用stack: N,每執行緒/每核資源用groups框起來。
描述格式(YAML)
title: dcache_pipe:直接映射 D-Cache 尋找管線
stages: [{id: S0, sub: 選擇 / 讀取陣列}, {id: S1, sub: 命中判斷}, {id: S2, sub: 缺失 / 重播}]
lanes: [{id: ld, name: Load 管線}, {id: st, name: Store 管線}] # 可選,多條管線時
nodes:
- {id: arr, type: table, label: "Cache 陣列 · 64 項", fields: [V, "Tag (20b)", "Data (32b)"], stage: S0}
- {id: r1, type: reg, label: addr, between: [S0, S1]} # 落在 S0|S1 分界線上
- {id: tcmp, type: cmp, label: Tag 比較, stage: S1, hl: true} # 圓形 "=",說明文字在下方
- {id: rmux, type: mux, stage: S0, ports: 2}
edges:
- "rmux => arr : idx | w=6" # -> 資料 => 匯流排(粗) ~> 控制(虛線);w=N 位元寬
- "rpl -> rmux.sel : replay | fb hl" # .sel/.en 從底部進,.top 從頂部進;fb 反饋,hl 強調,via=top,pos=src|dst
groups: [{label: "每執行緒 ×4", members: [rf, alu, lsu], hl: true}]
legend: {reg: 管線暫存器, fb: 反饋, ctrl: 控制, hl: 關鍵設計} # 可用鍵:data bus ctrl fb hl hl_edge hl_region reg
callouts: {1: "只有在需要對照說明時才編號"}
節點類型:block reg fifo table(fields, rows, stack) array(dims, cell) mux(ports) adder mult cmp/op(sym)
alu and or xor nand nor xnor not cloud mem xbar in out text dot。
通用屬性:stage / lane / between,hl bold num stack,微調用 row / col(在級內的行 / 列,可為負)、dx / dy、w / h。
其它:array 支援 cell_labels(逐格標籤,如 T0..T3)和 cell_h(長方格,畫執行緒通道);groups 支援 stack: N
(整塊區域疊放,如「Core ×2」並露出內部)和 label_align: right;lanes 的 name 留空 = 只分區不畫標籤;
邊的 | both 畫雙向箭頭(請求/回應);同一列上下兩個單元之間的反饋畫成緊貼的短豎線(如 Dispatcher ⇅ Core)。
流程
outline+ 讀取關鍵原始碼(或讀論文/專利),寫下核心思想 → 決定畫哪一層、哪些是重點。- 把描述寫到臨時目錄(scratchpad),每個節點都給
stage(有級時)。迴圈自動斷開:名字像控制器的節點 (sched / ctrl / fsm / arbiter / dispatch / 排程 / 控制 …,其次是控制線扇出多的)先開始,它的輸出往右、回到它的邊畫成反饋; 判斷不對時對那條邊顯式寫| fb。 可選:構圖參考(gpt-image-bridge,需使用者逐張確認)——網格排版器保證連線正確,但不會「構圖」。模組 ≥ 10 或 有明顯分割槽(控制面/資料面/儲存層次)時,可先出 draw.io 基線,再把從程式碼得出的模組清單和連線寫進提示詞 (論文插圖風格、白底、單一強調色、三區佈局、文字極少、英文短標籤),用 gpt-image-bridge 生成 1 張草圖並 Read 它。 只借鑑佈局與視覺層次(分割槽、誰在誰上方、陣型、留白、重點位置),換算成lane/row/col/groups; 模組、連線、名稱一律以程式碼為準——草圖常見錯誤:連錯源頭、多畫/少畫模組、深色背景。生成前按該 skill 規定 列出圖片(用途、路徑、尺寸、提示詞摘要)並等使用者明確同意;草圖存diagrams/sketch_*.png,描述檔頭部註解引用它。 render --png→validate.py(error 必須為 0;分界虛線穿過級頭/暫存器的 overlap 警告是設計如此)→ Read PNG 目視檢查, 用row/col微調最多兩輪(常見:讓主資料路徑佔 row 0 走直線,旁路 / 控制放下一行)。- 交付
.drawio+.png;描述檔留在 scratchpad,使用者要自己改時再給。
範例:tiny-gpu 頂層 ~/hardware-project/tiny-gpu/diagrams/arch_gpu.*(由草圖 sketch_gpu_top.png 指導構圖,
對照未用草圖的 arch_gpu_baseline.png);examples/dcache_pipe.v → dcache_pipe.arch.yaml(由程式碼寫出的描述)→ .png;arch_dual_issue.yaml(雙管線對齊 + 轉遞 +
編號註釋)、arch_mac.yaml(電路級:乘 / 加符號、累加反饋、MUX 選擇端)。tiny-gpu core 的方塊級圖見
~/hardware-project/tiny-gpu/diagrams/arch_core.*。
一、結構框圖(rtl2drawio.py,閘級細節)
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --list # 模組及單元構成
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --module alu -o diagrams/alu.drawio --png
流程:sv2v(遇到 .sv 自動)→ Yosys proc; opt; opt_dff; wreduce; memory -nomap → JSON 網表 → 單元級管線分析 → 簡化 → 列約束佈局。
生成後執行 python3 ~/.claude/skills/drawio-skill/scripts/validate.py X.drawio(error 必須為 0;穿過級分界線/暫存器壓在分界線上
產生的 through-vertex / overlap 警告是設計如此),再 Read PNG 目視檢查。
佈局規則(--layout pipeline;預設 auto:以子模組實例為主的層次模組用自由佈局,其餘用管線級佈局)
- 輸入在左、輸出在右,邏輯按組合深度從左往右分列;多級管線按 S0 → Sn 從左到右,虛線分界,列頭標級號。
- 暫存器分兩類:
- 管線暫存器(窄豎條)放在級分界線上;最後只接輸出埠的一排是輸出暫存器(不算一級、不畫分界線)。
- 狀態暫存器(FSM / 計數器 / 指標:輸出只經組合邏輯就回到自己輸入)畫在級內,輸出作為反饋。
- 暫存器名帶級號時(
s2_x、x_s2、stage2_x、if_id_/id_ex_/ex_mem_/mem_wb_,且至少兩種)以名字為準—— 帶 kill 的 valid 位即使有組合迴路也仍是管線暫存器。設計不按級命名時只能靠結構推斷,輸入埠的到達時間未知,級號可能偏早。
- 級號推算:從帶級號暫存器出發,單元的級 = 最晚到達的暫存器運算元(取最大);只由輸入埠驅動的邏輯按「被哪一級暫存器採走」 歸級(ALAP),對應的輸入埠也放到那一級開頭(例如 TLB 回應放在 S1)。
- 反饋:進入某個帶級號暫存器輸入錐、卻只可能來自更後級的信號(replay→S0、kill→S1),以及狀態暫存器的迴圈,畫成
紅線沿底部外圈繞回,跨度大的在外;同一對級之間反饋多於 3 根時只畫一根粗線代表一捆(標
反饋 ×N:…)。 - 成捆:同一分界上管線暫存器多於 4 個時合併為一個暫存器組(豎條標
S1→S2 暫存器 ×N和總位元寬度)。 - 簡化:同名暫存器切片合併;巢狀 if/case 的 MUX 鏈合成一個
N:1MUX;只驅動一個 MUX 選擇端 / 暫存器致能的小解碼邏輯 並進去畫成選擇線 / 致能線;3 個以內的閘畫成與/或/非/異或閘符號,更大的摺疊成「組合邏輯」雲(會在夾著運算單元處切開, 避免假迴圈);generate 迴圈展開的同源(同一原始碼位置)單元與threads[i].x實例摺疊成 「×N」(帶陰影)。 所有合併都只在同一級內進行。clk/reset 預設隱藏。 - 連線:實線=資料(粗=多位元匯流排,
/N=位元寬度);紫虛線=MUX 選擇;藍點線=致能;紅線=反饋。
參數
| 參數 | 作用 |
|---|---|
--list | 列出模組及單元構成後退出 |
--module M | 畫哪個模組(參數化變體畫第一個,會提示) |
--layout auto|pipeline|free | 預設 auto:有子模組實例且組合邏輯節點不多於實例(頂層膠合模組)用 free(塊間雙向通訊不是管線反饋),否則 pipeline |
--show-clock | 畫出 clk/reset |
--no-fold | 不摺疊 generate 實例 / 同源單元 |
--no-collapse | 不摺疊控制/組合邏輯 |
--sv2v auto|always|never | SV 前端(非壓縮陣列埠等語法需要 sv2v) |
--keep-json PATH | 保留 Yosys JSON 網表 |
二、狀態機圖(fsm2drawio.py fsm)
python3 $S/scripts/fsm2drawio.py list src/*.sv
python3 $S/scripts/fsm2drawio.py fsm src/*.sv [--module M] -d diagrams --png [--no-actions]
- 直接解析原始碼,轉移條件保留原始運算式。辨認
case (S)分支裡給 S(或S <= N的次態變數 N)賦值;狀態名取 localparam; 比較別的 FSM 狀態變數的常數翻譯成對方狀態名(core_state == 3'b001→core_state == FETCH)。 - 轉移條件 = 直接包住狀態賦值的 if/case/for;
else if只寫自己的條件;for裡的條件寫成∃j: …;外層for i包住整個 case 表示按 i 複製;case 外的守衛(enable、讀/寫分支)進註解框;迴圈裡置 1 的區域布林變數在註解框給出定義。 - 畫法:每個狀態一個等大圓圈(名字在
_處換行,下方小字為編碼和 ⟳),終態雙圈,黑點=復位入口。 主流程涵蓋大部分狀態時排成 U 形環(上排左→右、拐下、下排右→左);分支型按離初始狀態的步數均勻分列。 相鄰狀態直連;跳過其他狀態的轉移從上方/下方拱過,標籤在拱頂;A↔B 往返各彎向一側;顯式自環畫在圓上方。 列距按最長標籤自適應。/後為轉移時的賦值(最多顯示 2 個)。 - 不辨認:沒有
case(state)結構的控制(計數器式、純組合解碼)——list裡不出現,屬預期。
三、整體控制結構(fsm2drawio.py ctrl)
python3 $S/scripts/fsm2drawio.py ctrl src/*.sv --top gpu --module core -o diagrams/ctrl_core.drawio --png
- 畫模組 L 裡的子模組實例(generate 實例摺疊 ×N),只保留控制信號:橙粗線=FSM 狀態廣播;藍線=valid/ready 握手; 紫虛線=start/done/致能/選擇/復位等;資料匯流排省略。含 FSM 的實例框列出狀態序列,子層次裡有 FSM 的標「內含 FSM」。
- 控制信號穿透最多 3 層基本單元(比較器、閘、暫存器)追到源頭。分類靠信號名,命名不規範時可能漏畫/多畫。
- 建議從上往下:先 top(誰啟動誰、誰和儲存器握手)→ 下一層(主控 FSM 怎麼排程)→ 關鍵 FSM 的狀態機圖。
限制
- 佈局由 Graphviz 按列約束自動完成,交叉無法完全消除;精修請在 draw.io 裡拖動(全是原生可編輯物件)。
- 大型控制類模組(上百單元)框圖仍會亂,請按上文改採狀態機圖 / 控制結構圖,或逐個子模組畫。
- 狀態機圖的圓圈/弧線是依座標直接畫的,拖動狀態時弧線的拐點不會跟著移動,需要順手調整一下。