Verilog → draw.io:アーキテクチャ図 / 構造ブロック図 / 状態遷移図 / 制御構造図
4つの視点があり、問題に応じて選択します。設計思想は何か(人に説明する、論文/レポートに掲載する)→ arch2drawio.py(第ゼロ節、デフォルト)。
ゲートレベルはどのようなものか(ユニットごとのデータパス)→ rtl2drawio.py。どのように動作するか(単一FSM)→ fsm2drawio.py fsm。
誰が誰を制御するか(全体制御構造)→ fsm2drawio.py ctrl。制御主体のモジュール(FSM、デコード、アービトレーションコントローラ)はブロック図では乱雑になるため、後者の2つを使用します。
依存関係: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 に1行のヒントが出力されるだけです。ブロック図/制御構造図にはさらに yosys、sv2v(SystemVerilog)、dot(Graphviz)、およびインストール済みの drawio-skill が必要です
(デフォルトは ~/.claude/skills/drawio-skill、環境変数 DRAWIO_SKILL_DIR で上書き可能;その autolayout.py のみを呼び出し、変更は加えません)。
3つの生成スクリプトはすべて --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 はこのスキルのディレクトリを示します。examples/ には2つの比較サンプルがあります:wrr_arbiter.v(単一ステージ + ステートレジスタ)、load_pipe.v(リプレイ機能付きマルチステージパイプライン)。
零、アーキテクチャ図(arch2drawio.py)——デフォルト
入力はコード(論文、特許、ユーザーの説明でも可)です。Yosys ネットリストは細かすぎてアーキテクチャ図には適さないため、ここでは合成しません。スクリプトはソースコードの概要を提供し、 あなた(エージェント)がコードを読み、核となる部分を抽出し、図の説明を記述し、スクリプトが決定論的にレイアウトして図を生成します。ユーザーは説明ファイルを記述する必要はなく、図を見るだけで済みます。 ただし、ユーザーが自分で説明を変更したい場合は除きます。
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/ループ変数は除去済み)。論文/特許/説明が入力された場合はこの手順はスキップされます。
図の描画ルール(説明を記述する際に従うこと)
- 核となる部分のみを描く:まず1~2文で設計の核となる考え方を説明し、それを体現するコンポーネントのみを残します。グルーロジック、clk/reset、デバッグポート、散在する制御はすべて省略します。 通常は8~20個のボックスに収めます。それを超える場合は2枚に分割します(トップレベル1枚 + キーユニット1枚)。
- 重点を強調する: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)を使用します。同じ深さ、同じアドレスを持つ並列配列は1つのテーブルにまとめ、各配列を1つのフィールドとします。 - パイプライン:
stagesでステージを定義します(id+ 機能sub、例:{id: S1, sub: 命中判定})。スクリプトは点線の境界とステージ名を描画します。 ステージ間のレジスタ/FIFO はbetween: [S0, S1]を使用し、スクリプトはそれを境界線上に描画します。フィードバック(より前のステージがターゲット)は自動的に下部の廊下に沿って回り込みます。| fb、| fb via=top(上部から回り込み)も指定できます。複数のパイプラインがある場合はlanesを使用し、各レーンはステージの列を共有し、時間的に対応するステージは自動的に整列します。 - ステージ分割しないブロックレベル図(トップレベル、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: ロードパイプライン}, {id: st, name: ストアパイプライン}] # オプション、複数パイプライン時
nodes:
- {id: arr, type: table, label: "キャッシュアレイ · 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、ユーザーが1枚ずつ確認する必要あり)——グリッドレイアウターは配線の正確性を保証しますが、「構図」は行いません。モジュール数が10以上、または 明確な区分(制御プレーン/データプレーン/メモリ階層)がある場合は、まず draw.io のベースラインを生成し、その後コードから得られたモジュールリストと接続をプロンプトに記述し (論文イラスト風、白背景、単一の強調色、3つの領域レイアウト、最小限のテキスト、英語の短いラベル)、gpt-image-bridge を使用して1枚のスケッチを生成し、Read します。 レイアウトと視覚的な階層のみを参考にします(区分、誰が誰の上にいるか、陣形、余白、重要な位置)。それらをlane/row/col/groupsに変換します。 モジュール、配線、名前はすべてコードを基準とします——スケッチの一般的な誤り:接続元の間違い、モジュールの過不足、暗い背景。生成前に、このスキルの規定に従って 画像(用途、パス、サイズ、プロンプトの要約)をリストアップし、ユーザーの明示的な同意を待ちます。スケッチはdiagrams/sketch_*.pngに保存され、記述ファイルのヘッダーコメントでそれを参照します。 render --png→validate.py(エラーは0でなければなりません。境界点線がステージヘッダー/レジスタを通過するオーバーラップ警告は設計上のものです)→ Read PNG で目視確認し、row/colで最大2回の微調整を行います(一般的な調整:メインデータパスを 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 を実行します(エラーは0でなければなりません。ステージ境界線を通過する/レジスタが境界線上に重なることによる
through-vertex / overlap 警告は設計上のものです)。その後、Read PNG で目視確認します。
レイアウトルール(--layout pipeline;デフォルト auto:サブモジュールインスタンスが主体の階層モジュールは自由レイアウト、それ以外はパイプラインレイアウト)
- 入力は左、出力は右、論理は組み合わせの深さに従って左から右へ列分割。マルチステージパイプラインは S0 → Sn を左から右へ、点線で境界、列ヘッダーにステージ番号。
- レジスタは2種類に分類:
- パイプラインレジスタ(細い縦棒)はステージ境界線上に配置。最後に出力ポートのみに接続する列は出力レジスタ(ステージとしてカウントせず、境界線を描画しない)。
- ステートレジスタ(FSM / カウンタ / ポインタ:出力が組み合わせロジックのみを介して自身の入力に戻る)はステージ内に描画し、出力はフィードバックとして扱う。
- レジスタ名にステージ番号が含まれる場合(
s2_x、x_s2、stage2_x、if_id_/id_ex_/ex_mem_/mem_wb_、かつ少なくとも2種類存在)名前を優先—— kill を持つ valid ビットは、たとえ組み合わせループがあってもパイプラインレジスタとして扱う。設計がステージ番号で命名されていない場合は構造からの推論に頼るしかなく、入力ポートの到達時間は不明なため、ステージ番号が早すぎる可能性がある。
- ステージ番号の推論:ステージ番号を持つレジスタから開始し、ユニットのステージ = 最も遅く到達するレジスタオペランド(最大値を取る)。入力ポートのみで駆動されるロジックは、「どのステージのレジスタに取り込まれるか」でステージを割り当てる(ALAP)。対応する入力ポートもそのステージの先頭に配置する(例:TLB 応答は S1 に配置)。
- フィードバック:特定のステージ番号を持つレジスタの入力コーンに入るが、より後のステージからのみ可能性がある信号(replay→S0、kill→S1)、およびステートレジスタのループバックは、
赤線で下部の外側の廊下に沿って回り込ませ、スパンが大きいものは外側に配置。同じステージペア間のフィードバックが3本を超える場合は、1本の太線で束を表現(ラベルに
フィードバック ×N:…を記述)。 - 束ねる:同じ境界上のパイプラインレジスタが4個を超える場合は、1つのレジスタグループにまとめる(縦棒、ラベルは
S1→S2 レジスタ ×Nと総ビット幅)。 - 簡略化:同名のレジスタスライスをマージ。ネストされた if/case の MUX チェーンは1つの
N:1MUX に統合。1つの MUX 選択端/レジスタイネーブルのみを駆動する小さなデコードロジックは、それに統合して選択線/イネーブル線として描画。3個以内のゲートはAND/OR/NOT/XOR ゲート記号で描画し、それより大きいものは「組み合わせロジック」クラウドに折りたたむ(演算ユニットを挟む場所で分割し、誤ったループを回避)。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層の基本ユニット(比較器、ゲート、レジスタ)を透過してソースまで追跡。分類は信号名に依存するため、命名規則が不適切な場合は描画漏れ/過剰描画の可能性がある。
- 推奨手順:上から下へ。まずトップレベル(誰が誰を起動するか、誰がメモリとハンドシェイクするか)→ 次の階層(メイン制御 FSM がどのようにスケジュールするか)→ キー FSM の状態遷移図。
制限
- レイアウトは Graphviz が列制約に基づいて自動的に行うため、交差を完全に排除することはできません。微調整は draw.io 内でドラッグして行います(すべてがネイティブ編集可能オブジェクトです)。
- 大規模な制御系モジュール(数百ユニット)のブロック図は依然として乱雑になる可能性があるため、前述のように状態遷移図/制御構造図を使用するか、サブモジュールごとに描画します。
- 状態遷移図の円や弧は座標に基づいて直接描画されるため、状態をドラッグしても弧の折れ点は追従しません。その場合は手動で調整する必要があります。