chipgraph 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chipgraph/__init__.py +7 -0
- chipgraph/adapters/__init__.py +1 -0
- chipgraph/adapters/format/__init__.py +6 -0
- chipgraph/adapters/format/__main__.py +16 -0
- chipgraph/adapters/format/_yaml_lines.py +54 -0
- chipgraph/adapters/format/chip_yaml.py +323 -0
- chipgraph/adapters/format/qsoc_contract.py +246 -0
- chipgraph/adapters/llm/__init__.py +58 -0
- chipgraph/adapters/llm/_budget.py +173 -0
- chipgraph/adapters/llm/_errors.py +50 -0
- chipgraph/adapters/llm/_nda.py +30 -0
- chipgraph/adapters/llm/_retry.py +85 -0
- chipgraph/adapters/llm/anthropic.py +247 -0
- chipgraph/adapters/llm/decide_backend.py +179 -0
- chipgraph/adapters/llm/fake.py +87 -0
- chipgraph/adapters/llm/guard.py +68 -0
- chipgraph/adapters/parser/__init__.py +22 -0
- chipgraph/adapters/parser/cocotb.py +304 -0
- chipgraph/adapters/parser/generic_regex.py +75 -0
- chipgraph/adapters/parser/verible.py +55 -0
- chipgraph/adapters/parser/verilator.py +46 -0
- chipgraph/adapters/review/__init__.py +7 -0
- chipgraph/adapters/review/file.py +123 -0
- chipgraph/adapters/review/github.py +886 -0
- chipgraph/adapters/runner/__init__.py +7 -0
- chipgraph/adapters/runner/local.py +122 -0
- chipgraph/adapters/runtime/__init__.py +5 -0
- chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
- chipgraph/adapters/runtime/claude_code/agents.py +158 -0
- chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
- chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
- chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
- chipgraph/adapters/runtime/claude_code/loop.py +342 -0
- chipgraph/adapters/runtime/claude_code/previous.py +419 -0
- chipgraph/adapters/runtime/claude_code/review.py +492 -0
- chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
- chipgraph/adapters/runtime/claude_code/service.py +2285 -0
- chipgraph/adapters/tool/__init__.py +7 -0
- chipgraph/adapters/tool/cmd.py +229 -0
- chipgraph/adapters/tool/edalize.py +773 -0
- chipgraph/adapters/tool/filelist.py +259 -0
- chipgraph/adapters/tool/pyslang.py +485 -0
- chipgraph/adapters/vcs/__init__.py +1 -0
- chipgraph/adapters/vcs/git.py +521 -0
- chipgraph/app/__init__.py +10 -0
- chipgraph/app/baseline.py +339 -0
- chipgraph/app/build.py +332 -0
- chipgraph/app/checks.py +202 -0
- chipgraph/app/context.py +191 -0
- chipgraph/app/errors.py +11 -0
- chipgraph/app/executors.py +79 -0
- chipgraph/app/findings.py +203 -0
- chipgraph/app/ingest.py +523 -0
- chipgraph/checks/__init__.py +52 -0
- chipgraph/checks/_cdc_analyze.py +272 -0
- chipgraph/checks/_cdc_elaborate.py +455 -0
- chipgraph/checks/_common.py +268 -0
- chipgraph/checks/_cross.py +96 -0
- chipgraph/checks/_model.py +64 -0
- chipgraph/checks/_naming_pyslang.py +235 -0
- chipgraph/checks/_naming_rules.py +127 -0
- chipgraph/checks/_xref_syntax.py +260 -0
- chipgraph/checks/cdc_struct.py +235 -0
- chipgraph/checks/connect.py +353 -0
- chipgraph/checks/cross_chip.py +454 -0
- chipgraph/checks/duplicate.py +153 -0
- chipgraph/checks/filelist.py +323 -0
- chipgraph/checks/format.py +268 -0
- chipgraph/checks/generated.py +151 -0
- chipgraph/checks/hardcode.py +233 -0
- chipgraph/checks/layout.py +155 -0
- chipgraph/checks/naming.py +311 -0
- chipgraph/checks/plan_check.py +901 -0
- chipgraph/checks/ports_diff.py +203 -0
- chipgraph/checks/spec_schema.py +277 -0
- chipgraph/checks/style.py +910 -0
- chipgraph/checks/tb_static.py +599 -0
- chipgraph/checks/trace.py +224 -0
- chipgraph/checks/vendor_guard.py +369 -0
- chipgraph/checks/wrap_check.py +486 -0
- chipgraph/cli/__init__.py +1688 -0
- chipgraph/cli/next_steps.py +137 -0
- chipgraph/core/__init__.py +1 -0
- chipgraph/core/config/__init__.py +93 -0
- chipgraph/core/config/defaults.py +14 -0
- chipgraph/core/config/errors.py +32 -0
- chipgraph/core/config/loader.py +766 -0
- chipgraph/core/config/models.py +845 -0
- chipgraph/core/config/templates.py +340 -0
- chipgraph/core/contracts/__init__.py +75 -0
- chipgraph/core/contracts/_paths.py +26 -0
- chipgraph/core/contracts/agent.py +49 -0
- chipgraph/core/contracts/artifact.py +68 -0
- chipgraph/core/contracts/check.py +76 -0
- chipgraph/core/contracts/event.py +70 -0
- chipgraph/core/contracts/export.py +128 -0
- chipgraph/core/contracts/finding.py +171 -0
- chipgraph/core/contracts/gate.py +50 -0
- chipgraph/core/contracts/rule.py +177 -0
- chipgraph/core/contracts/types.py +45 -0
- chipgraph/core/engine/__init__.py +70 -0
- chipgraph/core/engine/agent_rule.py +1362 -0
- chipgraph/core/engine/baseline.py +283 -0
- chipgraph/core/engine/decide.py +462 -0
- chipgraph/core/engine/fanout.py +1358 -0
- chipgraph/core/engine/gate.py +170 -0
- chipgraph/core/engine/graph.py +647 -0
- chipgraph/core/engine/records.py +60 -0
- chipgraph/core/engine/rules.py +92 -0
- chipgraph/core/engine/scheduler.py +637 -0
- chipgraph/core/engine/select.py +1239 -0
- chipgraph/core/engine/targets.py +342 -0
- chipgraph/core/model/__init__.py +98 -0
- chipgraph/core/model/entities.py +244 -0
- chipgraph/core/model/export.py +16 -0
- chipgraph/core/model/ingest.py +693 -0
- chipgraph/core/model/json_value.py +4 -0
- chipgraph/core/model/keys.py +95 -0
- chipgraph/core/model/model.py +153 -0
- chipgraph/core/model/provenance.py +27 -0
- chipgraph/core/model/query.py +583 -0
- chipgraph/core/model/registry.py +80 -0
- chipgraph/core/model/relations.py +61 -0
- chipgraph/core/model/store.py +279 -0
- chipgraph/core/plugin_api/__init__.py +81 -0
- chipgraph/core/plugin_api/local.py +167 -0
- chipgraph/core/plugin_api/pack.py +159 -0
- chipgraph/core/plugin_api/protocols.py +177 -0
- chipgraph/core/plugin_api/registry.py +111 -0
- chipgraph/core/plugin_api/types.py +94 -0
- chipgraph/core/runtime/__init__.py +41 -0
- chipgraph/core/runtime/executor.py +79 -0
- chipgraph/core/runtime/queue.py +565 -0
- chipgraph/core/runtime/roles/__init__.py +91 -0
- chipgraph/core/runtime/roles/data/author.md +41 -0
- chipgraph/core/runtime/roles/data/critic.md +56 -0
- chipgraph/core/runtime/roles/data/planner.md +38 -0
- chipgraph/core/runtime/roles/data/researcher.md +37 -0
- chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
- chipgraph/core/runtime/roles/data/tb-author.md +48 -0
- chipgraph/core/runtime/roles/data/triage.md +34 -0
- chipgraph/core/runtime/roles/policy.py +299 -0
- chipgraph/core/runtime/roles/registry.py +52 -0
- chipgraph/core/runtime/roles/review.py +275 -0
- chipgraph/core/runtime/roles/schema.py +82 -0
- chipgraph/core/runtime/roles/skills.py +134 -0
- chipgraph/core/runtime/roles/spec.py +232 -0
- chipgraph/core/state/__init__.py +1 -0
- chipgraph/core/state/artifacts.py +156 -0
- chipgraph/core/state/backend.py +117 -0
- chipgraph/core/state/findings.py +151 -0
- chipgraph/core/state/handoff.py +411 -0
- chipgraph/core/state/idempotency.py +59 -0
- chipgraph/core/state/journal.py +238 -0
- chipgraph/core/state/layout.py +82 -0
- chipgraph/core/state/lock.py +195 -0
- chipgraph/core/state/trace.py +225 -0
- chipgraph/learn/__init__.py +96 -0
- chipgraph/learn/draft.py +187 -0
- chipgraph/learn/infer.py +580 -0
- chipgraph/learn/models.py +145 -0
- chipgraph/learn/try_run.py +228 -0
- chipgraph/mcp/__init__.py +12 -0
- chipgraph/mcp/model_tools.py +167 -0
- chipgraph/mcp/server.py +787 -0
- chipgraph/orgs/qnsc/naming-v1.yml +113 -0
- chipgraph/orgs/qnsc/profile.yml +5 -0
- chipgraph/packs/__init__.py +8 -0
- chipgraph/packs/assist/__init__.py +16 -0
- chipgraph/packs/assist/ask/__init__.py +58 -0
- chipgraph/packs/assist/ask/_db.py +57 -0
- chipgraph/packs/assist/ask/_project.py +230 -0
- chipgraph/packs/assist/ask/answer.py +266 -0
- chipgraph/packs/assist/ask/check.py +182 -0
- chipgraph/packs/assist/ask/contract.py +115 -0
- chipgraph/packs/assist/ask/documents.py +157 -0
- chipgraph/packs/assist/ask/fts.py +80 -0
- chipgraph/packs/assist/ask/retrieve.py +331 -0
- chipgraph/packs/assist/audit/__init__.py +383 -0
- chipgraph/packs/assist/pack.yml +13 -0
- chipgraph/packs/assist/triage/__init__.py +64 -0
- chipgraph/packs/assist/triage/contract.py +94 -0
- chipgraph/packs/assist/triage/parse.py +371 -0
- chipgraph/packs/assist/triage/question.py +384 -0
- chipgraph/packs/assist/triage/report.py +160 -0
- chipgraph/packs/assist/triage/rules.py +447 -0
- chipgraph/packs/assist/triage/run.py +224 -0
- chipgraph/packs/digital_rtl/__init__.py +19 -0
- chipgraph/packs/digital_rtl/layout.py +144 -0
- chipgraph/packs/digital_rtl/pack.yml +25 -0
- chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
- chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
- chipgraph/packs/digital_rtl/plan/context.py +82 -0
- chipgraph/packs/digital_rtl/plan/expand.py +94 -0
- chipgraph/packs/digital_rtl/plan/model.py +150 -0
- chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
- chipgraph/packs/digital_rtl/plan/show.py +176 -0
- chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
- chipgraph/packs/digital_rtl/pr/body.py +165 -0
- chipgraph/packs/digital_rtl/pr/gate.py +110 -0
- chipgraph/packs/digital_rtl/pr/open.py +397 -0
- chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
- chipgraph/packs/digital_rtl/rtl/context.py +272 -0
- chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
- chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
- chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
- chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
- chipgraph/packs/digital_rtl/rules/review.yml +31 -0
- chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
- chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
- chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
- chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
- chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
- chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
- chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
- chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
- chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
- chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
- chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
- chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
- chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
- chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
- chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
- chipgraph/packs/dv/__init__.py +15 -0
- chipgraph/packs/dv/declaration.py +392 -0
- chipgraph/packs/dv/interface.py +596 -0
- chipgraph/packs/dv/pack.yml +14 -0
- chipgraph/packs/dv/rules/tb_module.yml +42 -0
- chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
- chipgraph/packs/spec_core/__init__.py +9 -0
- chipgraph/packs/spec_core/extract/__init__.py +12 -0
- chipgraph/packs/spec_core/extract/_markdown.py +371 -0
- chipgraph/packs/spec_core/extract/_text.py +116 -0
- chipgraph/packs/spec_core/extract/mas.py +975 -0
- chipgraph/packs/spec_core/gen/__init__.py +11 -0
- chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
- chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
- chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
- chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
- chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
- chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
- chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
- chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
- chipgraph/packs/spec_core/pack.yml +6 -0
- chipgraph/py.typed +0 -0
- chipgraph-0.1.0.dist-info/METADATA +53 -0
- chipgraph-0.1.0.dist-info/RECORD +252 -0
- chipgraph-0.1.0.dist-info/WHEEL +4 -0
- chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
- chipgraph-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""RTL extractor using pyslang 12 as the parser.
|
|
2
|
+
|
|
3
|
+
Reads a Verilog/SystemVerilog design via filelists or source paths, extracts hierarchy,
|
|
4
|
+
ports (with directions and widths), parameters, instances, clock/reset signals (detected
|
|
5
|
+
by name patterns from the profile), and optionally FSM facts. Returns a `DesignModel`
|
|
6
|
+
with entities (module, port, parameter, clock, reset) and relations (contains, instantiates).
|
|
7
|
+
|
|
8
|
+
Diagnostics (parse errors, warnings) are returned alongside, never raised.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Iterable, Mapping
|
|
15
|
+
from contextlib import suppress
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Literal, NamedTuple
|
|
19
|
+
|
|
20
|
+
from pyslang import ast
|
|
21
|
+
from pyslang.driver import CommandLineOptions, Driver
|
|
22
|
+
|
|
23
|
+
from chipgraph.adapters.tool.filelist import Filelist, FilelistError, read_filelist
|
|
24
|
+
from chipgraph.core.model.entities import (
|
|
25
|
+
ClockEntity,
|
|
26
|
+
EntityBase,
|
|
27
|
+
ModuleEntity,
|
|
28
|
+
ParameterEntity,
|
|
29
|
+
PortEntity,
|
|
30
|
+
ResetEntity,
|
|
31
|
+
)
|
|
32
|
+
from chipgraph.core.model.keys import make_key
|
|
33
|
+
from chipgraph.core.model.model import DesignModel
|
|
34
|
+
from chipgraph.core.model.provenance import Provenance
|
|
35
|
+
from chipgraph.core.model.relations import Relation
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ParseDiagnostic(NamedTuple):
|
|
39
|
+
"""A parse error or warning with file:line context."""
|
|
40
|
+
|
|
41
|
+
file: str | None
|
|
42
|
+
line: int | None
|
|
43
|
+
severity: str # "error" or "warning"
|
|
44
|
+
message: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True, slots=True)
|
|
48
|
+
class PyslangExtractorOptions:
|
|
49
|
+
"""Options for pyslang extraction."""
|
|
50
|
+
|
|
51
|
+
timescale: str = "1ns/1ps"
|
|
52
|
+
"""Default timescale if source files lack one."""
|
|
53
|
+
|
|
54
|
+
clock_patterns: tuple[str, ...] = (r"^i_clk", r"^clk")
|
|
55
|
+
"""Regex patterns (anchored) to detect clock ports by name."""
|
|
56
|
+
|
|
57
|
+
reset_patterns: tuple[str, ...] = (r"^i_rst", r"^rst")
|
|
58
|
+
"""Regex patterns (anchored) to detect reset ports by name."""
|
|
59
|
+
|
|
60
|
+
defines: dict[str, str | None] | None = None
|
|
61
|
+
"""Additional preprocessor definitions."""
|
|
62
|
+
|
|
63
|
+
tops: tuple[str, ...] | None = None
|
|
64
|
+
"""Specific top modules to elaborate. If None, all are elaborated."""
|
|
65
|
+
|
|
66
|
+
root: Path | None = None
|
|
67
|
+
"""Project root: provenance paths under it are written relative to it."""
|
|
68
|
+
|
|
69
|
+
block: str | None = None
|
|
70
|
+
"""The model key prefix for `contains` relations (e.g., 'block:timer')."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PyslangExtractor:
|
|
74
|
+
"""Extracts RTL facts from a filelist using pyslang 12 as the elaborator."""
|
|
75
|
+
|
|
76
|
+
name = "pyslang"
|
|
77
|
+
|
|
78
|
+
def extract(self, path: Path) -> Iterable[Mapping[str, object]]:
|
|
79
|
+
"""The `Extractor` protocol: `path` is a filelist (paths relative to it) or one
|
|
80
|
+
source file; returns every entity and relation of the model as plain mappings.
|
|
81
|
+
"""
|
|
82
|
+
source: Path | Filelist = read_filelist(path) if path.suffix == ".f" else path
|
|
83
|
+
model, _ = self.extract_model(source if isinstance(source, Filelist) else [source])
|
|
84
|
+
facts: list[Mapping[str, object]] = [
|
|
85
|
+
e.model_dump(mode="json") for e in sorted(model.entities.values(), key=_by_key)
|
|
86
|
+
]
|
|
87
|
+
facts.extend(r.model_dump(mode="json") for r in model.relations)
|
|
88
|
+
return facts
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def extract_model(
|
|
92
|
+
cls,
|
|
93
|
+
filelist_or_sources: Path | Filelist | Iterable[Path],
|
|
94
|
+
*,
|
|
95
|
+
tops: tuple[str, ...] | None = None,
|
|
96
|
+
block: str | None = None,
|
|
97
|
+
timescale: str = "1ns/1ps",
|
|
98
|
+
defines: dict[str, str | None] | None = None,
|
|
99
|
+
clock_patterns: tuple[str, ...] = (r"^i_clk", r"^clk"),
|
|
100
|
+
reset_patterns: tuple[str, ...] = (r"^i_rst", r"^rst"),
|
|
101
|
+
root: Path | None = None,
|
|
102
|
+
) -> tuple[DesignModel, tuple[ParseDiagnostic, ...]]:
|
|
103
|
+
"""Extract RTL hierarchy, ports, parameters from a filelist or source paths.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
filelist_or_sources: A Path to a filelist, a Filelist object, or an Iterable
|
|
107
|
+
of source file Paths.
|
|
108
|
+
tops: Specific top-level modules to elaborate; if None, all are used.
|
|
109
|
+
block: Model key prefix for `contains` relations (e.g., "block:timer").
|
|
110
|
+
timescale: Default timescale (e.g., "1ns/1ps").
|
|
111
|
+
defines: Additional preprocessor definitions.
|
|
112
|
+
clock_patterns: Regex patterns to detect clock ports by name.
|
|
113
|
+
reset_patterns: Regex patterns to detect reset ports by name.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A tuple of (DesignModel, diagnostics). Diagnostics are never raised;
|
|
117
|
+
parse errors are returned alongside the partial model.
|
|
118
|
+
"""
|
|
119
|
+
opts = PyslangExtractorOptions(
|
|
120
|
+
timescale=timescale,
|
|
121
|
+
clock_patterns=clock_patterns,
|
|
122
|
+
reset_patterns=reset_patterns,
|
|
123
|
+
defines=defines,
|
|
124
|
+
tops=tops,
|
|
125
|
+
block=block,
|
|
126
|
+
root=root,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Parse filelist if a Path is given.
|
|
130
|
+
filelist = cls._read_filelist(filelist_or_sources)
|
|
131
|
+
|
|
132
|
+
# Run pyslang elaboration.
|
|
133
|
+
diagnostics, root_sym, src_mgr = cls._elaborate(filelist, opts)
|
|
134
|
+
|
|
135
|
+
# Walk the hierarchy and extract entities and relations.
|
|
136
|
+
entities, relations = cls._walk_hierarchy(root_sym, src_mgr, filelist, opts)
|
|
137
|
+
|
|
138
|
+
# Build and return the model.
|
|
139
|
+
model = DesignModel.build(entities, relations)
|
|
140
|
+
return model, tuple(diagnostics)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def _read_filelist(cls, filelist_or_sources: Path | Filelist | Iterable[Path]) -> Filelist:
|
|
144
|
+
"""Normalize input to a Filelist."""
|
|
145
|
+
if isinstance(filelist_or_sources, Filelist):
|
|
146
|
+
return filelist_or_sources
|
|
147
|
+
if isinstance(filelist_or_sources, Path):
|
|
148
|
+
try:
|
|
149
|
+
return read_filelist(filelist_or_sources)
|
|
150
|
+
except FilelistError as exc:
|
|
151
|
+
raise ValueError(f"failed to read filelist: {exc}") from exc
|
|
152
|
+
sources = tuple(Path(p) for p in filelist_or_sources)
|
|
153
|
+
return Filelist(sources=sources)
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def _elaborate(
|
|
157
|
+
cls, filelist: Filelist, opts: PyslangExtractorOptions
|
|
158
|
+
) -> tuple[list[ParseDiagnostic], object, object]:
|
|
159
|
+
"""Run pyslang elaboration on the filelist."""
|
|
160
|
+
diags: list[ParseDiagnostic] = []
|
|
161
|
+
|
|
162
|
+
driver = Driver()
|
|
163
|
+
driver.addStandardArgs()
|
|
164
|
+
|
|
165
|
+
# Build command line arguments as space-separated string.
|
|
166
|
+
args = ["slang", "--timescale", opts.timescale, "--ignore-unknown-modules", "-Wnone"]
|
|
167
|
+
|
|
168
|
+
# Add sources.
|
|
169
|
+
for src in filelist.sources:
|
|
170
|
+
args.append(str(src))
|
|
171
|
+
|
|
172
|
+
# Add include directories.
|
|
173
|
+
for inc in filelist.incdirs:
|
|
174
|
+
args.append(f"+incdir+{inc}")
|
|
175
|
+
|
|
176
|
+
# Add defines from filelist.
|
|
177
|
+
for name, value in filelist.defines.items():
|
|
178
|
+
if value is not None:
|
|
179
|
+
args.append(f"+define+{name}={value}")
|
|
180
|
+
else:
|
|
181
|
+
args.append(f"+define+{name}")
|
|
182
|
+
|
|
183
|
+
# Add defines from options.
|
|
184
|
+
if opts.defines:
|
|
185
|
+
for name, value in opts.defines.items():
|
|
186
|
+
if value is not None:
|
|
187
|
+
args.append(f"+define+{name}={value}")
|
|
188
|
+
else:
|
|
189
|
+
args.append(f"+define+{name}")
|
|
190
|
+
|
|
191
|
+
# Top modules: the caller's, else the filelist's own `--top-module`. Without one,
|
|
192
|
+
# slang treats every uninstantiated module (vendored IP, test helpers) as a top.
|
|
193
|
+
for top in opts.tops or filelist.tops:
|
|
194
|
+
args.append("--top")
|
|
195
|
+
args.append(top)
|
|
196
|
+
|
|
197
|
+
# Parse command line.
|
|
198
|
+
cmdline = " ".join(args)
|
|
199
|
+
if not driver.parseCommandLine(cmdline, CommandLineOptions()):
|
|
200
|
+
msg = "failed to parse command line"
|
|
201
|
+
diags.append(ParseDiagnostic(file=None, line=None, severity="error", message=msg))
|
|
202
|
+
return diags, None, None
|
|
203
|
+
|
|
204
|
+
driver.processOptions()
|
|
205
|
+
|
|
206
|
+
# Parse sources.
|
|
207
|
+
if not driver.parseAllSources():
|
|
208
|
+
msg = "failed to parse sources"
|
|
209
|
+
diags.append(ParseDiagnostic(file=None, line=None, severity="error", message=msg))
|
|
210
|
+
return diags, None, None
|
|
211
|
+
|
|
212
|
+
# Elaborate.
|
|
213
|
+
comp = driver.createCompilation()
|
|
214
|
+
src_mgr = comp.sourceManager
|
|
215
|
+
|
|
216
|
+
# Collect diagnostics.
|
|
217
|
+
for diag in comp.getAllDiagnostics():
|
|
218
|
+
severity = "error" if diag.isError() else "warning"
|
|
219
|
+
file = src_mgr.getFileName(diag.location) if diag.location else None
|
|
220
|
+
line = src_mgr.getLineNumber(diag.location) if diag.location else None
|
|
221
|
+
msg_str = str(driver.diagEngine.formatMessage(diag))
|
|
222
|
+
diags.append(ParseDiagnostic(file=file, line=line, severity=severity, message=msg_str))
|
|
223
|
+
|
|
224
|
+
root_sym = comp.getRoot()
|
|
225
|
+
return diags, root_sym, src_mgr
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def _walk_hierarchy(
|
|
229
|
+
cls,
|
|
230
|
+
root_sym: Any,
|
|
231
|
+
src_mgr: Any,
|
|
232
|
+
filelist: Filelist,
|
|
233
|
+
opts: PyslangExtractorOptions,
|
|
234
|
+
) -> tuple[list[EntityBase], list[Relation]]:
|
|
235
|
+
"""Walk the hierarchy and extract entities and relations."""
|
|
236
|
+
entities: list[EntityBase] = []
|
|
237
|
+
relations: list[Relation] = []
|
|
238
|
+
seen_modules: set[str] = set() # module keys, plus clock/reset keys emitted
|
|
239
|
+
|
|
240
|
+
# Compile clock and reset pattern matchers.
|
|
241
|
+
clock_matchers = [re.compile(p) for p in opts.clock_patterns]
|
|
242
|
+
reset_matchers = [re.compile(p) for p in opts.reset_patterns]
|
|
243
|
+
|
|
244
|
+
def matches_any(name: str, matchers: list[re.Pattern[str]]) -> bool:
|
|
245
|
+
return any(m.match(name) for m in matchers)
|
|
246
|
+
|
|
247
|
+
# Walk top instances.
|
|
248
|
+
if root_sym is None or not hasattr(root_sym, "topInstances"):
|
|
249
|
+
return entities, relations
|
|
250
|
+
|
|
251
|
+
for top_inst in root_sym.topInstances:
|
|
252
|
+
_walk_instance(
|
|
253
|
+
top_inst,
|
|
254
|
+
src_mgr,
|
|
255
|
+
entities,
|
|
256
|
+
relations,
|
|
257
|
+
seen_modules,
|
|
258
|
+
clock_matchers,
|
|
259
|
+
reset_matchers,
|
|
260
|
+
opts,
|
|
261
|
+
is_top=True,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return entities, relations
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _walk_instance(
|
|
268
|
+
inst: Any,
|
|
269
|
+
src_mgr: Any,
|
|
270
|
+
entities: list[EntityBase],
|
|
271
|
+
relations: list[Relation],
|
|
272
|
+
seen_modules: set[str],
|
|
273
|
+
clock_matchers: list[re.Pattern[str]],
|
|
274
|
+
reset_matchers: list[re.Pattern[str]],
|
|
275
|
+
opts: PyslangExtractorOptions,
|
|
276
|
+
*,
|
|
277
|
+
is_top: bool = False,
|
|
278
|
+
) -> None:
|
|
279
|
+
"""Recursively walk an instance and its children.
|
|
280
|
+
|
|
281
|
+
Clock and reset entities come only from the top module's ports: a port of a child
|
|
282
|
+
module that matches the patterns is marked (`attrs["clock_like"]` /
|
|
283
|
+
`attrs["reset_like"]`), but which top clock drives it needs connection tracing,
|
|
284
|
+
which is not done here.
|
|
285
|
+
"""
|
|
286
|
+
if not hasattr(inst, "body") or not hasattr(inst, "name"):
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
body = inst.body
|
|
290
|
+
definition = getattr(body, "definition", None)
|
|
291
|
+
def_name = definition.name if definition is not None else "unknown"
|
|
292
|
+
|
|
293
|
+
def locate(sym: Any) -> tuple[str | None, int | None]:
|
|
294
|
+
loc = getattr(sym, "location", None)
|
|
295
|
+
if not loc:
|
|
296
|
+
return None, None
|
|
297
|
+
return _display_path(src_mgr.getFileName(loc), opts.root), src_mgr.getLineNumber(loc)
|
|
298
|
+
|
|
299
|
+
# A module is described once, from its definition: ports and parameters of later
|
|
300
|
+
# instances of the same module are not repeated (the first elaboration wins, so a
|
|
301
|
+
# parameterised module keeps the widths of its first instance).
|
|
302
|
+
module_key = make_key("module", def_name)
|
|
303
|
+
first_visit = module_key not in seen_modules
|
|
304
|
+
file, line = locate(definition if definition is not None else inst)
|
|
305
|
+
if first_visit:
|
|
306
|
+
seen_modules.add(module_key)
|
|
307
|
+
module_entity = ModuleEntity(
|
|
308
|
+
key=module_key,
|
|
309
|
+
name=def_name,
|
|
310
|
+
file=file,
|
|
311
|
+
block=opts.block,
|
|
312
|
+
source=Provenance(file=file, line=line, extractor="pyslang"),
|
|
313
|
+
)
|
|
314
|
+
entities.append(module_entity)
|
|
315
|
+
|
|
316
|
+
# Extract ports.
|
|
317
|
+
if first_visit and hasattr(body, "portList"):
|
|
318
|
+
for port_sym in body.portList:
|
|
319
|
+
if not hasattr(port_sym, "kind"):
|
|
320
|
+
continue
|
|
321
|
+
if port_sym.kind != ast.SymbolKind.Port:
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
port_name = port_sym.name if hasattr(port_sym, "name") else None
|
|
325
|
+
if not port_name:
|
|
326
|
+
continue
|
|
327
|
+
file, line = locate(port_sym)
|
|
328
|
+
|
|
329
|
+
# Determine direction.
|
|
330
|
+
direction: Literal["input", "output", "inout"] | None = None
|
|
331
|
+
if hasattr(port_sym, "direction"):
|
|
332
|
+
# Extract direction from enum like "ArgumentDirection.Out" -> "Out"
|
|
333
|
+
dir_str = str(port_sym.direction).split(".")[-1].lower()
|
|
334
|
+
# Map direction names to standard port directions
|
|
335
|
+
if dir_str == "in":
|
|
336
|
+
direction = "input"
|
|
337
|
+
elif dir_str == "out":
|
|
338
|
+
direction = "output"
|
|
339
|
+
elif dir_str in ("inout", "ref"):
|
|
340
|
+
direction = "inout"
|
|
341
|
+
|
|
342
|
+
# Determine width.
|
|
343
|
+
width = None
|
|
344
|
+
if hasattr(port_sym, "type"):
|
|
345
|
+
type_str = str(port_sym.type)
|
|
346
|
+
# Try to extract width from type string (e.g., "logic[31:0]").
|
|
347
|
+
match = re.search(r"\[(\d+):0\]", type_str)
|
|
348
|
+
width = int(match.group(1)) + 1 if match else 1
|
|
349
|
+
|
|
350
|
+
# Detect clock and reset by name.
|
|
351
|
+
clock_key = None
|
|
352
|
+
reset_key = None
|
|
353
|
+
port_attrs: dict[str, bool] = {}
|
|
354
|
+
is_clock = any(m.match(port_name) for m in clock_matchers)
|
|
355
|
+
is_reset = any(m.match(port_name) for m in reset_matchers)
|
|
356
|
+
# Several top modules (e.g. vendored IP nobody instantiates yet) may each
|
|
357
|
+
# have a `clk_i`: the clock is one entity per name, from the first top seen.
|
|
358
|
+
if is_clock and is_top:
|
|
359
|
+
clock_key = make_key("clock", port_name)
|
|
360
|
+
if clock_key not in seen_modules:
|
|
361
|
+
seen_modules.add(clock_key)
|
|
362
|
+
entities.append(
|
|
363
|
+
ClockEntity(
|
|
364
|
+
key=clock_key,
|
|
365
|
+
name=port_name,
|
|
366
|
+
source=Provenance(file=file, line=line, extractor="pyslang"),
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
elif is_clock:
|
|
370
|
+
port_attrs["clock_like"] = True
|
|
371
|
+
if is_reset and is_top:
|
|
372
|
+
active_low = "_n" in port_name or "_inv" in port_name
|
|
373
|
+
reset_key = make_key("reset", port_name)
|
|
374
|
+
if reset_key not in seen_modules:
|
|
375
|
+
seen_modules.add(reset_key)
|
|
376
|
+
entities.append(
|
|
377
|
+
ResetEntity(
|
|
378
|
+
key=reset_key,
|
|
379
|
+
name=port_name,
|
|
380
|
+
active_low=active_low,
|
|
381
|
+
source=Provenance(file=file, line=line, extractor="pyslang"),
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
elif is_reset:
|
|
385
|
+
port_attrs["reset_like"] = True
|
|
386
|
+
|
|
387
|
+
port_key = make_key("port", def_name, port_name)
|
|
388
|
+
port_entity = PortEntity(
|
|
389
|
+
key=port_key,
|
|
390
|
+
name=port_name,
|
|
391
|
+
direction=direction,
|
|
392
|
+
width=width,
|
|
393
|
+
clock=clock_key,
|
|
394
|
+
reset=reset_key,
|
|
395
|
+
module=module_key,
|
|
396
|
+
attrs=dict(port_attrs),
|
|
397
|
+
source=Provenance(file=file, line=line, extractor="pyslang"),
|
|
398
|
+
)
|
|
399
|
+
entities.append(port_entity)
|
|
400
|
+
|
|
401
|
+
# Extract parameters.
|
|
402
|
+
if first_visit and hasattr(body, "parameters"):
|
|
403
|
+
for param_sym in body.parameters:
|
|
404
|
+
if not hasattr(param_sym, "name"):
|
|
405
|
+
continue
|
|
406
|
+
param_name = param_sym.name
|
|
407
|
+
file, line = locate(param_sym)
|
|
408
|
+
param_value = None
|
|
409
|
+
if hasattr(param_sym, "value"):
|
|
410
|
+
param_value = str(param_sym.value)
|
|
411
|
+
|
|
412
|
+
param_key = make_key("parameter", def_name, param_name)
|
|
413
|
+
param_entity = ParameterEntity(
|
|
414
|
+
key=param_key,
|
|
415
|
+
name=param_name,
|
|
416
|
+
value=param_value,
|
|
417
|
+
module=module_key,
|
|
418
|
+
source=Provenance(file=file, line=line, extractor="pyslang"),
|
|
419
|
+
)
|
|
420
|
+
entities.append(param_entity)
|
|
421
|
+
|
|
422
|
+
# Extract instantiations and recurse.
|
|
423
|
+
if hasattr(body, "visit"):
|
|
424
|
+
|
|
425
|
+
def visit_fn(sym: object) -> object:
|
|
426
|
+
if not hasattr(sym, "kind"):
|
|
427
|
+
return ast.VisitAction.Advance
|
|
428
|
+
if sym.kind == ast.SymbolKind.Instance:
|
|
429
|
+
child_inst = sym
|
|
430
|
+
if hasattr(child_inst, "name") and hasattr(child_inst, "body"):
|
|
431
|
+
child_def_name = (
|
|
432
|
+
child_inst.body.definition.name
|
|
433
|
+
if hasattr(child_inst.body, "definition")
|
|
434
|
+
else "unknown"
|
|
435
|
+
)
|
|
436
|
+
child_module_key = make_key("module", child_def_name)
|
|
437
|
+
|
|
438
|
+
# Create instantiates relation.
|
|
439
|
+
rel_attrs = {}
|
|
440
|
+
if hasattr(child_inst, "name"):
|
|
441
|
+
rel_attrs["instance_name"] = child_inst.name
|
|
442
|
+
if file:
|
|
443
|
+
rel_attrs["file"] = file
|
|
444
|
+
if line:
|
|
445
|
+
rel_attrs["line"] = line
|
|
446
|
+
|
|
447
|
+
relation = Relation(
|
|
448
|
+
kind="instantiates",
|
|
449
|
+
src=module_key,
|
|
450
|
+
dst=child_module_key,
|
|
451
|
+
attrs=rel_attrs,
|
|
452
|
+
source=Provenance(file=file, line=line, extractor="pyslang"),
|
|
453
|
+
)
|
|
454
|
+
relations.append(relation)
|
|
455
|
+
|
|
456
|
+
# Recurse.
|
|
457
|
+
_walk_instance(
|
|
458
|
+
child_inst,
|
|
459
|
+
src_mgr,
|
|
460
|
+
entities,
|
|
461
|
+
relations,
|
|
462
|
+
seen_modules,
|
|
463
|
+
clock_matchers,
|
|
464
|
+
reset_matchers,
|
|
465
|
+
opts,
|
|
466
|
+
)
|
|
467
|
+
return ast.VisitAction.Skip
|
|
468
|
+
return ast.VisitAction.Advance
|
|
469
|
+
|
|
470
|
+
with suppress(Exception):
|
|
471
|
+
# Silently skip if visit fails on this subtree.
|
|
472
|
+
body.visit(visit_fn)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _display_path(raw: str, root: Path | None) -> str:
|
|
476
|
+
"""`raw` relative to `root` (POSIX) when it is under it, else absolute."""
|
|
477
|
+
path = Path(raw).resolve()
|
|
478
|
+
if root is not None:
|
|
479
|
+
with suppress(ValueError):
|
|
480
|
+
return path.relative_to(root.resolve()).as_posix()
|
|
481
|
+
return path.as_posix()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _by_key(entity: EntityBase) -> str:
|
|
485
|
+
return entity.key
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""VCS adapters: version control operations the engine needs, behind `VcsAdapter`."""
|