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,455 @@
|
|
|
1
|
+
"""Elaborate a block's RTL with pyslang and expose the facts the CDC check needs.
|
|
2
|
+
|
|
3
|
+
The Design Model does not hold nets or connections (see `adapters/tool/pyslang.py`), so
|
|
4
|
+
the structural CDC check must look at the RTL itself. This module runs a real pyslang
|
|
5
|
+
*elaboration* (unlike `naming`/`connect`, which parse a single file's syntax tree) so
|
|
6
|
+
that named references resolve to symbols, instance port connections are known, and one
|
|
7
|
+
level of hierarchy can be followed.
|
|
8
|
+
|
|
9
|
+
Everything here is pure and deterministic and never raises on malformed input: parse or
|
|
10
|
+
elaboration errors come back as `Diagnostic`s alongside a best-effort model, never thrown.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Iterable
|
|
16
|
+
from contextlib import suppress
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from pyslang import Bag
|
|
22
|
+
from pyslang.ast import (
|
|
23
|
+
Compilation,
|
|
24
|
+
CompilationFlags,
|
|
25
|
+
CompilationOptions,
|
|
26
|
+
EdgeKind,
|
|
27
|
+
ExpressionKind,
|
|
28
|
+
StatementKind,
|
|
29
|
+
SymbolKind,
|
|
30
|
+
TimingControlKind,
|
|
31
|
+
VisitAction,
|
|
32
|
+
)
|
|
33
|
+
from pyslang.syntax import SyntaxTree
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class Diagnostic:
|
|
38
|
+
"""A parse/elaboration error, with a repo-relative file and 1-based line when known."""
|
|
39
|
+
|
|
40
|
+
file: str | None
|
|
41
|
+
line: int | None
|
|
42
|
+
message: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True)
|
|
46
|
+
class Loc:
|
|
47
|
+
"""A source location: repo-relative file and 1-based line."""
|
|
48
|
+
|
|
49
|
+
file: str | None
|
|
50
|
+
line: int | None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(slots=True)
|
|
54
|
+
class Register:
|
|
55
|
+
"""A variable assigned by an `always_ff`, with the clock root it is driven by."""
|
|
56
|
+
|
|
57
|
+
path: str
|
|
58
|
+
"""The elaborated hierarchical path, a stable identity (e.g. `top.a_q`)."""
|
|
59
|
+
|
|
60
|
+
name: str
|
|
61
|
+
clock_root: str | None
|
|
62
|
+
"""The module input clock port name the driving `always_ff` samples, or None."""
|
|
63
|
+
|
|
64
|
+
loc: Loc
|
|
65
|
+
reads: frozenset[str] = frozenset()
|
|
66
|
+
"""Hierarchical paths this register's next-state expression reads (before comb expansion)."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(slots=True)
|
|
70
|
+
class CombNode:
|
|
71
|
+
"""A combinationally-driven signal (an `assign` LHS or `always_comb` target)."""
|
|
72
|
+
|
|
73
|
+
path: str
|
|
74
|
+
reads: set[str] = field(default_factory=set)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(slots=True)
|
|
78
|
+
class SubInstance:
|
|
79
|
+
"""A child module instance and its named port connections (one hierarchy level)."""
|
|
80
|
+
|
|
81
|
+
name: str
|
|
82
|
+
module: str
|
|
83
|
+
loc: Loc
|
|
84
|
+
is_sync: bool
|
|
85
|
+
inputs: dict[str, frozenset[str]] = field(default_factory=dict)
|
|
86
|
+
"""Input port name -> hierarchical paths the connected expression reads (parent scope)."""
|
|
87
|
+
|
|
88
|
+
outputs: dict[str, str] = field(default_factory=dict)
|
|
89
|
+
"""Output port name -> the single parent hierarchical path it drives, when it is one."""
|
|
90
|
+
|
|
91
|
+
clock_inputs: dict[str, str | None] = field(default_factory=dict)
|
|
92
|
+
"""Clock-like input port name -> parent path it is connected to, when resolvable."""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(slots=True)
|
|
96
|
+
class ModuleFacts:
|
|
97
|
+
"""The CDC-relevant facts of one elaborated module instance."""
|
|
98
|
+
|
|
99
|
+
name: str
|
|
100
|
+
registers: dict[str, Register] = field(default_factory=dict)
|
|
101
|
+
comb: dict[str, CombNode] = field(default_factory=dict)
|
|
102
|
+
sub_instances: list[SubInstance] = field(default_factory=list)
|
|
103
|
+
clock_roots: set[str] = field(default_factory=set)
|
|
104
|
+
"""Input port names used as a clock by some `always_ff` in this module."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(slots=True)
|
|
108
|
+
class Elaboration:
|
|
109
|
+
"""The result of elaborating a block: per-top-module facts and any diagnostics."""
|
|
110
|
+
|
|
111
|
+
modules: dict[str, ModuleFacts] = field(default_factory=dict)
|
|
112
|
+
diagnostics: tuple[Diagnostic, ...] = ()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def elaborate(
|
|
116
|
+
sources: Iterable[Path],
|
|
117
|
+
*,
|
|
118
|
+
sync_cells: frozenset[str],
|
|
119
|
+
clock_matchers: Iterable[Any],
|
|
120
|
+
repo_root: Path | None = None,
|
|
121
|
+
tops: Iterable[str] | None = None,
|
|
122
|
+
) -> Elaboration:
|
|
123
|
+
"""Elaborate `sources` together and extract CDC facts for each top module.
|
|
124
|
+
|
|
125
|
+
`sync_cells` are the module names to treat as synchronisers; `clock_matchers` are
|
|
126
|
+
compiled regexes that recognise a clock-like port by name. `tops`, when given, limits
|
|
127
|
+
which modules become analysis roots; otherwise every uninstantiated module is a top.
|
|
128
|
+
"""
|
|
129
|
+
comp = Compilation(_ignore_unknown_modules())
|
|
130
|
+
diags: list[Diagnostic] = []
|
|
131
|
+
for src in sorted(set(sources)):
|
|
132
|
+
try:
|
|
133
|
+
text = src.read_text(errors="ignore")
|
|
134
|
+
except OSError as exc:
|
|
135
|
+
diags.append(Diagnostic(file=_rel(src, repo_root), line=None, message=str(exc)))
|
|
136
|
+
continue
|
|
137
|
+
comp.addSyntaxTree(SyntaxTree.fromText(text, src.as_posix()))
|
|
138
|
+
|
|
139
|
+
sm = comp.sourceManager
|
|
140
|
+
diags.extend(_diagnostics(comp, sm, repo_root))
|
|
141
|
+
|
|
142
|
+
root = comp.getRoot()
|
|
143
|
+
top_names = set(tops) if tops is not None else None
|
|
144
|
+
matchers = list(clock_matchers)
|
|
145
|
+
modules: dict[str, ModuleFacts] = {}
|
|
146
|
+
for top in getattr(root, "topInstances", ()):
|
|
147
|
+
def_name = _definition_name(top)
|
|
148
|
+
if top_names is not None and def_name not in top_names:
|
|
149
|
+
continue
|
|
150
|
+
facts = _extract_module(top, sm, sync_cells, matchers, repo_root)
|
|
151
|
+
modules.setdefault(facts.name, facts)
|
|
152
|
+
return Elaboration(modules=modules, diagnostics=tuple(diags))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# --------------------------------------------------------------------------------------
|
|
156
|
+
# per-module extraction
|
|
157
|
+
# --------------------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _ignore_unknown_modules() -> Bag:
|
|
161
|
+
"""Compilation options that treat a module instantiated but not in scope as a blackbox.
|
|
162
|
+
|
|
163
|
+
A per-block scope holds only the block's own files; the child IP a wrapper
|
|
164
|
+
instantiates lives in another block. Ignoring unknown modules (as the pyslang
|
|
165
|
+
extractor does) lets the block elaborate, with the missing child as an unanalysed
|
|
166
|
+
blackbox (its output resolves to `not_analysed`, an `info`), instead of an error.
|
|
167
|
+
"""
|
|
168
|
+
opts = CompilationOptions()
|
|
169
|
+
opts.flags = CompilationFlags.IgnoreUnknownModules
|
|
170
|
+
return Bag([opts])
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _extract_module(
|
|
174
|
+
inst: Any,
|
|
175
|
+
sm: Any,
|
|
176
|
+
sync_cells: frozenset[str],
|
|
177
|
+
clock_matchers: list[Any],
|
|
178
|
+
repo_root: Path | None,
|
|
179
|
+
) -> ModuleFacts:
|
|
180
|
+
facts = ModuleFacts(name=_definition_name(inst))
|
|
181
|
+
for member in inst.body:
|
|
182
|
+
kind = getattr(member, "kind", None)
|
|
183
|
+
if kind == SymbolKind.ProceduralBlock:
|
|
184
|
+
_extract_procedural(member, sm, facts, repo_root)
|
|
185
|
+
elif kind == SymbolKind.ContinuousAssign:
|
|
186
|
+
_extract_continuous_assign(member, facts)
|
|
187
|
+
elif kind == SymbolKind.Instance:
|
|
188
|
+
sub = _extract_instance(member, sm, sync_cells, clock_matchers, repo_root)
|
|
189
|
+
if sub is not None:
|
|
190
|
+
facts.sub_instances.append(sub)
|
|
191
|
+
return facts
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _extract_procedural(block: Any, sm: Any, facts: ModuleFacts, repo_root: Path | None) -> None:
|
|
195
|
+
"""Extract registers (always_ff) or comb nodes (always_comb/always) from a block."""
|
|
196
|
+
proc_kind = str(getattr(block, "procedureKind", ""))
|
|
197
|
+
body = getattr(block, "body", None)
|
|
198
|
+
if body is None:
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
is_ff = proc_kind.endswith("AlwaysFF")
|
|
202
|
+
clock_root: str | None = None
|
|
203
|
+
inner = body
|
|
204
|
+
if getattr(body, "kind", None) == StatementKind.Timed:
|
|
205
|
+
if is_ff:
|
|
206
|
+
clock_root = _clock_root_of(getattr(body, "timing", None))
|
|
207
|
+
if clock_root is not None:
|
|
208
|
+
facts.clock_roots.add(clock_root)
|
|
209
|
+
inner = getattr(body, "stmt", body)
|
|
210
|
+
|
|
211
|
+
for lhs, reads, loc in _assignments(inner, sm, repo_root):
|
|
212
|
+
if is_ff:
|
|
213
|
+
existing = facts.registers.get(lhs.path)
|
|
214
|
+
merged_reads = (existing.reads if existing else frozenset()) | reads
|
|
215
|
+
facts.registers[lhs.path] = Register(
|
|
216
|
+
path=lhs.path,
|
|
217
|
+
name=lhs.name,
|
|
218
|
+
clock_root=(
|
|
219
|
+
clock_root if existing is None else (existing.clock_root or clock_root)
|
|
220
|
+
),
|
|
221
|
+
loc=existing.loc if existing is not None else loc,
|
|
222
|
+
reads=merged_reads,
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
node = facts.comb.setdefault(lhs.path, CombNode(path=lhs.path))
|
|
226
|
+
node.reads |= reads
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _extract_continuous_assign(member: Any, facts: ModuleFacts) -> None:
|
|
230
|
+
assignment = getattr(member, "assignment", None)
|
|
231
|
+
if assignment is None or getattr(assignment, "kind", None) != ExpressionKind.Assignment:
|
|
232
|
+
return
|
|
233
|
+
lhs = _lvalue_target(getattr(assignment, "left", None))
|
|
234
|
+
if lhs is None:
|
|
235
|
+
return
|
|
236
|
+
reads = _named_reads(getattr(assignment, "right", None))
|
|
237
|
+
node = facts.comb.setdefault(lhs.path, CombNode(path=lhs.path))
|
|
238
|
+
node.reads |= reads
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _extract_instance(
|
|
242
|
+
inst: Any,
|
|
243
|
+
sm: Any,
|
|
244
|
+
sync_cells: frozenset[str],
|
|
245
|
+
clock_matchers: list[Any],
|
|
246
|
+
repo_root: Path | None,
|
|
247
|
+
) -> SubInstance | None:
|
|
248
|
+
module = _definition_name(inst)
|
|
249
|
+
if not module:
|
|
250
|
+
return None
|
|
251
|
+
sub = SubInstance(
|
|
252
|
+
name=getattr(inst, "name", "") or "",
|
|
253
|
+
module=module,
|
|
254
|
+
loc=_loc(inst, sm, repo_root),
|
|
255
|
+
is_sync=module in sync_cells,
|
|
256
|
+
)
|
|
257
|
+
body = getattr(inst, "body", None)
|
|
258
|
+
port_list = getattr(body, "portList", ()) if body is not None else ()
|
|
259
|
+
for port in port_list:
|
|
260
|
+
try:
|
|
261
|
+
conn = inst.getPortConnection(port)
|
|
262
|
+
except Exception: # pragma: no cover - defensive
|
|
263
|
+
conn = None
|
|
264
|
+
if conn is None:
|
|
265
|
+
continue
|
|
266
|
+
expr = getattr(conn, "expression", None)
|
|
267
|
+
direction = str(getattr(port, "direction", "")).split(".")[-1]
|
|
268
|
+
pname = getattr(port, "name", "") or ""
|
|
269
|
+
if direction == "In":
|
|
270
|
+
reads = _named_reads(expr)
|
|
271
|
+
sub.inputs[pname] = reads
|
|
272
|
+
if any(m.match(pname) for m in clock_matchers):
|
|
273
|
+
sub.clock_inputs[pname] = next(iter(sorted(reads)), None)
|
|
274
|
+
elif direction == "Out":
|
|
275
|
+
target = _lvalue_target(expr)
|
|
276
|
+
if target is not None:
|
|
277
|
+
sub.outputs[pname] = target.path
|
|
278
|
+
return sub
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# --------------------------------------------------------------------------------------
|
|
282
|
+
# expression / statement walking
|
|
283
|
+
# --------------------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@dataclass(frozen=True, slots=True)
|
|
287
|
+
class _Target:
|
|
288
|
+
path: str
|
|
289
|
+
name: str
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _assignments(
|
|
293
|
+
stmt: Any, sm: Any, repo_root: Path | None
|
|
294
|
+
) -> list[tuple[_Target, frozenset[str], Loc]]:
|
|
295
|
+
"""Every assignment reachable from `stmt`: its LHS target, RHS reads and location."""
|
|
296
|
+
out: list[tuple[_Target, frozenset[str], Loc]] = []
|
|
297
|
+
|
|
298
|
+
def visit(node: Any) -> Any:
|
|
299
|
+
if getattr(node, "kind", None) == ExpressionKind.Assignment:
|
|
300
|
+
target = _lvalue_target(getattr(node, "left", None))
|
|
301
|
+
if target is not None:
|
|
302
|
+
reads = _named_reads(getattr(node, "right", None))
|
|
303
|
+
out.append((target, reads, _loc(node, sm, repo_root)))
|
|
304
|
+
return VisitAction.Advance
|
|
305
|
+
|
|
306
|
+
_safe_visit(stmt, visit)
|
|
307
|
+
return out
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _lvalue_target(expr: Any) -> _Target | None:
|
|
311
|
+
"""The single named variable an l-value writes (peeling selects to one name)."""
|
|
312
|
+
names = _named_symbols(expr)
|
|
313
|
+
if len(names) != 1:
|
|
314
|
+
return None
|
|
315
|
+
path, name = names[0]
|
|
316
|
+
return _Target(path=path, name=name)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _named_reads(expr: Any) -> frozenset[str]:
|
|
320
|
+
"""The hierarchical paths of every `NamedValue` read in `expr`."""
|
|
321
|
+
return frozenset(path for path, _ in _named_symbols(expr))
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _named_symbols(expr: Any) -> list[tuple[str, str]]:
|
|
325
|
+
"""Every `(hierarchical_path, name)` referenced by a `NamedValue` in `expr`."""
|
|
326
|
+
out: list[tuple[str, str]] = []
|
|
327
|
+
seen: set[str] = set()
|
|
328
|
+
|
|
329
|
+
def visit(node: Any) -> Any:
|
|
330
|
+
if getattr(node, "kind", None) == ExpressionKind.NamedValue:
|
|
331
|
+
sym = getattr(node, "symbol", None)
|
|
332
|
+
path = getattr(sym, "hierarchicalPath", None)
|
|
333
|
+
if isinstance(path, str) and path not in seen:
|
|
334
|
+
seen.add(path)
|
|
335
|
+
out.append((path, getattr(sym, "name", "") or ""))
|
|
336
|
+
return VisitAction.Advance
|
|
337
|
+
|
|
338
|
+
_safe_visit(expr, visit)
|
|
339
|
+
return out
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _clock_root_of(timing: Any) -> str | None:
|
|
343
|
+
"""The clock a posedge/negedge event samples: the signal name of its first edge event.
|
|
344
|
+
|
|
345
|
+
A reset event in the same list (`negedge rst_n`) is left for the caller's domain
|
|
346
|
+
logic to ignore -- a reset name never becomes a register's domain root because it is
|
|
347
|
+
not itself a clock any `always_ff` is keyed on for its data.
|
|
348
|
+
"""
|
|
349
|
+
if timing is None:
|
|
350
|
+
return None
|
|
351
|
+
kind = getattr(timing, "kind", None)
|
|
352
|
+
events: list[Any] = []
|
|
353
|
+
if kind == TimingControlKind.SignalEvent:
|
|
354
|
+
events = [timing]
|
|
355
|
+
elif kind == TimingControlKind.EventList:
|
|
356
|
+
events = list(getattr(timing, "events", ()))
|
|
357
|
+
for ev in events:
|
|
358
|
+
edge = getattr(ev, "edge", None)
|
|
359
|
+
if edge not in (EdgeKind.PosEdge, EdgeKind.NegEdge):
|
|
360
|
+
continue
|
|
361
|
+
expr = getattr(ev, "expr", None)
|
|
362
|
+
if getattr(expr, "kind", None) == ExpressionKind.NamedValue:
|
|
363
|
+
sym = getattr(expr, "symbol", None)
|
|
364
|
+
name = getattr(sym, "name", None)
|
|
365
|
+
if isinstance(name, str) and name:
|
|
366
|
+
return name
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _safe_visit(node: Any, visit: Any) -> None:
|
|
371
|
+
if node is None:
|
|
372
|
+
return
|
|
373
|
+
with suppress(Exception): # pragma: no cover - defensive against odd nodes
|
|
374
|
+
node.visit(visit)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _definition_name(inst: Any) -> str:
|
|
378
|
+
body = getattr(inst, "body", None)
|
|
379
|
+
definition = getattr(body, "definition", None)
|
|
380
|
+
name = getattr(definition, "name", None)
|
|
381
|
+
return name if isinstance(name, str) else ""
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _loc(node: Any, sm: Any, repo_root: Path | None) -> Loc:
|
|
385
|
+
loc = getattr(node, "location", None)
|
|
386
|
+
if not loc:
|
|
387
|
+
source_range = getattr(node, "sourceRange", None)
|
|
388
|
+
loc = getattr(source_range, "start", None) if source_range is not None else None
|
|
389
|
+
if not loc:
|
|
390
|
+
return Loc(file=None, line=None)
|
|
391
|
+
try:
|
|
392
|
+
raw = sm.getFileName(loc)
|
|
393
|
+
line = sm.getLineNumber(loc)
|
|
394
|
+
except Exception: # pragma: no cover - defensive
|
|
395
|
+
return Loc(file=None, line=None)
|
|
396
|
+
return Loc(file=_rel_str(raw, repo_root), line=int(line) if line else None)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _diagnostics(comp: Any, sm: Any, repo_root: Path | None) -> list[Diagnostic]:
|
|
400
|
+
"""Genuine syntax errors of the scope, as `Diagnostic`s (never raised).
|
|
401
|
+
|
|
402
|
+
Only `Parser`/`Lexer` subsystem errors count -- those are malformed SystemVerilog.
|
|
403
|
+
A partial per-block scope is elaborated without every package or child module it
|
|
404
|
+
needs, so `Lookup`/`Elaboration` diagnostics (an unknown package, an unresolved name
|
|
405
|
+
from a file not in scope) are expected and ignored, exactly as `naming`'s single-file
|
|
406
|
+
parse ignores `Preprocessor` diagnostics. Unknown *modules* are already tolerated by
|
|
407
|
+
the `IgnoreUnknownModules` flag.
|
|
408
|
+
"""
|
|
409
|
+
out: list[Diagnostic] = []
|
|
410
|
+
try:
|
|
411
|
+
all_diags = comp.getAllDiagnostics()
|
|
412
|
+
except Exception: # pragma: no cover - defensive
|
|
413
|
+
return out
|
|
414
|
+
for diag in all_diags:
|
|
415
|
+
if not diag.isError():
|
|
416
|
+
continue
|
|
417
|
+
try:
|
|
418
|
+
subsystem = str(diag.code.getSubsystem()).rsplit(".", 1)[-1]
|
|
419
|
+
except Exception: # pragma: no cover - defensive
|
|
420
|
+
subsystem = ""
|
|
421
|
+
if subsystem not in ("Parser", "Lexer"):
|
|
422
|
+
continue
|
|
423
|
+
loc = getattr(diag, "location", None)
|
|
424
|
+
file = _rel_str(sm.getFileName(loc), repo_root) if loc else None
|
|
425
|
+
line = sm.getLineNumber(loc) if loc else None
|
|
426
|
+
out.append(Diagnostic(file=file, line=int(line) if line else None, message=str(diag.code)))
|
|
427
|
+
return out
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _rel(path: Path, repo_root: Path | None) -> str:
|
|
431
|
+
result = _rel_str(path.as_posix(), repo_root)
|
|
432
|
+
return result if result is not None else path.as_posix()
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _rel_str(raw: str | None, repo_root: Path | None) -> str | None:
|
|
436
|
+
if raw is None:
|
|
437
|
+
return None
|
|
438
|
+
if repo_root is None:
|
|
439
|
+
return raw
|
|
440
|
+
try:
|
|
441
|
+
return Path(raw).resolve().relative_to(repo_root.resolve()).as_posix()
|
|
442
|
+
except ValueError:
|
|
443
|
+
return raw
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
__all__ = [
|
|
447
|
+
"CombNode",
|
|
448
|
+
"Diagnostic",
|
|
449
|
+
"Elaboration",
|
|
450
|
+
"Loc",
|
|
451
|
+
"ModuleFacts",
|
|
452
|
+
"Register",
|
|
453
|
+
"SubInstance",
|
|
454
|
+
"elaborate",
|
|
455
|
+
]
|