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,63 @@
|
|
|
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
|
+
from typing import Iterable, Iterator, Type, TypeVar
|
|
15
|
+
|
|
16
|
+
VT = TypeVar("VT")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RingBuffer(Iterable[VT]):
|
|
20
|
+
"""
|
|
21
|
+
A generic ring (circular) buffer that automatically pops the element at the
|
|
22
|
+
head when full, and emplaces a new element in its place.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
t: Type[VT],
|
|
28
|
+
max: int,
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
self._store = [t()] * max
|
|
32
|
+
self._max = max
|
|
33
|
+
self._head = 0
|
|
34
|
+
self._tail = 0
|
|
35
|
+
self._len = 0
|
|
36
|
+
|
|
37
|
+
def pop(self) -> VT:
|
|
38
|
+
if self._len == 0:
|
|
39
|
+
raise IndexError("pop from empty ring buffer")
|
|
40
|
+
element = self[0]
|
|
41
|
+
self._head = (self._head + 1) % self._max
|
|
42
|
+
self._len -= 1
|
|
43
|
+
return element
|
|
44
|
+
|
|
45
|
+
def push(self, element: VT):
|
|
46
|
+
if self._len == self._max:
|
|
47
|
+
self.pop()
|
|
48
|
+
self._store[self._tail] = element
|
|
49
|
+
self._tail = (self._tail + 1) % self._max
|
|
50
|
+
self._len += 1
|
|
51
|
+
|
|
52
|
+
def __getitem__(self, idx: int, /) -> VT:
|
|
53
|
+
if idx + 1 > self._len:
|
|
54
|
+
raise IndexError(f"{idx} is out of range")
|
|
55
|
+
i = (self._head + idx) % self._max
|
|
56
|
+
return self._store[i]
|
|
57
|
+
|
|
58
|
+
def __len__(self) -> int:
|
|
59
|
+
return self._len
|
|
60
|
+
|
|
61
|
+
def __iter__(self) -> Iterator[VT]:
|
|
62
|
+
for i in range(0, self._len):
|
|
63
|
+
yield self[i]
|
librelane/common/tcl.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
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 re
|
|
15
|
+
import tkinter
|
|
16
|
+
from typing import Dict, Mapping, Any, Iterable
|
|
17
|
+
|
|
18
|
+
_env_rx = re.compile(r"(?:\:\:)?env\((\w+)\)")
|
|
19
|
+
_find_unsafe = re.compile(r"[^\w@%+=:,./-]", re.ASCII).search
|
|
20
|
+
_escapes_in_quotes = re.compile(r"([\\\$\"\[])")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TclUtils(object):
|
|
24
|
+
"""
|
|
25
|
+
A collection of useful Tcl utilities.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
raise TypeError(f"Cannot create instances of '{self.__class__.__name__}'")
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def escape(s: str) -> str:
|
|
33
|
+
"""
|
|
34
|
+
:returns: If the string can be parsed by Tcl as a single token, the string
|
|
35
|
+
is returned verbatim.
|
|
36
|
+
|
|
37
|
+
Otherwise, the string is returned in double quotes, with any unsafe
|
|
38
|
+
characters escaped with a backslash.
|
|
39
|
+
"""
|
|
40
|
+
if s == "":
|
|
41
|
+
return '""'
|
|
42
|
+
if not _find_unsafe(s):
|
|
43
|
+
return s
|
|
44
|
+
return '"' + _escapes_in_quotes.sub(r"\\\1", s).replace("\n", r"\n") + '"'
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def join(ss: Iterable[str]) -> str:
|
|
48
|
+
"""
|
|
49
|
+
:param ss: Input list
|
|
50
|
+
:returns: The input list converted to a Tcl-compatible list where each
|
|
51
|
+
element is either a single token or double-quoted (i.e. interpreted
|
|
52
|
+
by Tcl as a single element.)
|
|
53
|
+
"""
|
|
54
|
+
return " ".join(TclUtils.escape(arg) for arg in ss)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _eval_env(env_in: Mapping[str, Any], tcl_in: str) -> Dict[str, Any]:
|
|
58
|
+
interpreter = tkinter.Tcl()
|
|
59
|
+
|
|
60
|
+
interpreter.eval("array unset ::env")
|
|
61
|
+
for key, value in env_in.items():
|
|
62
|
+
interpreter.setvar(f"env({key})", str(value))
|
|
63
|
+
|
|
64
|
+
env_out = dict(env_in)
|
|
65
|
+
|
|
66
|
+
def py_set(key, value=None):
|
|
67
|
+
if match := _env_rx.fullmatch(key):
|
|
68
|
+
if value is not None:
|
|
69
|
+
env_out[match.group(1)] = value
|
|
70
|
+
|
|
71
|
+
py_set_name = interpreter.register(py_set)
|
|
72
|
+
interpreter.call("rename", py_set_name, "_py_set")
|
|
73
|
+
interpreter.call("rename", "set", "_orig_set")
|
|
74
|
+
interpreter.eval(
|
|
75
|
+
"proc set args { _py_set {*}$args; tailcall _orig_set {*}$args; }"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
interpreter.eval(tcl_in)
|
|
79
|
+
|
|
80
|
+
return env_out
|
|
@@ -0,0 +1,549 @@
|
|
|
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 re
|
|
16
|
+
import uuid
|
|
17
|
+
import shutil
|
|
18
|
+
import tempfile
|
|
19
|
+
import subprocess
|
|
20
|
+
from enum import IntEnum
|
|
21
|
+
from decimal import Decimal
|
|
22
|
+
from functools import lru_cache
|
|
23
|
+
from typing import (
|
|
24
|
+
Any,
|
|
25
|
+
Callable,
|
|
26
|
+
Dict,
|
|
27
|
+
FrozenSet,
|
|
28
|
+
Iterable,
|
|
29
|
+
Literal,
|
|
30
|
+
Mapping,
|
|
31
|
+
Optional,
|
|
32
|
+
Sequence,
|
|
33
|
+
Tuple,
|
|
34
|
+
List,
|
|
35
|
+
Union,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
import libparse
|
|
39
|
+
from deprecated.sphinx import deprecated
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
from .misc import mkdirp, gzopen
|
|
43
|
+
from .types import Path
|
|
44
|
+
from .metrics import aggregate_metrics
|
|
45
|
+
from .generic_dict import GenericImmutableDict, is_string
|
|
46
|
+
from ..state import DesignFormat
|
|
47
|
+
from ..common import Filter
|
|
48
|
+
from ..logging import debug, warn, err
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Toolbox(object):
|
|
52
|
+
"""
|
|
53
|
+
An assisting object shared by a Flow and all its constituent Steps.
|
|
54
|
+
|
|
55
|
+
The toolbox may create artifacts that are cached to avoid constant re-creation
|
|
56
|
+
between steps.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, tmp_dir: str) -> None:
|
|
60
|
+
# Only create before use, otherwise users will end up with
|
|
61
|
+
# "librelane_run/tmp" created in their PWD because of the global toolbox
|
|
62
|
+
self.tmp_dir = tmp_dir
|
|
63
|
+
|
|
64
|
+
self.remove_cells_from_lib = lru_cache(16, True)(self.remove_cells_from_lib) # type: ignore
|
|
65
|
+
self.create_blackbox_model = lru_cache(16, True)(self.create_blackbox_model) # type: ignore
|
|
66
|
+
|
|
67
|
+
@deprecated(
|
|
68
|
+
version="2.0.0b1",
|
|
69
|
+
reason="Use 'aggregate_metrics' from 'librelane.common'",
|
|
70
|
+
action="once",
|
|
71
|
+
)
|
|
72
|
+
def aggregate_metrics(
|
|
73
|
+
self,
|
|
74
|
+
input: Dict[str, Any],
|
|
75
|
+
aggregator_by_metric: Dict[str, Tuple[Any, Callable[[Iterable], Any]]],
|
|
76
|
+
) -> Dict[str, Any]:
|
|
77
|
+
return aggregate_metrics(input, aggregator_by_metric)
|
|
78
|
+
|
|
79
|
+
def filter_views(
|
|
80
|
+
self,
|
|
81
|
+
config: Mapping[str, Any],
|
|
82
|
+
views_by_corner: Mapping[str, Union[Path, Iterable[Path]]],
|
|
83
|
+
timing_corner: Optional[str] = None,
|
|
84
|
+
) -> List[Path]:
|
|
85
|
+
"""
|
|
86
|
+
Given a mapping from (wildcards of) corner names to views, this function
|
|
87
|
+
enumerates all views matching either the default timing corner or
|
|
88
|
+
an explicitly-provided override.
|
|
89
|
+
|
|
90
|
+
:param config: The configuration. Used solely to extract the default
|
|
91
|
+
corner.
|
|
92
|
+
:param views_by_corner: The mapping from (wild cards) of corner names to
|
|
93
|
+
views.
|
|
94
|
+
:param corner: An explicit override for the default corner. Must be a
|
|
95
|
+
fully qualified IPVT corner.
|
|
96
|
+
:returns: The created list
|
|
97
|
+
"""
|
|
98
|
+
timing_corner = timing_corner or config["DEFAULT_CORNER"]
|
|
99
|
+
result: List[Path] = []
|
|
100
|
+
|
|
101
|
+
for key in Filter(views_by_corner).get_matching_wildcards(timing_corner):
|
|
102
|
+
value = views_by_corner[key]
|
|
103
|
+
if is_string(value):
|
|
104
|
+
result += [value] # type: ignore
|
|
105
|
+
else:
|
|
106
|
+
result += list(value) # type: ignore
|
|
107
|
+
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
def get_macro_views(
|
|
111
|
+
self,
|
|
112
|
+
config: Mapping[str, Any],
|
|
113
|
+
view: DesignFormat,
|
|
114
|
+
timing_corner: Optional[str] = None,
|
|
115
|
+
unless_exist: Union[None, DesignFormat, Sequence[DesignFormat]] = None,
|
|
116
|
+
) -> List[Path]:
|
|
117
|
+
"""
|
|
118
|
+
For :class:`Config` objects (or similar Mappings) that have Macro
|
|
119
|
+
information, this function gets all Macro views matching a certain
|
|
120
|
+
:class:`DesignFormat` for either the default timing corner or an
|
|
121
|
+
explicitly-provided override.
|
|
122
|
+
|
|
123
|
+
:param config: The configuration.
|
|
124
|
+
:param view: The design format to return views of.
|
|
125
|
+
:param timing_corner: An explicit override for the default corner set
|
|
126
|
+
by the configuration.
|
|
127
|
+
:param corner: An explicit override for the default corner. Must be a
|
|
128
|
+
fully qualified IPVT corner.
|
|
129
|
+
:param unless_exist: If a Macro also has a view for these
|
|
130
|
+
``DesignFormat``\\s, do not return a result for the requested
|
|
131
|
+
``DesignFormat``\\.
|
|
132
|
+
|
|
133
|
+
Useful for if you want to return say, Netlists if reliable LIB files
|
|
134
|
+
do not exist.
|
|
135
|
+
:returns: A list of the Macro views matched by the process described
|
|
136
|
+
above.
|
|
137
|
+
"""
|
|
138
|
+
from ..config import Macro
|
|
139
|
+
|
|
140
|
+
timing_corner = timing_corner or config["DEFAULT_CORNER"]
|
|
141
|
+
macros = config["MACROS"]
|
|
142
|
+
result: List[Path] = []
|
|
143
|
+
|
|
144
|
+
if macros is None:
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
unless_exist = unless_exist or []
|
|
148
|
+
if isinstance(unless_exist, DesignFormat):
|
|
149
|
+
unless_exist = [unless_exist]
|
|
150
|
+
|
|
151
|
+
for module, macro in macros.items():
|
|
152
|
+
if not isinstance(macro, Macro):
|
|
153
|
+
raise TypeError(
|
|
154
|
+
f"Misconstructed configuration: macro definition for key {module} is not of type 'Macro'."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
views = macro.view_by_df(view)
|
|
158
|
+
if views is None:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
alt_views: List[Path] = []
|
|
162
|
+
for alternate_format in unless_exist:
|
|
163
|
+
entry = macro.view_by_df(alternate_format)
|
|
164
|
+
if entry is not None:
|
|
165
|
+
current = entry
|
|
166
|
+
if isinstance(current, dict):
|
|
167
|
+
current = self.filter_views(config, current, timing_corner)
|
|
168
|
+
elif not isinstance(current, list):
|
|
169
|
+
current = [current]
|
|
170
|
+
alt_views += current
|
|
171
|
+
|
|
172
|
+
if len(alt_views) != 0:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
if isinstance(views, dict):
|
|
176
|
+
views_filtered = self.filter_views(config, views, timing_corner)
|
|
177
|
+
result += views_filtered
|
|
178
|
+
elif isinstance(views, list):
|
|
179
|
+
result += views
|
|
180
|
+
elif views is not None:
|
|
181
|
+
result += [Path(views)]
|
|
182
|
+
|
|
183
|
+
return [element for element in result if str(element) != Path._dummy_path]
|
|
184
|
+
|
|
185
|
+
def get_macro_views_by_priority(
|
|
186
|
+
self,
|
|
187
|
+
config: Mapping[str, Any],
|
|
188
|
+
design_formats: Sequence[DesignFormat],
|
|
189
|
+
timing_corner: Optional[str] = None,
|
|
190
|
+
) -> List[Tuple[Path, DesignFormat]]:
|
|
191
|
+
result: List[Tuple[Path, DesignFormat]] = []
|
|
192
|
+
formats_so_far: List[DesignFormat] = []
|
|
193
|
+
for format in design_formats:
|
|
194
|
+
views = self.get_macro_views(
|
|
195
|
+
config,
|
|
196
|
+
format,
|
|
197
|
+
unless_exist=formats_so_far,
|
|
198
|
+
timing_corner=timing_corner,
|
|
199
|
+
)
|
|
200
|
+
for view in views:
|
|
201
|
+
result.append((view, format))
|
|
202
|
+
formats_so_far.append(format)
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def get_timing_files_categorized(
|
|
206
|
+
self,
|
|
207
|
+
config: Mapping[str, Any],
|
|
208
|
+
timing_corner: Optional[str] = None,
|
|
209
|
+
prioritize_nl: bool = False,
|
|
210
|
+
) -> Tuple[str, List[Path], List[Path], List[Tuple[str, Path]]]:
|
|
211
|
+
"""
|
|
212
|
+
Returns the lib files for a given configuration and timing corner.
|
|
213
|
+
|
|
214
|
+
:param config: A configuration object or a similar mapping.
|
|
215
|
+
:param timing_corner:
|
|
216
|
+
A fully qualified IPVT corner to get SCL libs for.
|
|
217
|
+
|
|
218
|
+
If not specified, the value for ``DEFAULT_CORNER`` from the SCL will
|
|
219
|
+
be used.
|
|
220
|
+
:param prioritize_nl:
|
|
221
|
+
Do not return lib files for macros that have gate-Level Netlists and
|
|
222
|
+
SPEF views.
|
|
223
|
+
|
|
224
|
+
If set to ``false``\\, only lib files are returned.
|
|
225
|
+
:returns: A tuple of:
|
|
226
|
+
* The name of the timing corner
|
|
227
|
+
* A list of lib files
|
|
228
|
+
* A list of netlists
|
|
229
|
+
* A list of tuples of instances and SPEFs
|
|
230
|
+
"""
|
|
231
|
+
from ..config import Macro
|
|
232
|
+
|
|
233
|
+
timing_corner = timing_corner or config["DEFAULT_CORNER"]
|
|
234
|
+
|
|
235
|
+
all_libs: List[Path] = self.filter_views(config, config["LIB"], timing_corner)
|
|
236
|
+
if len(all_libs) == 0:
|
|
237
|
+
warn(f"No SCL lib files found for {timing_corner}.")
|
|
238
|
+
|
|
239
|
+
all_netlists: List[Path] = []
|
|
240
|
+
all_spefs: List[Tuple[str, Path]] = []
|
|
241
|
+
|
|
242
|
+
macros = config["MACROS"]
|
|
243
|
+
if macros is None:
|
|
244
|
+
macros = {}
|
|
245
|
+
|
|
246
|
+
for module, macro in macros.items():
|
|
247
|
+
if not isinstance(macro, Macro):
|
|
248
|
+
raise TypeError(
|
|
249
|
+
f"Misconstructed configuration: macro definition for key {module} is not of type 'Macro'."
|
|
250
|
+
)
|
|
251
|
+
if prioritize_nl:
|
|
252
|
+
netlists = macro.nl
|
|
253
|
+
if isinstance(netlists, Path):
|
|
254
|
+
netlists = [netlists]
|
|
255
|
+
|
|
256
|
+
spefs = self.filter_views(
|
|
257
|
+
config,
|
|
258
|
+
macro.spef,
|
|
259
|
+
timing_corner,
|
|
260
|
+
)
|
|
261
|
+
if len(netlists) and not len(spefs):
|
|
262
|
+
warn(
|
|
263
|
+
f"Netlists found for macro {module}, but no parasitics extraction found at corner {timing_corner}. The netlist cannot be used for timing on this module."
|
|
264
|
+
)
|
|
265
|
+
elif len(spefs) and not len(netlists):
|
|
266
|
+
warn(
|
|
267
|
+
f"Parasitics extraction(s) found for macro {module} at corner {timing_corner}, but no netlist found. The parasitics cannot be used for timing on this module."
|
|
268
|
+
)
|
|
269
|
+
elif len(spefs) and len(netlists):
|
|
270
|
+
debug(f"Adding {[netlists + spefs]} to timing info…")
|
|
271
|
+
all_netlists += netlists
|
|
272
|
+
for spef in spefs:
|
|
273
|
+
for instance in macro.instances:
|
|
274
|
+
all_spefs.append((instance, spef))
|
|
275
|
+
continue
|
|
276
|
+
# NL/SPEF not prioritized or not found
|
|
277
|
+
libs = self.filter_views(
|
|
278
|
+
config,
|
|
279
|
+
macro.lib,
|
|
280
|
+
timing_corner,
|
|
281
|
+
)
|
|
282
|
+
if not len(libs):
|
|
283
|
+
warn(
|
|
284
|
+
f"No libs found for macro {module} at corner {timing_corner}. The module will be black-boxed."
|
|
285
|
+
)
|
|
286
|
+
continue
|
|
287
|
+
debug(f"Adding {libs} to timing info…")
|
|
288
|
+
all_libs += libs
|
|
289
|
+
|
|
290
|
+
return (timing_corner, all_libs, all_netlists, all_spefs)
|
|
291
|
+
|
|
292
|
+
def get_timing_files(
|
|
293
|
+
self,
|
|
294
|
+
config: Mapping[str, Any],
|
|
295
|
+
timing_corner: Optional[str] = None,
|
|
296
|
+
prioritize_nl: bool = False,
|
|
297
|
+
) -> Tuple[str, List[str]]:
|
|
298
|
+
"""
|
|
299
|
+
Returns the lib files for a given configuration and timing corner.
|
|
300
|
+
|
|
301
|
+
:param config: A configuration object or a similar mapping.
|
|
302
|
+
:param timing_corner:
|
|
303
|
+
A fully qualified IPVT corner to get SCL libs for.
|
|
304
|
+
|
|
305
|
+
If not specified, the value for ``DEFAULT_CORNER`` from the SCL will
|
|
306
|
+
be used.
|
|
307
|
+
:param prioritize_nl:
|
|
308
|
+
Do not return lib files for macros that have gate-Level Netlists and
|
|
309
|
+
SPEF views.
|
|
310
|
+
|
|
311
|
+
If set to ``false``\\, only lib files are returned.
|
|
312
|
+
:returns: A tuple of:
|
|
313
|
+
|
|
314
|
+
* The name of the timing corner
|
|
315
|
+
* A heterogeneous list of files composed of: Lib files are returned as-is,
|
|
316
|
+
Netlists are returned as-is, and SPEF files are returned in the
|
|
317
|
+
format ``{instance_name}@{spef_path}``\\.
|
|
318
|
+
|
|
319
|
+
It is left up to the step or tool to process this list as they see
|
|
320
|
+
fit.
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
timing_corner, libs, netlists, spefs = self.get_timing_files_categorized(
|
|
324
|
+
config=config,
|
|
325
|
+
timing_corner=timing_corner,
|
|
326
|
+
prioritize_nl=prioritize_nl,
|
|
327
|
+
)
|
|
328
|
+
results = [str(path) for path in libs + netlists]
|
|
329
|
+
for instance, spef in spefs:
|
|
330
|
+
results.append(f"{instance}@{spef}")
|
|
331
|
+
return (timing_corner, results)
|
|
332
|
+
|
|
333
|
+
def render_png(
|
|
334
|
+
self,
|
|
335
|
+
config: GenericImmutableDict[str, Any],
|
|
336
|
+
state_in: GenericImmutableDict[str, Any],
|
|
337
|
+
) -> Optional[bytes]: # pragma: no cover
|
|
338
|
+
try:
|
|
339
|
+
from ..steps import KLayout, StepError
|
|
340
|
+
from ..config import Config, InvalidConfig
|
|
341
|
+
from ..state import State
|
|
342
|
+
|
|
343
|
+
# I'm too damn tired to figure out a way to forward-declare those two,
|
|
344
|
+
# have fun if you want to
|
|
345
|
+
if not isinstance(config, Config):
|
|
346
|
+
raise TypeError("parameter config must be of type Config")
|
|
347
|
+
|
|
348
|
+
if not isinstance(state_in, State):
|
|
349
|
+
raise TypeError("parameter state_in must be of type State")
|
|
350
|
+
|
|
351
|
+
with tempfile.TemporaryDirectory(prefix="librelane_klayout_tmp_") as d:
|
|
352
|
+
render_step = KLayout.Render(config, state_in, _config_quiet=True)
|
|
353
|
+
render_step.start(self, d)
|
|
354
|
+
return open(os.path.join(d, "out.png"), "rb").read()
|
|
355
|
+
except InvalidConfig:
|
|
356
|
+
warn("PDK is incompatible with KLayout. Unable to generate preview.")
|
|
357
|
+
return None
|
|
358
|
+
except StepError as e:
|
|
359
|
+
warn(f"Failed to generate preview: {e}.")
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
def remove_cells_from_lib(
|
|
363
|
+
self,
|
|
364
|
+
input_lib_files: FrozenSet[str],
|
|
365
|
+
excluded_cells: FrozenSet[str],
|
|
366
|
+
) -> List[str]:
|
|
367
|
+
"""
|
|
368
|
+
Creates a new lib file with some cells removed.
|
|
369
|
+
|
|
370
|
+
This function is memoized, i.e., results are cached for a specific set
|
|
371
|
+
of inputs.
|
|
372
|
+
|
|
373
|
+
:param input_lib_files: A `frozenset` of input lib files.
|
|
374
|
+
:param excluded_cells: A `frozenset` of wildcards of cells to remove
|
|
375
|
+
from the files.
|
|
376
|
+
:returns: A path to the lib file with the removed cells.
|
|
377
|
+
"""
|
|
378
|
+
mkdirp(self.tmp_dir)
|
|
379
|
+
|
|
380
|
+
class State(IntEnum):
|
|
381
|
+
initial = 0
|
|
382
|
+
cell = 10
|
|
383
|
+
excluded_cell = 11
|
|
384
|
+
|
|
385
|
+
cell_start_rx = re.compile(r"(\s*)cell\s*\(\"?(.*?)\"?\)\s*\{")
|
|
386
|
+
out_paths = []
|
|
387
|
+
|
|
388
|
+
excluded_cells_filter = Filter(excluded_cells)
|
|
389
|
+
|
|
390
|
+
for file in input_lib_files:
|
|
391
|
+
input_lib_stream = gzopen(file)
|
|
392
|
+
# can't be gzip -- abc cannot read gzipped lib files
|
|
393
|
+
out_path = os.path.join(self.tmp_dir, f"{uuid.uuid4().hex}.lib")
|
|
394
|
+
|
|
395
|
+
state = State.initial
|
|
396
|
+
brace_count = 0
|
|
397
|
+
output_file_handle = open(out_path, "w")
|
|
398
|
+
write = lambda x: print(x, file=output_file_handle, end="")
|
|
399
|
+
for line in input_lib_stream:
|
|
400
|
+
if state == State.initial:
|
|
401
|
+
cell_m = cell_start_rx.search(line)
|
|
402
|
+
if cell_m is not None:
|
|
403
|
+
whitespace = cell_m[1]
|
|
404
|
+
cell_name = cell_m[2]
|
|
405
|
+
if excluded_cells_filter.match(cell_name):
|
|
406
|
+
state = State.excluded_cell
|
|
407
|
+
write(f"{whitespace}/* removed {cell_name} */\n")
|
|
408
|
+
else:
|
|
409
|
+
state = State.cell
|
|
410
|
+
write(line)
|
|
411
|
+
brace_count = 1
|
|
412
|
+
else:
|
|
413
|
+
write(line)
|
|
414
|
+
elif state in [State.cell, State.excluded_cell]:
|
|
415
|
+
if "{" in line:
|
|
416
|
+
brace_count += 1
|
|
417
|
+
if "}" in line:
|
|
418
|
+
brace_count -= 1
|
|
419
|
+
if state == State.cell:
|
|
420
|
+
write(line)
|
|
421
|
+
if brace_count == 0:
|
|
422
|
+
state = State.initial
|
|
423
|
+
|
|
424
|
+
output_file_handle.close()
|
|
425
|
+
|
|
426
|
+
out_paths.append(out_path)
|
|
427
|
+
|
|
428
|
+
return out_paths
|
|
429
|
+
|
|
430
|
+
def create_blackbox_model(
|
|
431
|
+
self,
|
|
432
|
+
input_models: Union[frozenset, Tuple[str, ...]],
|
|
433
|
+
defines: FrozenSet[str],
|
|
434
|
+
) -> str:
|
|
435
|
+
mkdirp(self.tmp_dir)
|
|
436
|
+
out_path = os.path.join(self.tmp_dir, f"{uuid.uuid4().hex}.bb.v")
|
|
437
|
+
debug(f"Creating cell models for {input_models} at '{out_path}'…")
|
|
438
|
+
bad_yosys_line = re.compile(r"^\s+(\w+|(\\\S+?))\s*\(.*\).*;")
|
|
439
|
+
|
|
440
|
+
stack: List[Literal["specify", "primitive"]] = []
|
|
441
|
+
with open(out_path, "w", encoding="utf8") as out:
|
|
442
|
+
for model in input_models:
|
|
443
|
+
try:
|
|
444
|
+
for line in open(model, "r", encoding="utf8"):
|
|
445
|
+
if len(stack) == 0:
|
|
446
|
+
if line.strip().startswith("specify"):
|
|
447
|
+
stack.append("specify")
|
|
448
|
+
elif line.strip().startswith("primitive"):
|
|
449
|
+
stack.append("primitive")
|
|
450
|
+
elif bad_yosys_line.search(line) is None:
|
|
451
|
+
print(line.strip("\n"), file=out)
|
|
452
|
+
else:
|
|
453
|
+
if line.strip().startswith("endspecify"):
|
|
454
|
+
current = stack.pop()
|
|
455
|
+
if current != "specify":
|
|
456
|
+
raise ValueError(
|
|
457
|
+
f"Invalid specify block in {model}"
|
|
458
|
+
)
|
|
459
|
+
print("/* removed specify */", file=out)
|
|
460
|
+
elif line.strip().startswith("endprimitive"):
|
|
461
|
+
current = stack.pop()
|
|
462
|
+
if current != "primitive":
|
|
463
|
+
raise ValueError(
|
|
464
|
+
f"Invalid primitive block in {model}"
|
|
465
|
+
)
|
|
466
|
+
print("/* removed primitive */", file=out)
|
|
467
|
+
print("", file=out)
|
|
468
|
+
except ValueError as e:
|
|
469
|
+
err(f"Failed to pre-process input models for linting: {e}")
|
|
470
|
+
|
|
471
|
+
yosys = shutil.which("yosys") or shutil.which("yowasp-yosys")
|
|
472
|
+
|
|
473
|
+
if yosys is None:
|
|
474
|
+
warn(
|
|
475
|
+
"yosys and yowasp-yosys not found in PATH. This may trigger issues with blackboxing."
|
|
476
|
+
)
|
|
477
|
+
return out_path
|
|
478
|
+
|
|
479
|
+
commands = ""
|
|
480
|
+
for define in list(defines):
|
|
481
|
+
commands += f"verilog_defines -D{define};\n"
|
|
482
|
+
commands += f"read_verilog -sv -lib {out_path};\n"
|
|
483
|
+
|
|
484
|
+
output_log_path = f"{out_path}_yosys.log"
|
|
485
|
+
output_log = open(output_log_path, "wb")
|
|
486
|
+
try:
|
|
487
|
+
subprocess.check_call(
|
|
488
|
+
[
|
|
489
|
+
yosys,
|
|
490
|
+
"-p",
|
|
491
|
+
f"""
|
|
492
|
+
{commands}
|
|
493
|
+
blackbox;
|
|
494
|
+
write_verilog -noattr -noexpr -nohex -nodec -defparam -blackboxes {out_path};
|
|
495
|
+
""",
|
|
496
|
+
],
|
|
497
|
+
stdout=output_log,
|
|
498
|
+
stderr=subprocess.STDOUT,
|
|
499
|
+
)
|
|
500
|
+
except subprocess.CalledProcessError as e:
|
|
501
|
+
output_log.close()
|
|
502
|
+
err(f"Failed to pre-process input models for linting with Yosys: {e}")
|
|
503
|
+
err(open(output_log_path, "r", encoding="utf8").read())
|
|
504
|
+
err("Will attempt to load models into linter as-is.")
|
|
505
|
+
|
|
506
|
+
return out_path
|
|
507
|
+
|
|
508
|
+
def get_lib_voltage(
|
|
509
|
+
self,
|
|
510
|
+
input_lib: str,
|
|
511
|
+
) -> Optional[Decimal]:
|
|
512
|
+
"""
|
|
513
|
+
Extract the voltage from the default operating conditions of a liberty file.
|
|
514
|
+
|
|
515
|
+
Returns ``None`` if and only if the ``default_operating_conditions`` key
|
|
516
|
+
does not exist and the number of operating conditions enumerated is not
|
|
517
|
+
exactly 1 (one).
|
|
518
|
+
|
|
519
|
+
:param input_lib: The lib file in question
|
|
520
|
+
:returns: The voltage in question
|
|
521
|
+
"""
|
|
522
|
+
parser = libparse.LibertyParser(open(input_lib, encoding="utf8"))
|
|
523
|
+
ast = parser.ast
|
|
524
|
+
|
|
525
|
+
default_operating_conditions_id = None
|
|
526
|
+
operating_conditions_raw = {}
|
|
527
|
+
for child in ast.children:
|
|
528
|
+
if child.id == "default_operating_conditions":
|
|
529
|
+
default_operating_conditions_id = child.value
|
|
530
|
+
if child.id == "operating_conditions":
|
|
531
|
+
operating_conditions_raw[child.args[0]] = child
|
|
532
|
+
|
|
533
|
+
if default_operating_conditions_id is None:
|
|
534
|
+
if len(operating_conditions_raw) > 1:
|
|
535
|
+
warn(
|
|
536
|
+
f"No default operating condition defined in lib file '{input_lib}', and the lib file has multiple operating conditions."
|
|
537
|
+
)
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
elif len(operating_conditions_raw) < 1:
|
|
541
|
+
warn(f"Lib file '{input_lib}' has no operating conditions set.")
|
|
542
|
+
return None
|
|
543
|
+
default_operating_conditions_id = list(operating_conditions_raw.keys())[0]
|
|
544
|
+
|
|
545
|
+
operating_conditions = operating_conditions_raw[default_operating_conditions_id]
|
|
546
|
+
operating_condition_dict = {}
|
|
547
|
+
for child in operating_conditions.children:
|
|
548
|
+
operating_condition_dict[child.id] = child.value
|
|
549
|
+
return Decimal(operating_condition_dict["voltage"])
|