librelane 2.4.0.dev0__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.
Potentially problematic release.
This version of librelane might be problematic. Click here for more details.
- librelane/__init__.py +38 -0
- librelane/__main__.py +470 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +61 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +245 -0
- librelane/common/generic_dict.py +319 -0
- librelane/common/metrics/__init__.py +35 -0
- librelane/common/metrics/__main__.py +413 -0
- librelane/common/metrics/library.py +354 -0
- librelane/common/metrics/metric.py +186 -0
- librelane/common/metrics/util.py +279 -0
- librelane/common/misc.py +402 -0
- librelane/common/ring_buffer.py +63 -0
- librelane/common/tcl.py +80 -0
- librelane/common/toolbox.py +549 -0
- librelane/common/tpe.py +41 -0
- librelane/common/types.py +117 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +158 -0
- librelane/config/config.py +1025 -0
- librelane/config/flow.py +490 -0
- librelane/config/pdk_compat.py +255 -0
- librelane/config/preprocessor.py +464 -0
- librelane/config/removals.py +45 -0
- librelane/config/variable.py +722 -0
- librelane/container.py +264 -0
- librelane/env_info.py +306 -0
- librelane/examples/spm/config.yaml +33 -0
- librelane/examples/spm/pin_order.cfg +14 -0
- librelane/examples/spm/src/impl.sdc +73 -0
- librelane/examples/spm/src/signoff.sdc +68 -0
- librelane/examples/spm/src/spm.v +73 -0
- librelane/examples/spm/verify/spm_tb.v +106 -0
- librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
- librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
- librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
- librelane/examples/spm-user_project_wrapper/config.json +13 -0
- librelane/examples/spm-user_project_wrapper/defines.v +66 -0
- librelane/examples/spm-user_project_wrapper/template.def +7656 -0
- librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
- librelane/flows/__init__.py +24 -0
- librelane/flows/builtins.py +18 -0
- librelane/flows/classic.py +330 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +985 -0
- librelane/flows/misc.py +71 -0
- librelane/flows/optimizing.py +179 -0
- librelane/flows/sequential.py +367 -0
- librelane/flows/synth_explore.py +173 -0
- librelane/logging/__init__.py +40 -0
- librelane/logging/logger.py +323 -0
- librelane/open_pdks_rev +1 -0
- librelane/plugins.py +21 -0
- librelane/py.typed +0 -0
- librelane/scripts/base.sdc +80 -0
- librelane/scripts/klayout/Readme.md +2 -0
- librelane/scripts/klayout/open_design.py +63 -0
- librelane/scripts/klayout/render.py +121 -0
- librelane/scripts/klayout/stream_out.py +176 -0
- librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
- librelane/scripts/klayout/xor.drc +120 -0
- librelane/scripts/magic/Readme.md +1 -0
- librelane/scripts/magic/common/read.tcl +114 -0
- librelane/scripts/magic/def/antenna_check.tcl +35 -0
- librelane/scripts/magic/def/mag.tcl +19 -0
- librelane/scripts/magic/def/mag_gds.tcl +81 -0
- librelane/scripts/magic/drc.tcl +79 -0
- librelane/scripts/magic/extract_spice.tcl +98 -0
- librelane/scripts/magic/gds/drc_batch.tcl +74 -0
- librelane/scripts/magic/gds/erase_box.tcl +32 -0
- librelane/scripts/magic/gds/extras_mag.tcl +47 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
- librelane/scripts/magic/lef/maglef.tcl +27 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +19 -0
- librelane/scripts/netgen/setup.tcl +28 -0
- librelane/scripts/odbpy/apply_def_template.py +49 -0
- librelane/scripts/odbpy/cell_frequency.py +107 -0
- librelane/scripts/odbpy/check_antenna_properties.py +116 -0
- librelane/scripts/odbpy/contextualize.py +109 -0
- librelane/scripts/odbpy/defutil.py +574 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/exception_codes.py +17 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/label_macro_pins.py +277 -0
- librelane/scripts/odbpy/lefutil.py +97 -0
- librelane/scripts/odbpy/placers.py +162 -0
- librelane/scripts/odbpy/power_utils.py +395 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +246 -0
- librelane/scripts/odbpy/remove_buffers.py +173 -0
- librelane/scripts/odbpy/snap_to_grid.py +57 -0
- librelane/scripts/odbpy/wire_lengths.py +93 -0
- librelane/scripts/openroad/antenna_check.tcl +20 -0
- librelane/scripts/openroad/antenna_repair.tcl +31 -0
- librelane/scripts/openroad/basic_mp.tcl +24 -0
- librelane/scripts/openroad/buffer_list.tcl +10 -0
- librelane/scripts/openroad/common/dpl.tcl +24 -0
- librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
- librelane/scripts/openroad/common/grt.tcl +32 -0
- librelane/scripts/openroad/common/io.tcl +476 -0
- librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
- librelane/scripts/openroad/common/resizer.tcl +103 -0
- librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
- librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
- librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
- librelane/scripts/openroad/common/set_rc.tcl +75 -0
- librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
- librelane/scripts/openroad/cts.tcl +80 -0
- librelane/scripts/openroad/cut_rows.tcl +24 -0
- librelane/scripts/openroad/dpl.tcl +24 -0
- librelane/scripts/openroad/drt.tcl +37 -0
- librelane/scripts/openroad/fill.tcl +30 -0
- librelane/scripts/openroad/floorplan.tcl +145 -0
- librelane/scripts/openroad/gpl.tcl +88 -0
- librelane/scripts/openroad/grt.tcl +30 -0
- librelane/scripts/openroad/gui.tcl +15 -0
- librelane/scripts/openroad/insert_buffer.tcl +127 -0
- librelane/scripts/openroad/ioplacer.tcl +67 -0
- librelane/scripts/openroad/irdrop.tcl +51 -0
- librelane/scripts/openroad/pdn.tcl +52 -0
- librelane/scripts/openroad/rcx.tcl +32 -0
- librelane/scripts/openroad/repair_design.tcl +70 -0
- librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
- librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
- librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
- librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
- librelane/scripts/openroad/sta/corner.tcl +393 -0
- librelane/scripts/openroad/tapcell.tcl +25 -0
- librelane/scripts/openroad/write_views.tcl +27 -0
- librelane/scripts/pyosys/construct_abc_script.py +177 -0
- librelane/scripts/pyosys/json_header.py +84 -0
- librelane/scripts/pyosys/synthesize.py +493 -0
- librelane/scripts/pyosys/ys_common.py +153 -0
- librelane/scripts/tclsh/hello.tcl +1 -0
- librelane/state/__init__.py +24 -0
- librelane/state/__main__.py +61 -0
- librelane/state/design_format.py +180 -0
- librelane/state/state.py +351 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +511 -0
- librelane/steps/checker.py +637 -0
- librelane/steps/common_variables.py +340 -0
- librelane/steps/cvc_rv.py +169 -0
- librelane/steps/klayout.py +509 -0
- librelane/steps/magic.py +566 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +955 -0
- librelane/steps/openroad.py +2433 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +629 -0
- librelane/steps/step.py +1547 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dev0.dist-info/METADATA +151 -0
- librelane-2.4.0.dev0.dist-info/RECORD +166 -0
- librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dev0.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,1025 @@
|
|
|
1
|
+
# Copyright 2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import json
|
|
16
|
+
import yaml
|
|
17
|
+
from yamlcore import CCoreLoader
|
|
18
|
+
import dataclasses
|
|
19
|
+
from glob import glob
|
|
20
|
+
from decimal import Decimal
|
|
21
|
+
from textwrap import dedent
|
|
22
|
+
from functools import lru_cache
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import (
|
|
25
|
+
Any,
|
|
26
|
+
ClassVar,
|
|
27
|
+
Literal,
|
|
28
|
+
Mapping,
|
|
29
|
+
Tuple,
|
|
30
|
+
Union,
|
|
31
|
+
List,
|
|
32
|
+
Optional,
|
|
33
|
+
Sequence,
|
|
34
|
+
Dict,
|
|
35
|
+
Set,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from .variable import Variable, MissingRequiredVariable
|
|
39
|
+
from .removals import removed_variables
|
|
40
|
+
from .flow import pdk_variables, scl_variables, flow_common_variables
|
|
41
|
+
from .pdk_compat import migrate_old_config
|
|
42
|
+
from .preprocessor import preprocess_dict, Keys as SpecialKeys
|
|
43
|
+
from ..logging import info, warn
|
|
44
|
+
from ..__version__ import __version__
|
|
45
|
+
from ..common import (
|
|
46
|
+
GenericDict,
|
|
47
|
+
GenericImmutableDict,
|
|
48
|
+
TclUtils,
|
|
49
|
+
AnyPath,
|
|
50
|
+
is_string,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
AnyConfig = Union[AnyPath, Mapping[str, Any]]
|
|
54
|
+
AnyConfigs = Union[AnyConfig, Sequence[AnyConfig]]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _OpenLaneYAMLLoader(CCoreLoader):
|
|
58
|
+
def construct_yaml_float(self, node: yaml.ScalarNode) -> Decimal: # type: ignore
|
|
59
|
+
value = str(self.construct_scalar(node))
|
|
60
|
+
value = value.replace("_", "").lower()
|
|
61
|
+
sign = +1
|
|
62
|
+
if value[0] == "-":
|
|
63
|
+
sign = -1
|
|
64
|
+
if value[0] in "+-":
|
|
65
|
+
value = value[1:]
|
|
66
|
+
if value == ".inf":
|
|
67
|
+
return sign * Decimal("Infinity")
|
|
68
|
+
elif value == ".nan":
|
|
69
|
+
return Decimal("nan")
|
|
70
|
+
else:
|
|
71
|
+
return sign * Decimal(value)
|
|
72
|
+
|
|
73
|
+
def __init__(self, stream) -> None:
|
|
74
|
+
super().__init__(stream)
|
|
75
|
+
self.add_constructor(
|
|
76
|
+
"tag:yaml.org,2002:float",
|
|
77
|
+
constructor=_OpenLaneYAMLLoader.construct_yaml_float,
|
|
78
|
+
)
|
|
79
|
+
# print(list(self.yaml_implicit_resolvers.keys()))
|
|
80
|
+
# del self.yaml_implicit_resolvers["tag:yaml.org,2002:float"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class UnknownExtensionError(ValueError):
|
|
84
|
+
"""
|
|
85
|
+
When a passed configuration file has an unrecognized extension, i.e.,
|
|
86
|
+
not .json, .yml/.yaml or .tcl.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, config: AnyPath) -> None:
|
|
90
|
+
self.config = str(config)
|
|
91
|
+
_, ext = os.path.splitext(config)
|
|
92
|
+
super().__init__(
|
|
93
|
+
f"Unsupported configuration file extension '{ext}' for '{config}'."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class PassedDirectoryError(ValueError):
|
|
98
|
+
"""
|
|
99
|
+
When a passed configuration file is in fact a directory.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, config: AnyPath) -> None:
|
|
103
|
+
self.config = str(config)
|
|
104
|
+
super().__init__(
|
|
105
|
+
"Passing design directories as arguments is unsupported in LibreLane 2 or higher: please pass the configuration file(s) directly."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _validate_config_file(config: AnyPath) -> Literal["json", "tcl", "yaml"]:
|
|
110
|
+
config = str(config)
|
|
111
|
+
if config.endswith(".tcl"):
|
|
112
|
+
return "tcl"
|
|
113
|
+
elif config.endswith(".json"):
|
|
114
|
+
return "json"
|
|
115
|
+
elif config.endswith(".yml") or config.endswith(".yaml"):
|
|
116
|
+
return "yaml"
|
|
117
|
+
elif os.path.isdir(config):
|
|
118
|
+
raise PassedDirectoryError(config)
|
|
119
|
+
else:
|
|
120
|
+
raise UnknownExtensionError(config)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class InvalidConfig(ValueError):
|
|
124
|
+
"""
|
|
125
|
+
An error raised when a configuration under resolution is invalid.
|
|
126
|
+
|
|
127
|
+
:param config: A human-readable name for the particular configuration file
|
|
128
|
+
causing this exception, i.e. whether it's a PDK configuration file or a
|
|
129
|
+
user configuration file.
|
|
130
|
+
:param warnings: A list of warnings generated during the loading of this
|
|
131
|
+
configuration file.
|
|
132
|
+
:param errors: A list of errors generated during the loading of this
|
|
133
|
+
configuration file.
|
|
134
|
+
:param args: Further arguments to be passed onto the constructor of
|
|
135
|
+
:class:`ValueError`.
|
|
136
|
+
:param message: An optional override for the Exception message.
|
|
137
|
+
:param kwargs: Further keyword arguments to be passed onto the constructor of
|
|
138
|
+
:class:`ValueError`.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
config: str,
|
|
144
|
+
warnings: List[str],
|
|
145
|
+
errors: List[str],
|
|
146
|
+
message: Optional[str] = None,
|
|
147
|
+
*args,
|
|
148
|
+
**kwargs,
|
|
149
|
+
) -> None:
|
|
150
|
+
self.config = config
|
|
151
|
+
self.warnings = warnings
|
|
152
|
+
self.errors = errors
|
|
153
|
+
if message is None:
|
|
154
|
+
message = "The following errors were encountered: \n"
|
|
155
|
+
for error in self.errors:
|
|
156
|
+
message += f"\t* {error}\n"
|
|
157
|
+
message = message.strip()
|
|
158
|
+
super().__init__(message, *args, **kwargs)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass
|
|
162
|
+
class Meta:
|
|
163
|
+
"""
|
|
164
|
+
Constitutes metadata for a configuration object.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
version: int = 1
|
|
168
|
+
flow: Union[None, str, List[str]] = None
|
|
169
|
+
substituting_steps: Union[None, Dict[str, Union[str, None]]] = None
|
|
170
|
+
step: Union[None, str] = None
|
|
171
|
+
librelane_version: Union[None, str] = __version__
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def from_dict(Self, meta_dict: dict):
|
|
175
|
+
meta_dict_copy = meta_dict.copy()
|
|
176
|
+
if "openlane_version" in meta_dict_copy:
|
|
177
|
+
meta_dict_copy["librelane_version"] = meta_dict_copy["openlane_version"]
|
|
178
|
+
del meta_dict_copy["openlane_version"]
|
|
179
|
+
return Self(**meta_dict_copy)
|
|
180
|
+
|
|
181
|
+
def copy(self) -> "Meta":
|
|
182
|
+
return dataclasses.replace(self)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Config(GenericImmutableDict[str, Any]):
|
|
186
|
+
"""
|
|
187
|
+
A map from LibreLane configuration variable keys to their values.
|
|
188
|
+
|
|
189
|
+
It is recommended that you use :meth:`load` to create new, validated
|
|
190
|
+
configurations from dictionaries or files.
|
|
191
|
+
|
|
192
|
+
:param meta: The :class:`Meta` object for this configuration. If ``None`` is
|
|
193
|
+
passed, the default Meta object will be assigned.
|
|
194
|
+
:param final: Whether the configuration is final (i.e. has been
|
|
195
|
+
pre-assembled for an entire flow) or may be incremented per-step.
|
|
196
|
+
|
|
197
|
+
Final configurations may not be adjusted or incremented.
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
current_interactive: ClassVar[Optional["Config"]] = None
|
|
202
|
+
meta: Meta
|
|
203
|
+
|
|
204
|
+
def __init__(
|
|
205
|
+
self,
|
|
206
|
+
*args,
|
|
207
|
+
meta: Optional[Meta] = None,
|
|
208
|
+
**kwargs,
|
|
209
|
+
):
|
|
210
|
+
if meta is None:
|
|
211
|
+
meta = Meta(version=1)
|
|
212
|
+
|
|
213
|
+
self.meta = meta
|
|
214
|
+
|
|
215
|
+
super().__init__(*args, **kwargs)
|
|
216
|
+
|
|
217
|
+
def copy(self, **overrides) -> "Config":
|
|
218
|
+
"""
|
|
219
|
+
Produces a *shallow* copy of the configuration object.
|
|
220
|
+
|
|
221
|
+
:param overrides: A series of configuration overrides as key-value pairs.
|
|
222
|
+
These values are NOT validated and you should not be overriding these
|
|
223
|
+
haphazardly.
|
|
224
|
+
"""
|
|
225
|
+
return Config(self, meta=self.meta, overrides=overrides)
|
|
226
|
+
|
|
227
|
+
def to_raw_dict(self, include_meta: bool = True) -> Dict[str, Any]:
|
|
228
|
+
"""
|
|
229
|
+
:param include_meta: Whether to include the "meta" object or not
|
|
230
|
+
:returns: A raw dictionary representation including the ``meta`` object.
|
|
231
|
+
"""
|
|
232
|
+
final = super().to_raw_dict()
|
|
233
|
+
if include_meta:
|
|
234
|
+
final["meta"] = self.meta
|
|
235
|
+
return final
|
|
236
|
+
|
|
237
|
+
def dumps(self, include_meta: bool = True, **kwargs) -> str:
|
|
238
|
+
"""
|
|
239
|
+
:param include_meta: Whether to include the ``meta`` object in the
|
|
240
|
+
serialized string.
|
|
241
|
+
:param kwargs: Passed to ``json.dumps``.
|
|
242
|
+
:returns: A JSON string representing the the GenericDict object.
|
|
243
|
+
"""
|
|
244
|
+
if "indent" not in kwargs:
|
|
245
|
+
kwargs["indent"] = 4
|
|
246
|
+
return json.dumps(
|
|
247
|
+
self.to_raw_dict(include_meta), cls=self.get_encoder(), **kwargs
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def copy_filtered(
|
|
251
|
+
self,
|
|
252
|
+
config_vars: Sequence[Variable],
|
|
253
|
+
include_flow_variables: bool = True,
|
|
254
|
+
) -> "Config":
|
|
255
|
+
"""
|
|
256
|
+
Creates a new copy of the configuration object, but only with the
|
|
257
|
+
configuration variables defined by the parameter.
|
|
258
|
+
|
|
259
|
+
:param config_vars: A list of configuration variables to include in
|
|
260
|
+
the filtered copy.
|
|
261
|
+
:param include_flow_variables: Whether to include the common flow
|
|
262
|
+
variables in the copy or not.
|
|
263
|
+
|
|
264
|
+
This parameter is deprecated as of LibreLane 2.0.0b5 and should be
|
|
265
|
+
set to ``False`` by callers.
|
|
266
|
+
:returns: The new copy
|
|
267
|
+
"""
|
|
268
|
+
variables: Set[str] = set([variable.name for variable in config_vars])
|
|
269
|
+
if include_flow_variables:
|
|
270
|
+
variables = variables.union(
|
|
271
|
+
set([variable.name for variable in flow_common_variables])
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return Config(
|
|
275
|
+
{variable: self[variable] for variable in variables},
|
|
276
|
+
meta=dataclasses.replace(self.meta),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def with_increment(
|
|
280
|
+
self,
|
|
281
|
+
config_vars: Sequence[Variable],
|
|
282
|
+
other_inputs: Mapping[str, Any],
|
|
283
|
+
config_quiet: bool = False,
|
|
284
|
+
) -> "Config":
|
|
285
|
+
"""
|
|
286
|
+
Creates a new ``Config`` object by copying all values
|
|
287
|
+
from the original in addition to any new variables (and removing
|
|
288
|
+
any variables not in `config_vars`).
|
|
289
|
+
|
|
290
|
+
Furthermore, inputs can be provided incrementally by passing the object
|
|
291
|
+
``other_inputs``, which will also use these as overrides to the
|
|
292
|
+
values in the base ``Config`` object.
|
|
293
|
+
|
|
294
|
+
All values, including those in the base ``Config`` object and in
|
|
295
|
+
``other_inputs``, will be re-validated.
|
|
296
|
+
|
|
297
|
+
:param config_vars: A list of configuration variables to include and
|
|
298
|
+
validate.
|
|
299
|
+
:param other_inputs: A mapping of other inputs.
|
|
300
|
+
:returns: The new ``Config`` object
|
|
301
|
+
"""
|
|
302
|
+
incremental_pdk_vars = [variable for variable in config_vars if variable.pdk]
|
|
303
|
+
|
|
304
|
+
mutable, _, _ = self.__get_pdk_config(
|
|
305
|
+
self["PDK"],
|
|
306
|
+
self["STD_CELL_LIBRARY"],
|
|
307
|
+
self["PDK_ROOT"],
|
|
308
|
+
incremental_pdk_vars,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
mutable.update(self)
|
|
312
|
+
mutable.update(other_inputs)
|
|
313
|
+
|
|
314
|
+
processed, design_warnings, design_errors = Config.__process_variable_list(
|
|
315
|
+
mutable,
|
|
316
|
+
config_vars,
|
|
317
|
+
removed_variables,
|
|
318
|
+
on_unknown_key=None,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if len(design_errors) != 0:
|
|
322
|
+
raise InvalidConfig(
|
|
323
|
+
"incremental configuration", design_warnings, design_errors
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if not config_quiet:
|
|
327
|
+
if len(design_warnings) > 0:
|
|
328
|
+
info(
|
|
329
|
+
"Loading the incremental configuration has generated the following warnings:"
|
|
330
|
+
)
|
|
331
|
+
for warning in design_warnings:
|
|
332
|
+
warn(warning)
|
|
333
|
+
|
|
334
|
+
return Config(
|
|
335
|
+
processed,
|
|
336
|
+
meta=self.meta.copy(),
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
@classmethod
|
|
340
|
+
def get_meta(
|
|
341
|
+
Self,
|
|
342
|
+
config_in: AnyConfig,
|
|
343
|
+
flow_override: Optional[str] = None,
|
|
344
|
+
) -> Meta:
|
|
345
|
+
"""
|
|
346
|
+
Returns the Meta object of a configuration dictionary or file.
|
|
347
|
+
|
|
348
|
+
:param config_in: A configuration object or file.
|
|
349
|
+
:returns: Either a Meta object, or if the file is invalid, None.
|
|
350
|
+
"""
|
|
351
|
+
default_meta_version = 2
|
|
352
|
+
|
|
353
|
+
if is_string(config_in):
|
|
354
|
+
config_in = str(config_in)
|
|
355
|
+
validated_type = _validate_config_file(config_in)
|
|
356
|
+
if validated_type == "tcl":
|
|
357
|
+
default_meta_version = 1
|
|
358
|
+
return Meta(version=default_meta_version)
|
|
359
|
+
elif validated_type == "json":
|
|
360
|
+
default_meta_version = 1
|
|
361
|
+
config_in = json.load(open(config_in, encoding="utf8"))
|
|
362
|
+
elif validated_type == "yaml":
|
|
363
|
+
config_in = yaml.load(
|
|
364
|
+
open(config_in, encoding="utf8"),
|
|
365
|
+
Loader=_OpenLaneYAMLLoader,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
assert not isinstance(config_in, str)
|
|
369
|
+
assert not isinstance(config_in, os.PathLike)
|
|
370
|
+
|
|
371
|
+
meta = Meta(version=default_meta_version)
|
|
372
|
+
if meta_raw := config_in.get("meta"):
|
|
373
|
+
meta = Meta.from_dict(meta_raw)
|
|
374
|
+
|
|
375
|
+
if flow_override is not None:
|
|
376
|
+
meta.flow = flow_override
|
|
377
|
+
|
|
378
|
+
return meta
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def interactive(
|
|
382
|
+
Self,
|
|
383
|
+
DESIGN_NAME: str,
|
|
384
|
+
PDK: str,
|
|
385
|
+
STD_CELL_LIBRARY: Optional[str] = None,
|
|
386
|
+
PDK_ROOT: Optional[str] = None,
|
|
387
|
+
**kwargs,
|
|
388
|
+
) -> "Config":
|
|
389
|
+
"""
|
|
390
|
+
This constructs a partial configuration object that may be incrementally
|
|
391
|
+
adjusted per-step, and activates LibreLane's **interactive mode**.
|
|
392
|
+
|
|
393
|
+
The interactive mode is overall less rigid than the pure mode, adding various
|
|
394
|
+
references to global objects to make the REPL or Notebook experience more
|
|
395
|
+
pleasant, however, it is not as resilient as the pure mode and should not
|
|
396
|
+
be used in production code.
|
|
397
|
+
|
|
398
|
+
:param DESIGN_NAME: The name of the design to be used.
|
|
399
|
+
:param PDK: The name of the PDK.
|
|
400
|
+
:param STD_CELL_LIBRARY: The name of the standard cell library.
|
|
401
|
+
|
|
402
|
+
If not specified, the PDK's default SCL will be used.
|
|
403
|
+
:param PDK_ROOT: Required if Volare is not installed.
|
|
404
|
+
|
|
405
|
+
If Volare is installed, this value can be used to optionally override
|
|
406
|
+
Volare's default.
|
|
407
|
+
|
|
408
|
+
:param kwargs: Any overrides to PDK values and/or common flow default variables
|
|
409
|
+
can be passed as keyword arguments to this function.
|
|
410
|
+
|
|
411
|
+
Useful examples are CLOCK_PORT, CLOCK_PERIOD, et cetera, which while
|
|
412
|
+
not bound to a specific :class:`Step`, affects most Steps' behavior.
|
|
413
|
+
"""
|
|
414
|
+
PDK_ROOT = Self.__resolve_pdk_root(PDK_ROOT)
|
|
415
|
+
|
|
416
|
+
raw, _, _ = Self.__get_pdk_config(
|
|
417
|
+
PDK,
|
|
418
|
+
STD_CELL_LIBRARY,
|
|
419
|
+
PDK_ROOT,
|
|
420
|
+
pdk_variables + scl_variables,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
kwargs["DESIGN_NAME"] = DESIGN_NAME
|
|
424
|
+
kwargs["DESIGN_DIR"] = kwargs.get("DESIGN_DIR", ".")
|
|
425
|
+
|
|
426
|
+
raw.update(kwargs)
|
|
427
|
+
|
|
428
|
+
processed, design_warnings, design_errors = Config.__process_variable_list(
|
|
429
|
+
raw,
|
|
430
|
+
flow_common_variables,
|
|
431
|
+
removed_variables,
|
|
432
|
+
on_unknown_key="error",
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if len(design_errors) != 0:
|
|
436
|
+
raise InvalidConfig("default configuration", design_warnings, design_errors)
|
|
437
|
+
|
|
438
|
+
if len(design_warnings) > 0:
|
|
439
|
+
info(
|
|
440
|
+
"Loading the default configuration has generated the following warnings:"
|
|
441
|
+
)
|
|
442
|
+
for warning in design_warnings:
|
|
443
|
+
warn(warning)
|
|
444
|
+
|
|
445
|
+
Config.current_interactive = Config(processed)
|
|
446
|
+
|
|
447
|
+
return Config.current_interactive
|
|
448
|
+
|
|
449
|
+
@classmethod
|
|
450
|
+
def load(
|
|
451
|
+
Self,
|
|
452
|
+
config_in: AnyConfigs,
|
|
453
|
+
flow_config_vars: Sequence[Variable],
|
|
454
|
+
*,
|
|
455
|
+
config_override_strings: Optional[Sequence[str]] = None,
|
|
456
|
+
pdk: Optional[str] = None,
|
|
457
|
+
pdk_root: Optional[str] = None,
|
|
458
|
+
scl: Optional[str] = None,
|
|
459
|
+
design_dir: Optional[str] = None,
|
|
460
|
+
_load_pdk_configs: bool = True,
|
|
461
|
+
) -> Tuple["Config", str]:
|
|
462
|
+
"""
|
|
463
|
+
Creates a new Config object based on a Tcl file, a JSON file, or a
|
|
464
|
+
dictionary.
|
|
465
|
+
|
|
466
|
+
The returned config object is locked and cannot be modified.
|
|
467
|
+
|
|
468
|
+
:param config_in: Either a file path to a JSON file or a Python
|
|
469
|
+
Mapping object (such as ``dict``) representing an unprocessed
|
|
470
|
+
LibreLane configuration object.
|
|
471
|
+
|
|
472
|
+
Tcl files are also supported, but are deprecated and will be removed
|
|
473
|
+
in the future.
|
|
474
|
+
|
|
475
|
+
:param config_override_strings: A list of "overrides" in the form of
|
|
476
|
+
NAME=VALUE strings. These are primarily for running LibreLane from
|
|
477
|
+
the command-line and strictly speaking should not be used in the API.
|
|
478
|
+
|
|
479
|
+
:param design_dir: The design directory for said configuration(s).
|
|
480
|
+
|
|
481
|
+
If not explicitly provided, the design directory will be the
|
|
482
|
+
directory holding the last file in the list.
|
|
483
|
+
|
|
484
|
+
If no files are provided, this argument is required.
|
|
485
|
+
|
|
486
|
+
:param pdk: A process design kit to use. Required unless specified via the
|
|
487
|
+
"PDK" key in a configuration object.
|
|
488
|
+
|
|
489
|
+
:param pdk_root: Required if Volare is not installed.
|
|
490
|
+
|
|
491
|
+
If Volare is installed, this value can be used to optionally override
|
|
492
|
+
Volare's default.
|
|
493
|
+
|
|
494
|
+
:param scl: A standard cell library to use. If not specified, the PDK's
|
|
495
|
+
default standard cell library will be used instead.
|
|
496
|
+
|
|
497
|
+
:returns: A tuple containing a Config object and the design directory.
|
|
498
|
+
"""
|
|
499
|
+
if isinstance(config_in, Mapping):
|
|
500
|
+
config_in = [config_in]
|
|
501
|
+
elif is_string(config_in):
|
|
502
|
+
config_in = [str(config_in)]
|
|
503
|
+
|
|
504
|
+
assert not isinstance(config_in, str)
|
|
505
|
+
assert not isinstance(config_in, os.PathLike)
|
|
506
|
+
|
|
507
|
+
if len(config_in) == 0:
|
|
508
|
+
raise ValueError("The value for config_in must not be empty.")
|
|
509
|
+
|
|
510
|
+
file_design_dir = None
|
|
511
|
+
configs_validated: List[AnyConfig] = []
|
|
512
|
+
for config in config_in:
|
|
513
|
+
if isinstance(config, Mapping):
|
|
514
|
+
configs_validated.append(config)
|
|
515
|
+
# Path
|
|
516
|
+
else:
|
|
517
|
+
config = str(config)
|
|
518
|
+
_validate_config_file(config)
|
|
519
|
+
config_abspath = os.path.abspath(config)
|
|
520
|
+
file_design_dir = os.path.dirname(config_abspath)
|
|
521
|
+
configs_validated.append(config_abspath)
|
|
522
|
+
|
|
523
|
+
design_dir = design_dir or file_design_dir
|
|
524
|
+
if design_dir is None:
|
|
525
|
+
raise ValueError(
|
|
526
|
+
"The design_dir argument is required when configuration dictionaries are used."
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
config_obj = Config()
|
|
530
|
+
for config_validated in configs_validated:
|
|
531
|
+
try:
|
|
532
|
+
meta = Self.get_meta(config_validated)
|
|
533
|
+
except TypeError as e:
|
|
534
|
+
identifier = "configuration dict"
|
|
535
|
+
if is_string(config_validated):
|
|
536
|
+
identifier = os.path.relpath(str(config_validated))
|
|
537
|
+
raise InvalidConfig(identifier, [], [f"'meta' object is invalid: {e}"])
|
|
538
|
+
|
|
539
|
+
mapping = None
|
|
540
|
+
if isinstance(config_validated, Mapping):
|
|
541
|
+
mapping = config_validated
|
|
542
|
+
elif isinstance(config_validated, str):
|
|
543
|
+
validated_type = _validate_config_file(config_validated)
|
|
544
|
+
if validated_type == "tcl":
|
|
545
|
+
mapping = Self.__mapping_from_tcl(
|
|
546
|
+
config_validated,
|
|
547
|
+
design_dir,
|
|
548
|
+
pdk_root=pdk_root,
|
|
549
|
+
pdk=pdk,
|
|
550
|
+
scl=scl,
|
|
551
|
+
)
|
|
552
|
+
elif validated_type == "json":
|
|
553
|
+
mapping = json.load(
|
|
554
|
+
open(config_validated, encoding="utf8"),
|
|
555
|
+
parse_float=Decimal,
|
|
556
|
+
)
|
|
557
|
+
elif validated_type == "yaml":
|
|
558
|
+
mapping = yaml.load(
|
|
559
|
+
open(config_validated, encoding="utf8"),
|
|
560
|
+
Loader=_OpenLaneYAMLLoader,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
assert mapping is not None, "Invalid validated config"
|
|
564
|
+
|
|
565
|
+
mutable = config_obj.copy_mut()
|
|
566
|
+
mutable.update_reorder(mapping)
|
|
567
|
+
config_obj = Self.__load_dict(
|
|
568
|
+
mutable,
|
|
569
|
+
design_dir,
|
|
570
|
+
flow_config_vars=flow_config_vars,
|
|
571
|
+
pdk_root=pdk_root,
|
|
572
|
+
pdk=pdk,
|
|
573
|
+
scl=scl,
|
|
574
|
+
meta=meta,
|
|
575
|
+
permissive_typing=meta.version < 2,
|
|
576
|
+
missing_ok=True,
|
|
577
|
+
_load_pdk_configs=_load_pdk_configs,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
_load_pdk_configs = False # one time's enough
|
|
581
|
+
|
|
582
|
+
# Final signoff + override strings
|
|
583
|
+
config_override_strings = config_override_strings or []
|
|
584
|
+
mutable = config_obj.copy_mut()
|
|
585
|
+
for string in config_override_strings:
|
|
586
|
+
key, value = string.split("=", 1)
|
|
587
|
+
mutable[key] = value
|
|
588
|
+
|
|
589
|
+
config_obj = Self.__load_dict(
|
|
590
|
+
mutable,
|
|
591
|
+
design_dir,
|
|
592
|
+
flow_config_vars=flow_config_vars,
|
|
593
|
+
pdk_root=pdk_root,
|
|
594
|
+
pdk=pdk,
|
|
595
|
+
scl=scl,
|
|
596
|
+
meta=config_obj.meta, # carry forward
|
|
597
|
+
missing_ok=False, # must all exist
|
|
598
|
+
permissive_typing=True, # so we can parse things from the commandline
|
|
599
|
+
_load_pdk_configs=False, # one time's enough
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
return (config_obj, design_dir)
|
|
603
|
+
|
|
604
|
+
## For Jupyter
|
|
605
|
+
def _repr_markdown_(self) -> str: # pragma: no cover
|
|
606
|
+
title = (
|
|
607
|
+
"Interactive Configuration"
|
|
608
|
+
if self == Config.current_interactive
|
|
609
|
+
else "Configuration"
|
|
610
|
+
)
|
|
611
|
+
values_title = (
|
|
612
|
+
"Initial Values" if self == Config.current_interactive else "Values"
|
|
613
|
+
)
|
|
614
|
+
return (
|
|
615
|
+
dedent(
|
|
616
|
+
f"""
|
|
617
|
+
### {title}
|
|
618
|
+
#### {values_title}
|
|
619
|
+
|
|
620
|
+
<br />
|
|
621
|
+
|
|
622
|
+
```yaml
|
|
623
|
+
%s
|
|
624
|
+
```
|
|
625
|
+
"""
|
|
626
|
+
)
|
|
627
|
+
% yaml.safe_dump(json.loads(self.dumps()))
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
## Private Methods
|
|
631
|
+
@classmethod
|
|
632
|
+
def __load_dict(
|
|
633
|
+
Self,
|
|
634
|
+
mapping_in: Mapping[str, Any],
|
|
635
|
+
design_dir: str,
|
|
636
|
+
flow_config_vars: Sequence[Variable],
|
|
637
|
+
*,
|
|
638
|
+
meta: Meta,
|
|
639
|
+
pdk_root: Optional[str] = None,
|
|
640
|
+
pdk: Optional[str] = None,
|
|
641
|
+
scl: Optional[str] = None,
|
|
642
|
+
full_pdk_warnings: bool = False,
|
|
643
|
+
permissive_typing: bool = False,
|
|
644
|
+
missing_ok: bool = False,
|
|
645
|
+
_load_pdk_configs: bool = True,
|
|
646
|
+
) -> "Config":
|
|
647
|
+
raw = dict(mapping_in)
|
|
648
|
+
|
|
649
|
+
if "meta" in raw:
|
|
650
|
+
del raw["meta"]
|
|
651
|
+
|
|
652
|
+
flow_option_vars = []
|
|
653
|
+
flow_pdk_vars = []
|
|
654
|
+
for variable in flow_config_vars:
|
|
655
|
+
if variable.pdk:
|
|
656
|
+
flow_pdk_vars.append(variable)
|
|
657
|
+
else:
|
|
658
|
+
flow_option_vars.append(variable)
|
|
659
|
+
|
|
660
|
+
mutable = GenericDict(
|
|
661
|
+
preprocess_dict(
|
|
662
|
+
raw,
|
|
663
|
+
only_extract_process_info=True,
|
|
664
|
+
design_dir=design_dir,
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
pdk = mutable.get(SpecialKeys.pdk) or pdk
|
|
669
|
+
scl = mutable.get(SpecialKeys.scl) or scl
|
|
670
|
+
pdkpath = ""
|
|
671
|
+
|
|
672
|
+
mutable["PDK_ROOT"] = pdk_root
|
|
673
|
+
|
|
674
|
+
if _load_pdk_configs:
|
|
675
|
+
pdk_root = Self.__resolve_pdk_root(pdk_root)
|
|
676
|
+
if pdk is None:
|
|
677
|
+
raise ValueError(
|
|
678
|
+
"The pdk argument is required as the configuration object lacks a 'PDK' key."
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
mutable, pdkpath, scl = Self.__get_pdk_config(
|
|
682
|
+
pdk=pdk,
|
|
683
|
+
scl=scl,
|
|
684
|
+
pdk_root=pdk_root,
|
|
685
|
+
full_pdk_warnings=full_pdk_warnings,
|
|
686
|
+
flow_pdk_vars=flow_pdk_vars,
|
|
687
|
+
)
|
|
688
|
+
else:
|
|
689
|
+
if pdk_root is not None:
|
|
690
|
+
pdkpath = os.path.join(pdk_root, mutable["PDK"])
|
|
691
|
+
|
|
692
|
+
readable_paths = [
|
|
693
|
+
os.path.abspath(design_dir),
|
|
694
|
+
]
|
|
695
|
+
if pdkpath != "":
|
|
696
|
+
readable_paths.append(os.path.abspath(pdkpath))
|
|
697
|
+
|
|
698
|
+
mutable.update(
|
|
699
|
+
preprocess_dict(
|
|
700
|
+
raw,
|
|
701
|
+
pdk=pdk,
|
|
702
|
+
pdkpath=pdkpath,
|
|
703
|
+
scl=mutable[SpecialKeys.scl],
|
|
704
|
+
design_dir=design_dir,
|
|
705
|
+
readable_paths=readable_paths,
|
|
706
|
+
)
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
processed, design_warnings, design_errors = Config.__process_variable_list(
|
|
710
|
+
mutable,
|
|
711
|
+
list(flow_config_vars),
|
|
712
|
+
removed_variables,
|
|
713
|
+
missing_ok=missing_ok,
|
|
714
|
+
permissive_typing=permissive_typing,
|
|
715
|
+
on_unknown_key="warn" if permissive_typing else "error",
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
if len(design_errors) != 0:
|
|
719
|
+
raise InvalidConfig(
|
|
720
|
+
"design configuration file", design_warnings, design_errors
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
if len(design_warnings) > 0:
|
|
724
|
+
info(
|
|
725
|
+
"Loading the design configuration file has generated the following warnings:"
|
|
726
|
+
)
|
|
727
|
+
for warning in design_warnings:
|
|
728
|
+
warn(warning)
|
|
729
|
+
|
|
730
|
+
return Config(processed, meta=meta)
|
|
731
|
+
|
|
732
|
+
@classmethod
|
|
733
|
+
def __mapping_from_tcl(
|
|
734
|
+
Self,
|
|
735
|
+
config: AnyPath,
|
|
736
|
+
design_dir: str,
|
|
737
|
+
*,
|
|
738
|
+
pdk_root: Optional[str] = None,
|
|
739
|
+
pdk: Optional[str] = None,
|
|
740
|
+
scl: Optional[str] = None,
|
|
741
|
+
) -> Mapping[str, Any]:
|
|
742
|
+
config_str = open(config, encoding="utf8").read()
|
|
743
|
+
|
|
744
|
+
warn(
|
|
745
|
+
"Support for .tcl configuration files is deprecated. Please migrate to a .json file at your earliest convenience."
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
pdk_root = Self.__resolve_pdk_root(pdk_root)
|
|
749
|
+
|
|
750
|
+
tcl_vars_in = GenericDict(
|
|
751
|
+
{
|
|
752
|
+
SpecialKeys.pdk_root: pdk_root,
|
|
753
|
+
SpecialKeys.pdk: pdk,
|
|
754
|
+
}
|
|
755
|
+
)
|
|
756
|
+
tcl_vars_in[SpecialKeys.scl] = ""
|
|
757
|
+
tcl_vars_in[SpecialKeys.design_dir] = design_dir
|
|
758
|
+
tcl_config = GenericDict(TclUtils._eval_env(tcl_vars_in, config_str))
|
|
759
|
+
|
|
760
|
+
process_info = preprocess_dict(
|
|
761
|
+
tcl_config,
|
|
762
|
+
only_extract_process_info=True,
|
|
763
|
+
design_dir=design_dir,
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
pdk = process_info.get(SpecialKeys.pdk) or pdk
|
|
767
|
+
|
|
768
|
+
if pdk is None:
|
|
769
|
+
raise ValueError(
|
|
770
|
+
"The pdk argument is required as the configuration object lacks a 'PDK' key."
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
_, _, scl = Self.__get_pdk_config(
|
|
774
|
+
pdk=pdk,
|
|
775
|
+
scl=scl,
|
|
776
|
+
pdk_root=pdk_root,
|
|
777
|
+
full_pdk_warnings=False,
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
tcl_vars_in[SpecialKeys.pdk] = pdk
|
|
781
|
+
tcl_vars_in[SpecialKeys.scl] = scl
|
|
782
|
+
tcl_vars_in[SpecialKeys.design_dir] = design_dir
|
|
783
|
+
|
|
784
|
+
tcl_mapping = GenericDict(TclUtils._eval_env(tcl_vars_in, config_str))
|
|
785
|
+
|
|
786
|
+
return tcl_mapping
|
|
787
|
+
|
|
788
|
+
@classmethod
|
|
789
|
+
def __resolve_pdk_root(
|
|
790
|
+
Self,
|
|
791
|
+
pdk_root: Optional[str],
|
|
792
|
+
) -> str:
|
|
793
|
+
if pdk_root is None:
|
|
794
|
+
try:
|
|
795
|
+
import ciel
|
|
796
|
+
|
|
797
|
+
pdk_root = ciel.get_ciel_home(pdk_root)
|
|
798
|
+
except ImportError:
|
|
799
|
+
raise ValueError(
|
|
800
|
+
"The pdk_root argument is required as Ciel is not installed."
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
return os.path.abspath(pdk_root)
|
|
804
|
+
|
|
805
|
+
@staticmethod
|
|
806
|
+
@lru_cache(1, True)
|
|
807
|
+
def __get_pdk_raw(
|
|
808
|
+
pdk_root: str, pdk: str, scl: Optional[str]
|
|
809
|
+
) -> Tuple[GenericImmutableDict[str, Any], str, str]:
|
|
810
|
+
pdk_config: GenericDict[str, Any] = GenericDict(
|
|
811
|
+
{
|
|
812
|
+
SpecialKeys.pdk_root: pdk_root,
|
|
813
|
+
SpecialKeys.pdk: pdk,
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
if scl is not None:
|
|
818
|
+
pdk_config[SpecialKeys.scl] = scl
|
|
819
|
+
|
|
820
|
+
pdkpath = os.path.join(pdk_root, pdk)
|
|
821
|
+
if not os.path.exists(pdkpath):
|
|
822
|
+
matches = sorted(glob(f"{pdkpath}*"))
|
|
823
|
+
errors = [f"The PDK {pdk} was not found."]
|
|
824
|
+
warnings = []
|
|
825
|
+
for match in matches:
|
|
826
|
+
basename = os.path.basename(match)
|
|
827
|
+
warnings.append(f"A similarly-named PDK was found: {basename}")
|
|
828
|
+
raise InvalidConfig("PDK configuration", warnings, errors)
|
|
829
|
+
|
|
830
|
+
pdk_config_path = os.path.join(pdkpath, "libs.tech", "librelane", "config.tcl")
|
|
831
|
+
if not os.path.exists(pdk_config_path):
|
|
832
|
+
pdk_config_path_alt = os.path.join(
|
|
833
|
+
pdkpath, "libs.tech", "openlane", "config.tcl"
|
|
834
|
+
)
|
|
835
|
+
if not os.path.exists(pdk_config_path_alt):
|
|
836
|
+
raise InvalidConfig(
|
|
837
|
+
"PDk configuration",
|
|
838
|
+
[],
|
|
839
|
+
[
|
|
840
|
+
f"Neither '{pdk_config_path}' nor '{pdk_config_path_alt} were found.'"
|
|
841
|
+
],
|
|
842
|
+
)
|
|
843
|
+
pdk_config_path = pdk_config_path_alt
|
|
844
|
+
|
|
845
|
+
pdk_env = TclUtils._eval_env(
|
|
846
|
+
pdk_config,
|
|
847
|
+
open(pdk_config_path, encoding="utf8").read(),
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
scl = pdk_env["STD_CELL_LIBRARY"]
|
|
851
|
+
assert (
|
|
852
|
+
scl is not None
|
|
853
|
+
), "Fatal error: STD_CELL_LIBRARY default value not set by PDK."
|
|
854
|
+
|
|
855
|
+
scl_config_path = os.path.join(
|
|
856
|
+
pdkpath, "libs.tech", "librelane", scl, "config.tcl"
|
|
857
|
+
)
|
|
858
|
+
if not os.path.exists(scl_config_path):
|
|
859
|
+
scl_config_path_alt = os.path.join(
|
|
860
|
+
pdkpath, "libs.tech", "openlane", scl, "config.tcl"
|
|
861
|
+
)
|
|
862
|
+
if not os.path.exists(scl_config_path_alt):
|
|
863
|
+
raise InvalidConfig(
|
|
864
|
+
"PDk configuration",
|
|
865
|
+
[],
|
|
866
|
+
[
|
|
867
|
+
f"Neither '{scl_config_path}' nor '{scl_config_path_alt} were found.'"
|
|
868
|
+
],
|
|
869
|
+
)
|
|
870
|
+
scl_config_path = scl_config_path_alt
|
|
871
|
+
|
|
872
|
+
scl_env = migrate_old_config(
|
|
873
|
+
TclUtils._eval_env(
|
|
874
|
+
pdk_env,
|
|
875
|
+
open(scl_config_path, encoding="utf8").read(),
|
|
876
|
+
)
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
return GenericImmutableDict(scl_env), pdkpath, scl
|
|
880
|
+
|
|
881
|
+
@staticmethod
|
|
882
|
+
def __get_pdk_config(
|
|
883
|
+
pdk: str,
|
|
884
|
+
scl: Optional[str],
|
|
885
|
+
pdk_root: str,
|
|
886
|
+
flow_pdk_vars: Optional[List[Variable]] = None,
|
|
887
|
+
full_pdk_warnings: Optional[bool] = False,
|
|
888
|
+
) -> Tuple[GenericDict[str, Any], str, str]:
|
|
889
|
+
"""
|
|
890
|
+
:returns: A tuple of the PDK configuration, the PDK path and the SCL.
|
|
891
|
+
"""
|
|
892
|
+
|
|
893
|
+
frozen, pdkpath, scl = Config.__get_pdk_raw(pdk_root, pdk, scl)
|
|
894
|
+
if flow_pdk_vars is None or len(flow_pdk_vars) == 0:
|
|
895
|
+
return (GenericDict(), pdkpath, scl)
|
|
896
|
+
|
|
897
|
+
raw: GenericDict[str, Any] = GenericDict(frozen) # microwave
|
|
898
|
+
processed, pdk_warnings, pdk_errors = Config.__process_variable_list(
|
|
899
|
+
raw,
|
|
900
|
+
flow_pdk_vars,
|
|
901
|
+
on_unknown_key=None,
|
|
902
|
+
permissive_typing=True,
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
if len(pdk_errors) != 0:
|
|
906
|
+
raise InvalidConfig("PDK configuration files", pdk_warnings, pdk_errors)
|
|
907
|
+
|
|
908
|
+
if len(pdk_warnings) > 0:
|
|
909
|
+
if full_pdk_warnings:
|
|
910
|
+
info(
|
|
911
|
+
"Loading the PDK configuration files has generated the following warnings:"
|
|
912
|
+
)
|
|
913
|
+
for warning in pdk_warnings:
|
|
914
|
+
warn(warning)
|
|
915
|
+
|
|
916
|
+
processed["PDK_ROOT"] = pdk_root
|
|
917
|
+
processed["PDK"] = pdk
|
|
918
|
+
|
|
919
|
+
return (processed, pdkpath, scl)
|
|
920
|
+
|
|
921
|
+
def __process_variable_list(
|
|
922
|
+
mutable: GenericDict[str, Any],
|
|
923
|
+
variables: Sequence["Variable"],
|
|
924
|
+
removed: Optional[Mapping[str, str]] = None,
|
|
925
|
+
*,
|
|
926
|
+
on_unknown_key: Union[Literal["error", "warn"], None] = "warn",
|
|
927
|
+
permissive_typing: bool = False,
|
|
928
|
+
missing_ok: bool = False,
|
|
929
|
+
) -> Tuple[GenericDict[str, Any], List[str], List[str]]:
|
|
930
|
+
"""
|
|
931
|
+
Verifies a configuration object against a list of variables, returning
|
|
932
|
+
an object with the variables normalized according to their types.
|
|
933
|
+
|
|
934
|
+
:param config: The input, raw configuration object.
|
|
935
|
+
:param variables: A sequence or some other iterable of variables.
|
|
936
|
+
:param removed: A dictionary of variables that may have existed at a point in
|
|
937
|
+
time, but then have gotten removed. Useful to give feedback to the user.
|
|
938
|
+
:returns: A tuple of:
|
|
939
|
+
[0] A final, processed configuration.
|
|
940
|
+
[1] A list of warnings.
|
|
941
|
+
[2] A list of errors.
|
|
942
|
+
|
|
943
|
+
If the third element is non-empty, the first object is invalid.
|
|
944
|
+
"""
|
|
945
|
+
if removed is None:
|
|
946
|
+
removed = {}
|
|
947
|
+
warnings: List[str] = []
|
|
948
|
+
errors = []
|
|
949
|
+
final: GenericDict[str, Any] = GenericDict()
|
|
950
|
+
|
|
951
|
+
# Special Deprecation Behaviors
|
|
952
|
+
if (
|
|
953
|
+
mutable.get("DIODE_INSERTION_STRATEGY") is not None
|
|
954
|
+
): # Can't use := because 0 is a valid value
|
|
955
|
+
dis = mutable["DIODE_INSERTION_STRATEGY"]
|
|
956
|
+
del mutable["DIODE_INSERTION_STRATEGY"]
|
|
957
|
+
try:
|
|
958
|
+
dis = int(dis)
|
|
959
|
+
except ValueError:
|
|
960
|
+
pass
|
|
961
|
+
if not isinstance(dis, int) or dis in [1, 2, 5] or dis > 6:
|
|
962
|
+
errors.append(
|
|
963
|
+
f"DIODE_INSERTION_STRATEGY '{dis}' is not available in LibreLane 2 or higher. See 'Migrating DIODE_INSERTION_STRATEGY' in the docs for more info."
|
|
964
|
+
)
|
|
965
|
+
else:
|
|
966
|
+
warnings.append(
|
|
967
|
+
"The DIODE_INSERTION_STRATEGY variable has been deprecated. See 'Migrating DIODE_INSERTION_STRATEGY' in the docs for more info."
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
mutable["GRT_REPAIR_ANTENNAS"] = False
|
|
971
|
+
mutable["RUN_HEURISTIC_DIODE_INSERTION"] = False
|
|
972
|
+
mutable["DIODE_ON_PORTS"] = "none"
|
|
973
|
+
if dis in [3, 6]:
|
|
974
|
+
mutable["GRT_REPAIR_ANTENNAS"] = True
|
|
975
|
+
if dis in [4, 6]:
|
|
976
|
+
mutable["RUN_HEURISTIC_DIODE_INSERTION"] = True
|
|
977
|
+
mutable["DIODE_ON_PORTS"] = "in"
|
|
978
|
+
|
|
979
|
+
for variable in variables:
|
|
980
|
+
try:
|
|
981
|
+
key, value_processed = variable.compile(
|
|
982
|
+
mutable_config=mutable,
|
|
983
|
+
warning_list_ref=warnings,
|
|
984
|
+
values_so_far=final,
|
|
985
|
+
permissive_typing=permissive_typing,
|
|
986
|
+
)
|
|
987
|
+
if key is not None:
|
|
988
|
+
del mutable[key]
|
|
989
|
+
final[variable.name] = value_processed
|
|
990
|
+
except MissingRequiredVariable as e:
|
|
991
|
+
if not missing_ok:
|
|
992
|
+
errors.append(str(e))
|
|
993
|
+
except ValueError as e:
|
|
994
|
+
errors.append(str(e))
|
|
995
|
+
if variable.name in mutable:
|
|
996
|
+
del mutable[variable.name]
|
|
997
|
+
|
|
998
|
+
for key in sorted(mutable.keys()):
|
|
999
|
+
assert isinstance(key, str)
|
|
1000
|
+
|
|
1001
|
+
if key in vars(SpecialKeys).values():
|
|
1002
|
+
continue
|
|
1003
|
+
if key in removed:
|
|
1004
|
+
warnings.append(f"'{key}' has been removed: {removed[key]}")
|
|
1005
|
+
elif (
|
|
1006
|
+
"_OPT" not in key
|
|
1007
|
+
and not key.startswith("//")
|
|
1008
|
+
and not key.startswith("#")
|
|
1009
|
+
):
|
|
1010
|
+
if on_unknown_key == "error":
|
|
1011
|
+
if key in Variable.known_variable_names:
|
|
1012
|
+
warnings.append(
|
|
1013
|
+
f"Key '{key}' provided is unused by the current flow."
|
|
1014
|
+
)
|
|
1015
|
+
else:
|
|
1016
|
+
errors.append(f"Unknown key '{key}' provided.")
|
|
1017
|
+
elif on_unknown_key == "warn":
|
|
1018
|
+
if key in Variable.known_variable_names:
|
|
1019
|
+
warnings.append(
|
|
1020
|
+
f"Key '{key}' provided is unused by the current flow."
|
|
1021
|
+
)
|
|
1022
|
+
else:
|
|
1023
|
+
warnings.append(f"An unknown key '{key}' was provided.")
|
|
1024
|
+
|
|
1025
|
+
return (final, warnings, errors)
|