Verilog → draw.io : Diagrammes d'architecture / blocs / machines d'état / contrôle
Quatre perspectives, choisissez selon la question : quelle est l'idée de conception (pour expliquer à quelqu'un, mettre dans un article/soutenance) → arch2drawio.py (section zéro, par défaut) ;
à quoi ressemble le niveau portes (chemin de données unité par unité) → rtl2drawio.py ; comment ça fonctionne (une seule FSM) → fsm2drawio.py fsm ;
qui commande qui (structure de contrôle globale) → fsm2drawio.py ctrl. Pour les modules à dominante contrôle (FSM, décodeur, arbitre), un schéma de structure serait confus, préférez les deux derniers.
Dépendances : PyYAML ; export PNG/SVG nécessite drawio (ordre de recherche $DRAWIO_BIN → PATH → app macOS → ~/.local/opt/drawio ;
sur Linux sans $DISPLAY, utilise automatiquement xvfb-run ; sur serveur sans root, utilisez tools/install-drawio-linux.sh du dépôt). Sans moteur d'export, toutes les commandes produisent normalement un fichier .drawio, il manque juste le PNG, avec un message sur stderr. Les diagrammes de blocs et de contrôle nécessitent en plus yosys, sv2v (SystemVerilog), dot (Graphviz), et le skill drawio-skill installé (par défaut ~/.claude/skills/drawio-skill, modifiable via la variable d'environnement DRAWIO_SKILL_DIR ; il n'utilise que son autolayout.py, ne le modifie pas).
Les trois scripts acceptent --png / --svg / --scale N (PNG scale par défaut 3, environ 1 unité modèle = 3 px ; $DRAWIO_SCALE peut modifier le défaut),
ou exportez séparément : python3 $S/scripts/drawio_export.py X.drawio --png --svg. Pour une intégration dans Markdown/Obsidian, privilégiez SVG (non pixelisé). $S désigne le répertoire de ce skill. examples/ contient deux exemples : wrr_arbiter.v (un seul étage + registre d'état), load_pipe.v (pipeline multi-étages avec replay).
Zéro : Diagramme d'architecture style papier (arch2drawio.py) – par défaut
L'entrée est du code (ou un article, un brevet, une description de l'utilisateur). Le netlist Yosys est trop détaillé pour un diagramme d'architecture, donc ici on ne synthétise pas : le script donne un plan du code source, c'est vous (l'agent) qui lisez le code, en dégagez le cœur et écrivez la description du diagramme, puis le script le met en page de manière déterministe. L'utilisateur n'a aucun fichier de description à écrire ; montrez-lui seulement le diagramme, sauf s'il veut modifier la description lui-même.
python3 $S/scripts/arch2drawio.py outline src/*.sv --top gpu [--module core] # plan du code source (pas de synthèse)
python3 $S/scripts/arch2drawio.py render fig.yaml -o diagrams/arch_core.drawio --png
outline liste : arbre hiérarchique (generate déplié ×N), ports, tableaux de stockage (profondeur × largeur, champs de packed struct, tableaux de même profondeur suggérés « peut-être une seule table »),
registres de pipeline nommés par numéro d'étage (s1_ / stage2_ / if_id_ …), machines d'état, signaux de rétroaction suspectés (replay/kill/flush/stall/forward/refill…),
flux de données entre instances (via signaux partagés, sans clk/reset/variables de boucle). Pour une entrée article/brevet/description, sautez cette étape.
Règles de dessin (à respecter lors de l'écriture de la description)
- Ne dessinez que le cœur : commencez par une ou deux phrases décrivant l'idée centrale de la conception, ne gardez que les composants qui la matérialisent ; logique de colle, clk/reset, ports de débogage, signaux de contrôle épars sont omis. En général 8–20 boîtes ; si plus, divisez en deux (une pour le top, une pour l'unité clé).
- Mettez en valeur les points importants : 1–3 points de conception clés
hl: true(couleur contrastée : fond foncé, texte blanc) oubold: true(gras) ; les lignes de chemin critique peuvent aussi avoir| hl. Pour souligner une zone, utilisezgroups+hl: true(boîte à fond clair). - Ne numérotez pas chaque composant : ne donnez un
num: Nqu'aux composants centraux nécessitant un renvoi texte, et écrivez l'explication danscallouts. - Texte concis dans les boîtes : à l'intérieur, seulement le nom (≤ 2 lignes), le sens est exprimé par la forme : opération avec
adder/mult/cmp/op(symbole circulaire) oualu, sélection avecmux(entrée de sélectionx.selpar le bas), stockage avectable, file d'attente avecfifo, registre avecreg, porte avecand/or/xor/not. - Tableaux SRAM : écrivez les champs d'une seule entrée : pour
table,fieldsliste la signification de chaque champ (ex.[V, Tag (20b), Data (32b)]), pour un cache multi-associatif, utilisezstack: 4(empilé + ×4) ; pour des tableaux parallèles de même profondeur et adresse, fusionnez-les en une seule table, chaque tableau étant un champ. - Pipeline :
stagesdéfinit les étages (id+ fonctionsub, ex.{id: S1, sub: Vérification de hit}), le script dessine des séparateurs en pointillés et le nom des étages ; les registres/FIFO entre étages utilisentbetween: [S0, S1], le script les place sur la ligne de séparation ; la rétroaction (vers un étage antérieur) revient automatiquement par le couloir du bas, ou utilisez| fb,| fb via=top(par le haut). Pour les pipelines multiples, utilisezlanes, chaque lane partage la colonne d'un même étage, les étages temporellement correspondants sont automatiquement alignés. - Les diagrammes sans étages (top, SoC) n'écrivent pas
stages; les unités dupliquées utilisentstack: N, les ressources par thread/cœur sont encadrées pargroups.
Format de description (YAML)
title: dcache_pipe : Pipeline de recherche de cache D direct
stages: [{id: S0, sub: Sélection / Lecture du tableau}, {id: S1, sub: Vérification de hit}, {id: S2, sub: Miss / Relecture}]
lanes: [{id: ld, name: Pipeline Load}, {id: st, name: Pipeline Store}] # optionnel, pour pipelines multiples
nodes:
- {id: arr, type: table, label: "Tableau de cache · 64 entrées", fields: [V, "Tag (20b)", "Data (32b)"], stage: S0}
- {id: r1, type: reg, label: addr, between: [S0, S1]} # placé sur la séparation S0|S1
- {id: tcmp, type: cmp, label: Comparaison Tag, stage: S1, hl: true} # cercle "=", texte d'explication en dessous
- {id: rmux, type: mux, stage: S0, ports: 2}
edges:
- "rmux => arr : idx | w=6" # -> données => bus (gras) ~> contrôle (pointillé) ; w=N largeur
- "rpl -> rmux.sel : replay | fb hl" # .sel/.en par le bas, .top par le haut ; fb rétroaction, hl mise en valeur, via=top, pos=src|dst
groups: [{label: "Par thread ×4", members: [rf, alu, lsu], hl: true}]
legend: {reg: Registre de pipeline, fb: Rétroaction, ctrl: Contrôle, hl: Conception clé} # clés possibles : data bus ctrl fb hl hl_edge hl_region reg
callouts: {1: "Numérotez uniquement si nécessaire pour renvoyer à l'explication"}
Types de nœuds : 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.
Attributs communs : stage / lane / between, hl bold num stack, micro-ajustements avec row / col (ligne/colonne dans l'étage, peut être négatif), dx / dy, w / h.
Autres : array supporte cell_labels (étiquettes par cellule, ex. T0..T3) et cell_h (cellule rectangulaire, pour canaux de threads) ; groups supporte stack: N
(empilement d'une zone entière, ex. « Cœur ×2 » avec intérieur visible) et label_align: right ; le name de lanes s'il est vide = seulement la bande, pas d'étiquette ;
les arêtes avec | both dessinent une flèche bidirectionnelle (requête/réponse) ; la rétroaction entre deux unités d'une même colonne (haut/bas) est représentée par une courte ligne verticale serrée (ex. Dispatcher ⇅ Cœur).
Processus
outline+ lecture du code source clé (ou lecture d'article/brevet), écrivez l'idée centrale → décidez quel niveau dessiner, quels points sont importants.- Écrivez la description dans un répertoire temporaire (scratchpad), donnez
stageà chaque nœud (s'il y a des étages). Coupez automatiquement les boucles : les nœuds dont le nom ressemble à un contrôleur (sched / ctrl / fsm / arbiter / dispatch / 调度 / 控制 …, puis ceux avec beaucoup de sorties de contrôle) commencent, leurs sorties vont vers la droite, les arêtes qui reviennent sont marquées en rétroaction ; si la détection est incorrecte, écrivez explicitement| fbpour cette arête. Optionnel : référence de composition (gpt-image-bridge, nécessite validation par l'utilisateur pour chaque image) — le moteur de mise en page par grille assure des connexions correctes, mais ne « compose » pas. Pour les modules ≥ 10 ou avec une partition évidente (plan de contrôle/plan de données/hiérarchie de mémoire), vous pouvez d'abord sortir une baseline draw.io, puis écrire la liste des modules issus du code et leurs connexions dans le prompt (style d'illustration d'article, fond blanc, une seule couleur d'accent, disposition en trois zones, très peu de texte, étiquettes courtes en anglais), générez 1 croquis avec gpt-image-bridge et lisez-le. N'empruntez que la disposition et la hiérarchie visuelle (zones, qui est au-dessus de qui, formation, espace blanc, emplacement des éléments importants) ; convertissez enlane/row/col/groups; Les modules, les connexions et les noms doivent être strictement basés sur le code — erreurs fréquentes des croquis : source de connexion erronée, module en trop/manquant, fond sombre. Avant de générer, listez les images (usage, chemin, dimensions, résumé du prompt) et attendez le consentement explicite de l'utilisateur ; les croquis sont enregistrés sousdiagrams/sketch_*.png, le fichier de description y fait référence dans un commentaire d'en-tête. render --png→validate.py(error doit être 0 ; les avertissements de chevauchement/through-vertex pour les séparateurs pointillés traversant les en-têtes d'étage/registres sont normaux) → Lisez le PNG à l'œil, utilisezrow/colpour micro-ajuster au maximum deux fois (courant : faire en sorte que le chemin de données principal occupe la ligne 0 en ligne droite, les chemins alternatifs/contrôle sur la ligne suivante).- Livrez
.drawio+.png; le fichier de description reste dans le scratchpad, à donner à l'utilisateur s'il veut modifier lui-même.
Exemples : top de tiny-gpu ~/hardware-project/tiny-gpu/diagrams/arch_gpu.* (disposition guidée par le croquis sketch_gpu_top.png,
comparé avec arch_gpu_baseline.png sans croquis) ; examples/dcache_pipe.v → dcache_pipe.arch.yaml (description écrite d'après le code) → .png ; arch_dual_issue.yaml (double pipeline aligné + forwarding +
annotation numérotée), arch_mac.yaml (niveau circuit : symboles de multiplication/addition, rétroaction d'accumulation, entrées de sélection MUX). Le diagramme de niveau bloc du cœur tiny-gpu se trouve dans
~/hardware-project/tiny-gpu/diagrams/arch_core.*.
Un : Schéma de structure (rtl2drawio.py, détails au niveau portes)
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --list # modules et leur composition en unités
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --module alu -o diagrams/alu.drawio --png
Processus : sv2v (automatique pour .sv) → Yosys proc; opt; opt_dff; wreduce; memory -nomap → netlist JSON → analyse du pipeline au niveau unité → simplification → placement contraint par colonne.
Après génération, exécutez python3 ~/.claude/skills/drawio-skill/scripts/validate.py X.drawio (error doit être 0 ; les avertissements through-vertex/overlap pour les séparateurs traversant les étages/registres sur les séparateurs sont normaux), puis lisez le PNG à l'œil.
Règles de placement (--layout pipeline ; défaut auto : pour les modules hiérarchiques à base d'instances de sous-modules, disposition libre, sinon disposition par étages de pipeline)
- Entrées à gauche, sorties à droite ; la logique est disposée de gauche à droite par profondeur combinatoire ; pour un pipeline multi-étages, S0 → Sn de gauche à droite, séparateurs pointillés, en-têtes de colonne avec numéro d'étage.
- Deux types de registres :
- Registres de pipeline (barre verticale étroite) placés sur la ligne de séparation entre étages ; la dernière rangée qui ne fait que connecter les sorties est constituée de registres de sortie (qui ne comptent pas comme un étage, sans séparateur).
- Registres d'état (FSM / compteur / pointeur : la sortie ne repasse que par de la logique combinatoire pour revenir à l'entrée) dessinés à l'intérieur d'un étage, avec la sortie en rétroaction.
- Lorsque le nom d'un registre contient un numéro d'étage (
s2_x,x_s2,stage2_x,if_id_/id_ex_/ex_mem_/mem_wb_, et au moins deux de ces motifs) le nom prévaut — le bit valid avec kill, même avec boucle combinatoire, reste un registre de pipeline. Si la conception n'est pas nommée par numéro d'étage, on ne peut se fier qu'à la structure ; le temps d'arrivée des entrées est inconnu, le numéro d'étage peut être trop précoce.
- Estimation du numéro d'étage : en partant des registres nommés par numéro d'étage, l'étage d'une unité = le maximum des registres opérandes les plus tardifs ; la logique uniquement pilotée par les ports d'entrée est affectée à l'étage en fonction de l'étage qui l'échantillonne (ALAP), les ports d'entrée correspondants sont également placés en tête de cet étage (ex. la réponse TLB en S1).
- Rétroaction : les signaux qui entrent dans le cône d'entrée d'un registre nommé par étage mais qui ne peuvent provenir que d'un étage ultérieur (replay→S0, kill→S1), ainsi que les boucles des registres d'état, sont dessinés comme une ligne rouge revenant par l'extérieur en bas, les grandes portées à l'extérieur ; lorsqu'il y a plus de 3 signaux de rétroaction entre la même paire d'étages, ne dessinez qu'une seule ligne épaisse représentant le faisceau (marquée
Rétroaction ×N : …). - Regroupement : si plus de 4 registres de pipeline sont sur la même séparation, fusionnez-les en un groupe de registres (barre verticale étiquetée
Registres S1→S2 ×Net largeur totale). - Simplification : fusion des tranches de registres de même nom ; chaînes de MUX imbriquées if/case en un seul MUX
N:1; petite logique de décodage qui ne pilote qu'une entrée de sélection de MUX / une activation de registre, intégrée et dessinée comme ligne de sélection/activation ; portes (jusqu'à 3) dessinées comme symboles de porte AND/OR/NOT/XOR, les plus grandes repliées en nuage « logique combinatoire » (coupé là où il y a une unité opérationnelle pour éviter les faux cycles) ; les unités issues d'un même endroit du code source dans une boucle generate et les instancesthreads[i].xrepliées en « ×N » (avec ombre). Toutes les fusions ne se font qu'à l'intérieur d'un même étage. clk/reset sont masqués par défaut. - Connexions : trait plein = données (gros = bus multi-bits,
/N= largeur) ; trait pointillé violet = sélection MUX ; pointillé bleu = activation ; trait rouge = rétroaction.
Paramètres
| Paramètre | Rôle |
|---|---|
--list | Liste les modules et leur composition en unités puis quitte |
--module M | Dessine un module spécifique (la première variante paramétrée, avec un message d'info) |
--layout auto|pipeline|free | Défaut auto : s'il y a des instances de sous-modules et peu de nœuds de logique combinatoire (module de colle de niveau top), utilise free (communication bidirectionnelle entre blocs, ce n'est pas une rétroaction de pipeline), sinon pipeline |
--show-clock | Dessine clk/reset |
--no-fold | Ne replie pas les instances generate / unités de même origine |
--no-collapse | Ne replie pas la logique de contrôle/combinatoire |
--sv2v auto|always|never | Front-end SV (certaines syntaxes comme les ports de tableaux non contraints nécessitent sv2v) |
--keep-json PATH | Conserve le netlist JSON de Yosys |
Deux : Diagramme de machine d'état (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]
- Analyse directe du code source, les conditions de transition conservent l'expression originale. Reconnaît les branches
case (S)qui assignent une valeur à S (ou à la variable d'état suivanteS <= N). Les noms d'état sont issus des localparam ; les comparaisons avec des variables d'état d'autres FSM sont traduites en nom d'état de l'autre (core_state == 3'b001→core_state == FETCH). - Conditions de transition = les if/case/for qui entourent directement l'assignation de l'état ;
else ifn'écrit que sa propre condition ; les conditions dansfors'écrivent∃j: …; unfor iexterne englobant tout le case signifie duplication par i ; les gardes hors case (enable, branche lecture/écriture) vont dans une boîte de commentaire ; les variables booléennes locales mises à 1 dans une boucle sont définies dans la boîte de commentaire. - Dessin : chaque état est un cercle de taille égale (nom coupé au
_, petit texte en dessous pour le code et ⟳), état final en double cercle, point noir = entrée de reset. Lorsque le flux principal couvre la plupart des états, ils sont disposés en boucle en U (ligne du haut gauche→droite, puis descente, ligne du bas droite→gauche) ; pour les branches, ils sont répartis uniformément en colonnes selon la distance en nombre d'étapes depuis l'état initial. Les états adjacents sont connectés directement ; les transitions qui sautent par-dessus d'autres états passent en arc par le haut/le bas, l'étiquette est au sommet de l'arc ; les allers-retours A↔B se courbent chacun d'un côté ; les boucles explicites sont dessinées au-dessus du cercle. L'espacement des colonnes s'adapte automatiquement à l'étiquette la plus longue. Après le/, les affectations lors de la transition (affichage de 2 maximum). - Non reconnu : les structures sans
case(state)(compteur, décodage purement combinatoire) — ils n'apparaissent pas danslist, c'est attendu.
Trois : Structure de contrôle globale (fsm2drawio.py ctrl)
python3 $S/scripts/fsm2drawio.py ctrl src/*.sv --top gpu --module core -o diagrams/ctrl_core.drawio --png
- Dessine les instances de sous-modules dans le module L (instances generate repliées en ×N), ne conserve que les signaux de contrôle : trait orange épais = diffusion de l'état des FSM ; trait bleu = handshake valid/ready ; trait pointillé violet = start/done/activation/sélection/reset etc. ; les bus de données sont omis. Les boîtes d'instances contenant une FSM listent la séquence d'états, celles avec une FSM dans la sous-hiérarchie sont marquées « Contient FSM ».
- Les signaux de contrôle traversent au plus 3 couches d'unités de base (comparateur, porte, registre) pour remonter à la source. La classification est basée sur le nom du signal ; si les noms ne sont pas standardisés, des omissions/ajouts intempestifs sont possibles.
- Suggestion : du haut vers le bas : d'abord top (qui démarre quoi, qui fait handshake avec la mémoire) → niveau suivant (comment la FSM principale planifie) → diagramme de machine d'état de la FSM clé.
Limites
- La mise en page est effectuée automatiquement par Graphviz avec des contraintes de colonnes ; les croisements ne peuvent pas être complètement éliminés ; les finitions se font dans draw.io (tous les objets sont natifs et éditables).
- Pour les grands modules de contrôle (centaines d'unités), le diagramme de structure reste confus ; utilisez plutôt le diagramme de machine d'état / de structure de contrôle, ou dessinez sous-module par sous-module.
- Les cercles/arcs des diagrammes de machine d'état sont dessinés en fonction des coordonnées ; si vous faites glisser un état, les points d'inflexion des arcs ne suivent pas, il faut les ajuster manuellement.