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,342 @@
1
+ """Named targets: a pack-provided set of graph selectors, e.g. `rtl:<block>` (M2-11a).
2
+
3
+ A target is what a person asks for in one word, `chipgraph build rtl:timer`, instead of
4
+ naming every rule. A pack ships its targets as YAML files in its `targets/` directory,
5
+ one per file::
6
+
7
+ schema_version: 1
8
+ target: rtl # the name: `rtl:<params>`
9
+ description: The RTL of one block, from its plan to a pull request.
10
+ params:
11
+ - { name: block, description: A block of the profile. }
12
+ select: # graph selectors, `{param}` filled in
13
+ - select: "pack/plan_expand[block={block}]"
14
+ - select: "pack/pr_gate[block={block}]"
15
+ optional: true # may match nothing (yet)
16
+
17
+ `BuildGraph.select("rtl:timer")` (with a `TargetRegistry`) is the union of what each
18
+ selector selects, each with its upstream instances, exactly as `select` of a rule id.
19
+ A required selector that matches nothing is an error (an unknown param value, a pack
20
+ that is off); an optional one is skipped. A selector is a rule selector only
21
+ (`pack/name[k=v]`), never another target.
22
+
23
+ Params are given in order after the colon, `rtl:timer`, or by name, `rtl:block=timer`,
24
+ comma-separated. This module knows no pack, rule or project: only names and templates.
25
+
26
+ A target file is data a pack ships, so it is read strictly: every selector is parsed when
27
+ the file loads (a malformed one is an error then, not a selector skipped forever), its
28
+ rule part is a literal rule id (no `:`, so never a target), `optional` is a real boolean,
29
+ param names are unique, a YAML key given twice is an error, and the file's size, nesting
30
+ depth and YAML aliases are capped. Errors are short and never quote the file's values.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import re
36
+ from collections.abc import Hashable, Iterable, Mapping
37
+ from pathlib import Path
38
+ from typing import Literal
39
+
40
+ import yaml
41
+ from pydantic import (
42
+ BaseModel,
43
+ ConfigDict,
44
+ Field,
45
+ StrictBool,
46
+ ValidationError,
47
+ field_validator,
48
+ model_validator,
49
+ )
50
+
51
+ from chipgraph.core.plugin_api.pack import Pack
52
+
53
+ TARGETS_DIR = "targets"
54
+ """The directory of a pack that holds its target files."""
55
+ MAX_FILE_BYTES = 64 * 1024
56
+ """The largest target file read."""
57
+ MAX_YAML_DEPTH = 32
58
+ """The deepest nesting of YAML collections in a target file."""
59
+ MAX_YAML_ALIASES = 16
60
+ """The most YAML aliases (`*name`) in a target file."""
61
+
62
+ _NAME_RE = r"^[a-z][a-z0-9_-]*$"
63
+ _VALUE_RE = re.compile(r"^[^\s\[\],=:]+$")
64
+ _PLACEHOLDER_RE = re.compile(r"\{([^{}]+)\}")
65
+ _RULE_ID_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*/[a-z0-9][a-z0-9_]*$")
66
+ """A rule id, as the contracts' `RuleId` (a selector's rule part)."""
67
+ _KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
68
+ _SELECTOR_VALUE_RE = re.compile(r"^[^\s\[\],=]+$")
69
+ _MAX_ERRORS = 5
70
+ _MAX_MSG = 200
71
+
72
+
73
+ class TargetError(ValueError):
74
+ """A target file is invalid, or a target is unknown or given the wrong params."""
75
+
76
+
77
+ class _Model(BaseModel):
78
+ model_config = ConfigDict(extra="forbid", frozen=True)
79
+
80
+
81
+ class TargetParam(_Model):
82
+ """One param of a target."""
83
+
84
+ name: str = Field(pattern=r"^[a-z][a-z0-9_]*$", description="The param name.")
85
+ description: str = Field(default="", description="What the param is.")
86
+
87
+
88
+ def check_selector(value: str) -> None:
89
+ """Raise `ValueError` unless `value` is a rule selector, `pack/name` or
90
+ `pack/name[k=v,...]` (values may hold `{param}` placeholders)."""
91
+ rule, bracket, rest = value.partition("[")
92
+ if not _RULE_ID_RE.match(rule):
93
+ raise ValueError(
94
+ "a target selector names a rule ('pack/name[...]', never a target), "
95
+ f"got {value[:_MAX_MSG]!r}"
96
+ )
97
+ if not bracket:
98
+ return
99
+ if not rest.endswith("]") or "[" in rest[:-1] or "]" in rest[:-1] or not rest[:-1]:
100
+ raise ValueError(f"malformed selector {value[:_MAX_MSG]!r}: expected '[key=value,...]'")
101
+ keys: set[str] = set()
102
+ for pair in rest[:-1].split(","):
103
+ key, sep, item = (p.strip() for p in pair.partition("="))
104
+ filled = _PLACEHOLDER_RE.sub("x", item)
105
+ if not sep or not _KEY_RE.match(key) or not _SELECTOR_VALUE_RE.match(filled):
106
+ raise ValueError(f"malformed selector {value[:_MAX_MSG]!r}: expected 'key=value'")
107
+ if "{" in filled or "}" in filled:
108
+ raise ValueError(f"malformed selector {value[:_MAX_MSG]!r}: a stray brace")
109
+ if key in keys:
110
+ raise ValueError(f"malformed selector {value[:_MAX_MSG]!r}: {key!r} given twice")
111
+ keys.add(key)
112
+
113
+
114
+ class TargetSelector(_Model):
115
+ """One graph selector of a target."""
116
+
117
+ select: str = Field(description="A rule selector, `pack/name[k={param}]`.")
118
+ optional: StrictBool = Field(
119
+ default=False, description="It may match no instance (it is skipped then)."
120
+ )
121
+
122
+ @field_validator("select")
123
+ @classmethod
124
+ def _rule_selector(cls, value: str) -> str:
125
+ check_selector(value)
126
+ return value
127
+
128
+
129
+ class TargetSpec(_Model):
130
+ """A named target: its params and selectors."""
131
+
132
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this file.")
133
+ name: str = Field(alias="target", pattern=_NAME_RE, description="The target's name.")
134
+ description: str = Field(default="", description="What building the target does.")
135
+ params: tuple[TargetParam, ...] = Field(default=(), description="The params, in order.")
136
+ select: tuple[TargetSelector, ...] = Field(min_length=1, description="The selectors.")
137
+ pack: str | None = Field(default=None, description="The pack that provides it.")
138
+
139
+ model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True)
140
+
141
+ @model_validator(mode="after")
142
+ def _unique_params(self) -> TargetSpec:
143
+ names = [p.name for p in self.params]
144
+ twice = sorted({n for n in names if names.count(n) > 1})
145
+ if twice:
146
+ raise ValueError(f"param(s) {', '.join(twice)} declared twice")
147
+ return self
148
+
149
+ def selectors(self, params: Mapping[str, str]) -> tuple[TargetSelector, ...]:
150
+ """The selectors with `params` filled in."""
151
+ return tuple(
152
+ item.model_copy(update={"select": _fill(item.select, params, self.name)})
153
+ for item in self.select
154
+ )
155
+
156
+
157
+ def _fill(template: str, params: Mapping[str, str], name: str) -> str:
158
+ def _replace(match: re.Match[str]) -> str:
159
+ key = match.group(1)
160
+ if key not in params:
161
+ raise TargetError(f"target {name!r}: selector {template!r} uses unknown {{{key}}}")
162
+ return params[key]
163
+
164
+ return _PLACEHOLDER_RE.sub(_replace, template)
165
+
166
+
167
+ class TargetRegistry:
168
+ """The targets of a project's packs, by name."""
169
+
170
+ def __init__(self, specs: Iterable[TargetSpec] = ()) -> None:
171
+ self._specs: dict[str, TargetSpec] = {}
172
+ for spec in specs:
173
+ self.add(spec)
174
+
175
+ def add(self, spec: TargetSpec) -> None:
176
+ """Register `spec`; a second target of the same name is an error."""
177
+ other = self._specs.get(spec.name)
178
+ if other is not None:
179
+ raise TargetError(
180
+ f"target {spec.name!r} is defined twice (packs {other.pack!r} and {spec.pack!r})"
181
+ )
182
+ for item in spec.select:
183
+ for key in _PLACEHOLDER_RE.findall(item.select):
184
+ if key not in {p.name for p in spec.params}:
185
+ raise TargetError(
186
+ f"target {spec.name!r}: selector {item.select!r} uses {{{key}}}, "
187
+ "which is not one of its params"
188
+ )
189
+ self._specs[spec.name] = spec
190
+
191
+ def names(self) -> tuple[str, ...]:
192
+ """Every target name, sorted."""
193
+ return tuple(sorted(self._specs))
194
+
195
+ def get(self, name: str) -> TargetSpec | None:
196
+ """The target called `name`, if any."""
197
+ return self._specs.get(name)
198
+
199
+ def is_target(self, text: str) -> bool:
200
+ """Whether `text` names a target: `<name>` or `<name>:<params>` of a known name."""
201
+ name = text.partition(":")[0]
202
+ return "/" not in name and "[" not in name and name in self._specs
203
+
204
+ def parse(self, text: str) -> tuple[TargetSpec, dict[str, str]]:
205
+ """The target `text` names and its params. Raises `TargetError`."""
206
+ name, _, rest = text.partition(":")
207
+ spec = self._specs.get(name)
208
+ if spec is None:
209
+ known = ", ".join(self.names()) or "none"
210
+ raise TargetError(f"unknown target {name!r}; known targets: {known}")
211
+ wanted = [p.name for p in spec.params]
212
+ values: dict[str, str] = {}
213
+ parts = [p.strip() for p in rest.split(",")] if rest else []
214
+ for index, part in enumerate(parts):
215
+ key, sep, value = part.partition("=")
216
+ if not sep:
217
+ if index >= len(wanted):
218
+ raise TargetError(f"target {name!r} takes {len(wanted)} param(s): {text!r}")
219
+ key, value = wanted[index], part
220
+ key, value = key.strip(), value.strip()
221
+ if key not in wanted:
222
+ raise TargetError(f"target {name!r} has no param {key!r} (params: {wanted})")
223
+ if key in values:
224
+ raise TargetError(f"target {name!r}: param {key!r} given twice in {text!r}")
225
+ if not _VALUE_RE.match(value):
226
+ raise TargetError(f"target {name!r}: invalid value {value!r} for {key!r}")
227
+ values[key] = value
228
+ missing = [k for k in wanted if k not in values]
229
+ if missing:
230
+ usage = f"{name}:" + ",".join(f"<{k}>" for k in wanted)
231
+ raise TargetError(f"target {name!r} needs {', '.join(missing)}: write {usage!r}")
232
+ return spec, values
233
+
234
+ def resolve(self, text: str) -> tuple[TargetSelector, ...]:
235
+ """The selectors of the target `text`, params filled in. Raises `TargetError`."""
236
+ spec, values = self.parse(text)
237
+ return spec.selectors(values)
238
+
239
+ def describe_params(self, text: str) -> str:
240
+ """`k='v', ...` of the target `text`'s params, for an error message."""
241
+ try:
242
+ _, values = self.parse(text)
243
+ except TargetError:
244
+ return text
245
+ return ", ".join(f"{k}={v!r}" for k, v in values.items())
246
+
247
+
248
+ class _StrictLoader(yaml.SafeLoader):
249
+ """`yaml.SafeLoader` that refuses duplicate keys, deep nesting and many aliases."""
250
+
251
+ def __init__(self, stream: str) -> None:
252
+ super().__init__(stream)
253
+ self._depth = 0
254
+ self._aliases = 0
255
+
256
+ def compose_node(self, parent: yaml.Node | None, index: object) -> yaml.Node | None:
257
+ if self.check_event(yaml.AliasEvent):
258
+ self._aliases += 1
259
+ if self._aliases > MAX_YAML_ALIASES:
260
+ raise yaml.YAMLError(f"more than {MAX_YAML_ALIASES} YAML aliases")
261
+ self._depth += 1
262
+ try:
263
+ if self._depth > MAX_YAML_DEPTH:
264
+ raise yaml.YAMLError(f"YAML nested deeper than {MAX_YAML_DEPTH} levels")
265
+ return super().compose_node(parent, index) # type: ignore[arg-type]
266
+ finally:
267
+ self._depth -= 1
268
+
269
+ def construct_mapping(
270
+ self, node: yaml.MappingNode, deep: bool = False
271
+ ) -> dict[Hashable, object]:
272
+ seen: set[object] = set()
273
+ for key_node, _ in node.value:
274
+ key = self.construct_object(key_node, deep=True)
275
+ try:
276
+ twice = key in seen
277
+ except TypeError:
278
+ continue # an unhashable key: SafeConstructor refuses it below
279
+ if twice:
280
+ mark = key_node.start_mark
281
+ raise yaml.YAMLError(f"line {mark.line + 1}: a key is given twice")
282
+ seen.add(key)
283
+ return super().construct_mapping(node, deep=deep)
284
+
285
+
286
+ def _short(exc: ValidationError) -> str:
287
+ """The validation errors as `where: what`, without the values (they may be huge)."""
288
+ errors = exc.errors(include_url=False, include_input=False, include_context=False)
289
+ parts = [
290
+ f"{'.'.join(str(p) for p in e['loc']) or '<root>'}: {e['msg'][:_MAX_MSG]}"
291
+ for e in errors[:_MAX_ERRORS]
292
+ ]
293
+ if len(errors) > _MAX_ERRORS:
294
+ parts.append(f"and {len(errors) - _MAX_ERRORS} more")
295
+ return "; ".join(parts)
296
+
297
+
298
+ def load_target_file(path: Path, *, pack: str | None = None) -> TargetSpec:
299
+ """Load one target file. Raises `TargetError` naming `path` (see the module)."""
300
+ try:
301
+ data = path.read_bytes()
302
+ if len(data) > MAX_FILE_BYTES:
303
+ raise TargetError(f"target file {path} is larger than {MAX_FILE_BYTES} bytes")
304
+ raw = yaml.load(data.decode("utf-8"), Loader=_StrictLoader)
305
+ except TargetError:
306
+ raise
307
+ except UnicodeDecodeError as exc:
308
+ raise TargetError(f"cannot read target file {path}: not UTF-8 (byte {exc.start})") from exc
309
+ except (OSError, yaml.YAMLError, RecursionError) as exc:
310
+ reason = str(exc)[:_MAX_MSG] if not isinstance(exc, RecursionError) else "nested too deep"
311
+ raise TargetError(f"cannot read target file {path}: {reason}") from exc
312
+ if not isinstance(raw, dict):
313
+ raise TargetError(f"target file {path} must contain a YAML mapping")
314
+ try:
315
+ spec = TargetSpec.model_validate({**raw, "pack": pack})
316
+ except ValidationError as exc:
317
+ raise TargetError(f"invalid target file {path}: {_short(exc)}") from exc
318
+ except RecursionError as exc:
319
+ raise TargetError(f"invalid target file {path}: nested too deep") from exc
320
+ return spec
321
+
322
+
323
+ def load_pack_targets(pack: Pack) -> list[TargetSpec]:
324
+ """Every target in `<pack>/targets/*.yml` (none when the directory is absent)."""
325
+ directory = pack.root / TARGETS_DIR
326
+ if not directory.is_dir():
327
+ return []
328
+ files = sorted({*directory.glob("*.yml"), *directory.glob("*.yaml")})
329
+ return [load_target_file(path, pack=pack.manifest.name) for path in files]
330
+
331
+
332
+ __all__ = [
333
+ "TARGETS_DIR",
334
+ "TargetError",
335
+ "TargetParam",
336
+ "TargetRegistry",
337
+ "TargetSelector",
338
+ "TargetSpec",
339
+ "check_selector",
340
+ "load_pack_targets",
341
+ "load_target_file",
342
+ ]
@@ -0,0 +1,98 @@
1
+ """The Design Model (DESIGN.md 4): a typed knowledge model of a chip design.
2
+
3
+ `chipgraph.core.model` is the schema and SQLite-backed store (M1-01). It knows nothing
4
+ about any specific chip project, bus or tool: entity kinds like `port` and `register`
5
+ are generic hardware concepts. The query API (`model.block(...)`, `model.find(...)`,
6
+ `model.trace(...)`) is built on top of this in M1-02.
7
+ """
8
+
9
+ from chipgraph.core.model.entities import (
10
+ CORE_ENTITY_CLASSES,
11
+ BlockEntity,
12
+ ClockEntity,
13
+ DecisionEntity,
14
+ EarsParts,
15
+ EntityBase,
16
+ ExtEntity,
17
+ FieldEntity,
18
+ InterfaceEntity,
19
+ InterruptEntity,
20
+ MemoryRegionEntity,
21
+ ModuleEntity,
22
+ OpenItemEntity,
23
+ ParameterEntity,
24
+ PortEntity,
25
+ ProjectEntity,
26
+ RegisterEntity,
27
+ RequirementEntity,
28
+ ResetEntity,
29
+ TestEntity,
30
+ )
31
+ from chipgraph.core.model.json_value import JSONValue
32
+ from chipgraph.core.model.keys import InvalidKeyError, key_kind, make_key, parse_key
33
+ from chipgraph.core.model.model import DesignModel, ModelConflict
34
+ from chipgraph.core.model.provenance import Provenance
35
+ from chipgraph.core.model.query import (
36
+ BlockInfo,
37
+ EntityMatch,
38
+ ImpactNode,
39
+ ImpactResult,
40
+ ModelQuery,
41
+ NeighborResult,
42
+ QueryError,
43
+ SearchResult,
44
+ TraceResult,
45
+ )
46
+ from chipgraph.core.model.registry import DEFAULT_ENTITY_KINDS, DuplicateKindError, EntityKinds
47
+ from chipgraph.core.model.relations import CORE_RELATION_KINDS, CoreRelationKind, Relation
48
+ from chipgraph.core.model.store import ModelStore, ModelStoreError, SearchHit, default_model_db_path
49
+
50
+ __all__ = [
51
+ "CORE_ENTITY_CLASSES",
52
+ "CORE_RELATION_KINDS",
53
+ "DEFAULT_ENTITY_KINDS",
54
+ "BlockEntity",
55
+ "BlockInfo",
56
+ "ClockEntity",
57
+ "CoreRelationKind",
58
+ "DecisionEntity",
59
+ "DesignModel",
60
+ "DuplicateKindError",
61
+ "EarsParts",
62
+ "EntityBase",
63
+ "EntityKinds",
64
+ "EntityMatch",
65
+ "ExtEntity",
66
+ "FieldEntity",
67
+ "ImpactNode",
68
+ "ImpactResult",
69
+ "InterfaceEntity",
70
+ "InterruptEntity",
71
+ "InvalidKeyError",
72
+ "JSONValue",
73
+ "MemoryRegionEntity",
74
+ "ModelConflict",
75
+ "ModelQuery",
76
+ "ModelStore",
77
+ "ModelStoreError",
78
+ "ModuleEntity",
79
+ "NeighborResult",
80
+ "OpenItemEntity",
81
+ "ParameterEntity",
82
+ "PortEntity",
83
+ "ProjectEntity",
84
+ "Provenance",
85
+ "QueryError",
86
+ "RegisterEntity",
87
+ "Relation",
88
+ "RequirementEntity",
89
+ "ResetEntity",
90
+ "SearchHit",
91
+ "SearchResult",
92
+ "TestEntity",
93
+ "TraceResult",
94
+ "default_model_db_path",
95
+ "key_kind",
96
+ "make_key",
97
+ "parse_key",
98
+ ]
@@ -0,0 +1,244 @@
1
+ """The core Design Model entities (DESIGN.md 4.2).
2
+
3
+ One frozen pydantic class per core kind, all sharing `EntityBase`. Every field beyond
4
+ the base is optional: extractors fill in what they know, and later tasks (M1-04..M1-07)
5
+ narrow things down over time. Anything an extractor cannot type yet goes in `attrs`.
6
+
7
+ Entity kinds are generic hardware concepts (port, register, clock, ...). Nothing here
8
+ names a specific chip project, bus protocol or tool.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import warnings
14
+ from typing import Literal
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field
17
+
18
+ from chipgraph.core.model.json_value import JSONValue
19
+ from chipgraph.core.model.provenance import Provenance
20
+
21
+
22
+ class EntityBase(BaseModel):
23
+ """Fields shared by every entity kind, core or pack-defined."""
24
+
25
+ model_config = ConfigDict(extra="forbid", frozen=True)
26
+
27
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
28
+ kind: str = Field(description="The entity kind; overridden per subclass with a Literal.")
29
+ key: str = Field(description="The stable model key, e.g. 'register:timer.CTRL'.")
30
+ name: str = Field(description="A short human-readable name for this entity.")
31
+ source: Provenance = Field(
32
+ default_factory=Provenance, description="Where this fact was learned from."
33
+ )
34
+ attrs: dict[str, JSONValue] = Field(
35
+ default_factory=dict, description="Open-ended extra facts not covered by typed fields."
36
+ )
37
+
38
+
39
+ class EarsParts(BaseModel):
40
+ """A requirement's text, broken into EARS-style structured parts (DESIGN.md 8/M3-08).
41
+
42
+ Optional: an extractor that only has the raw sentence leaves these unset.
43
+ """
44
+
45
+ model_config = ConfigDict(extra="forbid", frozen=True)
46
+
47
+ trigger: str | None = Field(default=None, description="The triggering event ('when ...').")
48
+ condition: str | None = Field(default=None, description="A qualifying state ('while ...').")
49
+ subject: str | None = Field(default=None, description="The system or object the REQ is about.")
50
+ action: str | None = Field(default=None, description="The required behavior ('shall ...').")
51
+
52
+
53
+ class ProjectEntity(EntityBase):
54
+ """The top-level project or chip this Design Model describes."""
55
+
56
+ kind: Literal["project"] = "project"
57
+
58
+
59
+ class BlockEntity(EntityBase):
60
+ """A design block (an IP or subsystem), e.g. 'timer'."""
61
+
62
+ kind: Literal["block"] = "block"
63
+ owner: str | None = Field(default=None, description="Who owns this block.")
64
+ path: str | None = Field(default=None, description="Repo-relative directory for this block.")
65
+
66
+
67
+ class ModuleEntity(EntityBase):
68
+ """A single RTL module."""
69
+
70
+ kind: Literal["module"] = "module"
71
+ file: str | None = Field(default=None, description="Repo-relative file this module is in.")
72
+ block: str | None = Field(default=None, description="The model key of the owning block.")
73
+
74
+
75
+ class PortEntity(EntityBase):
76
+ """A port on a module."""
77
+
78
+ kind: Literal["port"] = "port"
79
+ direction: Literal["input", "output", "inout"] | None = Field(
80
+ default=None, description="Port direction."
81
+ )
82
+ width: int | str | None = Field(
83
+ default=None, description="Port width: an int, or an unresolved expression string."
84
+ )
85
+ clock: str | None = Field(default=None, description="Model key of the clock this port is in.")
86
+ reset: str | None = Field(default=None, description="Model key of the reset this port uses.")
87
+ module: str | None = Field(default=None, description="Model key of the owning module.")
88
+
89
+
90
+ class InterfaceEntity(EntityBase):
91
+ """A bus/protocol interface (APB, AXI, TileLink, ...), described as data."""
92
+
93
+ kind: Literal["interface"] = "interface"
94
+ protocol: str | None = Field(default=None, description="The protocol this interface follows.")
95
+
96
+
97
+ class ClockEntity(EntityBase):
98
+ """A clock domain."""
99
+
100
+ kind: Literal["clock"] = "clock"
101
+ frequency_hz: float | None = Field(default=None, ge=0, description="Nominal frequency, in Hz.")
102
+
103
+
104
+ class ResetEntity(EntityBase):
105
+ """A reset signal."""
106
+
107
+ kind: Literal["reset"] = "reset"
108
+ active_low: bool | None = Field(default=None, description="Whether this reset is active-low.")
109
+ sync: bool | None = Field(default=None, description="Whether this reset is synchronous.")
110
+
111
+
112
+ class ParameterEntity(EntityBase):
113
+ """A module parameter."""
114
+
115
+ kind: Literal["parameter"] = "parameter"
116
+ value: str | int | float | bool | None = Field(
117
+ default=None, description="The parameter's value or default value."
118
+ )
119
+ module: str | None = Field(default=None, description="Model key of the owning module.")
120
+
121
+
122
+ class RegisterEntity(EntityBase):
123
+ """A register."""
124
+
125
+ kind: Literal["register"] = "register"
126
+ block: str | None = Field(default=None, description="Model key of the owning block.")
127
+ offset: int | str | None = Field(
128
+ default=None, description="Byte offset: an int, or an unresolved expression string."
129
+ )
130
+ width: int | None = Field(default=None, ge=1, description="Register width, in bits.")
131
+ reset_value: int | str | None = Field(default=None, description="Reset value.")
132
+ access: str | None = Field(default=None, description="Access mode, e.g. 'rw', 'ro', 'w1c'.")
133
+
134
+
135
+ # `register` shadows `ABCMeta.register` (pydantic model classes are ABCs), and pydantic
136
+ # warns about it on every import. The field is an instance attribute and nothing calls
137
+ # `FieldEntity.register(...)` as an ABC, so the name is kept (it is in the stored model and
138
+ # its JSON Schema) and only that one warning is silenced.
139
+ with warnings.catch_warnings():
140
+ warnings.filterwarnings(
141
+ "ignore",
142
+ message='Field name "register" in "FieldEntity" shadows an attribute',
143
+ category=UserWarning,
144
+ )
145
+
146
+ class FieldEntity(EntityBase):
147
+ """A bit field within a register."""
148
+
149
+ kind: Literal["field"] = "field"
150
+ register: str | None = Field(default=None, description="Model key of the owning register.")
151
+ lsb: int | None = Field(default=None, ge=0, description="Least-significant bit index.")
152
+ msb: int | None = Field(default=None, ge=0, description="Most-significant bit index.")
153
+ access: str | None = Field(default=None, description="Access mode, e.g. 'rw', 'ro', 'w1c'.")
154
+ reset_value: int | str | None = Field(default=None, description="Reset value.")
155
+
156
+
157
+ class InterruptEntity(EntityBase):
158
+ """An interrupt line."""
159
+
160
+ kind: Literal["interrupt"] = "interrupt"
161
+ block: str | None = Field(default=None, description="Model key of the owning block.")
162
+ line: int | None = Field(default=None, ge=0, description="Interrupt line number.")
163
+
164
+
165
+ class MemoryRegionEntity(EntityBase):
166
+ """A region of the address map."""
167
+
168
+ kind: Literal["memory_region"] = "memory_region"
169
+ base: int | str | None = Field(
170
+ default=None, description="Base address: an int, or an unresolved expression string."
171
+ )
172
+ size: int | str | None = Field(
173
+ default=None, description="Region size in bytes: an int, or an unresolved expression."
174
+ )
175
+ block: str | None = Field(default=None, description="Model key of the owning block.")
176
+
177
+
178
+ class RequirementEntity(EntityBase):
179
+ """A single requirement, usually with a REQ-ID."""
180
+
181
+ kind: Literal["requirement"] = "requirement"
182
+ text: str | None = Field(default=None, description="The requirement's full sentence.")
183
+ ears: EarsParts | None = Field(
184
+ default=None, description="Structured EARS parts, when available."
185
+ )
186
+
187
+
188
+ class DecisionEntity(EntityBase):
189
+ """A recorded design decision (not to be confused with `contracts.Decision`)."""
190
+
191
+ kind: Literal["decision"] = "decision"
192
+ text: str | None = Field(default=None, description="What was decided.")
193
+ rationale: str | None = Field(default=None, description="Why it was decided.")
194
+
195
+
196
+ class OpenItemEntity(EntityBase):
197
+ """An open question or todo against a spec."""
198
+
199
+ kind: Literal["open_item"] = "open_item"
200
+ status: Literal["open", "resolved"] | None = Field(
201
+ default=None, description="Whether this open item is still open."
202
+ )
203
+
204
+
205
+ class TestEntity(EntityBase):
206
+ """A test, mapped back to the requirements it verifies."""
207
+
208
+ __test__ = False # tell pytest this "Test*" class is not a test case to collect
209
+
210
+ kind: Literal["test"] = "test"
211
+ requirement_keys: tuple[str, ...] = Field(
212
+ default=(), description="Model keys of the requirements this test verifies."
213
+ )
214
+
215
+
216
+ CORE_ENTITY_CLASSES: tuple[type[EntityBase], ...] = (
217
+ ProjectEntity,
218
+ BlockEntity,
219
+ ModuleEntity,
220
+ PortEntity,
221
+ InterfaceEntity,
222
+ ClockEntity,
223
+ ResetEntity,
224
+ ParameterEntity,
225
+ RegisterEntity,
226
+ FieldEntity,
227
+ InterruptEntity,
228
+ MemoryRegionEntity,
229
+ RequirementEntity,
230
+ DecisionEntity,
231
+ OpenItemEntity,
232
+ TestEntity,
233
+ )
234
+ """Every core entity class, in the order they should appear in generated schemas."""
235
+
236
+
237
+ class ExtEntity(EntityBase):
238
+ """A pack-defined entity kind the reader has no typed class registered for.
239
+
240
+ Round-trips losslessly: `kind` and every extra fact live in `attrs`/`kind` so a
241
+ store can persist and reload entities from packs it does not know about.
242
+ """
243
+
244
+ kind: str = Field(description="The pack-defined kind, e.g. 'pin_spec'.")