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,203 @@
|
|
|
1
|
+
"""`PortsDiffCheck`: an IP's spec ports against the RTL ports of its top module (layer 1).
|
|
2
|
+
|
|
3
|
+
For each IP block, compares the spec's declared ports (`port:spec.<block>.*`, D36/M1-05)
|
|
4
|
+
to the ports of the block's top RTL module (DESIGN.md 4.4 "Spec và RTL", 4.8 layer 1):
|
|
5
|
+
|
|
6
|
+
- the top module is the one module owned by the block (`ModuleEntity.block`) that no
|
|
7
|
+
other module owned by the block instantiates; if there is not exactly one, a name
|
|
8
|
+
template (arg `top`, e.g. `tiny_{block}`) picks it; if none can be found, one `info`
|
|
9
|
+
issue is emitted for the block and it is not failed;
|
|
10
|
+
- reports a port missing in RTL, a port missing in the spec, a direction mismatch, and a
|
|
11
|
+
width mismatch (integer widths only; a string width is skipped).
|
|
12
|
+
|
|
13
|
+
With a `--block`, only that IP (or an instance's IP) is compared.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from chipgraph.checks._common import ArgError, error_result, optional_str
|
|
21
|
+
from chipgraph.checks._cross import (
|
|
22
|
+
block_param,
|
|
23
|
+
load_model_or_skip,
|
|
24
|
+
result_from_issues,
|
|
25
|
+
)
|
|
26
|
+
from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
|
|
27
|
+
from chipgraph.core.model.entities import ModuleEntity, PortEntity
|
|
28
|
+
from chipgraph.core.model.model import DesignModel
|
|
29
|
+
from chipgraph.core.plugin_api.types import ToolContext
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PortsDiffCheck:
|
|
33
|
+
"""Checks an IP's spec ports against the ports of its top RTL module."""
|
|
34
|
+
|
|
35
|
+
id = "ports_diff"
|
|
36
|
+
name = "Ports diff"
|
|
37
|
+
|
|
38
|
+
async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
|
|
39
|
+
start = time.monotonic()
|
|
40
|
+
try:
|
|
41
|
+
top_template = optional_str(spec.args, "top", "")
|
|
42
|
+
except ArgError as exc:
|
|
43
|
+
return error_result(spec, str(exc), start)
|
|
44
|
+
|
|
45
|
+
loaded = load_model_or_skip(spec, ctx, start)
|
|
46
|
+
if isinstance(loaded, CheckResult):
|
|
47
|
+
return loaded
|
|
48
|
+
model = loaded.model
|
|
49
|
+
block = block_param(ctx)
|
|
50
|
+
|
|
51
|
+
issues: list[Issue] = []
|
|
52
|
+
for ip in _ips_with_spec_ports(model):
|
|
53
|
+
if block is not None and ip != block:
|
|
54
|
+
continue
|
|
55
|
+
issues.extend(_diff_block(model, ip, top_template))
|
|
56
|
+
return result_from_issues(spec, issues, loaded, start)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _ips_with_spec_ports(model: DesignModel) -> list[str]:
|
|
60
|
+
"""Every IP block name that has at least one spec port, sorted."""
|
|
61
|
+
ips: set[str] = set()
|
|
62
|
+
for port in model.by_kind("port"):
|
|
63
|
+
assert isinstance(port, PortEntity)
|
|
64
|
+
ip = _spec_port_block(port)
|
|
65
|
+
if ip is not None:
|
|
66
|
+
ips.add(ip)
|
|
67
|
+
return sorted(ips)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _spec_port_block(port: PortEntity) -> str | None:
|
|
71
|
+
_, _, rest = port.key.partition(":")
|
|
72
|
+
parts = rest.split(".")
|
|
73
|
+
if len(parts) >= 3 and parts[0] == "spec":
|
|
74
|
+
return parts[1]
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _spec_port_name(port: PortEntity) -> str:
|
|
79
|
+
_, _, rest = port.key.partition(":")
|
|
80
|
+
return rest.split(".", 2)[2]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _diff_block(model: DesignModel, ip: str, top_template: str) -> list[Issue]:
|
|
84
|
+
top = _top_module(model, ip, top_template)
|
|
85
|
+
if top is None:
|
|
86
|
+
return [
|
|
87
|
+
Issue(
|
|
88
|
+
file=None,
|
|
89
|
+
line=None,
|
|
90
|
+
rule="ports_diff.no_top",
|
|
91
|
+
severity="info",
|
|
92
|
+
msg=(
|
|
93
|
+
f"no single top RTL module for IP {ip!r}; set the check's `top` template "
|
|
94
|
+
"(e.g. 'tiny_{block}') to compare its spec ports to RTL"
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
spec_ports = {
|
|
100
|
+
_spec_port_name(p): p
|
|
101
|
+
for p in model.by_kind("port")
|
|
102
|
+
if isinstance(p, PortEntity) and _spec_port_block(p) == ip
|
|
103
|
+
}
|
|
104
|
+
rtl_ports = {
|
|
105
|
+
p.name: p
|
|
106
|
+
for p in model.by_kind("port")
|
|
107
|
+
if isinstance(p, PortEntity) and getattr(p, "module", None) == top.key
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
issues: list[Issue] = []
|
|
111
|
+
for name in sorted(set(spec_ports) | set(rtl_ports)):
|
|
112
|
+
spec_port = spec_ports.get(name)
|
|
113
|
+
rtl_port = rtl_ports.get(name)
|
|
114
|
+
if spec_port is not None and rtl_port is None:
|
|
115
|
+
issues.append(
|
|
116
|
+
Issue(
|
|
117
|
+
file=spec_port.source.file,
|
|
118
|
+
line=spec_port.source.line,
|
|
119
|
+
rule="ports_diff.missing_in_rtl",
|
|
120
|
+
severity="error",
|
|
121
|
+
msg=f"spec port {name!r} of IP {ip!r} is missing from RTL module {top.name!r}",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
continue
|
|
125
|
+
if rtl_port is not None and spec_port is None:
|
|
126
|
+
issues.append(
|
|
127
|
+
Issue(
|
|
128
|
+
file=rtl_port.source.file,
|
|
129
|
+
line=rtl_port.source.line,
|
|
130
|
+
rule="ports_diff.missing_in_spec",
|
|
131
|
+
severity="error",
|
|
132
|
+
msg=(
|
|
133
|
+
f"RTL port {name!r} of module {top.name!r} is missing from IP {ip!r}'s spec"
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
continue
|
|
138
|
+
assert spec_port is not None and rtl_port is not None
|
|
139
|
+
if (
|
|
140
|
+
spec_port.direction is not None
|
|
141
|
+
and rtl_port.direction is not None
|
|
142
|
+
and spec_port.direction != rtl_port.direction
|
|
143
|
+
):
|
|
144
|
+
issues.append(
|
|
145
|
+
Issue(
|
|
146
|
+
file=rtl_port.source.file,
|
|
147
|
+
line=rtl_port.source.line,
|
|
148
|
+
rule="ports_diff.direction",
|
|
149
|
+
severity="error",
|
|
150
|
+
msg=(
|
|
151
|
+
f"port {name!r} of IP {ip!r}: spec direction {spec_port.direction!r} "
|
|
152
|
+
f"but RTL {rtl_port.direction!r}"
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
if (
|
|
157
|
+
isinstance(spec_port.width, int)
|
|
158
|
+
and isinstance(rtl_port.width, int)
|
|
159
|
+
and spec_port.width != rtl_port.width
|
|
160
|
+
):
|
|
161
|
+
issues.append(
|
|
162
|
+
Issue(
|
|
163
|
+
file=rtl_port.source.file,
|
|
164
|
+
line=rtl_port.source.line,
|
|
165
|
+
rule="ports_diff.width",
|
|
166
|
+
severity="error",
|
|
167
|
+
msg=(
|
|
168
|
+
f"port {name!r} of IP {ip!r}: spec width {spec_port.width} "
|
|
169
|
+
f"but RTL width {rtl_port.width}"
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
return issues
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _top_module(model: DesignModel, ip: str, top_template: str) -> ModuleEntity | None:
|
|
177
|
+
"""The block's top RTL module, or None when it cannot be identified.
|
|
178
|
+
|
|
179
|
+
The top is the one module owned by the block that no other module owned by the block
|
|
180
|
+
instantiates. If there is not exactly one such module, a `top` name template (with
|
|
181
|
+
`{block}` substituted) picks a module by name.
|
|
182
|
+
"""
|
|
183
|
+
ip_key = f"block:{ip}"
|
|
184
|
+
owned = [
|
|
185
|
+
m for m in model.by_kind("module") if isinstance(m, ModuleEntity) and m.block == ip_key
|
|
186
|
+
]
|
|
187
|
+
if owned:
|
|
188
|
+
instantiated = {
|
|
189
|
+
r.dst for m in owned for r in model.get_relations(src=m.key, kind="instantiates")
|
|
190
|
+
}
|
|
191
|
+
roots = [m for m in owned if m.key not in instantiated]
|
|
192
|
+
if len(roots) == 1:
|
|
193
|
+
return roots[0]
|
|
194
|
+
|
|
195
|
+
if top_template:
|
|
196
|
+
want = top_template.replace("{block}", ip)
|
|
197
|
+
for module in model.by_kind("module"):
|
|
198
|
+
if isinstance(module, ModuleEntity) and module.name == want:
|
|
199
|
+
return module
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
__all__ = ["PortsDiffCheck"]
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""`SpecSchemaCheck`: per-entity sanity of the spec side of the Design Model (layer 1).
|
|
2
|
+
|
|
3
|
+
Deterministic schema checks on what a spec declared, run over the model `chipgraph
|
|
4
|
+
ingest` built (DESIGN.md 4.4 "Schema", 4.8 layer 1):
|
|
5
|
+
|
|
6
|
+
- a register with no access, or no reset value;
|
|
7
|
+
- a field whose bits fall outside its register's width (32 when the width is unknown),
|
|
8
|
+
or overlap another field of the same register, or have `msb < lsb`;
|
|
9
|
+
- a spec port with no direction, or no width;
|
|
10
|
+
- a requirement with empty text;
|
|
11
|
+
- `requirement.missing_id`: in a MAS file that declares at least one requirement ID, a
|
|
12
|
+
top-level numbered item of its Verification section (`requirements.infer_heading`) that
|
|
13
|
+
carries no ID (the extractor records it with `attrs.id_source = "missing"`). The issue
|
|
14
|
+
points at the item's line and, when one can be proposed, names the next free ID of the
|
|
15
|
+
file (`attrs.suggested_id`). See docs/REQUIREMENT_IDS.md.
|
|
16
|
+
|
|
17
|
+
Scope: with a `--block` (D38 IP block), only entities of that block (or its instances)
|
|
18
|
+
are checked; without one, the whole model is checked.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
from chipgraph.checks._cross import (
|
|
26
|
+
block_param,
|
|
27
|
+
load_model_or_skip,
|
|
28
|
+
result_from_issues,
|
|
29
|
+
scope_block_keys,
|
|
30
|
+
)
|
|
31
|
+
from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
|
|
32
|
+
from chipgraph.core.model.entities import (
|
|
33
|
+
EntityBase,
|
|
34
|
+
FieldEntity,
|
|
35
|
+
PortEntity,
|
|
36
|
+
RegisterEntity,
|
|
37
|
+
RequirementEntity,
|
|
38
|
+
)
|
|
39
|
+
from chipgraph.core.model.model import DesignModel
|
|
40
|
+
from chipgraph.core.plugin_api.types import ToolContext
|
|
41
|
+
|
|
42
|
+
_DEFAULT_REGISTER_WIDTH = 32
|
|
43
|
+
"""The register width assumed for field-bounds checks when the register does not state
|
|
44
|
+
one (a common case: MAS register tables give field bits, not a register width)."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SpecSchemaCheck:
|
|
48
|
+
"""Checks the spec side of the Design Model for missing or inconsistent fields."""
|
|
49
|
+
|
|
50
|
+
id = "spec_schema"
|
|
51
|
+
name = "Spec schema"
|
|
52
|
+
|
|
53
|
+
async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
|
|
54
|
+
start = time.monotonic()
|
|
55
|
+
loaded = load_model_or_skip(spec, ctx, start)
|
|
56
|
+
if isinstance(loaded, CheckResult):
|
|
57
|
+
return loaded
|
|
58
|
+
model = loaded.model
|
|
59
|
+
scope = _scope(model, block_param(ctx))
|
|
60
|
+
|
|
61
|
+
issues: list[Issue] = []
|
|
62
|
+
issues.extend(_register_issues(model, scope))
|
|
63
|
+
issues.extend(_field_issues(model, scope))
|
|
64
|
+
issues.extend(_port_issues(model, scope))
|
|
65
|
+
issues.extend(_requirement_issues(model, scope))
|
|
66
|
+
return result_from_issues(spec, issues, loaded, start)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _scope(model: DesignModel, block: str | None) -> set[str] | None:
|
|
70
|
+
"""The set of block keys to restrict to, or None for the whole model."""
|
|
71
|
+
if block is None:
|
|
72
|
+
return None
|
|
73
|
+
return scope_block_keys(model, block)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _entity_block(entity: EntityBase) -> str | None:
|
|
77
|
+
"""The block key an entity belongs to, from its typed field or `attrs.block`."""
|
|
78
|
+
block = getattr(entity, "block", None)
|
|
79
|
+
if isinstance(block, str) and block:
|
|
80
|
+
return block
|
|
81
|
+
attr_block = entity.attrs.get("block")
|
|
82
|
+
if isinstance(attr_block, str) and attr_block:
|
|
83
|
+
return attr_block
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _prov(entity: EntityBase) -> tuple[str | None, int | None]:
|
|
88
|
+
return entity.source.file, entity.source.line
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _register_issues(model: DesignModel, scope: set[str] | None) -> list[Issue]:
|
|
92
|
+
issues: list[Issue] = []
|
|
93
|
+
for reg in model.by_kind("register"):
|
|
94
|
+
assert isinstance(reg, RegisterEntity)
|
|
95
|
+
if scope is not None and _entity_block(reg) not in scope:
|
|
96
|
+
continue
|
|
97
|
+
file, line = _prov(reg)
|
|
98
|
+
if not reg.access:
|
|
99
|
+
issues.append(
|
|
100
|
+
Issue(
|
|
101
|
+
file=file,
|
|
102
|
+
line=line,
|
|
103
|
+
rule="register.no_access",
|
|
104
|
+
severity="error",
|
|
105
|
+
msg=f"register {reg.key!r} has no access mode",
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
if reg.reset_value is None:
|
|
109
|
+
issues.append(
|
|
110
|
+
Issue(
|
|
111
|
+
file=file,
|
|
112
|
+
line=line,
|
|
113
|
+
rule="register.no_reset",
|
|
114
|
+
severity="error",
|
|
115
|
+
msg=f"register {reg.key!r} has no reset value",
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
return issues
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _field_issues(model: DesignModel, scope: set[str] | None) -> list[Issue]:
|
|
122
|
+
issues: list[Issue] = []
|
|
123
|
+
fields_by_register: dict[str, list[FieldEntity]] = {}
|
|
124
|
+
for field in model.by_kind("field"):
|
|
125
|
+
assert isinstance(field, FieldEntity)
|
|
126
|
+
if field.register is None:
|
|
127
|
+
continue
|
|
128
|
+
fields_by_register.setdefault(field.register, []).append(field)
|
|
129
|
+
|
|
130
|
+
for reg_key, fields in fields_by_register.items():
|
|
131
|
+
reg = model.get(reg_key)
|
|
132
|
+
if scope is not None and (reg is None or _entity_block(reg) not in scope):
|
|
133
|
+
continue
|
|
134
|
+
width = reg.width if isinstance(reg, RegisterEntity) and reg.width else None
|
|
135
|
+
limit = width if width is not None else _DEFAULT_REGISTER_WIDTH
|
|
136
|
+
placed: list[tuple[int, int, str]] = [] # (lsb, msb, key), for overlap
|
|
137
|
+
for field in sorted(fields, key=lambda f: f.key):
|
|
138
|
+
file, line = _prov(field)
|
|
139
|
+
if field.msb is None or field.lsb is None:
|
|
140
|
+
# A field with no bit range cannot be range-checked; skip silently: a
|
|
141
|
+
# missing width is a different concern from an out-of-range field.
|
|
142
|
+
continue
|
|
143
|
+
if field.msb < field.lsb:
|
|
144
|
+
issues.append(
|
|
145
|
+
Issue(
|
|
146
|
+
file=file,
|
|
147
|
+
line=line,
|
|
148
|
+
rule="field.msb_lt_lsb",
|
|
149
|
+
severity="error",
|
|
150
|
+
msg=f"field {field.key!r} has msb {field.msb} < lsb {field.lsb}",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
if field.msb >= limit:
|
|
155
|
+
issues.append(
|
|
156
|
+
Issue(
|
|
157
|
+
file=file,
|
|
158
|
+
line=line,
|
|
159
|
+
rule="field.out_of_range",
|
|
160
|
+
severity="error",
|
|
161
|
+
msg=(
|
|
162
|
+
f"field {field.key!r} bits [{field.msb}:{field.lsb}] fall outside "
|
|
163
|
+
f"register {reg_key!r} width {limit}"
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
continue
|
|
168
|
+
for lsb2, msb2, key2 in placed:
|
|
169
|
+
if field.lsb <= msb2 and lsb2 <= field.msb:
|
|
170
|
+
issues.append(
|
|
171
|
+
Issue(
|
|
172
|
+
file=file,
|
|
173
|
+
line=line,
|
|
174
|
+
rule="field.overlap",
|
|
175
|
+
severity="error",
|
|
176
|
+
msg=(
|
|
177
|
+
f"field {field.key!r} bits [{field.msb}:{field.lsb}] overlap "
|
|
178
|
+
f"field {key2!r} bits [{msb2}:{lsb2}]"
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
break
|
|
183
|
+
placed.append((field.lsb, field.msb, field.key))
|
|
184
|
+
return issues
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _port_issues(model: DesignModel, scope: set[str] | None) -> list[Issue]:
|
|
188
|
+
issues: list[Issue] = []
|
|
189
|
+
for port in model.by_kind("port"):
|
|
190
|
+
assert isinstance(port, PortEntity)
|
|
191
|
+
block = _spec_port_block(port)
|
|
192
|
+
if block is None:
|
|
193
|
+
continue # not a spec port (`port:spec.<block>.*`)
|
|
194
|
+
if scope is not None and f"block:{block}" not in scope:
|
|
195
|
+
continue
|
|
196
|
+
file, line = _prov(port)
|
|
197
|
+
if port.direction is None:
|
|
198
|
+
issues.append(
|
|
199
|
+
Issue(
|
|
200
|
+
file=file,
|
|
201
|
+
line=line,
|
|
202
|
+
rule="port.no_direction",
|
|
203
|
+
severity="error",
|
|
204
|
+
msg=f"spec port {port.key!r} has no direction",
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
if port.width is None:
|
|
208
|
+
issues.append(
|
|
209
|
+
Issue(
|
|
210
|
+
file=file,
|
|
211
|
+
line=line,
|
|
212
|
+
rule="port.no_width",
|
|
213
|
+
severity="error",
|
|
214
|
+
msg=f"spec port {port.key!r} has no width",
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
return issues
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _spec_port_block(port: PortEntity) -> str | None:
|
|
221
|
+
"""The block name of a spec port key (`port:spec.<block>.<name>`), else None."""
|
|
222
|
+
_, _, rest = port.key.partition(":")
|
|
223
|
+
parts = rest.split(".")
|
|
224
|
+
if len(parts) >= 3 and parts[0] == "spec":
|
|
225
|
+
return parts[1]
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _requirement_issues(model: DesignModel, scope: set[str] | None) -> list[Issue]:
|
|
230
|
+
issues: list[Issue] = []
|
|
231
|
+
for req in model.by_kind("requirement"):
|
|
232
|
+
assert isinstance(req, RequirementEntity)
|
|
233
|
+
if scope is not None and not _requirement_in_scope(req, scope):
|
|
234
|
+
continue
|
|
235
|
+
if req.attrs.get("id_source") == "missing":
|
|
236
|
+
file, line = _prov(req)
|
|
237
|
+
suggested = req.attrs.get("suggested_id")
|
|
238
|
+
hint = f" Suggested next ID: {suggested}" if isinstance(suggested, str) else ""
|
|
239
|
+
issues.append(
|
|
240
|
+
Issue(
|
|
241
|
+
file=file,
|
|
242
|
+
line=line,
|
|
243
|
+
rule="requirement.missing_id",
|
|
244
|
+
severity="error",
|
|
245
|
+
msg=(
|
|
246
|
+
f"Verification item has no ID; the other items of this file have one.{hint}"
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
if req.text is None or not req.text.strip():
|
|
251
|
+
file, line = _prov(req)
|
|
252
|
+
issues.append(
|
|
253
|
+
Issue(
|
|
254
|
+
file=file,
|
|
255
|
+
line=line,
|
|
256
|
+
rule="requirement.empty_text",
|
|
257
|
+
severity="error",
|
|
258
|
+
msg=f"requirement {req.key!r} has empty text",
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
return issues
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _requirement_in_scope(req: RequirementEntity, scope: set[str]) -> bool:
|
|
265
|
+
"""Whether a requirement belongs to a scoped block, by its `block`/key prefix."""
|
|
266
|
+
block = _entity_block(req)
|
|
267
|
+
if block is not None:
|
|
268
|
+
return block in scope
|
|
269
|
+
# A requirement key is `requirement:<block>.h<hash>` (inferred or missing, D37) or
|
|
270
|
+
# `requirement:<ID>` (declared). Only the hashed form carries a block; a declared
|
|
271
|
+
# ID that names no block is left to a whole-model run.
|
|
272
|
+
_, _, rest = req.key.partition(":")
|
|
273
|
+
head = rest.split(".", 1)[0]
|
|
274
|
+
return f"block:{head}" in scope
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
__all__ = ["SpecSchemaCheck"]
|