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.
Files changed (252) hide show
  1. chipgraph/__init__.py +7 -0
  2. chipgraph/adapters/__init__.py +1 -0
  3. chipgraph/adapters/format/__init__.py +6 -0
  4. chipgraph/adapters/format/__main__.py +16 -0
  5. chipgraph/adapters/format/_yaml_lines.py +54 -0
  6. chipgraph/adapters/format/chip_yaml.py +323 -0
  7. chipgraph/adapters/format/qsoc_contract.py +246 -0
  8. chipgraph/adapters/llm/__init__.py +58 -0
  9. chipgraph/adapters/llm/_budget.py +173 -0
  10. chipgraph/adapters/llm/_errors.py +50 -0
  11. chipgraph/adapters/llm/_nda.py +30 -0
  12. chipgraph/adapters/llm/_retry.py +85 -0
  13. chipgraph/adapters/llm/anthropic.py +247 -0
  14. chipgraph/adapters/llm/decide_backend.py +179 -0
  15. chipgraph/adapters/llm/fake.py +87 -0
  16. chipgraph/adapters/llm/guard.py +68 -0
  17. chipgraph/adapters/parser/__init__.py +22 -0
  18. chipgraph/adapters/parser/cocotb.py +304 -0
  19. chipgraph/adapters/parser/generic_regex.py +75 -0
  20. chipgraph/adapters/parser/verible.py +55 -0
  21. chipgraph/adapters/parser/verilator.py +46 -0
  22. chipgraph/adapters/review/__init__.py +7 -0
  23. chipgraph/adapters/review/file.py +123 -0
  24. chipgraph/adapters/review/github.py +886 -0
  25. chipgraph/adapters/runner/__init__.py +7 -0
  26. chipgraph/adapters/runner/local.py +122 -0
  27. chipgraph/adapters/runtime/__init__.py +5 -0
  28. chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
  29. chipgraph/adapters/runtime/claude_code/agents.py +158 -0
  30. chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
  31. chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
  32. chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
  33. chipgraph/adapters/runtime/claude_code/loop.py +342 -0
  34. chipgraph/adapters/runtime/claude_code/previous.py +419 -0
  35. chipgraph/adapters/runtime/claude_code/review.py +492 -0
  36. chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
  37. chipgraph/adapters/runtime/claude_code/service.py +2285 -0
  38. chipgraph/adapters/tool/__init__.py +7 -0
  39. chipgraph/adapters/tool/cmd.py +229 -0
  40. chipgraph/adapters/tool/edalize.py +773 -0
  41. chipgraph/adapters/tool/filelist.py +259 -0
  42. chipgraph/adapters/tool/pyslang.py +485 -0
  43. chipgraph/adapters/vcs/__init__.py +1 -0
  44. chipgraph/adapters/vcs/git.py +521 -0
  45. chipgraph/app/__init__.py +10 -0
  46. chipgraph/app/baseline.py +339 -0
  47. chipgraph/app/build.py +332 -0
  48. chipgraph/app/checks.py +202 -0
  49. chipgraph/app/context.py +191 -0
  50. chipgraph/app/errors.py +11 -0
  51. chipgraph/app/executors.py +79 -0
  52. chipgraph/app/findings.py +203 -0
  53. chipgraph/app/ingest.py +523 -0
  54. chipgraph/checks/__init__.py +52 -0
  55. chipgraph/checks/_cdc_analyze.py +272 -0
  56. chipgraph/checks/_cdc_elaborate.py +455 -0
  57. chipgraph/checks/_common.py +268 -0
  58. chipgraph/checks/_cross.py +96 -0
  59. chipgraph/checks/_model.py +64 -0
  60. chipgraph/checks/_naming_pyslang.py +235 -0
  61. chipgraph/checks/_naming_rules.py +127 -0
  62. chipgraph/checks/_xref_syntax.py +260 -0
  63. chipgraph/checks/cdc_struct.py +235 -0
  64. chipgraph/checks/connect.py +353 -0
  65. chipgraph/checks/cross_chip.py +454 -0
  66. chipgraph/checks/duplicate.py +153 -0
  67. chipgraph/checks/filelist.py +323 -0
  68. chipgraph/checks/format.py +268 -0
  69. chipgraph/checks/generated.py +151 -0
  70. chipgraph/checks/hardcode.py +233 -0
  71. chipgraph/checks/layout.py +155 -0
  72. chipgraph/checks/naming.py +311 -0
  73. chipgraph/checks/plan_check.py +901 -0
  74. chipgraph/checks/ports_diff.py +203 -0
  75. chipgraph/checks/spec_schema.py +277 -0
  76. chipgraph/checks/style.py +910 -0
  77. chipgraph/checks/tb_static.py +599 -0
  78. chipgraph/checks/trace.py +224 -0
  79. chipgraph/checks/vendor_guard.py +369 -0
  80. chipgraph/checks/wrap_check.py +486 -0
  81. chipgraph/cli/__init__.py +1688 -0
  82. chipgraph/cli/next_steps.py +137 -0
  83. chipgraph/core/__init__.py +1 -0
  84. chipgraph/core/config/__init__.py +93 -0
  85. chipgraph/core/config/defaults.py +14 -0
  86. chipgraph/core/config/errors.py +32 -0
  87. chipgraph/core/config/loader.py +766 -0
  88. chipgraph/core/config/models.py +845 -0
  89. chipgraph/core/config/templates.py +340 -0
  90. chipgraph/core/contracts/__init__.py +75 -0
  91. chipgraph/core/contracts/_paths.py +26 -0
  92. chipgraph/core/contracts/agent.py +49 -0
  93. chipgraph/core/contracts/artifact.py +68 -0
  94. chipgraph/core/contracts/check.py +76 -0
  95. chipgraph/core/contracts/event.py +70 -0
  96. chipgraph/core/contracts/export.py +128 -0
  97. chipgraph/core/contracts/finding.py +171 -0
  98. chipgraph/core/contracts/gate.py +50 -0
  99. chipgraph/core/contracts/rule.py +177 -0
  100. chipgraph/core/contracts/types.py +45 -0
  101. chipgraph/core/engine/__init__.py +70 -0
  102. chipgraph/core/engine/agent_rule.py +1362 -0
  103. chipgraph/core/engine/baseline.py +283 -0
  104. chipgraph/core/engine/decide.py +462 -0
  105. chipgraph/core/engine/fanout.py +1358 -0
  106. chipgraph/core/engine/gate.py +170 -0
  107. chipgraph/core/engine/graph.py +647 -0
  108. chipgraph/core/engine/records.py +60 -0
  109. chipgraph/core/engine/rules.py +92 -0
  110. chipgraph/core/engine/scheduler.py +637 -0
  111. chipgraph/core/engine/select.py +1239 -0
  112. chipgraph/core/engine/targets.py +342 -0
  113. chipgraph/core/model/__init__.py +98 -0
  114. chipgraph/core/model/entities.py +244 -0
  115. chipgraph/core/model/export.py +16 -0
  116. chipgraph/core/model/ingest.py +693 -0
  117. chipgraph/core/model/json_value.py +4 -0
  118. chipgraph/core/model/keys.py +95 -0
  119. chipgraph/core/model/model.py +153 -0
  120. chipgraph/core/model/provenance.py +27 -0
  121. chipgraph/core/model/query.py +583 -0
  122. chipgraph/core/model/registry.py +80 -0
  123. chipgraph/core/model/relations.py +61 -0
  124. chipgraph/core/model/store.py +279 -0
  125. chipgraph/core/plugin_api/__init__.py +81 -0
  126. chipgraph/core/plugin_api/local.py +167 -0
  127. chipgraph/core/plugin_api/pack.py +159 -0
  128. chipgraph/core/plugin_api/protocols.py +177 -0
  129. chipgraph/core/plugin_api/registry.py +111 -0
  130. chipgraph/core/plugin_api/types.py +94 -0
  131. chipgraph/core/runtime/__init__.py +41 -0
  132. chipgraph/core/runtime/executor.py +79 -0
  133. chipgraph/core/runtime/queue.py +565 -0
  134. chipgraph/core/runtime/roles/__init__.py +91 -0
  135. chipgraph/core/runtime/roles/data/author.md +41 -0
  136. chipgraph/core/runtime/roles/data/critic.md +56 -0
  137. chipgraph/core/runtime/roles/data/planner.md +38 -0
  138. chipgraph/core/runtime/roles/data/researcher.md +37 -0
  139. chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
  140. chipgraph/core/runtime/roles/data/tb-author.md +48 -0
  141. chipgraph/core/runtime/roles/data/triage.md +34 -0
  142. chipgraph/core/runtime/roles/policy.py +299 -0
  143. chipgraph/core/runtime/roles/registry.py +52 -0
  144. chipgraph/core/runtime/roles/review.py +275 -0
  145. chipgraph/core/runtime/roles/schema.py +82 -0
  146. chipgraph/core/runtime/roles/skills.py +134 -0
  147. chipgraph/core/runtime/roles/spec.py +232 -0
  148. chipgraph/core/state/__init__.py +1 -0
  149. chipgraph/core/state/artifacts.py +156 -0
  150. chipgraph/core/state/backend.py +117 -0
  151. chipgraph/core/state/findings.py +151 -0
  152. chipgraph/core/state/handoff.py +411 -0
  153. chipgraph/core/state/idempotency.py +59 -0
  154. chipgraph/core/state/journal.py +238 -0
  155. chipgraph/core/state/layout.py +82 -0
  156. chipgraph/core/state/lock.py +195 -0
  157. chipgraph/core/state/trace.py +225 -0
  158. chipgraph/learn/__init__.py +96 -0
  159. chipgraph/learn/draft.py +187 -0
  160. chipgraph/learn/infer.py +580 -0
  161. chipgraph/learn/models.py +145 -0
  162. chipgraph/learn/try_run.py +228 -0
  163. chipgraph/mcp/__init__.py +12 -0
  164. chipgraph/mcp/model_tools.py +167 -0
  165. chipgraph/mcp/server.py +787 -0
  166. chipgraph/orgs/qnsc/naming-v1.yml +113 -0
  167. chipgraph/orgs/qnsc/profile.yml +5 -0
  168. chipgraph/packs/__init__.py +8 -0
  169. chipgraph/packs/assist/__init__.py +16 -0
  170. chipgraph/packs/assist/ask/__init__.py +58 -0
  171. chipgraph/packs/assist/ask/_db.py +57 -0
  172. chipgraph/packs/assist/ask/_project.py +230 -0
  173. chipgraph/packs/assist/ask/answer.py +266 -0
  174. chipgraph/packs/assist/ask/check.py +182 -0
  175. chipgraph/packs/assist/ask/contract.py +115 -0
  176. chipgraph/packs/assist/ask/documents.py +157 -0
  177. chipgraph/packs/assist/ask/fts.py +80 -0
  178. chipgraph/packs/assist/ask/retrieve.py +331 -0
  179. chipgraph/packs/assist/audit/__init__.py +383 -0
  180. chipgraph/packs/assist/pack.yml +13 -0
  181. chipgraph/packs/assist/triage/__init__.py +64 -0
  182. chipgraph/packs/assist/triage/contract.py +94 -0
  183. chipgraph/packs/assist/triage/parse.py +371 -0
  184. chipgraph/packs/assist/triage/question.py +384 -0
  185. chipgraph/packs/assist/triage/report.py +160 -0
  186. chipgraph/packs/assist/triage/rules.py +447 -0
  187. chipgraph/packs/assist/triage/run.py +224 -0
  188. chipgraph/packs/digital_rtl/__init__.py +19 -0
  189. chipgraph/packs/digital_rtl/layout.py +144 -0
  190. chipgraph/packs/digital_rtl/pack.yml +25 -0
  191. chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
  192. chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
  193. chipgraph/packs/digital_rtl/plan/context.py +82 -0
  194. chipgraph/packs/digital_rtl/plan/expand.py +94 -0
  195. chipgraph/packs/digital_rtl/plan/model.py +150 -0
  196. chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
  197. chipgraph/packs/digital_rtl/plan/show.py +176 -0
  198. chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
  199. chipgraph/packs/digital_rtl/pr/body.py +165 -0
  200. chipgraph/packs/digital_rtl/pr/gate.py +110 -0
  201. chipgraph/packs/digital_rtl/pr/open.py +397 -0
  202. chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
  203. chipgraph/packs/digital_rtl/rtl/context.py +272 -0
  204. chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
  205. chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
  206. chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
  207. chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
  208. chipgraph/packs/digital_rtl/rules/review.yml +31 -0
  209. chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
  210. chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
  211. chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
  212. chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
  213. chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
  214. chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
  215. chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
  216. chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
  217. chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
  218. chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
  219. chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
  220. chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
  221. chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
  222. chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
  223. chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
  224. chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
  225. chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
  226. chipgraph/packs/dv/__init__.py +15 -0
  227. chipgraph/packs/dv/declaration.py +392 -0
  228. chipgraph/packs/dv/interface.py +596 -0
  229. chipgraph/packs/dv/pack.yml +14 -0
  230. chipgraph/packs/dv/rules/tb_module.yml +42 -0
  231. chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
  232. chipgraph/packs/spec_core/__init__.py +9 -0
  233. chipgraph/packs/spec_core/extract/__init__.py +12 -0
  234. chipgraph/packs/spec_core/extract/_markdown.py +371 -0
  235. chipgraph/packs/spec_core/extract/_text.py +116 -0
  236. chipgraph/packs/spec_core/extract/mas.py +975 -0
  237. chipgraph/packs/spec_core/gen/__init__.py +11 -0
  238. chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
  239. chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
  240. chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
  241. chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
  242. chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
  243. chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
  244. chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
  245. chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
  246. chipgraph/packs/spec_core/pack.yml +6 -0
  247. chipgraph/py.typed +0 -0
  248. chipgraph-0.1.0.dist-info/METADATA +53 -0
  249. chipgraph-0.1.0.dist-info/RECORD +252 -0
  250. chipgraph-0.1.0.dist-info/WHEEL +4 -0
  251. chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
  252. 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"]