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,268 @@
|
|
|
1
|
+
"""Shared helpers for built-in checks: template/glob matching, file walking, hashing, timing.
|
|
2
|
+
|
|
3
|
+
Nothing here knows about a specific check; `layout.py`, `filelist.py` and `generated.py`
|
|
4
|
+
build on top of this module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
from collections.abc import Iterable, Mapping
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Argument validation
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ArgError(Exception):
|
|
27
|
+
"""Raised for invalid `CheckSpec.args`; the caller turns this into an `error` result."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def require_str(args: Mapping[str, Any], key: str) -> str:
|
|
31
|
+
"""Return `args[key]` as a string, or raise `ArgError`."""
|
|
32
|
+
if key not in args:
|
|
33
|
+
raise ArgError(f"args[{key!r}] is required")
|
|
34
|
+
val = args[key]
|
|
35
|
+
if not isinstance(val, str):
|
|
36
|
+
raise ArgError(f"args[{key!r}] must be a string")
|
|
37
|
+
return val
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_dict(args: Mapping[str, Any], key: str) -> dict[str, Any]:
|
|
41
|
+
"""Return `args[key]` as a dict, or raise `ArgError`."""
|
|
42
|
+
if key not in args:
|
|
43
|
+
raise ArgError(f"args[{key!r}] is required")
|
|
44
|
+
val = args[key]
|
|
45
|
+
if not isinstance(val, dict):
|
|
46
|
+
raise ArgError(f"args[{key!r}] must be a mapping")
|
|
47
|
+
return val
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def optional_bool(args: Mapping[str, Any], key: str, default: bool) -> bool:
|
|
51
|
+
"""Return `args[key]` as a bool, defaulting to `default`, or raise `ArgError`."""
|
|
52
|
+
val = args.get(key, default)
|
|
53
|
+
if not isinstance(val, bool):
|
|
54
|
+
raise ArgError(f"args[{key!r}] must be a boolean")
|
|
55
|
+
return val
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def optional_str(args: Mapping[str, Any], key: str, default: str) -> str:
|
|
59
|
+
"""Return `args[key]` as a string, defaulting to `default`, or raise `ArgError`."""
|
|
60
|
+
val = args.get(key, default)
|
|
61
|
+
if not isinstance(val, str):
|
|
62
|
+
raise ArgError(f"args[{key!r}] must be a string")
|
|
63
|
+
return val
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def require_list_of_str(args: Mapping[str, Any], key: str) -> list[str]:
|
|
67
|
+
"""Return `args[key]` as a list of strings, or raise `ArgError`."""
|
|
68
|
+
if key not in args:
|
|
69
|
+
raise ArgError(f"args[{key!r}] is required")
|
|
70
|
+
return _as_list_of_str(args[key], key)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def optional_list_of_str(args: Mapping[str, Any], key: str) -> list[str]:
|
|
74
|
+
"""Return `args[key]` as a list of strings, defaulting to `[]`, or raise `ArgError`."""
|
|
75
|
+
if key not in args:
|
|
76
|
+
return []
|
|
77
|
+
return _as_list_of_str(args[key], key)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _as_list_of_str(val: Any, key: str) -> list[str]:
|
|
81
|
+
if not isinstance(val, list) or not all(isinstance(v, str) for v in val):
|
|
82
|
+
raise ArgError(f"args[{key!r}] must be a list of strings")
|
|
83
|
+
return list(val)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def str_or_list_of_str(val: Any, where: str) -> list[str]:
|
|
87
|
+
"""Normalize a `str | list[str]` value into a list of strings, or raise `ArgError`."""
|
|
88
|
+
if isinstance(val, str):
|
|
89
|
+
return [val]
|
|
90
|
+
if isinstance(val, list) and all(isinstance(v, str) for v in val):
|
|
91
|
+
return list(val)
|
|
92
|
+
raise ArgError(f"{where} must be a string or a list of strings")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Template and glob matching
|
|
97
|
+
#
|
|
98
|
+
# A template is a repo-relative path pattern with two kinds of wildcards:
|
|
99
|
+
# - `{name}` matches exactly one path segment (or part of one); repeating the same
|
|
100
|
+
# name later in the template requires the same value (a backreference).
|
|
101
|
+
# - `**` (as a whole path segment, e.g. `a/**/b.sv` or `vendor/**`) matches any depth,
|
|
102
|
+
# including zero.
|
|
103
|
+
# - `*` and `?` behave as usual within a single path segment.
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
_PLACEHOLDER_TOKEN = re.compile(r"\{([a-zA-Z_][a-zA-Z0-9_.]*)\}")
|
|
107
|
+
_GLOB_SPECIAL = re.compile(r"(\*\*/|/\*\*|\*\*|\*|\?)")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _sanitize(name: str) -> str:
|
|
111
|
+
"""A valid, collision-resistant Python regex group name for placeholder `name`."""
|
|
112
|
+
return "p_" + name.replace(".", "_")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _glob_literal_to_regex(text: str) -> str:
|
|
116
|
+
"""Translate glob wildcards (`**`, `*`, `?`) in a literal chunk of text to a regex body."""
|
|
117
|
+
parts = _GLOB_SPECIAL.split(text)
|
|
118
|
+
out: list[str] = []
|
|
119
|
+
for part in parts:
|
|
120
|
+
if part == "**/":
|
|
121
|
+
out.append("(?:.*/)?")
|
|
122
|
+
elif part == "/**":
|
|
123
|
+
out.append("(?:/.*)?")
|
|
124
|
+
elif part == "**":
|
|
125
|
+
out.append(".*")
|
|
126
|
+
elif part == "*":
|
|
127
|
+
out.append("[^/]*")
|
|
128
|
+
elif part == "?":
|
|
129
|
+
out.append("[^/]")
|
|
130
|
+
else:
|
|
131
|
+
out.append(re.escape(part))
|
|
132
|
+
return "".join(out)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def glob_to_regex(pattern: str) -> re.Pattern[str]:
|
|
136
|
+
"""Compile a glob pattern (`**`, `*`, `?`) into a regex matching a whole repo-relative path."""
|
|
137
|
+
return re.compile("^" + _glob_literal_to_regex(pattern) + "$")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class CompiledTemplate:
|
|
142
|
+
"""A path template compiled to a regex, with its placeholder names in first-seen order."""
|
|
143
|
+
|
|
144
|
+
regex: re.Pattern[str]
|
|
145
|
+
names: tuple[str, ...]
|
|
146
|
+
|
|
147
|
+
def match(self, path: str) -> dict[str, str] | None:
|
|
148
|
+
"""Match `path` (a repo-relative POSIX path) against this template.
|
|
149
|
+
|
|
150
|
+
Returns a mapping of placeholder name to captured value, or `None` if `path`
|
|
151
|
+
does not match.
|
|
152
|
+
"""
|
|
153
|
+
m = self.regex.fullmatch(path)
|
|
154
|
+
if m is None:
|
|
155
|
+
return None
|
|
156
|
+
groups = m.groupdict()
|
|
157
|
+
return {name: groups[_sanitize(name)] for name in self.names}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def compile_template(template: str) -> CompiledTemplate:
|
|
161
|
+
"""Compile a path template (placeholders, `**`, `*`, `?`) into a `CompiledTemplate`."""
|
|
162
|
+
tokens = _PLACEHOLDER_TOKEN.split(template)
|
|
163
|
+
seen: set[str] = set()
|
|
164
|
+
order: list[str] = []
|
|
165
|
+
parts: list[str] = []
|
|
166
|
+
is_placeholder = False
|
|
167
|
+
for tok in tokens:
|
|
168
|
+
if is_placeholder:
|
|
169
|
+
sanitized = _sanitize(tok)
|
|
170
|
+
if tok in seen:
|
|
171
|
+
parts.append(f"(?P={sanitized})")
|
|
172
|
+
else:
|
|
173
|
+
seen.add(tok)
|
|
174
|
+
order.append(tok)
|
|
175
|
+
parts.append(f"(?P<{sanitized}>[^/]+)")
|
|
176
|
+
else:
|
|
177
|
+
parts.append(_glob_literal_to_regex(tok))
|
|
178
|
+
is_placeholder = not is_placeholder
|
|
179
|
+
regex = re.compile("^" + "".join(parts) + "$")
|
|
180
|
+
return CompiledTemplate(regex=regex, names=tuple(order))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# Repo file walking
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
_ALWAYS_SKIP_DIR = ".git"
|
|
188
|
+
_STATE_DIR = (".chipgraph", "state")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def iter_repo_files(root: Path) -> list[str]:
|
|
192
|
+
"""List every file under `root` as a repo-relative POSIX path, sorted.
|
|
193
|
+
|
|
194
|
+
Always skips `.git` and `.chipgraph/state`, regardless of check configuration.
|
|
195
|
+
"""
|
|
196
|
+
out: list[str] = []
|
|
197
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
198
|
+
rel_dir = Path(dirpath).relative_to(root)
|
|
199
|
+
rel_parts = () if rel_dir == Path() else rel_dir.parts
|
|
200
|
+
dirnames[:] = [
|
|
201
|
+
d for d in dirnames if d != _ALWAYS_SKIP_DIR and (*rel_parts, d) != _STATE_DIR
|
|
202
|
+
]
|
|
203
|
+
for filename in filenames:
|
|
204
|
+
rel = "/".join((*rel_parts, filename)) if rel_parts else filename
|
|
205
|
+
out.append(rel)
|
|
206
|
+
return sorted(out)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def norm_path(path: Path) -> Path:
|
|
210
|
+
"""Normalize `path` (collapse `..`/`.`) without resolving symlinks."""
|
|
211
|
+
return Path(os.path.normpath(str(path)))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def to_repo_rel(path: Path, root: Path) -> str | None:
|
|
215
|
+
"""Return `path` as a repo-relative POSIX string, or `None` if it is outside `root`."""
|
|
216
|
+
try:
|
|
217
|
+
return norm_path(path).relative_to(norm_path(root)).as_posix()
|
|
218
|
+
except ValueError:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
# Hashing and idempotency
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def sha256_bytes(data: bytes) -> str:
|
|
228
|
+
"""Return the hex sha256 digest of `data`."""
|
|
229
|
+
return hashlib.sha256(data).hexdigest()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def sha256_file(path: Path) -> str:
|
|
233
|
+
"""Return the hex sha256 digest of the content of the file at `path`."""
|
|
234
|
+
return sha256_bytes(path.read_bytes())
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def canonical_json(obj: Any) -> str:
|
|
238
|
+
"""Render `obj` as canonical JSON (sorted keys, no extra whitespace)."""
|
|
239
|
+
return json.dumps(obj, sort_keys=True, separators=(",", ":"), default=str)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def compute_idempotency_key(
|
|
243
|
+
check_id: str,
|
|
244
|
+
args: Mapping[str, Any],
|
|
245
|
+
file_hashes: Iterable[tuple[str, str]],
|
|
246
|
+
) -> str:
|
|
247
|
+
"""A stable idempotency key over the check id, its args, and the files it read.
|
|
248
|
+
|
|
249
|
+
`file_hashes` is an iterable of `(repo_relative_path, content_sha256)` pairs; it is
|
|
250
|
+
sorted here so caller order does not matter.
|
|
251
|
+
"""
|
|
252
|
+
payload = {
|
|
253
|
+
"check_id": check_id,
|
|
254
|
+
"args": args,
|
|
255
|
+
"files": sorted(file_hashes),
|
|
256
|
+
}
|
|
257
|
+
return sha256_bytes(canonical_json(payload).encode("utf-8"))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def error_result(spec: CheckSpec, msg: str, start: float, *, rule: str = "args") -> CheckResult:
|
|
261
|
+
"""Build an `error` `CheckResult` for a bad-args failure, timed from `start`."""
|
|
262
|
+
return CheckResult(
|
|
263
|
+
check_id=spec.id,
|
|
264
|
+
status="error",
|
|
265
|
+
issues=(Issue(msg=msg, severity="error", rule=rule),),
|
|
266
|
+
duration_s=time.monotonic() - start,
|
|
267
|
+
idempotency_key=compute_idempotency_key(spec.id, spec.args, ()),
|
|
268
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Shared helpers for the model-based cross checks (M1-07 part A, DESIGN.md 4.4/4.8).
|
|
2
|
+
|
|
3
|
+
Everything a `spec_schema`/`cross_chip`/`ports_diff` check needs beyond `_model.py`:
|
|
4
|
+
building the `error` result when the model is missing, turning a loaded model plus a
|
|
5
|
+
list of `Issue`s into a `pass`/`fail` `CheckResult`, and small model helpers (the block
|
|
6
|
+
key a scope maps to, the entities owned by a block).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
from chipgraph.checks._common import compute_idempotency_key, error_result
|
|
14
|
+
from chipgraph.checks._model import LoadedModel, ModelUnavailable, block_param, load_model
|
|
15
|
+
from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
|
|
16
|
+
from chipgraph.core.contracts.types import CheckStatus
|
|
17
|
+
from chipgraph.core.model.model import DesignModel
|
|
18
|
+
from chipgraph.core.plugin_api.types import ToolContext
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_model_or_skip(
|
|
22
|
+
spec: CheckSpec, ctx: ToolContext, start: float
|
|
23
|
+
) -> LoadedModel | CheckResult:
|
|
24
|
+
"""Load the Design Model, or return an `error` `CheckResult` when it is missing.
|
|
25
|
+
|
|
26
|
+
The cross checks never build the model themselves (`_model.py` docstring); a missing
|
|
27
|
+
store means `chipgraph ingest` has not run yet, and the message says so. The same
|
|
28
|
+
rule holds for every model-based check (`trace`, `connect`, `hardcode` too).
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
return load_model(ctx.repo_root)
|
|
32
|
+
except ModelUnavailable as exc:
|
|
33
|
+
return error_result(spec, str(exc), start, rule="model")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def result_from_issues(
|
|
37
|
+
spec: CheckSpec,
|
|
38
|
+
issues: list[Issue],
|
|
39
|
+
loaded: LoadedModel,
|
|
40
|
+
start: float,
|
|
41
|
+
) -> CheckResult:
|
|
42
|
+
"""Build a deterministic `pass`/`fail` `CheckResult` from model-derived issues.
|
|
43
|
+
|
|
44
|
+
Issues are sorted by (file, line, rule, msg) so the same model always yields the same
|
|
45
|
+
order. The idempotency key is over the check id, args and the model's build-inputs
|
|
46
|
+
hash: the same ingested model gives the same key, and re-ingesting after an edit
|
|
47
|
+
changes it. A result is `fail` when any issue is an `error`, else `pass` (an `info`
|
|
48
|
+
issue does not fail a check).
|
|
49
|
+
"""
|
|
50
|
+
ordered = sort_issues(issues)
|
|
51
|
+
key = compute_idempotency_key(spec.id, spec.args, [("<model>", loaded.build_inputs_hash or "")])
|
|
52
|
+
status: CheckStatus = "fail" if any(i.severity == "error" for i in ordered) else "pass"
|
|
53
|
+
return CheckResult(
|
|
54
|
+
check_id=spec.id,
|
|
55
|
+
status=status,
|
|
56
|
+
issues=tuple(ordered),
|
|
57
|
+
duration_s=time.monotonic() - start,
|
|
58
|
+
idempotency_key=key,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def sort_issues(issues: list[Issue]) -> list[Issue]:
|
|
63
|
+
"""Return `issues` in a stable, deterministic order."""
|
|
64
|
+
return sorted(issues, key=lambda i: (i.file or "", i.line or 0, i.rule, i.msg))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def block_key(name: str) -> str:
|
|
68
|
+
"""The model key of the block named `name` (`block:<name>`)."""
|
|
69
|
+
return f"block:{name}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def scope_block_keys(model: DesignModel, block: str) -> set[str]:
|
|
73
|
+
"""The block key `block` scopes to, plus its instances (`instance_of` -> it).
|
|
74
|
+
|
|
75
|
+
A `--block timer` run covers `block:timer` and every `block:<instance>` that is an
|
|
76
|
+
`instance_of` it (D38: `timer_0`, `timer_1`), so an issue about an instance of the
|
|
77
|
+
scoped IP is still reported.
|
|
78
|
+
"""
|
|
79
|
+
ip_key = block_key(block)
|
|
80
|
+
keys = {ip_key}
|
|
81
|
+
for rel in model.get_relations(dst=ip_key, kind="instance_of"):
|
|
82
|
+
keys.add(rel.src)
|
|
83
|
+
return keys
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = [
|
|
87
|
+
"LoadedModel",
|
|
88
|
+
"ModelUnavailable",
|
|
89
|
+
"block_key",
|
|
90
|
+
"block_param",
|
|
91
|
+
"load_model",
|
|
92
|
+
"load_model_or_skip",
|
|
93
|
+
"result_from_issues",
|
|
94
|
+
"scope_block_keys",
|
|
95
|
+
"sort_issues",
|
|
96
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Shared helpers for the model-based cross checks (M1-07, DESIGN.md 4.4).
|
|
2
|
+
|
|
3
|
+
Cross checks read the Design Model that `chipgraph ingest` wrote to the project's
|
|
4
|
+
`ModelStore`; they never run extractors themselves. A missing store is a check `error`
|
|
5
|
+
telling the user to run `chipgraph ingest` first.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sqlite3
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from chipgraph.core.model.model import DesignModel
|
|
15
|
+
from chipgraph.core.model.store import ModelStore, default_model_db_path
|
|
16
|
+
from chipgraph.core.plugin_api.types import ToolContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ModelUnavailable(Exception):
|
|
20
|
+
"""Raised when there is no Design Model to check (ingest has not run)."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class LoadedModel:
|
|
25
|
+
"""The stored model, plus the hash of the inputs it was built from."""
|
|
26
|
+
|
|
27
|
+
model: DesignModel
|
|
28
|
+
build_inputs_hash: str | None
|
|
29
|
+
db_path: Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_model(repo_root: Path) -> LoadedModel:
|
|
33
|
+
"""Read the model `chipgraph ingest` wrote for `repo_root`.
|
|
34
|
+
|
|
35
|
+
Raises `ModelUnavailable` with a message naming the command to run when the store
|
|
36
|
+
does not exist yet.
|
|
37
|
+
"""
|
|
38
|
+
db_path = default_model_db_path(repo_root)
|
|
39
|
+
if not db_path.is_file():
|
|
40
|
+
raise ModelUnavailable(
|
|
41
|
+
f"no Design Model at {db_path}; run `chipgraph ingest` before the cross checks"
|
|
42
|
+
)
|
|
43
|
+
model = ModelStore(db_path).read()
|
|
44
|
+
return LoadedModel(model=model, build_inputs_hash=_build_inputs_hash(db_path), db_path=db_path)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def block_param(ctx: ToolContext) -> str | None:
|
|
48
|
+
"""The block this check run is scoped to (`chipgraph check --block`), if any."""
|
|
49
|
+
value = ctx.params.get("block")
|
|
50
|
+
return value if isinstance(value, str) and value else None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _build_inputs_hash(db_path: Path) -> str | None:
|
|
54
|
+
conn = sqlite3.connect(db_path)
|
|
55
|
+
try:
|
|
56
|
+
row = conn.execute("SELECT value FROM meta WHERE key = 'build_inputs_hash'").fetchone()
|
|
57
|
+
except sqlite3.Error:
|
|
58
|
+
return None
|
|
59
|
+
finally:
|
|
60
|
+
conn.close()
|
|
61
|
+
return str(row[0]) if row else None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = ["LoadedModel", "ModelUnavailable", "block_param", "load_model"]
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Classify the identifiers a SystemVerilog file *declares*, using pyslang's syntax tree.
|
|
2
|
+
|
|
3
|
+
This is the point of the `naming` check: pyslang knows whether a name is a module, a
|
|
4
|
+
port, a parameter, a signal or a memory; a regex over text only guesses. We parse a file
|
|
5
|
+
into a syntax tree (no elaboration, so a single file with missing packages still parses)
|
|
6
|
+
and walk it, yielding one `Decl` per declared identifier with its object kind and line.
|
|
7
|
+
|
|
8
|
+
Only *declarations* are yielded. A reference like `.clk_i(...)` (a connection to a
|
|
9
|
+
vendored module's port), `pkg::Member`, `$clog2` or `` `MACRO`` names something the file
|
|
10
|
+
does not own, so it is never classified here -- matching the reference checker's rule
|
|
11
|
+
that we only name what we declare.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Iterator
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from pyslang.syntax import SyntaxKind, SyntaxNode, SyntaxTree
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class Decl:
|
|
26
|
+
"""One declared identifier: its object kind, its name, and its 1-based line."""
|
|
27
|
+
|
|
28
|
+
kind: str
|
|
29
|
+
name: str
|
|
30
|
+
line: int
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class ParseFailure:
|
|
35
|
+
"""A parse error, with a 1-based line when the parser could locate one."""
|
|
36
|
+
|
|
37
|
+
line: int | None
|
|
38
|
+
message: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_text(text: str, name: str = "source") -> Any:
|
|
42
|
+
"""Parse `text` into a pyslang syntax tree (syntax only, no elaboration)."""
|
|
43
|
+
return SyntaxTree.fromText(text, name)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_file(path: Path) -> Any:
|
|
47
|
+
"""Parse the file at `path` into a pyslang syntax tree (syntax only, no elaboration)."""
|
|
48
|
+
return SyntaxTree.fromText(path.read_text(errors="ignore"), path.as_posix())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_errors(tree: Any) -> list[ParseFailure]:
|
|
52
|
+
"""Real syntax errors of `tree`, as `ParseFailure`s (never raised).
|
|
53
|
+
|
|
54
|
+
Only `Parser`- and `Lexer`-subsystem error diagnostics count: those are genuine
|
|
55
|
+
malformed SystemVerilog. `Preprocessor` diagnostics (a `` `include`` we cannot open,
|
|
56
|
+
an `` `UNKNOWN_MACRO`` directive defined in a package we were not given) are expected
|
|
57
|
+
when a single file is parsed without its include paths -- the whole point of a
|
|
58
|
+
syntax-only, single-file parse -- and are ignored, matching the reference checker,
|
|
59
|
+
which does no preprocessing at all.
|
|
60
|
+
"""
|
|
61
|
+
sm = tree.sourceManager
|
|
62
|
+
out: list[ParseFailure] = []
|
|
63
|
+
for diag in tree.diagnostics:
|
|
64
|
+
if not diag.isError():
|
|
65
|
+
continue
|
|
66
|
+
subsystem = str(diag.code.getSubsystem()).rsplit(".", 1)[-1]
|
|
67
|
+
if subsystem not in ("Parser", "Lexer"):
|
|
68
|
+
continue
|
|
69
|
+
loc = getattr(diag, "location", None)
|
|
70
|
+
line = sm.getLineNumber(loc) if loc else None
|
|
71
|
+
out.append(ParseFailure(line=line, message=str(diag.code)))
|
|
72
|
+
return out
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def declared_identifiers(tree: Any) -> list[Decl]:
|
|
76
|
+
"""Every identifier declared in `tree`, classified by object kind, sorted by line.
|
|
77
|
+
|
|
78
|
+
The walk dispatches on syntax node kind. It handles: module/interface/program/package
|
|
79
|
+
headers; ANSI and non-ANSI port declarations; parameter vs localparam; enum members;
|
|
80
|
+
variable/net declarations (a declarator with an unpacked dimension is a `memory`, else
|
|
81
|
+
a `signal`); genvars; and hierarchy instantiations.
|
|
82
|
+
"""
|
|
83
|
+
sm = tree.sourceManager
|
|
84
|
+
|
|
85
|
+
def line_of(token: Any) -> int:
|
|
86
|
+
return int(sm.getLineNumber(token.location))
|
|
87
|
+
|
|
88
|
+
out: list[Decl] = []
|
|
89
|
+
|
|
90
|
+
def emit(kind: str, token: Any) -> None:
|
|
91
|
+
name = token.valueText
|
|
92
|
+
if name:
|
|
93
|
+
out.append(Decl(kind=kind, name=name, line=line_of(token)))
|
|
94
|
+
|
|
95
|
+
K = SyntaxKind
|
|
96
|
+
# Only a `module` declaration is checked for the module-name rule. Interface, program
|
|
97
|
+
# and package names have no rule in the source document and are not checked by the
|
|
98
|
+
# reference, so we do not classify them here.
|
|
99
|
+
header_kinds = {K.ModuleHeader}
|
|
100
|
+
|
|
101
|
+
def visit(node: Any) -> None:
|
|
102
|
+
kind = getattr(node, "kind", None)
|
|
103
|
+
if kind is None:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
if kind in header_kinds:
|
|
107
|
+
_emit_header_name(node, "module", emit)
|
|
108
|
+
elif kind == K.ImplicitAnsiPort or kind == K.ExplicitAnsiPort:
|
|
109
|
+
_emit_declarator(node, "port", emit)
|
|
110
|
+
elif kind == K.PortDeclaration:
|
|
111
|
+
_emit_declarators(node, "port", emit)
|
|
112
|
+
elif kind == K.ParameterDeclaration:
|
|
113
|
+
_emit_parameter(node, emit)
|
|
114
|
+
elif kind == K.EnumType:
|
|
115
|
+
_emit_enum_members(node, emit)
|
|
116
|
+
elif kind == K.DataDeclaration:
|
|
117
|
+
_emit_signal_declarators(node, emit, is_net=False)
|
|
118
|
+
elif kind == K.NetDeclaration:
|
|
119
|
+
_emit_signal_declarators(node, emit, is_net=True)
|
|
120
|
+
elif kind == K.GenvarDeclaration:
|
|
121
|
+
_emit_genvars(node, emit)
|
|
122
|
+
elif kind == K.HierarchicalInstance:
|
|
123
|
+
_emit_instance(node, emit)
|
|
124
|
+
|
|
125
|
+
_walk(tree.root, visit)
|
|
126
|
+
out.sort(key=lambda d: (d.line, d.kind, d.name))
|
|
127
|
+
return out
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --------------------------------------------------------------------------------------
|
|
131
|
+
# per-kind extraction helpers
|
|
132
|
+
# --------------------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _walk(node: Any, visit: Any) -> None:
|
|
136
|
+
"""Depth-first walk, calling `visit(node)` on every syntax node."""
|
|
137
|
+
if node is None:
|
|
138
|
+
return
|
|
139
|
+
visit(node)
|
|
140
|
+
if isinstance(node, SyntaxNode):
|
|
141
|
+
for i in range(len(node)):
|
|
142
|
+
try:
|
|
143
|
+
child = node[i]
|
|
144
|
+
except Exception: # pragma: no cover - defensive against odd child slots
|
|
145
|
+
child = None
|
|
146
|
+
_walk(child, visit)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _emit_header_name(node: Any, kind: str, emit: Any) -> None:
|
|
150
|
+
name = getattr(node, "name", None)
|
|
151
|
+
if name is not None and not name.isMissing:
|
|
152
|
+
emit(kind, name)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _iter_declarators(node: Any, attr: str) -> Iterator[Any]:
|
|
156
|
+
"""Yield the `Declarator` items of `node.<attr>`, skipping the separator commas.
|
|
157
|
+
|
|
158
|
+
A pyslang separated list (`declarators`, `members`) iterates its comma tokens too;
|
|
159
|
+
we keep only the `Declarator` nodes.
|
|
160
|
+
"""
|
|
161
|
+
for item in getattr(node, attr, []):
|
|
162
|
+
if getattr(item, "kind", None) == SyntaxKind.Declarator and not item.name.isMissing:
|
|
163
|
+
yield item
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _emit_declarator(node: Any, kind: str, emit: Any) -> None:
|
|
167
|
+
dec = getattr(node, "declarator", None)
|
|
168
|
+
if dec is not None and not dec.name.isMissing:
|
|
169
|
+
emit(kind, dec.name)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _emit_declarators(node: Any, kind: str, emit: Any) -> None:
|
|
173
|
+
for dec in _iter_declarators(node, "declarators"):
|
|
174
|
+
emit(kind, dec.name)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _emit_parameter(node: Any, emit: Any) -> None:
|
|
178
|
+
keyword = getattr(node, "keyword", None)
|
|
179
|
+
is_local = keyword is not None and str(keyword.kind).endswith("LocalParamKeyword")
|
|
180
|
+
kind = "localparam" if is_local else "parameter"
|
|
181
|
+
for dec in _iter_declarators(node, "declarators"):
|
|
182
|
+
emit(kind, dec.name)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _emit_enum_members(node: Any, emit: Any) -> None:
|
|
186
|
+
for dec in _iter_declarators(node, "members"):
|
|
187
|
+
emit("enum_value", dec.name)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_SIGNAL_TYPE_KINDS = frozenset({"LogicType", "RegType"})
|
|
191
|
+
"""DataDeclaration types the check treats as signals: `logic` and `reg`. A declaration of
|
|
192
|
+
a user-defined type (`my_struct_t x;`) or an integer type (`int i;`) is not a signal, so
|
|
193
|
+
it is left alone -- matching the reference, which only matches `reg`/`wire`/`logic`."""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _emit_signal_declarators(node: Any, emit: Any, *, is_net: bool) -> None:
|
|
197
|
+
if not is_net:
|
|
198
|
+
# A DataDeclaration is only a signal when its type is `logic` or `reg`. Anything
|
|
199
|
+
# else (a named type, `int`, a struct) is not what this rule governs.
|
|
200
|
+
type_kind = str(getattr(getattr(node, "type", None), "kind", "")).rsplit(".", 1)[-1]
|
|
201
|
+
if type_kind not in _SIGNAL_TYPE_KINDS:
|
|
202
|
+
return
|
|
203
|
+
for dec in _iter_declarators(node, "declarators"):
|
|
204
|
+
# An unpacked dimension on the declarator (e.g. `logic [7:0] mem [0:15]`) makes
|
|
205
|
+
# this a memory; a plain declarator is a signal.
|
|
206
|
+
kind = "memory" if len(getattr(dec, "dimensions", [])) > 0 else "signal"
|
|
207
|
+
emit(kind, dec.name)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _emit_genvars(node: Any, emit: Any) -> None:
|
|
211
|
+
for ident in getattr(node, "identifiers", []):
|
|
212
|
+
if getattr(ident, "kind", None) != SyntaxKind.IdentifierName:
|
|
213
|
+
continue
|
|
214
|
+
token = getattr(ident, "identifier", None)
|
|
215
|
+
if token is not None and not token.isMissing:
|
|
216
|
+
emit("genvar", token)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _emit_instance(node: Any, emit: Any) -> None:
|
|
220
|
+
decl = getattr(node, "decl", None)
|
|
221
|
+
if decl is None:
|
|
222
|
+
return
|
|
223
|
+
name = getattr(decl, "name", None)
|
|
224
|
+
if name is not None and not name.isMissing:
|
|
225
|
+
emit("instance", name)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
__all__ = [
|
|
229
|
+
"Decl",
|
|
230
|
+
"ParseFailure",
|
|
231
|
+
"declared_identifiers",
|
|
232
|
+
"parse_errors",
|
|
233
|
+
"parse_file",
|
|
234
|
+
"parse_text",
|
|
235
|
+
]
|