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,144 @@
|
|
|
1
|
+
"""Where a block's RTL and testbench files go: from the profile's `layout` (M2-11a).
|
|
2
|
+
|
|
3
|
+
A project says where each kind of file lives with `layout:` templates (and per block,
|
|
4
|
+
`blocks.<b>.layout`). The built-in rules write new files there:
|
|
5
|
+
|
|
6
|
+
- `digital-rtl/rtl_module` writes `{layout.rtl}/{module}.sv`;
|
|
7
|
+
- `dv/tb_module` writes `{layout.tb}/test_{block}.py`;
|
|
8
|
+
- a plan's write sets must stay inside the block's `rtl` and `tb` layout paths.
|
|
9
|
+
|
|
10
|
+
`layout_dir(resolved, block, kind)` is the directory part of the block's first `kind`
|
|
11
|
+
template: `{block}`/`{BLOCK}` filled in, cut before the file name and before the first
|
|
12
|
+
segment with a glob or another placeholder. QSoC's `design/{block}/rtl/*.sv` gives
|
|
13
|
+
`design/pwm/rtl` for `pwm`; tinysoc's `rtl/*.sv` gives `rtl`; `dv/{block}/**` gives
|
|
14
|
+
`dv/timer`. With a list of templates the first one is where new files go.
|
|
15
|
+
|
|
16
|
+
`layout_vars(resolved, block)` is what the graph fills `{layout.rtl}` and `{layout.tb}`
|
|
17
|
+
with (template values, not params: they never change an instance id). A project with no
|
|
18
|
+
`rtl` or `tb` layout key keeps the built-in defaults, `rtl` and `dv/{block}`.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
|
|
26
|
+
from chipgraph.core.config.loader import ResolvedProfile
|
|
27
|
+
from chipgraph.core.config.models import Profile
|
|
28
|
+
|
|
29
|
+
LayoutValue = str | tuple[str, ...]
|
|
30
|
+
|
|
31
|
+
RTL_VAR = "layout.rtl"
|
|
32
|
+
"""The template value of the block's RTL directory."""
|
|
33
|
+
TB_VAR = "layout.tb"
|
|
34
|
+
"""The template value of the block's testbench directory."""
|
|
35
|
+
|
|
36
|
+
DEFAULT_DIRS: Mapping[str, str] = {"rtl": "rtl", "tb": "dv/{block}"}
|
|
37
|
+
"""The directories the built-in rules use when the profile has no layout key for a kind."""
|
|
38
|
+
|
|
39
|
+
_KINDS: Mapping[str, str] = {RTL_VAR: "rtl", TB_VAR: "tb"}
|
|
40
|
+
_DYNAMIC = re.compile(r"[*?\[\]{}]")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LayoutError(ValueError):
|
|
44
|
+
"""The profile's layout does not say where a kind of file goes."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _profile(resolved: ResolvedProfile | Profile, block: str) -> Profile:
|
|
48
|
+
if isinstance(resolved, ResolvedProfile):
|
|
49
|
+
return resolved.for_block(block)
|
|
50
|
+
override = resolved.blocks.get(block)
|
|
51
|
+
if override is not None and override.layout:
|
|
52
|
+
return resolved.model_copy(update={"layout": {**resolved.layout, **override.layout}})
|
|
53
|
+
return resolved
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _fill(template: str, block: str) -> str:
|
|
57
|
+
return template.replace("{block}", block).replace("{BLOCK}", block.upper())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def templates(resolved: ResolvedProfile | Profile, block: str, kind: str) -> tuple[str, ...]:
|
|
61
|
+
"""The block's `kind` layout templates, `{block}`/`{BLOCK}` filled in (maybe none)."""
|
|
62
|
+
value: LayoutValue | None = _profile(resolved, block).layout.get(kind)
|
|
63
|
+
if value is None:
|
|
64
|
+
return ()
|
|
65
|
+
raw = (value,) if isinstance(value, str) else tuple(value)
|
|
66
|
+
return tuple(_fill(t, block) for t in raw)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def template_dir(template: str) -> str:
|
|
70
|
+
"""The directory part of a filled layout template ('' when it has none)."""
|
|
71
|
+
parts = template.split("/")
|
|
72
|
+
out: list[str] = []
|
|
73
|
+
for part in parts[:-1]: # the last segment is the file (or `**`)
|
|
74
|
+
if not part or part == "." or _DYNAMIC.search(part):
|
|
75
|
+
break
|
|
76
|
+
out.append(part)
|
|
77
|
+
return "/".join(out)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def layout_dir(resolved: ResolvedProfile | Profile, block: str, kind: str) -> str:
|
|
81
|
+
"""The directory new `kind` files of `block` go to (see the module docstring).
|
|
82
|
+
|
|
83
|
+
Raises `LayoutError` when the block's layout has no `kind` key, or its first
|
|
84
|
+
template has no directory part.
|
|
85
|
+
"""
|
|
86
|
+
found = templates(resolved, block, kind)
|
|
87
|
+
if not found:
|
|
88
|
+
raise LayoutError(
|
|
89
|
+
f"block {block!r} has no layout.{kind}: say where its {kind} files go in "
|
|
90
|
+
f'.chipgraph.yml, e.g. `layout: {{ {kind}: "<dir>/*" }}`'
|
|
91
|
+
)
|
|
92
|
+
directory = template_dir(found[0])
|
|
93
|
+
if not directory or directory == ".." or directory.startswith("../"):
|
|
94
|
+
raise LayoutError(
|
|
95
|
+
f"block {block!r}: layout.{kind} {found[0]!r} has no directory part before its "
|
|
96
|
+
"first glob or placeholder; start it with the directory, e.g. 'rtl/*.sv'"
|
|
97
|
+
)
|
|
98
|
+
return directory
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def dir_or_default(resolved: ResolvedProfile | Profile, block: str, kind: str) -> str:
|
|
102
|
+
"""`layout_dir`, or the built-in default (`DEFAULT_DIRS`) when the key is missing."""
|
|
103
|
+
if not templates(resolved, block, kind):
|
|
104
|
+
return _fill(DEFAULT_DIRS[kind], block)
|
|
105
|
+
return layout_dir(resolved, block, kind)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def layout_vars(resolved: ResolvedProfile | Profile, block: str) -> dict[str, str]:
|
|
109
|
+
"""`{layout.rtl}` and `{layout.tb}` for `block` (raises `LayoutError` on a bad template)."""
|
|
110
|
+
return {var: dir_or_default(resolved, block, kind) for var, kind in _KINDS.items()}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def write_globs(resolved: ResolvedProfile | Profile, block: str) -> tuple[str, ...]:
|
|
114
|
+
"""The paths a plan of `block` may write: its `rtl` and `tb` layout templates.
|
|
115
|
+
|
|
116
|
+
A kind with no layout key allows everything under its default directory.
|
|
117
|
+
"""
|
|
118
|
+
out: list[str] = []
|
|
119
|
+
for kind in ("rtl", "tb"):
|
|
120
|
+
found = templates(resolved, block, kind)
|
|
121
|
+
out.extend(found or (f"{_fill(DEFAULT_DIRS[kind], block)}/**",))
|
|
122
|
+
return tuple(dict.fromkeys(out))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def fill_vars(template: str, values: Mapping[str, str]) -> str:
|
|
126
|
+
"""`template` with each `{name}` of `values` replaced; other placeholders stay."""
|
|
127
|
+
for name, value in values.items():
|
|
128
|
+
template = template.replace("{" + name + "}", value)
|
|
129
|
+
return template
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"DEFAULT_DIRS",
|
|
134
|
+
"RTL_VAR",
|
|
135
|
+
"TB_VAR",
|
|
136
|
+
"LayoutError",
|
|
137
|
+
"dir_or_default",
|
|
138
|
+
"fill_vars",
|
|
139
|
+
"layout_dir",
|
|
140
|
+
"layout_vars",
|
|
141
|
+
"template_dir",
|
|
142
|
+
"templates",
|
|
143
|
+
"write_globs",
|
|
144
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
name: digital-rtl
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
description: >-
|
|
5
|
+
Rules and skills for digital RTL (DESIGN.md 8.2). Ships the Critic review of a block's
|
|
6
|
+
change (the rule `digital-rtl/review`, the skill `review/diff`) and the Planner: the
|
|
7
|
+
rule `digital-rtl/plan` (skill `plan/modules`, check `plan_check`) and the gate
|
|
8
|
+
`plan:{block}` on `digital-rtl/plan_expand`, after which rules may say
|
|
9
|
+
`foreach: plan.modules`; and the RTL of each planned module: the rule
|
|
10
|
+
`digital-rtl/rtl_module` (role `rtl-author`, which never sees a testbench; skills
|
|
11
|
+
`lang-sv/rtl` and `org/naming`; checks format, lint, naming, style, hardcode). The
|
|
12
|
+
target `rtl:<block>` (`targets/`) ties them to the testbench of the `dv` pack, the review and
|
|
13
|
+
a pull request: `open_pr` pushes the block's branch and opens the PR, `pr_gate` waits on
|
|
14
|
+
the gate `pr:{block}`; chipgraph never merges. Where a module goes comes from the profile's
|
|
15
|
+
`layout.rtl`, the testbench's from `layout.tb`. Also the wrappers around vendor IP in the
|
|
16
|
+
emacs verilog-mode AUTO style: the rule `digital-rtl/wrapper` (role `rtl-author`, skill
|
|
17
|
+
`rtl/wrapper-auto`) writes a wrapper's source, `digital-rtl/wrapper_gen` (tool `emacs_auto`)
|
|
18
|
+
expands it (needs Emacs 31 or newer), check `wrap_check`.
|
|
19
|
+
provides:
|
|
20
|
+
rules:
|
|
21
|
+
- rules
|
|
22
|
+
- wrapper/rules
|
|
23
|
+
skills:
|
|
24
|
+
- skills
|
|
25
|
+
- wrapper/skills
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""A block's plan: the file a Planner writes, and what the engine does with it (M2-03).
|
|
2
|
+
|
|
3
|
+
- `model`: the plan file (`Plan`, `schemas/formats/plan.schema.json`);
|
|
4
|
+
- `chipgraph.checks.plan_check`: `plan_check`, the validation before a person approves;
|
|
5
|
+
- `resolver`: the `foreach: plan.modules` expansion, only from an approved plan;
|
|
6
|
+
- `expand`: the `plan_expand` tool behind the gate `plan:<block>`;
|
|
7
|
+
- `show`: the summary `chipgraph plan show <block>` prints for the approver;
|
|
8
|
+
- `context`: what a `planner` task gets in its context.
|
|
9
|
+
|
|
10
|
+
Only the model is imported here: the other modules import the check, which imports the
|
|
11
|
+
model, so importing them from this package's `__init__` would make a cycle.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from chipgraph.packs.digital_rtl.plan.model import (
|
|
15
|
+
ModuleBudget,
|
|
16
|
+
Plan,
|
|
17
|
+
PlanInterface,
|
|
18
|
+
PlanModule,
|
|
19
|
+
PlanPort,
|
|
20
|
+
PlanWrite,
|
|
21
|
+
UnassignedReq,
|
|
22
|
+
schema_json,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ModuleBudget",
|
|
27
|
+
"Plan",
|
|
28
|
+
"PlanInterface",
|
|
29
|
+
"PlanModule",
|
|
30
|
+
"PlanPort",
|
|
31
|
+
"PlanWrite",
|
|
32
|
+
"UnassignedReq",
|
|
33
|
+
"schema_json",
|
|
34
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""`python -m chipgraph.packs.digital_rtl.plan [PATH]`: write the plan file JSON Schema."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from chipgraph.packs.digital_rtl.plan.model import schema_json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
out = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("schemas/formats/plan.schema.json")
|
|
11
|
+
out.write_text(schema_json(), encoding="utf-8")
|
|
12
|
+
print(f"wrote {out}")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""What a `planner` task gets in its context, on top of its inputs (DESIGN.md 5.1, 5.3).
|
|
2
|
+
|
|
3
|
+
So the reply is a valid plan the first time: the block's slice of the Design Model
|
|
4
|
+
(requirements, interface ports, existing modules and their ports), the layout paths a
|
|
5
|
+
module may write, the files that already exist there and those other rules produce,
|
|
6
|
+
the files each module's writes must include, the limits, the naming rule for module
|
|
7
|
+
names, and the plan file's JSON Schema.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from chipgraph.checks._common import compile_template, iter_repo_files
|
|
17
|
+
from chipgraph.checks.plan_check import PlanEnv, PlanEnvError, load_env
|
|
18
|
+
from chipgraph.core.config.loader import ResolvedProfile
|
|
19
|
+
from chipgraph.core.contracts import RuleSpec
|
|
20
|
+
from chipgraph.packs.digital_rtl.plan.model import Plan
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _ports(ports: Sequence[Any]) -> list[dict[str, Any]]:
|
|
24
|
+
return [{"name": p.name, "direction": p.direction, "width": p.width} for p in ports]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _naming(env: PlanEnv) -> dict[str, Any] | None:
|
|
28
|
+
if env.naming is None:
|
|
29
|
+
return None
|
|
30
|
+
module = env.naming.kind_patterns.get("module")
|
|
31
|
+
return {
|
|
32
|
+
"rules": env.naming_ref,
|
|
33
|
+
"module": (
|
|
34
|
+
{"rule": module[0], "pattern": module[1].pattern, "message": module[2]}
|
|
35
|
+
if module is not None
|
|
36
|
+
else None
|
|
37
|
+
),
|
|
38
|
+
"vocabulary": dict(env.naming.vocabulary.replace) if env.naming.vocabulary else {},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def planner_context(
|
|
43
|
+
root: Path,
|
|
44
|
+
resolved: ResolvedProfile,
|
|
45
|
+
rules: Sequence[RuleSpec],
|
|
46
|
+
block: str,
|
|
47
|
+
*,
|
|
48
|
+
plan_path: str,
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
"""The extra context of block `block`'s planner task (`get_context` key `plan`)."""
|
|
51
|
+
try:
|
|
52
|
+
env = load_env(root, block, resolved=resolved, rules=rules)
|
|
53
|
+
except PlanEnvError as exc:
|
|
54
|
+
return {"block": block, "plan_path": plan_path, "note": str(exc)}
|
|
55
|
+
layout = [compile_template(t) for t in env.layout]
|
|
56
|
+
in_layout = [f for f in iter_repo_files(root) if any(t.match(f) is not None for t in layout)]
|
|
57
|
+
taken = {**env.agent_outputs, **env.engine_outputs}
|
|
58
|
+
return {
|
|
59
|
+
"block": block,
|
|
60
|
+
"plan_path": plan_path,
|
|
61
|
+
"requirements": [
|
|
62
|
+
{"id": r.ref, "declared": r.declared, "text": r.text} for r in env.requirements
|
|
63
|
+
],
|
|
64
|
+
"interface": {"source": env.interface_source, "ports": _ports(env.interface)},
|
|
65
|
+
"existing_modules": [
|
|
66
|
+
{"name": m.name, "file": m.file, "ports": _ports(m.ports)} for m in env.existing_modules
|
|
67
|
+
],
|
|
68
|
+
"layout": list(env.layout),
|
|
69
|
+
"existing_files": [f for f in in_layout if f not in taken],
|
|
70
|
+
"other_rules_write": sorted(
|
|
71
|
+
p for p in taken if any(t.match(p) is not None for t in layout)
|
|
72
|
+
),
|
|
73
|
+
"module_outputs": [
|
|
74
|
+
{"rule": rule_id, "template": template} for rule_id, template in env.module_outputs
|
|
75
|
+
],
|
|
76
|
+
"limits": env.limits.model_dump(mode="json"),
|
|
77
|
+
"naming": _naming(env),
|
|
78
|
+
"schema": Plan.model_json_schema(),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ["planner_context"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""`PlanExpandTool`: the `plan_expand` tool of rule `digital-rtl/plan_expand`.
|
|
2
|
+
|
|
3
|
+
The rule is gated by `plan:{block}`, so the scheduler runs this only once a person has
|
|
4
|
+
approved the plan at its current hash. It checks the plan once more (all of
|
|
5
|
+
`plan_check`, existing files included) and writes the approved plan artifact
|
|
6
|
+
(`plan/<block>.approved.json` by default): the block, the plan's path and hash, the
|
|
7
|
+
modules in build order with their dependencies and write sets. Every `plan.modules`
|
|
8
|
+
instance takes that artifact as an input, so it runs after this step.
|
|
9
|
+
|
|
10
|
+
Rule args (both `{block}` templates): `plan` (default `plan/{block}.plan.yml`) and
|
|
11
|
+
`out` (default `plan/{block}.approved.json`).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
from chipgraph.checks._common import compute_idempotency_key
|
|
20
|
+
from chipgraph.checks.plan_check import (
|
|
21
|
+
DEFAULT_PLAN_PATH,
|
|
22
|
+
PlanEnvError,
|
|
23
|
+
check_plan_file,
|
|
24
|
+
topo_order,
|
|
25
|
+
)
|
|
26
|
+
from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
|
|
27
|
+
from chipgraph.core.plugin_api.types import ToolContext
|
|
28
|
+
|
|
29
|
+
DEFAULT_APPROVED_PATH = "plan/{block}.approved.json"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _result(spec: CheckSpec, status: str, issues: tuple[Issue, ...], start: float) -> CheckResult:
|
|
33
|
+
return CheckResult(
|
|
34
|
+
check_id=spec.id,
|
|
35
|
+
status=status, # type: ignore[arg-type]
|
|
36
|
+
issues=issues,
|
|
37
|
+
duration_s=time.monotonic() - start,
|
|
38
|
+
idempotency_key=compute_idempotency_key(spec.id, spec.args, ()),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PlanExpandTool:
|
|
43
|
+
"""Writes a block's approved plan artifact once its gate is approved."""
|
|
44
|
+
|
|
45
|
+
name = "plan_expand"
|
|
46
|
+
capability = "gen"
|
|
47
|
+
|
|
48
|
+
async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
|
|
49
|
+
start = time.monotonic()
|
|
50
|
+
block = ctx.params.get("block")
|
|
51
|
+
plan_t = spec.args.get("plan", DEFAULT_PLAN_PATH)
|
|
52
|
+
out_t = spec.args.get("out", DEFAULT_APPROVED_PATH)
|
|
53
|
+
if not block or not isinstance(plan_t, str) or not isinstance(out_t, str):
|
|
54
|
+
msg = "plan_expand needs a block param and string 'plan'/'out' templates"
|
|
55
|
+
return _result(spec, "error", (Issue(msg=msg),), start)
|
|
56
|
+
try:
|
|
57
|
+
report = check_plan_file(ctx.repo_root, block, plan_path=plan_t)
|
|
58
|
+
except PlanEnvError as exc:
|
|
59
|
+
return _result(spec, "error", (Issue(rule="plan.env", msg=str(exc)),), start)
|
|
60
|
+
if not report.exists:
|
|
61
|
+
issue = Issue(file=report.path, rule="plan.missing", msg=f"no plan at {report.path}")
|
|
62
|
+
return _result(spec, "fail", (issue,), start)
|
|
63
|
+
if not report.ok or report.plan is None:
|
|
64
|
+
errors = tuple(i for i in report.issues if i.severity == "error")
|
|
65
|
+
return _result(spec, "fail", errors, start)
|
|
66
|
+
|
|
67
|
+
plan = report.plan
|
|
68
|
+
order = topo_order(plan)
|
|
69
|
+
artifact = {
|
|
70
|
+
"schema_version": 1,
|
|
71
|
+
"block": block,
|
|
72
|
+
"plan": report.path,
|
|
73
|
+
"plan_sha256": report.sha256,
|
|
74
|
+
"top": plan.top,
|
|
75
|
+
"order": order,
|
|
76
|
+
"modules": [
|
|
77
|
+
{
|
|
78
|
+
"name": name,
|
|
79
|
+
"depends_on": list(module.depends_on),
|
|
80
|
+
"writes": list(module.write_paths),
|
|
81
|
+
"budget": module.budget.model_dump(mode="json"),
|
|
82
|
+
}
|
|
83
|
+
for name in order
|
|
84
|
+
if (module := plan.module(name)) is not None
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
out = ctx.repo_root / out_t.replace("{block}", block)
|
|
88
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
out.write_text(json.dumps(artifact, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
90
|
+
warnings = tuple(i for i in report.issues if i.severity != "error")
|
|
91
|
+
return _result(spec, "pass", warnings, start)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = ["DEFAULT_APPROVED_PATH", "PlanExpandTool"]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""The plan file a Planner writes for one block: `plan/<block>.plan.yml` (DESIGN.md 5.1, 5.3).
|
|
2
|
+
|
|
3
|
+
A plan splits a block into modules. Each module says what it does, which requirements
|
|
4
|
+
it covers, its interface (ports taken from the Design Model, F1), which other modules
|
|
5
|
+
it depends on, the files it writes (its write set, F2) and its budget. The plan also
|
|
6
|
+
lists the assumptions it rests on (F4), the questions a person must answer, and every
|
|
7
|
+
requirement of the block it does not assign, with the reason.
|
|
8
|
+
|
|
9
|
+
The plan is data a person approves (gate `plan:<block>`) before any node it describes
|
|
10
|
+
can run; `plan_check` (`chipgraph.checks.plan_check`) validates it. The JSON Schema is
|
|
11
|
+
committed at `schemas/formats/plan.schema.json` (regenerate with
|
|
12
|
+
`python -m chipgraph.packs.digital_rtl.plan`).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
from typing import Literal
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
22
|
+
|
|
23
|
+
from chipgraph.core.contracts._paths import validate_relative_path
|
|
24
|
+
from chipgraph.core.contracts.types import ModelTier
|
|
25
|
+
|
|
26
|
+
_NAME_RE = r"^[A-Za-z_][A-Za-z0-9_]*$"
|
|
27
|
+
_GLOB_OR_PLACEHOLDER = re.compile(r"[*?\[\]{}]")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _Model(BaseModel):
|
|
31
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PlanPort(_Model):
|
|
35
|
+
"""One port of a module: from the block's interface in the Design Model, or internal."""
|
|
36
|
+
|
|
37
|
+
name: str = Field(pattern=_NAME_RE, description="The port name, as in the Design Model.")
|
|
38
|
+
direction: Literal["input", "output", "inout"] = Field(description="The port direction.")
|
|
39
|
+
width: int | str = Field(description="The port width: bits, or the model's expression.")
|
|
40
|
+
internal: bool = Field(
|
|
41
|
+
default=False,
|
|
42
|
+
description=(
|
|
43
|
+
"A helper port between modules of the block, not on the block's interface. "
|
|
44
|
+
"Not allowed on the block's top module."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PlanInterface(_Model):
|
|
50
|
+
"""A module's interface."""
|
|
51
|
+
|
|
52
|
+
ports: tuple[PlanPort, ...] = Field(default=(), description="The module's ports.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PlanWrite(_Model):
|
|
56
|
+
"""One file a module writes."""
|
|
57
|
+
|
|
58
|
+
path: str = Field(description="Repo-relative POSIX path of the file.")
|
|
59
|
+
replaces: bool = Field(
|
|
60
|
+
default=False,
|
|
61
|
+
description="The file exists and this module replaces it (said explicitly).",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@field_validator("path")
|
|
65
|
+
@classmethod
|
|
66
|
+
def _check_path(cls, value: str) -> str:
|
|
67
|
+
validate_relative_path(value, field_name="writes.path")
|
|
68
|
+
if _GLOB_OR_PLACEHOLDER.search(value):
|
|
69
|
+
raise ValueError(f"writes.path must be a plain file path, got {value!r}")
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ModuleBudget(_Model):
|
|
74
|
+
"""How much a module's nodes may spend: tries and the model tier."""
|
|
75
|
+
|
|
76
|
+
tries: int = Field(default=3, ge=1, description="Tries the module's agent task may use.")
|
|
77
|
+
tier: ModelTier = Field(default="medium", description="The model tier to start with.")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class PlanModule(_Model):
|
|
81
|
+
"""One module of the plan: one dynamic node per downstream rule."""
|
|
82
|
+
|
|
83
|
+
name: str = Field(pattern=_NAME_RE, description="The module name (an RTL identifier).")
|
|
84
|
+
summary: str = Field(min_length=1, description="What the module does, in a sentence or two.")
|
|
85
|
+
reqs: tuple[str, ...] = Field(
|
|
86
|
+
default=(), description="Requirements it covers: REQ ids or inferred requirement keys."
|
|
87
|
+
)
|
|
88
|
+
interface: PlanInterface = Field(
|
|
89
|
+
default_factory=PlanInterface, description="The module's ports."
|
|
90
|
+
)
|
|
91
|
+
depends_on: tuple[str, ...] = Field(
|
|
92
|
+
default=(), description="Modules of this plan that must be built first."
|
|
93
|
+
)
|
|
94
|
+
writes: tuple[PlanWrite, ...] = Field(
|
|
95
|
+
min_length=1, description="Every file the module's nodes write (its write set)."
|
|
96
|
+
)
|
|
97
|
+
budget: ModuleBudget = Field(
|
|
98
|
+
default_factory=ModuleBudget, description="The module's tries and model tier."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def write_paths(self) -> tuple[str, ...]:
|
|
103
|
+
"""The paths of `writes`, in order."""
|
|
104
|
+
return tuple(w.path for w in self.writes)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class UnassignedReq(_Model):
|
|
108
|
+
"""A requirement of the block that no module covers, and why."""
|
|
109
|
+
|
|
110
|
+
req: str = Field(min_length=1, description="The REQ id or inferred requirement key.")
|
|
111
|
+
reason: str = Field(min_length=1, description="Why no module of this plan covers it.")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Plan(_Model):
|
|
115
|
+
"""A block's plan: its modules, their write sets and order, and what a person must read."""
|
|
116
|
+
|
|
117
|
+
schema_version: Literal[1] = Field(default=1, description="Schema version of this file.")
|
|
118
|
+
block: str = Field(min_length=1, description="The block this plan is for.")
|
|
119
|
+
top: str = Field(description="The module that is the block's top (its interface).")
|
|
120
|
+
modules: tuple[PlanModule, ...] = Field(min_length=1, description="The modules.")
|
|
121
|
+
assumptions: tuple[str, ...] = Field(
|
|
122
|
+
default=(), description="Assumptions the plan rests on (F4)."
|
|
123
|
+
)
|
|
124
|
+
open_questions: tuple[str, ...] = Field(
|
|
125
|
+
default=(), description="Questions the approver must read and answer."
|
|
126
|
+
)
|
|
127
|
+
unassigned_reqs: tuple[UnassignedReq, ...] = Field(
|
|
128
|
+
default=(), description="Requirements of the block no module covers, with the reason."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def module(self, name: str) -> PlanModule | None:
|
|
132
|
+
"""The module called `name`, if the plan has one."""
|
|
133
|
+
return next((m for m in self.modules if m.name == name), None)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def schema_json() -> str:
|
|
137
|
+
"""The JSON Schema of a plan file, as committed under `schemas/formats/`."""
|
|
138
|
+
return json.dumps(Plan.model_json_schema(), indent=2, sort_keys=True) + "\n"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = [
|
|
142
|
+
"ModuleBudget",
|
|
143
|
+
"Plan",
|
|
144
|
+
"PlanInterface",
|
|
145
|
+
"PlanModule",
|
|
146
|
+
"PlanPort",
|
|
147
|
+
"PlanWrite",
|
|
148
|
+
"UnassignedReq",
|
|
149
|
+
"schema_json",
|
|
150
|
+
]
|