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.
Files changed (252) hide show
  1. chipgraph/__init__.py +7 -0
  2. chipgraph/adapters/__init__.py +1 -0
  3. chipgraph/adapters/format/__init__.py +6 -0
  4. chipgraph/adapters/format/__main__.py +16 -0
  5. chipgraph/adapters/format/_yaml_lines.py +54 -0
  6. chipgraph/adapters/format/chip_yaml.py +323 -0
  7. chipgraph/adapters/format/qsoc_contract.py +246 -0
  8. chipgraph/adapters/llm/__init__.py +58 -0
  9. chipgraph/adapters/llm/_budget.py +173 -0
  10. chipgraph/adapters/llm/_errors.py +50 -0
  11. chipgraph/adapters/llm/_nda.py +30 -0
  12. chipgraph/adapters/llm/_retry.py +85 -0
  13. chipgraph/adapters/llm/anthropic.py +247 -0
  14. chipgraph/adapters/llm/decide_backend.py +179 -0
  15. chipgraph/adapters/llm/fake.py +87 -0
  16. chipgraph/adapters/llm/guard.py +68 -0
  17. chipgraph/adapters/parser/__init__.py +22 -0
  18. chipgraph/adapters/parser/cocotb.py +304 -0
  19. chipgraph/adapters/parser/generic_regex.py +75 -0
  20. chipgraph/adapters/parser/verible.py +55 -0
  21. chipgraph/adapters/parser/verilator.py +46 -0
  22. chipgraph/adapters/review/__init__.py +7 -0
  23. chipgraph/adapters/review/file.py +123 -0
  24. chipgraph/adapters/review/github.py +886 -0
  25. chipgraph/adapters/runner/__init__.py +7 -0
  26. chipgraph/adapters/runner/local.py +122 -0
  27. chipgraph/adapters/runtime/__init__.py +5 -0
  28. chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
  29. chipgraph/adapters/runtime/claude_code/agents.py +158 -0
  30. chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
  31. chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
  32. chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
  33. chipgraph/adapters/runtime/claude_code/loop.py +342 -0
  34. chipgraph/adapters/runtime/claude_code/previous.py +419 -0
  35. chipgraph/adapters/runtime/claude_code/review.py +492 -0
  36. chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
  37. chipgraph/adapters/runtime/claude_code/service.py +2285 -0
  38. chipgraph/adapters/tool/__init__.py +7 -0
  39. chipgraph/adapters/tool/cmd.py +229 -0
  40. chipgraph/adapters/tool/edalize.py +773 -0
  41. chipgraph/adapters/tool/filelist.py +259 -0
  42. chipgraph/adapters/tool/pyslang.py +485 -0
  43. chipgraph/adapters/vcs/__init__.py +1 -0
  44. chipgraph/adapters/vcs/git.py +521 -0
  45. chipgraph/app/__init__.py +10 -0
  46. chipgraph/app/baseline.py +339 -0
  47. chipgraph/app/build.py +332 -0
  48. chipgraph/app/checks.py +202 -0
  49. chipgraph/app/context.py +191 -0
  50. chipgraph/app/errors.py +11 -0
  51. chipgraph/app/executors.py +79 -0
  52. chipgraph/app/findings.py +203 -0
  53. chipgraph/app/ingest.py +523 -0
  54. chipgraph/checks/__init__.py +52 -0
  55. chipgraph/checks/_cdc_analyze.py +272 -0
  56. chipgraph/checks/_cdc_elaborate.py +455 -0
  57. chipgraph/checks/_common.py +268 -0
  58. chipgraph/checks/_cross.py +96 -0
  59. chipgraph/checks/_model.py +64 -0
  60. chipgraph/checks/_naming_pyslang.py +235 -0
  61. chipgraph/checks/_naming_rules.py +127 -0
  62. chipgraph/checks/_xref_syntax.py +260 -0
  63. chipgraph/checks/cdc_struct.py +235 -0
  64. chipgraph/checks/connect.py +353 -0
  65. chipgraph/checks/cross_chip.py +454 -0
  66. chipgraph/checks/duplicate.py +153 -0
  67. chipgraph/checks/filelist.py +323 -0
  68. chipgraph/checks/format.py +268 -0
  69. chipgraph/checks/generated.py +151 -0
  70. chipgraph/checks/hardcode.py +233 -0
  71. chipgraph/checks/layout.py +155 -0
  72. chipgraph/checks/naming.py +311 -0
  73. chipgraph/checks/plan_check.py +901 -0
  74. chipgraph/checks/ports_diff.py +203 -0
  75. chipgraph/checks/spec_schema.py +277 -0
  76. chipgraph/checks/style.py +910 -0
  77. chipgraph/checks/tb_static.py +599 -0
  78. chipgraph/checks/trace.py +224 -0
  79. chipgraph/checks/vendor_guard.py +369 -0
  80. chipgraph/checks/wrap_check.py +486 -0
  81. chipgraph/cli/__init__.py +1688 -0
  82. chipgraph/cli/next_steps.py +137 -0
  83. chipgraph/core/__init__.py +1 -0
  84. chipgraph/core/config/__init__.py +93 -0
  85. chipgraph/core/config/defaults.py +14 -0
  86. chipgraph/core/config/errors.py +32 -0
  87. chipgraph/core/config/loader.py +766 -0
  88. chipgraph/core/config/models.py +845 -0
  89. chipgraph/core/config/templates.py +340 -0
  90. chipgraph/core/contracts/__init__.py +75 -0
  91. chipgraph/core/contracts/_paths.py +26 -0
  92. chipgraph/core/contracts/agent.py +49 -0
  93. chipgraph/core/contracts/artifact.py +68 -0
  94. chipgraph/core/contracts/check.py +76 -0
  95. chipgraph/core/contracts/event.py +70 -0
  96. chipgraph/core/contracts/export.py +128 -0
  97. chipgraph/core/contracts/finding.py +171 -0
  98. chipgraph/core/contracts/gate.py +50 -0
  99. chipgraph/core/contracts/rule.py +177 -0
  100. chipgraph/core/contracts/types.py +45 -0
  101. chipgraph/core/engine/__init__.py +70 -0
  102. chipgraph/core/engine/agent_rule.py +1362 -0
  103. chipgraph/core/engine/baseline.py +283 -0
  104. chipgraph/core/engine/decide.py +462 -0
  105. chipgraph/core/engine/fanout.py +1358 -0
  106. chipgraph/core/engine/gate.py +170 -0
  107. chipgraph/core/engine/graph.py +647 -0
  108. chipgraph/core/engine/records.py +60 -0
  109. chipgraph/core/engine/rules.py +92 -0
  110. chipgraph/core/engine/scheduler.py +637 -0
  111. chipgraph/core/engine/select.py +1239 -0
  112. chipgraph/core/engine/targets.py +342 -0
  113. chipgraph/core/model/__init__.py +98 -0
  114. chipgraph/core/model/entities.py +244 -0
  115. chipgraph/core/model/export.py +16 -0
  116. chipgraph/core/model/ingest.py +693 -0
  117. chipgraph/core/model/json_value.py +4 -0
  118. chipgraph/core/model/keys.py +95 -0
  119. chipgraph/core/model/model.py +153 -0
  120. chipgraph/core/model/provenance.py +27 -0
  121. chipgraph/core/model/query.py +583 -0
  122. chipgraph/core/model/registry.py +80 -0
  123. chipgraph/core/model/relations.py +61 -0
  124. chipgraph/core/model/store.py +279 -0
  125. chipgraph/core/plugin_api/__init__.py +81 -0
  126. chipgraph/core/plugin_api/local.py +167 -0
  127. chipgraph/core/plugin_api/pack.py +159 -0
  128. chipgraph/core/plugin_api/protocols.py +177 -0
  129. chipgraph/core/plugin_api/registry.py +111 -0
  130. chipgraph/core/plugin_api/types.py +94 -0
  131. chipgraph/core/runtime/__init__.py +41 -0
  132. chipgraph/core/runtime/executor.py +79 -0
  133. chipgraph/core/runtime/queue.py +565 -0
  134. chipgraph/core/runtime/roles/__init__.py +91 -0
  135. chipgraph/core/runtime/roles/data/author.md +41 -0
  136. chipgraph/core/runtime/roles/data/critic.md +56 -0
  137. chipgraph/core/runtime/roles/data/planner.md +38 -0
  138. chipgraph/core/runtime/roles/data/researcher.md +37 -0
  139. chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
  140. chipgraph/core/runtime/roles/data/tb-author.md +48 -0
  141. chipgraph/core/runtime/roles/data/triage.md +34 -0
  142. chipgraph/core/runtime/roles/policy.py +299 -0
  143. chipgraph/core/runtime/roles/registry.py +52 -0
  144. chipgraph/core/runtime/roles/review.py +275 -0
  145. chipgraph/core/runtime/roles/schema.py +82 -0
  146. chipgraph/core/runtime/roles/skills.py +134 -0
  147. chipgraph/core/runtime/roles/spec.py +232 -0
  148. chipgraph/core/state/__init__.py +1 -0
  149. chipgraph/core/state/artifacts.py +156 -0
  150. chipgraph/core/state/backend.py +117 -0
  151. chipgraph/core/state/findings.py +151 -0
  152. chipgraph/core/state/handoff.py +411 -0
  153. chipgraph/core/state/idempotency.py +59 -0
  154. chipgraph/core/state/journal.py +238 -0
  155. chipgraph/core/state/layout.py +82 -0
  156. chipgraph/core/state/lock.py +195 -0
  157. chipgraph/core/state/trace.py +225 -0
  158. chipgraph/learn/__init__.py +96 -0
  159. chipgraph/learn/draft.py +187 -0
  160. chipgraph/learn/infer.py +580 -0
  161. chipgraph/learn/models.py +145 -0
  162. chipgraph/learn/try_run.py +228 -0
  163. chipgraph/mcp/__init__.py +12 -0
  164. chipgraph/mcp/model_tools.py +167 -0
  165. chipgraph/mcp/server.py +787 -0
  166. chipgraph/orgs/qnsc/naming-v1.yml +113 -0
  167. chipgraph/orgs/qnsc/profile.yml +5 -0
  168. chipgraph/packs/__init__.py +8 -0
  169. chipgraph/packs/assist/__init__.py +16 -0
  170. chipgraph/packs/assist/ask/__init__.py +58 -0
  171. chipgraph/packs/assist/ask/_db.py +57 -0
  172. chipgraph/packs/assist/ask/_project.py +230 -0
  173. chipgraph/packs/assist/ask/answer.py +266 -0
  174. chipgraph/packs/assist/ask/check.py +182 -0
  175. chipgraph/packs/assist/ask/contract.py +115 -0
  176. chipgraph/packs/assist/ask/documents.py +157 -0
  177. chipgraph/packs/assist/ask/fts.py +80 -0
  178. chipgraph/packs/assist/ask/retrieve.py +331 -0
  179. chipgraph/packs/assist/audit/__init__.py +383 -0
  180. chipgraph/packs/assist/pack.yml +13 -0
  181. chipgraph/packs/assist/triage/__init__.py +64 -0
  182. chipgraph/packs/assist/triage/contract.py +94 -0
  183. chipgraph/packs/assist/triage/parse.py +371 -0
  184. chipgraph/packs/assist/triage/question.py +384 -0
  185. chipgraph/packs/assist/triage/report.py +160 -0
  186. chipgraph/packs/assist/triage/rules.py +447 -0
  187. chipgraph/packs/assist/triage/run.py +224 -0
  188. chipgraph/packs/digital_rtl/__init__.py +19 -0
  189. chipgraph/packs/digital_rtl/layout.py +144 -0
  190. chipgraph/packs/digital_rtl/pack.yml +25 -0
  191. chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
  192. chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
  193. chipgraph/packs/digital_rtl/plan/context.py +82 -0
  194. chipgraph/packs/digital_rtl/plan/expand.py +94 -0
  195. chipgraph/packs/digital_rtl/plan/model.py +150 -0
  196. chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
  197. chipgraph/packs/digital_rtl/plan/show.py +176 -0
  198. chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
  199. chipgraph/packs/digital_rtl/pr/body.py +165 -0
  200. chipgraph/packs/digital_rtl/pr/gate.py +110 -0
  201. chipgraph/packs/digital_rtl/pr/open.py +397 -0
  202. chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
  203. chipgraph/packs/digital_rtl/rtl/context.py +272 -0
  204. chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
  205. chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
  206. chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
  207. chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
  208. chipgraph/packs/digital_rtl/rules/review.yml +31 -0
  209. chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
  210. chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
  211. chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
  212. chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
  213. chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
  214. chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
  215. chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
  216. chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
  217. chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
  218. chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
  219. chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
  220. chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
  221. chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
  222. chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
  223. chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
  224. chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
  225. chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
  226. chipgraph/packs/dv/__init__.py +15 -0
  227. chipgraph/packs/dv/declaration.py +392 -0
  228. chipgraph/packs/dv/interface.py +596 -0
  229. chipgraph/packs/dv/pack.yml +14 -0
  230. chipgraph/packs/dv/rules/tb_module.yml +42 -0
  231. chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
  232. chipgraph/packs/spec_core/__init__.py +9 -0
  233. chipgraph/packs/spec_core/extract/__init__.py +12 -0
  234. chipgraph/packs/spec_core/extract/_markdown.py +371 -0
  235. chipgraph/packs/spec_core/extract/_text.py +116 -0
  236. chipgraph/packs/spec_core/extract/mas.py +975 -0
  237. chipgraph/packs/spec_core/gen/__init__.py +11 -0
  238. chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
  239. chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
  240. chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
  241. chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
  242. chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
  243. chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
  244. chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
  245. chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
  246. chipgraph/packs/spec_core/pack.yml +6 -0
  247. chipgraph/py.typed +0 -0
  248. chipgraph-0.1.0.dist-info/METADATA +53 -0
  249. chipgraph-0.1.0.dist-info/RECORD +252 -0
  250. chipgraph-0.1.0.dist-info/WHEEL +4 -0
  251. chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
  252. 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`."""