Verilog → draw.io:Architekturdiagramm / Strukturblockdiagramm / Zustandsmaschine / Steuerungsstruktur
Vier Perspektiven, je nach Frage auswählen: Designgedanke (für Präsentationen, Paper/Reports) → arch2drawio.py (Abschnitt Null, Standard);
Gatterebene (Datenpfad pro Einheit) → rtl2drawio.py; Ablauf (einzelner FSM) → fsm2drawio.py fsm;
Wer steuert wen (gesamte Steuerungsstruktur) → fsm2drawio.py ctrl. Bei steuerungsorientierten Modulen (FSM, Decoder, Arbitrierungs-Controller) wird das Blockdiagramm unübersichtlich; dann lieber die letzten beiden verwenden.
Abhängigkeiten: PyYAML; Export nach PNG/SVG benötigt drawio (Suchreihenfolge $DRAWIO_BIN → PATH → macOS App → ~/.local/opt/drawio;
unter Linux ohne $DISPLAY wird automatisch xvfb-run verwendet, für Server ohne Root-Rechte gibt es tools/install-drawio-linux.sh im Repository). Ohne Export-Tool werden alle Befehle normal .drawio-Dateien erzeugen, nur ohne PNG, mit einem Hinweis in stderr. Blockdiagramm/Steuerungsstruktur benötigen zusätzlich yosys, sv2v (für SystemVerilog), dot (Graphviz) sowie das installierte drawio-skill
(standardmäßig ~/.claude/skills/drawio-skill, überschreibbar mit Umgebungsvariable DRAWIO_SKILL_DIR; es verwendet nur dessen autolayout.py, ohne es zu modifizieren).
Alle drei Generierungsskripte akzeptieren --png / --svg / --scale N (Standard-Scale für PNG ist 3, ca. 1 Model Unit = 3 px; $DRAWIO_SCALE kann den Standard ändern),
auch separater Export möglich: python3 $S/scripts/drawio_export.py X.drawio --png --svg. Für Markdown/Obsidian bevorzugt SVG, da nicht unscharf.
$S bezeichnet das Skill-Verzeichnis. examples/ enthält zwei Vergleichsbeispiele: wrr_arbiter.v (einstufig + Zustandsregister), load_pipe.v (mehrstufige Pipeline mit Replay).
Null、Architekturdiagramm im Paper-Stil (arch2drawio.py) — Standard
Eingabe ist Code (oder Paper/Patent/Benutzerbeschreibung). Yosys-Netlisten sind zu detailliert für Architekturdiagramme, daher wird hier nicht synthetisiert: Das Skript gibt eine Quellcodegliederung aus, und Sie (der Agent) lesen den Code, extrahieren das Wesentliche und schreiben eine Diagrammbeschreibung, die das Skript dann deterministisch in ein Diagramm layoutet. Der Benutzer muss keine Beschreibungsdatei schreiben; er sieht nur das Diagramm, es sei denn, er möchte die Beschreibung selbst ändern.
python3 $S/scripts/arch2drawio.py outline src/*.sv --top gpu [--module core] # Quellcode-Gliederung (ohne Synthese)
python3 $S/scripts/arch2drawio.py render fig.yaml -o diagrams/arch_core.drawio --png
outline listet: Hierarchiebaum (generate-Vervielfältigungen ×N), Ports, Speicher-Arrays (Tiefe × Breite, Felder von packed structs, Arrays gleicher Tiefe als Hinweis auf „möglicherweise gleiche Tabelle“),
nach Stufen benannte sequentielle Register (s1_ / stage2_ / if_id_ …), Zustandsmaschinen, mutmaßliche Rückkopplungssignale (replay/kill/flush/stall/forward/refill…),
Datenflüsse zwischen Instanzen (über gemeinsame Signale, bereits ohne clk/reset/Schleifenvariablen). Bei Paper/Patent/Beschreibungseingabe wird dieser Schritt übersprungen.
Zeichenregeln (beim Schreiben der Beschreibung beachten)
- Nur das Wesentliche zeichnen: Zuerst den Kerngedanken des Designs in ein bis zwei Sätzen beschreiben, nur die Komponenten erhalten, die ihn widerspiegeln; Klebelogik, clk/reset, Debug-Schnittstellen, vereinzelte Steuerung weglassen. In der Regel 8–20 Kästchen; bei mehr auf zwei Blätter aufteilen (ein Top-Level-Blatt + ein Blatt für die Schlüsseleinheit).
- Schwerpunkte setzen: 1–3 wichtige Designpunkte mit
hl: true(Kontrastfarbe: dunkler Hintergrund, weiße Schrift) oderbold: true(fett); auch Leitungen auf kritischen Pfaden können mit| hlmarkiert werden. Wenn ein Bereich hervorgehoben werden soll,groups+hl: true(heller Hintergrundrahmen) verwenden. - Nicht jede Komponente nummerieren: Nur bei Kernkomponenten, die einer textuellen Erläuterung bedürfen,
num: Nsetzen und incalloutserklären. - Kästchenbeschriftungen kurz und prägnant: In das Kästchen nur den Namen schreiben (≤ 2 Zeilen), die Bedeutung wird durch die Form ausgedrückt: Rechenoperationen mit
adder/mult/cmp/op(Kreissymbole) oderalu, Auswahl mitmux(Auswahl-Eingangx.selvon unten), Speicher mittable, Warteschlangen mitfifo, Register mitreg, Gatter mitand/or/xor/not. - SRAM-Tabelle: Felder eines einzelnen Eintrags ausschreiben: Bei
tablemitfieldsdie Bedeutung jedes Felds angeben (z. B.[V, Tag (20b), Data (32b)]), mehrwege-assoziativ mitstack: 4(gestapelt + ×4); parallele Arrays gleicher Tiefe und Adresse zu einer Tabelle zusammenfassen, jedes Array als ein Feld. - Pipeline:
stagesdefinieren die Stufen (id+ Funktionsub, z. B.{id: S1, sub: Treffererkennung}), das Skript zeichnet gestrichelte Trennlinien und Stufennamen; Zwischenstufenregister / FIFOs mitbetween: [S0, S1]angeben, das Skript platziert sie auf der Trennlinie; Rückkopplungen (Ziel in einer früheren Stufe) werden automatisch über einen unteren Korridor zurückgeführt, auch mit| fboder| fb via=top(von oben). Mehrere Pipelines mitlanesangeben, die Lanes teilen sich die Spalten der Stufen, zeitlich korrespondierende Stufen werden automatisch ausgerichtet. - Für blockweise Diagramme ohne Stufen (Top-Level, SoC) kein
stagesschreiben; vervielfältigte Einheiten mitstack: N, Ressourcen pro Thread/Kern mitgroupsumrahmen.
Beschreibungsformat (YAML)
title: dcache_pipe:Direct-Mapped D-Cache Lookup Pipeline
stages: [{id: S0, sub: Auswahl / Array-Lesen}, {id: S1, sub: Treffererkennung}, {id: S2, sub: Fehlzugriff / Wiederholung}]
lanes: [{id: ld, name: Load-Pipeline}, {id: st, name: Store-Pipeline}] # optional, bei mehreren Pipelines
nodes:
- {id: arr, type: table, label: "Cache-Array · 64 Einträge", fields: [V, "Tag (20b)", "Data (32b)"], stage: S0}
- {id: r1, type: reg, label: addr, between: [S0, S1]} # liegt auf der Trennlinie S0|S1
- {id: tcmp, type: cmp, label: Tag-Vergleich, stage: S1, hl: true} # Kreissymbol "=", Erläuterung darunter
- {id: rmux, type: mux, stage: S0, ports: 2}
edges:
- "rmux => arr : idx | w=6" # -> Daten => Bus (dick) ~> Steuerung (gestrichelt);w=N Bitbreite
- "rpl -> rmux.sel : replay | fb hl" # .sel/.en von unten, .top von oben;fb Rückkopplung, hl Hervorhebung, via=top, pos=src|dst
groups: [{label: "Pro Thread ×4", members: [rf, alu, lsu], hl: true}]
legend: {reg: Pipeline-Register, fb: Rückkopplung, ctrl: Steuerung, hl: Schlüsseldesign} # mögliche Schlüssel: data bus ctrl fb hl hl_edge hl_region reg
callouts: {1: "Nur nummerieren, wenn eine erläuternde Beschreibung nötig ist"}
Knotentypen: 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.
Allgemeine Attribute: stage / lane / between, hl bold num stack, Feintuning mit row / col (Zeile / Spalte innerhalb der Stufe, auch negativ), dx / dy, w / h.
Sonstiges: array unterstützt cell_labels (Zellen-Beschriftungen, z. B. T0..T3) und cell_h (rechteckige Zellen, für Thread-Kanäle); groups unterstützt stack: N
(gesamter Bereich gestapelt, z. B. „Core ×2“ mit sichtbarem Inneren) und label_align: right;lanes mit leerem name = nur Spur, keine Beschriftung;
Kanten mit | both zeichnen bidirektionale Pfeile (Anfrage/Antwort); Rückkopplung zwischen zwei übereinanderliegenden Einheiten derselben Spalte als enge kurze vertikale Linie (z. B. Dispatcher ⇅ Core).
Ablauf
outline+ Lesen des relevanten Quellcodes (oder Lesen von Paper/Patent), Kerngedanke notieren → entscheiden, welche Ebene gezeichnet wird, was die Schwerpunkte sind.- Beschreibung in ein temporäres Verzeichnis (Scratchpad) schreiben, jedem Knoten
stagezuweisen (wenn vorhanden). Schleifen automatisch auflösen: Knoten, deren Namen wie Controller klingen (sched / ctrl / fsm / arbiter / dispatch / 调度 / 控制 …, gefolgt von Knoten mit vielen ausgehenden Steuerleitungen), beginnen; ihre Ausgänge gehen nach rechts, Kanten, die zu ihnen zurückführen, werden als Rückkopplung gezeichnet; bei falscher Erkennung für diese Kante explizit| fbangeben. Optional: Layout-Referenz (gpt-image-bridge, erfordert Benutzerbestätigung pro Bild) – der Grid-Layouter stellt korrekte Verbindungen sicher, aber er „komponiert“ kein Layout. Bei Modulen ≥ 10 oder mit offensichtlichen Partitionen (Steuerungsebene/Datenebene/Speicherhierarchie) kann zuerst eine draw.io-Baseline erstellt werden und dann die aus dem Code gewonnene Modulliste und Verbindungen in den Prompt geschrieben werden (Paper-Abbildungsstil, weißer Hintergrund, einzelne Akzentfarbe, Drei-Bereichs-Layout, extrem wenig Text, englische Kurzbeschriftungen), um mit gpt-image-bridge 1 Skizze zu generieren und zu lesen. Nur Layout und visuelle Hierarchie übernehmen (Bereiche, wer über wem, Formation, Weißraum, Position der Schwerpunkte); inlane/row/col/groupsumrechnen; Module, Leitungen und Namen müssen ausschließlich dem Code entsprechen – häufige Fehler in Skizzen: falsche Quellen verbunden, zu viele/wenige Module gezeichnet, dunkler Hintergrund. Vor der Generierung gemäß dieser Skill-Regel die Bilder (Zweck, Pfad, Größe, Prompt-Zusammenfassung) auflisten und auf explizite Zustimmung des Benutzers warten; Skizze alsdiagrams/sketch_*.pngspeichern, in der Beschreibungsdatei im Kopfkommentar darauf verweisen. render --png→validate.py(error muss 0 sein; Warnungen zu durch Stufentrennlinien/Registerüberlappungen sind beabsichtigt) → PNG lesen und visuell prüfen, mitrow/colmaximal zwei Runden Feinjustierung (üblich: Hauptdatenpfad in row 0 gerade durchziehen, Nebenpfad/Steuerung in die nächste Zeile).- Ausliefern
.drawio+.png; Beschreibungsdatei im Scratchpad belassen, nur herausgeben, wenn der Benutzer sie selbst ändern möchte.
Beispiele: tiny-gpu Top-Level ~/hardware-project/tiny-gpu/diagrams/arch_gpu.* (Layout angeleitet durch Skizze sketch_gpu_top.png,
Vergleich mit arch_gpu_baseline.png ohne Skizze); examples/dcache_pipe.v → dcache_pipe.arch.yaml (aus Code geschriebene Beschreibung) → .png; arch_dual_issue.yaml (Zwei-Pipeline-Ausrichtung + Vorwärtspfad +
nummerierte Anmerkungen), arch_mac.yaml (Schaltkreis-Ebene: Multiplikations-/Additionssymbole, Akkumulationsrückkopplung, MUX-Auswahl-Eingang). Blockdiagramm des tiny-gpu Cores siehe
~/hardware-project/tiny-gpu/diagrams/arch_core.*.
Eins、Strukturblockdiagramm (rtl2drawio.py, Gatterebene Details)
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --list # Module und deren Einheiten
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --module alu -o diagrams/alu.drawio --png
Ablauf: sv2v (automatisch bei .sv) → Yosys proc; opt; opt_dff; wreduce; memory -nomap → JSON-Netlist → Einheiten-Pipeline-Analyse → Vereinfachung → Spalten-basiertes Layout.
Nach der Generierung python3 ~/.claude/skills/drawio-skill/scripts/validate.py X.drawio ausführen (error muss 0 sein; Warnungen zu durch Stufentrennlinien/Registerüberlappungen sind beabsichtigt),
dann PNG lesen und visuell prüfen.
Layout-Regeln (--layout pipeline; Standard auto: Hierarchische Module mit überwiegend Submodul-Instanzen verwenden freies Layout, sonst Pipeline-Stufen-Layout)
- Eingänge links, Ausgänge rechts, Logik von links nach rechts nach Kombinationstiefe spalten; mehrstufige Pipelines von S0 → Sn links nach rechts, gestrichelte Trennlinien, Spaltenkopf mit Stufennummer.
- Zwei Arten von Registern:
- Pipeline-Register (schmaler vertikaler Balken) auf die Stufentrennlinie setzen; die letzte Reihe, die nur Ausgangsports treibt, ist das Ausgangsregister (zählt nicht als Stufe, keine Trennlinie).
- Zustandsregister (FSM / Zähler / Zeiger: Ausgang geht nur über kombinatorische Logik zurück zu sich selbst) innerhalb der Stufe zeichnen, Ausgang als Rückkopplung.
- Wenn Registernamen eine Stufennummer enthalten (
s2_x,x_s2,stage2_x,if_id_/id_ex_/ex_mem_/mem_wb_, und mindestens zwei verschiedene) gilt der Name – ein Valid-Bit mit Kill, selbst mit kombinatorischer Schleife, ist dennoch ein Pipeline-Register. Wenn das Design nicht nach Stufen benennt, kann nur durch Struktur inferiert werden; die Ankunftszeit der Eingangsports ist unbekannt, die Stufennummer kann zu früh geschätzt werden.
- Stufenschätzung: Ausgehend von Registern mit Stufennummer, die Stufe einer Einheit = spätestes ankommendes Register-Operand (Maximum); nur von Eingangsports getriebene Logik wird der Stufe zugeordnet, die sie abtastet (ALAP), der zugehörige Eingangsport wird ebenfalls an den Anfang dieser Stufe gesetzt (z. B. TLB-Antwort in S1).
- Rückkopplung: Signale, die in die Eingangskonus eines Registers mit Stufennummer eintreten, aber nur von späteren Stufen kommen können (replay→S0, kill→S1), sowie Rückkopplungen von Zustandsregistern, werden als rote Linie entlang des unteren Außenkorridors zurückgeführt, weit auseinanderliegende außen; bei mehr als 3 Rückkopplungen zwischen demselben Stufenpaar nur eine dicke Linie als Bündel zeichnen (Beschriftung
Rückkopplung ×N: …). - Bündelung: Wenn mehr als 4 Pipeline-Register auf derselben Trennlinie liegen, zu einer Registergruppe zusammenfassen (vertikaler Balken mit Beschriftung
S1→S2 Register ×Nund Gesamtbitbreite). - Vereinfachung: Gleichnamige Register-Slices zusammenführen; MUX-Ketten aus verschachtelten if/case zu einem
N:1MUX zusammenfassen; kleine Dekodier-Logik, die nur einen MUX-Auswahleingang / Register-Enable treibt, integrieren und als Auswahl-/Enable-Leitung zeichnen; bis zu 3 Gatter als UND/ODER/NICHT/XOR-Gatter-Symbole zeichnen, größere zu einer „Kombinatorik“-Wolke zusammenfalten (wird an Recheneinheiten aufgeschnitten, um falsche Schleifen zu vermeiden); generate-Schleifen entfaltete gleichartige (gleiche Quellcode-Position) Einheiten undthreads[i].x-Instanzen zu „×N“ zusammenfalten (mit Schatten). Alle Zusammenfassungen nur innerhalb derselben Stufe. clk/reset standardmäßig versteckt. - Leitungen: durchgezogen = Daten (dick = Mehrfachbus,
/N= Bitbreite); lila gestrichelt = MUX-Auswahl; blau gepunktet = Enable; rot = Rückkopplung.
Parameter
| Parameter | Funktion |
|---|---|
--list | Module und deren Einheiten auflisten und beenden |
--module M | Welches Modul zeichnen (parametrisierte Varianten zeichnen die erste, mit Hinweis) |
--layout auto|pipeline|free | Standard auto: Bei Modulen mit Submodul-Instanzen und nicht mehr kombinatorischen Knoten als Instanzen (Top-Level-Klebemodul) free (bidirektionale Kommunikation zwischen Blöcken ist keine Pipeline-Rückkopplung), sonst pipeline |
--show-clock | clk/reset zeichnen |
--no-fold | generate-Instanzen / gleichartige Einheiten nicht falten |
--no-collapse | Steuerungs-/Kombinatorik-Logik nicht falten |
--sv2v auto|always|never | SV-Frontend (nicht zusammengefasste Array-Ports usw. benötigen sv2v) |
--keep-json PATH | Yosys JSON-Netlist behalten |
Zwei、Zustandsmaschinendiagramm (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]
- Direktes Parsen des Quellcodes, Übergangsbedingungen bleiben als Originalausdrücke erhalten. Erkennt
case (S)-Zweige, die S (oder die nächste Zustandsvariable N mitS <= N) zuweisen; Zustandsnamen aus localparam; Konstantenvergleiche mit anderen FSM-Zustandsvariablen in den entsprechenden Zustandsnamen übersetzen (core_state == 3'b001→core_state == FETCH). - Übergangsbedingung = die if/case/for, die direkt die Zustandszuweisung umschließt;
else ifschreibt nur seine eigene Bedingung;for-Bedingungen als∃j: …; äußeresfor i, das die gesamte case umschließt, bedeutet Vervielfältigung nach i; Guards außerhalb des case (enable, Lese-/Schreibzweige) kommen in einen Kommentarkasten; in Schleifen gesetzte lokale boolesche Variablen werden im Kommentarkasten definiert. - Zeichenweise: Jeder Zustand ein gleich großer Kreis (Name bei
_umbrechen, darunter klein Codierung und ⟳), Endzustand Doppelkreis, schwarzer Punkt = Reset-Einsprung. Wenn der Hauptablauf die meisten Zustände abdeckt, als U-förmige Schleife anordnen (obere Reihe links→rechts, runter, untere Reihe rechts→links); verzweigende Typen nach Abstand vom Initialzustand gleichmäßig auf Spalten verteilen. Benachbarte Zustände direkt verbinden; Sprünge über andere Zustände hinweg oben/unten überwölben, Beschriftung im Bogenscheitel; A↔B Hin und Rück je zur Seite gebogen; explizite Selbstschleife oberhalb des Kreises zeichnen. Spaltenbreite automatisch an längste Beschriftung angepasst. Nach/die Zuweisungen beim Übergang (maximal 2 anzeigen). - Nicht erkannt: Steuerungen ohne
case(state)-Struktur (Zähler-artig, rein kombinatorische Dekodierung) – erscheinen nicht inlist, ist beabsichtigt.
Drei、Gesamtsteuerungsstruktur (fsm2drawio.py ctrl)
python3 $S/scripts/fsm2drawio.py ctrl src/*.sv --top gpu --module core -o diagrams/ctrl_core.drawio --png
- Zeichnet Submodul-Instanzen innerhalb des Moduls L (generate-Instanzen als ×N falten), nur Steuersignale behalten: orange dicke Linie = FSM-Zustandsbroadcast; blau = valid/ready Handshake; lila gestrichelt = start/done/Enable/Auswahl/Reset usw.; Datenbusse weglassen. Instanzenkästchen mit FSM listen die Zustandssequenz auf, Unterebenen mit FSM markieren „enthält FSM“.
- Steuersignale maximal 3 Ebenen von Basiseinheiten (Komparator, Gatter, Register) zurückverfolgen. Klassifizierung anhand des Signalnamens; bei nicht standardkonformer Benennung können Signale fehlen/überflüssig sein.
- Empfehlung von oben nach unten: Zuerst Top-Level (wer startet wen, wer handshakt mit Speicher) → nächste Ebene (wie der Hauptsteuer-FSM dispatchen) → Zustandsmaschinendiagramm des kritischen FSM.
Einschränkungen
- Das Layout wird von Graphviz automatisch nach Spaltenbeschränkungen erstellt; Kreuzungen können nicht vollständig vermieden werden; Feinschliff durch Ziehen in draw.io (alle Objekte sind nativ editierbar).
- Große steuerungsorientierte Module (hunderte Einheiten) bleiben im Blockdiagramm unübersichtlich; gemäß obiger Anleitung Zustandsmaschinen-/Steuerungsstrukturdiagramm verwenden oder einzeln als Submodule zeichnen.
- Kreise/Bögen in Zustandsmaschinendiagrammen werden direkt anhand von Koordinaten gezeichnet; beim Ziehen eines Zustands folgen die Bogen-Stützpunkte nicht automatisch, sie müssen nachgezogen werden.