rocket-engine 0.0.2b1__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.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
"""Shared schematic connectivity extraction (CONNECT-GRAPH).
|
|
2
|
+
|
|
3
|
+
One extraction feeds every netlist backend plus ERC: wires and
|
|
4
|
+
instance pins coalesce through a spatial hash, labels propagate net
|
|
5
|
+
names, unlabeled nets and instances get stable generated names, and
|
|
6
|
+
`lvs_ignore` instances stay out of the graph. Emitters and ERC never
|
|
7
|
+
name a net themselves — `Connectivity.net_name` is the single
|
|
8
|
+
authority they all read.
|
|
9
|
+
|
|
10
|
+
Schematic record (plain data, JSON-safe):
|
|
11
|
+
|
|
12
|
+
- cell: top cell name (default "top").
|
|
13
|
+
- wires: list of polylines; each a list of [x, y] integer points.
|
|
14
|
+
Zero-length segments are skipped.
|
|
15
|
+
- pins: [{instance, pin, xy}]; instance may be "" only when the
|
|
16
|
+
schematic holds exactly one unnamed instance.
|
|
17
|
+
- labels: [{name, xy}]; names propagate onto the nets they touch.
|
|
18
|
+
- instances: [{name?, symbol, attrs?, custom?, format?, generics?}];
|
|
19
|
+
attrs.lvs_ignore excludes the instance from graph and netlist.
|
|
20
|
+
- symbols: {name: {pins?, custom?, format?, generics?}}.
|
|
21
|
+
- ports: [{name, direction}]; direction is input/output/inout and
|
|
22
|
+
every port must name an extracted net.
|
|
23
|
+
|
|
24
|
+
Two wires connect when an endpoint of one lies on the other (shared
|
|
25
|
+
endpoints and T-junctions); crossing interiors do not connect.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import hashlib
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
RULE_SHORT = "connectivity.short"
|
|
32
|
+
RULE_UNCONNECTED_PIN = "connectivity.unconnected_pin"
|
|
33
|
+
RULE_DANGLING_NET = "connectivity.dangling_net"
|
|
34
|
+
RULE_ORPHAN_LABEL = "connectivity.orphan_label"
|
|
35
|
+
|
|
36
|
+
DEFAULT_POLICY = {
|
|
37
|
+
RULE_SHORT: "error",
|
|
38
|
+
RULE_UNCONNECTED_PIN: "warning",
|
|
39
|
+
RULE_DANGLING_NET: "warning",
|
|
40
|
+
RULE_ORPHAN_LABEL: "warning",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_PORT_DIRECTIONS = ("input", "output", "inout")
|
|
44
|
+
_HASH_CELL = 16
|
|
45
|
+
|
|
46
|
+
_CACHE = {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConnectGraphError(ValueError):
|
|
50
|
+
"""Malformed schematic, unknown net id, or bad extraction request."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Net:
|
|
54
|
+
"""One extracted net: canonical name plus its members.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
id: Deterministic net id (nets sort by anchor member).
|
|
58
|
+
name: Canonical name from label propagation or generation.
|
|
59
|
+
pins: Sorted ((instance, pin)) members.
|
|
60
|
+
labels: Distinct sorted label names attached to this net.
|
|
61
|
+
has_wires: True when at least one wire segment joins.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, id, name, pins, labels, has_wires):
|
|
65
|
+
self.id = id
|
|
66
|
+
self.name = name
|
|
67
|
+
self.pins = tuple(pins)
|
|
68
|
+
self.labels = tuple(labels)
|
|
69
|
+
self.has_wires = has_wires
|
|
70
|
+
|
|
71
|
+
def __eq__(self, other):
|
|
72
|
+
if not isinstance(other, Net):
|
|
73
|
+
return NotImplemented
|
|
74
|
+
return (
|
|
75
|
+
self.id,
|
|
76
|
+
self.name,
|
|
77
|
+
self.pins,
|
|
78
|
+
self.labels,
|
|
79
|
+
self.has_wires,
|
|
80
|
+
) == (
|
|
81
|
+
other.id,
|
|
82
|
+
other.name,
|
|
83
|
+
other.pins,
|
|
84
|
+
other.labels,
|
|
85
|
+
other.has_wires,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def __repr__(self):
|
|
89
|
+
return f"Net(id={self.id!r}, name={self.name!r})"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ResolvedInstance:
|
|
93
|
+
"""One netlisted instance with pins bound to net ids.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
name: Instance name (generated when the schematic omits it).
|
|
97
|
+
symbol: Symbol name.
|
|
98
|
+
pins: ((pin, net_id)) in symbol pin order, else pin-name order.
|
|
99
|
+
generics: Symbol defaults overridden by instance values.
|
|
100
|
+
custom: Instance-level custom templates by language.
|
|
101
|
+
format: Instance-level format templates by language.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, name, symbol, pins, generics, custom=None, format=None):
|
|
105
|
+
self.name = name
|
|
106
|
+
self.symbol = symbol
|
|
107
|
+
self.pins = tuple(pins)
|
|
108
|
+
self.generics = dict(generics)
|
|
109
|
+
self.custom = dict(custom or {})
|
|
110
|
+
self.format = dict(format or {})
|
|
111
|
+
|
|
112
|
+
def __eq__(self, other):
|
|
113
|
+
if not isinstance(other, ResolvedInstance):
|
|
114
|
+
return NotImplemented
|
|
115
|
+
return (
|
|
116
|
+
self.name,
|
|
117
|
+
self.symbol,
|
|
118
|
+
self.pins,
|
|
119
|
+
self.generics,
|
|
120
|
+
self.custom,
|
|
121
|
+
self.format,
|
|
122
|
+
) == (
|
|
123
|
+
other.name,
|
|
124
|
+
other.symbol,
|
|
125
|
+
other.pins,
|
|
126
|
+
other.generics,
|
|
127
|
+
other.custom,
|
|
128
|
+
other.format,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def __repr__(self):
|
|
132
|
+
return f"ResolvedInstance(name={self.name!r}, symbol={self.symbol!r})"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Connectivity:
|
|
136
|
+
"""Extracted nets with the shared net-naming authority.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
cell: Top cell name.
|
|
140
|
+
nets: Nets in id order.
|
|
141
|
+
shorts: [{net, labels}] for nets with conflicting labels.
|
|
142
|
+
pin_index: (instance, pin) -> net id for graphed pins.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(self, cell, nets, shorts, pin_index):
|
|
146
|
+
self.cell = cell
|
|
147
|
+
self.nets = tuple(nets)
|
|
148
|
+
self.shorts = tuple(dict(s) for s in shorts)
|
|
149
|
+
self._pin_index = dict(pin_index)
|
|
150
|
+
self._names = {net.id: net.name for net in self.nets}
|
|
151
|
+
|
|
152
|
+
def net_name(self, net_id):
|
|
153
|
+
"""Return the canonical name of one net.
|
|
154
|
+
|
|
155
|
+
The single naming authority: emitters and ERC resolve every
|
|
156
|
+
net through this method, never from their own tables.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
net_id: Extracted net id.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Canonical net name.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ConnectGraphError: Unknown net id.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
return self._names[net_id]
|
|
169
|
+
except KeyError:
|
|
170
|
+
raise ConnectGraphError(f"Unknown net id: {net_id!r}.") from None
|
|
171
|
+
|
|
172
|
+
def pin_net(self, instance, pin):
|
|
173
|
+
"""Return the net id of one instance pin.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
instance: Instance name.
|
|
177
|
+
pin: Pin name.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Net id, or None for pins of `lvs_ignore` instances.
|
|
181
|
+
"""
|
|
182
|
+
return self._pin_index.get((instance, pin))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class NetlistStructs:
|
|
186
|
+
"""Everything the emitters need: connectivity plus instances.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
cell: Top cell name.
|
|
190
|
+
connectivity: Extracted nets and the naming authority.
|
|
191
|
+
instances: Resolved non-ignored instances, schematic order.
|
|
192
|
+
symbols: Normalized symbol mappings (pins/custom/format).
|
|
193
|
+
ports: [{name, direction}] in schematic order.
|
|
194
|
+
ignored: Ignored instance names in schematic order.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(self, cell, connectivity, instances, symbols, ports, ignored):
|
|
198
|
+
self.cell = cell
|
|
199
|
+
self.connectivity = connectivity
|
|
200
|
+
self.instances = tuple(instances)
|
|
201
|
+
self.symbols = dict(symbols)
|
|
202
|
+
self.ports = tuple(dict(p) for p in ports)
|
|
203
|
+
self.ignored = tuple(ignored)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def prepare_netlist_structs(schematic, run_key=None):
|
|
207
|
+
"""Extract connectivity plus resolved instances from a schematic.
|
|
208
|
+
|
|
209
|
+
Wires and pins coalesce through a spatial hash, labels propagate
|
|
210
|
+
names, unlabeled nets become net1..netN in net order, unnamed
|
|
211
|
+
instances become X<n>, and `lvs_ignore` instances drop out.
|
|
212
|
+
Results cache only behind an explicit run key, and a changed
|
|
213
|
+
schematic under a reused key rebuilds instead of serving stale
|
|
214
|
+
nets.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
schematic: Schematic record (see module docstring).
|
|
218
|
+
run_key: Explicit cache key (run/snapshot id); None
|
|
219
|
+
disables the cache and always rebuilds.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
NetlistStructs with connectivity, instances, and ports.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
ConnectGraphError: Malformed schematic, dangling port,
|
|
226
|
+
unknown symbol, or a non-string run key.
|
|
227
|
+
"""
|
|
228
|
+
if run_key is not None and (not isinstance(run_key, str) or not run_key):
|
|
229
|
+
raise ConnectGraphError(f"run_key must be a non-empty string, got {run_key!r}.")
|
|
230
|
+
normalized = _normalize_schematic(schematic)
|
|
231
|
+
if run_key is None:
|
|
232
|
+
return _extract(normalized)
|
|
233
|
+
digest = _digest(normalized)
|
|
234
|
+
hit = _CACHE.get(run_key)
|
|
235
|
+
if hit is not None and hit[0] == digest:
|
|
236
|
+
return hit[1]
|
|
237
|
+
structs = _extract(normalized)
|
|
238
|
+
_CACHE[run_key] = (digest, structs)
|
|
239
|
+
return structs
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def invalidate_run(run_key):
|
|
243
|
+
"""Drop one cached extraction.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
run_key: Cache key passed to prepare_netlist_structs.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
1 when an entry was dropped, else 0.
|
|
250
|
+
"""
|
|
251
|
+
return 1 if _CACHE.pop(run_key, None) is not None else 0
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def clear_connectivity_cache():
|
|
255
|
+
"""Drop every cached extraction.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Number of entries dropped.
|
|
259
|
+
"""
|
|
260
|
+
count = len(_CACHE)
|
|
261
|
+
_CACHE.clear()
|
|
262
|
+
return count
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def erc_findings(connectivity, policy=None):
|
|
266
|
+
"""List connectivity findings with authority-resolved net names.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
connectivity: Extracted connectivity.
|
|
270
|
+
policy: rule -> severity overrides; defaults warn on
|
|
271
|
+
everything but shorts.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Findings sorted by (rule, net id); every finding names its
|
|
275
|
+
net through `Connectivity.net_name`.
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
ConnectGraphError: Unknown rule in the policy.
|
|
279
|
+
"""
|
|
280
|
+
merged = dict(DEFAULT_POLICY)
|
|
281
|
+
for rule in (policy or {}):
|
|
282
|
+
if rule not in merged:
|
|
283
|
+
raise ConnectGraphError(f"Unknown ERC rule: {rule!r}.")
|
|
284
|
+
merged[rule] = policy[rule]
|
|
285
|
+
findings = []
|
|
286
|
+
for net in connectivity.nets:
|
|
287
|
+
name = connectivity.net_name(net.id)
|
|
288
|
+
if len(net.labels) > 1:
|
|
289
|
+
findings.append(
|
|
290
|
+
{
|
|
291
|
+
"rule": RULE_SHORT,
|
|
292
|
+
"severity": merged[RULE_SHORT],
|
|
293
|
+
"message": (
|
|
294
|
+
f"net {name} has conflicting labels: "
|
|
295
|
+
+ ", ".join(net.labels)
|
|
296
|
+
),
|
|
297
|
+
"net": name,
|
|
298
|
+
"net_id": net.id,
|
|
299
|
+
"labels": list(net.labels),
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
if len(net.pins) == 1 and not net.has_wires and not net.labels:
|
|
303
|
+
(instance, pin) = net.pins[0]
|
|
304
|
+
findings.append(
|
|
305
|
+
{
|
|
306
|
+
"rule": RULE_UNCONNECTED_PIN,
|
|
307
|
+
"severity": merged[RULE_UNCONNECTED_PIN],
|
|
308
|
+
"message": f"pin {instance}.{pin} on net {name} connects to nothing",
|
|
309
|
+
"net": name,
|
|
310
|
+
"net_id": net.id,
|
|
311
|
+
"pins": [{"instance": instance, "pin": pin}],
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
elif not net.pins and net.has_wires:
|
|
315
|
+
findings.append(
|
|
316
|
+
{
|
|
317
|
+
"rule": RULE_DANGLING_NET,
|
|
318
|
+
"severity": merged[RULE_DANGLING_NET],
|
|
319
|
+
"message": f"net {name} has wires but no pins",
|
|
320
|
+
"net": name,
|
|
321
|
+
"net_id": net.id,
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
elif not net.pins and not net.has_wires and net.labels:
|
|
325
|
+
findings.append(
|
|
326
|
+
{
|
|
327
|
+
"rule": RULE_ORPHAN_LABEL,
|
|
328
|
+
"severity": merged[RULE_ORPHAN_LABEL],
|
|
329
|
+
"message": f"label {net.labels[0]} on net {name} touches nothing",
|
|
330
|
+
"net": name,
|
|
331
|
+
"net_id": net.id,
|
|
332
|
+
"labels": list(net.labels),
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
findings.sort(key=lambda f: (f["rule"], f["net_id"]))
|
|
336
|
+
return findings
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _parse_point(value, what):
|
|
340
|
+
if (
|
|
341
|
+
not isinstance(value, (list, tuple))
|
|
342
|
+
or len(value) != 2
|
|
343
|
+
or any(not isinstance(c, int) or isinstance(c, bool) for c in value)
|
|
344
|
+
):
|
|
345
|
+
raise ConnectGraphError(f"{what} must be [x, y] ints, got {value!r}.")
|
|
346
|
+
return (value[0], value[1])
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _parse_mapping(value, what):
|
|
350
|
+
if value is None:
|
|
351
|
+
return {}
|
|
352
|
+
if not isinstance(value, dict):
|
|
353
|
+
raise ConnectGraphError(f"{what} must be a mapping, got {value!r}.")
|
|
354
|
+
return dict(value)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _normalize_schematic(schematic):
|
|
358
|
+
if not isinstance(schematic, dict):
|
|
359
|
+
raise ConnectGraphError(f"Schematic must be a mapping, got {schematic!r}.")
|
|
360
|
+
cell = schematic.get("cell", "top")
|
|
361
|
+
if not isinstance(cell, str) or not cell:
|
|
362
|
+
raise ConnectGraphError(f"cell must be a non-empty string, got {cell!r}.")
|
|
363
|
+
|
|
364
|
+
wires = schematic.get("wires", [])
|
|
365
|
+
if not isinstance(wires, list):
|
|
366
|
+
raise ConnectGraphError(f"wires must be a list, got {wires!r}.")
|
|
367
|
+
segments = []
|
|
368
|
+
for index, polyline in enumerate(wires):
|
|
369
|
+
if not isinstance(polyline, (list, tuple)) or len(polyline) < 2:
|
|
370
|
+
raise ConnectGraphError(
|
|
371
|
+
f"wires[{index}] must hold at least 2 points, got {polyline!r}."
|
|
372
|
+
)
|
|
373
|
+
points = [_parse_point(p, f"wires[{index}]") for p in polyline]
|
|
374
|
+
for a, b in zip(points, points[1:]):
|
|
375
|
+
if a != b:
|
|
376
|
+
segments.append((a, b))
|
|
377
|
+
|
|
378
|
+
symbols = schematic.get("symbols", {})
|
|
379
|
+
if not isinstance(symbols, dict):
|
|
380
|
+
raise ConnectGraphError(f"symbols must be a mapping, got {symbols!r}.")
|
|
381
|
+
normalized_symbols = {}
|
|
382
|
+
for name, symbol in symbols.items():
|
|
383
|
+
if not isinstance(symbol, dict):
|
|
384
|
+
raise ConnectGraphError(f"symbol {name!r} must be a mapping.")
|
|
385
|
+
pin_order = symbol.get("pins", [])
|
|
386
|
+
if not isinstance(pin_order, list) or not all(
|
|
387
|
+
isinstance(p, str) and p for p in pin_order
|
|
388
|
+
):
|
|
389
|
+
raise ConnectGraphError(f"symbol {name!r} pins must be a str list.")
|
|
390
|
+
normalized_symbols[name] = {
|
|
391
|
+
"pins": list(pin_order),
|
|
392
|
+
"custom": _parse_mapping(symbol.get("custom"), f"symbol {name!r} custom"),
|
|
393
|
+
"format": _parse_mapping(symbol.get("format"), f"symbol {name!r} format"),
|
|
394
|
+
"generics": _parse_mapping(symbol.get("generics"), f"symbol {name!r} generics"),
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
instances = schematic.get("instances", [])
|
|
398
|
+
if not isinstance(instances, list):
|
|
399
|
+
raise ConnectGraphError(f"instances must be a list, got {instances!r}.")
|
|
400
|
+
normalized_instances = []
|
|
401
|
+
taken = set()
|
|
402
|
+
for index, instance in enumerate(instances):
|
|
403
|
+
if not isinstance(instance, dict):
|
|
404
|
+
raise ConnectGraphError(f"instances[{index}] must be a mapping.")
|
|
405
|
+
symbol = instance.get("symbol")
|
|
406
|
+
if not isinstance(symbol, str) or not symbol:
|
|
407
|
+
raise ConnectGraphError(f"instances[{index}] needs a symbol name.")
|
|
408
|
+
if symbol not in normalized_symbols:
|
|
409
|
+
raise ConnectGraphError(
|
|
410
|
+
f"instances[{index}] names unknown symbol {symbol!r}."
|
|
411
|
+
)
|
|
412
|
+
name = instance.get("name") or ""
|
|
413
|
+
if not isinstance(name, str):
|
|
414
|
+
raise ConnectGraphError(f"instances[{index}] name must be a string.")
|
|
415
|
+
if name:
|
|
416
|
+
if name in taken:
|
|
417
|
+
raise ConnectGraphError(f"Duplicate instance name: {name!r}.")
|
|
418
|
+
taken.add(name)
|
|
419
|
+
attrs = _parse_mapping(instance.get("attrs"), f"instances[{index}] attrs")
|
|
420
|
+
normalized_instances.append(
|
|
421
|
+
{
|
|
422
|
+
"name": name,
|
|
423
|
+
"symbol": symbol,
|
|
424
|
+
"ignored": bool(attrs.get("lvs_ignore", False)),
|
|
425
|
+
"custom": _parse_mapping(instance.get("custom"), f"instances[{index}] custom"),
|
|
426
|
+
"format": _parse_mapping(instance.get("format"), f"instances[{index}] format"),
|
|
427
|
+
"generics": _parse_mapping(
|
|
428
|
+
instance.get("generics"), f"instances[{index}] generics"
|
|
429
|
+
),
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
unnamed = [i for i in normalized_instances if not i["name"]]
|
|
433
|
+
counter = 1
|
|
434
|
+
for instance in unnamed:
|
|
435
|
+
while f"X{counter}" in taken:
|
|
436
|
+
counter += 1
|
|
437
|
+
instance["name"] = f"X{counter}"
|
|
438
|
+
taken.add(instance["name"])
|
|
439
|
+
counter += 1
|
|
440
|
+
|
|
441
|
+
pins = schematic.get("pins", [])
|
|
442
|
+
if not isinstance(pins, list):
|
|
443
|
+
raise ConnectGraphError(f"pins must be a list, got {pins!r}.")
|
|
444
|
+
normalized_pins = []
|
|
445
|
+
seen_pins = set()
|
|
446
|
+
for index, pin in enumerate(pins):
|
|
447
|
+
if not isinstance(pin, dict):
|
|
448
|
+
raise ConnectGraphError(f"pins[{index}] must be a mapping.")
|
|
449
|
+
instance = pin.get("instance", "")
|
|
450
|
+
if not isinstance(instance, str):
|
|
451
|
+
raise ConnectGraphError(f"pins[{index}] instance must be a string.")
|
|
452
|
+
if not instance:
|
|
453
|
+
if len(unnamed) != 1:
|
|
454
|
+
raise ConnectGraphError(
|
|
455
|
+
f"pins[{index}] names no instance and the schematic holds "
|
|
456
|
+
f"{len(unnamed)} unnamed instances."
|
|
457
|
+
)
|
|
458
|
+
instance = unnamed[0]["name"]
|
|
459
|
+
if instance not in taken:
|
|
460
|
+
raise ConnectGraphError(f"pins[{index}] names unknown instance {instance!r}.")
|
|
461
|
+
pin_name = pin.get("pin")
|
|
462
|
+
if not isinstance(pin_name, str) or not pin_name:
|
|
463
|
+
raise ConnectGraphError(f"pins[{index}] needs a pin name.")
|
|
464
|
+
if (instance, pin_name) in seen_pins:
|
|
465
|
+
raise ConnectGraphError(f"Duplicate pin: {instance}.{pin_name}.")
|
|
466
|
+
seen_pins.add((instance, pin_name))
|
|
467
|
+
normalized_pins.append(
|
|
468
|
+
{"instance": instance, "pin": pin_name, "xy": _parse_point(pin.get("xy"), f"pins[{index}]")}
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
labels = schematic.get("labels", [])
|
|
472
|
+
if not isinstance(labels, list):
|
|
473
|
+
raise ConnectGraphError(f"labels must be a list, got {labels!r}.")
|
|
474
|
+
normalized_labels = []
|
|
475
|
+
for index, label in enumerate(labels):
|
|
476
|
+
if not isinstance(label, dict):
|
|
477
|
+
raise ConnectGraphError(f"labels[{index}] must be a mapping.")
|
|
478
|
+
name = label.get("name")
|
|
479
|
+
if not isinstance(name, str) or not name:
|
|
480
|
+
raise ConnectGraphError(f"labels[{index}] needs a name.")
|
|
481
|
+
normalized_labels.append(
|
|
482
|
+
{"name": name, "xy": _parse_point(label.get("xy"), f"labels[{index}]")}
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
ports = schematic.get("ports", [])
|
|
486
|
+
if not isinstance(ports, list):
|
|
487
|
+
raise ConnectGraphError(f"ports must be a list, got {ports!r}.")
|
|
488
|
+
normalized_ports = []
|
|
489
|
+
for index, port in enumerate(ports):
|
|
490
|
+
if not isinstance(port, dict):
|
|
491
|
+
raise ConnectGraphError(f"ports[{index}] must be a mapping.")
|
|
492
|
+
name = port.get("name")
|
|
493
|
+
direction = port.get("direction")
|
|
494
|
+
if not isinstance(name, str) or not name:
|
|
495
|
+
raise ConnectGraphError(f"ports[{index}] needs a name.")
|
|
496
|
+
if direction not in _PORT_DIRECTIONS:
|
|
497
|
+
raise ConnectGraphError(
|
|
498
|
+
f"ports[{index}] direction must be one of {_PORT_DIRECTIONS}, "
|
|
499
|
+
f"got {direction!r}."
|
|
500
|
+
)
|
|
501
|
+
normalized_ports.append({"name": name, "direction": direction})
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
"cell": cell,
|
|
505
|
+
"segments": segments,
|
|
506
|
+
"pins": normalized_pins,
|
|
507
|
+
"labels": normalized_labels,
|
|
508
|
+
"instances": normalized_instances,
|
|
509
|
+
"symbols": normalized_symbols,
|
|
510
|
+
"ports": normalized_ports,
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _digest(normalized):
|
|
515
|
+
text = json.dumps(
|
|
516
|
+
{
|
|
517
|
+
"cell": normalized["cell"],
|
|
518
|
+
"segments": [sorted([list(a), list(b)]) for a, b in normalized["segments"]],
|
|
519
|
+
"pins": normalized["pins"],
|
|
520
|
+
"labels": normalized["labels"],
|
|
521
|
+
"instances": normalized["instances"],
|
|
522
|
+
"symbols": normalized["symbols"],
|
|
523
|
+
"ports": normalized["ports"],
|
|
524
|
+
},
|
|
525
|
+
sort_keys=True,
|
|
526
|
+
)
|
|
527
|
+
return hashlib.sha256(text.encode()).hexdigest()
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _point_on_segment(point, seg_a, seg_b):
|
|
531
|
+
(px, py), (ax, ay), (bx, by) = point, seg_a, seg_b
|
|
532
|
+
if (bx - ax) * (py - ay) != (by - ay) * (px - ax):
|
|
533
|
+
return False
|
|
534
|
+
return min(ax, bx) <= px <= max(ax, bx) and min(ay, by) <= py <= max(ay, by)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class _UnionFind:
|
|
538
|
+
"""Union-find with path compression over small integer ids."""
|
|
539
|
+
|
|
540
|
+
def __init__(self):
|
|
541
|
+
self._parent = {}
|
|
542
|
+
|
|
543
|
+
def _root(self, node):
|
|
544
|
+
parent = self._parent.setdefault(node, node)
|
|
545
|
+
while parent != self._parent[parent]:
|
|
546
|
+
self._parent[node] = self._parent[parent]
|
|
547
|
+
node = parent
|
|
548
|
+
parent = self._parent[node]
|
|
549
|
+
return parent
|
|
550
|
+
|
|
551
|
+
def union(self, first, second):
|
|
552
|
+
"""Merge the sets holding two nodes.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
first: First node.
|
|
556
|
+
second: Second node.
|
|
557
|
+
"""
|
|
558
|
+
root_first, root_second = self._root(first), self._root(second)
|
|
559
|
+
if root_first != root_second:
|
|
560
|
+
self._parent[root_second] = root_first
|
|
561
|
+
|
|
562
|
+
def groups(self):
|
|
563
|
+
"""Return the disjoint node sets.
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
List of node groups.
|
|
567
|
+
"""
|
|
568
|
+
grouped = {}
|
|
569
|
+
for node in self._parent:
|
|
570
|
+
grouped.setdefault(self._root(node), []).append(node)
|
|
571
|
+
return list(grouped.values())
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
class _SpatialHash:
|
|
575
|
+
"""Grid index from segment bboxes to segment ids."""
|
|
576
|
+
|
|
577
|
+
def __init__(self, segments):
|
|
578
|
+
self._segments = segments
|
|
579
|
+
self._cells = {}
|
|
580
|
+
for index, (a, b) in enumerate(segments):
|
|
581
|
+
for cx in range(
|
|
582
|
+
min(a[0], b[0]) // _HASH_CELL, max(a[0], b[0]) // _HASH_CELL + 1
|
|
583
|
+
):
|
|
584
|
+
for cy in range(
|
|
585
|
+
min(a[1], b[1]) // _HASH_CELL, max(a[1], b[1]) // _HASH_CELL + 1
|
|
586
|
+
):
|
|
587
|
+
self._cells.setdefault((cx, cy), []).append(index)
|
|
588
|
+
|
|
589
|
+
def segments_near(self, point):
|
|
590
|
+
"""Return candidate segment ids whose bbox covers a point.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
point: (x, y) query point.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Segment ids indexed under the point's grid cell.
|
|
597
|
+
"""
|
|
598
|
+
return self._cells.get((point[0] // _HASH_CELL, point[1] // _HASH_CELL), [])
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _extract(normalized):
|
|
602
|
+
ignored = {i["name"] for i in normalized["instances"] if i["ignored"]}
|
|
603
|
+
pins = [p for p in normalized["pins"] if p["instance"] not in ignored]
|
|
604
|
+
segments = normalized["segments"]
|
|
605
|
+
labels = normalized["labels"]
|
|
606
|
+
|
|
607
|
+
union = _UnionFind()
|
|
608
|
+
point_nodes = {}
|
|
609
|
+
|
|
610
|
+
def _claim(point, node):
|
|
611
|
+
union._root(node)
|
|
612
|
+
first = point_nodes.setdefault(point, node)
|
|
613
|
+
union.union(first, node)
|
|
614
|
+
|
|
615
|
+
for index, (a, b) in enumerate(segments):
|
|
616
|
+
_claim(a, ("s", index))
|
|
617
|
+
_claim(b, ("s", index))
|
|
618
|
+
for index, pin in enumerate(pins):
|
|
619
|
+
_claim(pin["xy"], ("p", index))
|
|
620
|
+
for index, label in enumerate(labels):
|
|
621
|
+
_claim(label["xy"], ("l", index))
|
|
622
|
+
|
|
623
|
+
grid = _SpatialHash(segments)
|
|
624
|
+
touch_points = (
|
|
625
|
+
[a for a, _ in segments]
|
|
626
|
+
+ [b for _, b in segments]
|
|
627
|
+
+ [p["xy"] for p in pins]
|
|
628
|
+
+ [label["xy"] for label in labels]
|
|
629
|
+
)
|
|
630
|
+
for point in touch_points:
|
|
631
|
+
for index in grid.segments_near(point):
|
|
632
|
+
if _point_on_segment(point, *segments[index]):
|
|
633
|
+
union.union(point_nodes[point], ("s", index))
|
|
634
|
+
|
|
635
|
+
member_of = {}
|
|
636
|
+
for index, pin in enumerate(pins):
|
|
637
|
+
member_of[("p", index)] = ("pin", pin["instance"], pin["pin"])
|
|
638
|
+
for index, label in enumerate(labels):
|
|
639
|
+
member_of[("l", index)] = ("label", label["name"], index)
|
|
640
|
+
for index in range(len(segments)):
|
|
641
|
+
member_of[("s", index)] = ("seg", index)
|
|
642
|
+
|
|
643
|
+
nets = []
|
|
644
|
+
for group in union.groups():
|
|
645
|
+
members = sorted(member_of[n] for n in group)
|
|
646
|
+
pins_here = sorted((m[1], m[2]) for m in members if m[0] == "pin")
|
|
647
|
+
labels_here = sorted({m[1] for m in members if m[0] == "label"})
|
|
648
|
+
nets.append(
|
|
649
|
+
{"anchor": members[0], "pins": pins_here, "labels": labels_here,
|
|
650
|
+
"has_wires": any(m[0] == "seg" for m in members)}
|
|
651
|
+
)
|
|
652
|
+
nets.sort(key=lambda n: n["anchor"])
|
|
653
|
+
|
|
654
|
+
used_names = {label for net in nets for label in net["labels"]}
|
|
655
|
+
counter = 1
|
|
656
|
+
final = []
|
|
657
|
+
for net_id, net in enumerate(nets):
|
|
658
|
+
if net["labels"]:
|
|
659
|
+
name = net["labels"][0]
|
|
660
|
+
else:
|
|
661
|
+
while f"net{counter}" in used_names:
|
|
662
|
+
counter += 1
|
|
663
|
+
name = f"net{counter}"
|
|
664
|
+
used_names.add(name)
|
|
665
|
+
counter += 1
|
|
666
|
+
final.append(
|
|
667
|
+
Net(
|
|
668
|
+
id=net_id,
|
|
669
|
+
name=name,
|
|
670
|
+
pins=net["pins"],
|
|
671
|
+
labels=net["labels"],
|
|
672
|
+
has_wires=net["has_wires"],
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
pin_index = {}
|
|
677
|
+
for net in final:
|
|
678
|
+
for member in net.pins:
|
|
679
|
+
pin_index[member] = net.id
|
|
680
|
+
shorts = [
|
|
681
|
+
{"net": net.id, "labels": list(net.labels)}
|
|
682
|
+
for net in final
|
|
683
|
+
if len(net.labels) > 1
|
|
684
|
+
]
|
|
685
|
+
connectivity = Connectivity(
|
|
686
|
+
cell=normalized["cell"], nets=final, shorts=shorts, pin_index=pin_index
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
canonical = {net.name for net in final}
|
|
690
|
+
for port in normalized["ports"]:
|
|
691
|
+
if port["name"] not in canonical:
|
|
692
|
+
raise ConnectGraphError(
|
|
693
|
+
f"Port {port['name']!r} names no extracted net "
|
|
694
|
+
f"(nets: {sorted(canonical)})."
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
by_instance = {}
|
|
698
|
+
for pin in normalized["pins"]:
|
|
699
|
+
if pin["instance"] in ignored:
|
|
700
|
+
continue
|
|
701
|
+
by_instance.setdefault(pin["instance"], {})[pin["pin"]] = pin_index[
|
|
702
|
+
(pin["instance"], pin["pin"])
|
|
703
|
+
]
|
|
704
|
+
instances = []
|
|
705
|
+
for instance in normalized["instances"]:
|
|
706
|
+
if instance["ignored"]:
|
|
707
|
+
continue
|
|
708
|
+
symbol = normalized["symbols"][instance["symbol"]]
|
|
709
|
+
order = symbol["pins"]
|
|
710
|
+
pin_map = by_instance.get(instance["name"], {})
|
|
711
|
+
if order:
|
|
712
|
+
ordered = [(p, pin_map[p]) for p in order if p in pin_map]
|
|
713
|
+
ordered += sorted((p, n) for p, n in pin_map.items() if p not in order)
|
|
714
|
+
else:
|
|
715
|
+
ordered = sorted(pin_map.items())
|
|
716
|
+
instances.append(
|
|
717
|
+
ResolvedInstance(
|
|
718
|
+
name=instance["name"],
|
|
719
|
+
symbol=instance["symbol"],
|
|
720
|
+
pins=ordered,
|
|
721
|
+
generics={**symbol["generics"], **instance["generics"]},
|
|
722
|
+
custom=instance["custom"],
|
|
723
|
+
format=instance["format"],
|
|
724
|
+
)
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
return NetlistStructs(
|
|
728
|
+
cell=normalized["cell"],
|
|
729
|
+
connectivity=connectivity,
|
|
730
|
+
instances=instances,
|
|
731
|
+
symbols=normalized["symbols"],
|
|
732
|
+
ports=normalized["ports"],
|
|
733
|
+
ignored=[i["name"] for i in normalized["instances"] if i["ignored"]],
|
|
734
|
+
)
|