Back to Learn

Closure & enterprise workflow

Schematic Issue Highlighting: Mapping Findings to RTL Nodes

alu.v:42 - inferred latch on y is a stack trace. ASIC-trained engineers do not think in stack traces — they think in cells, fanouts, and nets. The same finding overlayed onto a generated schematic, with the offending cell circled in red, closes the gap between source and silicon in one glance. This page explains how the bridge between the two views is built, what it can do today, and where it falls back to best effort.

Why visual matters

A senior IC verification engineer has spent a career reasoning about hardware in a particular shape. They think in flip-flops, combinational cones, fanout trees, and clock domains. The textual form of a finding — rule ID, file, line, signal — is a lossy projection of that mental model into the language of a software bug tracker. The engineer who knows the design well can rebuild the schematic in their head from the textual form, but it is work, and it is the kind of work that adds friction to every finding triage.

The visual form does the rebuild for them. A latch inferred on a control signal is one cell in the netlist; an undriven net is a single floating wire; a width-mismatch is a fan-in or fan-out mismatch you can see at a glance. Showing the netlist with the offending node highlighted in colour matches how the engineer already thinks about the design, which makes triage faster and the diagnosis more reliable.

The pipeline behind the schematic

Three artifacts feed the highlighted view, in order:

  1. Yosys JSON. The design-structure engine runs a Yosys script that elaborates the module of interest and emits the netlist as JSON. This is the same JSON nextpnr consumes; it is well-defined, stable across releases, and contains the cell, wire, and port records the schematic needs.
  2. netlistsvg SVG. netlistsvg renders the JSON to an SVG using the same Yosys-shipped digital schematic conventions you see in the textbooks. Each cell and net in the JSON ends up as an SVG element with a stable id attribute keyed off the JSON record.
  3. Highlight overlay. The schematic_highlight service takes the candidate signal names from the latest analysis and CDC findings and matches them against the <text> labels in the (re-sanitized) SVG. Each matching text element gets a data-cv-highlight-id attribute, and the service returns the annotated SVG plus a highlights list and an unmatched_targets list.

The Yosys side of the pipeline is a small script — the same one a designer would run by hand to inspect a module:

# design-structure pass
read_verilog -sv rtl/uart_rx.sv
hierarchy -check -top uart_rx
proc; opt; fsm; opt; memory; opt
write_json out/uart_rx.json   # consumed by netlistsvg

The response from the highlight endpoint — the SVG plus the two lists — is what the frontend consumes:

// GET /api/schematic/{project_id}/highlight
{
  "project_id": "proj_19f201",
  "svg": "<svg ...>...<text data-cv-highlight-id=\"cvh-0\">data_out</text>...</svg>",
  "design_structure_id": "ds_4f0a",
  "analysis_id": "an_8821",
  "cdc_result_id": "cdc_5510",
  "highlights": [
    {
      "signal":      "data_out",
      "rule":        "inferred_latch",
      "severity":    "warn",
      "message":     "data_out latches when state_in is 2'b11",
      "source_file": "rtl/uart_rx.sv",
      "source_line": 9,
      "id":          "cvh-0",
      "selector":    "[data-cv-highlight-id=\"cvh-0\"]",
      "matched_text": ["data_out"],
      "match_count": 1
    }
  ],
  "unmatched_targets": [
    { "signal": "tmp", "rule": "width_mismatch", "source_file": "rtl/uart_rx.sv", "source_line": 14 }
  ]
}

The matching problem

Going from a finding to a place on the diagram is the interesting part. ChipVerify does it by name, not by Yosys cell IDs or src attributes: it extracts candidate signal names from each finding and looks for those names in the SVG’s text labels. That keeps the bridge simple and tool-agnostic, at the cost of being a best-effort text match rather than a structural one.

  • Candidate names. For an analysis issue the service reads the target, signal, net, and port fields; for a CDC violation it reads the source/destination signal and domain fields. Hierarchical and slash-scoped names are reduced to their bare leaf identifier so they can match a label.
  • Matched. When a candidate name matches one or more <text> labels, those elements are tagged with a shared data-cv-highlight-id and an entry is added to highlights with the rule, severity, message, source file/line, the matched text, and a CSS selector.
  • Unmatched. When a finding’s signal name does not appear anywhere in the SVG text, it goes into unmatched_targets instead. The finding is still real — it just has no label on this diagram to attach to.

Because the match is on text, not on a structural cell ID, the service never claims to have found the exact gate — it annotates the labels that carry the signal’s name. It does not move, recolor, or restyle any geometry; it only appends safe data/class attributes after re-sanitizing the stored SVG.

The matcher, in pseudocode

# schematic_highlight service - simplified pseudocode
def build_schematic_highlight(project_id):
    svg     = sanitize(latest_design_structure_svg(project_id))   # raises if none
    targets = signal_names_from(latest_analysis(project_id),
                                latest_cdc(project_id))           # finding -> candidate names
    highlights, unmatched = [], []
    for target in targets:
        # match the candidate name against the SVG's <text> node labels
        matched = [t for t in svg_text_nodes(svg)
                   if name_matches_label(target.signal, t.label)]
        if not matched:
            unmatched.append(target.metadata())                  # name not on the diagram
            continue
        for node in matched:
            node.set("data-cv-highlight-id", target.id)          # tag, do not move/recolor
        highlights.append(target.metadata() | {"matched_text": labels(matched)})
    return {"svg": serialize(svg), "highlights": highlights, "unmatched_targets": unmatched}

The whole thing is one pass over the targets and one pass over the SVG text nodes. There is no Yosys src-attribute parsing and no netname lookup — the SVG is treated as untrusted text, re-sanitized, and matched by signal name only.

What the engineer sees

The frontend renders the annotated SVG inline and uses the highlights list to mark the labels that matched a finding, with the rule, severity, and source line available from each highlight entry. Findings whose signal names did not appear on the diagram show up in the unmatched_targets list so they are not silently lost.

The endpoint is a read-only presentation surface. It does not provide click-through-to-code, and it does not expose waiver or fix actions — those live on the issues table and the dedicated waiver/fix-verify flows, not on the schematic response.

Limitations

  • Needs design-structure to have run. No JSON, no SVG, no highlights. The pipeline runs design-structure opportunistically; if the engine is disabled or the run failed, the schematic tab degrades to a notice and a re-run button.
  • Multiple modules per file. The current filename-to-module bridge picks the dominant module. Files declaring two or more peers will misroute findings on the non-dominant module. Workaround: prefer one module per file in new code (this is also a Verible-style guideline).
  • Macros and generates. A finding inside a heavily-macroed file may end up on a synthetic line number that does not match the editor’s view. Yosys’src attribute comes from the post-preprocess source; the mapper does its best to walk back through preprocessor line directives but is not perfect.
  • Top-level only by default. The schematic renders the elaborated top of the module under inspection. Hierarchical drill-down is supported per-cell, but the cross-module finding mapping is shallow — a finding in an instantiated submodule is highlighted in the submodule’s own schematic view, not on the top-level cell that contains it.

Where this is going

The honest summary of the current implementation: a regex bridge from filename to module name plus a structural lookup of cell and wire IDs. It works on the overwhelming majority of synthesizable designs and produces the right highlight for the four highest-yield rule classes. What is on the roadmap, in priority order:

  • Full LSP-style code-to-schematic linking. Replace the regex bridge with a real symbol-to-cell index built from the parser’s AST. Multi-module files become trivial.
  • Cross-module highlighting. A finding in a leaf module shows up not just in the leaf schematic but on the cell instance in every parent that contains it.
  • Waveform-to-schematic linking. Jumping from a stuck-signal finding produced by waveform intelligence to the corresponding driver in the schematic.
  • Signal-following. Click a wire, see every cell driving and consuming it, with cone-shaped fanout/fanin highlighting.

Why this rounds out the closure dashboard

The textual report is the right artifact for diff-friendly review, CI integration, and the manager’s closure dashboard. The schematic view is the right artifact for the engineer staring at a finding and asking "where is this in my design?" Having both available, with a stable bidirectional link, means each consumer gets the form they think in. The closure number on the front page stays the same; the time to triage the underlying findings goes down.

Related reading

Try it on your project

Paste a GitHub URL at chipverify.ai/tinytapeout (free) for a one-shot scan, or sign in at chipverify.ai/dashboard for the schematic tab with click-through highlighting on every finding.

See your findings on the netlist

Sign in and ChipVerify AI maps each finding onto a generated schematic so the offending cell and net light up where the bug is — pre-signoff evidence and visualization, not a foundry signoff.