librelane 2.4.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of librelane might be problematic. Click here for more details.
- librelane/__init__.py +38 -0
- librelane/__main__.py +470 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +61 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +245 -0
- librelane/common/generic_dict.py +319 -0
- librelane/common/metrics/__init__.py +35 -0
- librelane/common/metrics/__main__.py +413 -0
- librelane/common/metrics/library.py +354 -0
- librelane/common/metrics/metric.py +186 -0
- librelane/common/metrics/util.py +279 -0
- librelane/common/misc.py +402 -0
- librelane/common/ring_buffer.py +63 -0
- librelane/common/tcl.py +80 -0
- librelane/common/toolbox.py +549 -0
- librelane/common/tpe.py +41 -0
- librelane/common/types.py +117 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +158 -0
- librelane/config/config.py +1025 -0
- librelane/config/flow.py +490 -0
- librelane/config/pdk_compat.py +255 -0
- librelane/config/preprocessor.py +464 -0
- librelane/config/removals.py +45 -0
- librelane/config/variable.py +722 -0
- librelane/container.py +264 -0
- librelane/env_info.py +306 -0
- librelane/examples/spm/config.yaml +33 -0
- librelane/examples/spm/pin_order.cfg +14 -0
- librelane/examples/spm/src/impl.sdc +73 -0
- librelane/examples/spm/src/signoff.sdc +68 -0
- librelane/examples/spm/src/spm.v +73 -0
- librelane/examples/spm/verify/spm_tb.v +106 -0
- librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
- librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
- librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
- librelane/examples/spm-user_project_wrapper/config.json +13 -0
- librelane/examples/spm-user_project_wrapper/defines.v +66 -0
- librelane/examples/spm-user_project_wrapper/template.def +7656 -0
- librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
- librelane/flows/__init__.py +24 -0
- librelane/flows/builtins.py +18 -0
- librelane/flows/classic.py +330 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +985 -0
- librelane/flows/misc.py +71 -0
- librelane/flows/optimizing.py +179 -0
- librelane/flows/sequential.py +367 -0
- librelane/flows/synth_explore.py +173 -0
- librelane/logging/__init__.py +40 -0
- librelane/logging/logger.py +323 -0
- librelane/open_pdks_rev +1 -0
- librelane/plugins.py +21 -0
- librelane/py.typed +0 -0
- librelane/scripts/base.sdc +80 -0
- librelane/scripts/klayout/Readme.md +2 -0
- librelane/scripts/klayout/open_design.py +63 -0
- librelane/scripts/klayout/render.py +121 -0
- librelane/scripts/klayout/stream_out.py +176 -0
- librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
- librelane/scripts/klayout/xor.drc +120 -0
- librelane/scripts/magic/Readme.md +1 -0
- librelane/scripts/magic/common/read.tcl +114 -0
- librelane/scripts/magic/def/antenna_check.tcl +35 -0
- librelane/scripts/magic/def/mag.tcl +19 -0
- librelane/scripts/magic/def/mag_gds.tcl +81 -0
- librelane/scripts/magic/drc.tcl +79 -0
- librelane/scripts/magic/extract_spice.tcl +98 -0
- librelane/scripts/magic/gds/drc_batch.tcl +74 -0
- librelane/scripts/magic/gds/erase_box.tcl +32 -0
- librelane/scripts/magic/gds/extras_mag.tcl +47 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
- librelane/scripts/magic/lef/maglef.tcl +27 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +19 -0
- librelane/scripts/netgen/setup.tcl +28 -0
- librelane/scripts/odbpy/apply_def_template.py +49 -0
- librelane/scripts/odbpy/cell_frequency.py +107 -0
- librelane/scripts/odbpy/check_antenna_properties.py +116 -0
- librelane/scripts/odbpy/contextualize.py +109 -0
- librelane/scripts/odbpy/defutil.py +574 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/exception_codes.py +17 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/label_macro_pins.py +277 -0
- librelane/scripts/odbpy/lefutil.py +97 -0
- librelane/scripts/odbpy/placers.py +162 -0
- librelane/scripts/odbpy/power_utils.py +395 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +246 -0
- librelane/scripts/odbpy/remove_buffers.py +173 -0
- librelane/scripts/odbpy/snap_to_grid.py +57 -0
- librelane/scripts/odbpy/wire_lengths.py +93 -0
- librelane/scripts/openroad/antenna_check.tcl +20 -0
- librelane/scripts/openroad/antenna_repair.tcl +31 -0
- librelane/scripts/openroad/basic_mp.tcl +24 -0
- librelane/scripts/openroad/buffer_list.tcl +10 -0
- librelane/scripts/openroad/common/dpl.tcl +24 -0
- librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
- librelane/scripts/openroad/common/grt.tcl +32 -0
- librelane/scripts/openroad/common/io.tcl +476 -0
- librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
- librelane/scripts/openroad/common/resizer.tcl +103 -0
- librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
- librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
- librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
- librelane/scripts/openroad/common/set_rc.tcl +75 -0
- librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
- librelane/scripts/openroad/cts.tcl +80 -0
- librelane/scripts/openroad/cut_rows.tcl +24 -0
- librelane/scripts/openroad/dpl.tcl +24 -0
- librelane/scripts/openroad/drt.tcl +37 -0
- librelane/scripts/openroad/fill.tcl +30 -0
- librelane/scripts/openroad/floorplan.tcl +145 -0
- librelane/scripts/openroad/gpl.tcl +88 -0
- librelane/scripts/openroad/grt.tcl +30 -0
- librelane/scripts/openroad/gui.tcl +15 -0
- librelane/scripts/openroad/insert_buffer.tcl +127 -0
- librelane/scripts/openroad/ioplacer.tcl +67 -0
- librelane/scripts/openroad/irdrop.tcl +51 -0
- librelane/scripts/openroad/pdn.tcl +52 -0
- librelane/scripts/openroad/rcx.tcl +32 -0
- librelane/scripts/openroad/repair_design.tcl +70 -0
- librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
- librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
- librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
- librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
- librelane/scripts/openroad/sta/corner.tcl +393 -0
- librelane/scripts/openroad/tapcell.tcl +25 -0
- librelane/scripts/openroad/write_views.tcl +27 -0
- librelane/scripts/pyosys/construct_abc_script.py +177 -0
- librelane/scripts/pyosys/json_header.py +84 -0
- librelane/scripts/pyosys/synthesize.py +493 -0
- librelane/scripts/pyosys/ys_common.py +153 -0
- librelane/scripts/tclsh/hello.tcl +1 -0
- librelane/state/__init__.py +24 -0
- librelane/state/__main__.py +61 -0
- librelane/state/design_format.py +180 -0
- librelane/state/state.py +351 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +511 -0
- librelane/steps/checker.py +637 -0
- librelane/steps/common_variables.py +340 -0
- librelane/steps/cvc_rv.py +169 -0
- librelane/steps/klayout.py +509 -0
- librelane/steps/magic.py +566 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +955 -0
- librelane/steps/openroad.py +2433 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +629 -0
- librelane/steps/step.py +1547 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dev0.dist-info/METADATA +151 -0
- librelane-2.4.0.dev0.dist-info/RECORD +166 -0
- librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dev0.dist-info/entry_points.txt +8 -0
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()
|
librelane/__version__.py
ADDED
|
@@ -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
|
librelane/common/cli.py
ADDED
|
@@ -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
|