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
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,470 @@
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 sys
16
+ import glob
17
+ import shutil
18
+ import marshal
19
+ import tempfile
20
+ import traceback
21
+ import subprocess
22
+ from textwrap import dedent
23
+ from functools import partial
24
+ from typing import Any, Dict, Sequence, Tuple, Type, Optional, List
25
+
26
+ import click
27
+ from cloup import (
28
+ option,
29
+ option_group,
30
+ command,
31
+ )
32
+ from cloup.constraints import (
33
+ mutually_exclusive,
34
+ )
35
+
36
+
37
+ from .__version__ import __version__
38
+ from .state import State, DesignFormat
39
+ from .logging import (
40
+ debug,
41
+ err,
42
+ warn,
43
+ info,
44
+ )
45
+ from . import common
46
+ from .container import run_in_container
47
+ from .plugins import discovered_plugins
48
+ from .common.cli import formatter_settings
49
+ from .config import Config, InvalidConfig, PassedDirectoryError
50
+ from .flows import Flow, SequentialFlow, FlowException, FlowError, cloup_flow_opts
51
+
52
+
53
+ def run(
54
+ ctx: click.Context,
55
+ flow_name: Optional[str],
56
+ pdk_root: Optional[str],
57
+ pdk: str,
58
+ scl: Optional[str],
59
+ config_files: Sequence[str],
60
+ tag: Optional[str],
61
+ last_run: bool,
62
+ frm: Optional[str],
63
+ to: Optional[str],
64
+ skip: Tuple[str, ...],
65
+ overwrite: bool,
66
+ reproducible: Optional[str],
67
+ with_initial_state: Optional[State],
68
+ config_override_strings: List[str],
69
+ _force_run_dir: Optional[str],
70
+ design_dir: Optional[str],
71
+ initial_state_element_override: Sequence[str],
72
+ view_save_path: Optional[str] = None,
73
+ ef_view_save_path: Optional[str] = None,
74
+ ):
75
+ try:
76
+ if len(config_files) == 0:
77
+ err("No config file(s) have been provided.")
78
+ ctx.exit(1)
79
+
80
+ TargetFlow: Optional[Type[Flow]] = Flow.factory.get("Classic")
81
+
82
+ for config_file in config_files:
83
+ if meta := Config.get_meta(config_file):
84
+ # to maintain backwards compat, in 3 you will need to explicitly
85
+ # set the flow you're substituting
86
+ target_flow_desc = meta.flow or "Classic"
87
+
88
+ if isinstance(target_flow_desc, str):
89
+ if found := Flow.factory.get(target_flow_desc):
90
+ TargetFlow = found
91
+ else:
92
+ err(
93
+ f"Unknown flow '{meta.flow}' specified in configuration file's 'meta' object."
94
+ )
95
+ ctx.exit(1)
96
+ elif isinstance(target_flow_desc, list):
97
+ TargetFlow = SequentialFlow.make(target_flow_desc)
98
+ if meta.substituting_steps is not None and issubclass(
99
+ TargetFlow, SequentialFlow
100
+ ):
101
+ if meta.flow is None:
102
+ warn(
103
+ '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".'
104
+ )
105
+ TargetFlow = TargetFlow.Substitute(meta.substituting_steps) # type: ignore # Type checker is being rowdy with this one
106
+
107
+ if flow_name is not None:
108
+ if found := Flow.factory.get(flow_name):
109
+ TargetFlow = found
110
+ else:
111
+ err(f"Unknown flow '{flow_name}' passed to initialization function.")
112
+ ctx.exit(1)
113
+
114
+ if len(initial_state_element_override):
115
+ if with_initial_state is None:
116
+ with_initial_state = State()
117
+ overrides = {}
118
+ for element in initial_state_element_override:
119
+ element_split = element.split("=", maxsplit=1)
120
+ if len(element_split) < 2:
121
+ err(f"Invalid initial state element override: '{element}'.")
122
+ ctx.exit(1)
123
+ df_id, path = element_split
124
+ design_format = DesignFormat.by_id(df_id)
125
+ if design_format is None:
126
+ err(f"Invalid design format ID: '{df_id}'.")
127
+ ctx.exit(1)
128
+ overrides[design_format] = common.Path(path)
129
+
130
+ with_initial_state = with_initial_state.__class__(
131
+ with_initial_state,
132
+ overrides=overrides,
133
+ )
134
+
135
+ assert (
136
+ TargetFlow is not None
137
+ ), "TargetFlow is unexpectedly None. Please report this as a bug."
138
+
139
+ kwargs: Dict[str, Any] = {
140
+ "pdk_root": pdk_root,
141
+ "pdk": pdk,
142
+ "scl": scl,
143
+ "config_override_strings": config_override_strings,
144
+ "design_dir": design_dir,
145
+ }
146
+ flow = TargetFlow(config_files, **kwargs)
147
+ except PassedDirectoryError as e:
148
+ err(e)
149
+ info(
150
+ f"If you meant to pass this as a design directory alongside valid configuration files, pass it as '--design-dir {e.config}'."
151
+ )
152
+ ctx.exit(1)
153
+ except InvalidConfig as e:
154
+ if len(e.warnings) > 0:
155
+ warn("The following warnings have been generated:")
156
+ for warning in e.warnings:
157
+ warn(warning)
158
+ err(f"Errors have occurred while loading the {e.config}.")
159
+ for error in e.errors:
160
+ err(error)
161
+
162
+ err("LibreLane will now quit. Please check your configuration.")
163
+ ctx.exit(1)
164
+ except ValueError as e:
165
+ err(e)
166
+ debug(traceback.format_exc())
167
+ err("LibreLane will now quit.")
168
+ ctx.exit(1)
169
+
170
+ try:
171
+ state_out = flow.start(
172
+ tag=tag,
173
+ last_run=last_run,
174
+ frm=frm,
175
+ to=to,
176
+ skip=skip,
177
+ with_initial_state=with_initial_state,
178
+ reproducible=reproducible,
179
+ _force_run_dir=_force_run_dir,
180
+ overwrite=overwrite,
181
+ )
182
+ except FlowException as e:
183
+ err(f"The flow has encountered an unexpected error:\n{e}")
184
+ err("LibreLane will now quit.")
185
+ ctx.exit(1)
186
+ except FlowError as e:
187
+ err(f"The following error was encountered while running the flow:\n{e}")
188
+ err("LibreLane will now quit.")
189
+ ctx.exit(2)
190
+
191
+ if vsp := view_save_path:
192
+ state_out.save_snapshot(vsp)
193
+ if evsp := ef_view_save_path:
194
+ flow._save_snapshot_ef(evsp)
195
+
196
+
197
+ def print_version(ctx: click.Context, param: click.Parameter, value: bool):
198
+ if not value:
199
+ return
200
+
201
+ message = dedent(
202
+ f"""
203
+ LibreLane v{__version__}
204
+
205
+ Copyright ©2020-2023 Efabless Corporation and other contributors.
206
+
207
+ Available under the Apache License, version 2. Included with the source code,
208
+ but you can also get a copy at https://www.apache.org/licenses/LICENSE-2.0
209
+
210
+ Included tools and utilities may be distributed under stricter licenses.
211
+ """
212
+ ).strip()
213
+
214
+ print(message)
215
+
216
+ if len(discovered_plugins) > 0:
217
+ print("Discovered plugins:")
218
+ for name, module in discovered_plugins.items():
219
+ print(f"{name} -> {module.__version__}")
220
+
221
+ ctx.exit(0)
222
+
223
+
224
+ def print_bare_version(
225
+ ctx: click.Context,
226
+ param: click.Parameter,
227
+ value: bool,
228
+ ):
229
+ if not value:
230
+ return
231
+ print(__version__, end="")
232
+ ctx.exit(0)
233
+
234
+
235
+ def run_included_example(
236
+ ctx: click.Context,
237
+ smoke_test: bool,
238
+ example: Optional[str],
239
+ **kwargs,
240
+ ):
241
+ assert smoke_test or example is not None
242
+ value = "spm"
243
+ if not smoke_test and example is not None:
244
+ value = example
245
+
246
+ example_path = os.path.join(common.get_librelane_root(), "examples", value)
247
+ if not os.path.isdir(example_path):
248
+ print(f"Unknown example '{value}'.", file=sys.stderr)
249
+ ctx.exit(1)
250
+
251
+ status = 0
252
+ final_path = os.path.join(os.getcwd(), value)
253
+ cleanup = False
254
+ if smoke_test:
255
+ d = tempfile.mkdtemp("librelane")
256
+ final_path = os.path.join(d, "smoke_test_design")
257
+ cleanup = True
258
+ kwargs.update(
259
+ flow_name=None,
260
+ scl=None,
261
+ tag=None,
262
+ last_run=False,
263
+ frm=None,
264
+ to=None,
265
+ reproducible=None,
266
+ skip=(),
267
+ with_initial_state=None,
268
+ config_override_strings=[],
269
+ _force_run_dir=None,
270
+ design_dir=None,
271
+ )
272
+ try:
273
+ if os.path.isdir(final_path):
274
+ print(f"A directory named {value} already exists.", file=sys.stderr)
275
+ ctx.exit(1)
276
+ # 1. Copy the files
277
+ shutil.copytree(
278
+ example_path,
279
+ final_path,
280
+ symlinks=False,
281
+ )
282
+
283
+ # 2. Make files writable
284
+ if os.name == "posix":
285
+ subprocess.check_call(["chmod", "-R", "755", final_path])
286
+
287
+ config_file = glob.glob(os.path.join(final_path, "config.*"))[0]
288
+
289
+ # 3. Run
290
+ run(
291
+ ctx,
292
+ config_files=[config_file],
293
+ **kwargs,
294
+ )
295
+ if smoke_test:
296
+ info("Smoke test passed.")
297
+ except KeyboardInterrupt:
298
+ if smoke_test:
299
+ info("Smoke test aborted.")
300
+ status = -1
301
+ finally:
302
+ try:
303
+ if cleanup:
304
+ shutil.rmtree(final_path)
305
+ except FileNotFoundError:
306
+ pass
307
+
308
+ ctx.exit(status)
309
+
310
+
311
+ def cli_in_container(
312
+ ctx: click.Context,
313
+ param: click.Parameter,
314
+ value: bool,
315
+ ):
316
+ if not value:
317
+ return
318
+
319
+ docker_mounts = list(ctx.params.get("docker_mounts") or ())
320
+ docker_tty: bool = ctx.params.get("docker_tty", True)
321
+ pdk_root = ctx.params.get("pdk_root")
322
+ argv = sys.argv[sys.argv.index("--dockerized") + 1 :]
323
+
324
+ final_argv = ["zsh"]
325
+ if len(argv) != 0:
326
+ final_argv = ["librelane"] + argv
327
+
328
+ docker_image = os.getenv(
329
+ "LIBRELANE_IMAGE_OVERRIDE", f"ghcr.io/librelane/librelane:{__version__}"
330
+ )
331
+
332
+ try:
333
+ run_in_container(
334
+ docker_image,
335
+ final_argv,
336
+ pdk_root=pdk_root,
337
+ other_mounts=docker_mounts,
338
+ tty=docker_tty,
339
+ )
340
+ except Exception as e:
341
+ err(e)
342
+ ctx.exit(1)
343
+
344
+ ctx.exit(0)
345
+
346
+
347
+ o = partial(option, show_default=True)
348
+
349
+
350
+ @command(
351
+ no_args_is_help=True,
352
+ formatter_settings=formatter_settings,
353
+ )
354
+ @option_group(
355
+ "Copy final views",
356
+ o(
357
+ "--save-views-to",
358
+ "view_save_path",
359
+ type=click.Path(file_okay=False, dir_okay=True),
360
+ default=None,
361
+ 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.)",
362
+ ),
363
+ o(
364
+ "--ef-save-views-to",
365
+ "ef_view_save_path",
366
+ type=click.Path(file_okay=False, dir_okay=True),
367
+ default=None,
368
+ help="A directory to copy the final views to in the Efabless format, compatible with Caravel User Project.",
369
+ ),
370
+ )
371
+ @option_group(
372
+ "Containerization options",
373
+ o(
374
+ "--docker-mount",
375
+ "-m",
376
+ "docker_mounts",
377
+ multiple=True,
378
+ is_eager=True, # docker options should be processed before anything else
379
+ default=(),
380
+ 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 Docker-compatible container engine verbatim. Must be passed before --dockerized, has no effect if --dockerized is not set.",
381
+ ),
382
+ o(
383
+ "--docker-tty/--docker-no-tty",
384
+ is_eager=True, # docker options should be processed before anything else
385
+ default=True,
386
+ help="Controls the allocation of a virtual terminal by passing -t to the Docker-compatible container engine invocation. Must be passed before --dockerized, has no effect if --dockerized is not set.",
387
+ ),
388
+ o(
389
+ "--dockerized",
390
+ default=False,
391
+ is_flag=True,
392
+ is_eager=True, # docker options should be processed before anything else
393
+ help="Run the remaining flags using a Docker container. Some caveats apply. Must precede all options except --docker-mount, --docker-tty/--docker-no-tty.",
394
+ callback=cli_in_container,
395
+ ),
396
+ )
397
+ @option_group(
398
+ "Subcommands",
399
+ o(
400
+ "--version",
401
+ is_flag=True,
402
+ is_eager=True,
403
+ help="Prints version information and exits",
404
+ callback=print_version,
405
+ ),
406
+ o(
407
+ "--bare-version",
408
+ is_flag=True,
409
+ is_eager=True,
410
+ callback=print_bare_version,
411
+ hidden=True,
412
+ ),
413
+ o(
414
+ "--smoke-test",
415
+ is_flag=True,
416
+ help="Runs a basic LibreLane smoke test, the results of which are temporary and discarded.",
417
+ ),
418
+ o(
419
+ "--run-example",
420
+ default=None,
421
+ help="Copies one of the LibreLane examples to the current working directory and runs it.",
422
+ ),
423
+ constraint=mutually_exclusive,
424
+ )
425
+ @cloup_flow_opts(
426
+ _enable_debug_flags=True,
427
+ sequential_flow_reproducible=True,
428
+ enable_overwrite_flag=True,
429
+ enable_initial_state_element=True,
430
+ )
431
+ @click.pass_context
432
+ def cli(ctx, /, **kwargs):
433
+ """
434
+ Runs an LibreLane flow via the commandline using a design configuration
435
+ object.
436
+
437
+ Try 'python3 -m librelane.steps --help' for step-specific options, including
438
+ reproducibles and running a step standalone.
439
+ """
440
+ args = kwargs["config_files"]
441
+ run_kwargs = kwargs.copy()
442
+
443
+ if len(args) == 1 and args[0].endswith(".marshalled"):
444
+ run_kwargs = marshal.load(open(args[0], "rb"))
445
+ run_kwargs.update(**{k: kwargs[k] for k in ["pdk_root", "pdk", "scl"]})
446
+
447
+ smoke_test = kwargs.pop("smoke_test", False)
448
+ example = kwargs.pop("run_example", None)
449
+
450
+ for subcommand_flag in [
451
+ "docker_tty",
452
+ "docker_mounts",
453
+ "dockerized",
454
+ "version",
455
+ "bare_version",
456
+ "smoke_test",
457
+ "run_example",
458
+ ]:
459
+ if subcommand_flag in run_kwargs:
460
+ del run_kwargs[subcommand_flag]
461
+ if smoke_test or example is not None:
462
+ run_kwargs.pop("config_files", None)
463
+ run_included_example(ctx, smoke_test, example, **run_kwargs)
464
+ else:
465
+ run(ctx, **run_kwargs)
466
+ ctx.exit(0)
467
+
468
+
469
+ if __name__ == "__main__":
470
+ 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,61 @@
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
+ get_latest_file,
45
+ process_list_file,
46
+ _get_process_limit,
47
+ )
48
+ from .types import (
49
+ is_number,
50
+ is_real_number,
51
+ is_string,
52
+ Number,
53
+ Path,
54
+ AnyPath,
55
+ ScopedFile,
56
+ )
57
+ from .toolbox import Toolbox
58
+ from .drc import DRC, Violation
59
+ from . import cli
60
+ from .tpe import get_tpe, set_tpe
61
+ 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