CommunityCoding & Developmentgithub.com

BeenLi/claude-skills

Convert Verilog/SystemVerilog (as well as papers, patents, and verbal descriptions) into editable draw.io diagrams across four views: architecture block diagram, structural block diagram, state machine, and control structure.

What is claude-skills?

This skill automates the generation of structured, editable draw.io diagrams from hardware design sources. It primarily targets Verilog/SystemVerilog code but also accepts papers, patents, and verbal descriptions. The tool offers four complementary perspectives: a high-level architecture block diagram (default) that captures the design's core ideas using pipeline stages, SRAM tables, and classic circuit symbols; a detailed structural block diagram derived from Yosys netlists for gate-level datapath analysis; a state machine diagram that parses `case(state)` logic into evenly spaced circles; and a control structure diagram that focuses solely on control signal connectivity between FSMs and modules. It supports multiple flows, feedback arcs, and customizable styling for publication-quality figures. The output is fully editable in draw.io, and the tool can also export PNG/SVG. It integrates with drawio-skill for layout and validation, and accepts optional hints for layout refinements.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/BeenLi/claude-skills/tree/HEAD/skills/verilog-diagram

Installed? Explore more Coding & Development skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Verilog → draw.io: Architecture / Structure / State Machine / Control Structure

Four perspectives, choose based on the question: What is the design idea? (For explaining to people, for papers/reports) → arch2drawio.py (default); What does the gate level look like? (Unit-by-unit datapath) → rtl2drawio.py; How does it step? (Single FSM) → fsm2drawio.py fsm; Who commands whom? (Overall control structure) → fsm2drawio.py ctrl. For control-dominated modules (FSM, decoder, arbiter), block diagrams become messy; use the latter two instead.

Dependencies: PyYAML; export to PNG/SVG requires drawio (lookup order: $DRAWIO_BIN → PATH → macOS app → ~/.local/opt/drawio; on Linux without $DISPLAY, xvfb-run is used automatically; on servers without root, use tools/install-drawio-linux.sh from the repository). Without an exporter, all commands still produce .drawio files, only missing PNG, with a single stderr hint. Block and control diagrams additionally require yosys, sv2v (for SystemVerilog), dot (Graphviz), and an installed drawio-skill (default ~/.claude/skills/drawio-skill, overridable by env var DRAWIO_SKILL_DIR; only uses its autolayout.py, does not modify it). All three generating scripts accept --png / --svg / --scale N (PNG default scale is 3, approximately 1 model unit = 3 px; $DRAWIO_SCALE can change the default). For separate export: python3 $S/scripts/drawio_export.py X.drawio --png --svg. Prefer SVG for Markdown/Obsidian (no blur). $S refers to this skill's directory. examples/ contains two comparison samples: wrr_arbiter.v (single-stage + state register) and load_pipe.v (multi-stage pipeline with replay).

0. Architecture Block Diagram (arch2drawio.py) – Default

Input is code (or a paper/patent/user description). Yosys netlists are too detailed for architecture diagrams, so we do not synthesize here: the script produces a source outline, you (the agent) read the code, extract the core, and write a diagram description, which the script then deterministically lays out. The user does not need to write any description file; only show the diagram to the user, unless the user wants to modify the description themselves.

python3 $S/scripts/arch2drawio.py outline src/*.sv --top gpu [--module core]   # Source outline (no synthesis)
python3 $S/scripts/arch2drawio.py render  fig.yaml -o diagrams/arch_core.drawio --png

outline lists: the hierarchy tree (generate replicates ×N), ports, storage arrays (depth × width, packed struct fields, same-depth arrays hint "possibly same table"), sequential registers named by stage (s1_ / stage2_ / if_id_ …), state machines, suspected feedback signals (replay/kill/flush/stall/forward/refill…), inter-instance data flow (via shared signals, with clk/reset/loop variables removed). This step is skipped when input is a paper/patent/description.

Drawing Rules (follow when writing descriptions)

  1. Draw only the core: First describe the core design idea in one or two sentences; retain only components that embody it. Omit glue logic, clk/reset, debug ports, and scattered control. Generally 8–20 boxes; if more, split into two diagrams (one top-level + one for the key unit).
  2. Highlight emphases: Mark 1–3 key design points with hl: true (contrast color: dark background, white text) or bold: true (bold). Key path lines can also use | hl. Use groups + hl: true (light background box) to emphasize a region.
  3. Do not number every component: Only number core components with num: N when text references are needed, and include explanations in callouts.
  4. Keep unit boxes concise: Box content is only the name (≤ 2 lines). Meaning is conveyed by shape: use adder / mult / cmp / op circular symbols or alu for operations; mux for selection (select input x.sel enters from bottom); table for storage; fifo for queues; reg for registers; and/or/xor/not for gates.
  5. Write individual entry fields in SRAM tables: For table, write each field's meaning in fields (e.g. [V, Tag (20b), Data (32b)]). Use stack: 4 for multi-way set-associative (stacked + ×4). Merge parallel arrays of the same depth and address into one table, with each array as a field.
  6. Pipelines: stages defines pipeline stages (id + function sub, e.g., {id: S1, sub: hit check}). The script draws dashed stage dividers and stage names. Inter-stage registers/FIFOs use between: [S0, S1], and the script places them on the stage boundary. Feedback (target is an earlier stage) automatically loops back along the bottom corridor, or use | fb, | fb via=top (loop from above). Multiple pipelines use lanes, sharing column positions by stage; corresponding stages across lanes are automatically aligned.
  7. For non-pipelined block-level diagrams (top-level, SoC), omit stages. Use stack: N for replicated units, and groups to enclose per-thread/per-core resources.

Description Format (YAML)

title: dcache_pipe: Direct-mapped D-Cache Lookup Pipeline
stages: [{id: S0, sub: Select / Read Array}, {id: S1, sub: Hit Check}, {id: S2, sub: Miss / Replay}]
lanes:  [{id: ld, name: Load Pipeline}, {id: st, name: Store Pipeline}]     # Optional, for multiple pipelines
nodes:
  - {id: arr, type: table, label: "Cache Array · 64 Entries", fields: [V, "Tag (20b)", "Data (32b)"], stage: S0}
  - {id: r1, type: reg, label: addr, between: [S0, S1]}              # Falls on the S0|S1 boundary
  - {id: tcmp, type: cmp, label: Tag Compare, stage: S1, hl: true}      # Circle "=", description below
  - {id: rmux, type: mux, stage: S0, ports: 2}
edges:
  - "rmux => arr : idx | w=6"            # -> data  => bus (thick)  ~> control (dashed); w=N bit width
  - "rpl -> rmux.sel : replay | fb hl"   # .sel/.en enters from bottom, .top from top; fb feedback, hl highlight, via=top, pos=src|dst
groups: [{label: "×4 per thread", members: [rf, alu, lsu], hl: true}]
legend: {reg: Pipeline register, fb: Feedback, ctrl: Control, hl: Key design}      # Usable keys: data bus ctrl fb hl hl_edge hl_region reg
callouts: {1: "Number only when reference is needed"}

Node types: 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. Common attributes: stage / lane / between, hl bold num stack, fine-tuning with row / col (row/column within a stage, can be negative), dx / dy, w / h. Misc: array supports cell_labels (per-cell labels, e.g., T0..T3) and cell_h (rectangular cells for thread channels); groups supports stack: N (stack the whole region, e.g., "Core ×2" with internals exposed) and label_align: right; leaving lanes' name empty = only splits, no label; edges with | both draw bidirectional arrows (request/response); a feedback between two units in the same column is drawn as a tight vertical short line (e.g., Dispatcher ⇅ Core).

Procedure

  1. outline + read key source code (or read paper/patent), write down the core idea → decide which layer to draw and what are the highlights.
  2. Write the description to a scratchpad directory. Give each node a stage (if stages exist). Automatically break loops: nodes whose names resemble controllers (sched / ctrl / fsm / arbiter / dispatch / scheduler / controller …, followed by high-fanout control wires) start first; their outputs go right, and edges returning to them become feedback. If heuristic is wrong, explicitly mark that edge with | fb. Optional: Composition reference (gpt-image-bridge, requires user confirmation per image) — The grid layout ensures correct connections but does not "compose." When modules ≥ 10 or there are clear partitions (control plane / data plane / memory hierarchy), you can first output a draw.io baseline, then write the module list and connections derived from code into the prompt (paper illustration style, white background, single accent color, three-section layout, minimal text, short English labels). Use gpt-image-bridge to generate 1 sketch and Read it. Only borrow layout and visual hierarchy (partitioning, what goes above what, arrangement, whitespace, key positions). Convert to lane / row / col / groups. Modules, connections, and names must all be based on code — common sketch mistakes: connecting to the wrong source, missing/extra modules, dark backgrounds. Before generation, list the images (purpose, path, size, prompt summary) per this skill and wait for explicit user consent. Save sketches as diagrams/sketch_*.png, with a comment at the top of the description file referencing it.
  3. render --png → validate.py (error must be 0; overlap warnings for stage divider lines crossing stage headers/registers are by design) → Read PNG for visual inspection. Fine-tune with row / col for at most two rounds (common: make the main datapath occupy row 0 for a straight line; put bypass/control on the next row).
  4. Deliver .drawio + .png. Leave the description file in the scratchpad; only give it to the user if they want to modify it themselves.

Examples: tiny-gpu top-level ~/hardware-project/tiny-gpu/diagrams/arch_gpu.* (composition guided by sketch sketch_gpu_top.png, compared to unsketched baseline arch_gpu_baseline.png); examples/dcache_pipe.v → dcache_pipe.arch.yaml (description written from code) → .png; arch_dual_issue.yaml (dual pipeline alignment + forwarding + numbered annotations), arch_mac.yaml (circuit level: multiply / add symbols, accumulate feedback, MUX select input). Block diagram of tiny-gpu core: ~/hardware-project/tiny-gpu/diagrams/arch_core.*.

1. Structural Block Diagram (rtl2drawio.py, Gate-Level Details)

python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --list                     # List modules and their unit composition
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --module alu -o diagrams/alu.drawio --png

Procedure: sv2v (automatic for .sv files) → Yosys proc; opt; opt_dff; wreduce; memory -nomap → JSON netlist → unit-level pipeline analysis → simplification → column-constrained layout. After generation, run python3 ~/.claude/skills/drawio-skill/scripts/validate.py X.drawio (error must be 0; through-vertex / overlap warnings for stage dividers crossing registers on boundaries are by design), then Read PNG for visual inspection.

Layout Rules (--layout pipeline; default auto: for hierarchical modules dominated by submodule instances, use free layout; otherwise pipeline layout)

  • Inputs on the left, outputs on the right, logic arranged left to right by combinational depth. Multi-stage pipelines go S0 → Sn left to right, with dashed dividers and stage labels in the column header.
  • Two register types:
    • Pipeline registers (narrow vertical bars) placed on stage boundaries. A row of registers only connected to output ports at the end is an output register (not a stage, no divider drawn).
    • State registers (FSM / counter / pointers: outputs only go through combinational logic back to their own input) drawn inside the stage, with outputs as feedback.
    • When register names include stage numbers (e.g., s2_x, x_s2, stage2_x, if_id_/id_ex_/ex_mem_/mem_wb_, at least two types) the name takes precedence — valid bits with kill are still pipeline registers even with combinational loops. For designs not named by stage, inference is structural; input arrival times are unknown, so stage numbers may be too early.
  • Stage number inference: Start from registers with stage numbers. A unit's stage = max stage of its register operands. Logic driven only by input ports is assigned to the stage of the register that captures it (ALAP); corresponding input ports are also placed at the beginning of that stage (e.g., TLB response placed in S1).
  • Feedback: Signals that enter the input cone of a stage-numbered register but can only come from a later stage (replay→S0, kill→S1), along with state register loops, are drawn as red lines looping back along the outer bottom edge, with wider-spanning lines on the outside. When there are more than 3 feedbacks between the same pair of stages, draw one thick line representing the bundle (label it Feedback ×N: …).
  • Bundling: When there are more than 4 pipeline registers on the same boundary, merge them into one register group (vertical bar labeled S1→S2 Registers ×N and total bit width).
  • Simplification: Merge slices of same-named registers. Combine MUX chains from nested if/case into a single N:1 MUX. Inline small decode logic that only drives one MUX select/enable, drawing it as a select/enable line. Draw up to 3 gates as AND/OR/NOT/XOR gate symbols; larger gates fold into a "combinational logic" cloud (which is cut at arithmetic units to avoid false loops). Fold generate loop-expanded units of the same origin (same source location) and threads[i].x instances into "×N" (with shadow). All merging is done only within the same stage. clk/reset are hidden by default.
  • Wires: solid = data (thick = multi-bit bus, /N = bit width); purple dashed = MUX select; blue dotted = enable; red = feedback.

Parameters

ParameterFunction
--listList modules and unit composition, then exit
--module MDraw a specific module (parameterized variant draws the first; will prompt)
--layout auto|pipeline|freeDefault auto: if there are submodule instances and combinational logic nodes are fewer than instances (top-level glue), use free (bidirectional inter-block communication is not pipeline feedback); otherwise pipeline
--show-clockDraw clk/reset
--no-foldDo not fold generate instances / same-origin units
--no-collapseDo not collapse control/combinational logic
--sv2v auto|always|neverSV front-end (syntaxes like unpacked array ports require sv2v)
--keep-json PATHKeep the Yosys JSON netlist

2. State Machine Diagram (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]
  • Direct source code parsing, transition conditions preserve original expressions. Recognizes assignments to S (or the next-state variable N via S <= N) inside case (S) branches. Uses localparam names for states. Constants comparing other FSM state variables are translated to the other's state name (e.g., core_state == 3'b001 → core_state == FETCH).
  • Transition conditions = the if/case/for directly enclosing the state assignment; else if only writes its own condition; conditions inside for are written as ∃j: …; an outer for i wrapping the entire case indicates replication by i. Guards (enable, read/write branches) outside the case go into a comment box. Local boolean variables set to 1 in a loop are defined in the comment box.
  • Drawing method: Each state is an equal-sized circle (name wraps at _, below in small text are encoding and ⟳), final state double circle, black dot = reset entry. When the main flow covers most states, arrange in a U-shaped loop (top row left→right, turn down, bottom row right→left). For branch types, arrange evenly in columns by step distance from the initial state. Adjacent states are directly connected. Transitions skipping other states arch over from above/below, label at the arch top. A↔B round trips each curve to one side. Explicit self-loops are drawn above the circle. Column spacing auto-adjusts to the longest label. Assignments on transition (after /) show at most 2.
  • Not recognized: controls without case(state) structure (counter-based, purely combinational decode) — these will not appear in list, which is expected.

3. Overall Control Structure (fsm2drawio.py ctrl)

python3 $S/scripts/fsm2drawio.py ctrl src/*.sv --top gpu --module core -o diagrams/ctrl_core.drawio --png
  • Draws submodule instances within module L (generate instances folded ×N), retaining only control signals: Orange thick lines = FSM state broadcast; Blue lines = valid/ready handshake; Purple dashed = start/done/enable/select/reset, etc. Data buses are omitted. Instance boxes containing FSMs list the state sequence; sub-hierarchies with FSMs are marked "Contains FSM."
  • Control signals are traced through at most 3 layers of basic units (comparators, gates, registers) to find the source. Classification relies on signal names; non-standard naming may result in missing/extra signals.
  • Suggested top-down order: Start with top-level (who starts whom, who handshakes with memory) → next level (how the main controller FSM schedules) → the state machine diagram of key FSMs.

Limitations

  • Layout is automatically done by Graphviz with column constraints; crossings cannot be fully eliminated. Fine-tune by dragging in draw.io (all objects are native and editable).
  • Block diagrams for large control-oriented modules (hundreds of units) will still be messy. Use the state machine / control structure diagram methods instead, or draw each submodule separately.
  • The circles and arcs in state machine diagrams are drawn directly based on coordinates. Draging a state will not automatically follow the arc's control points; you will need to adjust them manually.

Related Skills