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,11 @@
|
|
|
1
|
+
# `chipgraph build plan:<block>` (and `chipgraph plan <block>`): a block's plan, up to and
|
|
2
|
+
# including its gate (task M2-11a): the planner writes it, plan_check checks it, and the
|
|
3
|
+
# build waits on the gate `plan:<block>` until a person approves it.
|
|
4
|
+
schema_version: 1
|
|
5
|
+
target: plan
|
|
6
|
+
description: A block's plan, up to and including the gate plan:<block>.
|
|
7
|
+
params:
|
|
8
|
+
- name: block
|
|
9
|
+
description: A block of the profile.
|
|
10
|
+
select:
|
|
11
|
+
- select: "digital-rtl/plan_expand[block={block}]"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# `chipgraph build rtl:<block>` (and `chipgraph rtl <block>`, MCP next_task(target=...)):
|
|
2
|
+
# the RTL of one block, from its plan to a pull request (task M2-11a).
|
|
3
|
+
#
|
|
4
|
+
# digital-rtl/plan -> plan_expand (gate plan:<block>) -> rtl_module, one per planned
|
|
5
|
+
# module (in dependency order) -> with dv/tb_module (independent of the RTL) ->
|
|
6
|
+
# digital-rtl/review -> open_pr -> pr_gate (gate pr:<block>)
|
|
7
|
+
#
|
|
8
|
+
# Before the plan is approved only the first selector matches: the build stops at the
|
|
9
|
+
# plan gate and nothing after the plan runs. Once it is approved, `pr_gate` exists and
|
|
10
|
+
# brings in everything it depends on (the modules, the testbench, the review, open_pr).
|
|
11
|
+
schema_version: 1
|
|
12
|
+
target: rtl
|
|
13
|
+
description: The RTL of one block, from its plan to a pull request and its gate.
|
|
14
|
+
params:
|
|
15
|
+
- name: block
|
|
16
|
+
description: A block of the profile.
|
|
17
|
+
select:
|
|
18
|
+
- select: "digital-rtl/plan_expand[block={block}]"
|
|
19
|
+
- select: "digital-rtl/pr_gate[block={block}]"
|
|
20
|
+
optional: true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Wrappers around third-party (vendor) IP, in the emacs verilog-mode AUTO style (M2-12).
|
|
2
|
+
|
|
3
|
+
A wrapper instantiates an IP module it never edits and maps the IP's ports to the
|
|
4
|
+
project's names. The RTL Author writes only the wrapper's *source*,
|
|
5
|
+
`<wrapper>.src.sv`: its port groups (`/*AUTOINPUT("regexp")*/`, ...), an `AUTO_TEMPLATE`
|
|
6
|
+
that maps the IP's ports (regexps, tie-offs, `unused_*` sinks), the instance with its
|
|
7
|
+
parameters and `/*AUTOINST*/`, and nothing expanded by hand. A generator runs emacs on
|
|
8
|
+
it and writes the expanded `<wrapper>.sv`; a check regenerates and compares.
|
|
9
|
+
|
|
10
|
+
- `common`: default paths, the profile's `blocks.<b>.wrappers`, path templates;
|
|
11
|
+
- `lisp`: the Emacs Lisp a source may carry (`AUTO_LISP`, `@"(...)"` in a template),
|
|
12
|
+
allowed only from a small list of side-effect-free forms: emacs runs it;
|
|
13
|
+
- `emacs`: the gen tool `emacs_auto` (rule `digital-rtl/wrapper_gen`): `emacs -Q --batch`
|
|
14
|
+
on a scratch copy, the verilog-mode variables pinned, through the `Runner`;
|
|
15
|
+
- `expansion`: the expanded instance read back (its connections, `// Templated` or not)
|
|
16
|
+
and checked against the IP's port declaration (pyslang, header only);
|
|
17
|
+
- `resolver`: the `foreach` selectors `wrappers` and `wrappers.vendor`;
|
|
18
|
+
- `context`: the `rtl.wrapper` section of the author's context.
|
|
19
|
+
|
|
20
|
+
The rules are `rules/wrapper.yml` (agent, `rtl-author`) and `rules/wrapper_gen.yml`
|
|
21
|
+
(gen); the skill is `skills/wrapper-auto.md` (`rtl/wrapper-auto`); the check is
|
|
22
|
+
`chipgraph.checks.wrap_check`, and `chipgraph.checks.vendor_guard` keeps the IP files
|
|
23
|
+
as recorded.
|
|
24
|
+
"""
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Shared by the wrapper rules, tool, check and context: paths and the profile entries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from chipgraph.core.config.models import Profile, WrapperCfg
|
|
12
|
+
|
|
13
|
+
DEFAULT_SRC = "rtl/emacs/{wrapper}.src.sv"
|
|
14
|
+
"""Where a wrapper's source is, unless a rule or check names another template."""
|
|
15
|
+
DEFAULT_OUT = "rtl/{wrapper}.sv"
|
|
16
|
+
"""Where the generated wrapper is, unless a rule or check names another template."""
|
|
17
|
+
RULE = "digital-rtl/wrapper"
|
|
18
|
+
"""The author's rule: writes the source."""
|
|
19
|
+
GEN_RULE = "digital-rtl/wrapper_gen"
|
|
20
|
+
"""The generator's rule: writes the expanded wrapper."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WrapperError(Exception):
|
|
24
|
+
"""A wrapper that cannot be named: no such entry, a bad path template."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class Wrapper:
|
|
29
|
+
"""One `blocks.<block>.wrappers` entry, with the block it belongs to."""
|
|
30
|
+
|
|
31
|
+
block: str
|
|
32
|
+
cfg: WrapperCfg
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def name(self) -> str:
|
|
36
|
+
return self.cfg.name
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def params(self) -> dict[str, str]:
|
|
40
|
+
"""The params of its rule instances."""
|
|
41
|
+
return {"block": self.block, "wrapper": self.cfg.name}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def all_wrappers(profile: Profile, block: str | None = None) -> list[Wrapper]:
|
|
45
|
+
"""Every wrapper of the profile (of `block` only, when given), blocks sorted."""
|
|
46
|
+
blocks = [block] if block is not None else sorted(profile.blocks)
|
|
47
|
+
out: list[Wrapper] = []
|
|
48
|
+
for name in blocks:
|
|
49
|
+
override = profile.blocks.get(name)
|
|
50
|
+
if override is not None:
|
|
51
|
+
out.extend(Wrapper(block=name, cfg=cfg) for cfg in override.wrappers)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def find_wrapper(profile: Profile, params: Mapping[str, str]) -> Wrapper:
|
|
56
|
+
"""The wrapper a task's params (`block`, `wrapper`) name; `WrapperError` if none."""
|
|
57
|
+
name = params.get("wrapper")
|
|
58
|
+
block = params.get("block")
|
|
59
|
+
if not name:
|
|
60
|
+
raise WrapperError("the task has no `wrapper` param")
|
|
61
|
+
for wrapper in all_wrappers(profile, block):
|
|
62
|
+
if wrapper.name == name:
|
|
63
|
+
return wrapper
|
|
64
|
+
where = f"blocks.{block}.wrappers" if block else "any block's wrappers"
|
|
65
|
+
raise WrapperError(f"no wrapper {name!r} in the profile's {where}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def fill(template: str, params: Mapping[str, str]) -> str:
|
|
69
|
+
"""`template` with `{key}` / `{KEY}` from `params`; `WrapperError` if one is left."""
|
|
70
|
+
out = template
|
|
71
|
+
for key, value in params.items():
|
|
72
|
+
out = out.replace("{" + key + "}", value).replace("{" + key.upper() + "}", value.upper())
|
|
73
|
+
if "{" in out or "}" in out:
|
|
74
|
+
raise WrapperError(f"path template {template!r} has a placeholder no param fills")
|
|
75
|
+
return repo_rel(out)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def repo_rel(path: str) -> str:
|
|
79
|
+
"""`path` as a normalised repo-relative POSIX path; `WrapperError` if it is not one."""
|
|
80
|
+
text = path.replace("\\", "/").strip()
|
|
81
|
+
parts = [p for p in text.split("/") if p not in ("", ".")]
|
|
82
|
+
if not parts or text.startswith("/") or ".." in parts or (len(text) > 1 and text[1] == ":"):
|
|
83
|
+
raise WrapperError(f"{path!r} is not a repo-relative path")
|
|
84
|
+
return "/".join(parts)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def inside(root: Path, rel: str) -> Path:
|
|
88
|
+
"""The file `rel` of the repo at `root`, refused when it resolves outside it."""
|
|
89
|
+
path = root / repo_rel(rel)
|
|
90
|
+
real = Path(os.path.realpath(path))
|
|
91
|
+
if not real.is_relative_to(Path(os.path.realpath(root))):
|
|
92
|
+
raise WrapperError(f"{rel} resolves outside the repository")
|
|
93
|
+
return path
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
MAX_TIMEOUT_S = 600.0
|
|
97
|
+
"""The longest `timeout_s` a tool or check accepts."""
|
|
98
|
+
_PROTECTED_DIRS = (".git", ".chipgraph")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def timeout_arg(value: object, default: float) -> float:
|
|
102
|
+
"""`timeout_s`: `default` when None, else a finite number, `0 < t <= MAX_TIMEOUT_S`
|
|
103
|
+
(never a bool or a string); `WrapperError` otherwise."""
|
|
104
|
+
if value is None:
|
|
105
|
+
value = default
|
|
106
|
+
if (
|
|
107
|
+
isinstance(value, bool)
|
|
108
|
+
or not isinstance(value, int | float)
|
|
109
|
+
or not math.isfinite(value)
|
|
110
|
+
or not 0 < value <= MAX_TIMEOUT_S
|
|
111
|
+
):
|
|
112
|
+
raise WrapperError(
|
|
113
|
+
f"args['timeout_s'] must be a number of seconds, 0 < timeout_s <= {MAX_TIMEOUT_S:g}"
|
|
114
|
+
)
|
|
115
|
+
return float(value)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def emacs_arg(value: str) -> str:
|
|
119
|
+
"""`emacs`: a bare name (looked up on PATH) or an absolute path to an executable file;
|
|
120
|
+
`WrapperError` otherwise (a relative path would run a file the repository holds)."""
|
|
121
|
+
if "/" in value or "\\" in value or os.sep in value:
|
|
122
|
+
path = Path(value)
|
|
123
|
+
if not path.is_absolute() or not path.is_file() or not os.access(path, os.X_OK):
|
|
124
|
+
raise WrapperError(
|
|
125
|
+
"args['emacs'] must be a command name or an absolute path to an executable"
|
|
126
|
+
)
|
|
127
|
+
return value
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def guarded(root: Path, rel: str, key: str = "path") -> Path:
|
|
131
|
+
"""The repo file `rel` (`inside`: refused outside the repository), refused too when any
|
|
132
|
+
part is `.git` (in any case) or it is under `.chipgraph/`, as written or as resolved."""
|
|
133
|
+
path = inside(root, rel)
|
|
134
|
+
real = Path(os.path.realpath(path)).relative_to(Path(os.path.realpath(root)))
|
|
135
|
+
for parts in (Path(repo_rel(rel)).parts, real.parts):
|
|
136
|
+
folded = [p.casefold() for p in parts]
|
|
137
|
+
if ".git" in folded or (folded and folded[0] in _PROTECTED_DIRS):
|
|
138
|
+
raise WrapperError(f"args[{key!r}] may not name a file under .git/ or .chipgraph/")
|
|
139
|
+
return path
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def paths(wrapper: Wrapper, src: str | None = None, out: str | None = None) -> tuple[str, str]:
|
|
143
|
+
"""The wrapper's source and generated paths, from templates (default: the rules')."""
|
|
144
|
+
params = wrapper.params
|
|
145
|
+
return fill(src or DEFAULT_SRC, params), fill(out or DEFAULT_OUT, params)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def instance_id(rule: str, wrapper: Wrapper) -> str:
|
|
149
|
+
"""The id of `rule`'s instance for `wrapper` (as `RuleInstance.make_id` builds it)."""
|
|
150
|
+
return f"{rule}[block={wrapper.block},wrapper={wrapper.name}]"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
__all__ = [
|
|
154
|
+
"DEFAULT_OUT",
|
|
155
|
+
"DEFAULT_SRC",
|
|
156
|
+
"GEN_RULE",
|
|
157
|
+
"MAX_TIMEOUT_S",
|
|
158
|
+
"RULE",
|
|
159
|
+
"Wrapper",
|
|
160
|
+
"WrapperError",
|
|
161
|
+
"all_wrappers",
|
|
162
|
+
"emacs_arg",
|
|
163
|
+
"fill",
|
|
164
|
+
"find_wrapper",
|
|
165
|
+
"guarded",
|
|
166
|
+
"inside",
|
|
167
|
+
"instance_id",
|
|
168
|
+
"paths",
|
|
169
|
+
"repo_rel",
|
|
170
|
+
"timeout_arg",
|
|
171
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""What an `rtl-author` task of rule `digital-rtl/wrapper` gets: the `rtl.wrapper` section.
|
|
2
|
+
|
|
3
|
+
The author writes a wrapper's source, never its expansion, and cannot run emacs. Its
|
|
4
|
+
context holds what the source is written from, and no IP body:
|
|
5
|
+
|
|
6
|
+
- `wrapper`: the profile's entry (name, block, IP module, IP files), the source it writes
|
|
7
|
+
and the file the generator writes from it;
|
|
8
|
+
- `vendor`: the IP module's declaration only, read here with pyslang from the IP files:
|
|
9
|
+
its parameters and its ports (name, direction, type, packed and unpacked dimensions),
|
|
10
|
+
never a line of its body, a comment or a line number;
|
|
11
|
+
- `interface`: the wrapper's own ports when the project knows them: the Design Model's
|
|
12
|
+
(the block's spec ports when the wrapper is the block's top module, else the model's
|
|
13
|
+
RTL ports of the module), else the block's approved plan's entry of that name; else
|
|
14
|
+
none, and the author names them from the IP ports and the naming rules;
|
|
15
|
+
- `naming` (next to `wrapper`, as for an RTL module): the project's naming rules as text;
|
|
16
|
+
- `exemplar`: the profile's wrapper exemplar, when it lists one (its text is an input
|
|
17
|
+
of the task);
|
|
18
|
+
- `generator`: how the source is expanded (the variables the generator pins, the Lisp
|
|
19
|
+
the source may carry), so the author knows what emacs will do with its template.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import Callable, Sequence
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
import yaml
|
|
29
|
+
|
|
30
|
+
from chipgraph.core.config.loader import ResolvedProfile
|
|
31
|
+
from chipgraph.core.contracts import RuleSpec
|
|
32
|
+
from chipgraph.core.model import DesignModel
|
|
33
|
+
from chipgraph.packs.digital_rtl.plan.model import Plan
|
|
34
|
+
from chipgraph.packs.digital_rtl.rtl.context import _naming, approved_plan_path
|
|
35
|
+
from chipgraph.packs.digital_rtl.wrapper import lisp
|
|
36
|
+
from chipgraph.packs.digital_rtl.wrapper.common import (
|
|
37
|
+
GEN_RULE,
|
|
38
|
+
Wrapper,
|
|
39
|
+
WrapperError,
|
|
40
|
+
find_wrapper,
|
|
41
|
+
inside,
|
|
42
|
+
paths,
|
|
43
|
+
)
|
|
44
|
+
from chipgraph.packs.digital_rtl.wrapper.emacs import PINNED
|
|
45
|
+
from chipgraph.packs.dv.declaration import ModuleDeclaration, extract_declaration
|
|
46
|
+
from chipgraph.packs.dv.interface import _rtl_ports, interface_for, top_module
|
|
47
|
+
|
|
48
|
+
INSTRUCTIONS = (
|
|
49
|
+
" Everything about the wrapper is in `rtl.wrapper`: the IP module's port declaration "
|
|
50
|
+
"(`vendor`), the wrapper's ports when the project knows them (`interface`), the naming "
|
|
51
|
+
"rules (`rtl.naming.text`) and how the generator expands your source (`generator`). Write "
|
|
52
|
+
"the source only (the template and the structure, never the expanded port list): on "
|
|
53
|
+
"submit the engine expands it with emacs and `wrap_check` reports what the expansion "
|
|
54
|
+
"got wrong, with the expanded lines."
|
|
55
|
+
)
|
|
56
|
+
"""What `get_context` adds to a wrapper task's instructions."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_SHOWN = frozenset(
|
|
60
|
+
{
|
|
61
|
+
"verilog-auto-inst-param-value",
|
|
62
|
+
"verilog-auto-inst-vector",
|
|
63
|
+
"verilog-auto-inst-sort",
|
|
64
|
+
"verilog-auto-arg-sort",
|
|
65
|
+
"verilog-auto-star-expand",
|
|
66
|
+
"verilog-auto-input-ignore-regexp",
|
|
67
|
+
"verilog-auto-output-ignore-regexp",
|
|
68
|
+
"verilog-case-fold",
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
"""The pinned variables the author's template can depend on (the rest is layout)."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _vendor(root: Path, wrapper: Wrapper) -> tuple[dict[str, Any], ModuleDeclaration | None]:
|
|
75
|
+
files = []
|
|
76
|
+
for rel in wrapper.cfg.vendor_files:
|
|
77
|
+
try:
|
|
78
|
+
files.append(inside(root, rel))
|
|
79
|
+
except WrapperError:
|
|
80
|
+
continue
|
|
81
|
+
decl = extract_declaration(files, wrapper.cfg.vendor_module)
|
|
82
|
+
if decl is None:
|
|
83
|
+
return {
|
|
84
|
+
"module": wrapper.cfg.vendor_module,
|
|
85
|
+
"note": (
|
|
86
|
+
f"no declaration of `{wrapper.cfg.vendor_module}` was found in the IP files: "
|
|
87
|
+
"report needs_human"
|
|
88
|
+
),
|
|
89
|
+
}, None
|
|
90
|
+
view = decl.model_dump(mode="json", exclude={"schema_version"})
|
|
91
|
+
for item in (*view["parameters"], *view["ports"]):
|
|
92
|
+
item.pop("schema_version", None)
|
|
93
|
+
view["note"] = (
|
|
94
|
+
"the IP module's header only (parameters and ports), read by the engine: map every "
|
|
95
|
+
"port in the AUTO_TEMPLATE; never edit the IP's files"
|
|
96
|
+
)
|
|
97
|
+
return view, decl
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _plan_ports(root: Path, block: str, name: str) -> list[dict[str, Any]] | None:
|
|
101
|
+
rel = approved_plan_path(root, block)
|
|
102
|
+
if rel is None:
|
|
103
|
+
return None
|
|
104
|
+
try:
|
|
105
|
+
plan = Plan.model_validate(yaml.safe_load((root / rel).read_text(encoding="utf-8")))
|
|
106
|
+
except (OSError, ValueError, yaml.YAMLError):
|
|
107
|
+
return None
|
|
108
|
+
module = plan.module(name)
|
|
109
|
+
if module is None:
|
|
110
|
+
return None
|
|
111
|
+
return [
|
|
112
|
+
{"name": p.name, "direction": p.direction, "width": p.width, "source": "plan"}
|
|
113
|
+
for p in module.interface.ports
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _interface(root: Path, model: DesignModel | None, block: str, name: str) -> dict[str, Any]:
|
|
118
|
+
if model is not None:
|
|
119
|
+
if top_module(model, block) == name:
|
|
120
|
+
known = interface_for(name, model=model, block=block)
|
|
121
|
+
if known.source in ("spec", "model_rtl"):
|
|
122
|
+
return {
|
|
123
|
+
"source": known.source,
|
|
124
|
+
"ports": [
|
|
125
|
+
{"name": p.name, "direction": p.direction, "width": p.width}
|
|
126
|
+
for p in known.ports
|
|
127
|
+
],
|
|
128
|
+
"note": "the wrapper is the block's top module: these are its ports",
|
|
129
|
+
}
|
|
130
|
+
rtl = _rtl_ports(model, name)
|
|
131
|
+
if rtl:
|
|
132
|
+
return {
|
|
133
|
+
"source": "model_rtl",
|
|
134
|
+
"ports": [
|
|
135
|
+
{"name": p.name, "direction": p.direction, "width": p.width} for p in rtl
|
|
136
|
+
],
|
|
137
|
+
"note": "the Design Model's ports of the existing wrapper: keep them",
|
|
138
|
+
}
|
|
139
|
+
planned = _plan_ports(root, block, name)
|
|
140
|
+
if planned:
|
|
141
|
+
return {"source": "plan", "ports": planned, "note": "the approved plan's ports"}
|
|
142
|
+
return {
|
|
143
|
+
"source": None,
|
|
144
|
+
"ports": [],
|
|
145
|
+
"note": (
|
|
146
|
+
"the project names no ports for this wrapper yet: derive each one from an IP "
|
|
147
|
+
"port, renamed by the naming rules (the AUTO_TEMPLATE does the renaming)"
|
|
148
|
+
),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _out_template(rules: Sequence[RuleSpec]) -> str | None:
|
|
153
|
+
rule = next((r for r in rules if r.id == GEN_RULE), None)
|
|
154
|
+
if rule is None or rule.run is None:
|
|
155
|
+
return None
|
|
156
|
+
out = rule.run.args.get("out")
|
|
157
|
+
return out if isinstance(out, str) else None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def wrapper_author_context(
|
|
161
|
+
root: Path,
|
|
162
|
+
resolved: ResolvedProfile,
|
|
163
|
+
rules: Sequence[RuleSpec],
|
|
164
|
+
*,
|
|
165
|
+
params: dict[str, str],
|
|
166
|
+
outputs: Sequence[str],
|
|
167
|
+
model: DesignModel | None,
|
|
168
|
+
is_denied: Callable[[str], bool],
|
|
169
|
+
) -> dict[str, Any]:
|
|
170
|
+
"""The `rtl` section of a wrapper task's context (see the module docstring)."""
|
|
171
|
+
block = params.get("block")
|
|
172
|
+
out: dict[str, Any] = {"block": block, "wrapper_name": params.get("wrapper")}
|
|
173
|
+
try:
|
|
174
|
+
wrapper = find_wrapper(resolved.profile, params)
|
|
175
|
+
except WrapperError as exc:
|
|
176
|
+
out["note"] = f"{exc}: report needs_human"
|
|
177
|
+
return out
|
|
178
|
+
src_default, gen_default = paths(wrapper, None, _out_template(rules))
|
|
179
|
+
source = outputs[0] if outputs else src_default
|
|
180
|
+
vendor, _ = _vendor(root, wrapper)
|
|
181
|
+
entry: dict[str, Any] = {
|
|
182
|
+
"name": wrapper.name,
|
|
183
|
+
"block": wrapper.block,
|
|
184
|
+
"source": source,
|
|
185
|
+
"generated": gen_default,
|
|
186
|
+
"vendor_module": wrapper.cfg.vendor_module,
|
|
187
|
+
"vendor_files": list(wrapper.cfg.vendor_files),
|
|
188
|
+
"vendor": vendor,
|
|
189
|
+
"interface": _interface(root, model, wrapper.block, wrapper.name),
|
|
190
|
+
"generator": {
|
|
191
|
+
"tool": "emacs_auto",
|
|
192
|
+
"command": "emacs -Q --batch <source copy> -f verilog-batch-auto",
|
|
193
|
+
"pinned": {k: v for k, v in PINNED if k in _SHOWN},
|
|
194
|
+
"pinned_others": f"{len(PINNED) - len(_SHOWN)} more verilog-mode variables, "
|
|
195
|
+
"at verilog-mode's defaults or fixed for layout only",
|
|
196
|
+
"library": "the IP files above (verilog-library-files); nothing else is read",
|
|
197
|
+
"file_local_variables": "ignored: do not write a Local Variables block",
|
|
198
|
+
"lisp_functions": sorted(lisp.FUNCTIONS),
|
|
199
|
+
"lisp_variables": sorted(lisp.VARIABLES),
|
|
200
|
+
"auto_lisp_may_set": lisp.SETQ_VARIABLE.pattern,
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
exemplar = wrapper.cfg.exemplar
|
|
204
|
+
if exemplar is not None:
|
|
205
|
+
entry["exemplar"] = (
|
|
206
|
+
{"path": exemplar, "refused": "a file this role must not read: it is not shown"}
|
|
207
|
+
if is_denied(exemplar)
|
|
208
|
+
else {
|
|
209
|
+
"path": exemplar,
|
|
210
|
+
"label": (
|
|
211
|
+
"a wrapper of this project: imitate its structure (port groups, comments, "
|
|
212
|
+
"template layout), not its IP or its names"
|
|
213
|
+
),
|
|
214
|
+
"note": "its text is in this task's `inputs`",
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
out["wrapper"] = entry
|
|
218
|
+
out["naming"] = _naming(root, resolved, wrapper.block)
|
|
219
|
+
return out
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__all__ = ["INSTRUCTIONS", "wrapper_author_context"]
|