librelane 2.4.0__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 (170) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +479 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +63 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +246 -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 +456 -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 +116 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +155 -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 +743 -0
  27. librelane/container.py +285 -0
  28. librelane/env_info.py +320 -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 +327 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +1049 -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/help/__main__.py +39 -0
  52. librelane/logging/__init__.py +40 -0
  53. librelane/logging/logger.py +323 -0
  54. librelane/open_pdks_rev +1 -0
  55. librelane/plugins.py +21 -0
  56. librelane/py.typed +0 -0
  57. librelane/scripts/base.sdc +80 -0
  58. librelane/scripts/klayout/Readme.md +2 -0
  59. librelane/scripts/klayout/open_design.py +63 -0
  60. librelane/scripts/klayout/render.py +121 -0
  61. librelane/scripts/klayout/stream_out.py +176 -0
  62. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  63. librelane/scripts/klayout/xor.drc +120 -0
  64. librelane/scripts/magic/Readme.md +1 -0
  65. librelane/scripts/magic/common/read.tcl +114 -0
  66. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  67. librelane/scripts/magic/def/mag.tcl +19 -0
  68. librelane/scripts/magic/def/mag_gds.tcl +79 -0
  69. librelane/scripts/magic/drc.tcl +78 -0
  70. librelane/scripts/magic/extract_spice.tcl +98 -0
  71. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  72. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  73. librelane/scripts/magic/gds/extras_mag.tcl +45 -0
  74. librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
  75. librelane/scripts/magic/get_bbox.tcl +11 -0
  76. librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
  77. librelane/scripts/magic/lef/maglef.tcl +26 -0
  78. librelane/scripts/magic/lef.tcl +57 -0
  79. librelane/scripts/magic/open.tcl +28 -0
  80. librelane/scripts/magic/wrapper.tcl +21 -0
  81. librelane/scripts/netgen/setup.tcl +28 -0
  82. librelane/scripts/odbpy/apply_def_template.py +49 -0
  83. librelane/scripts/odbpy/cell_frequency.py +107 -0
  84. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  85. librelane/scripts/odbpy/contextualize.py +109 -0
  86. librelane/scripts/odbpy/defutil.py +573 -0
  87. librelane/scripts/odbpy/diodes.py +373 -0
  88. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  89. librelane/scripts/odbpy/eco_buffer.py +181 -0
  90. librelane/scripts/odbpy/eco_diode.py +139 -0
  91. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  92. librelane/scripts/odbpy/io_place.py +482 -0
  93. librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
  94. librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
  95. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  96. librelane/scripts/odbpy/lefutil.py +97 -0
  97. librelane/scripts/odbpy/placers.py +162 -0
  98. librelane/scripts/odbpy/power_utils.py +397 -0
  99. librelane/scripts/odbpy/random_place.py +57 -0
  100. librelane/scripts/odbpy/reader.py +250 -0
  101. librelane/scripts/odbpy/remove_buffers.py +173 -0
  102. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  103. librelane/scripts/odbpy/wire_lengths.py +93 -0
  104. librelane/scripts/openroad/antenna_check.tcl +20 -0
  105. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  106. librelane/scripts/openroad/basic_mp.tcl +24 -0
  107. librelane/scripts/openroad/buffer_list.tcl +10 -0
  108. librelane/scripts/openroad/common/dpl.tcl +24 -0
  109. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  110. librelane/scripts/openroad/common/grt.tcl +32 -0
  111. librelane/scripts/openroad/common/io.tcl +540 -0
  112. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  113. librelane/scripts/openroad/common/resizer.tcl +103 -0
  114. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  115. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  116. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  117. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  118. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  119. librelane/scripts/openroad/cts.tcl +80 -0
  120. librelane/scripts/openroad/cut_rows.tcl +24 -0
  121. librelane/scripts/openroad/dpl.tcl +24 -0
  122. librelane/scripts/openroad/drt.tcl +37 -0
  123. librelane/scripts/openroad/fill.tcl +30 -0
  124. librelane/scripts/openroad/floorplan.tcl +145 -0
  125. librelane/scripts/openroad/gpl.tcl +88 -0
  126. librelane/scripts/openroad/grt.tcl +30 -0
  127. librelane/scripts/openroad/gui.tcl +37 -0
  128. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  129. librelane/scripts/openroad/ioplacer.tcl +67 -0
  130. librelane/scripts/openroad/irdrop.tcl +51 -0
  131. librelane/scripts/openroad/pdn.tcl +52 -0
  132. librelane/scripts/openroad/rcx.tcl +32 -0
  133. librelane/scripts/openroad/repair_design.tcl +70 -0
  134. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  135. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  136. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  137. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  138. librelane/scripts/openroad/sta/corner.tcl +393 -0
  139. librelane/scripts/openroad/tapcell.tcl +25 -0
  140. librelane/scripts/openroad/write_views.tcl +27 -0
  141. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  142. librelane/scripts/pyosys/json_header.py +84 -0
  143. librelane/scripts/pyosys/synthesize.py +493 -0
  144. librelane/scripts/pyosys/ys_common.py +153 -0
  145. librelane/scripts/tclsh/hello.tcl +1 -0
  146. librelane/state/__init__.py +24 -0
  147. librelane/state/__main__.py +61 -0
  148. librelane/state/design_format.py +195 -0
  149. librelane/state/state.py +359 -0
  150. librelane/steps/__init__.py +61 -0
  151. librelane/steps/__main__.py +510 -0
  152. librelane/steps/checker.py +637 -0
  153. librelane/steps/common_variables.py +340 -0
  154. librelane/steps/cvc_rv.py +169 -0
  155. librelane/steps/klayout.py +509 -0
  156. librelane/steps/magic.py +576 -0
  157. librelane/steps/misc.py +160 -0
  158. librelane/steps/netgen.py +253 -0
  159. librelane/steps/odb.py +1088 -0
  160. librelane/steps/openroad.py +2460 -0
  161. librelane/steps/openroad_alerts.py +102 -0
  162. librelane/steps/pyosys.py +640 -0
  163. librelane/steps/step.py +1571 -0
  164. librelane/steps/tclstep.py +288 -0
  165. librelane/steps/verilator.py +222 -0
  166. librelane/steps/yosys.py +371 -0
  167. librelane-2.4.0.dist-info/METADATA +169 -0
  168. librelane-2.4.0.dist-info/RECORD +170 -0
  169. librelane-2.4.0.dist-info/WHEEL +4 -0
  170. librelane-2.4.0.dist-info/entry_points.txt +9 -0
librelane/__init__.py ADDED
@@ -0,0 +1,38 @@
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
+ """
15
+ The LibreLane API
16
+ ----------------
17
+
18
+ Documented elements of this API represent the primary programming interface for
19
+ the LibreLane infrastructure.
20
+
21
+ The various elements of LibreLane are organized into modules. You may import them
22
+ using their module name as follows:
23
+
24
+ .. code-block:: python
25
+
26
+ import librelane.common
27
+
28
+ .. no-imported-members
29
+
30
+ .. comment
31
+ .. data:: discovered_plugins
32
+
33
+ A dictionary of detected LibreLane plugins, with the module name as a key and
34
+ the module version as a version.
35
+ """
36
+ from .plugins import discovered_plugins
37
+ from .__version__ import __version__
38
+ from .env_info import env_info_cli
librelane/__main__.py ADDED
@@ -0,0 +1,479 @@
1
+ # Copyright 2025 LibreLane Contributors
2
+ #
3
+ # Adapted from OpenLane
4
+ #
5
+ # Copyright 2023 Efabless Corporation
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ import os
19
+ import sys
20
+ import glob
21
+ import shutil
22
+ import marshal
23
+ import tempfile
24
+ import traceback
25
+ from textwrap import dedent
26
+ from functools import partial
27
+ from typing import Any, Dict, Sequence, Tuple, Type, Optional, List
28
+
29
+ import click
30
+ from cloup import (
31
+ option,
32
+ option_group,
33
+ command,
34
+ )
35
+ from cloup.constraints import (
36
+ mutually_exclusive,
37
+ )
38
+
39
+
40
+ from .__version__ import __version__
41
+ from .state import State, DesignFormat
42
+ from .logging import (
43
+ debug,
44
+ err,
45
+ warn,
46
+ info,
47
+ )
48
+ from . import common
49
+ from .container import run_in_container
50
+ from .plugins import discovered_plugins
51
+ from .common.cli import formatter_settings
52
+ from .config import Config, InvalidConfig, PassedDirectoryError
53
+ from .flows import Flow, SequentialFlow, FlowException, FlowError, cloup_flow_opts
54
+
55
+
56
+ def run(
57
+ ctx: click.Context,
58
+ flow_name: Optional[str],
59
+ pdk_root: Optional[str],
60
+ pdk: str,
61
+ scl: Optional[str],
62
+ config_files: Sequence[str],
63
+ tag: Optional[str],
64
+ last_run: bool,
65
+ frm: Optional[str],
66
+ to: Optional[str],
67
+ skip: Tuple[str, ...],
68
+ overwrite: bool,
69
+ reproducible: Optional[str],
70
+ with_initial_state: Optional[State],
71
+ config_override_strings: List[str],
72
+ _force_run_dir: Optional[str],
73
+ design_dir: Optional[str],
74
+ initial_state_element_override: Sequence[str],
75
+ view_save_path: Optional[str] = None,
76
+ ef_view_save_path: Optional[str] = None,
77
+ ):
78
+ try:
79
+ if len(config_files) == 0:
80
+ err("No config file(s) have been provided.")
81
+ ctx.exit(1)
82
+
83
+ TargetFlow: Optional[Type[Flow]] = Flow.factory.get("Classic")
84
+
85
+ for config_file in config_files:
86
+ if meta := Config.get_meta(config_file):
87
+ # to maintain backwards compat, in 3 you will need to explicitly
88
+ # set the flow you're substituting
89
+ target_flow_desc = meta.flow or "Classic"
90
+
91
+ if isinstance(target_flow_desc, str):
92
+ if found := Flow.factory.get(target_flow_desc):
93
+ TargetFlow = found
94
+ else:
95
+ err(
96
+ f"Unknown flow '{meta.flow}' specified in configuration file's 'meta' object."
97
+ )
98
+ ctx.exit(1)
99
+ elif isinstance(target_flow_desc, list):
100
+ TargetFlow = SequentialFlow.make(target_flow_desc)
101
+ if meta.substituting_steps is not None and issubclass(
102
+ TargetFlow, SequentialFlow
103
+ ):
104
+ if meta.flow is None:
105
+ warn(
106
+ 'config_file currently has substituting_steps set with no flow, where it will fall back to Classic. Starting LibreLane 3.0.0, this will be an error. Please update your configuration to explicitly set "flow" to "Classic".'
107
+ )
108
+ TargetFlow = TargetFlow.Substitute(meta.substituting_steps) # type: ignore # Type checker is being rowdy with this one
109
+
110
+ if flow_name is not None:
111
+ if found := Flow.factory.get(flow_name):
112
+ TargetFlow = found
113
+ else:
114
+ err(f"Unknown flow '{flow_name}' passed to initialization function.")
115
+ ctx.exit(1)
116
+
117
+ if len(initial_state_element_override):
118
+ if with_initial_state is None:
119
+ with_initial_state = State()
120
+ overrides = {}
121
+ for element in initial_state_element_override:
122
+ element_split = element.split("=", maxsplit=1)
123
+ if len(element_split) < 2:
124
+ err(f"Invalid initial state element override: '{element}'.")
125
+ ctx.exit(1)
126
+ df_id, path = element_split
127
+ design_format = DesignFormat.by_id(df_id)
128
+ if design_format is None:
129
+ err(f"Invalid design format ID: '{df_id}'.")
130
+ ctx.exit(1)
131
+ overrides[design_format] = common.Path(path)
132
+
133
+ with_initial_state = with_initial_state.__class__(
134
+ with_initial_state,
135
+ overrides=overrides,
136
+ )
137
+
138
+ assert (
139
+ TargetFlow is not None
140
+ ), "TargetFlow is unexpectedly None. Please report this as a bug."
141
+
142
+ kwargs: Dict[str, Any] = {
143
+ "pdk_root": pdk_root,
144
+ "pdk": pdk,
145
+ "scl": scl,
146
+ "config_override_strings": config_override_strings,
147
+ "design_dir": design_dir,
148
+ }
149
+ flow = TargetFlow(config_files, **kwargs)
150
+ except PassedDirectoryError as e:
151
+ err(e)
152
+ info(
153
+ f"If you meant to pass this as a design directory alongside valid configuration files, pass it as '--design-dir {e.config}'."
154
+ )
155
+ ctx.exit(1)
156
+ except InvalidConfig as e:
157
+ if len(e.warnings) > 0:
158
+ warn("The following warnings have been generated:")
159
+ for warning in e.warnings:
160
+ warn(warning)
161
+ err(f"Errors have occurred while loading the {e.config}.")
162
+ for error in e.errors:
163
+ err(error)
164
+
165
+ err("LibreLane will now quit. Please check your configuration.")
166
+ ctx.exit(1)
167
+ except ValueError as e:
168
+ err(e)
169
+ debug(traceback.format_exc())
170
+ err("LibreLane will now quit.")
171
+ ctx.exit(1)
172
+
173
+ try:
174
+ state_out = flow.start(
175
+ tag=tag,
176
+ last_run=last_run,
177
+ frm=frm,
178
+ to=to,
179
+ skip=skip,
180
+ with_initial_state=with_initial_state,
181
+ reproducible=reproducible,
182
+ _force_run_dir=_force_run_dir,
183
+ overwrite=overwrite,
184
+ )
185
+ except FlowException as e:
186
+ err(f"The flow has encountered an unexpected error:\n{e}")
187
+ err("LibreLane will now quit.")
188
+ ctx.exit(1)
189
+ except FlowError as e:
190
+ err(f"The following error was encountered while running the flow:\n{e}")
191
+ err("LibreLane will now quit.")
192
+ ctx.exit(2)
193
+
194
+ if vsp := view_save_path:
195
+ state_out.save_snapshot(vsp)
196
+ if evsp := ef_view_save_path:
197
+ flow._save_snapshot_ef(evsp)
198
+
199
+
200
+ def print_version(ctx: click.Context, param: click.Parameter, value: bool):
201
+ if not value:
202
+ return
203
+
204
+ message = dedent(
205
+ f"""
206
+ LibreLane v{__version__}
207
+
208
+ Copyright ©2020-2025 Efabless Corporation, The American University in
209
+ Cairo, and other contributors.
210
+
211
+ Available under the Apache License, version 2. Included with the source code,
212
+ but you can also get a copy at https://www.apache.org/licenses/LICENSE-2.0
213
+
214
+ Included tools and utilities may be distributed under stricter licenses.
215
+ """
216
+ ).strip()
217
+
218
+ print(message)
219
+
220
+ if len(discovered_plugins) > 0:
221
+ print("Discovered plugins:")
222
+ for name, module in discovered_plugins.items():
223
+ print(f"{name} -> {module.__version__}")
224
+
225
+ ctx.exit(0)
226
+
227
+
228
+ def print_bare_version(
229
+ ctx: click.Context,
230
+ param: click.Parameter,
231
+ value: bool,
232
+ ):
233
+ if not value:
234
+ return
235
+ print(__version__, end="")
236
+ ctx.exit(0)
237
+
238
+
239
+ def run_included_example(
240
+ ctx: click.Context,
241
+ smoke_test: bool,
242
+ example: Optional[str],
243
+ **kwargs,
244
+ ):
245
+ assert smoke_test or example is not None
246
+ value = "spm"
247
+ if not smoke_test and example is not None:
248
+ value = example
249
+
250
+ example_path = os.path.join(common.get_librelane_root(), "examples", value)
251
+ if not os.path.isdir(example_path):
252
+ print(f"Unknown example '{value}'.", file=sys.stderr)
253
+ ctx.exit(1)
254
+
255
+ status = 0
256
+ final_path = os.path.join(os.getcwd(), value)
257
+ cleanup = False
258
+ if smoke_test:
259
+ d = tempfile.mkdtemp("librelane")
260
+ final_path = os.path.join(d, "smoke_test_design")
261
+ cleanup = True
262
+ kwargs.update(
263
+ flow_name=None,
264
+ scl=None,
265
+ tag=None,
266
+ last_run=False,
267
+ frm=None,
268
+ to=None,
269
+ reproducible=None,
270
+ skip=(),
271
+ with_initial_state=None,
272
+ config_override_strings=[],
273
+ _force_run_dir=None,
274
+ design_dir=None,
275
+ )
276
+ try:
277
+ if os.path.isdir(final_path):
278
+ print(f"A directory named {value} already exists.", file=sys.stderr)
279
+ ctx.exit(1)
280
+
281
+ # 1. Copy the files
282
+ common.recreate_tree(example_path, final_path)
283
+ config_file = glob.glob(os.path.join(final_path, "config.*"))[0]
284
+
285
+ # 2. Run
286
+ run(
287
+ ctx,
288
+ config_files=[config_file],
289
+ **kwargs,
290
+ )
291
+ if smoke_test:
292
+ info("Smoke test passed.")
293
+ except KeyboardInterrupt:
294
+ if smoke_test:
295
+ info("Smoke test aborted.")
296
+ status = -1
297
+ finally:
298
+ try:
299
+ if cleanup:
300
+ shutil.rmtree(final_path)
301
+ except FileNotFoundError:
302
+ pass
303
+
304
+ ctx.exit(status)
305
+
306
+
307
+ def cli_in_container(
308
+ ctx: click.Context,
309
+ param: click.Parameter,
310
+ value: bool,
311
+ ):
312
+ if not value:
313
+ return
314
+
315
+ mounts = list(ctx.params.get("docker_mounts") or ())
316
+ tty: bool = ctx.params.get("docker_tty", True)
317
+ pdk_root = ctx.params.get("pdk_root")
318
+
319
+ try:
320
+ containerized_index = sys.argv.index("--dockerized")
321
+ except ValueError:
322
+ containerized_index = sys.argv.index("--containerized")
323
+
324
+ argv = sys.argv[containerized_index + 1 :]
325
+
326
+ final_argv = ["zsh"]
327
+ if len(argv) != 0:
328
+ final_argv = ["python3", "-m", "librelane"] + argv
329
+
330
+ container_image = os.getenv(
331
+ "LIBRELANE_IMAGE_OVERRIDE", f"ghcr.io/librelane/librelane:{__version__}"
332
+ )
333
+
334
+ try:
335
+ run_in_container(
336
+ container_image,
337
+ final_argv,
338
+ pdk_root=pdk_root,
339
+ other_mounts=mounts,
340
+ tty=tty,
341
+ )
342
+ except ValueError as e:
343
+ err(e)
344
+ ctx.exit(1)
345
+ except Exception as e:
346
+ traceback.print_exc(file=sys.stderr)
347
+ err(e)
348
+ ctx.exit(1)
349
+
350
+ ctx.exit(0)
351
+
352
+
353
+ o = partial(option, show_default=True)
354
+
355
+
356
+ @command(
357
+ no_args_is_help=True,
358
+ formatter_settings=formatter_settings,
359
+ )
360
+ @option_group(
361
+ "Copy final views",
362
+ o(
363
+ "--save-views-to",
364
+ "view_save_path",
365
+ type=click.Path(file_okay=False, dir_okay=True),
366
+ default=None,
367
+ help="A directory to copy the final views to, where each format is saved under a directory named after the corner ID (much like the 'final' directory after running a flow.)",
368
+ ),
369
+ o(
370
+ "--ef-save-views-to",
371
+ "ef_view_save_path",
372
+ type=click.Path(file_okay=False, dir_okay=True),
373
+ default=None,
374
+ help="A directory to copy the final views to in the Efabless format, compatible with Caravel User Project.",
375
+ ),
376
+ )
377
+ @option_group(
378
+ "Containerization options",
379
+ o(
380
+ "--docker-mount",
381
+ "--container-mount",
382
+ "-m",
383
+ "docker_mounts",
384
+ multiple=True,
385
+ is_eager=True, # container options should be processed before anything else
386
+ default=(),
387
+ help="Used to mount more directories in dockerized mode. If a valid directory is specified, it will be mounted in the same path in the container. Otherwise, the value of the option will be passed to the container engine verbatim. Must be passed before --containerized/--dockerized, has no effect if not set.",
388
+ ),
389
+ o(
390
+ "--docker-tty/--docker-no-tty",
391
+ "--container-tty/--container-no-tty",
392
+ is_eager=True, # container options should be processed before anything else
393
+ default=True,
394
+ help="Controls the allocation of a virtual terminal by passing -t to the Docker-compatible container engine invocation. Must be passed before --containerized/--dockerized, has no effect if not set.",
395
+ ),
396
+ o(
397
+ "--dockerized",
398
+ "--containerized",
399
+ default=False,
400
+ is_flag=True,
401
+ is_eager=True, # docker options should be processed before anything else
402
+ help="Run the remaining flags using a containerized version of LibreLane. Some caveats apply. Must precede all options except --{docker,container}-mount, --{docker,container}-[no-]tty.",
403
+ callback=cli_in_container,
404
+ ),
405
+ )
406
+ @option_group(
407
+ "Subcommands",
408
+ o(
409
+ "--version",
410
+ is_flag=True,
411
+ is_eager=True,
412
+ help="Prints version information and exits",
413
+ callback=print_version,
414
+ ),
415
+ o(
416
+ "--bare-version",
417
+ is_flag=True,
418
+ is_eager=True,
419
+ callback=print_bare_version,
420
+ hidden=True,
421
+ ),
422
+ o(
423
+ "--smoke-test",
424
+ is_flag=True,
425
+ help="Runs a basic LibreLane smoke test, the results of which are temporary and discarded.",
426
+ ),
427
+ o(
428
+ "--run-example",
429
+ default=None,
430
+ help="Copies one of the LibreLane examples to the current working directory and runs it.",
431
+ ),
432
+ constraint=mutually_exclusive,
433
+ )
434
+ @cloup_flow_opts(
435
+ _enable_debug_flags=True,
436
+ sequential_flow_reproducible=True,
437
+ enable_overwrite_flag=True,
438
+ enable_initial_state_element=True,
439
+ )
440
+ @click.pass_context
441
+ def cli(ctx, /, **kwargs):
442
+ """
443
+ Runs an LibreLane flow via the commandline using a design configuration
444
+ object.
445
+
446
+ Try 'python3 -m librelane.steps --help' for step-specific options, including
447
+ reproducibles and running a step standalone.
448
+ """
449
+ args = kwargs["config_files"]
450
+ run_kwargs = kwargs.copy()
451
+
452
+ if len(args) == 1 and args[0].endswith(".marshalled"):
453
+ run_kwargs = marshal.load(open(args[0], "rb"))
454
+ run_kwargs.update(**{k: kwargs[k] for k in ["pdk_root", "pdk", "scl"]})
455
+
456
+ smoke_test = kwargs.pop("smoke_test", False)
457
+ example = kwargs.pop("run_example", None)
458
+
459
+ for subcommand_flag in [
460
+ "docker_tty",
461
+ "docker_mounts",
462
+ "dockerized",
463
+ "version",
464
+ "bare_version",
465
+ "smoke_test",
466
+ "run_example",
467
+ ]:
468
+ if subcommand_flag in run_kwargs:
469
+ del run_kwargs[subcommand_flag]
470
+ if smoke_test or example is not None:
471
+ run_kwargs.pop("config_files", None)
472
+ run_included_example(ctx, smoke_test, example, **run_kwargs)
473
+ else:
474
+ run(ctx, **run_kwargs)
475
+ ctx.exit(0)
476
+
477
+
478
+ if __name__ == "__main__":
479
+ cli()
@@ -0,0 +1,43 @@
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 importlib.metadata
16
+ import sys
17
+
18
+
19
+ def __get_version():
20
+ try:
21
+ return importlib.metadata.version(__package__ or __name__)
22
+ except importlib.metadata.PackageNotFoundError:
23
+ import re
24
+
25
+ rx = re.compile(r"version\s*=\s*\"([^\"]+)\"")
26
+ librelane_directory = os.path.dirname(
27
+ os.path.dirname(os.path.abspath(__file__))
28
+ )
29
+ pyproject_path = os.path.join(librelane_directory, "pyproject.toml")
30
+ try:
31
+ match = rx.search(open(pyproject_path, encoding="utf8").read())
32
+ assert match is not None, "pyproject.toml found, but without a version"
33
+ return match[1]
34
+ except FileNotFoundError:
35
+ print("Warning: Failed to extract LibreLane version.", file=sys.stderr)
36
+ return "UNKNOWN"
37
+
38
+
39
+ __version__ = __get_version()
40
+
41
+
42
+ if __name__ == "__main__":
43
+ print(__version__, end="")
@@ -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
+ """
15
+ Common Utilities Module
16
+ -----------------------
17
+
18
+ A number of common utility functions and classes used throughout the codebase.
19
+ """
20
+ import os
21
+
22
+ from .tcl import TclUtils
23
+ from .metrics import parse_metric_modifiers, aggregate_metrics
24
+ from . import metrics
25
+ from .generic_dict import (
26
+ GenericDictEncoder,
27
+ GenericDict,
28
+ GenericImmutableDict,
29
+ copy_recursive,
30
+ )
31
+ from .misc import (
32
+ idem,
33
+ get_librelane_root,
34
+ get_script_dir,
35
+ get_opdks_rev,
36
+ slugify,
37
+ protected,
38
+ final,
39
+ mkdirp,
40
+ zip_first,
41
+ format_size,
42
+ format_elapsed_time,
43
+ Filter,
44
+ recreate_tree,
45
+ get_latest_file,
46
+ process_list_file,
47
+ count_occurences,
48
+ _get_process_limit,
49
+ )
50
+ from .types import (
51
+ is_number,
52
+ is_real_number,
53
+ is_string,
54
+ Number,
55
+ Path,
56
+ AnyPath,
57
+ ScopedFile,
58
+ )
59
+ from .toolbox import Toolbox
60
+ from .drc import DRC, Violation
61
+ from . import cli
62
+ from .tpe import get_tpe, set_tpe
63
+ from .ring_buffer import RingBuffer
@@ -0,0 +1,75 @@
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 enum import IntEnum
15
+ from cloup import (
16
+ HelpFormatter,
17
+ HelpTheme,
18
+ Style,
19
+ )
20
+ from typing import Optional, Type, Union
21
+
22
+ from click import (
23
+ Choice,
24
+ Context,
25
+ Parameter,
26
+ )
27
+
28
+ formatter_settings = HelpFormatter.settings(
29
+ theme=HelpTheme(
30
+ invoked_command=Style(fg="bright_yellow"),
31
+ heading=Style(fg="cyan", bold=True),
32
+ constraint=Style(fg="magenta"),
33
+ col1=Style(fg="bright_yellow"),
34
+ )
35
+ )
36
+
37
+
38
+ class IntEnumChoice(Choice):
39
+ def __init__(self, enum: Type[IntEnum], case_sensitive: bool = True) -> None:
40
+ super().__init__([e.name for e in enum], case_sensitive)
41
+ self.__enum = enum
42
+
43
+ def convert(
44
+ self,
45
+ value: Union[str, int],
46
+ param: Optional[Parameter],
47
+ ctx: Optional[Context],
48
+ ) -> IntEnum:
49
+ try:
50
+ if isinstance(value, int):
51
+ return self.__enum(value)
52
+ else:
53
+ as_int: Optional[int] = None
54
+ try:
55
+ as_int = int(value)
56
+ except ValueError:
57
+ pass
58
+ if as_int is not None:
59
+ return self.__enum(as_int)
60
+ return self.__enum[value]
61
+ except KeyError:
62
+ self.fail(
63
+ f"{value} is not a not a valid key nor value for IntEnum {self.__enum.__name__}"
64
+ )
65
+ except ValueError:
66
+ self.fail(
67
+ f"{value} is not a not a valid value for IntEnum {self.__enum.__name__}"
68
+ )
69
+
70
+ def get_metavar(self, param: Parameter) -> str:
71
+ _bk = self.choices
72
+ self.choices = [f"{e.name} or {e.value}" for e in self.__enum]
73
+ result = super().get_metavar(param)
74
+ self.choices = _bk
75
+ return result