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,272 @@
|
|
|
1
|
+
"""Turn one module's elaborated facts into clock-domain crossings.
|
|
2
|
+
|
|
3
|
+
Conservative, structural analysis (no timing, no value analysis), per DESIGN.md 4.8
|
|
4
|
+
layer 1:
|
|
5
|
+
|
|
6
|
+
1. Each `always_ff` has a clock *root* -- the module input clock its event samples. Every
|
|
7
|
+
register (a variable it assigns) belongs to that root's domain.
|
|
8
|
+
2. A register's next-state expression reads other signals; each read is expanded through
|
|
9
|
+
combinational logic (`assign`, `always_comb`) to the set of *source registers* it
|
|
10
|
+
ultimately depends on.
|
|
11
|
+
3. A source register in domain A feeding a register in domain B != A is a crossing.
|
|
12
|
+
4. The crossing is *synchronised* when the path goes through a synchroniser cell: a sync
|
|
13
|
+
instance's output is a fresh signal that carries no source domain, so expansion stops
|
|
14
|
+
there (its source set is empty). A register reading a sync output is therefore never a
|
|
15
|
+
crossing, while the same value taken raw or through plain combinational logic still is.
|
|
16
|
+
5. When the source is a module input with no known domain, or the path leaves the analysed
|
|
17
|
+
level (deeper hierarchy), the result is `info` ("unknown"/"not analysed"), never an
|
|
18
|
+
error.
|
|
19
|
+
|
|
20
|
+
One level of hierarchy is followed. A non-sync child instance with a resolvable clock
|
|
21
|
+
input is modelled as a set of *pseudo-registers*: each of its outputs is a flop in the
|
|
22
|
+
domain its clock port maps to (the parent clock root the port connects to), sampling the
|
|
23
|
+
child's data inputs. That makes a crossing wired between two sibling instances through the
|
|
24
|
+
parent visible, and lets the child's output feed a further crossing. A child with no
|
|
25
|
+
resolvable clock is treated as combinational passthrough; a sync child breaks the path.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from enum import Enum
|
|
32
|
+
|
|
33
|
+
from chipgraph.checks._cdc_elaborate import Loc, ModuleFacts, SubInstance
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Kind(Enum):
|
|
37
|
+
"""What a resolved crossing is: a real one, or one we could not decide."""
|
|
38
|
+
|
|
39
|
+
UNSYNCHRONISED = "unsynchronised"
|
|
40
|
+
UNKNOWN_DOMAIN = "unknown_domain"
|
|
41
|
+
NOT_ANALYSED = "not_analysed"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class Crossing:
|
|
46
|
+
"""One resolved clock-domain crossing at a destination register."""
|
|
47
|
+
|
|
48
|
+
kind: Kind
|
|
49
|
+
dst_name: str
|
|
50
|
+
dst_domain: str
|
|
51
|
+
loc: Loc
|
|
52
|
+
detail: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class _Dst:
|
|
57
|
+
"""A crossing destination: a real register or a child instance's pseudo-register."""
|
|
58
|
+
|
|
59
|
+
name: str
|
|
60
|
+
domain: str | None
|
|
61
|
+
loc: Loc
|
|
62
|
+
reads: frozenset[str]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def analyse(facts: ModuleFacts) -> list[Crossing]:
|
|
66
|
+
"""Every crossing at a destination of `facts`, deterministically ordered.
|
|
67
|
+
|
|
68
|
+
A destination in domain B whose fan-in reaches a source register in domain A != B is a
|
|
69
|
+
crossing (unsynchronised), unless every A-domain path is broken by a synchroniser
|
|
70
|
+
(which yields an empty source set for its output).
|
|
71
|
+
"""
|
|
72
|
+
domains, dsts = _domains_and_destinations(facts)
|
|
73
|
+
resolver = _Resolver(facts, domains)
|
|
74
|
+
|
|
75
|
+
crossings: list[Crossing] = []
|
|
76
|
+
for dst in sorted(dsts, key=lambda d: (d.loc.line or 0, d.name)):
|
|
77
|
+
dst_domain = dst.domain
|
|
78
|
+
if dst_domain is None:
|
|
79
|
+
# The destination itself has no known clock: cannot judge crossings into it.
|
|
80
|
+
continue
|
|
81
|
+
sources = _SourceSet()
|
|
82
|
+
for read in dst.reads:
|
|
83
|
+
sources.merge(resolver.sources_of(read))
|
|
84
|
+
|
|
85
|
+
bad_domains = {d for d in sources.domains if d is not None and d != dst_domain}
|
|
86
|
+
if bad_domains:
|
|
87
|
+
crossings.append(
|
|
88
|
+
Crossing(
|
|
89
|
+
kind=Kind.UNSYNCHRONISED,
|
|
90
|
+
dst_name=dst.name,
|
|
91
|
+
dst_domain=dst_domain,
|
|
92
|
+
loc=dst.loc,
|
|
93
|
+
detail=(
|
|
94
|
+
f"{dst.name!r} in clock domain {dst_domain!r} samples a register in "
|
|
95
|
+
f"domain {', '.join(sorted(bad_domains))} without a synchroniser "
|
|
96
|
+
"between them"
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
continue
|
|
101
|
+
# No hard crossing, but an unresolved source that *might* be another domain.
|
|
102
|
+
if sources.unknown_input is not None:
|
|
103
|
+
crossings.append(
|
|
104
|
+
Crossing(
|
|
105
|
+
kind=Kind.UNKNOWN_DOMAIN,
|
|
106
|
+
dst_name=dst.name,
|
|
107
|
+
dst_domain=dst_domain,
|
|
108
|
+
loc=dst.loc,
|
|
109
|
+
detail=(
|
|
110
|
+
f"{dst.name!r} in clock domain {dst_domain!r} samples "
|
|
111
|
+
f"{sources.unknown_input!r}, an input with no known clock domain"
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
elif sources.unresolved is not None:
|
|
116
|
+
crossings.append(
|
|
117
|
+
Crossing(
|
|
118
|
+
kind=Kind.NOT_ANALYSED,
|
|
119
|
+
dst_name=dst.name,
|
|
120
|
+
dst_domain=dst_domain,
|
|
121
|
+
loc=dst.loc,
|
|
122
|
+
detail=(
|
|
123
|
+
f"{dst.name!r} in clock domain {dst_domain!r} reads "
|
|
124
|
+
f"{sources.unresolved!r}, which this check does not analyse deeper"
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
return crossings
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _domains_and_destinations(
|
|
132
|
+
facts: ModuleFacts,
|
|
133
|
+
) -> tuple[dict[str, str | None], list[_Dst]]:
|
|
134
|
+
"""The domain of every register/pseudo-register, and the list of crossing destinations.
|
|
135
|
+
|
|
136
|
+
A real register maps its hierarchical path to its clock root. A non-sync child with a
|
|
137
|
+
resolvable clock also contributes: each output is a pseudo-register in the child's
|
|
138
|
+
mapped domain, reading the child's data inputs -- so the child's output can be both a
|
|
139
|
+
crossing destination and a further source.
|
|
140
|
+
"""
|
|
141
|
+
domains: dict[str, str | None] = {}
|
|
142
|
+
dsts: list[_Dst] = []
|
|
143
|
+
for reg in facts.registers.values():
|
|
144
|
+
domains[reg.path] = reg.clock_root
|
|
145
|
+
dsts.append(_Dst(name=reg.name, domain=reg.clock_root, loc=reg.loc, reads=reg.reads))
|
|
146
|
+
|
|
147
|
+
for sub in facts.sub_instances:
|
|
148
|
+
if sub.is_sync:
|
|
149
|
+
continue
|
|
150
|
+
child_domain = _child_domain(sub)
|
|
151
|
+
if child_domain is None:
|
|
152
|
+
continue # combinational passthrough; handled by the resolver, not a flop here
|
|
153
|
+
data_reads = _child_data_reads(sub)
|
|
154
|
+
for port, out_path in sorted(sub.outputs.items()):
|
|
155
|
+
domains[out_path] = child_domain
|
|
156
|
+
dsts.append(
|
|
157
|
+
_Dst(
|
|
158
|
+
name=f"{sub.name}.{port}",
|
|
159
|
+
domain=child_domain,
|
|
160
|
+
loc=sub.loc,
|
|
161
|
+
reads=data_reads,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
return domains, dsts
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _child_domain(sub: SubInstance) -> str | None:
|
|
168
|
+
"""The parent clock root a child's single clock input connects to, or None.
|
|
169
|
+
|
|
170
|
+
The connected parent expression is a path like `top.clk_b`; its domain root is the
|
|
171
|
+
last segment (`clk_b`). More than one clock input, or an unresolvable connection, is
|
|
172
|
+
left to the resolver as passthrough rather than guessed at.
|
|
173
|
+
"""
|
|
174
|
+
roots = {parent for parent in sub.clock_inputs.values() if parent is not None}
|
|
175
|
+
if len(roots) != 1:
|
|
176
|
+
return None
|
|
177
|
+
(parent,) = roots
|
|
178
|
+
return parent.rsplit(".", 1)[-1]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _child_data_reads(sub: SubInstance) -> frozenset[str]:
|
|
182
|
+
"""The parent paths a child's non-clock inputs read (its data inputs)."""
|
|
183
|
+
reads: set[str] = set()
|
|
184
|
+
for port, in_reads in sub.inputs.items():
|
|
185
|
+
if port in sub.clock_inputs:
|
|
186
|
+
continue
|
|
187
|
+
reads |= in_reads
|
|
188
|
+
return frozenset(reads)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass(slots=True)
|
|
192
|
+
class _SourceSet:
|
|
193
|
+
"""The domains a signal's fan-in reaches, plus any unresolved source it hit."""
|
|
194
|
+
|
|
195
|
+
domains: set[str | None] = field(default_factory=set)
|
|
196
|
+
unknown_input: str | None = None
|
|
197
|
+
unresolved: str | None = None
|
|
198
|
+
|
|
199
|
+
def merge(self, other: _SourceSet) -> None:
|
|
200
|
+
self.domains |= other.domains
|
|
201
|
+
self.unknown_input = self.unknown_input or other.unknown_input
|
|
202
|
+
self.unresolved = self.unresolved or other.unresolved
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class _Resolver:
|
|
206
|
+
"""Expands a read to the source-register domains it depends on, with memoisation."""
|
|
207
|
+
|
|
208
|
+
def __init__(self, facts: ModuleFacts, domains: dict[str, str | None]) -> None:
|
|
209
|
+
self._domains = domains
|
|
210
|
+
self._comb = facts.comb
|
|
211
|
+
self._cache: dict[str, _SourceSet] = {}
|
|
212
|
+
self._sync_outputs: set[str] = set()
|
|
213
|
+
self._sub_output_reads: dict[str, frozenset[str]] = {}
|
|
214
|
+
for sub in facts.sub_instances:
|
|
215
|
+
for out_path in sub.outputs.values():
|
|
216
|
+
if sub.is_sync:
|
|
217
|
+
self._sync_outputs.add(out_path)
|
|
218
|
+
elif out_path not in domains:
|
|
219
|
+
# A combinational (no-clock) child: its output passes its inputs
|
|
220
|
+
# through. A clocked child's output is already a domain source above.
|
|
221
|
+
reads: set[str] = set()
|
|
222
|
+
for in_reads in sub.inputs.values():
|
|
223
|
+
reads |= in_reads
|
|
224
|
+
self._sub_output_reads[out_path] = frozenset(reads)
|
|
225
|
+
|
|
226
|
+
def sources_of(self, path: str) -> _SourceSet:
|
|
227
|
+
return self._resolve(path, frozenset())
|
|
228
|
+
|
|
229
|
+
def _resolve(self, path: str, stack: frozenset[str]) -> _SourceSet:
|
|
230
|
+
cached = self._cache.get(path)
|
|
231
|
+
if cached is not None:
|
|
232
|
+
return cached
|
|
233
|
+
result = _SourceSet()
|
|
234
|
+
if path in stack:
|
|
235
|
+
return result # break a combinational loop conservatively (do not cache)
|
|
236
|
+
stack = stack | {path}
|
|
237
|
+
|
|
238
|
+
if path in self._sync_outputs:
|
|
239
|
+
# A synchroniser output is a fresh signal in the reader's domain: it carries
|
|
240
|
+
# no source domain, so the source set is empty and the crossing is clean.
|
|
241
|
+
self._cache[path] = result
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
if path in self._domains:
|
|
245
|
+
result.domains.add(self._domains[path])
|
|
246
|
+
self._cache[path] = result
|
|
247
|
+
return result
|
|
248
|
+
|
|
249
|
+
if path in self._comb:
|
|
250
|
+
for read in self._comb[path].reads:
|
|
251
|
+
result.merge(self._resolve(read, stack))
|
|
252
|
+
self._cache[path] = result
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
sub_reads = self._sub_output_reads.get(path)
|
|
256
|
+
if sub_reads is not None:
|
|
257
|
+
for read in sub_reads:
|
|
258
|
+
result.merge(self._resolve(read, stack))
|
|
259
|
+
self._cache[path] = result
|
|
260
|
+
return result
|
|
261
|
+
|
|
262
|
+
# Neither register, comb node, nor child output. A plain `top.name` is a module
|
|
263
|
+
# input with no known domain (info); a deeper path is something not analysed.
|
|
264
|
+
if path.count(".") == 1:
|
|
265
|
+
result.unknown_input = path
|
|
266
|
+
else:
|
|
267
|
+
result.unresolved = path
|
|
268
|
+
self._cache[path] = result
|
|
269
|
+
return result
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
__all__ = ["Crossing", "Kind", "analyse"]
|