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,504 @@
1
+ """The Emacs Lisp a wrapper source may carry, and nothing more.
2
+
3
+ verilog-mode evaluates Lisp written in the source: `AUTO_LISP(...)`, a template's
4
+ `@"(...)"` function, `AUTOINSERTLISP(...)`. The source is written by an agent, so this
5
+ module treats it as hostile: emacs must not become a shell. Before the generator runs
6
+ emacs, `scan` reads the **raw text** (no comment or string is skipped: verilog-mode does
7
+ not skip them either) and refuses the source unless every piece of Lisp in it is proved
8
+ harmless. Anything the scanner cannot prove harmless is refused; an error in the scanner
9
+ is a refusal too.
10
+
11
+ Where emacs finds Lisp, and what the scanner accepts:
12
+
13
+ - `AUTO_LISP(`: verilog-mode finds it **anywhere** in the buffer (a `//` comment, a string,
14
+ the middle of a block comment), in any letter case, and hands the region it spans to
15
+ `eval-region`, which evaluates every form in it. The scanner looks for
16
+ `auto_lisp\\s*\\(` in any case, on a skeleton of the text where every non-ASCII letter
17
+ that case-folds to an ASCII one (long s U+017F, Kelvin sign U+212A, dotless i U+0131,
18
+ fullwidth forms) is that ASCII letter, and accepts only the exact form `/*AUTO_LISP(`
19
+ ... `)*/`, whose text from `(` to the first `*/` is exactly one form,
20
+ `(setq <var> <value> ...)`, where `<var>` is one of verilog-mode's name filters (`SETQ_VARIABLE`).
21
+ - `AUTOINSERTLISP` and `AUTOINSERTLAST`, in any case, anywhere: refused.
22
+ - A template function `@"..."`: the scanner finds it with verilog-mode's own regexp and
23
+ applies its transform (`\\"` becomes `"`) before reading. The Lisp must be one form, may
24
+ not contain `@` (verilog-mode replaces it with the template number) nor a backslash other
25
+ than a quoted back-reference (`'\\1`, which pastes a name the template's regexp matched),
26
+ and `@@"` is refused: verilog-mode splices a function's result back into the template and
27
+ searches it again from the start, so a result that holds `@"` would run as Lisp in the
28
+ next round. No allowed function can build `@` or `"` from literal data: `format` is not
29
+ allowed, nor is quoted data other than a symbol.
30
+
31
+ The forms themselves may use only strings, integers, quoted symbols, `nil`/`t`, the
32
+ template's `vl-*` variables and calls of the side-effect-free string and arithmetic
33
+ functions in `FUNCTIONS` (none takes a function argument; `string-match` only with a
34
+ literal regexp without nested repetition). The text of a form is restricted so that the
35
+ Elisp reader and verilog-mode's Verilog syntax table see the same form: outside strings only
36
+ letters, digits, whitespace, `(`, `)`, `'`, `"` and the symbol characters `-_+*/%<>=:!`
37
+ (no `;`, `?`, `\\`, `[`, `]`, `{`, `}`, `#`, `` ` ``, `,`, `.`, `@`, `|`, nor `//`, `/*`, `*/`);
38
+ inside strings only printable ASCII with the escape `\\\\` (in `AUTO_LISP`; template strings
39
+ have none). Integers are plain decimal; a list nests at most `MAX_DEPTH` deep.
40
+
41
+ File-local variables are the other way to run Lisp: the generator makes them inert in the
42
+ copies it hands to emacs (`emacs.neutralize`), so a `Local Variables:` block (and its
43
+ `eval:`) or a `-*-` mode line is ignored, and `scan` reports it as a note.
44
+
45
+ What the scanner cannot see: a template function that reads a `vl-*` variable returns text
46
+ verilog-mode took from the IP's or the source's declarations (port names, widths with their
47
+ parameter values). The generator therefore also runs emacs with a guard that refuses, inside
48
+ emacs, any form outside the same allow-list (`emacs.GUARD`).
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ import functools
54
+ import re
55
+ import unicodedata
56
+ from dataclasses import dataclass
57
+
58
+ FUNCTIONS = frozenset(
59
+ {
60
+ "concat",
61
+ "downcase",
62
+ "upcase",
63
+ "capitalize",
64
+ "substring",
65
+ "string-to-number",
66
+ "number-to-string",
67
+ "if",
68
+ "when",
69
+ "unless",
70
+ "and",
71
+ "or",
72
+ "not",
73
+ "null",
74
+ "equal",
75
+ "eq",
76
+ "string=",
77
+ "string-equal",
78
+ "string-match",
79
+ "string-match-p",
80
+ "match-string",
81
+ "string-prefix-p",
82
+ "string-suffix-p",
83
+ "regexp-quote",
84
+ "symbol-name",
85
+ "length",
86
+ "progn",
87
+ "+",
88
+ "-",
89
+ "*",
90
+ "/",
91
+ "%",
92
+ "1+",
93
+ "1-",
94
+ "max",
95
+ "min",
96
+ "abs",
97
+ }
98
+ )
99
+ """Functions a form may call: pure string and arithmetic functions, and `if`-like forms.
100
+
101
+ `format` is not one of them: with `%c` it builds any character from numbers, so a template
102
+ function's result could hold a new `@"..."` that verilog-mode would evaluate next."""
103
+
104
+ REGEXP_FUNCTIONS = frozenset({"string-match", "string-match-p"})
105
+ """Functions whose first argument is a regexp: it must be a literal without nested repetition."""
106
+
107
+ VARIABLES = frozenset(
108
+ {
109
+ "nil",
110
+ "t",
111
+ "vl-name",
112
+ "vl-width",
113
+ "vl-bits",
114
+ "vl-mbits",
115
+ "vl-dir",
116
+ "vl-cell-type",
117
+ "vl-cell-name",
118
+ "vl-memory",
119
+ "vl-modport",
120
+ }
121
+ )
122
+ """Variables a form may read: verilog-mode's template variables."""
123
+
124
+ SETQ_VARIABLE = re.compile(
125
+ r"^(?:verilog-auto-[a-z]+-ignore-regexp|verilog-typedef-regexp|verilog-active-low-regexp)$"
126
+ )
127
+ """Variables an `AUTO_LISP` form may set: verilog-mode's name filters."""
128
+
129
+ MAX_DEPTH = 32
130
+ """How deep a form's lists may nest."""
131
+
132
+ ACCEPTED_AUTO_LISP = "/*AUTO_LISP("
133
+ """The one way a source may write `AUTO_LISP`: this, then the form's rest, then `*/`."""
134
+
135
+ # On the folded skeleton (see `skeleton`): every spelling emacs may take as these, and more.
136
+ _KEYWORDS = re.compile(r"auto_lisp\s*\(|autoinsertlisp|autoinsertlast")
137
+ # verilog-mode's template-function regexp `@"\(\([^\\"]\|\\.\)*\)"` (`.` stops at a newline).
138
+ _TEMPLATE_LISP = re.compile(r'@"((?:[^\\"]|\\.)*)"')
139
+ _TEMPLATE_START = re.compile(r'@"')
140
+ # On the skeleton; a superset of what emacs's case-folded search matches (see emacs.py).
141
+ _LOCAL_VARIABLES = re.compile(r"local\s*variables\s*:|-\*-")
142
+ _INTEGER = re.compile(r"^[-+]?[0-9]+$")
143
+ _BACKREF = re.compile(r"\\[0-9]")
144
+ _SYMBOL = re.compile(r"^[A-Za-z0-9_+*/%<>=:!-]+$")
145
+ _DIGIT_SYMBOLS = frozenset({"1+", "1-"})
146
+ _SPACE = frozenset(" \t\r\n")
147
+ _SYMBOL_CHARS = frozenset(
148
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+*/%<>=:!-"
149
+ )
150
+ _STRING_CHARS = frozenset(chr(c) for c in range(0x20, 0x7F))
151
+ _FOLD_TARGETS = frozenset("abcdefghijklmnopqrstuvwxyz0123456789_()*/:-")
152
+ _NOT_ASCII = "\x7f"
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class LispProblem:
157
+ """A form the generator refuses to hand to emacs, or a note about the source."""
158
+
159
+ line: int
160
+ message: str
161
+ note: bool = False
162
+ """True for a note (the source is not refused)."""
163
+
164
+
165
+ class _ParseError(Exception):
166
+ pass
167
+
168
+
169
+ @dataclass(frozen=True)
170
+ class _Sym:
171
+ name: str
172
+
173
+
174
+ @dataclass(frozen=True)
175
+ class _Quoted:
176
+ datum: object
177
+
178
+
179
+ @dataclass(frozen=True)
180
+ class _Backref:
181
+ """`'\\N` in a template function: the name the template's regexp group N matched."""
182
+
183
+ group: int
184
+
185
+
186
+ @functools.cache
187
+ def _fold(char: str) -> str:
188
+ """The ASCII character `char` may stand for under any case folding or compatibility
189
+ form, lowercased; `_NOT_ASCII` when there is none."""
190
+ for form in (
191
+ char.lower(),
192
+ char.upper(),
193
+ char.casefold(),
194
+ unicodedata.normalize("NFKC", char),
195
+ unicodedata.normalize("NFKD", char),
196
+ unicodedata.normalize("NFKC", char).casefold(),
197
+ ):
198
+ head = form[:1].lower()
199
+ if head in _FOLD_TARGETS:
200
+ return head
201
+ return _NOT_ASCII
202
+
203
+
204
+ def skeleton(text: str) -> str:
205
+ """`text` folded to lowercase ASCII, one character for one (see the module)."""
206
+ return "".join(c.lower() if c.isascii() else _fold(c) for c in text)
207
+
208
+
209
+ def _line(text: str, offset: int) -> int:
210
+ return text.count("\n", 0, offset) + 1
211
+
212
+
213
+ def _read_string(text: str, i: int, *, escapes: frozenset[str]) -> tuple[str, int]:
214
+ """The string literal opening at `text[i] == '"'`; (its value, end index)."""
215
+ out: list[str] = []
216
+ i += 1
217
+ while i < len(text):
218
+ char = text[i]
219
+ if char == '"':
220
+ return "".join(out), i + 1
221
+ if char == "\\":
222
+ nxt = text[i + 1 : i + 2]
223
+ if nxt not in escapes:
224
+ raise _ParseError("a string holds a backslash escape that is not allowed")
225
+ out.append(nxt)
226
+ i += 2
227
+ continue
228
+ if char not in _STRING_CHARS:
229
+ raise _ParseError(f"a string holds the character {_shown(char)}")
230
+ out.append(char)
231
+ i += 1
232
+ raise _ParseError("a string is not closed")
233
+
234
+
235
+ def _shown(char: str) -> str:
236
+ return repr(char) if char.isprintable() and char.isascii() else f"U+{ord(char):04X}"
237
+
238
+
239
+ def _read(text: str, *, template: bool) -> object:
240
+ """The one form `text` holds (only whitespace may follow it); iterative, at most
241
+ `MAX_DEPTH` deep. `_ParseError` for anything outside the restricted syntax."""
242
+ escapes = frozenset() if template else frozenset("\\")
243
+ n = len(text)
244
+ i = 0
245
+ # Each frame: (items, quote marks pending before the list).
246
+ stack: list[tuple[list[object], int]] = []
247
+ quotes = 0
248
+ result: object = None
249
+ done = False
250
+ while True:
251
+ while i < n and text[i] in _SPACE:
252
+ i += 1
253
+ if done:
254
+ if i < n:
255
+ raise _ParseError("something follows the form (only one form is allowed)")
256
+ return result
257
+ if i >= n:
258
+ raise _ParseError("the form ends early" if not stack else "a list is not closed")
259
+ char = text[i]
260
+ datum: object
261
+ if char == "(":
262
+ if len(stack) >= MAX_DEPTH:
263
+ raise _ParseError(f"lists nest deeper than {MAX_DEPTH}")
264
+ stack.append(([], quotes))
265
+ quotes = 0
266
+ i += 1
267
+ continue
268
+ if char == ")":
269
+ if not stack or quotes:
270
+ raise _ParseError("an unmatched ')'")
271
+ items, quotes = stack.pop()
272
+ datum = items
273
+ i += 1
274
+ elif char == "'":
275
+ quotes += 1
276
+ i += 1
277
+ continue
278
+ elif char == '"':
279
+ datum, i = _read_string(text, i, escapes=escapes)
280
+ elif template and char == "\\" and quotes and _BACKREF.match(text, i):
281
+ datum = _Backref(int(text[i + 1]))
282
+ i += 2
283
+ if i < n and text[i] in _SYMBOL_CHARS:
284
+ raise _ParseError("a back-reference is followed by more of a name")
285
+ quotes -= 1
286
+ datum = _Quoted(datum)
287
+ else:
288
+ if char in "/*" and text[i : i + 2] in ("//", "/*", "*/"):
289
+ raise _ParseError("a comment mark (`//`, `/*`, `*/`)")
290
+ if char in "`,#":
291
+ raise _ParseError(f"the reader syntax {char!r} (only quote is allowed)")
292
+ if char not in _SYMBOL_CHARS:
293
+ raise _ParseError(f"the character {_shown(char)} (outside a string)")
294
+ j = i
295
+ while j < n and text[j] in _SYMBOL_CHARS:
296
+ if text[j] in "/*" and text[j : j + 2] in ("//", "/*", "*/"):
297
+ raise _ParseError("a comment mark (`//`, `/*`, `*/`)")
298
+ j += 1
299
+ if j < n and text[j] not in _SPACE and text[j] not in "()":
300
+ raise _ParseError(f"the character {_shown(text[j])} (outside a string)")
301
+ atom = text[i:j]
302
+ i = j
303
+ if _INTEGER.match(atom):
304
+ datum = int(atom)
305
+ elif atom[0].isdigit() and atom not in _DIGIT_SYMBOLS:
306
+ raise _ParseError(f"`{atom}` is neither an integer nor a name")
307
+ elif not _SYMBOL.match(atom):
308
+ raise _ParseError(f"`{atom}` is not a name")
309
+ else:
310
+ datum = _Sym(atom)
311
+ while quotes:
312
+ datum = _Quoted(datum)
313
+ quotes -= 1
314
+ if stack:
315
+ stack[-1][0].append(datum)
316
+ else:
317
+ result, done = datum, True
318
+
319
+
320
+ def _nested_repetition(regexp: str) -> bool:
321
+ """Whether an Emacs regexp repeats a group that itself repeats (`\\(a+\\)*`)."""
322
+ groups: list[bool] = []
323
+ i = 0
324
+ while i < len(regexp):
325
+ char = regexp[i]
326
+ if char == "\\" and regexp[i + 1 : i + 2] == "(":
327
+ groups.append(False)
328
+ i += 2
329
+ continue
330
+ if char == "\\" and regexp[i + 1 : i + 2] == ")":
331
+ inner = groups.pop() if groups else False
332
+ i += 2
333
+ after = regexp[i : i + 1]
334
+ if inner and (after in ("*", "+", "?") or regexp[i : i + 2] == "\\{"):
335
+ return True
336
+ if groups:
337
+ groups[-1] = groups[-1] or inner
338
+ continue
339
+ if (char in "*+?" or regexp[i : i + 2] == "\\{") and groups:
340
+ groups[-1] = True
341
+ i += 2 if char == "\\" else 1
342
+ return False
343
+
344
+
345
+ def _check(datum: object, *, setq: bool) -> str | None:
346
+ """Why `datum` may not be evaluated, or None when it may (iterative)."""
347
+ todo: list[tuple[object, bool]] = [(datum, setq)]
348
+ while todo:
349
+ item, may_setq = todo.pop()
350
+ why = _check_one(item, may_setq, todo)
351
+ if why is not None:
352
+ return why
353
+ return None
354
+
355
+
356
+ def _check_one(datum: object, setq: bool, todo: list[tuple[object, bool]]) -> str | None:
357
+ if isinstance(datum, (str, int)):
358
+ return None
359
+ if isinstance(datum, _Quoted):
360
+ if isinstance(datum.datum, (_Sym, _Backref)):
361
+ return None
362
+ return "quotes data other than a name (a quoted list or string can build any text)"
363
+ if isinstance(datum, _Backref):
364
+ return "uses a back-reference that is not quoted (write '\\N)"
365
+ if isinstance(datum, _Sym):
366
+ name = datum.name
367
+ if name in VARIABLES or (name.startswith(":") and len(name) > 1):
368
+ return None
369
+ return f"reads the variable `{name}`, which is not allowed"
370
+ if not isinstance(datum, list):
371
+ return "holds something that is not a plain value"
372
+ if not datum:
373
+ return None
374
+ head = datum[0]
375
+ if not isinstance(head, _Sym):
376
+ return "calls something that is not a function name"
377
+ if head.name == "quote":
378
+ if len(datum) == 2 and isinstance(datum[1], _Sym):
379
+ return None
380
+ return "quotes data other than a name (a quoted list or string can build any text)"
381
+ if head.name == "setq":
382
+ if not setq:
383
+ return "sets a variable (`setq` is allowed in AUTO_LISP only)"
384
+ pairs = datum[1:]
385
+ if not pairs or len(pairs) % 2:
386
+ return "has a `setq` without a value"
387
+ for var, value in zip(pairs[::2], pairs[1::2], strict=True):
388
+ if not isinstance(var, _Sym) or not SETQ_VARIABLE.match(var.name):
389
+ shown = var.name if isinstance(var, _Sym) else "a non-symbol"
390
+ return (
391
+ f"sets `{shown}`; AUTO_LISP may set only verilog-mode's name "
392
+ "filters (verilog-auto-<kind>-ignore-regexp, verilog-typedef-regexp)"
393
+ )
394
+ todo.append((value, False))
395
+ return None
396
+ if head.name not in FUNCTIONS:
397
+ return f"calls `{head.name}`, which is not allowed"
398
+ if head.name in REGEXP_FUNCTIONS:
399
+ regexp = datum[1] if len(datum) > 1 else None
400
+ if not isinstance(regexp, str):
401
+ return f"calls `{head.name}` with a regexp that is not a literal string"
402
+ if _nested_repetition(regexp):
403
+ return f"calls `{head.name}` with a regexp that repeats a repeated group"
404
+ todo.extend((arg, False) for arg in datum[1:])
405
+ return None
406
+
407
+
408
+ def _auto_lisp(text: str, start: int) -> str | None:
409
+ """Why the `AUTO_LISP` at `start` (the `a` of the keyword) may not run, or None."""
410
+ begin = start - 2
411
+ if begin < 0 or not text.startswith(ACCEPTED_AUTO_LISP, begin):
412
+ return (
413
+ "AUTO_LISP is allowed only as `/*AUTO_LISP(setq ...)*/` (exact case, no space, "
414
+ "in a block comment of its own): emacs evaluates it wherever it appears"
415
+ )
416
+ open_paren = begin + len(ACCEPTED_AUTO_LISP) - 1
417
+ end = text.find("*/", open_paren)
418
+ if end < 0:
419
+ return "AUTO_LISP cannot be read: its comment is not closed"
420
+ try:
421
+ datum = _read(text[open_paren:end], template=False)
422
+ except _ParseError as exc:
423
+ return f"AUTO_LISP cannot be read: {exc}"
424
+ why = _check(datum, setq=True)
425
+ return None if why is None else f"AUTO_LISP {why}"
426
+
427
+
428
+ def _template(text: str, start: int) -> str | None:
429
+ """Why the template function `@"` at `start` may not run, or None."""
430
+ if start > 0 and text[start - 1] == "@":
431
+ return 'a template function @"..." follows an `@`: its result could form a new one'
432
+ match = _TEMPLATE_LISP.match(text, start)
433
+ if match is None:
434
+ return 'a template function @"..." cannot be read: its string is not closed'
435
+ code = match.group(1).replace('\\"', '"')
436
+ if "@" in code:
437
+ return 'a template function @"..." holds `@` (emacs replaces it with the template number)'
438
+ if "\\" in _BACKREF.sub("", code):
439
+ return 'a template function @"..." holds a backslash other than `\\"` or a back-reference'
440
+ try:
441
+ datum = _read(code, template=True)
442
+ except _ParseError as exc:
443
+ return f'a template function @"..." cannot be read: {exc}'
444
+ why = _check(datum, setq=False)
445
+ return None if why is None else f'a template function @"..." {why}'
446
+
447
+
448
+ def _scan(text: str) -> list[LispProblem]:
449
+ problems: list[LispProblem] = []
450
+ folded = skeleton(text)
451
+ for match in _KEYWORDS.finditer(folded):
452
+ line = _line(text, match.start())
453
+ word = match.group(0)
454
+ if word.startswith("autoinsert"):
455
+ problems.append(
456
+ LispProblem(
457
+ line,
458
+ f"{word.upper()} inserts the result of Lisp code: not allowed in a wrapper "
459
+ "source, in any letter case; use AUTO_TEMPLATE lines instead",
460
+ )
461
+ )
462
+ continue
463
+ why = _auto_lisp(text, match.start())
464
+ if why is not None:
465
+ problems.append(LispProblem(line, why))
466
+ for match in _TEMPLATE_START.finditer(text):
467
+ why = _template(text, match.start())
468
+ if why is not None:
469
+ problems.append(LispProblem(_line(text, match.start()), why))
470
+ for match in _LOCAL_VARIABLES.finditer(folded):
471
+ problems.append(
472
+ LispProblem(
473
+ _line(text, match.start()),
474
+ "file-local variables (`Local Variables:`, `-*-`) are ignored: the generator "
475
+ "pins verilog-mode's settings and reads the IP from the profile's vendor_files",
476
+ note=True,
477
+ )
478
+ )
479
+ return problems
480
+
481
+
482
+ def scan(text: str) -> list[LispProblem]:
483
+ """Every Lisp form of a wrapper source that may not run, and the notes (see above).
484
+
485
+ Fails closed: if the scan itself fails, the result is one refusal.
486
+ """
487
+ try:
488
+ problems = _scan(text)
489
+ except Exception as exc: # any scanner failure is a refusal
490
+ problems = [LispProblem(1, f"the Lisp scanner could not read the source ({exc!r})")]
491
+ return sorted(problems, key=lambda p: (p.line, p.message))
492
+
493
+
494
+ __all__ = [
495
+ "ACCEPTED_AUTO_LISP",
496
+ "FUNCTIONS",
497
+ "MAX_DEPTH",
498
+ "REGEXP_FUNCTIONS",
499
+ "SETQ_VARIABLE",
500
+ "VARIABLES",
501
+ "LispProblem",
502
+ "scan",
503
+ "skeleton",
504
+ ]
@@ -0,0 +1,60 @@
1
+ """`foreach: wrappers` and `foreach: wrappers.vendor`: one rule instance per wrapper.
2
+
3
+ Both expand to one entry per `blocks.<block>.wrappers` entry of the profile (blocks
4
+ sorted, wrappers in profile order), with params `block` and `wrapper`. They differ in
5
+ the extra inputs each entry carries:
6
+
7
+ - `wrappers` (the author's rule): the wrapper's `exemplar`, when the profile names one.
8
+ Never the IP's files: the author sees the IP's port declaration only, which its
9
+ context reads from them (`wrapper.context`), never their text;
10
+ - `wrappers.vendor` (the generator's rule): the IP's `vendor_files`, so a change to the
11
+ IP makes the generated wrapper stale and it is regenerated (and checked) again.
12
+
13
+ `WrapperForeach` adds these two selectors to a project's resolver and hands every other
14
+ selector to it.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from chipgraph.core.config.models import Profile
20
+ from chipgraph.core.engine.graph import EntryForeachResolver, ForeachEntry, ForeachResolver
21
+ from chipgraph.packs.digital_rtl.wrapper.common import all_wrappers
22
+
23
+ WRAPPERS = "wrappers"
24
+ """The selector of the author's rule."""
25
+ WRAPPERS_VENDOR = "wrappers.vendor"
26
+ """The selector of the generator's rule."""
27
+ SELECTORS = frozenset({WRAPPERS, WRAPPERS_VENDOR})
28
+
29
+
30
+ def wrapper_entries(profile: Profile, expr: str) -> list[ForeachEntry]:
31
+ """The entries of selector `expr` (one of `SELECTORS`) for the profile."""
32
+ entries: list[ForeachEntry] = []
33
+ for wrapper in all_wrappers(profile):
34
+ if expr == WRAPPERS_VENDOR:
35
+ inputs = tuple(wrapper.cfg.vendor_files)
36
+ else:
37
+ inputs = (wrapper.cfg.exemplar,) if wrapper.cfg.exemplar else ()
38
+ entries.append(ForeachEntry(params=wrapper.params, inputs=inputs))
39
+ return entries
40
+
41
+
42
+ class WrapperForeach:
43
+ """A project's resolver with the `wrappers` selectors added (see the module)."""
44
+
45
+ def __init__(self, inner: ForeachResolver, profile: Profile) -> None:
46
+ self.inner = inner
47
+ self.profile = profile
48
+
49
+ def expand(self, expr: str) -> list[dict[str, str]]:
50
+ return [dict(entry.params) for entry in self.expand_entries(expr)]
51
+
52
+ def expand_entries(self, expr: str) -> list[ForeachEntry]:
53
+ if expr in SELECTORS:
54
+ return wrapper_entries(self.profile, expr)
55
+ if isinstance(self.inner, EntryForeachResolver):
56
+ return self.inner.expand_entries(expr)
57
+ return [ForeachEntry(params=p) for p in self.inner.expand(expr)]
58
+
59
+
60
+ __all__ = ["SELECTORS", "WRAPPERS", "WRAPPERS_VENDOR", "WrapperForeach", "wrapper_entries"]
@@ -0,0 +1,41 @@
1
+ # A wrapper around a third-party (vendor) IP, in the emacs verilog-mode AUTO style
2
+ # (DESIGN.md 8.2; task M2-12).
3
+ #
4
+ # For each wrapper of the profile (`foreach: wrappers`, params `block` and `wrapper`;
5
+ # `blocks.<block>.wrappers: [{name, vendor_module, vendor_files, exemplar?}]`), an
6
+ # `rtl-author` task writes the wrapper's SOURCE: its port groups (`/*AUTOINPUT("re")*/`,
7
+ # ...), the `AUTO_TEMPLATE` that maps each IP port to a project name, the instance with
8
+ # its parameters and `/*AUTOINST*/`, `/*AUTOWIRE*/`; never the expanded port list, and
9
+ # never a change to the IP's files. Its context (`rtl.wrapper`) holds the IP module's
10
+ # port declaration only (pyslang, header; the IP's files are not inputs, so their text
11
+ # never reaches the author), the wrapper's ports from the Design Model or the plan when
12
+ # the project knows them, the naming rules, the exemplar wrapper when the profile names
13
+ # one (an input) and how the generator expands the source. The author has no shell and
14
+ # cannot run emacs: on submit `wrap_check` expands the source in a scratch copy and its
15
+ # issues (with the expanded lines) are the redo text of the next try.
16
+ #
17
+ # `digital-rtl/wrapper_gen` then writes the expanded `<wrapper>.sv` from this source.
18
+ # A project with another layout (QSoC: `design/{block}/rtl/emacs/`) writes its own copy
19
+ # of both rules in its pack, with the same `src`/`out` templates in the generator's args
20
+ # and the `wrap_check` adapter's.
21
+ #
22
+ # Checks: `wrap_check` (adapter `{ use: wrap_check }`; on the author's try it validates
23
+ # the expansion; it compares with the committed file only once one was generated from
24
+ # this source).
25
+ rule: wrapper
26
+ kind: agent
27
+ role: rtl-author
28
+ description: >-
29
+ Write the source of the wrapper around the vendor IP: the port groups, an AUTO_TEMPLATE
30
+ that maps every IP port to the project's names (tie-offs and unused outputs included),
31
+ and the instance with its parameters and /*AUTOINST*/; the generator expands it.
32
+ foreach: wrappers
33
+ inputs:
34
+ - model: "block/{block}"
35
+ - model: "interface/{wrapper}"
36
+ outputs:
37
+ - "rtl/emacs/{wrapper}.src.sv"
38
+ skills: [rtl/wrapper-auto, org/naming]
39
+ checks: [wrap_check]
40
+ budget:
41
+ tries: 3
@@ -0,0 +1,35 @@
1
+ # The generated wrapper: emacs verilog-mode expands a wrapper's source (task M2-12).
2
+ #
3
+ # For each wrapper (`foreach: wrappers.vendor`: the same instances as `digital-rtl/wrapper`,
4
+ # with the IP's `vendor_files` as inputs, so a change to the IP regenerates the wrapper),
5
+ # the tool `emacs_auto` runs `emacs -Q --batch <copy> -f verilog-batch-auto` on a scratch
6
+ # copy of the source, with verilog-mode's variables pinned, and writes `<wrapper>.sv`
7
+ # under a "generated, do not edit" header that names the source and its sha256 (with the
8
+ # `generated` check's marker). Same source, same IP: same bytes. Missing emacs or
9
+ # verilog-mode is an `error` with the install hint.
10
+ #
11
+ # Checks, each an adapter id of the profile:
12
+ # wrap_check the regeneration equals the written file, and the expanded instance
13
+ # connects every IP port through the template (`{ use: wrap_check }`)
14
+ # format the project's formatter, for a `files` arg only: without one it is skipped
15
+ # for a wrapper task (the layout of a generated wrapper is verilog-mode's,
16
+ # and reformatting it would make it differ from what the generator writes)
17
+ # lint the project's lint of the block (tinysoc: `make lint BLOCK={block}`, which
18
+ # also lints the block's wrappers)
19
+ # naming the project's naming rule, on the generated file too
20
+ rule: wrapper_gen
21
+ kind: gen
22
+ description: >-
23
+ Expand the wrapper's source with emacs verilog-mode (AUTOINST, AUTOWIRE, AUTOINPUT, ...)
24
+ into the wrapper the build compiles, marked as generated.
25
+ foreach: wrappers.vendor
26
+ inputs:
27
+ - artifact: "rtl/emacs/{wrapper}.src.sv"
28
+ outputs:
29
+ - "rtl/{wrapper}.sv"
30
+ run:
31
+ use: emacs_auto
32
+ args:
33
+ src: "rtl/emacs/{wrapper}.src.sv"
34
+ out: "rtl/{wrapper}.sv"
35
+ checks: [wrap_check, format, lint, naming]