はじめに
RTL を読む仕事は、書く仕事より多いと思っています。他人の(あるいは半年前の自分の)設計を渡されて、仕様書はあいまいで、ブロック図は無くて、always_ff の中に埋まった状態遷移を頭の中で図に起こしながら波形とにらめっこする。RTL からブロック図や仕様を「逆に」起こす作業は、実務ではかなりの頻度で発生します。
RTLScope は、その作業を助けるために作ったツールです。合成可能な SystemVerilog を読み込むと、ブロック図と状態遷移図を生成し、波形と突き合わせ、信号のトレースや CDC・ラッチ・段数の解析を、ぜんぶ 1 つの画面で確認できます。
Stim機能を使用することでGUI 上で波形を描いてシミュレーションを回すこともできます。
Rust 製で、Windows で開発しています。GUI・コマンドライン・MCP で操作が可能です。
リポジトリはこちらです: https://github.com/MameMame777/RTLScope
画面の全体像
8 ビットの小さな CPU(同梱サンプル cpu)を開いたところです。
- 左が Hierarchy(モジュールの入れ子)。テストベンチを読み込むと
simulation sourceの節が増えます - 右上が Diagram(選んだモジュールの中身のブロック図)
- 下がタブになっていて、Source / Wave / FSM / Trace / Pipeline / CDC / Lint / Diagnostics / Stim を切り替えます
ビューはすべてタブで、ドラッグして並べ替えたり、別ウィンドウに切り離したりできます。配置は次回起動時にも残ります。
| タブ | 見れるもの |
|---|---|
| Hierarchy | モジュールはどう入れ子になっているか |
| Diagram | このモジュールの中はどう繋がっているか |
| Source | その行はどこか。信号名をクリックすると図で光り、波形に載る |
| Wave | 録音で何が起きたか |
| FSM | 状態遷移図。状態をクリックすると case のアームがソースで指される |
| Trace | この信号は何から決まるか、何に影響するか |
| Pipeline | 何段あり、各段に何が載っているか |
| CDC / Lint / Diagnostics | クロック交差・ラッチ・読めなかった構文の一覧 |
| Stim | 波形を描いてシミュレーションにかける |
どのビューに出てくる file:line も、クリックすれば Source タブに繋がります。
サンプルが組み込まれている
インストール直後は開くものが無いので、14 個のサンプル設計をバイナリに埋め込んであります。GUI の open → a sample から選ぶか、名前で指定します。
rtlscope-gui --sample cpu
cpu は 7 ファイルに分かれた 8 ビットプロセッサで、隣に blink という別の設計も入っています。
2つのtop階層が混在した場合にどちらを top にするか選択可能です。
上部の tb: cpu_tb は、同梱のテストベンチが「設計とは別のもの」として読み込まれていることを示しています。
| サンプル | 特徴 |
|---|---|
hier |
3 階層と 3 種類の接続方法 |
cpu |
7 ファイルの 8 ビット CPU。top の選択、FSM、テストベンチの実行 |
pipeline3 |
3 段パイプラインが 3 つの箱に分かれる。録音付き |
fsm |
2 プロセスの FSM。録音付きで、カーソルの状態に輪が付く |
trace_demo |
「out_data はなぜこの値か」を Trace で遡る |
depth_demo |
「ここからそこまで何クロックか」。数えると見つかるバグ入り |
cdc latch comb_loop reconverge
|
それぞれの解析が何を捕まえるか |
axi4 |
AXI4 のトランザクションを波形からデコード |
counter fifo
|
Stim で刺激を描いて回す / ランダム刺激で回す |
veryl |
Veryl で書かれたプロジェクト。.veryl の行に飛ぶ |
ブロック図 — Diagram
SystemVerilog のファイルかフォルダを渡すと、まずブロック図が出ます。
ポートは両端の丸い箱、インスタンスは四角い箱、assign や always_comb の組合せ論理は雲、always_ff は flop + reset と書かれた箱です。線をクリックするとソースの該当行に飛び、波形が開いていればその信号が波形に載ります。
箱をダブルクリックすると、その中に入れます。ツールバーにパンくず(hier_top › u_ctrl)が出て、Hierarchy の対応する行も光ります。
パイプラインは、1 つの always_ff に 3 段が書かれていても、レジスタの隣接関係を読んで 3 つの箱に分けて描きます。
自分の値を読むカウンタには、出力から入力へ戻る線が引かれます(後述の counter の図で count[7:0] が箱の下を回っているのがそれです)。
状態遷移図 — FSM
case 文で書かれたステートマシンは、FSM タブが状態遷移図にします。cpu サンプルの control はこう描かれます。
元となったRTLです。
always_comb begin
next_state = state;
case (state)
FETCH: next_state = DECODE;
DECODE: next_state = EXECUTE;
EXECUTE: if (op == HLT) next_state = HALT;
else next_state = WRITEBACK;
WRITEBACK: next_state = FETCH;
HALT: next_state = HALT;
default: next_state = FETCH;
endcase
end
- 遷移の条件(
(op == HLT))が矢印に付きます -
resetの矢印はリセット時の初期状態を指します -
default:アームは「どの状態からでも」の矢印として描きます。描けなかった辺ではなく、入口です - 到達できない状態と出口の無い状態には色が付きます
- 状態は
typedef enumでもlocalparamでも読みます
状態をクリックすると、その case のアームがソースで指されます。遷移が多いときは list 表示のほうが読みやすいです。AXI4 マスタの 9 状態はこうなります。
波形 — Wave
VCD か FST をドロップすると波形が開き、設計と突き合わせます。設計を読んでいるので、enum 型の信号や FSM の state レジスタは数値ではなく名前で表示されます。
u_master.state が M_IDLE → M_AW → M_W → M_B → … と読めます。W_IDLE や R_DATA も同様です。0x4 のように名前の付いていない値は数値のまま出ます(ここでは localparam にない中間値)。
FSM 図と連動する
波形のカーソルを動かすと、FSM 図ではその時刻の状態に輪が付きます。輪の色はカーソルの色と同じです。
FSM タブのヘッダに at 90: S_IDLE と出ています。watch を押すと state レジスタが波形に載り、next ▸ で「選んだ状態に次に入る時刻」までカーソルが飛びます。
測る
← → で選んだ信号のエッジへ移動し、M でマーカーを置きます。2 つのマーカーの間の時間差(Δ)がヘッダに出ます。
パイプラインをサイクルに沿って並べる
stages… を押すと、Pipeline 解析が見つけた段を波形の下に並べ、各サイクルに どのデータがどの段にいるかを描きます。データが 1 サイクルずつ右の段へ流れていくのが見えます。
プロトコルをデコードする
decode… を押すと、信号名からバスを推定して(axi_awvalid axi_awready … があれば AXI4)候補を並べ、選んだバスのハンドシェイクをトランザクションにまとめます。結果は波形の下に レーンとして足されます。
-
axi4 read/writeのレーンには、AW+W+B を畳んだ書き込み、AR+R を畳んだ読み出しが 1 箱ずつ並びます。write 0x00000010 ×4 OKAYは「番地 0x10 に 4 ビートのバースト書き込み、応答 OKAY」です -
axi4 rbeat/wbeatのレーンは個々のデータビートで、赤い箱はエラー応答の付いたビートです - 箱にポインタを載せると全文が出ます。ここでは
read 0x00000080 ×1 SLVERR、スレーブが持っていない番地を読んだ結果です - ヘッダの
axi4: 70 clock edges, 2 writes, 4 readsが要約です
書き込み 1 回と読み出し 2 回が 2 周分入っています
VALID が READY を待たずに落ちた、待っている間に payload が変わった、といったプロトコル規則違反も同じレーンに印が付きます。
同じデコードはコマンドラインの rtlscope decode と MCP の decode ツールからも呼べます。
解析
Trace — この信号は何から決まるか
波形を見て「out_data はなぜこの値なのか」と思ったときに使うのが Trace です。信号をダブルクリックすると、その信号を駆動しているものが 1 段ずつ辿れます。
out_data は assign(組合せ)で staged から決まり、follow を押すと staged の来歴へ進みます。staged は always_ff のレジスタで、in_data がデータ、gate が条件。gate はインスタンス u_gate の出力で、そこへ入って allow を辿り、want はポートなので上へ戻り……と、設計の端まで行き着きます。
cone 表示に切り替えると、指定した深さまでの影響範囲をグラフで見せます。オレンジがレジスタを越える線、青が同じクロック内で決まる線です。
Pipeline — 何段あるか、何クロックかかるか
レジスタの隣接関係から段を数えます。pipeline3 なら clk に 3 段、各段に載っているレジスタが箱で並びます。
clocks from … to … に 2 つの信号を入れて count を押すと、その間のクロック数を答えます。depth_demo は「数えないと分からないバグ」のためのサンプルで、sample_in から stamped までを数えるとこうなります。
「3 または 4」。データは 3 段を通り、それを修飾するフラグは 2 段しか通らないので、stamped は 1 つ手前のサンプルの存在フラグで各サンプルを修飾しています。どちらの線も単独で見ると正しく、2 本を同時に頭に置いたときだけ間違っている、という種類のバグです。録音があれば measured: の行に実測も出ます。193 ビートすべてが 3 クロックだったので、「設計が実際に通る道は短いほう」と言っています。
CDC — クロックドメイン交差
クロックドメインを数え、ドメイン間を渡る信号を列挙します。認識するのは 2 段フロップのシンクロナイザだけで、それ以外は正しくても「認識できなかった」として並べます。
Questa CDCなどに比べるとお粗末ですが。
8 ビットの count_src が裸で渡っているのが multi-bit not recognised として捕まっています。非同期 FIFO やグレイコードは正しくても一覧に載ります。一覧は「確認すべきもの」であって「間違っているもの」ではない、という立場です。
Lint — ラッチ、死んだ信号、組合せループ
組合せ論理のすべてのパスで代入されない信号はラッチになります。if に else が無い、case に default が無い、が典型です。
latch_check.y_if this if has no else latch.sv:26:31 のように、理由と場所が付きます。読めなかった構文を含むプロセスは推測せずスキップします。
組合せループはコマンドラインの lint で報告されます(このループは Pipeline の段数カウントも拒否します)。
$ rtlscope lint comb_loop.sv
1 combinational loop(s)
A value that reaches itself without passing a clock has no settled
answer: synthesis refuses it, and simulation reports whichever order
it happened to evaluate in.
knot_a -> knot_b
knot_a at comb_loop.sv:22:11
knot_b at comb_loop.sv:23:11
Diagnostics — ツールでの読み込み結果の確認
systemVerilogの全文法には対応できていないです。
対象は合成可能サブセットで、interface や class、#遅延 や wait は読まずにスキップします。
ただしスキップしたことは必ず報告します。
RK0103 は「合成可能サブセットの外なので飛ばした」という警告、RK0206 は defparam のエラー(値が「どこから見るか」で変わるので対応しない)です。診断コードは固定で、RK09xx が出たら RTLScope 自身のバグです。
シミュレーション
Stim タブ:波形を描いて動作確認をする
テストベンチを書かずに「ちょっと動かして波形を見たい」ときのためのタブです。クロックは自動で入り、1 ビットの信号はセルをクリックするかドラッグして塗り、バスはクリックして値を打ちます。x はDon't Careです。
run を押すと、描いた波形から cocotb のテストベンチが生成され、Verilator で走り、結果の波形が戻ってきます。
count[7:0] に描いた値は期待値として扱われ、実際の値と食い違ったサイクルが count @6 count @7 … とボタンになって並びます。押すとその時刻にカーソルが飛びます。ステータスバーには simulated with cocotb on verilator と、どの道具で回したかが出ます。
自分のテストベンチをそのまま走らせる
テストベンチがあるなら、そちらをそのまま実行します。cpu サンプルには cpu_tb.sv が同梱されていて、run cpu_tb を押すと Verilator で走って cpu_tb.fst が開きます。
Hierarchy に simulation source の節ができ、cpu_tb → dut : cpu と読み込まれています。テストベンチに対して RTLScope が見るのは 2 点だけです。どのモジュールが top か(ポートの無いモジュール)と、波形を自分で書いているかどうか。「cpu_tb writes its own waveform; nothing was added to it」とある通り、ハーネスを被せることもダンプ命令を足すこともしません。
シミュレーションには外部ツール(Verilator、cocotb。--engine icarus で Icarus Verilog も)が必要で、別プロセスとして起動するだけです。図と解析だけなら Rust のみで動きます。
Veryl への対応
Veryl で書かれたプロジェクトは、Veryl.toml のあるフォルダを渡すと veryl build が走り、生成された SystemVerilog を解析します。Veryl が出す .sv.map(ブラウザの Source Map と同じ形式)を読むので、Source タブに出るのは書いた .veryl の行です。
show top.sv を押せば生成された側も見られます。
ソースへの往復と、操作の一覧
図の線、FSM の状態、Lint の項目、CDC の交差、どれをクリックしても Source タブがその行を開きます。open in editor で外部エディタ(既定は code -g {file}:{line}:{col})にも飛べます。
キー操作とジェスチャの一覧は help メニューにまとめてあります。
ダークとライトのテーマがあります(auto で OS に従います)。
コマンドライン
GUI と同じ解析を、テキスト・JSON・SVG で出します。CI で回したり、diff を取ったりする用です。
rtlscope diagram cpu/*.sv --top cpu -o cpu.svg # ブロック図を SVG に
rtlscope fsm cpu/*.sv --top cpu # 状態と遷移の一覧
rtlscope lint cpu/*.sv --top cpu
rtlscope cdc cpu/*.sv --top cpu
rtlscope pipeline cpu/*.sv --top cpu
rtlscope cone out cpu/*.sv --top cpu # `out` は何から決まるか
rtlscope sim cpu cpu/*.sv --top cpu # ランダム刺激で走らせて波形を開く
rtlscope sim cpu cpu/*.sv --top cpu --testbench cpu_tb.sv
$ rtlscope fsm cpu_pkg.sv imem.sv alu.sv regfile.sv control.sv datapath.sv cpu.sv --top cpu
1 state machine(s)
control.state (5 states, 7 transitions)
at control.sv:34:5
clock clk
next next_state
reset FETCH
transitions
FETCH -> DECODE
DECODE -> EXECUTE
EXECUTE -> HALT when (op == HLT)
EXECUTE -> WRITEBACK
WRITEBACK -> FETCH
HALT -> HALT
(any other) -> FETCH
$ rtlscope cdc cdc.sv
2 clock domain(s) over 2 clocked process(es)
1 dst_clk
1 src_clk
2 crossing(s): 1 into a two-flop synchroniser, 1 not recognised
no synchroniser recognised
src_clk -> dst_clk count_src [8 bit(s), multi-bit]
in u_child at cdc.sv:18:5
synchronised
src_clk -> dst_clk flag_src [1 bit(s), 2-flop]
in u_child at cdc.sv:18:5
コマンドは 17 個あります。rtlscope --help で一覧、--diag-format json で診断を機械可読にできます。
MCP サーバ — LLM に設計を読ませる
同じ解析結果を MCP(Model Context Protocol)サーバ経由で LLM にも渡せます。rtlscope-mcp を stdio で起動する定義を .mcp.json に置いておけば、Claude Code などのエージェントから modules / diagram / state_machines / cone / drivers / lint / pipeline_depth / path_latency / decode / yosys_check など 19 個のツールが呼べます。
files を省くと、GUI の窓で開いている設計が対象になります。窓を見ながら「この設計の out_data は何から決まる?」と聞ける、という使い方です。
たとえば cone を out_data について呼ぶと、こういう JSON が返ります。
{
"root": "out_data",
"towards": "drivers",
"nodes": [
{"signal": "out_data", "level": 0, "across_a_register": false},
{"signal": "staged", "level": 1, "across_a_register": false},
{"signal": "in_data", "level": 2, "across_a_register": true},
{"signal": "gate", "level": 2, "across_a_register": true},
{"signal": "in_valid", "level": 3, "across_a_register": true},
{"signal": "mode", "level": 3, "across_a_register": true},
{"signal": "u_gate.used", "level": 3, "across_a_register": true}
],
"edges": [
{"from": "staged", "through": "logic", "to": "out_data"},
{"from": "in_data", "through": "a register", "to": "staged"},
{"from": "gate", "through": "a register", "to": "staged"},
{"from": "in_valid", "through": "a register", "to": "gate"},
{"from": "mode", "through": "a register", "to": "gate"},
{"from": "u_gate.used", "through": "a register", "to": "gate"}
],
"clipped": 0
}
GUI の Trace タブで見た cone と同じ内容です。人が見る図と LLM が読む JSON が同じ中間表現から出ているので、答えが食い違いません。モデル化できなかったものは diagnostics ツールで確認できるので、LLM が「読めていないところ」を推測で埋めることも防げます。
設計思想 — 1 つの IR、複数のビュー
RTLScope のビューはどれも、パースとエラボレーションの結果である 1 つの中間表現(IR) を見ています。ブロック図も FSM も、波形の信号名の付け方も Lint も、同じ IR への別の問いです。だから図の線をクリックすると波形に出て、波形の行を選ぶと図が光る、という行き来で答えが食い違わない。CLI と MCP も同じ IR を返します。
理解する範囲
対応するのは module(ANSI / 非 ANSI)、整数の parameter、generate for/if、always_ff / always_comb / always_latch、assign、if / case / casez、function / task(呼び出し箇所に展開)、定数境界の for / while、initial(電源投入値として)、packed 範囲、1 次元の unpacked 配列、typedef enum、パッケージ、$clog2、サイズキャストです。
対応しないのは interface / class / program、struct / union、#遅延 / wait / forever、階層参照 a.b.c、多次元配列、SVA、DPI です。defparam はエラーになります。casex も読みません(x を暗黙のワイルドカードにするのは合成では危険なので)。
テストベンチを「設計として」読ませると、そこで止まります。テストベンチは tb: として別に読み込んでください。
使いはじめる
cargo build --release
rust-toolchain.toml が Rust 1.93.1 を指しているので、rustup があれば自動で揃います。target/release/ に rtlscope(CLI)、rtlscope-gui(GUI)、rtlscope-mcp(MCP サーバ)ができます。
rtlscope-gui --sample hier # まず動かす
rtlscope-gui path/to/rtl/ # フォルダごと開く
rtlscope-gui top.sv sub.sv --top top
rtlscope-gui top.sv --dump sim.vcd # 波形と一緒に
Windows 向けにはインストーラ(WiX)もあり、.sv .v .vcd .fst を「プログラムから開く」の候補に追加します。
| 用途 | 必要なもの |
|---|---|
| ビルド、図、解析 | Rust だけ |
| シミュレーション | Verilator(既定)、cocotb。--engine icarus なら Icarus Verilog |
yosys-check |
Yosys。IR とネットリストを突き合わせるときだけ |
.veryl プロジェクト |
Veryl |
おわりに
RTLの理解を助けるためのツールを作成しました。
波形ビューアは波形しか見せず、合成ツールの図は合成後の姿で、状態遷移図は手で描くしかない。RTLScope は、それらを 同じソースから、同じ IR で、1 つの窓に並べることを狙ったものです。
リポジトリ: https://github.com/MameMame777/RTLScope
MIT / Apache-2.0 のデュアルライセンスです。
コーディング、およびこの文章の作成にはclaude codeを使用しています。
























