Verilog → draw.io : Diagramas de Arquitectura / Estructural / Máquina de Estados / Control
Cuatro perspectivas, elige según la pregunta: ¿Cuál es la idea de diseño? (para explicar a otros, poner en un paper/informe) → arch2drawio.py (sección cero, por defecto); ¿Cómo se ve a nivel de compuertas? (ruta de datos unidad por unidad) → rtl2drawio.py; ¿Cómo funciona? (FSM individual) → fsm2drawio.py fsm; ¿Quién controla a quién? (estructura de control general) → fsm2drawio.py ctrl. Los módulos orientados al control (FSM, decodificadores, árbitros) se vuelven desordenados con diagramas estructurales; mejor usar los dos últimos.
Dependencias: PyYAML; para exportar PNG/SVG se necesita drawio (orden de búsqueda: $DRAWIO_BIN → PATH → app macOS → ~/.local/opt/drawio; Linux sin $DISPLAY usa automáticamente xvfb-run; si no hay root, usar tools/install-drawio-linux.sh del repositorio). Si no hay exportador, todos los comandos generan .drawio normalmente, solo falta PNG, con un aviso en stderr. Para diagramas estructurales y de control se necesita además yosys, sv2v (SystemVerilog), dot (Graphviz), y la habilidad drawio-skill ya instalada (por defecto ~/.claude/skills/drawio-skill, se puede cambiar con la variable de entorno DRAWIO_SKILL_DIR; solo llama a autolayout.py, no la modifica). Los tres scripts aceptan --png / --svg / --scale N (el PNG por defecto usa scale 3, ~1 model unit = 3 px; $DRAWIO_SCALE puede cambiar el valor por defecto). También se puede exportar individualmente: python3 $S/scripts/drawio_export.py X.drawio --png --svg. Para insertar en Markdown/Obsidian, priorizar SVG, no se pixelan. $S es el directorio de esta habilidad. examples/ contiene dos ejemplos de comparación: wrr_arbiter.v (single stage + state register) y load_pipe.v (multicycle pipeline con replay).
Cero: Diagrama de Arquitectura Estilo Paper (arch2drawio.py) — Por defecto
La entrada es código (también puede ser un artículo, patente o descripción del usuario). La netlist de Yosys es demasiado detallada para un diagrama de arquitectura, así que aquí no se sintetiza: el script da un esquema del código fuente, tú (el agente) lees el código, extraes lo esencial y escribes la descripción del diagrama, luego el script genera el diagrama de forma determinista. El usuario no necesita escribir ningún archivo de descripción; solo se muestra el diagrama al usuario, a menos que quiera modificar la descripción.
python3 $S/scripts/arch2drawio.py outline src/*.sv --top gpu [--module core] # Esquema del código fuente (sin síntesis)
python3 $S/scripts/arch2drawio.py render fig.yaml -o diagrams/arch_core.drawio --png
outline lista: árbol de jerarquía (generates copiados ×N), puertos, arreglos de almacenamiento (profundidad × ancho de bits, campos de packed struct, arreglos con misma profundidad indican "probablemente la misma tabla"), registros de pipeline nombrados por etapa (s1_ / stage2_ / if_id_ …), máquinas de estado, señales de realimentación sospechosas (replay/kill/flush/stall/forward/refill…), flujo de datos entre instancias (a través de señales compartidas, excluyendo clk/reset/variables de bucle). Para entrada de artículo/patente/descripción, se omite este paso.
Reglas para dibujar (al escribir la descripción)
- Solo lo esencial: Primero di en una o dos frases la idea central del diseño; conserva solo los componentes que la reflejan; omite lógica de pegamento, clk/reset, puertos de depuración y señales de control dispersas. Generalmente 8–20 cajas; si son más, divide en dos diagramas (uno superior + uno para la unidad clave).
- Destacar lo importante: 1–3 puntos de diseño clave con
hl: true(color contrastante: fondo oscuro, texto blanco) obold: true(negrita); las líneas de la ruta crítica también pueden tener| hl. Para destacar un área, usagroups+hl: true(recuadro de fondo claro). - Numerar solo cuando sea necesario: Dale
num: Nsolo a los componentes centrales cuando se necesite referencia en texto, y explica encallouts. - Texto conciso en las cajas: Dentro de la caja solo el nombre (≤ 2 líneas); el significado se expresa con la forma: las operaciones usan
adder/mult/cmp/op(símbolo circular) oalu, la selección usamux(el selectorx.selentra por la parte inferior), el almacenamiento usatable, las colas usanfifo, los registros usanreg, las compuertas usanand/or/xor/not. - Las tablas SRAM escriben los campos de cada entrada: Los
fieldsdetableescriben la función de cada campo (ej.[V, Tag (20b), Data (32b)]). Para memoria asociativa multivia, usastack: 4(apilado + ×4); los arreglos paralelos de misma profundidad y dirección se combinan en una sola tabla, cada arreglo como un campo. - Pipeline:
stagesdefine las etapas (id+ funciónsub, ej.{id: S1, sub: Hit detect}), el script dibuja las líneas discontinuas y los nombres de etapa; los registros / FIFOs entre etapas se indican conbetween: [S0, S1], el script los dibuja sobre la línea divisoria; las realimentaciones (destino en una etapa anterior) automáticamente se enrutan por el pasillo inferior, o se puede usar| fb,| fb via=top(desde arriba). Múltiples pipelines usanlanes, cada lane comparte la columna de la etapa, y las etapas correspondientes en el tiempo se alinean automáticamente. - Los diagramas de bloque sin etapas (top-level, SoC) no escriben
stages; las unidades replicadas usanstack: N, los recursos por hilo/núcleo se encierran congroups.
Formato de la descripción (YAML)
title: dcache_pipe: Pipeline de búsqueda D-Cache de mapeo directo
stages: [{id: S0, sub: Selección / Lectura de arreglo}, {id: S1, sub: Comparación de Tag}, {id: S2, sub: Fallo / Reproducción}]
lanes: [{id: ld, name: Pipeline de Load}, {id: st, name: Pipeline de Store}] # Opcional, para múltiples pipelines
nodes:
- {id: arr, type: table, label: "Arreglo de Cache · 64 entradas", fields: [V, "Tag (20b)", "Data (32b)"], stage: S0}
- {id: r1, type: reg, label: addr, between: [S0, S1]} # Sobre la línea divisoria S0|S1
- {id: tcmp, type: cmp, label: Comparación de Tag, stage: S1, hl: true} # Círculo "=", texto explicativo debajo
- {id: rmux, type: mux, stage: S0, ports: 2}
edges:
- "rmux => arr : idx | w=6" # -> datos => bus (grueso) ~> control (línea discontinua); w=N ancho de bits
- "rpl -> rmux.sel : replay | fb hl" # .sel/.en entra por abajo, .top entra por arriba; fb realimentación, hl enfatizar, via=top, pos=src|dst
groups: [{label: "×4 por hilo", members: [rf, alu, lsu], hl: true}]
legend: {reg: Registro de pipeline, fb: Realimentación, ctrl: Control, hl: Diseño clave} # Claves posibles: data bus ctrl fb hl hl_edge hl_region reg
callouts: {1: "Numerar solo cuando se necesita referencia en texto"}
Tipos de nodo: 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. Atributos generales: stage / lane / between, hl bold num stack, ajustes finos con row / col (fila/columna dentro de la etapa, puede ser negativo), dx / dy, w / h. Otros: array soporta cell_labels (etiqueta por celda, ej. T0..T3) y cell_h (celda alargada, para canales de hilo); groups soporta stack: N (apilar toda un área, ej. "Core ×2" y mostrar interior) y label_align: right; el name de lanes vacío = solo dividir sin dibujar etiqueta; en las aristas, | both dibuja flecha bidireccional (petición/respuesta); la realimentación entre dos unidades en la misma columna, una arriba y otra abajo, se dibuja como una línea vertical corta y ajustada (ej. Dispatcher ⇅ Core).
Flujo de trabajo
outline+ leer el código fuente clave (o leer el artículo/patente), anotar la idea central → decidir qué capa dibujar, cuáles son los puntos clave.- Escribir la descripción en un directorio temporal (scratchpad), cada nodo debe tener
stage(si hay etapas). Los bucles se rompen automáticamente: los nodos con nombre parecido a controlador (sched / ctrl / fsm / arbiter / dispatch / scheduler / control …, luego aquellos con muchos abanicos de salida en líneas de control) comienzan, sus salidas van a la derecha y las aristas que vuelven a ellos se dibujan como realimentación; si la suposición es incorrecta, escribe explícitamente| fben esa arista. Opcional: Referencia de composición (gpt-image-bridge, requiere confirmación del usuario para cada una): el diseño de cuadrícula garantiza que las conexiones sean correctas, pero no "compone" el diagrama. Cuando el módulo tenga ≥ 10 o tenga particiones evidentes (plano de control/plano de datos/jerarquía de memoria), se puede generar primero la línea base en draw.io, luego escribir la lista de módulos y conexiones obtenidas del código en el prompt (estilo ilustración de paper, fondo blanco, un solo color de énfasis, diseño en tres regiones, texto mínimo, etiquetas cortas en inglés), usar gpt-image-bridge para generar 1 boceto y leerlo. Tomar solo prestado el diseño y la jerarquía visual (particiones, quién está arriba de quién, formación, espacio en blanco, posición de elementos clave); convertir alane/row/col/groups; los módulos, conexiones y nombres deben basarse estrictamente en el código — errores comunes de los bocetos: conectar al origen equivocado, dibujar módulos de más o de menos, fondo oscuro. Antes de generar, según lo establecido en esta habilidad, listar las imágenes (propósito, ruta, tamaño, resumen de prompt) y esperar el consentimiento explícito del usuario; los bocetos se guardan endiagrams/sketch_*.png, y el archivo de descripción referencia el boceto en un comentario al inicio. render --png→validate.py(error debe ser 0; las advertencias de superposición de líneas divisorias de etapa con encabezados/registros son esperadas) → Leer PNG e inspeccionar visualmente, usarrow/colpara ajustes finos (como máximo dos rondas; común: hacer que la ruta de datos principal ocupe la fila 0 en línea recta, y las rutas secundarias/control en la siguiente fila).- Entregar
.drawio+.png; el archivo de descripción permanece en el scratchpad, se entrega al usuario solo si quiere modificarlo.
Ejemplos: top-level de tiny-gpu ~/hardware-project/tiny-gpu/diagrams/arch_gpu.* (diseño guiado por el boceto sketch_gpu_top.png, comparado con arch_gpu_baseline.png que no usó boceto); examples/dcache_pipe.v → dcache_pipe.arch.yaml (descripción escrita a partir del código) → .png; arch_dual_issue.yaml (alineación de dos pipelines + forwarding + anotaciones numeradas); arch_mac.yaml (nivel de circuito: símbolos de multiplicación/suma, realimentación de acumulación, selector MUX). El diagrama de bloques del core de tiny-gpu está en ~/hardware-project/tiny-gpu/diagrams/arch_core.*.
Uno: Diagrama Estructural (rtl2drawio.py, detalles a nivel de compuertas)
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --list # Listar módulos y sus unidades constituyentes
python3 $S/scripts/rtl2drawio.py src/*.sv --top gpu --module alu -o diagrams/alu.drawio --png
Flujo: sv2v (automático al encontrar .sv) → Yosys proc; opt; opt_dff; wreduce; memory -nomap → netlist JSON → análisis de pipeline por unidad → simplificación → diseño con restricciones de columna. Después de generar, ejecutar python3 ~/.claude/skills/drawio-skill/scripts/validate.py X.drawio (error debe ser 0; las advertencias de "through-vertex" / "overlap" causadas por líneas divisorias de etapa que atraviesan registros que están sobre ellas son esperadas), luego leer PNG e inspeccionar visualmente.
Reglas de diseño (--layout pipeline; por defecto auto: módulos jerárquicos con principalmente subinstancias usan diseño libre, otros usan diseño por etapas de pipeline)
- Entradas a la izquierda, salidas a la derecha, la lógica se organiza en columnas de izquierda a derecha según la profundidad combinacional; para pipelines multietapa, ordenar de S0 → Sn de izquierda a derecha, con líneas discontinuas divisorias de etapa y el número de etapa en el encabezado de la columna.
- Los registros se clasifican en dos tipos:
- Registros de pipeline (barra vertical estrecha) colocados sobre las líneas divisorias de etapa; la última fila de registros que solo alimentan puertos de salida son registros de salida (no cuentan como una etapa, no se dibuja línea divisoria).
- Registros de estado (FSM / contadores / punteros: su salida se vuelve a conectar a su propia entrada solo a través de lógica combinacional) se dibujan dentro de la etapa, con su salida como realimentación.
- Cuando el nombre del registro incluye un número de etapa (
s2_x,x_s2,stage2_x,if_id_/id_ex_/ex_mem_/mem_wb_, y hay al menos dos patrones) el nombre tiene prioridad — el bit de valid con kill, aunque tenga un bucle combinacional, sigue siendo un registro de pipeline. Si el diseño no nombra las etapas, solo se puede inferir por la estructura; el tiempo de llegada de los puertos de entrada es desconocido, y el número de etapa podría ser adelantado.
- Cálculo del número de etapa: partiendo de los registros con número de etapa, la etapa de una unidad = el operando de registro más tardío (tomar el máximo); la lógica impulsada solo por puertos de entrada se asigna a la etapa "según qué registro la captura" (ALAP), y el puerto de entrada correspondiente también se coloca al inicio de esa etapa (por ejemplo, la respuesta de TLB se coloca en S1).
- Realimentación: las señales que entran al cono de entrada de algún registro con número de etapa, pero que solo pueden provenir de etapas posteriores (replay→S0, kill→S1), así como los bucles de los registros de estado, se dibujan como líneas rojas que rodean por el borde inferior, cuanto mayor es la distancia, más hacia afuera; si hay más de 3 realimentaciones entre el mismo par de etapas, solo se dibuja una línea gruesa que representa un haz (etiquetado
Realimentaciones ×N: …). - Agrupación: cuando haya más de 4 registros de pipeline en la misma línea divisoria, se fusionan en un grupo de registros (barra vertical etiquetada
Registros S1→S2 ×Ny ancho total de bits). - Simplificación: fusionar rebanadas del mismo registro con el mismo nombre; las cadenas de MUX de if/case anidados se combinan en un solo MUX
N:1; la lógica de decodificación pequeña que solo impulsa la línea de selección de un MUX o habilitación de un registro se incorpora y se dibuja como línea de selección / habilitación; las compuertas de hasta 3 se dibujan como símbolos de AND/OR/NOT/XOR, las más grandes se pliegan en una nube de "lógica combinacional" (que se corta en medio de las unidades aritméticas para evitar bucles falsos); las unidades generadas por bucles generate de la misma fuente (misma ubicación en el código fuente) y las instanciasthreads[i].xse pliegan en "×N" (con sombreado). Todas las fusiones se realizan solo dentro de la misma etapa. clk/reset están ocultos por defecto. - Conexiones: línea sólida = datos (gruesa = bus multibit,
/N= ancho de bits); línea discontinua púrpura = selección de MUX; línea punteada azul = habilitación; línea roja = realimentación.
Parámetros
| Parámetro | Función |
|---|---|
--list | Listar módulos y sus unidades constituyentes y salir |
--module M | Dibujar el módulo específico (la variante parametrizada dibuja la primera, y se indica) |
--layout auto|pipeline|free | Por defecto auto: si el módulo tiene subinstancias y los nodos de lógica combinacional no son más numerosos que las instancias (módulo de pegamento top-level), usar free (comunicación bidireccional entre bloques no es realimentación de pipeline), de lo contrario pipeline |
--show-clock | Dibujar clk/reset |
--no-fold | No plegar instancias generate / unidades de la misma fuente |
--no-collapse | No plegar lógica de control/combinacional |
--sv2v auto|always|never | Frontend para SV (sintaxis como puertos de arreglos no empaquetados requiere sv2v) |
--keep-json PATH | Conservar la netlist JSON de Yosys |
Dos: Diagrama de Máquina de Estados (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]
- Analiza directamente el código fuente, las condiciones de transición mantienen las expresiones originales. Reconoce asignaciones a S (o a la variable de estado S) dentro de ramas
case (S); los nombres de estado se toman de localparam; las constantes de variables de estado de otras FSM se traducen al nombre del estado de esa FSM (core_state == 3'b001→core_state == FETCH). - Condiciones de transición = if/case/for que envuelve directamente la asignación de estado;
else ifescribe solo su propia condición; la condición dentro deforse escribe como∃j: …; unfor iexterno que envuelve todo el case indica duplicación por i; las guardas fuera del case (enable, rama de lectura/escritura) van en un cuadro de comentario; las variables booleanas locales que se ponen a 1 en el bucle se definen en un cuadro de comentario. - Dibujo: cada estado es un círculo del mismo tamaño (el nombre se parte en el
_en una línea; debajo, en letra pequeña, el código y ⟳), los estados finales con doble círculo, un punto negro = entrada de reset. Cuando el flujo principal cubre la mayoría de los estados, se organizan en un bucle en forma de U (fila superior de izquierda a derecha, giro hacia abajo, fila inferior de derecha a izquierda); los tipos de ramificación se organizan en columnas uniformes según la distancia desde el estado inicial. Los estados adyacentes se conectan directamente; las transiciones que saltan sobre otros estados se arquean por encima o por debajo, con la etiqueta en la cima del arco; A↔B, cada ida se curva hacia un lado; los bucles explícitos se dibujan encima del círculo. La distancia entre columnas se ajusta automáticamente según la etiqueta más larga. Después del/están las asignaciones durante la transición (se muestran como máximo 2). - No reconoce: control sin estructura
case(state)(contadores, decodificación puramente combinacional) — no aparecen enlist, es esperado.
Tres: Estructura de Control General (fsm2drawio.py ctrl)
python3 $S/scripts/fsm2drawio.py ctrl src/*.sv --top gpu --module core -o diagrams/ctrl_core.drawio --png
- Dibuja las instancias de submódulos dentro del módulo L (las instancias generate se pliegan ×N), conserva solo las señales de control: línea naranja gruesa = difusión del estado de la FSM; línea azul = handshake valid/ready; línea discontinua púrpura = start/done/habilitación/selección/reset, etc.; los buses de datos se omiten. Las cajas de instancias que contienen FSM listan la secuencia de estados; las de subniveles que tienen FSM se etiquetan "Contiene FSM".
- Las señales de control se rastrean hasta su origen a través de como máximo 3 niveles de unidades básicas (comparadores, compuertas, registros). La clasificación se basa en el nombre de la señal; cuando la nomenclatura no es estándar, puede omitir o dibujar de más.
- Se recomienda ir de arriba a abajo: primero top (quién inicia a quién, quién hace handshake con la memoria) → siguiente nivel (cómo la FSM principal programa) → diagrama de la máquina de estados de la FSM clave.
Limitaciones
- El diseño lo realiza Graphviz automáticamente según restricciones de columna; los cruces no se pueden eliminar por completo; los ajustes finos se hacen arrastrando en draw.io (todos los objetos son nativos editables).
- Los módulos de control grandes (cientos de unidades) seguirán siendo desordenados en el diagrama de bloques; cambiar a diagrama de máquina de estados / diagrama de estructura de control, o dibujar submódulo por submódulo.
- Los círculos y arcos del diagrama de máquina de estados se dibujan directamente según coordenadas; al arrastrar un estado, los puntos de inflexión de los arcos no se mueven con él; hay que ajustarlos manualmente.