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.
- chipgraph/__init__.py +7 -0
- chipgraph/adapters/__init__.py +1 -0
- chipgraph/adapters/format/__init__.py +6 -0
- chipgraph/adapters/format/__main__.py +16 -0
- chipgraph/adapters/format/_yaml_lines.py +54 -0
- chipgraph/adapters/format/chip_yaml.py +323 -0
- chipgraph/adapters/format/qsoc_contract.py +246 -0
- chipgraph/adapters/llm/__init__.py +58 -0
- chipgraph/adapters/llm/_budget.py +173 -0
- chipgraph/adapters/llm/_errors.py +50 -0
- chipgraph/adapters/llm/_nda.py +30 -0
- chipgraph/adapters/llm/_retry.py +85 -0
- chipgraph/adapters/llm/anthropic.py +247 -0
- chipgraph/adapters/llm/decide_backend.py +179 -0
- chipgraph/adapters/llm/fake.py +87 -0
- chipgraph/adapters/llm/guard.py +68 -0
- chipgraph/adapters/parser/__init__.py +22 -0
- chipgraph/adapters/parser/cocotb.py +304 -0
- chipgraph/adapters/parser/generic_regex.py +75 -0
- chipgraph/adapters/parser/verible.py +55 -0
- chipgraph/adapters/parser/verilator.py +46 -0
- chipgraph/adapters/review/__init__.py +7 -0
- chipgraph/adapters/review/file.py +123 -0
- chipgraph/adapters/review/github.py +886 -0
- chipgraph/adapters/runner/__init__.py +7 -0
- chipgraph/adapters/runner/local.py +122 -0
- chipgraph/adapters/runtime/__init__.py +5 -0
- chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
- chipgraph/adapters/runtime/claude_code/agents.py +158 -0
- chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
- chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
- chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
- chipgraph/adapters/runtime/claude_code/loop.py +342 -0
- chipgraph/adapters/runtime/claude_code/previous.py +419 -0
- chipgraph/adapters/runtime/claude_code/review.py +492 -0
- chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
- chipgraph/adapters/runtime/claude_code/service.py +2285 -0
- chipgraph/adapters/tool/__init__.py +7 -0
- chipgraph/adapters/tool/cmd.py +229 -0
- chipgraph/adapters/tool/edalize.py +773 -0
- chipgraph/adapters/tool/filelist.py +259 -0
- chipgraph/adapters/tool/pyslang.py +485 -0
- chipgraph/adapters/vcs/__init__.py +1 -0
- chipgraph/adapters/vcs/git.py +521 -0
- chipgraph/app/__init__.py +10 -0
- chipgraph/app/baseline.py +339 -0
- chipgraph/app/build.py +332 -0
- chipgraph/app/checks.py +202 -0
- chipgraph/app/context.py +191 -0
- chipgraph/app/errors.py +11 -0
- chipgraph/app/executors.py +79 -0
- chipgraph/app/findings.py +203 -0
- chipgraph/app/ingest.py +523 -0
- chipgraph/checks/__init__.py +52 -0
- chipgraph/checks/_cdc_analyze.py +272 -0
- chipgraph/checks/_cdc_elaborate.py +455 -0
- chipgraph/checks/_common.py +268 -0
- chipgraph/checks/_cross.py +96 -0
- chipgraph/checks/_model.py +64 -0
- chipgraph/checks/_naming_pyslang.py +235 -0
- chipgraph/checks/_naming_rules.py +127 -0
- chipgraph/checks/_xref_syntax.py +260 -0
- chipgraph/checks/cdc_struct.py +235 -0
- chipgraph/checks/connect.py +353 -0
- chipgraph/checks/cross_chip.py +454 -0
- chipgraph/checks/duplicate.py +153 -0
- chipgraph/checks/filelist.py +323 -0
- chipgraph/checks/format.py +268 -0
- chipgraph/checks/generated.py +151 -0
- chipgraph/checks/hardcode.py +233 -0
- chipgraph/checks/layout.py +155 -0
- chipgraph/checks/naming.py +311 -0
- chipgraph/checks/plan_check.py +901 -0
- chipgraph/checks/ports_diff.py +203 -0
- chipgraph/checks/spec_schema.py +277 -0
- chipgraph/checks/style.py +910 -0
- chipgraph/checks/tb_static.py +599 -0
- chipgraph/checks/trace.py +224 -0
- chipgraph/checks/vendor_guard.py +369 -0
- chipgraph/checks/wrap_check.py +486 -0
- chipgraph/cli/__init__.py +1688 -0
- chipgraph/cli/next_steps.py +137 -0
- chipgraph/core/__init__.py +1 -0
- chipgraph/core/config/__init__.py +93 -0
- chipgraph/core/config/defaults.py +14 -0
- chipgraph/core/config/errors.py +32 -0
- chipgraph/core/config/loader.py +766 -0
- chipgraph/core/config/models.py +845 -0
- chipgraph/core/config/templates.py +340 -0
- chipgraph/core/contracts/__init__.py +75 -0
- chipgraph/core/contracts/_paths.py +26 -0
- chipgraph/core/contracts/agent.py +49 -0
- chipgraph/core/contracts/artifact.py +68 -0
- chipgraph/core/contracts/check.py +76 -0
- chipgraph/core/contracts/event.py +70 -0
- chipgraph/core/contracts/export.py +128 -0
- chipgraph/core/contracts/finding.py +171 -0
- chipgraph/core/contracts/gate.py +50 -0
- chipgraph/core/contracts/rule.py +177 -0
- chipgraph/core/contracts/types.py +45 -0
- chipgraph/core/engine/__init__.py +70 -0
- chipgraph/core/engine/agent_rule.py +1362 -0
- chipgraph/core/engine/baseline.py +283 -0
- chipgraph/core/engine/decide.py +462 -0
- chipgraph/core/engine/fanout.py +1358 -0
- chipgraph/core/engine/gate.py +170 -0
- chipgraph/core/engine/graph.py +647 -0
- chipgraph/core/engine/records.py +60 -0
- chipgraph/core/engine/rules.py +92 -0
- chipgraph/core/engine/scheduler.py +637 -0
- chipgraph/core/engine/select.py +1239 -0
- chipgraph/core/engine/targets.py +342 -0
- chipgraph/core/model/__init__.py +98 -0
- chipgraph/core/model/entities.py +244 -0
- chipgraph/core/model/export.py +16 -0
- chipgraph/core/model/ingest.py +693 -0
- chipgraph/core/model/json_value.py +4 -0
- chipgraph/core/model/keys.py +95 -0
- chipgraph/core/model/model.py +153 -0
- chipgraph/core/model/provenance.py +27 -0
- chipgraph/core/model/query.py +583 -0
- chipgraph/core/model/registry.py +80 -0
- chipgraph/core/model/relations.py +61 -0
- chipgraph/core/model/store.py +279 -0
- chipgraph/core/plugin_api/__init__.py +81 -0
- chipgraph/core/plugin_api/local.py +167 -0
- chipgraph/core/plugin_api/pack.py +159 -0
- chipgraph/core/plugin_api/protocols.py +177 -0
- chipgraph/core/plugin_api/registry.py +111 -0
- chipgraph/core/plugin_api/types.py +94 -0
- chipgraph/core/runtime/__init__.py +41 -0
- chipgraph/core/runtime/executor.py +79 -0
- chipgraph/core/runtime/queue.py +565 -0
- chipgraph/core/runtime/roles/__init__.py +91 -0
- chipgraph/core/runtime/roles/data/author.md +41 -0
- chipgraph/core/runtime/roles/data/critic.md +56 -0
- chipgraph/core/runtime/roles/data/planner.md +38 -0
- chipgraph/core/runtime/roles/data/researcher.md +37 -0
- chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
- chipgraph/core/runtime/roles/data/tb-author.md +48 -0
- chipgraph/core/runtime/roles/data/triage.md +34 -0
- chipgraph/core/runtime/roles/policy.py +299 -0
- chipgraph/core/runtime/roles/registry.py +52 -0
- chipgraph/core/runtime/roles/review.py +275 -0
- chipgraph/core/runtime/roles/schema.py +82 -0
- chipgraph/core/runtime/roles/skills.py +134 -0
- chipgraph/core/runtime/roles/spec.py +232 -0
- chipgraph/core/state/__init__.py +1 -0
- chipgraph/core/state/artifacts.py +156 -0
- chipgraph/core/state/backend.py +117 -0
- chipgraph/core/state/findings.py +151 -0
- chipgraph/core/state/handoff.py +411 -0
- chipgraph/core/state/idempotency.py +59 -0
- chipgraph/core/state/journal.py +238 -0
- chipgraph/core/state/layout.py +82 -0
- chipgraph/core/state/lock.py +195 -0
- chipgraph/core/state/trace.py +225 -0
- chipgraph/learn/__init__.py +96 -0
- chipgraph/learn/draft.py +187 -0
- chipgraph/learn/infer.py +580 -0
- chipgraph/learn/models.py +145 -0
- chipgraph/learn/try_run.py +228 -0
- chipgraph/mcp/__init__.py +12 -0
- chipgraph/mcp/model_tools.py +167 -0
- chipgraph/mcp/server.py +787 -0
- chipgraph/orgs/qnsc/naming-v1.yml +113 -0
- chipgraph/orgs/qnsc/profile.yml +5 -0
- chipgraph/packs/__init__.py +8 -0
- chipgraph/packs/assist/__init__.py +16 -0
- chipgraph/packs/assist/ask/__init__.py +58 -0
- chipgraph/packs/assist/ask/_db.py +57 -0
- chipgraph/packs/assist/ask/_project.py +230 -0
- chipgraph/packs/assist/ask/answer.py +266 -0
- chipgraph/packs/assist/ask/check.py +182 -0
- chipgraph/packs/assist/ask/contract.py +115 -0
- chipgraph/packs/assist/ask/documents.py +157 -0
- chipgraph/packs/assist/ask/fts.py +80 -0
- chipgraph/packs/assist/ask/retrieve.py +331 -0
- chipgraph/packs/assist/audit/__init__.py +383 -0
- chipgraph/packs/assist/pack.yml +13 -0
- chipgraph/packs/assist/triage/__init__.py +64 -0
- chipgraph/packs/assist/triage/contract.py +94 -0
- chipgraph/packs/assist/triage/parse.py +371 -0
- chipgraph/packs/assist/triage/question.py +384 -0
- chipgraph/packs/assist/triage/report.py +160 -0
- chipgraph/packs/assist/triage/rules.py +447 -0
- chipgraph/packs/assist/triage/run.py +224 -0
- chipgraph/packs/digital_rtl/__init__.py +19 -0
- chipgraph/packs/digital_rtl/layout.py +144 -0
- chipgraph/packs/digital_rtl/pack.yml +25 -0
- chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
- chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
- chipgraph/packs/digital_rtl/plan/context.py +82 -0
- chipgraph/packs/digital_rtl/plan/expand.py +94 -0
- chipgraph/packs/digital_rtl/plan/model.py +150 -0
- chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
- chipgraph/packs/digital_rtl/plan/show.py +176 -0
- chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
- chipgraph/packs/digital_rtl/pr/body.py +165 -0
- chipgraph/packs/digital_rtl/pr/gate.py +110 -0
- chipgraph/packs/digital_rtl/pr/open.py +397 -0
- chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
- chipgraph/packs/digital_rtl/rtl/context.py +272 -0
- chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
- chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
- chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
- chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
- chipgraph/packs/digital_rtl/rules/review.yml +31 -0
- chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
- chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
- chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
- chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
- chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
- chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
- chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
- chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
- chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
- chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
- chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
- chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
- chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
- chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
- chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
- chipgraph/packs/dv/__init__.py +15 -0
- chipgraph/packs/dv/declaration.py +392 -0
- chipgraph/packs/dv/interface.py +596 -0
- chipgraph/packs/dv/pack.yml +14 -0
- chipgraph/packs/dv/rules/tb_module.yml +42 -0
- chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
- chipgraph/packs/spec_core/__init__.py +9 -0
- chipgraph/packs/spec_core/extract/__init__.py +12 -0
- chipgraph/packs/spec_core/extract/_markdown.py +371 -0
- chipgraph/packs/spec_core/extract/_text.py +116 -0
- chipgraph/packs/spec_core/extract/mas.py +975 -0
- chipgraph/packs/spec_core/gen/__init__.py +11 -0
- chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
- chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
- chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
- chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
- chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
- chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
- chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
- chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
- chipgraph/packs/spec_core/pack.yml +6 -0
- chipgraph/py.typed +0 -0
- chipgraph-0.1.0.dist-info/METADATA +53 -0
- chipgraph-0.1.0.dist-info/RECORD +252 -0
- chipgraph-0.1.0.dist-info/WHEEL +4 -0
- chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
- 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
|
+
]
|