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,272 @@
1
+ """What an `rtl-author` task gets in its context (DESIGN.md 5.1, 5.3; task M2-04).
2
+
3
+ Only the inputs an RTL module is written from, never a testbench:
4
+
5
+ - `module`: the module's entry of the block's approved plan (summary, requirement ids,
6
+ interface ports, dependencies, write set, budget), with the plan's top, assumptions
7
+ and open questions;
8
+ - `requirements`: the module's requirements, with their ids and text (the Design
9
+ Model's; the plan names them);
10
+ - `interface`: the module's ports. A port the Design Model knows (the block's spec
11
+ ports, else its top RTL module's) is shown as the model has it (`source: model`); an
12
+ internal port between the block's modules is shown as the approved plan gives it
13
+ (`source: plan`). Clock and reset come from the model's block;
14
+ - `dependencies`: the modules it depends on, declarations only: name and ports, from
15
+ the Design Model when it knows the module, else from the approved plan;
16
+ - `naming`: the project's naming rules, rendered as text (`naming_text`): the dynamic
17
+ part of the `org/naming` skill;
18
+ - `style`: the exemplars' text (`style.exemplars`, each capped, labelled "imitate this
19
+ style") and the style profile measured from them (`chipgraph.checks.style`). An
20
+ exemplar the role must not read (`is_denied`, e.g. a testbench) is refused: its entry
21
+ says so and holds no text.
22
+
23
+ `spec_side` is the block's spec slice (registers, interrupts, clock and reset) with the
24
+ requirements cut to the module's: what the model input `block/<block>` shows.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ from collections.abc import Callable, Mapping, Sequence
31
+ from pathlib import Path
32
+ from typing import Any
33
+
34
+ from chipgraph.checks._naming_rules import NamingRules
35
+ from chipgraph.checks.naming import _load_rules, _resolve_rules_path, _RulesError
36
+ from chipgraph.checks.plan_check import PlanEnv, PlanEnvError, check_plan_file
37
+ from chipgraph.checks.style import style_profile
38
+ from chipgraph.core.config.loader import ResolvedProfile
39
+ from chipgraph.core.contracts import RuleSpec
40
+ from chipgraph.core.model import DesignModel
41
+ from chipgraph.packs.digital_rtl.plan.model import Plan, PlanModule
42
+ from chipgraph.packs.dv.interface import spec_slice
43
+
44
+ MAX_EXEMPLAR_CHARS = 12_000
45
+ """Longest text of one exemplar in the context; longer files are cut."""
46
+ MAX_EXEMPLARS_CHARS = 36_000
47
+ """Most exemplar text in one context; exemplars past it are listed without text."""
48
+ DEFAULT_APPROVED = "plan/{block}.approved.json"
49
+
50
+ IMITATE = (
51
+ "imitate this style: indentation, begin/end placement, port declarations, reset and "
52
+ "clocking, signal suffixes, comments. Do not copy its logic."
53
+ )
54
+
55
+
56
+ def _naming_ref(resolved: ResolvedProfile, block: str) -> str | None:
57
+ profile = resolved.for_block(block)
58
+ if profile.naming.rules is not None:
59
+ return profile.naming.rules
60
+ cfg = profile.adapters.get("naming")
61
+ dumped = cfg.model_dump() if cfg is not None else {}
62
+ rules = dumped.get("rules")
63
+ return rules if dumped.get("use") == "naming" and isinstance(rules, str) else None
64
+
65
+
66
+ def naming_text(rules: NamingRules, ref: str) -> str:
67
+ """The naming rules as instructions a model can follow."""
68
+ lines = [
69
+ f"Naming rules ({rules.document} v{rules.version}, from {ref}). Every name you "
70
+ "declare must follow them; the `naming` check rejects the module otherwise.",
71
+ "",
72
+ "Allowed names, per kind (the whole name must match the pattern):",
73
+ ]
74
+ for kind, rule in rules.identifiers.items():
75
+ lines.append(f"- {kind}: `{rule.pattern}` ({rule.rule}): {rule.message}")
76
+ if rules.lexical:
77
+ lines.extend(["", "Lexical rules, for every name:"])
78
+ for lex in rules.lexical:
79
+ unless = f", unless it matches `{lex.allow_if_matches}`" if lex.allow_if_matches else ""
80
+ lines.append(f"- {lex.rule}: no match of `{lex.forbid_pattern}`{unless}: {lex.message}")
81
+ if rules.vocabulary is not None and rules.vocabulary.replace:
82
+ words = ", ".join(f"{bad} -> {good}" for bad, good in rules.vocabulary.replace.items())
83
+ lines.extend(["", f"Vocabulary ({rules.vocabulary.rule}): {words}"])
84
+ lines.extend(
85
+ [
86
+ "",
87
+ f"Do not use the exemption comment `// {rules.ignore_comment}`: it is for people.",
88
+ ]
89
+ )
90
+ return "\n".join(lines) + "\n"
91
+
92
+
93
+ def _naming(root: Path, resolved: ResolvedProfile, block: str) -> dict[str, Any] | None:
94
+ ref = _naming_ref(resolved, block)
95
+ if ref is None:
96
+ return None
97
+ try:
98
+ rules, _ = _load_rules(_resolve_rules_path(ref, root))
99
+ except _RulesError as exc:
100
+ return {"rules": ref, "note": str(exc)}
101
+ return {"rules": ref, "text": naming_text(rules, ref)}
102
+
103
+
104
+ def _exemplars(
105
+ root: Path,
106
+ resolved: ResolvedProfile,
107
+ is_denied: Callable[[str], bool],
108
+ ) -> dict[str, Any]:
109
+ style = resolved.profile.style
110
+ entries: list[dict[str, Any]] = []
111
+ texts: dict[str, str] = {}
112
+ used = 0
113
+ for rel in style.exemplars:
114
+ entry: dict[str, Any] = {"path": rel}
115
+ path = root / rel
116
+ if is_denied(rel):
117
+ entry["refused"] = (
118
+ f"{rel} is a file this role must not read (a testbench or test): it is not "
119
+ "shown. Remove it from the profile's style.exemplars."
120
+ )
121
+ elif not path.is_file():
122
+ entry["missing"] = True
123
+ else:
124
+ text = path.read_text(encoding="utf-8", errors="replace")
125
+ texts[rel] = text
126
+ if used >= MAX_EXEMPLARS_CHARS:
127
+ entry["omitted"] = "the context's exemplar budget is used up"
128
+ else:
129
+ cap = min(MAX_EXEMPLAR_CHARS, MAX_EXEMPLARS_CHARS - used)
130
+ if len(text) > cap:
131
+ entry["truncated"] = True
132
+ text = text[:cap]
133
+ used += len(text)
134
+ entry["label"] = IMITATE
135
+ entry["content"] = text
136
+ entries.append(entry)
137
+ out: dict[str, Any] = {"exemplars": entries, "enforce": list(style.enforce)}
138
+ if texts:
139
+ profile = style_profile(texts)
140
+ out["profile"] = profile.describe()
141
+ return out
142
+
143
+
144
+ def _ports(ports: Sequence[Any], source: str) -> list[dict[str, Any]]:
145
+ return [
146
+ {"name": p.name, "direction": p.direction, "width": p.width, "source": source}
147
+ for p in ports
148
+ ]
149
+
150
+
151
+ def _interface(
152
+ plan: Plan, module: PlanModule, env: PlanEnv, slice_: Mapping[str, Any]
153
+ ) -> dict[str, Any]:
154
+ model_ports = {p.name: p for p in env.interface}
155
+ ports: list[dict[str, Any]] = []
156
+ for port in module.interface.ports:
157
+ known = model_ports.get(port.name)
158
+ if known is not None and not port.internal:
159
+ ports.extend(_ports([known], "model"))
160
+ else:
161
+ ports.extend(_ports([port], "plan"))
162
+ return {
163
+ "module": module.name,
164
+ "top": plan.top == module.name,
165
+ "model_interface": env.interface_source,
166
+ "ports": ports,
167
+ "clock": slice_.get("clock"),
168
+ "reset": slice_.get("reset"),
169
+ }
170
+
171
+
172
+ def _dependencies(plan: Plan, module: PlanModule, env: PlanEnv) -> list[dict[str, Any]]:
173
+ existing = {m.name: m for m in env.existing_modules}
174
+ out: list[dict[str, Any]] = []
175
+ for name in dict.fromkeys(module.depends_on):
176
+ found = existing.get(name)
177
+ planned = plan.module(name)
178
+ if found is not None:
179
+ out.append({"name": name, "source": "model", "ports": _ports(found.ports, "model")})
180
+ elif planned is not None:
181
+ out.append(
182
+ {
183
+ "name": name,
184
+ "source": "plan",
185
+ "summary": planned.summary,
186
+ "ports": _ports(planned.interface.ports, "plan"),
187
+ }
188
+ )
189
+ return out
190
+
191
+
192
+ def approved_plan_path(root: Path, block: str, approved: str | None = None) -> str | None:
193
+ """The plan file an approved plan artifact names (None when there is none)."""
194
+ rel = (approved or DEFAULT_APPROVED).replace("{block}", block)
195
+ path = root / rel
196
+ if not path.is_file():
197
+ return None
198
+ try:
199
+ data = json.loads(path.read_text(encoding="utf-8"))
200
+ except (OSError, ValueError):
201
+ return None
202
+ plan = data.get("plan") if isinstance(data, dict) else None
203
+ return plan if isinstance(plan, str) else None
204
+
205
+
206
+ def rtl_author_context(
207
+ root: Path,
208
+ resolved: ResolvedProfile,
209
+ rules: Sequence[RuleSpec],
210
+ *,
211
+ block: str,
212
+ module: str | None,
213
+ model: DesignModel | None,
214
+ is_denied: Callable[[str], bool],
215
+ approved: str | None = None,
216
+ ) -> dict[str, Any]:
217
+ """The `rtl` section of an `rtl-author` task's context (see the module docstring)."""
218
+ out: dict[str, Any] = {"block": block, "module_name": module}
219
+ out["naming"] = _naming(root, resolved, block)
220
+ out["style"] = _exemplars(root, resolved, is_denied)
221
+ slice_ = spec_slice(model, block) if model is not None else {}
222
+ plan_path = approved_plan_path(root, block, approved)
223
+ if module is None or plan_path is None:
224
+ out["note"] = "no approved plan entry for this task: write from the spec and model"
225
+ out["requirements"] = list(slice_.get("requirements", []))
226
+ return out
227
+ try:
228
+ report = check_plan_file(
229
+ root, block, plan_path=plan_path, resolved=resolved, rules=rules, check_existing=False
230
+ )
231
+ except PlanEnvError as exc:
232
+ out["note"] = str(exc)
233
+ return out
234
+ plan, env = report.plan, report.env
235
+ entry = plan.module(module) if plan is not None else None
236
+ if plan is None or env is None or entry is None:
237
+ out["note"] = f"module {module!r} is not in the plan {plan_path}"
238
+ return out
239
+ wanted = set(entry.reqs)
240
+ out["module"] = {
241
+ **entry.model_dump(mode="json"),
242
+ "plan": plan_path,
243
+ "plan_top": plan.top,
244
+ "plan_assumptions": list(plan.assumptions),
245
+ "plan_open_questions": list(plan.open_questions),
246
+ }
247
+ out["requirements"] = [
248
+ {"id": r.ref, "text": r.text} for r in env.requirements if r.refs & wanted
249
+ ]
250
+ out["interface"] = _interface(plan, entry, env, slice_)
251
+ out["dependencies"] = _dependencies(plan, entry, env)
252
+ return out
253
+
254
+
255
+ def spec_side(model: DesignModel, block: str, reqs: Sequence[str] | None) -> dict[str, Any]:
256
+ """The block's spec slice with only the requirements in `reqs` (all when None)."""
257
+ slice_ = spec_slice(model, block)
258
+ if reqs is not None:
259
+ wanted = set(reqs)
260
+ slice_["requirements"] = [r for r in slice_["requirements"] if r["id"] in wanted]
261
+ return slice_
262
+
263
+
264
+ __all__ = [
265
+ "IMITATE",
266
+ "MAX_EXEMPLARS_CHARS",
267
+ "MAX_EXEMPLAR_CHARS",
268
+ "approved_plan_path",
269
+ "naming_text",
270
+ "rtl_author_context",
271
+ "spec_side",
272
+ ]
@@ -0,0 +1,30 @@
1
+ # A block's pull request (DESIGN.md 6.1; task M2-11a).
2
+ #
3
+ # For each block with an approved, valid plan (`foreach: plan.block`), once every module's
4
+ # RTL, the testbench and the Critic's review are built (the resolver adds the module
5
+ # files and the testbench as inputs), the `open_pr` tool builds the branch
6
+ # `chipgraph/<run id>/<block>` in a workspace outside the user's checkout (HEAD, index
7
+ # and working tree untouched), with exactly those files; with `review: {use: github}` it
8
+ # pushes it to `review.remote` (default origin; an https remote needs
9
+ # CHIPGRAPH_GITHUB_TOKEN, given to git through GIT_ASKPASS only) and opens the PR into
10
+ # `review.base` (idempotent), with the plan, requirement coverage, check results and the
11
+ # Critic's verdict as its body. It writes `pr/{block}.json`: branch, head commit, PR number
12
+ # and URL. A re-run with changed files moves the same branch forward (never a force push)
13
+ # and reuses the open PR.
14
+ #
15
+ # The gate `pr:{block}` is on `digital-rtl/pr_gate`. chipgraph never merges.
16
+ rule: open_pr
17
+ kind: gen
18
+ description: >-
19
+ Commit the block's RTL and testbench on a branch of their own, push it and open a pull
20
+ request whose body says what the plan, the checks and the Critic found.
21
+ foreach: plan.block
22
+ inputs:
23
+ - artifact: "reports/review/{block}.json"
24
+ outputs:
25
+ - "pr/{block}.json"
26
+ run:
27
+ use: open_pr
28
+ args:
29
+ review: "reports/review/{block}.json"
30
+ out: "pr/{block}.json"
@@ -0,0 +1,28 @@
1
+ # The Planner (DESIGN.md 5.1, 5.3; task M2-03).
2
+ #
3
+ # For each block, a `planner` task reads the block's slice of the Design Model
4
+ # (requirements, interface ports, existing modules) and writes one file: the block's plan
5
+ # (`Plan`, schemas/formats/plan.schema.json). Its context also gives the layout paths a
6
+ # module may write, the files that already exist there, the limits (profile `plan:`),
7
+ # the naming rule for module names and the plan schema.
8
+ #
9
+ # The plan is checked at once by `plan_check` (enable it in the profile:
10
+ # `adapters: { plan_check: { use: plan_check } }`); its issues are the redo text of the
11
+ # next try. A plan that passes is not used yet: `digital-rtl/plan_expand` waits on the
12
+ # gate `plan:{block}` until a person reads `chipgraph plan show <block>` and approves.
13
+ rule: plan
14
+ kind: agent
15
+ role: planner
16
+ description: >-
17
+ Split the block into modules: what each does, which requirements it covers, its ports
18
+ from the Design Model, what it depends on, the files it writes and its budget; list the
19
+ assumptions, the open questions and every requirement left unassigned, with the reason.
20
+ foreach: blocks
21
+ inputs:
22
+ - model: "block/{block}"
23
+ outputs:
24
+ - "plan/{block}.plan.yml"
25
+ skills: [plan/modules]
26
+ checks: [plan_check]
27
+ budget:
28
+ tries: 3
@@ -0,0 +1,29 @@
1
+ # The human gate on a block's plan (DESIGN.md 6.1; task M2-03).
2
+ #
3
+ # A `gen` rule whose only file input is the plan, gated by `plan:{block}`. The gate covers
4
+ # a rule instance's file inputs, so approving it pins the plan file's hash:
5
+ #
6
+ # chipgraph plan show <block> # read it first
7
+ # chipgraph approve plan:<block> --instance 'digital-rtl/plan_expand[block=<block>]'
8
+ #
9
+ # Editing the plan afterwards changes its hash and puts the gate back to waiting. Once
10
+ # approved, the `plan_expand` tool checks the plan again and writes the approved plan
11
+ # artifact below. Rules that say `foreach: plan.modules` then get one instance per
12
+ # module (params `block`, `module`), in dependency order, each bounded by the module's
13
+ # write set; with no approved, valid plan they get none.
14
+ rule: plan_expand
15
+ kind: gen
16
+ description: >-
17
+ After a person approves the block's plan, check it again and write the approved plan
18
+ (modules in build order, their dependencies and write sets).
19
+ foreach: blocks
20
+ inputs:
21
+ - artifact: "plan/{block}.plan.yml"
22
+ outputs:
23
+ - "plan/{block}.approved.json"
24
+ gate: "plan:{block}"
25
+ run:
26
+ use: plan_expand
27
+ args:
28
+ plan: "plan/{block}.plan.yml"
29
+ out: "plan/{block}.approved.json"
@@ -0,0 +1,27 @@
1
+ # The human gate on a block's pull request (DESIGN.md 6.1; task M2-11a).
2
+ #
3
+ # Its input is the PR record `open_pr` wrote (and, from `foreach: plan.block`, the block's
4
+ # files). With `review: {use: github}` the gate `pr:{block}` is the pull request's review:
5
+ # approved when a reviewer approves at the PR's current head commit (a later push makes
6
+ # the approval stale), rejected when one requests changes. With the `file` adapter (no
7
+ # GitHub, offline) a person approves it here:
8
+ #
9
+ # chipgraph approve pr:<block>
10
+ #
11
+ # Once approved, `pr_gate` writes the approved head below. Merging stays a person's act.
12
+ rule: pr_gate
13
+ kind: gen
14
+ description: >-
15
+ Wait for a person to approve the block's pull request at its current head, then
16
+ record what was approved.
17
+ foreach: plan.block
18
+ inputs:
19
+ - artifact: "pr/{block}.json"
20
+ outputs:
21
+ - "pr/{block}.approved.json"
22
+ gate: "pr:{block}"
23
+ run:
24
+ use: pr_gate
25
+ args:
26
+ pr: "pr/{block}.json"
27
+ out: "pr/{block}.approved.json"
@@ -0,0 +1,31 @@
1
+ # The Critic review of one block's change (DESIGN.md 4.8, 5.1; task M2-09).
2
+ #
3
+ # For each block: a `critic` task gets, with a fresh context (no conversation, no author
4
+ # reasoning, no earlier review), the block's diff (base ref vs the working tree, only the
5
+ # block's files), its spec slice (requirements, interface, registers from the Design
6
+ # Model) and its model slice. The Critic writes no file: it replies with a JSON review
7
+ # (`ReviewReport`, schemas/roles/ReviewReport.schema.json); `submit` validates it and the
8
+ # engine writes it to the one output below. An invalid reply is rejected and uses a try.
9
+ #
10
+ # The base ref is the instance param `base` when there is one, otherwise the merge-base
11
+ # of HEAD with the default branch (so on the default branch itself: HEAD, and the diff is
12
+ # the uncommitted change). Findings never block the build (DESIGN.md 4.8): a valid
13
+ # review is accepted whatever its verdict; people read the verdict.
14
+ #
15
+ # `foreach: plan.blocks` (M2-11a): one instance per block. A block with an approved plan
16
+ # takes every module's files and its testbench as inputs, so its review runs after all
17
+ # of them (target `rtl:<block>`); a block without one is reviewed as it is.
18
+ rule: review
19
+ kind: agent
20
+ role: critic
21
+ description: >-
22
+ Review the block's change against its requirements, interface and Design Model; reply
23
+ with a structured review, which the engine writes as the block's review report.
24
+ foreach: plan.blocks
25
+ inputs:
26
+ - model: "block/{block}"
27
+ outputs:
28
+ - "reports/review/{block}.json"
29
+ skills: [review/diff]
30
+ budget:
31
+ tries: 2
@@ -0,0 +1,53 @@
1
+ # The RTL of one planned module, written without seeing its testbench (DESIGN.md 5.1,
2
+ # 5.3, 8.6 V3; task M2-04).
3
+ #
4
+ # For each module of a block's approved plan (`foreach: plan.modules`, params `block` and
5
+ # `module`), an `rtl-author` task writes the module's RTL. Its context holds only what an
6
+ # RTL module is written from: the module's entry of the approved plan (the resolver adds
7
+ # `plan/{block}.approved.json` as an input), the model slice (`block/{block}`: the block's
8
+ # registers, clock and reset, with the requirements cut to the module's;
9
+ # `interface/{module}`: its ports from the Design Model, or the approved plan for an
10
+ # internal port; the declarations of the modules it depends on), the block's spec
11
+ # documents (the profile layout's `spec`), the naming rules as text and the profile's
12
+ # `style.exemplars`. Never a testbench: the role denies `tb` artifacts, the profile's
13
+ # `layout.tb` paths and files named like tests (`test_*.py`, `tb_*.sv`, `*_tb.sv`, ...);
14
+ # the rule validator refuses such an input, `get_context` refuses to serve one, the guard
15
+ # refuses reads of them, and what `submit` tells the author of a failed check is
16
+ # filtered the same way (behavioural text such as `expected 0x2, got 0x0` is kept).
17
+ #
18
+ # The output is the module's file in the block's RTL directory, `{layout.rtl}`: the
19
+ # directory part of the profile's `layout.rtl` for the block (QSoC's
20
+ # `design/{block}/rtl/*.sv` gives `design/pwm/rtl`), `rtl` when the profile has none
21
+ # (`chipgraph.packs.digital_rtl.layout`). The plan must list it in the module's writes
22
+ # (`plan_check` says so), and the graph refuses an output outside them.
23
+ #
24
+ # Besides the approved plan, the resolver adds the profile's `style.exemplars` and its
25
+ # naming rules file as inputs: editing one makes the module stale, so it is written
26
+ # again in the new style (an exemplar the plan itself rewrites is left out).
27
+ #
28
+ # Checks, in this order, each an adapter id of the profile:
29
+ # format the project's formatter on the outputs, run by the engine before the
30
+ # other checks (`chipgraph.checks.format`: `{ use: format }`, formatter from
31
+ # `style.formatter`, default verible-verilog-format when on PATH)
32
+ # lint the project's lint (tinysoc: `make lint BLOCK={block}`)
33
+ # naming the project's naming rule (`{ use: naming, rules: ..., scope: [...] }`)
34
+ # style the outputs against `style.exemplars` (`chipgraph.checks.style`:
35
+ # `{ use: style }`; errors only for the keys in `style.enforce`)
36
+ # hardcode no contract number typed by hand (`{ use: hardcode, scope: [...] }`)
37
+ rule: rtl_module
38
+ kind: agent
39
+ role: rtl-author
40
+ description: >-
41
+ Write the module's synthesizable RTL from its approved plan entry, its requirements,
42
+ its interface and the declarations it connects to, following the naming rules and the
43
+ style of the exemplars; cite each requirement id next to the logic that implements it.
44
+ foreach: plan.modules
45
+ inputs:
46
+ - model: "block/{block}"
47
+ - model: "interface/{module}"
48
+ outputs:
49
+ - "{layout.rtl}/{module}.sv"
50
+ skills: [lang-sv/rtl, org/naming]
51
+ checks: [format, lint, naming, style, hardcode]
52
+ budget:
53
+ tries: 3
@@ -0,0 +1,58 @@
1
+ ---
2
+ id: lang-sv/rtl
3
+ description: How to write one synthesizable SystemVerilog module from its plan entry, requirements and interface, in the project's style.
4
+ roles: [rtl-author]
5
+ version: 0.1.0
6
+ ---
7
+ Write one synthesizable SystemVerilog module per output file (`outputs[0]`), named as
8
+ `rtl.module.name`. You see the plan, the requirements, the interface, the naming rules and
9
+ the style exemplars; you never see a testbench, and must not guess one.
10
+
11
+ What to use from the context:
12
+
13
+ - `rtl.module`: the module's entry of the approved plan: its summary, the requirement ids
14
+ it covers (`reqs`), its ports, the modules it depends on and the files it may write.
15
+ Stay inside it: no file outside `outputs`, no port the entry does not list, no logic
16
+ that belongs to another module of the plan.
17
+ - `rtl.interface.ports`: the module's ports, exactly (name, direction, width). A port with
18
+ `source: model` is the Design Model's; one with `source: plan` is internal to the block,
19
+ as a person approved it. Do not add, drop or rename a port.
20
+ - `rtl.dependencies`: the modules this one instantiates or connects to, declarations only.
21
+ Instantiate them with named port connections (`.port(signal)`), every port connected.
22
+ - `rtl.requirements`: what the module must do. Implement each one, and put its id in a
23
+ comment next to the logic that implements it: `// REQ-TIM-001: ...`.
24
+ - `rtl.style.exemplars`: files of this project. **Follow their style where it differs
25
+ from your habits**: indentation, `begin` placement, how ports are declared and aligned,
26
+ the reset (asynchronous or synchronous, active low or high, its name) and the clocked
27
+ block, signal suffixes (`_q`/`_d`, `_r`/`_next`, ...), the comment header, blank lines,
28
+ line length. `rtl.style.profile` is the same, measured; the `style` check compares your
29
+ file with it (the keys in `rtl.style.enforce` fail the check).
30
+
31
+ Synthesizable RTL:
32
+
33
+ - `logic` for every signal; `always_ff` for registers, `always_comb` for logic, `assign`
34
+ for simple wires. No `initial` blocks for state, no `#` delays, no `$display`.
35
+ - One clock and one reset convention, the exemplars' (asynchronous or synchronous, the
36
+ active level, the reset's name); every register is reset.
37
+ - No latches: in `always_comb`, give every signal a value on every path (a default
38
+ assignment first, or a `default` branch in every `case`).
39
+ - Every output is driven; every input bit is used (see lint, below).
40
+ - Width-explicit literals (`8'd0`, `1'b1`, `32'h0000_0010`); parameters (`localparam`) for
41
+ every constant with a meaning, not magic numbers; never retype an address or size the
42
+ spec names as a number: the `hardcode` check rejects it.
43
+ - Nonblocking `<=` in `always_ff`, blocking `=` in `always_comb`, never mixed.
44
+
45
+ Lint is Verilator with `-Wall`, and any warning fails the check:
46
+
47
+ - Every input bit is used, or consumed on purpose: for bits the module ignores, write
48
+ `logic unused_ok; assign unused_ok = &{1'b0, wdata[31:8]};` (Verilator ignores a signal
49
+ whose name contains `unused`). Do the same for a part of an internal signal you never
50
+ read.
51
+ - No signal declared and never read, no width mismatch in an assignment or comparison
52
+ (size both sides, or slice and extend explicitly), no output left unconnected on an
53
+ instance (connect it to a named `unused_*` signal).
54
+ - No `lint_off` pragmas, unless the style exemplars use them.
55
+
56
+ The engine runs the project's formatter on your file, then lint, naming, style and
57
+ hardcode. If the requirements or the interface are ambiguous or contradict each other, do
58
+ not guess: stop and report `needs_human` with the question.
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: org/naming
3
+ description: How to name modules, ports, parameters and signals from the project's naming rules in the context.
4
+ roles: [rtl-author]
5
+ version: 0.1.0
6
+ ---
7
+ Names come from the project's naming rules, never from memory or another project's
8
+ habits. The rules are in your context as text: `rtl.naming.text` (from the file
9
+ `rtl.naming.rules`).
10
+
11
+ How to read them:
12
+
13
+ - "Allowed names, per kind": one line per kind of name you declare: `module`, `port`,
14
+ `parameter`, `localparam`, `enum_value`, `instance`, `signal`, `memory`, `genvar`.
15
+ The whole name must match the pattern of its kind. A port is checked as a port, a
16
+ signal as a signal: the check classifies each declaration by its syntax.
17
+ - "Lexical rules": apply to every name you declare (case, active-low marks, indices).
18
+ - "Vocabulary": a word on the left must not appear in a name; use the word on the right.
19
+
20
+ Then:
21
+
22
+ - The module name and the port names are fixed by `rtl.module` and `rtl.interface`; they
23
+ already follow the rules. Use them exactly.
24
+ - Name every other signal, parameter and instance by its kind's pattern. When the style
25
+ exemplars use a suffix the rules allow (`_q`, `_d`, `_r`, `_next`), use the exemplars'.
26
+ - When the rules and the exemplars disagree, the rules win: the `naming` check rejects a
27
+ name, the `style` check only warns unless the profile enforces that key.
28
+ - When `rtl.naming` is empty, the project has no naming rules: follow the exemplars.
29
+ - Never use the exemption comment the rules name.
@@ -0,0 +1,63 @@
1
+ ---
2
+ id: plan/modules
3
+ description: How to split a block into modules and write its plan file.
4
+ roles: [planner]
5
+ version: 0.1.0
6
+ ---
7
+ Write the block's plan to `outputs[0]` as YAML that follows `plan.schema` in your
8
+ context. A person reads it (`chipgraph plan show`) and approves it before any module
9
+ is built, so it must be complete and honest.
10
+
11
+ 1. **Interface first (F1).** Copy ports from `plan.interface.ports` exactly: name,
12
+ direction, width. Never invent a port. The `top` module carries the block's
13
+ interface. A helper port between two modules of the block is `internal: true`, and
14
+ is never on `top`.
15
+ 2. **Requirements.** Assign every requirement in `plan.requirements` to the module that
16
+ implements it, by its `id` (the REQ id; for an inferred requirement, its key such as
17
+ `timer.h1a2b3c4d`). One you cannot place goes in `unassigned_reqs` with a reason. Do
18
+ not invent requirements.
19
+ 3. **Write sets (F2).** Each module lists every file its nodes write, and no two modules
20
+ write the same file. Every path matches one of `plan.layout`. Each module's writes
21
+ include the files in `plan.module_outputs` (fill in `{block}` and `{module}`). Never
22
+ write a file in `plan.other_rules_write`. A file in `plan.existing_files` is written
23
+ only on purpose: say `replaces: true` on that write.
24
+ 4. **Dependencies.** `depends_on` names modules of this plan that must be built first
25
+ (a module needs the interfaces of the modules it instantiates). No cycles.
26
+ 5. **Size.** Stay inside `plan.limits`: at most `max_modules` modules, a dependency
27
+ chain of at most `max_depth` modules, and module `budget.tries` adding up to at most
28
+ `max_total_tries`. Prefer few modules with clear boundaries over many small ones.
29
+ 6. **Names.** Module names follow `plan.naming` when it is set.
30
+ 7. **Say what you assumed (F4).** List every assumption in `assumptions`, and anything
31
+ a person must decide in `open_questions` (the approver must read them).
32
+
33
+ Example:
34
+
35
+ ```yaml
36
+ schema_version: 1
37
+ block: timer
38
+ top: timer_top
39
+ modules:
40
+ - name: timer_top
41
+ summary: Register interface and the irq output; instantiates the counter.
42
+ reqs: [REQ-TIM-004]
43
+ interface:
44
+ ports:
45
+ - { name: clk, direction: input, width: 1 }
46
+ depends_on: [timer_counter]
47
+ writes:
48
+ - { path: rtl/timer_top.sv }
49
+ budget: { tries: 3, tier: medium }
50
+ - name: timer_counter
51
+ summary: The 32-bit counter and its compare.
52
+ reqs: [REQ-TIM-001]
53
+ interface:
54
+ ports:
55
+ - { name: clk, direction: input, width: 1 }
56
+ - { name: count, direction: output, width: 32, internal: true }
57
+ writes:
58
+ - { path: rtl/timer_counter.sv }
59
+ assumptions: [The counter wraps at 2^32.]
60
+ open_questions: []
61
+ unassigned_reqs:
62
+ - { req: REQ-TIM-005, reason: The read mux belongs to the bus wrapper, not planned here. }
63
+ ```
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: review/diff
3
+ description: How to review a block's diff against its requirements, interface and Design Model.
4
+ roles: [critic]
5
+ version: 0.1.0
6
+ ---
7
+ Review the diff in `review.diff` against `review.spec` (the block's requirements, its
8
+ interface and its registers) and `review.model`. Read a changed file in full when the
9
+ hunk alone does not show enough.
10
+
11
+ Look for, in this order:
12
+
13
+ - **spec/RTL mismatch**: behaviour a requirement states that the new code does not do,
14
+ or does differently (`spec_mismatch`; `missing_req` when a requirement's behaviour is
15
+ gone or never written).
16
+ - **reset**: a register's reset value differs from the spec's `Reset` column (`reset`).
17
+ - **width**: a signal, port or field narrower or wider than the spec says, a
18
+ truncating or extending assignment (`width`).
19
+ - **counters and compares**: off by one, `<` for `<=`, the wrong step (`counter`).
20
+ - **register map**: a register decoded at the wrong offset, a wrong access type, an
21
+ address decode that does not match the memory map (`register_map`).
22
+ - **bit order**: fields or bits swapped or reversed against the spec (`bit_order`).
23
+ - **latches**: a combinational block that does not assign every output on every path
24
+ (`latch`).
25
+ - **CDC**: a signal crossing clock domains without a synchroniser (`cdc`).
26
+ - **hard-coded values**: a literal where the spec has a register, parameter or named
27
+ constant (`hard_coded`).
28
+ - **naming** against the project's rules, and **test gaps**: a changed requirement with
29
+ no test (`naming`, `test_gap`). Anything else: `other`.
30
+
31
+ Rules:
32
+
33
+ - Every comment is on a `file:line` inside the diff, and its `evidence` quotes what the
34
+ claim rests on: the spec line (`doc/...md:68 While CTRL.EN = 1, ...`) or the code line.
35
+ No evidence, no comment.
36
+ - Name the requirement in `req_id` when there is one.
37
+ - `blocker` or `major`: the design is wrong against the spec. `minor`: a real but small
38
+ problem. `nit`: optional. Do not comment on formatting, whitespace or comments that a
39
+ formatter or lint covers, and do not invent requirements the spec does not state.
40
+ - When the diff is correct, say so: no comments, `reviewed` lists every file of the diff,
41
+ verdict `approve`.