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.

Files changed (166) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +470 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +61 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +245 -0
  7. librelane/common/generic_dict.py +319 -0
  8. librelane/common/metrics/__init__.py +35 -0
  9. librelane/common/metrics/__main__.py +413 -0
  10. librelane/common/metrics/library.py +354 -0
  11. librelane/common/metrics/metric.py +186 -0
  12. librelane/common/metrics/util.py +279 -0
  13. librelane/common/misc.py +402 -0
  14. librelane/common/ring_buffer.py +63 -0
  15. librelane/common/tcl.py +80 -0
  16. librelane/common/toolbox.py +549 -0
  17. librelane/common/tpe.py +41 -0
  18. librelane/common/types.py +117 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +158 -0
  21. librelane/config/config.py +1025 -0
  22. librelane/config/flow.py +490 -0
  23. librelane/config/pdk_compat.py +255 -0
  24. librelane/config/preprocessor.py +464 -0
  25. librelane/config/removals.py +45 -0
  26. librelane/config/variable.py +722 -0
  27. librelane/container.py +264 -0
  28. librelane/env_info.py +306 -0
  29. librelane/examples/spm/config.yaml +33 -0
  30. librelane/examples/spm/pin_order.cfg +14 -0
  31. librelane/examples/spm/src/impl.sdc +73 -0
  32. librelane/examples/spm/src/signoff.sdc +68 -0
  33. librelane/examples/spm/src/spm.v +73 -0
  34. librelane/examples/spm/verify/spm_tb.v +106 -0
  35. librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
  36. librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
  37. librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
  38. librelane/examples/spm-user_project_wrapper/config.json +13 -0
  39. librelane/examples/spm-user_project_wrapper/defines.v +66 -0
  40. librelane/examples/spm-user_project_wrapper/template.def +7656 -0
  41. librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
  42. librelane/flows/__init__.py +24 -0
  43. librelane/flows/builtins.py +18 -0
  44. librelane/flows/classic.py +330 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +985 -0
  47. librelane/flows/misc.py +71 -0
  48. librelane/flows/optimizing.py +179 -0
  49. librelane/flows/sequential.py +367 -0
  50. librelane/flows/synth_explore.py +173 -0
  51. librelane/logging/__init__.py +40 -0
  52. librelane/logging/logger.py +323 -0
  53. librelane/open_pdks_rev +1 -0
  54. librelane/plugins.py +21 -0
  55. librelane/py.typed +0 -0
  56. librelane/scripts/base.sdc +80 -0
  57. librelane/scripts/klayout/Readme.md +2 -0
  58. librelane/scripts/klayout/open_design.py +63 -0
  59. librelane/scripts/klayout/render.py +121 -0
  60. librelane/scripts/klayout/stream_out.py +176 -0
  61. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  62. librelane/scripts/klayout/xor.drc +120 -0
  63. librelane/scripts/magic/Readme.md +1 -0
  64. librelane/scripts/magic/common/read.tcl +114 -0
  65. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  66. librelane/scripts/magic/def/mag.tcl +19 -0
  67. librelane/scripts/magic/def/mag_gds.tcl +81 -0
  68. librelane/scripts/magic/drc.tcl +79 -0
  69. librelane/scripts/magic/extract_spice.tcl +98 -0
  70. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  71. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  72. librelane/scripts/magic/gds/extras_mag.tcl +47 -0
  73. librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
  74. librelane/scripts/magic/get_bbox.tcl +11 -0
  75. librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
  76. librelane/scripts/magic/lef/maglef.tcl +27 -0
  77. librelane/scripts/magic/lef.tcl +57 -0
  78. librelane/scripts/magic/open.tcl +28 -0
  79. librelane/scripts/magic/wrapper.tcl +19 -0
  80. librelane/scripts/netgen/setup.tcl +28 -0
  81. librelane/scripts/odbpy/apply_def_template.py +49 -0
  82. librelane/scripts/odbpy/cell_frequency.py +107 -0
  83. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  84. librelane/scripts/odbpy/contextualize.py +109 -0
  85. librelane/scripts/odbpy/defutil.py +574 -0
  86. librelane/scripts/odbpy/diodes.py +373 -0
  87. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  88. librelane/scripts/odbpy/exception_codes.py +17 -0
  89. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  90. librelane/scripts/odbpy/io_place.py +482 -0
  91. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  92. librelane/scripts/odbpy/lefutil.py +97 -0
  93. librelane/scripts/odbpy/placers.py +162 -0
  94. librelane/scripts/odbpy/power_utils.py +395 -0
  95. librelane/scripts/odbpy/random_place.py +57 -0
  96. librelane/scripts/odbpy/reader.py +246 -0
  97. librelane/scripts/odbpy/remove_buffers.py +173 -0
  98. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  99. librelane/scripts/odbpy/wire_lengths.py +93 -0
  100. librelane/scripts/openroad/antenna_check.tcl +20 -0
  101. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  102. librelane/scripts/openroad/basic_mp.tcl +24 -0
  103. librelane/scripts/openroad/buffer_list.tcl +10 -0
  104. librelane/scripts/openroad/common/dpl.tcl +24 -0
  105. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  106. librelane/scripts/openroad/common/grt.tcl +32 -0
  107. librelane/scripts/openroad/common/io.tcl +476 -0
  108. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  109. librelane/scripts/openroad/common/resizer.tcl +103 -0
  110. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  111. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  112. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  113. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  114. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  115. librelane/scripts/openroad/cts.tcl +80 -0
  116. librelane/scripts/openroad/cut_rows.tcl +24 -0
  117. librelane/scripts/openroad/dpl.tcl +24 -0
  118. librelane/scripts/openroad/drt.tcl +37 -0
  119. librelane/scripts/openroad/fill.tcl +30 -0
  120. librelane/scripts/openroad/floorplan.tcl +145 -0
  121. librelane/scripts/openroad/gpl.tcl +88 -0
  122. librelane/scripts/openroad/grt.tcl +30 -0
  123. librelane/scripts/openroad/gui.tcl +15 -0
  124. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  125. librelane/scripts/openroad/ioplacer.tcl +67 -0
  126. librelane/scripts/openroad/irdrop.tcl +51 -0
  127. librelane/scripts/openroad/pdn.tcl +52 -0
  128. librelane/scripts/openroad/rcx.tcl +32 -0
  129. librelane/scripts/openroad/repair_design.tcl +70 -0
  130. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  131. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  132. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  133. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  134. librelane/scripts/openroad/sta/corner.tcl +393 -0
  135. librelane/scripts/openroad/tapcell.tcl +25 -0
  136. librelane/scripts/openroad/write_views.tcl +27 -0
  137. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  138. librelane/scripts/pyosys/json_header.py +84 -0
  139. librelane/scripts/pyosys/synthesize.py +493 -0
  140. librelane/scripts/pyosys/ys_common.py +153 -0
  141. librelane/scripts/tclsh/hello.tcl +1 -0
  142. librelane/state/__init__.py +24 -0
  143. librelane/state/__main__.py +61 -0
  144. librelane/state/design_format.py +180 -0
  145. librelane/state/state.py +351 -0
  146. librelane/steps/__init__.py +61 -0
  147. librelane/steps/__main__.py +511 -0
  148. librelane/steps/checker.py +637 -0
  149. librelane/steps/common_variables.py +340 -0
  150. librelane/steps/cvc_rv.py +169 -0
  151. librelane/steps/klayout.py +509 -0
  152. librelane/steps/magic.py +566 -0
  153. librelane/steps/misc.py +160 -0
  154. librelane/steps/netgen.py +253 -0
  155. librelane/steps/odb.py +955 -0
  156. librelane/steps/openroad.py +2433 -0
  157. librelane/steps/openroad_alerts.py +102 -0
  158. librelane/steps/pyosys.py +629 -0
  159. librelane/steps/step.py +1547 -0
  160. librelane/steps/tclstep.py +288 -0
  161. librelane/steps/verilator.py +222 -0
  162. librelane/steps/yosys.py +371 -0
  163. librelane-2.4.0.dev0.dist-info/METADATA +151 -0
  164. librelane-2.4.0.dev0.dist-info/RECORD +166 -0
  165. librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
  166. 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]
@@ -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"])