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,195 @@
|
|
|
1
|
+
"""Resolve registry blocks against the rocket-blocks layout.
|
|
2
|
+
|
|
3
|
+
The rocket-blocks repo holds one directory per PDK, each with
|
|
4
|
+
one entry folder per prebuilt IP block::
|
|
5
|
+
|
|
6
|
+
<root>/<pdk>/<entry>/rocket/meta.yaml
|
|
7
|
+
|
|
8
|
+
Resolution is per-PDK by construction: the PDK names the
|
|
9
|
+
top-level directory, so a block registered under ``sky130``
|
|
10
|
+
can only ever resolve inside ``<root>/sky130/``. Names that
|
|
11
|
+
try to leave that directory (separators, ``..``, absolute
|
|
12
|
+
paths) are refused before any filesystem touch, and entry
|
|
13
|
+
folder names must be catalogue slugs (lowercase-with-dashes).
|
|
14
|
+
|
|
15
|
+
The entry contract itself lives in ``entry.schema.json`` (a
|
|
16
|
+
byte-identical copy of
|
|
17
|
+
``contracts/v1/schemas/ip-entry.schema.json``, which is the
|
|
18
|
+
single source). Every resolved entry is verified against that
|
|
19
|
+
schema at runtime: found-but-nonconforming entries fail loudly
|
|
20
|
+
instead of resolving.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import yaml
|
|
27
|
+
from jsonschema import Draft202012Validator
|
|
28
|
+
from jsonschema import FormatChecker
|
|
29
|
+
|
|
30
|
+
from ...interfaces.errors import ContractError
|
|
31
|
+
|
|
32
|
+
#: Entry metadata path under a block folder.
|
|
33
|
+
META_PARTS = ("rocket", "meta.yaml")
|
|
34
|
+
|
|
35
|
+
#: Catalogue entry folders are lowercase-with-dashes slugs.
|
|
36
|
+
_SLUG_RE = r"[a-z0-9]+(?:-[a-z0-9]+)*"
|
|
37
|
+
|
|
38
|
+
#: Root export id prefixing intra-bundle references.
|
|
39
|
+
_ROOT_ID = "urn:rocketeda:engine:v1"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _need_name(value, what, code):
|
|
43
|
+
"""Accept one clean, non-escaping segment or raise the code."""
|
|
44
|
+
if not isinstance(value, str) or not value:
|
|
45
|
+
raise ContractError(code, f"{what} must be non-empty text.")
|
|
46
|
+
if value in (".", "..") or "/" in value or "\\" in value:
|
|
47
|
+
raise ContractError(
|
|
48
|
+
code, f"{what} must be one path segment, got {value!r}."
|
|
49
|
+
)
|
|
50
|
+
if Path(value).is_absolute():
|
|
51
|
+
raise ContractError(
|
|
52
|
+
code, f"{what} must be relative, got {value!r}."
|
|
53
|
+
)
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _load_entry_schema():
|
|
58
|
+
"""Read the bundled entry contract (single source copy)."""
|
|
59
|
+
path = Path(__file__).with_name("entry.schema.json")
|
|
60
|
+
try:
|
|
61
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
62
|
+
except OSError as exc:
|
|
63
|
+
raise ContractError(
|
|
64
|
+
"unknown_ip",
|
|
65
|
+
f"IP entry contract unreadable: {path.name}.",
|
|
66
|
+
) from exc
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _inline_root_refs(node, defs):
|
|
70
|
+
"""Inline intra-bundle urn refs so the module stands alone."""
|
|
71
|
+
if isinstance(node, dict):
|
|
72
|
+
ref = node.get("$ref")
|
|
73
|
+
if isinstance(ref, str) and ref.startswith(_ROOT_ID + "#/$defs/"):
|
|
74
|
+
name = ref.rsplit("/", 1)[1]
|
|
75
|
+
try:
|
|
76
|
+
target = defs[name]
|
|
77
|
+
except KeyError:
|
|
78
|
+
raise ContractError(
|
|
79
|
+
"unknown_ip",
|
|
80
|
+
f"IP entry contract references unknown {name}.",
|
|
81
|
+
) from None
|
|
82
|
+
return _inline_root_refs(target, defs)
|
|
83
|
+
return {
|
|
84
|
+
key: _inline_root_refs(value, defs)
|
|
85
|
+
for key, value in node.items()
|
|
86
|
+
}
|
|
87
|
+
if isinstance(node, list):
|
|
88
|
+
return [_inline_root_refs(item, defs) for item in node]
|
|
89
|
+
return node
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _entry_validator():
|
|
93
|
+
"""Build the runtime validator for IpEntry mappings."""
|
|
94
|
+
module = _load_entry_schema()
|
|
95
|
+
defs = module.get("$defs", {})
|
|
96
|
+
try:
|
|
97
|
+
entry_schema = _inline_root_refs(defs["IpEntry"], defs)
|
|
98
|
+
except KeyError:
|
|
99
|
+
raise ContractError(
|
|
100
|
+
"unknown_ip", "IP entry contract has no IpEntry."
|
|
101
|
+
) from None
|
|
102
|
+
return Draft202012Validator(
|
|
103
|
+
entry_schema, format_checker=FormatChecker())
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _check_entry(meta_path, meta, name, version):
|
|
107
|
+
"""Refuse found-but-nonconforming entries loudly."""
|
|
108
|
+
if not isinstance(meta, dict):
|
|
109
|
+
raise ContractError(
|
|
110
|
+
"invalid_ip",
|
|
111
|
+
f"IP block metadata malformed: {meta_path}.",
|
|
112
|
+
)
|
|
113
|
+
for error in _entry_validator().iter_errors(meta):
|
|
114
|
+
raise ContractError(
|
|
115
|
+
"invalid_ip",
|
|
116
|
+
f"IP block metadata invalid "
|
|
117
|
+
f"({'/'.join(str(part) for part in error.path) or 'entry'}): "
|
|
118
|
+
f"{error.message}.",
|
|
119
|
+
) from None
|
|
120
|
+
if meta.get("name") != name:
|
|
121
|
+
raise ContractError(
|
|
122
|
+
"invalid_ip",
|
|
123
|
+
f"IP block metadata names {meta.get('name')!r}, "
|
|
124
|
+
f"addressed {name!r}.",
|
|
125
|
+
)
|
|
126
|
+
declared = meta.get("version")
|
|
127
|
+
if version is not None and declared != version:
|
|
128
|
+
raise ContractError(
|
|
129
|
+
"invalid_ip",
|
|
130
|
+
f"No such IP block version: {name}@{version}.",
|
|
131
|
+
)
|
|
132
|
+
return declared if declared is not None else version
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def resolve_block_ref(blocks_root, pdk, name, version=None):
|
|
136
|
+
"""Map a registry (pdk, name[, version]) to a blocks entry.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
blocks_root: rocket-blocks checkout root directory.
|
|
140
|
+
pdk: Owning PDK id; selects the ``<root>/<pdk>/`` scope.
|
|
141
|
+
name: Catalogue slug naming the entry folder.
|
|
142
|
+
version: Revision label, or None to accept whatever the
|
|
143
|
+
entry metadata declares.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Mapping with pdk, name, entry (directory text), version,
|
|
147
|
+
and meta (the parsed ``rocket/meta.yaml`` mapping).
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
ContractError: invalid_reference for a segment that
|
|
151
|
+
would escape the PDK scope or breaks the catalogue
|
|
152
|
+
slug rule; unknown_ip when the entry or its metadata
|
|
153
|
+
is missing; invalid_ip when the entry is found but
|
|
154
|
+
nonconforming (schema violation, name/version
|
|
155
|
+
disagreement, or an unprovenanced wrapped checkout).
|
|
156
|
+
"""
|
|
157
|
+
import re
|
|
158
|
+
|
|
159
|
+
clean_pdk = _need_name(pdk, "IP pdk", "invalid_reference")
|
|
160
|
+
clean_name = _need_name(name, "IP block name", "invalid_reference")
|
|
161
|
+
if not re.fullmatch(_SLUG_RE, clean_name):
|
|
162
|
+
raise ContractError(
|
|
163
|
+
"invalid_reference",
|
|
164
|
+
f"IP block name must be a catalogue slug "
|
|
165
|
+
f"(lowercase-with-dashes), got {clean_name!r}.",
|
|
166
|
+
)
|
|
167
|
+
entry = Path(blocks_root) / clean_pdk / clean_name
|
|
168
|
+
meta_path = entry.joinpath(*META_PARTS)
|
|
169
|
+
if not meta_path.is_file():
|
|
170
|
+
raise ContractError(
|
|
171
|
+
"unknown_ip",
|
|
172
|
+
f"No such IP block entry: {clean_pdk}/{clean_name}.",
|
|
173
|
+
)
|
|
174
|
+
try:
|
|
175
|
+
meta = yaml.safe_load(meta_path.read_text(encoding="utf-8"))
|
|
176
|
+
except OSError as exc:
|
|
177
|
+
raise ContractError(
|
|
178
|
+
"unknown_ip",
|
|
179
|
+
f"IP block entry unreadable: {clean_pdk}/{clean_name}.",
|
|
180
|
+
) from exc
|
|
181
|
+
resolved_version = _check_entry(
|
|
182
|
+
f"{clean_pdk}/{clean_name}", meta, clean_name, version)
|
|
183
|
+
if (entry / "entry_repo").exists() and not meta.get("upstream"):
|
|
184
|
+
raise ContractError(
|
|
185
|
+
"invalid_ip",
|
|
186
|
+
f"IP block wraps a nested checkout without upstream "
|
|
187
|
+
f"provenance: {clean_pdk}/{clean_name}.",
|
|
188
|
+
)
|
|
189
|
+
return {
|
|
190
|
+
"pdk": clean_pdk,
|
|
191
|
+
"name": clean_name,
|
|
192
|
+
"entry": str(entry),
|
|
193
|
+
"version": resolved_version,
|
|
194
|
+
"meta": meta,
|
|
195
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:rocketeda:engine:v1:schemas:ip-entry",
|
|
4
|
+
"title": "Engine v1 IP catalogue entry contract",
|
|
5
|
+
"$comment": "Authoritative meta.yaml shape for rocket-blocks catalogue entries (<root>/<pdk>/<entry>/rocket/meta.yaml). This file is the single source: src/project/ip/blocks.py verifies every entry against this exact document at runtime. Filesystem-level rules the schema cannot see stay in resolve_block_ref: the entry must live inside its owning <root>/<pdk>/ scope, the folder name must be a lowercase-with-dashes slug, the meta name must agree with the folder, and an entry wrapping a nested upstream checkout (entry_repo/) must declare upstream provenance.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"IpEntryUpstream": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"url": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"format": "uri",
|
|
14
|
+
"minLength": 1
|
|
15
|
+
},
|
|
16
|
+
"rev": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 1
|
|
19
|
+
},
|
|
20
|
+
"license": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["url", "rev"],
|
|
26
|
+
"description": "Provenance of a wrapped nested upstream checkout (entry_repo/): where it came from, which revision, under which license. Mandatory exactly when entry_repo/ is present."
|
|
27
|
+
},
|
|
28
|
+
"IpEntryPort": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": true,
|
|
31
|
+
"properties": {
|
|
32
|
+
"name": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["name"],
|
|
38
|
+
"description": "One block port (symbol pin or cell terminal). Only the name is contracted today; domain nests standardize later without breaking this shape."
|
|
39
|
+
},
|
|
40
|
+
"IpEntry": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"properties": {
|
|
44
|
+
"name": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
47
|
+
},
|
|
48
|
+
"version": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"minLength": 1,
|
|
51
|
+
"pattern": "^\\S+$"
|
|
52
|
+
},
|
|
53
|
+
"kind": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"minLength": 1
|
|
56
|
+
},
|
|
57
|
+
"ports": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"$ref": "urn:rocketeda:engine:v1#/$defs/IpEntryPort"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"links": {
|
|
64
|
+
"type": "object"
|
|
65
|
+
},
|
|
66
|
+
"license": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"minLength": 1
|
|
69
|
+
},
|
|
70
|
+
"upstream": {
|
|
71
|
+
"$ref": "urn:rocketeda:engine:v1#/$defs/IpEntryUpstream"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"required": ["name", "version"],
|
|
75
|
+
"description": "Catalogue entry identity plus adapter metadata. Top level is closed (unknown keys fail loudly) so future fields arrive through deliberate schema evolution; port nests stay open until their standard lands. name must equal the entry folder slug; version pins the entry revision a registry block resolves to."
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""IP lifecycle: per-PDK registries with symbol/footprint linkage.
|
|
2
|
+
|
|
3
|
+
IP blocks are prebuilt HIERARCHICAL schematic blocks on the
|
|
4
|
+
KiCad-catalogue model (buck converters, digital chips): each
|
|
5
|
+
ships a schematic symbol AND a footprint cell LINKED, and
|
|
6
|
+
"instantiate" places a block with that linkage intact.
|
|
7
|
+
|
|
8
|
+
INVARIANT (RULING-01): prebuilt IP is PER-PDK — each PDK
|
|
9
|
+
strictly owns its blocks. Searches scope to one named PDK;
|
|
10
|
+
instantiating a foreign-PDK block fails unless the block opted
|
|
11
|
+
into scope ``shared`` with a recorded reason (digital blocks
|
|
12
|
+
MAY generalize case-by-case; the default stays per-PDK).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from ...interfaces.errors import ContractError
|
|
16
|
+
|
|
17
|
+
#: Component name registered IP blocks carry on their entity.
|
|
18
|
+
IP_COMPONENT = "IpBlock"
|
|
19
|
+
|
|
20
|
+
#: Component name placed IP instances carry beside Instance.
|
|
21
|
+
IP_LINK_COMPONENT = "IpLink"
|
|
22
|
+
|
|
23
|
+
#: Registry scopes: per-PDK by default, shared by recorded exception.
|
|
24
|
+
SCOPES = ("pdk", "shared")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _need_text(value, what, code):
|
|
28
|
+
"""Accept non-empty text or raise the named code."""
|
|
29
|
+
if not isinstance(value, str) or not value:
|
|
30
|
+
raise ContractError(code, f"{what} must be non-empty text.")
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validate_ip_block(block):
|
|
35
|
+
"""Validate an IpBlock component mapping at edit time.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
block: Block mapping with pdk, name, version, symbol,
|
|
39
|
+
cell, pin_map, scope, reason, and promoted keys.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Normalized IpBlock copy.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ContractError: invalid_ip for any shape violation,
|
|
46
|
+
invalid_scope for a shared scope without a reason.
|
|
47
|
+
"""
|
|
48
|
+
if not isinstance(block, dict):
|
|
49
|
+
raise ContractError(
|
|
50
|
+
"invalid_ip", f"IpBlock must be a mapping, got {block!r}."
|
|
51
|
+
)
|
|
52
|
+
for key in block:
|
|
53
|
+
if key not in ("pdk", "name", "version", "symbol", "cell",
|
|
54
|
+
"pin_map", "scope", "reason", "promoted"):
|
|
55
|
+
raise ContractError(
|
|
56
|
+
"invalid_ip", f"Unknown IpBlock key: {key}."
|
|
57
|
+
)
|
|
58
|
+
pdk = _need_text(block.get("pdk"), "IP pdk", "invalid_pdk")
|
|
59
|
+
name = _need_text(block.get("name"), "IP name", "invalid_ip")
|
|
60
|
+
version = _need_text(block.get("version"), "IP version", "invalid_ip")
|
|
61
|
+
symbol = _need_text(block.get("symbol"), "IP symbol", "invalid_ip")
|
|
62
|
+
cell = _need_text(block.get("cell"), "IP cell", "invalid_ip")
|
|
63
|
+
pin_map = block.get("pin_map", [])
|
|
64
|
+
if not isinstance(pin_map, list) or not all(
|
|
65
|
+
isinstance(entry, dict)
|
|
66
|
+
and set(entry) == {"symPin", "cellPin"}
|
|
67
|
+
and entry.get("symPin") and entry.get("cellPin")
|
|
68
|
+
for entry in pin_map
|
|
69
|
+
):
|
|
70
|
+
raise ContractError(
|
|
71
|
+
"invalid_ip",
|
|
72
|
+
"IP pin_map must list {symPin, cellPin} entries.",
|
|
73
|
+
)
|
|
74
|
+
scope = block.get("scope", "pdk")
|
|
75
|
+
if scope not in SCOPES:
|
|
76
|
+
raise ContractError(
|
|
77
|
+
"invalid_scope", f"IP scope must be one of {SCOPES}."
|
|
78
|
+
)
|
|
79
|
+
reason = block.get("reason", "")
|
|
80
|
+
if not isinstance(reason, str):
|
|
81
|
+
raise ContractError(
|
|
82
|
+
"invalid_scope", "IP shared reason must be text."
|
|
83
|
+
)
|
|
84
|
+
if scope == "shared" and not reason:
|
|
85
|
+
raise ContractError(
|
|
86
|
+
"invalid_scope",
|
|
87
|
+
"Shared-scope IP needs a recorded reason naming why "
|
|
88
|
+
"this digital block generalizes across PDKs.",
|
|
89
|
+
)
|
|
90
|
+
promoted = block.get("promoted", False)
|
|
91
|
+
if not isinstance(promoted, bool):
|
|
92
|
+
raise ContractError(
|
|
93
|
+
"invalid_ip", "IP promoted must be a bool."
|
|
94
|
+
)
|
|
95
|
+
return {
|
|
96
|
+
"pdk": pdk, "name": name, "version": version,
|
|
97
|
+
"symbol": symbol, "cell": cell,
|
|
98
|
+
"pin_map": [dict(entry) for entry in pin_map],
|
|
99
|
+
"scope": scope, "reason": reason, "promoted": promoted,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _all_blocks(store):
|
|
104
|
+
"""List every registered block mapping, (pdk, name, version)-sorted."""
|
|
105
|
+
found = []
|
|
106
|
+
for entity in store.entities_with(IP_COMPONENT):
|
|
107
|
+
try:
|
|
108
|
+
raw = store.get_component(entity, IP_COMPONENT)
|
|
109
|
+
except LookupError:
|
|
110
|
+
continue
|
|
111
|
+
try:
|
|
112
|
+
value = validate_ip_block(raw)
|
|
113
|
+
except ContractError:
|
|
114
|
+
continue
|
|
115
|
+
value["id"] = entity
|
|
116
|
+
found.append(value)
|
|
117
|
+
found.sort(key=lambda item: (item["pdk"], item["name"],
|
|
118
|
+
item["version"]))
|
|
119
|
+
return found
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def ip_register(store, actor, pdk, name, version, symbol, cell,
|
|
123
|
+
pin_map=None, scope="pdk", reason=""):
|
|
124
|
+
"""Register one prebuilt IP block revision under its PDK.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
128
|
+
actor: Attribution recorded on the revision and events.
|
|
129
|
+
pdk: Owning PDK id (the per-PDK invariant key).
|
|
130
|
+
name: Block name within the PDK registry.
|
|
131
|
+
version: Revision label ("" forbidden; latest = the
|
|
132
|
+
greatest label in sort order).
|
|
133
|
+
symbol: Linked schematic symbol reference.
|
|
134
|
+
cell: Linked footprint cell reference.
|
|
135
|
+
pin_map: Explicit [{symPin, cellPin}] entries.
|
|
136
|
+
scope: "pdk" (default) or "shared" with a reason.
|
|
137
|
+
reason: Recorded justification for shared scope.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Receipt with op, actor, revision, ip id, and scope.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
ContractError: invalid_pdk for a missing PDK, ip_exists
|
|
144
|
+
for a duplicate (pdk, name, version), invalid_ip
|
|
145
|
+
for a malformed block, invalid_scope for shared
|
|
146
|
+
scope without a reason.
|
|
147
|
+
"""
|
|
148
|
+
value = validate_ip_block({
|
|
149
|
+
"pdk": pdk, "name": name, "version": version,
|
|
150
|
+
"symbol": symbol, "cell": cell,
|
|
151
|
+
"pin_map": pin_map or [], "scope": scope,
|
|
152
|
+
"reason": reason, "promoted": False,
|
|
153
|
+
})
|
|
154
|
+
for other in _all_blocks(store):
|
|
155
|
+
if (
|
|
156
|
+
other["pdk"] == value["pdk"]
|
|
157
|
+
and other["name"] == value["name"]
|
|
158
|
+
and other["version"] == value["version"]
|
|
159
|
+
):
|
|
160
|
+
raise ContractError(
|
|
161
|
+
"ip_exists",
|
|
162
|
+
f"IP already registered: {pdk}/{name}@{version}.",
|
|
163
|
+
)
|
|
164
|
+
made = store.create_entity(actor, f"ip_register {pdk}/{name}")
|
|
165
|
+
store.attach_component(
|
|
166
|
+
actor, made["id"], IP_COMPONENT, value,
|
|
167
|
+
f"ip_register {pdk}/{name}",
|
|
168
|
+
)
|
|
169
|
+
return {
|
|
170
|
+
"op": "ip_register", "actor": actor,
|
|
171
|
+
"revision": made["revision"], "ip": made["id"],
|
|
172
|
+
"pdk": pdk, "name": name, "version": version,
|
|
173
|
+
"scope": scope,
|
|
174
|
+
"events": [
|
|
175
|
+
{"event": "entity_created", "entity": made["id"]},
|
|
176
|
+
{"event": "component_changed", "entity": made["id"],
|
|
177
|
+
"component": IP_COMPONENT},
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def ip_search(store, pdk, name_filter=None):
|
|
183
|
+
"""Search one PDK's registry (the per-PDK invariant).
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
187
|
+
pdk: Owning PDK id scoping the search (required —
|
|
188
|
+
cross-PDK listing is refused by the invariant).
|
|
189
|
+
name_filter: Optional substring the block name must hold.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Block summaries (pdk, name, version, symbol, cell,
|
|
193
|
+
scope, promoted), sorted by (name, version).
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
ContractError: invalid_pdk for a missing PDK.
|
|
197
|
+
"""
|
|
198
|
+
_need_text(pdk, "IP pdk", "invalid_pdk")
|
|
199
|
+
hits = [
|
|
200
|
+
{
|
|
201
|
+
"id": item["id"], "pdk": item["pdk"], "name": item["name"],
|
|
202
|
+
"version": item["version"], "symbol": item["symbol"],
|
|
203
|
+
"cell": item["cell"], "scope": item["scope"],
|
|
204
|
+
"promoted": item["promoted"],
|
|
205
|
+
}
|
|
206
|
+
for item in _all_blocks(store)
|
|
207
|
+
if item["pdk"] == pdk
|
|
208
|
+
and (name_filter is None or name_filter in item["name"])
|
|
209
|
+
]
|
|
210
|
+
hits.sort(key=lambda item: (item["name"], item["version"]))
|
|
211
|
+
return hits
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def ip_get(store, pdk, name, version=None):
|
|
215
|
+
"""Read one registered block; the latest version by default.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
219
|
+
pdk: Owning PDK id scoping the lookup.
|
|
220
|
+
name: Block name within the PDK registry.
|
|
221
|
+
version: Revision label, or None for the greatest
|
|
222
|
+
label in sort order.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Full block mapping with id.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
ContractError: invalid_pdk for a missing PDK,
|
|
229
|
+
unknown_ip when no such block (or version) exists
|
|
230
|
+
under the PDK.
|
|
231
|
+
"""
|
|
232
|
+
_need_text(pdk, "IP pdk", "invalid_pdk")
|
|
233
|
+
candidates = [
|
|
234
|
+
item for item in _all_blocks(store)
|
|
235
|
+
if item["pdk"] == pdk and item["name"] == name
|
|
236
|
+
]
|
|
237
|
+
if not candidates:
|
|
238
|
+
candidates = [
|
|
239
|
+
item for item in _all_blocks(store)
|
|
240
|
+
if item["name"] == name and item["scope"] == "shared"
|
|
241
|
+
and (version is None or item["version"] == version)
|
|
242
|
+
]
|
|
243
|
+
candidates.sort(key=lambda item: (item["pdk"], item["version"]))
|
|
244
|
+
if version is not None:
|
|
245
|
+
for item in candidates:
|
|
246
|
+
if item["version"] == version:
|
|
247
|
+
return item
|
|
248
|
+
elif candidates:
|
|
249
|
+
return sorted(candidates,
|
|
250
|
+
key=lambda item: item["version"])[-1]
|
|
251
|
+
raise ContractError(
|
|
252
|
+
"unknown_ip",
|
|
253
|
+
f"No such IP block: {pdk}/{name}"
|
|
254
|
+
+ (f"@{version}." if version is not None else "."),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def ip_instantiate(store, actor, pdk, name, sheet, x, y,
|
|
259
|
+
version=None, rotation=0, mirror=False,
|
|
260
|
+
properties=None):
|
|
261
|
+
"""Place one IP block with its symbol/cell linkage intact.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
265
|
+
actor: Attribution recorded on the revision and events.
|
|
266
|
+
pdk: Consuming PDK id (must own the block unless the
|
|
267
|
+
block opted into shared scope).
|
|
268
|
+
name: Block name within the PDK registry.
|
|
269
|
+
sheet: Sheet entity id hosting the instance.
|
|
270
|
+
x: X position in schematic units.
|
|
271
|
+
y: Y position in schematic units.
|
|
272
|
+
version: Revision label, or None for the latest.
|
|
273
|
+
rotation: Rotation in degrees (0/90/180/270).
|
|
274
|
+
mirror: Mirror flag.
|
|
275
|
+
properties: Text-to-text instance properties.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Receipt with op, actor, revision, instance id, and
|
|
279
|
+
the resolved (pdk, name, version).
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
ContractError: unknown_ip for a missing block,
|
|
283
|
+
pdk_mismatch when a per-PDK block is asked from a
|
|
284
|
+
foreign PDK, unknown_sheet for a missing sheet,
|
|
285
|
+
invalid_instance for a malformed placement.
|
|
286
|
+
"""
|
|
287
|
+
block = ip_get(store, pdk, name, version)
|
|
288
|
+
if block["pdk"] != pdk and block["scope"] != "shared":
|
|
289
|
+
raise ContractError(
|
|
290
|
+
"pdk_mismatch",
|
|
291
|
+
f"IP {block['pdk']}/{name} is per-PDK and cannot "
|
|
292
|
+
f"instantiate under {pdk!r}.",
|
|
293
|
+
)
|
|
294
|
+
try:
|
|
295
|
+
store.get_component(sheet, "Sheet")
|
|
296
|
+
except LookupError:
|
|
297
|
+
raise ContractError(
|
|
298
|
+
"unknown_sheet", f"No such sheet entity: {sheet!r}."
|
|
299
|
+
) from None
|
|
300
|
+
if rotation not in (0, 90, 180, 270):
|
|
301
|
+
raise ContractError(
|
|
302
|
+
"invalid_instance",
|
|
303
|
+
"Instance rotation must be one of (0, 90, 180, 270).",
|
|
304
|
+
)
|
|
305
|
+
if not isinstance(mirror, bool):
|
|
306
|
+
raise ContractError(
|
|
307
|
+
"invalid_instance", "Instance mirror must be a bool."
|
|
308
|
+
)
|
|
309
|
+
clean_props = properties or {}
|
|
310
|
+
if not isinstance(clean_props, dict) or not all(
|
|
311
|
+
isinstance(key, str) and isinstance(val, str)
|
|
312
|
+
for key, val in clean_props.items()
|
|
313
|
+
):
|
|
314
|
+
raise ContractError(
|
|
315
|
+
"invalid_instance",
|
|
316
|
+
"Instance properties must map text to text.",
|
|
317
|
+
)
|
|
318
|
+
made = store.create_entity(actor, f"ip_instantiate {pdk}/{name}")
|
|
319
|
+
store.attach_component(
|
|
320
|
+
actor, made["id"], "Instance",
|
|
321
|
+
{"sheet": sheet, "symbol": block["symbol"], "x": x, "y": y,
|
|
322
|
+
"rotation": rotation, "mirror": mirror,
|
|
323
|
+
"properties": dict(clean_props), "ref": ""},
|
|
324
|
+
f"ip_instantiate {pdk}/{name}",
|
|
325
|
+
)
|
|
326
|
+
store.attach_component(
|
|
327
|
+
actor, made["id"], IP_LINK_COMPONENT,
|
|
328
|
+
{"pdk": block["pdk"], "name": name,
|
|
329
|
+
"version": block["version"], "symbol": block["symbol"],
|
|
330
|
+
"cell": block["cell"], "pin_map": block["pin_map"],
|
|
331
|
+
"scope": block["scope"]},
|
|
332
|
+
f"ip_instantiate {pdk}/{name}",
|
|
333
|
+
)
|
|
334
|
+
return {
|
|
335
|
+
"op": "ip_instantiate", "actor": actor,
|
|
336
|
+
"revision": made["revision"], "instance": made["id"],
|
|
337
|
+
"pdk": block["pdk"], "name": name,
|
|
338
|
+
"version": block["version"],
|
|
339
|
+
"events": [
|
|
340
|
+
{"event": "entity_created", "entity": made["id"]},
|
|
341
|
+
{"event": "component_changed", "entity": made["id"],
|
|
342
|
+
"component": "Instance"},
|
|
343
|
+
{"event": "component_changed", "entity": made["id"],
|
|
344
|
+
"component": IP_LINK_COMPONENT},
|
|
345
|
+
],
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def ip_promote(store, actor, pdk, name, version):
|
|
350
|
+
"""Promote one block version as the PDK registry's recommended one.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
store: Entity store serving the ENGINE-ECS seam.
|
|
354
|
+
actor: Attribution recorded on the revision and events.
|
|
355
|
+
pdk: Owning PDK id scoping the registry.
|
|
356
|
+
name: Block name within the PDK registry.
|
|
357
|
+
version: Revision label to promote.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Receipt with op, actor, revision, ip id, and the
|
|
361
|
+
promoted flag.
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
ContractError: unknown_ip when no such block version
|
|
365
|
+
exists under the PDK.
|
|
366
|
+
"""
|
|
367
|
+
_need_text(pdk, "IP pdk", "invalid_pdk")
|
|
368
|
+
siblings = [
|
|
369
|
+
item for item in _all_blocks(store)
|
|
370
|
+
if item["pdk"] == pdk and item["name"] == name
|
|
371
|
+
]
|
|
372
|
+
target = None
|
|
373
|
+
for item in siblings:
|
|
374
|
+
if item["version"] == version:
|
|
375
|
+
target = item
|
|
376
|
+
if target is None:
|
|
377
|
+
raise ContractError(
|
|
378
|
+
"unknown_ip",
|
|
379
|
+
f"No such IP block: {pdk}/{name}@{version}.",
|
|
380
|
+
)
|
|
381
|
+
head = None
|
|
382
|
+
for item in siblings:
|
|
383
|
+
want = item["id"] == target["id"]
|
|
384
|
+
if item["promoted"] != want:
|
|
385
|
+
clean = validate_ip_block({
|
|
386
|
+
"pdk": item["pdk"], "name": item["name"],
|
|
387
|
+
"version": item["version"], "symbol": item["symbol"],
|
|
388
|
+
"cell": item["cell"], "pin_map": item["pin_map"],
|
|
389
|
+
"scope": item["scope"], "reason": item["reason"],
|
|
390
|
+
"promoted": want,
|
|
391
|
+
})
|
|
392
|
+
changed = store.set_component(
|
|
393
|
+
actor, item["id"], IP_COMPONENT, clean,
|
|
394
|
+
f"ip_promote {pdk}/{name}@{version}",
|
|
395
|
+
)
|
|
396
|
+
head = changed["revision"]
|
|
397
|
+
return {
|
|
398
|
+
"op": "ip_promote", "actor": actor,
|
|
399
|
+
"revision": head, "ip": target["id"], "pdk": pdk,
|
|
400
|
+
"name": name, "version": version, "promoted": True,
|
|
401
|
+
"events": [],
|
|
402
|
+
}
|