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,626 @@
1
+ """The generator: emacs verilog-mode expands a wrapper source (`emacs_auto`, M2-12).
2
+
3
+ `expand` copies the source into a scratch directory outside the repository, as
4
+ `<wrapper>.sv`, and runs, through the `Runner` (never `subprocess` here)::
5
+
6
+ emacs -Q --batch --eval <settings> <scratch>/<wrapper>.sv -f verilog-batch-auto
7
+
8
+ `-Q` loads no init file, site file or site-lisp; `<settings>` turns backups off, and pins
9
+ every verilog-mode variable the expansion depends on (`PINNED`), to verilog-mode's
10
+ defaults except the three QSoC's flow sets too: `indent-tabs-mode nil`,
11
+ `verilog-auto-inst-param-value t` (instance parameters reach the declared widths),
12
+ `verilog-auto-star-expand nil`. The IP is found through `verilog-library-files`: its
13
+ `vendor_files`, copied (neutralised too) into the scratch directory, so the files in the
14
+ repository are neither written nor given to emacs. Before emacs runs, `lisp.scan` refuses
15
+ Lisp the source may not carry.
16
+
17
+ File-local variables are neutralised in the scratch copies (`neutralize`), not just turned
18
+ off: verilog-mode's batch command sets `enable-local-variables` and `enable-local-eval`
19
+ itself and rereads the `Local Variables:` block of the file it expands, and of the library
20
+ files it reads, so an `eval:` there would run (a test shows it). The copies keep their
21
+ line numbers, with `Local Variables:` and the `-*-` mode-line marks made inert; a
22
+ QSoC-style source that carries such a block (library flags, `indent-tabs-mode`) expands
23
+ without it.
24
+
25
+ The result is the expanded file, line endings `\\n`, exactly one trailing newline, under
26
+ a header that names its source and the source's sha256, stamped with the `generated`
27
+ check's marker (`chipgraph.checks.generated.stamp`): the same source and IP give the same
28
+ bytes. No emacs version goes in the file, so two machines with the same expansion write
29
+ the same file; the versions are in the tool's log.
30
+
31
+ Missing emacs, or an emacs without verilog-mode, is an `error` (infra) with the install
32
+ hint. A source emacs cannot expand (an IP module it cannot find, a template it cannot
33
+ read) is a `fail` whose issues point at the source's lines.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import hashlib
39
+ import re
40
+ import shutil
41
+ import tempfile
42
+ import time
43
+ from collections.abc import Mapping, Sequence
44
+ from dataclasses import dataclass, field
45
+ from pathlib import Path
46
+ from typing import Any
47
+
48
+ from chipgraph.checks._common import compute_idempotency_key, sha256_file
49
+ from chipgraph.checks.generated import stamp
50
+ from chipgraph.core.config.errors import ConfigError
51
+ from chipgraph.core.config.loader import load as load_profile
52
+ from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
53
+ from chipgraph.core.contracts.types import CheckStatus
54
+ from chipgraph.core.plugin_api.protocols import Runner
55
+ from chipgraph.core.plugin_api.types import ToolContext
56
+ from chipgraph.packs.digital_rtl.wrapper import lisp
57
+ from chipgraph.packs.digital_rtl.wrapper.common import (
58
+ GEN_RULE,
59
+ Wrapper,
60
+ WrapperError,
61
+ emacs_arg,
62
+ find_wrapper,
63
+ guarded,
64
+ inside,
65
+ paths,
66
+ timeout_arg,
67
+ )
68
+
69
+ EMACS = "emacs"
70
+ """The emacs executable, unless the tool's or check's `emacs` arg names another."""
71
+ TIMEOUT_S = 30.0
72
+ """How long one emacs run may take, unless `timeout_s` says otherwise (at most
73
+ `common.MAX_TIMEOUT_S`)."""
74
+ INSTALL_HINT = (
75
+ "install GNU Emacs, which ships verilog-mode: `brew install emacs` (macOS), "
76
+ "`apt install emacs-nox` (Debian, Ubuntu), `dnf install emacs-nox` (Fedora)"
77
+ )
78
+ _MISSING = 127
79
+
80
+ PINNED: tuple[tuple[str, str], ...] = (
81
+ ("verilog-auto-inst-param-value", "t"),
82
+ ("verilog-auto-star-expand", "nil"),
83
+ ("verilog-indent-level", "3"),
84
+ ("verilog-indent-level-module", "3"),
85
+ ("verilog-indent-level-declaration", "3"),
86
+ ("verilog-indent-level-behavioral", "3"),
87
+ ("verilog-indent-level-directive", "1"),
88
+ ("verilog-case-indent", "2"),
89
+ ("verilog-cexp-indent", "2"),
90
+ ("verilog-indent-lists", "t"),
91
+ ("verilog-auto-lineup", "'declarations"),
92
+ ("verilog-auto-endcomments", "t"),
93
+ ("verilog-minimum-comment-distance", "10"),
94
+ ("verilog-auto-inst-column", "40"),
95
+ ("verilog-auto-inst-vector", "t"),
96
+ ("verilog-auto-inst-sort", "nil"),
97
+ ("verilog-auto-arg-sort", "nil"),
98
+ ("verilog-auto-arg-format", "'packed"),
99
+ ("verilog-auto-inst-template-numbers", "nil"),
100
+ ("verilog-auto-inst-dot-name", "nil"),
101
+ ("verilog-auto-inst-interfaced-ports", "nil"),
102
+ ("verilog-auto-inst-param-value-type", "t"),
103
+ ("verilog-auto-declare-nettype", "nil"),
104
+ ("verilog-auto-wire-type", "nil"),
105
+ ("verilog-auto-wire-comment", "t"),
106
+ ("verilog-auto-template-warn-unused", "nil"),
107
+ ("verilog-auto-delete-trailing-whitespace", "nil"),
108
+ ("verilog-auto-tieoff-declaration", '"wire"'),
109
+ ("verilog-auto-reset-widths", "t"),
110
+ ("verilog-auto-reset-blocking-in-non", "t"),
111
+ ("verilog-auto-ignore-concat", "nil"),
112
+ ("verilog-auto-read-includes", "nil"),
113
+ ("verilog-auto-sense-include-inputs", "nil"),
114
+ ("verilog-auto-sense-defines-constant", "nil"),
115
+ ("verilog-auto-input-ignore-regexp", "nil"),
116
+ ("verilog-auto-output-ignore-regexp", "nil"),
117
+ ("verilog-auto-inout-ignore-regexp", "nil"),
118
+ ("verilog-auto-unused-ignore-regexp", "nil"),
119
+ ("verilog-auto-tieoff-ignore-regexp", "nil"),
120
+ ("verilog-typedef-regexp", "nil"),
121
+ ("verilog-active-low-regexp", "nil"),
122
+ ("verilog-case-fold", "nil"),
123
+ ("verilog-library-flags", "nil"),
124
+ ("verilog-library-directories", '\'(".")'),
125
+ ("verilog-library-extensions", '\'(".v" ".sv")'),
126
+ )
127
+ """verilog-mode's variables, as the generator sets them (Lisp values)."""
128
+
129
+ _PROBE = (
130
+ '(progn (require (quote verilog-mode)) (princ (format "chipgraph-emacs %s|%s\\n" '
131
+ "emacs-version verilog-mode-version)))"
132
+ )
133
+ _VERSION_RE = re.compile(r"chipgraph-emacs ([^|\s]+)\|(\S+)")
134
+ _ERROR_RE = re.compile(
135
+ r"^%Error:\s*(?P<file>[^\n:]+):(?P<line>\d+):\s*(?P<msg>[^\n]*)", re.MULTILINE
136
+ )
137
+ _HEADER_SOURCE_RE = re.compile(r"^// Generated by chipgraph from (\S+)\s*$")
138
+ _HEADER_SHA_RE = re.compile(r"^// source sha256 ([0-9a-f]{64})\s*$")
139
+
140
+
141
+ def lisp_string(text: str) -> str:
142
+ """`text` as an Emacs Lisp string literal."""
143
+ return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
144
+
145
+
146
+ def settings(library_files: Sequence[Path]) -> str:
147
+ """The `--eval` form: file-local variables off, no backups, `PINNED`, the library.
148
+
149
+ No backtrace on an error either: emacs prints one with `cl-prin1`, whose generic
150
+ functions build their dispatchers with `eval`, which `GUARD` would refuse."""
151
+ files = " ".join(lisp_string(str(p)) for p in library_files)
152
+ pinned = " ".join(f"{name} {value}" for name, value in PINNED)
153
+ return (
154
+ "(progn (setq-default indent-tabs-mode nil) "
155
+ "(setq enable-local-variables nil enable-local-eval nil enable-dir-local-variables nil "
156
+ "make-backup-files nil auto-save-default nil create-lockfiles nil "
157
+ "large-file-warning-threshold nil backtrace-on-error-noninteractive nil) "
158
+ f"(require (quote verilog-mode)) (setq {pinned} verilog-library-files (list {files})))"
159
+ )
160
+
161
+
162
+ GUARD_PREFIX = "chipgraph-guard:"
163
+ """How the guard's refusals start, in emacs's log and in the error it signals."""
164
+
165
+
166
+ def _elisp_regexp(pattern: str) -> str:
167
+ """A Python regexp of `lisp.SETQ_VARIABLE`'s shape (anchors, `(?:`, `|`) in Emacs syntax."""
168
+ out = pattern.replace("(?:", "\\(?:").replace("|", "\\|")
169
+ out = re.sub(r"(?<!\\)\)", r"\\)", out)
170
+ return "\\`" + out.removeprefix("^").removesuffix("$") + "\\'"
171
+
172
+
173
+ def _guard() -> str:
174
+ functions = " ".join(lisp_string(name) for name in sorted(lisp.FUNCTIONS))
175
+ regexp_functions = " ".join(lisp_string(name) for name in sorted(lisp.REGEXP_FUNCTIONS))
176
+ setq = lisp_string(_elisp_regexp(lisp.SETQ_VARIABLE.pattern))
177
+ return f"""(progn
178
+ (defvar chipgraph-guard--functions (list {functions}))
179
+ (defvar chipgraph-guard--regexp-functions (list {regexp_functions}))
180
+ (defvar chipgraph-guard--setq {setq})
181
+ (defun chipgraph-guard--refuse (why form)
182
+ (let ((msg (format "{GUARD_PREFIX} %s: %S" why form)))
183
+ (message "%s" msg)
184
+ (error "%s" msg)))
185
+ (defun chipgraph-guard--check (form setq depth)
186
+ (cond
187
+ ((> depth {lisp.MAX_DEPTH}) (chipgraph-guard--refuse "nested too deep" form))
188
+ ((or (stringp form) (integerp form) (null form) (eq form t) (keywordp form)) t)
189
+ ((symbolp form)
190
+ (unless (string-prefix-p "vl-" (symbol-name form))
191
+ (chipgraph-guard--refuse "a variable other than vl-*" form)))
192
+ ((not (and (consp form) (proper-list-p form)))
193
+ (chipgraph-guard--refuse "not a string, integer, symbol or call" form))
194
+ ((eq (car form) (quote quote))
195
+ (unless (and (= (length form) 2) (symbolp (nth 1 form)))
196
+ (chipgraph-guard--refuse "quoted data other than a symbol" form)))
197
+ ((and setq (eq (car form) (quote setq)))
198
+ (let ((rest (cdr form)))
199
+ (when (or (null rest) (= (% (length rest) 2) 1))
200
+ (chipgraph-guard--refuse "a setq without pairs" form))
201
+ (while rest
202
+ (unless (and (symbolp (car rest))
203
+ (let ((case-fold-search nil))
204
+ (string-match-p chipgraph-guard--setq (symbol-name (car rest)))))
205
+ (chipgraph-guard--refuse "a setq of a variable other than verilog-mode's name filters"
206
+ form))
207
+ (chipgraph-guard--check (nth 1 rest) nil (1+ depth))
208
+ (setq rest (nthcdr 2 rest)))))
209
+ ((not (and (symbolp (car form))
210
+ (member (symbol-name (car form)) chipgraph-guard--functions)))
211
+ (chipgraph-guard--refuse "a call of a function not allowed" form))
212
+ (t
213
+ (when (and (member (symbol-name (car form)) chipgraph-guard--regexp-functions)
214
+ (not (stringp (nth 1 form))))
215
+ (chipgraph-guard--refuse "a regexp that is not a literal string" form))
216
+ (dolist (arg (cdr form)) (chipgraph-guard--check arg nil (1+ depth))))))
217
+ (defun chipgraph-guard--eval (orig form &rest args)
218
+ (save-match-data (chipgraph-guard--check form nil 0))
219
+ (apply orig form args))
220
+ (defun chipgraph-guard--eval-region (orig start end &rest args)
221
+ (when (nth 1 args) (chipgraph-guard--refuse "eval-region with a read function" (nth 1 args)))
222
+ (save-match-data
223
+ (save-excursion
224
+ (save-restriction
225
+ (narrow-to-region start end)
226
+ (goto-char (point-min))
227
+ (let ((more t) form)
228
+ (while more
229
+ (condition-case err
230
+ (setq form (read (current-buffer)))
231
+ (end-of-file (setq more nil))
232
+ (error (chipgraph-guard--refuse "unreadable Lisp" err)))
233
+ (when more (chipgraph-guard--check form t 0)))))))
234
+ (apply orig start end args))
235
+ (advice-add (quote eval) :around (function chipgraph-guard--eval))
236
+ (advice-add (quote eval-region) :around (function chipgraph-guard--eval-region)))"""
237
+
238
+
239
+ GUARD = _guard()
240
+ """The `--eval` form after `settings`: `:around` advice on `eval` and `eval-region` that
241
+ checks, inside emacs, every form verilog-mode evaluates (each form of an `AUTO_LISP` region,
242
+ read with `read` narrowed to it; each template function) against `lisp`'s allow-list:
243
+ strings, integers, `nil`, `t`, keywords, `vl-*` variables, `(quote <symbol>)` and calls of
244
+ `lisp.FUNCTIONS` (a regexp function's regexp a literal string); `setq` only in a region and
245
+ only of `lisp.SETQ_VARIABLE`. Anything else is an error whose message starts with
246
+ `GUARD_PREFIX` (also printed to the log, in case verilog-mode catches it)."""
247
+
248
+
249
+ def emacs_argv(emacs: str, scratch_file: Path, library_files: Sequence[Path]) -> list[str]:
250
+ """The command line that expands `scratch_file` in place."""
251
+ return [
252
+ emacs,
253
+ "-Q",
254
+ "--batch",
255
+ "--eval",
256
+ settings(library_files),
257
+ "--eval",
258
+ GUARD,
259
+ str(scratch_file),
260
+ "-f",
261
+ "verilog-batch-auto",
262
+ ]
263
+
264
+
265
+ # On `lisp.skeleton(text)`, whose offsets are the text's: every spelling emacs may take as
266
+ # Lisp to run or as a file-local variable mark (a superset of what it matches).
267
+ _INERT_RE = re.compile(
268
+ r"(?P<lisp>auto_lisp(?P<ws>\s*)\()"
269
+ r"|autoinsert(?P<insert>lisp|last)"
270
+ r"|(?P<local>local(?P<w1>\s*)variables(?P<w2>\s*):)"
271
+ r"|(?P<mode>-\*-)"
272
+ )
273
+
274
+
275
+ def neutralize(text: str, *, keep_accepted: bool = False) -> str:
276
+ """`text` with its Lisp and file-local variables made inert, line for line.
277
+
278
+ Matched on `lisp.skeleton(text)`, so a spelling with look-alike letters is caught too:
279
+ `AUTO_LISP(` becomes `AUTO_LISP (` (verilog-mode looks for `AUTO_LISP(`), unless
280
+ `keep_accepted` and it is the exact form the scanner accepts, `/*AUTO_LISP(` (the
281
+ source's copy, which `lisp.scan` has already read; never a vendor file's);
282
+ `AUTOINSERTLISP`/`AUTOINSERTLAST` become `AUTOINSERT LISP`/`AUTOINSERT LAST`;
283
+ `Local Variables:` becomes `Local Variables (ignored):` and `-*-` becomes `- * -`.
284
+ Whitespace in a match is kept, so the line numbers are.
285
+ """
286
+ out: list[str] = []
287
+ last = 0
288
+ for match in _INERT_RE.finditer(lisp.skeleton(text)):
289
+ start, end = match.span()
290
+ if match.group("lisp") is not None:
291
+ if (
292
+ keep_accepted
293
+ and not match.group("ws")
294
+ and text.startswith(lisp.ACCEPTED_AUTO_LISP, start - 2)
295
+ and start >= 2
296
+ ):
297
+ continue
298
+ new = "AUTO_LISP " + text[match.start("ws") : match.end("ws")] + "("
299
+ elif match.group("insert") is not None:
300
+ new = "AUTOINSERT " + match.group("insert").upper()
301
+ elif match.group("local") is not None:
302
+ w1 = text[match.start("w1") : match.end("w1")] or " "
303
+ w2 = text[match.start("w2") : match.end("w2")]
304
+ new = f"Local{w1}Variables{w2} (ignored):"
305
+ else:
306
+ new = "- * -"
307
+ out.append(text[last:start])
308
+ out.append(new)
309
+ last = end
310
+ out.append(text[last:])
311
+ return "".join(out)
312
+
313
+
314
+ def normalize(text: str) -> str:
315
+ """Line endings `\\n` and exactly one trailing newline."""
316
+ text = text.replace("\r\n", "\n").replace("\r", "\n")
317
+ return text.rstrip("\n") + "\n"
318
+
319
+
320
+ def source_sha(text: str) -> str:
321
+ """The sha256 of a source's text, as the header records it (normalised first)."""
322
+ return hashlib.sha256(normalize(text).encode("utf-8")).hexdigest()
323
+
324
+
325
+ def header(src_rel: str, src_text: str) -> str:
326
+ """The comment a generated wrapper starts with (after the marker): its source, the
327
+ source's sha256, and that it must not be edited."""
328
+ return (
329
+ f"// Generated by chipgraph from {src_rel}\n"
330
+ f"// source sha256 {source_sha(src_text)}\n"
331
+ f"// by `{GEN_RULE}` (tool emacs_auto): emacs verilog-mode expanded its AUTO\n"
332
+ "// comments. Do not edit this file: edit the source and run the generator again;\n"
333
+ "// the `wrap_check` check fails when the two disagree.\n"
334
+ )
335
+
336
+
337
+ HEADER_LINES = 5
338
+ """Lines of `header`."""
339
+
340
+
341
+ def render(body: str, src_rel: str, src_text: str) -> str:
342
+ """The generated file: the marker, the header, then the expansion."""
343
+ return stamp(header(src_rel, src_text) + normalize(body))
344
+
345
+
346
+ def generated_from(text: str) -> tuple[str, str] | None:
347
+ """The (source path, source sha256) a generated file's header names, if any."""
348
+ lines = text.splitlines()[:8]
349
+ for i, line in enumerate(lines[:-1]):
350
+ source = _HEADER_SOURCE_RE.match(line)
351
+ sha = _HEADER_SHA_RE.match(lines[i + 1])
352
+ if source and sha:
353
+ return source.group(1), sha.group(1)
354
+ return None
355
+
356
+
357
+ def body_of(text: str) -> str:
358
+ """A generated file without its marker and header: the expansion itself."""
359
+ lines = text.splitlines(keepends=True)
360
+ for i, line in enumerate(lines[:3]):
361
+ if _HEADER_SOURCE_RE.match(line):
362
+ return "".join(lines[i + HEADER_LINES :])
363
+ return text
364
+
365
+
366
+ @dataclass
367
+ class Expansion:
368
+ """What one emacs run gave: the expanded text, or why there is none."""
369
+
370
+ ok: bool
371
+ text: str = ""
372
+ issues: list[Issue] = field(default_factory=list)
373
+ infra: bool = False
374
+ """True when emacs could not run at all (missing, no verilog-mode, timed out)."""
375
+ versions: tuple[str, str] | None = None
376
+ log: str = ""
377
+
378
+
379
+ def _hint(what: str, emacs: str) -> Issue:
380
+ return Issue(rule="wrap.emacs_missing", msg=f"{what} ({emacs}): {INSTALL_HINT}")
381
+
382
+
383
+ async def probe(runner: Runner, cwd: Path, emacs: str = EMACS) -> Expansion:
384
+ """Whether `emacs` runs and has verilog-mode; `versions` are (emacs, verilog-mode)."""
385
+ try:
386
+ result = await runner.run([emacs, "-Q", "--batch", "--eval", _PROBE], cwd=cwd, timeout_s=60)
387
+ except (FileNotFoundError, PermissionError, OSError) as exc:
388
+ return Expansion(ok=False, infra=True, issues=[_hint(f"emacs cannot run: {exc}", emacs)])
389
+ log = result.stdout + result.stderr
390
+ if result.returncode == _MISSING:
391
+ return Expansion(
392
+ ok=False, infra=True, issues=[_hint("emacs is not on PATH", emacs)], log=log
393
+ )
394
+ match = _VERSION_RE.search(result.stdout)
395
+ if result.returncode != 0 or match is None:
396
+ msg = "emacs runs but has no verilog-mode" if "verilog-mode" in log else "emacs failed"
397
+ tail = log.strip().splitlines()[-1:] or [""]
398
+ issue = _hint(f"{msg}: {tail[0][:200]}", emacs).model_copy(
399
+ update={"rule": "wrap.verilog_mode_missing"}
400
+ )
401
+ return Expansion(ok=False, infra=True, issues=[issue], log=log)
402
+ return Expansion(ok=True, versions=(match.group(1), match.group(2)), log=log)
403
+
404
+
405
+ async def expand(
406
+ runner: Runner,
407
+ root: Path,
408
+ src_rel: str,
409
+ vendor_files: Sequence[str],
410
+ *,
411
+ name: str,
412
+ emacs: str = EMACS,
413
+ timeout_s: float = TIMEOUT_S,
414
+ text: str | None = None,
415
+ ) -> Expansion:
416
+ """Expand the source `src_rel` (or `text`, as if it were that file) of wrapper `name`."""
417
+ try:
418
+ source = text if text is not None else inside(root, src_rel).read_text(encoding="utf-8")
419
+ library = [inside(root, rel).resolve() for rel in vendor_files]
420
+ except (OSError, UnicodeDecodeError, WrapperError) as exc:
421
+ return Expansion(ok=False, issues=[Issue(file=src_rel, rule="wrap.read", msg=str(exc))])
422
+ missing = [rel for rel, path in zip(vendor_files, library, strict=True) if not path.is_file()]
423
+ if missing:
424
+ msg = f"the IP file(s) {', '.join(missing)} do not exist (the wrapper's vendor_files)"
425
+ return Expansion(ok=False, infra=True, issues=[Issue(rule="wrap.vendor_missing", msg=msg)])
426
+ refused = [p for p in lisp.scan(source) if not p.note]
427
+ if refused:
428
+ issues = [
429
+ Issue(
430
+ file=src_rel, line=p.line, rule="wrap.lisp", msg=f"{p.message}; emacs was not run"
431
+ )
432
+ for p in refused
433
+ ]
434
+ return Expansion(ok=False, issues=issues)
435
+
436
+ found = await probe(runner, root, emacs)
437
+ if not found.ok:
438
+ return found
439
+ scratch = Path(tempfile.mkdtemp(prefix="chipgraph-wrap-"))
440
+ try:
441
+ target = scratch / f"{name}.sv"
442
+ target.write_text(neutralize(source, keep_accepted=True), encoding="utf-8", newline="")
443
+ copies = []
444
+ for index, path in enumerate(library):
445
+ copy = scratch / "ip" / str(index) / path.name
446
+ copy.parent.mkdir(parents=True)
447
+ text = path.read_text(encoding="utf-8", errors="replace")
448
+ copy.write_text(neutralize(text), encoding="utf-8", newline="")
449
+ copies.append(copy)
450
+ argv = emacs_argv(emacs, target, copies)
451
+ result = await runner.run(argv, cwd=scratch, timeout_s=timeout_s)
452
+ log = (result.stdout + result.stderr).replace(str(scratch) + "/", "")
453
+ for copy, rel in zip(copies, vendor_files, strict=True):
454
+ log = log.replace(str(copy.relative_to(scratch)), rel)
455
+ if result.timed_out:
456
+ issue = Issue(rule="wrap.emacs_timeout", msg=f"emacs ran over {timeout_s} s")
457
+ return Expansion(ok=False, infra=True, issues=[issue], versions=found.versions, log=log)
458
+ if result.returncode != 0 or GUARD_PREFIX in log:
459
+ return Expansion(
460
+ ok=False,
461
+ issues=_emacs_errors(log, src_rel, target.name),
462
+ versions=found.versions,
463
+ log=log,
464
+ )
465
+ expanded = target.read_text(encoding="utf-8")
466
+ finally:
467
+ shutil.rmtree(scratch, ignore_errors=True)
468
+ return Expansion(ok=True, text=normalize(expanded), versions=found.versions, log=log)
469
+
470
+
471
+ def _emacs_errors(log: str, src_rel: str, scratch_name: str) -> list[Issue]:
472
+ """emacs's `%Error: <file>:<line>: <msg>` lines, pointed at the source."""
473
+ issues: list[Issue] = []
474
+ guarded_lines = sorted(
475
+ {entry.strip() for entry in log.splitlines() if entry.strip().startswith(GUARD_PREFIX)}
476
+ )
477
+ for entry in guarded_lines:
478
+ issues.append(
479
+ Issue(
480
+ file=src_rel,
481
+ rule="wrap.lisp",
482
+ msg=f"emacs refused Lisp the expansion tried to run: {entry[:300]}",
483
+ )
484
+ )
485
+ if issues:
486
+ return issues
487
+ seen: set[tuple[int, str]] = set()
488
+ for match in _ERROR_RE.finditer(log):
489
+ line, msg = int(match.group("line")), match.group("msg").strip()
490
+ if (line, msg) in seen:
491
+ continue
492
+ seen.add((line, msg))
493
+ here = Path(match.group("file")).name == scratch_name
494
+ hint = (
495
+ " (is the IP module named as in its declaration, and is its file in the "
496
+ "wrapper's vendor_files?)"
497
+ if "locate" in msg
498
+ else ""
499
+ )
500
+ issues.append(
501
+ Issue(
502
+ file=src_rel if here else None,
503
+ line=line if here and line >= 1 else None,
504
+ rule="wrap.emacs",
505
+ msg=f"emacs verilog-mode could not expand the source: {msg}{hint}",
506
+ )
507
+ )
508
+ if not issues:
509
+ tail = "\n".join(log.strip().splitlines()[-6:])
510
+ issues.append(
511
+ Issue(file=src_rel, rule="wrap.emacs", msg=f"emacs failed to expand the source: {tail}")
512
+ )
513
+ return issues
514
+
515
+
516
+ def wrapper_for(root: Path, params: Mapping[str, str]) -> Wrapper:
517
+ """The profile's wrapper the params name (`WrapperError` when there is none)."""
518
+ try:
519
+ resolved = load_profile(root)
520
+ except ConfigError as exc:
521
+ raise WrapperError(f"the profile cannot be read: {exc}") from exc
522
+ if resolved is None:
523
+ raise WrapperError("no .chipgraph.yml: the wrappers are listed in the profile")
524
+ return find_wrapper(resolved.profile, params)
525
+
526
+
527
+ def _result(
528
+ spec: CheckSpec,
529
+ status: CheckStatus,
530
+ issues: Sequence[Issue],
531
+ start: float,
532
+ files: Sequence[tuple[str, str]] = (),
533
+ log: str = "",
534
+ ) -> CheckResult:
535
+ return CheckResult(
536
+ check_id=spec.id,
537
+ status=status,
538
+ issues=tuple(issues),
539
+ log_tail=log[-4000:],
540
+ duration_s=time.monotonic() - start,
541
+ idempotency_key=compute_idempotency_key(spec.id, spec.args, files),
542
+ )
543
+
544
+
545
+ def _str_arg(args: Mapping[str, Any], key: str, default: str | None) -> str | None:
546
+ value = args.get(key, default)
547
+ if value is not None and not isinstance(value, str):
548
+ raise WrapperError(f"args[{key!r}] must be a string")
549
+ return value
550
+
551
+
552
+ class EmacsAutoTool:
553
+ """The gen tool `emacs_auto`: `<wrapper>.src.sv` -> `<wrapper>.sv` (see the module).
554
+
555
+ Args (all optional): `src`, `out` (path templates with `{block}`, `{wrapper}`;
556
+ default `rtl/emacs/{wrapper}.src.sv`, `rtl/{wrapper}.sv`), `emacs` (the executable),
557
+ `timeout_s`. The IP's files are the profile's `blocks.<block>.wrappers` entry.
558
+ """
559
+
560
+ name = "emacs_auto"
561
+ capability = "gen"
562
+
563
+ async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
564
+ start = time.monotonic()
565
+ root = ctx.repo_root
566
+ try:
567
+ wrapper = wrapper_for(root, ctx.params)
568
+ src_rel, out_rel = paths(
569
+ wrapper, _str_arg(spec.args, "src", None), _str_arg(spec.args, "out", None)
570
+ )
571
+ emacs = emacs_arg(_str_arg(spec.args, "emacs", EMACS) or EMACS)
572
+ timeout = timeout_arg(spec.args.get("timeout_s"), TIMEOUT_S)
573
+ src_path, out_path = guarded(root, src_rel, "src"), guarded(root, out_rel, "out")
574
+ except (WrapperError, TypeError, ValueError) as exc:
575
+ return _result(spec, "error", [Issue(rule="wrap.args", msg=str(exc))], start)
576
+ if not src_path.is_file():
577
+ issue = Issue(file=src_rel, rule="wrap.src_missing", msg=f"no source at {src_rel}")
578
+ return _result(spec, "fail", [issue], start)
579
+ source = src_path.read_text(encoding="utf-8")
580
+ files = [(src_rel, sha256_file(src_path))]
581
+ expansion = await expand(
582
+ ctx.runner,
583
+ root,
584
+ src_rel,
585
+ wrapper.cfg.vendor_files,
586
+ name=wrapper.name,
587
+ emacs=emacs,
588
+ timeout_s=timeout,
589
+ text=source,
590
+ )
591
+ log = expansion.log
592
+ if expansion.versions is not None:
593
+ log = f"emacs {expansion.versions[0]}, verilog-mode {expansion.versions[1]}\n{log}"
594
+ if not expansion.ok:
595
+ status: CheckStatus = "error" if expansion.infra else "fail"
596
+ return _result(spec, status, expansion.issues, start, files, log)
597
+ text = render(expansion.text, src_rel, source)
598
+ out_path.parent.mkdir(parents=True, exist_ok=True)
599
+ if not out_path.is_file() or out_path.read_text(encoding="utf-8") != text:
600
+ out_path.write_text(text, encoding="utf-8", newline="")
601
+ files.append((out_rel, sha256_file(out_path)))
602
+ note = Issue(severity="info", file=out_rel, rule="wrap.generated", msg=f"wrote {out_rel}")
603
+ return _result(spec, "pass", [note], start, files, log)
604
+
605
+
606
+ __all__ = [
607
+ "EMACS",
608
+ "GUARD",
609
+ "GUARD_PREFIX",
610
+ "INSTALL_HINT",
611
+ "PINNED",
612
+ "TIMEOUT_S",
613
+ "EmacsAutoTool",
614
+ "Expansion",
615
+ "body_of",
616
+ "emacs_argv",
617
+ "expand",
618
+ "generated_from",
619
+ "header",
620
+ "normalize",
621
+ "probe",
622
+ "render",
623
+ "settings",
624
+ "source_sha",
625
+ "wrapper_for",
626
+ ]