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,120 @@
1
+ ---
2
+ id: rtl/wrapper-auto
3
+ description: How to write a wrapper's source around a vendor IP in the emacs verilog-mode AUTO style, which a generator expands.
4
+ roles: [rtl-author]
5
+ version: 0.1.0
6
+ ---
7
+ You write the **source** of a wrapper around a third-party IP: `outputs[0]`, a
8
+ `.src.sv` file. You never write its expansion. A generator copies your source, runs emacs
9
+ verilog-mode on it (`verilog-batch-auto`) with the settings in
10
+ `rtl.wrapper.generator.pinned`, and writes the expanded wrapper that the build compiles.
11
+ You cannot run emacs: on submit `wrap_check` expands your source and reports, with the
12
+ expanded lines, every IP port it left unmapped. Fix exactly what it reports.
13
+
14
+ What to use from the context:
15
+
16
+ - `rtl.wrapper.vendor`: the IP module's header: its parameters and its ports (name,
17
+ direction, type, dimensions). That is all you see of the IP, on purpose.
18
+ - `rtl.wrapper.interface`: the wrapper's own ports when the project knows them (Design
19
+ Model or plan). Your template must produce exactly those names. When it is empty,
20
+ name each wrapper port from the IP port it comes from, by the naming rules.
21
+ - `rtl.naming.text`: the project's naming rules (see the `org/naming` skill).
22
+ Every name the expansion declares (ports, wires) comes from your template, so the
23
+ template is where the naming rules are applied.
24
+ - `rtl.wrapper.exemplar`, when present, and its text in `inputs`: another wrapper of
25
+ this project. Imitate its structure (header comment, port groups, comments, how the
26
+ template is laid out), not its IP or its names.
27
+
28
+ ## The file you write
29
+
30
+ ```systemverilog
31
+ // <header comment: what the wrapper is, which IP, "edit this source only">
32
+
33
+ module <rtl.wrapper.name> (
34
+ // clock and reset
35
+ /*AUTOINPUT("^i_clk\|^i_rst")*/
36
+ // <a group per function: one AUTOINPUT/AUTOOUTPUT per kind of port>
37
+ /*AUTOINPUT("^i_push\|^i_data")*/
38
+ /*AUTOOUTPUT("^o_full")*/
39
+ // others: must stay empty once the template maps every IP port
40
+ /*AUTOINPUT*/
41
+ /*AUTOOUTPUT*/
42
+ );
43
+
44
+ /*AUTOWIRE*/
45
+
46
+ /*AUTO_LISP(setq verilog-auto-output-ignore-regexp "^unused_")*/
47
+
48
+ /* <ip module> AUTO_TEMPLATE (
49
+ .rst_ni (i_rst_n), // one port: rename
50
+ .testmode_i (1'b0), // tie an input off
51
+ .err_o (unused_err), // sink an output the wrapper does not use
52
+ .\(.*\)_i (i_\1[]), // a regexp: every other `<x>_i` becomes `i_<x>`
53
+ .\(.*\)_o (o_\1[]),
54
+ ); */
55
+ <ip module> #(
56
+ .<Param> (<value>) // every parameter the spec fixes, set here
57
+ ) u_<function> (/*AUTOINST*/);
58
+
59
+ endmodule
60
+ ```
61
+
62
+ The AUTO idioms:
63
+
64
+ - `/*AUTOINST*/` inside the instance's parentheses: emacs writes one `.port (signal),`
65
+ line per IP port, from the template. Write nothing else in those parentheses.
66
+ - `/* <module> AUTO_TEMPLATE ( ... ); */`, a comment just before the instance: one line
67
+ per rule, `.<ip port or regexp> (<signal>),`. The first rule that matches a port wins,
68
+ so put single ports first and catch-all regexps last. `\(...\)` captures part of the
69
+ port's name and `\1` reuses it; `[]` keeps the IP port's width (`o_data[7:0]`); a
70
+ slice is written out (`o_data[3:0]`). The template's module name must be exactly the
71
+ IP module's name, or no rule applies.
72
+ - `@"(...)"` in a template line is a Lisp expression, e.g.
73
+ `(o_bus_@"(downcase (symbol-name '\1))"[])`. Only the functions in
74
+ `rtl.wrapper.generator.lisp_functions` and the variables in `lisp_variables` (`vl-dir`,
75
+ `vl-width`, ...) are allowed; anything else makes the generator refuse the source.
76
+ - `/*AUTOINPUT("regexp")*/`, `/*AUTOOUTPUT("regexp")*/` (and `/*AUTOINOUT*/`) in the
77
+ port list: emacs declares, with their widths, the instance's inputs and outputs whose
78
+ connected signal matches the regexp and is not declared elsewhere. Group them by
79
+ function, each group under a comment; the last, unfiltered pair catches what no group
80
+ took and must expand to nothing. Declare no port by hand: a hand-written port next to
81
+ AUTO groups breaks the commas, and the expansion would then not be the interface.
82
+ - `/*AUTOWIRE*/` after the port list: emacs declares the instance outputs that are not
83
+ ports (the `unused_*` sinks). `/*AUTO_LISP(setq verilog-auto-output-ignore-regexp
84
+ "^unused_")*/` keeps those sinks from becoming outputs; AUTO_LISP may set only
85
+ verilog-mode's `verilog-auto-<kind>-ignore-regexp` filters.
86
+ - `/*AUTOARG*/` is for a non-ANSI header (`module m (/*AUTOARG*/); input ...`); a wrapper
87
+ uses the ANSI form above with AUTOINPUT/AUTOOUTPUT, so it needs no AUTOARG.
88
+ - `/*AUTORESET*/` resets the registers of an `always_ff` block; a wrapper has no
89
+ registers of its own unless its spec asks for one (a synchroniser), so it is rarely
90
+ used here.
91
+
92
+ Mapping the IP's names to the project's:
93
+
94
+ - Read each IP port in `rtl.wrapper.vendor.ports` and decide its project name by the
95
+ naming rules: the direction prefix or suffix the rules want (`i_`/`o_` or `_i`/`_o`),
96
+ the active-low mark (`rst_ni` becomes, e.g., `i_rst_n`), the project's vocabulary
97
+ (`irq` -> `int` when the rules say so). Prefer one regexp rule per naming pattern of
98
+ the IP (all `*_i`, all `*_o`), with single-port rules before it for the exceptions.
99
+ - Every IP input is connected: to a wrapper port, or tied off (`1'b0`, `'0`, a constant
100
+ the spec states). Never leave an input open (`()`).
101
+ - Every IP output is used: a wrapper port, or an `unused_<what>` sink (lint accepts a
102
+ signal whose name contains `unused`). Do not leave an output open (`()`): lint warns.
103
+ - Set every parameter the spec or the plan fixes in the instance's `#( ... )`; the
104
+ generator substitutes the values into the widths it declares.
105
+
106
+ Never:
107
+
108
+ - edit, copy or re-declare the IP's files, or write a module the IP already has: the
109
+ `vendor_guard` check fails on any change to them;
110
+ - write the expanded port list, the `// Beginning of automatic ...` blocks or the
111
+ `.port (signal), // Templated` lines yourself: emacs writes them, and `wrap_check`
112
+ rejects a source whose expansion does not match its own;
113
+ - put logic in the wrapper, except tie-offs and the width or polarity adaption the spec
114
+ states (an inverted reset, a zero-extended bus), each named by the naming rules;
115
+ - write a `Local Variables:` block, `AUTOINSERTLISP` or other Lisp: the generator
116
+ ignores file-local variables and refuses Lisp beyond the allowed list.
117
+
118
+ If the IP's ports cannot be mapped from the context (a port whose purpose is unclear,
119
+ an interface the project names that the IP cannot provide), stop and report
120
+ `needs_human` with the question.
@@ -0,0 +1,15 @@
1
+ """The ``dv`` pack: design verification (DESIGN.md 8.2), task M2-06.
2
+
3
+ - ``rules/tb_module.yml``: the rule ``dv/tb_module``, a cocotb test per block written by
4
+ the ``tb-author`` role from the spec and the interface, never the RTL;
5
+ - ``skills/dv/cocotb.md``: the skill ``dv/cocotb`` (for ``tb-author`` only);
6
+ - :mod:`chipgraph.packs.dv.interface`: the interface a test is written against
7
+ (``interface_for``: spec ports, else the Design Model's RTL ports, else the module's
8
+ declaration) and the block's spec slice;
9
+ - :mod:`chipgraph.packs.dv.declaration`: a module's declaration read with pyslang from
10
+ the syntax tree's module header only.
11
+
12
+ The pack directory is discovered by :func:`chipgraph.app.build.builtin_packs_dir`; a
13
+ project turns it on with ``packs: [dv]`` and configures the ``tb_static`` check
14
+ (``adapters.tb_static: {use: tb_static}``).
15
+ """
@@ -0,0 +1,392 @@
1
+ """A module's declaration, and nothing else, read from an RTL file with pyslang 12.
2
+
3
+ The testbench Author never sees the RTL (DESIGN.md 5.1, 5.3 F1). When a module's port
4
+ list is neither in its spec nor in the Design Model (a new module, a model not ingested
5
+ yet), the engine reads the RTL itself and keeps **only the module header**: the module
6
+ name, its parameter port list (name, kind, type, default expression) and its ports
7
+ (name, direction, net or variable type, packed and unpacked dimensions). For a non-ANSI
8
+ header (`module m(a, b); input [7:0] a; ...`) the body's port declarations are read too,
9
+ since they are the ports' declarations; every other body item is skipped unread.
10
+
11
+ It works on the syntax tree, never on the text: the `ModuleDeclaration`'s
12
+ `ModuleHeaderSyntax` (and, non-ANSI, its `PortDeclaration` members). Text is rebuilt
13
+ from tokens only, without their trivia, so a comment, a preprocessor directive or a
14
+ disabled `ifdef` branch never comes through, and no line number is kept. Macros used in
15
+ the header appear expanded. Output is capped (`MAX_PORTS`, `MAX_PARAMETERS`,
16
+ `MAX_TEXT`): a declaration cut short says so (`truncated`).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from collections.abc import Iterable, Iterator, Sequence
22
+ from pathlib import Path
23
+ from typing import Any, Literal
24
+
25
+ from pydantic import BaseModel, ConfigDict, Field
26
+ from pyslang.parsing import Token, TokenKind
27
+ from pyslang.syntax import SyntaxKind, SyntaxNode, SyntaxTree
28
+
29
+ MAX_PORTS = 256
30
+ """Ports kept per declaration; more set `truncated`."""
31
+ MAX_PARAMETERS = 64
32
+ """Parameters kept per declaration; more set `truncated`."""
33
+ MAX_TEXT = 120
34
+ """Characters kept of any one rebuilt text (a type, a dimension, a default)."""
35
+ MAX_FILE_BYTES = 4_000_000
36
+ """RTL files larger than this are not parsed."""
37
+
38
+ Direction = Literal["input", "output", "inout", "ref"]
39
+
40
+ _DIRECTIONS: dict[str, Direction] = {
41
+ "input": "input",
42
+ "output": "output",
43
+ "inout": "inout",
44
+ "ref": "ref",
45
+ }
46
+
47
+
48
+ class DeclaredParameter(BaseModel):
49
+ """One parameter of a module header."""
50
+
51
+ model_config = ConfigDict(extra="forbid", frozen=True)
52
+
53
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
54
+ name: str = Field(description="The parameter name.")
55
+ kind: Literal["parameter", "localparam", "type"] = Field(
56
+ description="'parameter', 'localparam', or 'type' for a type parameter."
57
+ )
58
+ type: str = Field(default="", description="Its declared type ('' when implicit).")
59
+ default: str | None = Field(default=None, description="Its default expression, as tokens.")
60
+
61
+
62
+ class DeclaredPort(BaseModel):
63
+ """One port of a module declaration."""
64
+
65
+ model_config = ConfigDict(extra="forbid", frozen=True)
66
+
67
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
68
+ name: str = Field(description="The port name.")
69
+ direction: Direction | None = Field(default=None, description="Its direction, if declared.")
70
+ type: str = Field(default="", description="Net/variable type and signing, e.g. 'logic'.")
71
+ packed: str = Field(default="", description="Packed dimensions, e.g. '[7:0]'.")
72
+ unpacked: str = Field(default="", description="Unpacked dimensions, e.g. '[2]'.")
73
+ width: int | None = Field(
74
+ default=None, description="Bits, when every packed dimension is a literal range."
75
+ )
76
+
77
+
78
+ class ModuleDeclaration(BaseModel):
79
+ """A module's header: its name, parameters and ports; never anything of its body."""
80
+
81
+ model_config = ConfigDict(extra="forbid", frozen=True)
82
+
83
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
84
+ module: str = Field(description="The module name.")
85
+ parameters: tuple[DeclaredParameter, ...] = Field(default=(), description="Its parameters.")
86
+ ports: tuple[DeclaredPort, ...] = Field(default=(), description="Its ports, in order.")
87
+ truncated: bool = Field(default=False, description="Whether the caps cut it short.")
88
+
89
+
90
+ # --- tokens to text ---------------------------------------------------------------------
91
+
92
+
93
+ def _tokens(node: Any) -> Iterator[Token]:
94
+ if node is None:
95
+ return
96
+ if isinstance(node, Token):
97
+ yield node
98
+ return
99
+ for child in node:
100
+ yield from _tokens(child)
101
+
102
+
103
+ def _wordy(text: str) -> bool:
104
+ return bool(text) and (text[0].isalnum() or text[0] in "_$`'\\")
105
+
106
+
107
+ def _text(node: Any) -> str:
108
+ """The node's tokens joined without trivia: no comment or directive ever comes along."""
109
+ parts: list[str] = []
110
+ previous: Token | None = None
111
+ for token in _tokens(node):
112
+ raw = token.rawText
113
+ if token.isMissing or not raw:
114
+ continue
115
+ if (
116
+ previous is not None
117
+ and _wordy(raw)
118
+ and _wordy(previous.rawText[-1:])
119
+ and previous.kind != TokenKind.IntegerBase
120
+ and not raw.startswith("'")
121
+ ):
122
+ parts.append(" ")
123
+ parts.append(raw)
124
+ previous = token
125
+ text = "".join(parts)
126
+ return text if len(text) <= MAX_TEXT else text[: MAX_TEXT - 1] + "…"
127
+
128
+
129
+ def _nodes(items: Any) -> list[Any]:
130
+ """The syntax nodes of a separated list (its comma tokens dropped)."""
131
+ if items is None:
132
+ return []
133
+ return [item for item in items if isinstance(item, SyntaxNode)]
134
+
135
+
136
+ # --- widths ------------------------------------------------------------------------------
137
+
138
+
139
+ def _literal(node: Any) -> int | None:
140
+ tokens = [t for t in _tokens(node) if not t.isMissing]
141
+ if len(tokens) == 1 and tokens[0].kind == TokenKind.IntegerLiteral:
142
+ try:
143
+ return int(tokens[0].rawText.replace("_", ""))
144
+ except ValueError:
145
+ return None
146
+ return None
147
+
148
+
149
+ def _width(dimensions: Sequence[Any]) -> int | None:
150
+ """The bits of packed `dimensions` when each is `[a:b]` with literal bounds; else None.
151
+
152
+ No packed dimension is one bit.
153
+ """
154
+ width = 1
155
+ for dim in dimensions:
156
+ spec = getattr(dim, "specifier", None)
157
+ selector = getattr(spec, "selector", None) if spec is not None else None
158
+ if selector is None or selector.kind != SyntaxKind.SimpleRangeSelect:
159
+ return None
160
+ left, right = _literal(selector.left), _literal(selector.right)
161
+ if left is None or right is None:
162
+ return None
163
+ width *= abs(left - right) + 1
164
+ return width
165
+
166
+
167
+ # --- ports ------------------------------------------------------------------------------
168
+
169
+
170
+ def _direction(token: Any) -> Direction | None:
171
+ if token is None or getattr(token, "isMissing", True):
172
+ return None
173
+ return _DIRECTIONS.get(str(token.rawText))
174
+
175
+
176
+ def _header_parts(header: Any) -> tuple[Direction | None, str, list[Any], bool]:
177
+ """Direction, type text, packed dimensions of a port header, and whether its type is
178
+ a built-in one (a named type, `pkg::t`, has a width only its definition knows)."""
179
+ direction = _direction(getattr(header, "direction", None))
180
+ data_type = getattr(header, "dataType", None)
181
+ dims = _nodes(getattr(data_type, "dimensions", None)) if data_type is not None else []
182
+ words: list[str] = []
183
+ if header.kind == SyntaxKind.NetPortHeader:
184
+ words.append(str(header.netType.rawText) if not header.netType.isMissing else "")
185
+ elif header.kind == SyntaxKind.VariablePortHeader:
186
+ var = header.varKeyword
187
+ if var is not None and not var.isMissing and var.rawText:
188
+ words.append(str(var.rawText))
189
+ elif header.kind == SyntaxKind.InterfacePortHeader:
190
+ words.append(_text(header))
191
+ return direction, " ".join(w for w in words if w), [], False
192
+ builtin = True
193
+ if data_type is not None:
194
+ keyword = getattr(data_type, "keyword", None)
195
+ if keyword is not None and not keyword.isMissing and keyword.rawText:
196
+ words.append(str(keyword.rawText))
197
+ elif data_type.kind != SyntaxKind.ImplicitType:
198
+ builtin = False
199
+ name = getattr(data_type, "name", None)
200
+ words.append(_text(name if name is not None else data_type))
201
+ signing = getattr(data_type, "signing", None)
202
+ if signing is not None and not signing.isMissing and signing.rawText:
203
+ words.append(str(signing.rawText))
204
+ return direction, " ".join(w for w in words if w), dims, builtin
205
+
206
+
207
+ def _port(header: Any, declarator: Any, inherited: DeclaredPort | None) -> DeclaredPort | None:
208
+ name_token = getattr(declarator, "name", None)
209
+ if name_token is None or name_token.isMissing or not name_token.rawText:
210
+ return None
211
+ if header is None and inherited is not None: # `input [7:0] a, b`: b keeps a's header
212
+ direction, type_text, packed, width = (
213
+ inherited.direction,
214
+ inherited.type,
215
+ inherited.packed,
216
+ inherited.width,
217
+ )
218
+ else:
219
+ direction, type_text, dims, builtin = (
220
+ _header_parts(header) if header is not None else (None, "", [], True)
221
+ )
222
+ packed = "".join(_text(d) for d in dims)
223
+ width = _width(dims) if builtin else None
224
+ unpacked = "".join(_text(d) for d in _nodes(getattr(declarator, "dimensions", None)))
225
+ return DeclaredPort(
226
+ name=str(name_token.rawText),
227
+ direction=direction,
228
+ type=type_text[:MAX_TEXT],
229
+ packed=packed[:MAX_TEXT],
230
+ unpacked=unpacked[:MAX_TEXT],
231
+ width=width,
232
+ )
233
+
234
+
235
+ def _ansi_ports(port_list: Any) -> Iterator[DeclaredPort]:
236
+ previous: DeclaredPort | None = None
237
+ for item in _nodes(port_list.ports):
238
+ if item.kind == SyntaxKind.ImplicitAnsiPort:
239
+ header = item.header
240
+ implicit = (
241
+ header.kind == SyntaxKind.VariablePortHeader
242
+ and _direction(header.direction) is None
243
+ and header.dataType.kind == SyntaxKind.ImplicitType
244
+ and not _nodes(header.dataType.dimensions)
245
+ )
246
+ port = _port(None if implicit else header, item.declarator, previous)
247
+ elif item.kind == SyntaxKind.ExplicitAnsiPort: # `.name(expr)`: the name only
248
+ name = item.name
249
+ port = (
250
+ DeclaredPort(name=str(name.rawText), direction=_direction(item.direction))
251
+ if name is not None and not name.isMissing
252
+ else None
253
+ )
254
+ else:
255
+ port = None
256
+ if port is not None:
257
+ previous = port
258
+ yield port
259
+
260
+
261
+ def _non_ansi_ports(port_list: Any, members: Iterable[Any]) -> Iterator[DeclaredPort]:
262
+ """A non-ANSI header lists names; the body's `PortDeclaration`s declare them."""
263
+ declared: dict[str, DeclaredPort] = {}
264
+ for member in members:
265
+ if member.kind != SyntaxKind.PortDeclaration:
266
+ continue # any other body item is never looked at
267
+ for declarator in _nodes(member.declarators):
268
+ port = _port(member.header, declarator, None)
269
+ if port is not None:
270
+ declared.setdefault(port.name, port)
271
+ for item in _nodes(port_list.ports):
272
+ if item.kind == SyntaxKind.ImplicitNonAnsiPort:
273
+ expr = item.expr
274
+ names = [t for t in _tokens(expr) if t.kind == TokenKind.Identifier]
275
+ if len(names) == 1:
276
+ name = str(names[0].rawText)
277
+ yield declared.get(name, DeclaredPort(name=name))
278
+ elif item.kind == SyntaxKind.ExplicitNonAnsiPort:
279
+ name = item.name
280
+ if name is not None and not name.isMissing:
281
+ yield DeclaredPort(name=str(name.rawText))
282
+
283
+
284
+ # --- parameters --------------------------------------------------------------------------
285
+
286
+
287
+ def _parameters(param_list: Any) -> Iterator[DeclaredParameter]:
288
+ if param_list is None:
289
+ return
290
+ for decl in _nodes(param_list.declarations):
291
+ keyword = getattr(decl, "keyword", None)
292
+ local = keyword is not None and keyword.kind == TokenKind.LocalParamKeyword
293
+ if decl.kind == SyntaxKind.TypeParameterDeclaration:
294
+ for item in _nodes(decl.declarators):
295
+ init = getattr(item, "assignment", None)
296
+ yield DeclaredParameter(
297
+ name=str(item.name.rawText),
298
+ kind="type",
299
+ default=_text(getattr(init, "type", None)) if init is not None else None,
300
+ )
301
+ elif decl.kind == SyntaxKind.ParameterDeclaration:
302
+ type_text = _text(decl.type)
303
+ for item in _nodes(decl.declarators):
304
+ init = getattr(item, "initializer", None)
305
+ yield DeclaredParameter(
306
+ name=str(item.name.rawText),
307
+ kind="localparam" if local else "parameter",
308
+ type=type_text,
309
+ default=_text(init.expr) if init is not None else None,
310
+ )
311
+
312
+
313
+ # --- the declaration -----------------------------------------------------------------------
314
+
315
+
316
+ def _modules(root: Any) -> Iterator[Any]:
317
+ """Every top-level module declaration of a tree (one whose root is a module, too)."""
318
+ if root.kind == SyntaxKind.ModuleDeclaration:
319
+ yield root
320
+ return
321
+ for member in getattr(root, "members", None) or ():
322
+ if isinstance(member, SyntaxNode) and member.kind == SyntaxKind.ModuleDeclaration:
323
+ yield member
324
+
325
+
326
+ def _declaration(node: Any) -> ModuleDeclaration:
327
+ header = node.header
328
+ params = list(_parameters(header.parameters))
329
+ ports: list[DeclaredPort] = []
330
+ if header.ports is not None and header.ports.kind == SyntaxKind.AnsiPortList:
331
+ ports = list(_ansi_ports(header.ports))
332
+ elif header.ports is not None and header.ports.kind == SyntaxKind.NonAnsiPortList:
333
+ ports = list(_non_ansi_ports(header.ports, node.members))
334
+ truncated = len(ports) > MAX_PORTS or len(params) > MAX_PARAMETERS
335
+ return ModuleDeclaration(
336
+ module=str(header.name.rawText),
337
+ parameters=tuple(params[:MAX_PARAMETERS]),
338
+ ports=tuple(ports[:MAX_PORTS]),
339
+ truncated=truncated,
340
+ )
341
+
342
+
343
+ def declarations_in_text(text: str, *, name: str = "source") -> tuple[ModuleDeclaration, ...]:
344
+ """Every module declaration in an RTL source text (headers only)."""
345
+ tree = SyntaxTree.fromText(text, name)
346
+ return tuple(_declaration(node) for node in _modules(tree.root))
347
+
348
+
349
+ def declaration_in_text(
350
+ text: str, module: str, *, name: str = "source"
351
+ ) -> ModuleDeclaration | None:
352
+ """The declaration of `module` in an RTL source text, or None if it is not there."""
353
+ for decl in declarations_in_text(text, name=name):
354
+ if decl.module == module:
355
+ return decl
356
+ return None
357
+
358
+
359
+ def extract_declaration(files: Iterable[Path], module: str | None) -> ModuleDeclaration | None:
360
+ """The declaration of `module` from the first of `files` that declares it.
361
+
362
+ With `module` None, the only module the files declare (None when they declare none
363
+ or several). Unreadable, missing or oversized files are skipped.
364
+ """
365
+ found: list[ModuleDeclaration] = []
366
+ for path in files:
367
+ try:
368
+ if not path.is_file() or path.stat().st_size > MAX_FILE_BYTES:
369
+ continue
370
+ text = path.read_text(encoding="utf-8", errors="replace")
371
+ except OSError:
372
+ continue
373
+ for decl in declarations_in_text(text, name=path.name):
374
+ if module is not None and decl.module == module:
375
+ return decl
376
+ found.append(decl)
377
+ if module is None and len({d.module for d in found}) == 1:
378
+ return found[0]
379
+ return None
380
+
381
+
382
+ __all__ = [
383
+ "MAX_PARAMETERS",
384
+ "MAX_PORTS",
385
+ "MAX_TEXT",
386
+ "DeclaredParameter",
387
+ "DeclaredPort",
388
+ "ModuleDeclaration",
389
+ "declaration_in_text",
390
+ "declarations_in_text",
391
+ "extract_declaration",
392
+ ]