--- name: vivado-rtl-lint description: Run Vivado's RTL linter to detect design issues before synthesis. Use when user asks to "lint RTL", "check HDL code", "run linter", "find RTL issues", or "check design quality". This skill ONLY analyzes the report generated by the RTL linter command. license: MIT allowed-tools: tool_search_tool_regex vivadoExecute vivado_doc_search read_file create_file replace_string_in_file argument-hint: "[project-path]" metadata: version: "1.0.0" --- # RTL Linting Skill ## Purpose Runs Vivado's RTL linter (`synth_design -lint`) to catch design issues — syntax errors, undriven signals, arithmetic overflow, inferred latches — before full synthesis. **Expected outcome:** A markdown report with every linter violation, diff-formatted code fixes, resolution-guide-backed recommendations, and UG901 references. **Prerequisites:** - RTL source files exist and are syntactically valid (parseable by Vivado) - Vivado project (.xpr) OR source files in workspace for non-project mode - Write access to project directory for report generation --- ## When to Use - User asks to lint RTL, check HDL code, run linter, or find RTL issues - After writing new modules or modifying existing RTL (early development cycle) - Iterative fix-and-verify workflow (apply fixes, re-run linter) ## When NOT to Use - RTL has syntax errors preventing elaboration → fix syntax first - Need full synthesis analysis → run `synth_design` without `-lint` - Post-synthesis/implementation issues → use timing/DRC reports --- ## DO's - Run early and often after any significant RTL changes - Fix critical warnings first (prioritize by severity) - **Always use resolution guides** for known violations — see [resolution-guide.md](resolution-guide.md) - Extract actual code snippets; include real source context in reports - Use vivado_doc_search for UG901 HDL coding best practices - Use `$ARGUMENTS` as project path if provided, otherwise use `pwd` ## DON'Ts - **Never create .tcl script files** — use vivadoExecute tool directly - **Never run `vivado -mode batch -source`** — use vivadoExecute instead - **Never fabricate violations** — only report what Vivado outputs - **Never misinterpret comments as code** — `// text` and `/* text */` are comments - **Never report wrong count** — if Vivado says 0 messages, report 0 violations - **Never omit `-file` option** — always use `synth_design -lint -file $path` - **Never use `-name` switch** — GUI-only; use `-file` in batch mode - **Never parse the `.rpt` file directly** — always run `parse_lint_report.py` first to produce the canonical CSV, then read the CSV. The `.rpt` ASCII table has edge cases (literal `|` in fields, multi-line cells) that ad-hoc parsing will mishandle. - Do not perform manual code inspection beyond what Vivado reports - Do not ignore INFO severity — low-severity items often impact QoR > **Anti-pattern to avoid:** Vivado reported "Total of 0 linter message(s) generated" > but agent added a "Duplicate Module Declaration" error by misreading > `endmodule // topmodule top(a,b,clk,out);` — the text after `//` is a comment. > **Trust Vivado's output. If Vivado says 0 violations, report 0 violations.** > **Anti-pattern to avoid:** Agent read the `.rpt` ASCII table directly with `read_file` > and reasoned over pipe-delimited columns instead of running `parse_lint_report.py`. > While the violation list happened to be correct, this bypasses the robust parser that > handles edge cases (literal `|` in ASSIGN-1/2 fields, multi-line cells, T1/T2 suffix > extraction). **Always run the Python parser first — the CSV is the single source of > truth for all downstream steps.** --- ## Workflow **Use `vivadoExecute` for ALL Vivado commands.** Before executing, load it: `tool_search_tool_regex` with pattern `"vivado"`. Execute steps **sequentially** in this exact order. ### Step 1: Detect Project Mode Detect project (.xpr) vs non-project mode. TCL: [tcl-reference.md § Step 1](tcl-reference.md) **Project mode:** Open project, get part from `get_property part [current_project]`. Honor `get_property top [current_fileset]` first — only if empty, fall back to `find_top` (UG835) and present multiple candidates to the user. **Non-project mode:** Glob `.v`, `.sv`, `.vhd`, `.vhdl` files. Recursively discover header directories (`.vh`, `.svh`, `.h`) for `include_dirs`. Read files with correct flags (`read_verilog -sv` for `.sv`). Run `update_compile_order -fileset sources_1`, then `find_top` to get ranked top-module candidates. **If `find_top` returns multiple candidates:** Stop and ask the user to confirm/select the correct top module before proceeding. Verify: Project/non-project detected, part number available, top module confirmed. ### Step 2: Create Report Directory Create `vivado_agentic_ai_reports/rtl-lint/` under the project directory. TCL: [tcl-reference.md § Step 2](tcl-reference.md) Verify: Directory exists and is writable. ### Step 3: Run RTL Linter Execute `synth_design -top $top_module -part $part_number -lint -file $lint_report_file`. Then verify `$lint_report_file` exists and is non-empty. TCL: [tcl-reference.md § Step 3](tcl-reference.md) Verify: `synth_design -lint` succeeded, `$lint_report_file` non-empty, design stays elaborated. ### Step 4: Parse Linter Report to CSV (MANDATORY) **This step is required for ALL Vivado versions.** Do NOT skip it. - **Vivado ≥ 2026.1** (`$lint_csv_format = true`): The lint report is already a CSV — use it directly. - **Vivado < 2026.1** (`$lint_csv_format = false`): Run [parse_lint_report.py](parse_lint_report.py) via `exec python3` in TCL to convert the `.rpt` into the canonical 7-column CSV. TCL: [tcl-reference.md § Step 4](tcl-reference.md) **Never read or parse the `.rpt` file directly.** The `.rpt` uses an ASCII table format with edge cases (literal `|` in field values, multi-line cells) that the Python parser handles robustly via fixed column-position slicing. Always produce the CSV first, then read it with `read_file`. **Fallback exception (Vivado < 2026.1 only):** If the Python parser produces an **empty CSV** but Vivado's output contains `"Total of X linter message(s) generated"` where X > 0, the parser failed to extract the table. In this case — and **only** in this case — the agent may read the `.rpt` file directly with `read_file` and parse the "2. Expanded" ASCII table manually. If X = 0, the empty CSV is correct — do not fall back. The CSV is the **single source of truth** for all downstream steps (grouping, counting, report generation). **One line = one violation. Empty file = 0 violations.** **Only report violations that appear in the CSV — never fabricate rows.** #### CSV Column Schema Headerless CSV. Split each line on `,` into exactly 7 positional fields: | Col | Field | Use | |-----|-------|-----| | 0 | rule_id | Dispatch to violation handler; group for "By rule ID" summary. May have `(T1)`/`(T2)` suffix. | | 1 | description | Human-readable message in report | | 2 | rtl_name | Signal/instance name. **Empty** for INFER-2, ASSIGN-14. | | 3 | info | Bit index (T1), signal count (T2), port name (ASSIGN-12), operator (ASSIGN-1/2), or `N/A` | | 4 | module | Parent module name; group for "By module" context | | 5 | file_name | Filename only (not full path) — resolve to absolute path for `read_file` | | 6 | line_number | Source line — use for `read_file(path, line-5, line+5)` and report links | **Special characters in field[2]:** `\.` (SV hierarchy separator), `[]` (bus indices), `\|` (bitwise OR in ASSIGN-1/2), empty string (adjacent commas for INFER-2, ASSIGN-14). #### T1 vs T2 Rows Rules ASSIGN-5, ASSIGN-6, ASSIGN-9, ASSIGN-10 emit two row types. Both count toward the total. - **T1 — per-signal:** field[0] has `(T1)` suffix. field[2] = signal name, field[3] = first affected bit index. `ASSIGN-5 (T1),Bit(s) not assigned,signal_name,14,module,file.sv,20` - **T2 — aggregate:** field[0] has `(T2)` suffix. field[2] = module/hierarchy name (not a signal), field[3] = count of affected signals. `ASSIGN-5 (T2),Bit(s) not assigned,module_name,118,hierarchy,file.sv,0` Report as: "118 signals with unassigned bits in module_name". For field[3] > 50, recommend `synth_design -lint -verbose` for detail. - **Non-ASSIGN rules** (INFER-1/2/3, RESET-2, ASSIGN-2, ASSIGN-14): field[3] = `N/A`. `INFER-2,Incomplete case statement,,N/A,module_name,file.sv,100` #### Parsing Algorithm Use the **agent's `read_file` tool** to read the CSV — do NOT use TCL, shell commands, or terminal scripts for parsing or grouping. The agent reads the text and reasons over the comma-separated fields directly. 1. Read the CSV via `read_file` (for large files, read in batches of ~500 lines) 2. Total violations = total non-empty lines. If 0 → report "Vivado found 0 lint violations" 3. For each line, split on comma into the 7 fields defined above 4. **Group by field[0]** (strip `(T1)`/`(T2)` suffix) → "By rule ID" summary with counts 5. **Group by field[5]** → "By source file (hotspots)" table ranked by count 6. For violations requiring source code (per Step 4b thresholds): - Resolve field[5] to absolute path: `get_files -filter {NAME =~ */field[5]}` - Call `read_file(resolved_path, field[6]-5, field[6]+5)` to get real code context 7. Use field[0] to dispatch to the correct handler in [violation-handlers.md](violation-handlers.md) Verify: Line count in CSV matches Vivado's stated total. ### Step 4b: Report Strategy (Always Apply) The following strategy applies to **every** lint run, regardless of violation count. The depth of each element scales with the size of the report — a 5-violation design gets a compact version; a 6,000-violation design gets full hotspot tables and tiered recommendations. 1. **Summary tables:** Always include two summary views: - **By rule ID** — table with counts, percentages, and severity. Gives an at-a-glance view of violation types for any design size. - **By source file (hotspots)** — rank files by violation count. For ≤20 total violations, a simple "files affected" list suffices. For >20, present a "Top N Files" table showing file, count, and dominant rule IDs — the top 5–10 files often account for 80%+ of violations. 2. **Critical-first prioritization:** Always address CRITICAL WARNINGs individually with full fix recommendations (including real source code — see Step 5). For WARNINGs and INFO: - ≤50 total violations → report each individually with source code - 51–200 → report all CRITICAL/WARNING individually; group INFO with representative examples and "N more in these files" summaries - >200 → report all CRITICALs individually; for WARNINGs, show representative examples per rule and group the rest with counts **At every tier, every code example shown — whether for an individual violation or a representative example — must use real source code read from the original RTL file.** "Representative" means you pick one actual violation from the group and show its real code; it does not mean fabricating a generic snippet. 3. **Waiver recommendations:** For high-count systemic patterns (e.g., RESET-2 on every register in a VHDL design that uses FPGA initialization instead of async reset), recommend `create_waiver` with clear justification. Signal when bulk waivers are appropriate vs when individual fixes are needed. 4. **Actionable tiers:** Structure the Recommendations section as: - **Immediate** — Critical warnings (fix before synthesis) - **High-impact quick wins** — Low-count violations with easy fixes (ASSIGN-12, INFER-3) - **Design cleanup** — Bulk warnings, often requiring team discussion (RESET-2, ASSIGN-5/6) - **Accept/waive** — Expected violations from standard interfaces (AXI unused fields, etc.) ### Step 5: Generate Markdown Report For **each** violation reported individually, and for each representative example chosen from a grouped set (per Step 4b thresholds): 1. Identify rule ID from **field[0]** (e.g., ASSIGN-3, INFER-1, RESET-2, CLOCK-1) 2. **Read the original source code** using **field[5]** (file_name) and **field[6]** (line_number): - Resolve field[5] to full path: `get_files -filter {NAME =~ */field[5]}` - Call `read_file(resolved_path, field[6]-5, field[6]+5)` for ±5 lines of context - This real code is used in both "Problematic Code" and "Recommended Fix" diff blocks - **Never fabricate or paraphrase code** 3. Check if resolution guide exists → `resolution/.md` (strip T1/T2 suffix from field[0]) 4. **If guide exists:** load with `read_file`, apply fix template from guide 5. **If no guide:** use vivado_doc_search for UG901 best practices 6. Use **field[2]** (rtl_name) and **field[3]** (info) for violation-specific context in the report 7. Format using the per-violation template in [report-format.md](report-format.md) **Violation handler details:** See [violation-handlers.md](violation-handlers.md) for per-rule-ID instructions (fix strategies, alternative options, IEEE references). **Formatting rules:** - Use `diff` syntax for all code blocks (never `verilog`/`vhdl`) - Every `-` line must have inline comment explaining removal - Every `+` line should have inline comment explaining fix - Use **workspace-relative paths** for file links — see [report-format.md](report-format.md) **Report templates:** See [examples/report-violations.md](examples/report-violations.md) and [examples/report-clean.md](examples/report-clean.md). Save report to: `vivado_agentic_ai_reports/rtl-lint/rtl_lint_report.md` Verify: Report file > 1KB, violation count matches Vivado output exactly. ### Step 6: Verify Report Accuracy 1. Re-read Vivado's `"Total of X linter message(s) generated"` 2. Count violations in your markdown report 3. Confirm: markdown count == Vivado's X 4. If mismatch → identify and remove fabricated violations, regenerate 5. If X = 0 → report must state "0 lint violations found" --- ## Inputs | Input | Type | Default | Description | |-------|------|---------|-------------| | project_path | path | `pwd` (or `$ARGUMENTS`) | Project directory or workspace root | | top_module | string | Auto-detect from .xpr | Top-level module name | | part_number | string | Auto-detect or `xc7k70tfbg676-2` | Target FPGA part | | report_dir | string | `vivado_agentic_ai_reports/rtl-lint` | Output directory | | severity_filter | enum | `ALL` | Minimum severity: CRITICAL, WARNING, INFO, ALL | --- ## Output **Location:** `vivado_agentic_ai_reports/rtl-lint/` ``` vivado_agentic_ai_reports/ └── rtl-lint/ ├── lint_report.rpt ← Raw Vivado linter output └── rtl_lint_report.md ← AI-generated analysis with fixes ``` --- ## Supporting Files | File | Purpose | |------|---------| | [tcl-reference.md](tcl-reference.md) | TCL command blocks for all workflow steps | | [violation-handlers.md](violation-handlers.md) | Per-rule-ID fix instructions and dispatch table | | [report-format.md](report-format.md) | Diff syntax rules, file link format, section template | | [resolution-guide.md](resolution-guide.md) | Resolution guide workflow and available guides list | | [examples/report-violations.md](examples/report-violations.md) | Example report with violations | | [examples/report-clean.md](examples/report-clean.md) | Example report for clean design | | `resolution/.md` | Individual validated fix templates | --- ## References Access via **vivado_doc_search** tool: - **UG901**: Vivado Synthesis User Guide (HDL Coding Techniques) - **UG906**: Vivado Design Analysis and Closure Techniques - **UG949**: UltraFast Design Methodology Guide --- ## Version History | Version | Date | Changes | |---------|------|---------| | 2.1.0 | 2026-03-11 | Fixed violation handler descriptions; added INFER-1/2/3, CLOCK-1 handlers; created INFER-3 resolution guide; added T1/T2 parsing docs; added high-violation-count scalability strategy | | 2.0.0 | 2026-03-10 | Restructured per Anthropic skill best practices; extracted supporting files | | 1.1.0 | 2026-03-10 | Promoted to standalone skill (moved out of rtl-assistant) | | 1.0.0 | 2026-01-16 | Converted to standardized template format | | 0.9.0 | 2025-12-01 | Added vivado_doc_search integration for documentation | | 0.8.0 | 2025-11-15 | Initial release as rtl-assistant subskill |