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