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 @@
|
|
|
1
|
+
"""Symbols package marker: symbol/cell authoring plus linkage."""
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Project-owned physical cells: ports, properties, revisions, hierarchy."""
|
|
2
|
+
|
|
3
|
+
from ...interfaces.errors import ContractError
|
|
4
|
+
|
|
5
|
+
#: Component name cells carry on their entity.
|
|
6
|
+
CELL_COMPONENT = "Cell"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _is_int(value):
|
|
10
|
+
"""True for real ints (bools excluded)."""
|
|
11
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_port(port):
|
|
15
|
+
"""Validate one cell port record at edit time.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
port: Port mapping with name, x, y, and layer keys.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Normalized port copy.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ContractError: invalid_cell for any shape violation.
|
|
25
|
+
"""
|
|
26
|
+
if not isinstance(port, dict):
|
|
27
|
+
raise ContractError(
|
|
28
|
+
"invalid_cell", f"Cell port must be a mapping, got {port!r}."
|
|
29
|
+
)
|
|
30
|
+
for key in port:
|
|
31
|
+
if key not in ("name", "x", "y", "layer"):
|
|
32
|
+
raise ContractError(
|
|
33
|
+
"invalid_cell", f"Unknown cell port key: {key}."
|
|
34
|
+
)
|
|
35
|
+
name = port.get("name")
|
|
36
|
+
if not isinstance(name, str) or not name:
|
|
37
|
+
raise ContractError(
|
|
38
|
+
"invalid_cell", "Cell port name must be non-empty text."
|
|
39
|
+
)
|
|
40
|
+
for axis in ("x", "y"):
|
|
41
|
+
if not _is_int(port.get(axis)):
|
|
42
|
+
raise ContractError(
|
|
43
|
+
"invalid_cell",
|
|
44
|
+
f"Cell port {axis} must be an int, got "
|
|
45
|
+
f"{port.get(axis)!r}.",
|
|
46
|
+
)
|
|
47
|
+
layer = port.get("layer")
|
|
48
|
+
if not isinstance(layer, str) or not layer:
|
|
49
|
+
raise ContractError(
|
|
50
|
+
"invalid_cell", "Cell port layer must be non-empty text."
|
|
51
|
+
)
|
|
52
|
+
return {"name": name, "x": port["x"], "y": port["y"], "layer": layer}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_cell(cell):
|
|
56
|
+
"""Validate a Cell component mapping at edit time.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
cell: Cell mapping with name, ports, properties, and
|
|
60
|
+
revision keys.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Normalized Cell copy.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ContractError: invalid_cell for any shape violation.
|
|
67
|
+
"""
|
|
68
|
+
if not isinstance(cell, dict):
|
|
69
|
+
raise ContractError(
|
|
70
|
+
"invalid_cell", f"Cell must be a mapping, got {cell!r}."
|
|
71
|
+
)
|
|
72
|
+
for key in cell:
|
|
73
|
+
if key not in ("name", "ports", "properties", "revision"):
|
|
74
|
+
raise ContractError(
|
|
75
|
+
"invalid_cell", f"Unknown Cell key: {key}."
|
|
76
|
+
)
|
|
77
|
+
name = cell.get("name")
|
|
78
|
+
if not isinstance(name, str) or not name:
|
|
79
|
+
raise ContractError(
|
|
80
|
+
"invalid_cell", "Cell name must be non-empty text."
|
|
81
|
+
)
|
|
82
|
+
ports = cell.get("ports", [])
|
|
83
|
+
if not isinstance(ports, list):
|
|
84
|
+
raise ContractError(
|
|
85
|
+
"invalid_cell", "Cell ports must be a list."
|
|
86
|
+
)
|
|
87
|
+
clean_ports = [validate_port(port) for port in ports]
|
|
88
|
+
seen = [port["name"] for port in clean_ports]
|
|
89
|
+
if len(set(seen)) != len(seen):
|
|
90
|
+
raise ContractError(
|
|
91
|
+
"invalid_cell", "Cell port names must be distinct."
|
|
92
|
+
)
|
|
93
|
+
properties = cell.get("properties", {})
|
|
94
|
+
if not isinstance(properties, dict) or not all(
|
|
95
|
+
isinstance(key, str) and isinstance(val, str)
|
|
96
|
+
for key, val in properties.items()
|
|
97
|
+
):
|
|
98
|
+
raise ContractError(
|
|
99
|
+
"invalid_cell", "Cell properties must map text to text."
|
|
100
|
+
)
|
|
101
|
+
revision = cell.get("revision", 1)
|
|
102
|
+
if not _is_int(revision) or revision < 1:
|
|
103
|
+
raise ContractError(
|
|
104
|
+
"invalid_cell", "Cell revision must be a positive int."
|
|
105
|
+
)
|
|
106
|
+
return {
|
|
107
|
+
"name": name, "ports": clean_ports,
|
|
108
|
+
"properties": dict(properties), "revision": revision,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _read_cell(store, cell_id):
|
|
113
|
+
"""Return the normalized Cell for one entity id."""
|
|
114
|
+
try:
|
|
115
|
+
raw = store.get_component(cell_id, CELL_COMPONENT)
|
|
116
|
+
except LookupError:
|
|
117
|
+
raise ContractError(
|
|
118
|
+
"unknown_cell", f"No such cell entity: {cell_id!r}."
|
|
119
|
+
) from None
|
|
120
|
+
value = validate_cell(raw)
|
|
121
|
+
value["id"] = cell_id
|
|
122
|
+
return value
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _all_cells(store):
|
|
126
|
+
"""List every cell mapping, name-sorted."""
|
|
127
|
+
found = []
|
|
128
|
+
for entity in store.entities_with(CELL_COMPONENT):
|
|
129
|
+
try:
|
|
130
|
+
found.append(_read_cell(store, entity))
|
|
131
|
+
except ContractError:
|
|
132
|
+
continue
|
|
133
|
+
found.sort(key=lambda item: item["name"])
|
|
134
|
+
return found
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _check_name_free(store, name, except_id=None):
|
|
138
|
+
"""Fail loudly when a cell name is already taken."""
|
|
139
|
+
for item in _all_cells(store):
|
|
140
|
+
if item["name"] == name and item["id"] != except_id:
|
|
141
|
+
raise ContractError(
|
|
142
|
+
"cell_exists", f"Cell already exists: {name!r}."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def create_cell(store, actor, name, ports, properties=None):
|
|
147
|
+
"""Create one project-owned physical cell definition.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
151
|
+
actor: Attribution recorded on the revision and events.
|
|
152
|
+
name: Unique cell name.
|
|
153
|
+
ports: Port records with name, x, y, and layer.
|
|
154
|
+
properties: Text-to-text cell properties.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Receipt with op, actor, revision, cell id, and events.
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ContractError: invalid_cell for a malformed cell,
|
|
161
|
+
cell_exists for a duplicate name.
|
|
162
|
+
"""
|
|
163
|
+
value = validate_cell({
|
|
164
|
+
"name": name, "ports": ports,
|
|
165
|
+
"properties": properties or {}, "revision": 1,
|
|
166
|
+
})
|
|
167
|
+
_check_name_free(store, value["name"])
|
|
168
|
+
made = store.create_entity(actor, f"create_cell {value['name']}")
|
|
169
|
+
store.attach_component(
|
|
170
|
+
actor, made["id"], CELL_COMPONENT, value,
|
|
171
|
+
f"create_cell {value['name']}",
|
|
172
|
+
)
|
|
173
|
+
return {
|
|
174
|
+
"op": "create_cell", "actor": actor,
|
|
175
|
+
"revision": made["revision"], "cell": made["id"],
|
|
176
|
+
"events": [
|
|
177
|
+
{"event": "entity_created", "entity": made["id"]},
|
|
178
|
+
{"event": "component_changed", "entity": made["id"],
|
|
179
|
+
"component": CELL_COMPONENT},
|
|
180
|
+
],
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def copy_cell(store, actor, cell_id, name):
|
|
185
|
+
"""Copy one cell definition under a new name at revision 1.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
189
|
+
actor: Attribution recorded on the revision and events.
|
|
190
|
+
cell_id: Source cell entity id.
|
|
191
|
+
name: New unique cell name.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Receipt with op, actor, revision, cell id, and events.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
ContractError: unknown_cell for a missing source,
|
|
198
|
+
cell_exists for a duplicate name.
|
|
199
|
+
"""
|
|
200
|
+
source = _read_cell(store, cell_id)
|
|
201
|
+
return create_cell(
|
|
202
|
+
store, actor, name, source["ports"],
|
|
203
|
+
properties=source["properties"],
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def rename_cell(store, actor, cell_id, name):
|
|
208
|
+
"""Rename one cell; refuse while linkage references it.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
212
|
+
actor: Attribution recorded on the revision and events.
|
|
213
|
+
cell_id: Cell entity id to rename.
|
|
214
|
+
name: New unique cell name.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Receipt with op, actor, revision, cell id, and name.
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
ContractError: unknown_cell for a missing cell,
|
|
221
|
+
cell_exists for a duplicate name, cell_in_use when
|
|
222
|
+
linkage references the old name.
|
|
223
|
+
"""
|
|
224
|
+
value = _read_cell(store, cell_id)
|
|
225
|
+
if not isinstance(name, str) or not name:
|
|
226
|
+
raise ContractError(
|
|
227
|
+
"invalid_cell", "Cell name must be non-empty text."
|
|
228
|
+
)
|
|
229
|
+
_check_name_free(store, name, except_id=cell_id)
|
|
230
|
+
for entity in store.entities_with("Linkage"):
|
|
231
|
+
try:
|
|
232
|
+
link = store.get_component(entity, "Linkage")
|
|
233
|
+
except LookupError:
|
|
234
|
+
continue
|
|
235
|
+
if (
|
|
236
|
+
isinstance(link, dict)
|
|
237
|
+
and link.get("cell") == value["name"]
|
|
238
|
+
):
|
|
239
|
+
raise ContractError(
|
|
240
|
+
"cell_in_use",
|
|
241
|
+
f"Cell {value['name']!r} is linked by {entity!r}.",
|
|
242
|
+
)
|
|
243
|
+
clean = validate_cell({
|
|
244
|
+
"name": name, "ports": value["ports"],
|
|
245
|
+
"properties": value["properties"],
|
|
246
|
+
"revision": value["revision"],
|
|
247
|
+
})
|
|
248
|
+
changed = store.set_component(
|
|
249
|
+
actor, cell_id, CELL_COMPONENT, clean,
|
|
250
|
+
f"rename_cell {name}",
|
|
251
|
+
)
|
|
252
|
+
return {
|
|
253
|
+
"op": "rename_cell", "actor": actor,
|
|
254
|
+
"revision": changed["revision"], "cell": cell_id,
|
|
255
|
+
"name": name, "events": [],
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def revise_cell(store, actor, cell_id, ports=None, properties=None):
|
|
260
|
+
"""Revise one cell definition, bumping its revision.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
264
|
+
actor: Attribution recorded on the revision and events.
|
|
265
|
+
cell_id: Cell entity id to revise.
|
|
266
|
+
ports: Replacement port records, or None to keep.
|
|
267
|
+
properties: Replacement properties, or None to keep.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Receipt with op, actor, revision, cell id, and the new
|
|
271
|
+
cell revision.
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
ContractError: unknown_cell for a missing cell,
|
|
275
|
+
invalid_cell for a malformed revision.
|
|
276
|
+
"""
|
|
277
|
+
value = _read_cell(store, cell_id)
|
|
278
|
+
clean = validate_cell({
|
|
279
|
+
"name": value["name"],
|
|
280
|
+
"ports": ports if ports is not None else value["ports"],
|
|
281
|
+
"properties": (
|
|
282
|
+
properties if properties is not None
|
|
283
|
+
else value["properties"]
|
|
284
|
+
),
|
|
285
|
+
"revision": value["revision"] + 1,
|
|
286
|
+
})
|
|
287
|
+
changed = store.set_component(
|
|
288
|
+
actor, cell_id, CELL_COMPONENT, clean,
|
|
289
|
+
f"revise_cell {value['name']}",
|
|
290
|
+
)
|
|
291
|
+
return {
|
|
292
|
+
"op": "revise_cell", "actor": actor,
|
|
293
|
+
"revision": changed["revision"], "cell": cell_id,
|
|
294
|
+
"cell_revision": clean["revision"], "events": [],
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_cell(store, cell_id):
|
|
299
|
+
"""Read one cell definition by entity id.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
303
|
+
cell_id: Cell entity id to read.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Cell mapping with id, name, ports, properties, and
|
|
307
|
+
revision.
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
ContractError: unknown_cell for a missing cell.
|
|
311
|
+
"""
|
|
312
|
+
return _read_cell(store, cell_id)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def list_cells(store):
|
|
316
|
+
"""List every cell definition, name-sorted.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Cell mappings with ids, name-sorted.
|
|
323
|
+
"""
|
|
324
|
+
return _all_cells(store)
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""Symbol-to-cell linkage: durable pin maps with CELL-LINK semantics.
|
|
2
|
+
|
|
3
|
+
A Linkage entity binds one project symbol to one cell with an
|
|
4
|
+
explicit symbol-pin-to-cell-port map. Resolution reuses the
|
|
5
|
+
CELL-LINK contract (explicit entries win, unlisted pins resolve
|
|
6
|
+
to themselves); the frontend renders persisted maps as visible
|
|
7
|
+
pin-link strings while unmatched and conflicting pins are
|
|
8
|
+
reported explicitly, never hidden.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ...interfaces import celllink
|
|
12
|
+
from ...interfaces.errors import ContractError
|
|
13
|
+
|
|
14
|
+
#: Component name linkages carry on their entity.
|
|
15
|
+
LINKAGE_COMPONENT = "Linkage"
|
|
16
|
+
|
|
17
|
+
#: Prefixes treated as immutable upstream cell references.
|
|
18
|
+
SCOPED_PREFIXES = ("pdk:", "lib:", "pkg:")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_int(value):
|
|
22
|
+
"""True for real ints (bools excluded)."""
|
|
23
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def validate_linkage(linkage):
|
|
27
|
+
"""Validate a Linkage component mapping at edit time.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
linkage: Linkage mapping with symbol, cell, pin_map, and
|
|
31
|
+
revision keys.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Normalized Linkage copy.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ContractError: invalid_linkage for any shape violation.
|
|
38
|
+
"""
|
|
39
|
+
if not isinstance(linkage, dict):
|
|
40
|
+
raise ContractError(
|
|
41
|
+
"invalid_linkage",
|
|
42
|
+
f"Linkage must be a mapping, got {linkage!r}.",
|
|
43
|
+
)
|
|
44
|
+
for key in linkage:
|
|
45
|
+
if key not in ("symbol", "cell", "pin_map", "revision"):
|
|
46
|
+
raise ContractError(
|
|
47
|
+
"invalid_linkage", f"Unknown Linkage key: {key}."
|
|
48
|
+
)
|
|
49
|
+
symbol = linkage.get("symbol")
|
|
50
|
+
cell = linkage.get("cell")
|
|
51
|
+
if not isinstance(symbol, str) or not symbol:
|
|
52
|
+
raise ContractError(
|
|
53
|
+
"invalid_linkage", "Linkage symbol must be non-empty text."
|
|
54
|
+
)
|
|
55
|
+
if not isinstance(cell, str) or not cell:
|
|
56
|
+
raise ContractError(
|
|
57
|
+
"invalid_linkage", "Linkage cell must be non-empty text."
|
|
58
|
+
)
|
|
59
|
+
pin_map = linkage.get("pin_map", [])
|
|
60
|
+
if not isinstance(pin_map, list):
|
|
61
|
+
raise ContractError(
|
|
62
|
+
"invalid_linkage", "Linkage pin_map must be a list."
|
|
63
|
+
)
|
|
64
|
+
clean_map = []
|
|
65
|
+
seen = set()
|
|
66
|
+
for entry in pin_map:
|
|
67
|
+
if (
|
|
68
|
+
not isinstance(entry, dict)
|
|
69
|
+
or set(entry) != {"symPin", "cellPin"}
|
|
70
|
+
or not entry.get("symPin")
|
|
71
|
+
or not isinstance(entry["symPin"], str)
|
|
72
|
+
or not entry.get("cellPin")
|
|
73
|
+
or not isinstance(entry["cellPin"], str)
|
|
74
|
+
):
|
|
75
|
+
raise ContractError(
|
|
76
|
+
"invalid_linkage",
|
|
77
|
+
"pin_map entries need symPin + cellPin text.",
|
|
78
|
+
)
|
|
79
|
+
if entry["symPin"] in seen:
|
|
80
|
+
raise ContractError(
|
|
81
|
+
"invalid_linkage",
|
|
82
|
+
f"symPin {entry['symPin']!r} names more than one target.",
|
|
83
|
+
)
|
|
84
|
+
seen.add(entry["symPin"])
|
|
85
|
+
clean_map.append(
|
|
86
|
+
{"symPin": entry["symPin"], "cellPin": entry["cellPin"]}
|
|
87
|
+
)
|
|
88
|
+
revision = linkage.get("revision", 1)
|
|
89
|
+
if not _is_int(revision) or revision < 1:
|
|
90
|
+
raise ContractError(
|
|
91
|
+
"invalid_linkage", "Linkage revision must be a positive int."
|
|
92
|
+
)
|
|
93
|
+
return {
|
|
94
|
+
"symbol": symbol, "cell": cell,
|
|
95
|
+
"pin_map": clean_map, "revision": revision,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _symbol_pins(store, symbol):
|
|
100
|
+
"""Return pin names for one project symbol, failing loudly."""
|
|
101
|
+
for entity in store.entities_with("Symbol"):
|
|
102
|
+
try:
|
|
103
|
+
found = store.get_component(entity, "Symbol")
|
|
104
|
+
except LookupError:
|
|
105
|
+
continue
|
|
106
|
+
if isinstance(found, dict) and found.get("name") == symbol:
|
|
107
|
+
pins = found.get("pins", [])
|
|
108
|
+
return [pin["name"] for pin in pins
|
|
109
|
+
if isinstance(pin, dict) and pin.get("name")]
|
|
110
|
+
raise ContractError(
|
|
111
|
+
"unknown_symbol", f"No such project symbol: {symbol!r}."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _cell_ports(store, cell):
|
|
116
|
+
"""Return port names for one cell; scoped refs pass with []."""
|
|
117
|
+
if cell.startswith(SCOPED_PREFIXES):
|
|
118
|
+
return []
|
|
119
|
+
for entity in store.entities_with("Cell"):
|
|
120
|
+
try:
|
|
121
|
+
found = store.get_component(entity, "Cell")
|
|
122
|
+
except LookupError:
|
|
123
|
+
continue
|
|
124
|
+
if isinstance(found, dict) and found.get("name") == cell:
|
|
125
|
+
ports = found.get("ports", [])
|
|
126
|
+
return [port["name"] for port in ports
|
|
127
|
+
if isinstance(port, dict) and port.get("name")]
|
|
128
|
+
raise ContractError(
|
|
129
|
+
"unknown_cell", f"No such project cell: {cell!r}."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _find_linkage(store, symbol, cell):
|
|
134
|
+
"""Return (entity, linkage) for one pair, or (None, None)."""
|
|
135
|
+
for entity in store.entities_with(LINKAGE_COMPONENT):
|
|
136
|
+
try:
|
|
137
|
+
raw = store.get_component(entity, LINKAGE_COMPONENT)
|
|
138
|
+
except LookupError:
|
|
139
|
+
continue
|
|
140
|
+
try:
|
|
141
|
+
value = validate_linkage(raw)
|
|
142
|
+
except ContractError:
|
|
143
|
+
continue
|
|
144
|
+
if value["symbol"] == symbol and value["cell"] == cell:
|
|
145
|
+
value["id"] = entity
|
|
146
|
+
return entity, value
|
|
147
|
+
return None, None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def map_pins(store, actor, symbol, cell, pin_map):
|
|
151
|
+
"""Create or remap the pin map for one symbol-cell pair.
|
|
152
|
+
|
|
153
|
+
A first map lands at revision 1; remapping the same pair
|
|
154
|
+
bumps the stored revision. Geometry is never duplicated —
|
|
155
|
+
only the pin correspondence is stored.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
159
|
+
actor: Attribution recorded on the revision and events.
|
|
160
|
+
symbol: Project symbol name being linked.
|
|
161
|
+
cell: Project cell name (or scoped upstream reference).
|
|
162
|
+
pin_map: Explicit [{symPin, cellPin}] entries.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Receipt with op, actor, revision, linkage id, and the
|
|
166
|
+
stored linkage revision.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ContractError: unknown_symbol for a missing symbol,
|
|
170
|
+
unknown_cell for a missing project cell,
|
|
171
|
+
invalid_linkage for a malformed map.
|
|
172
|
+
"""
|
|
173
|
+
_symbol_pins(store, symbol)
|
|
174
|
+
_cell_ports(store, cell)
|
|
175
|
+
value = validate_linkage({
|
|
176
|
+
"symbol": symbol, "cell": cell,
|
|
177
|
+
"pin_map": pin_map, "revision": 1,
|
|
178
|
+
})
|
|
179
|
+
entity, current = _find_linkage(store, symbol, cell)
|
|
180
|
+
if entity is None:
|
|
181
|
+
made = store.create_entity(actor, f"map_pins {symbol}")
|
|
182
|
+
store.attach_component(
|
|
183
|
+
actor, made["id"], LINKAGE_COMPONENT, value,
|
|
184
|
+
f"map_pins {symbol}",
|
|
185
|
+
)
|
|
186
|
+
return {
|
|
187
|
+
"op": "map_pins", "actor": actor,
|
|
188
|
+
"revision": made["revision"], "linkage": made["id"],
|
|
189
|
+
"linkage_revision": 1,
|
|
190
|
+
"events": [
|
|
191
|
+
{"event": "entity_created", "entity": made["id"]},
|
|
192
|
+
{"event": "component_changed", "entity": made["id"],
|
|
193
|
+
"component": LINKAGE_COMPONENT},
|
|
194
|
+
],
|
|
195
|
+
}
|
|
196
|
+
value["revision"] = current["revision"] + 1
|
|
197
|
+
changed = store.set_component(
|
|
198
|
+
actor, entity, LINKAGE_COMPONENT, value,
|
|
199
|
+
f"map_pins {symbol}",
|
|
200
|
+
)
|
|
201
|
+
return {
|
|
202
|
+
"op": "map_pins", "actor": actor,
|
|
203
|
+
"revision": changed["revision"], "linkage": entity,
|
|
204
|
+
"linkage_revision": value["revision"], "events": [],
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def check_mapping(store, symbol, cell=None):
|
|
209
|
+
"""Report unmatched and conflicting pins for one linkage.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
213
|
+
symbol: Project symbol name to check.
|
|
214
|
+
cell: Cell name, or None to use the stored linkage cell.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Mapping with symbol, cell, unmatched_symbol_pins,
|
|
218
|
+
unmatched_cell_ports, and conflicts (each naming
|
|
219
|
+
the offending symPin and reason).
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
ContractError: unknown_symbol for a missing symbol,
|
|
223
|
+
unknown_linkage when no linkage exists.
|
|
224
|
+
"""
|
|
225
|
+
pins = _symbol_pins(store, symbol)
|
|
226
|
+
entity, value = None, None
|
|
227
|
+
if cell is not None:
|
|
228
|
+
_cell_ports(store, cell)
|
|
229
|
+
entity, value = _find_linkage(store, symbol, cell)
|
|
230
|
+
else:
|
|
231
|
+
for candidate in store.entities_with(LINKAGE_COMPONENT):
|
|
232
|
+
try:
|
|
233
|
+
raw = store.get_component(candidate, LINKAGE_COMPONENT)
|
|
234
|
+
except LookupError:
|
|
235
|
+
continue
|
|
236
|
+
try:
|
|
237
|
+
parsed = validate_linkage(raw)
|
|
238
|
+
except ContractError:
|
|
239
|
+
continue
|
|
240
|
+
if parsed["symbol"] == symbol:
|
|
241
|
+
entity, value = candidate, parsed
|
|
242
|
+
value["id"] = candidate
|
|
243
|
+
break
|
|
244
|
+
if value is None:
|
|
245
|
+
raise ContractError(
|
|
246
|
+
"unknown_linkage",
|
|
247
|
+
f"No linkage stored for symbol {symbol!r}.",
|
|
248
|
+
)
|
|
249
|
+
ports = _cell_ports(store, value["cell"])
|
|
250
|
+
mapped = {entry["symPin"] for entry in value["pin_map"]}
|
|
251
|
+
targeted = {entry["cellPin"] for entry in value["pin_map"]}
|
|
252
|
+
conflicts = []
|
|
253
|
+
for entry in value["pin_map"]:
|
|
254
|
+
if entry["symPin"] not in pins:
|
|
255
|
+
conflicts.append({
|
|
256
|
+
"symPin": entry["symPin"],
|
|
257
|
+
"reason": f"no such symbol pin on {symbol!r}",
|
|
258
|
+
})
|
|
259
|
+
elif ports and entry["cellPin"] not in ports:
|
|
260
|
+
conflicts.append({
|
|
261
|
+
"symPin": entry["symPin"],
|
|
262
|
+
"reason": f"no such cell port on {value['cell']!r}",
|
|
263
|
+
})
|
|
264
|
+
return {
|
|
265
|
+
"symbol": symbol, "cell": value["cell"], "linkage": entity,
|
|
266
|
+
"unmatched_symbol_pins": sorted(set(pins) - mapped),
|
|
267
|
+
"unmatched_cell_ports": sorted(set(ports) - targeted),
|
|
268
|
+
"conflicts": conflicts,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def pin_link_strings(store, symbol, cell=None):
|
|
273
|
+
"""Render persisted maps as visible pin-link strings.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
277
|
+
symbol: Project symbol name to render.
|
|
278
|
+
cell: Cell name, or None to use the stored linkage cell.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Mapping with symbol, cell, links (``SYM.pin ->
|
|
282
|
+
CELL.port`` strings in map order), plus the
|
|
283
|
+
unmatched and conflicting pin reports.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
ContractError: unknown_symbol for a missing symbol,
|
|
287
|
+
unknown_linkage when no linkage exists.
|
|
288
|
+
"""
|
|
289
|
+
report = check_mapping(store, symbol, cell)
|
|
290
|
+
entity = report["linkage"]
|
|
291
|
+
try:
|
|
292
|
+
raw = store.get_component(entity, LINKAGE_COMPONENT)
|
|
293
|
+
except LookupError:
|
|
294
|
+
raise ContractError(
|
|
295
|
+
"unknown_linkage",
|
|
296
|
+
f"Linkage vanished mid-read: {entity!r}.",
|
|
297
|
+
) from None
|
|
298
|
+
value = validate_linkage(raw)
|
|
299
|
+
return {
|
|
300
|
+
"symbol": symbol, "cell": report["cell"],
|
|
301
|
+
"links": [
|
|
302
|
+
f"{symbol}.{entry['symPin']} -> "
|
|
303
|
+
f"{report['cell']}.{entry['cellPin']}"
|
|
304
|
+
for entry in value["pin_map"]
|
|
305
|
+
],
|
|
306
|
+
"unmatched_symbol_pins": report["unmatched_symbol_pins"],
|
|
307
|
+
"unmatched_cell_ports": report["unmatched_cell_ports"],
|
|
308
|
+
"conflicts": report["conflicts"],
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def resolve_mapped_pin(store, symbol, pin, cell=None):
|
|
313
|
+
"""Resolve one symbol pin to its cell pin (pin chokepoint).
|
|
314
|
+
|
|
315
|
+
Explicit map entries win; unlisted pins resolve to
|
|
316
|
+
themselves (identity default), per CELL-LINK semantics.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
320
|
+
symbol: Project symbol name holding the pin.
|
|
321
|
+
pin: Symbol pin to resolve.
|
|
322
|
+
cell: Cell name, or None to use the stored linkage cell.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Resolved cell-pin name ("" when no linkage exists —
|
|
326
|
+
resolution stays total for the query lane).
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
ContractError: unknown_symbol for a missing symbol.
|
|
330
|
+
"""
|
|
331
|
+
_symbol_pins(store, symbol)
|
|
332
|
+
_, value = (None, None)
|
|
333
|
+
if cell is not None:
|
|
334
|
+
_, value = _find_linkage(store, symbol, cell)
|
|
335
|
+
else:
|
|
336
|
+
for candidate in store.entities_with(LINKAGE_COMPONENT):
|
|
337
|
+
try:
|
|
338
|
+
raw = store.get_component(candidate, LINKAGE_COMPONENT)
|
|
339
|
+
except LookupError:
|
|
340
|
+
continue
|
|
341
|
+
try:
|
|
342
|
+
parsed = validate_linkage(raw)
|
|
343
|
+
except ContractError:
|
|
344
|
+
continue
|
|
345
|
+
if parsed["symbol"] == symbol:
|
|
346
|
+
value = parsed
|
|
347
|
+
break
|
|
348
|
+
if value is None:
|
|
349
|
+
return pin
|
|
350
|
+
block = {"CellLink": {
|
|
351
|
+
"cell": value["cell"],
|
|
352
|
+
"pinMap": value["pin_map"],
|
|
353
|
+
"cellFilters": [],
|
|
354
|
+
"variantOverrides": {},
|
|
355
|
+
}}
|
|
356
|
+
return celllink.resolve_pin(block, pin)
|