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.
- librelane/__init__.py +38 -0
- librelane/__main__.py +479 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +63 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +246 -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 +456 -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 +116 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +155 -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 +743 -0
- librelane/container.py +285 -0
- librelane/env_info.py +320 -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 +327 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +1049 -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/help/__main__.py +39 -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 +79 -0
- librelane/scripts/magic/drc.tcl +78 -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 +45 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
- librelane/scripts/magic/lef/maglef.tcl +26 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +21 -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 +573 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/eco_buffer.py +181 -0
- librelane/scripts/odbpy/eco_diode.py +139 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
- librelane/scripts/odbpy/ioplace_parser/parse.py +147 -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 +397 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +250 -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 +540 -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 +37 -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 +195 -0
- librelane/state/state.py +359 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +510 -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 +576 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +1088 -0
- librelane/steps/openroad.py +2460 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +640 -0
- librelane/steps/step.py +1571 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dist-info/METADATA +169 -0
- librelane-2.4.0.dist-info/RECORD +170 -0
- librelane-2.4.0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dist-info/entry_points.txt +9 -0
|
@@ -0,0 +1,173 @@
|
|
|
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 __future__ import annotations
|
|
15
|
+
from decimal import Decimal
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
import rich
|
|
19
|
+
import rich.table
|
|
20
|
+
from concurrent.futures import Future
|
|
21
|
+
from typing import Dict, List, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
from .flow import Flow
|
|
24
|
+
from ..state import State
|
|
25
|
+
from ..config import Config
|
|
26
|
+
from ..logging import success
|
|
27
|
+
from ..logging import options, console
|
|
28
|
+
from ..steps import Step, Yosys, OpenROAD, StepError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# "Synthesis Exploration" is a non-seqeuential flow that tries all synthesis
|
|
32
|
+
# strategies and shows which ones yield the best area XOR delay
|
|
33
|
+
@Flow.factory.register()
|
|
34
|
+
class SynthesisExploration(Flow):
|
|
35
|
+
"""
|
|
36
|
+
Synthesis Exploration is a feature that tries multiple synthesis strategies
|
|
37
|
+
(in the form of different scripts for the ABC utility) to try and find which
|
|
38
|
+
strategy is better by either minimizing area or maximizing slack (and thus
|
|
39
|
+
frequency.)
|
|
40
|
+
|
|
41
|
+
The output is represented in a tabulated format, e.g.: ::
|
|
42
|
+
|
|
43
|
+
┏━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
|
|
44
|
+
┃ ┃ ┃ ┃ Worst Setup ┃ Total Negative ┃
|
|
45
|
+
┃ SYNTH_STRATEGY ┃ Gates ┃ Area (µm²) ┃ Slack (ns) ┃ Setup Slack (ns) ┃
|
|
46
|
+
┡━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
|
|
47
|
+
│ AREA 0 │ 8781 │ 97141.916800 │ 6.288794 │ 0.0 │
|
|
48
|
+
│ AREA 1 │ 8692 │ 96447.500800 │ 6.434102 │ 0.0 │
|
|
49
|
+
│ AREA 2 │ 8681 │ 96339.897600 │ 6.276806 │ 0.0 │
|
|
50
|
+
│ AREA 3 │ 11793 │ 111084.038400 │ 7.011374 │ 0.0 │
|
|
51
|
+
│ DELAY 0 │ 8969 │ 101418.518400 │ 6.511191 │ 0.0 │
|
|
52
|
+
│ DELAY 1 │ 8997 │ 101275.881600 │ 6.656564 │ 0.0 │
|
|
53
|
+
│ DELAY 2 │ 9013 │ 101177.036800 │ 6.691765 │ 0.0 │
|
|
54
|
+
│ DELAY 3 │ 8733 │ 99190.131200 │ 6.414865 │ 0.0 │
|
|
55
|
+
│ DELAY 4 │ 8739 │ 101011.878400 │ 6.274565 │ 0.0 │
|
|
56
|
+
└────────────────┴───────┴───────────────┴──────────────────┴──────────────────┘
|
|
57
|
+
|
|
58
|
+
You can then update your config file with the best ``SYNTH_STRATEGY`` for your
|
|
59
|
+
use-case so it can be used with other flows.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
Steps = [
|
|
63
|
+
Yosys.Synthesis,
|
|
64
|
+
OpenROAD.CheckSDCFiles,
|
|
65
|
+
OpenROAD.STAPrePNR,
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
def run(
|
|
69
|
+
self,
|
|
70
|
+
initial_state: State,
|
|
71
|
+
**kwargs,
|
|
72
|
+
) -> Tuple[State, List[Step]]:
|
|
73
|
+
step_list: List[Step] = []
|
|
74
|
+
|
|
75
|
+
self.progress_bar.set_max_stage_count(1)
|
|
76
|
+
|
|
77
|
+
synth_futures: List[Tuple[Config, Future[State]]] = []
|
|
78
|
+
self.progress_bar.start_stage("Synthesis Exploration")
|
|
79
|
+
|
|
80
|
+
options.set_condensed_mode(True)
|
|
81
|
+
|
|
82
|
+
for strategy in [
|
|
83
|
+
"AREA 0",
|
|
84
|
+
"AREA 1",
|
|
85
|
+
"AREA 2",
|
|
86
|
+
"AREA 3",
|
|
87
|
+
"DELAY 0",
|
|
88
|
+
"DELAY 1",
|
|
89
|
+
"DELAY 2",
|
|
90
|
+
"DELAY 3",
|
|
91
|
+
"DELAY 4",
|
|
92
|
+
]:
|
|
93
|
+
config = self.config.copy(SYNTH_STRATEGY=strategy)
|
|
94
|
+
|
|
95
|
+
synth_step = Yosys.Synthesis(
|
|
96
|
+
config,
|
|
97
|
+
id=f"synthesis-{strategy}",
|
|
98
|
+
state_in=initial_state,
|
|
99
|
+
)
|
|
100
|
+
synth_future = self.start_step_async(synth_step)
|
|
101
|
+
step_list.append(synth_step)
|
|
102
|
+
|
|
103
|
+
sdc_step = OpenROAD.CheckSDCFiles(
|
|
104
|
+
config,
|
|
105
|
+
id=f"sdc-{strategy}",
|
|
106
|
+
state_in=synth_future,
|
|
107
|
+
)
|
|
108
|
+
sdc_future = self.start_step_async(sdc_step)
|
|
109
|
+
step_list.append(sdc_step)
|
|
110
|
+
|
|
111
|
+
sta_step = OpenROAD.STAPrePNR(
|
|
112
|
+
config,
|
|
113
|
+
state_in=sdc_future,
|
|
114
|
+
id=f"sta-{strategy}",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
step_list.append(sta_step)
|
|
118
|
+
sta_future = self.start_step_async(sta_step)
|
|
119
|
+
|
|
120
|
+
synth_futures.append((config, sta_future))
|
|
121
|
+
|
|
122
|
+
results: Dict[str, Optional[Tuple[Decimal, Decimal, Decimal, Decimal]]] = {}
|
|
123
|
+
for config, future in synth_futures:
|
|
124
|
+
strategy = config["SYNTH_STRATEGY"]
|
|
125
|
+
results[strategy] = None
|
|
126
|
+
try:
|
|
127
|
+
state = future.result()
|
|
128
|
+
results[strategy] = (
|
|
129
|
+
state.metrics["design__instance__count"],
|
|
130
|
+
state.metrics["design__instance__area"],
|
|
131
|
+
state.metrics["timing__setup__ws"],
|
|
132
|
+
state.metrics["timing__setup__tns"],
|
|
133
|
+
)
|
|
134
|
+
except StepError:
|
|
135
|
+
pass # None == failure
|
|
136
|
+
self.progress_bar.end_stage()
|
|
137
|
+
options.set_condensed_mode(False)
|
|
138
|
+
|
|
139
|
+
successful_results = {k: v for k, v in results.items() if v is not None}
|
|
140
|
+
min_gates = min(map(lambda x: x[0], successful_results.values()))
|
|
141
|
+
min_area = min(map(lambda x: x[1], successful_results.values()))
|
|
142
|
+
max_slack = max(map(lambda x: x[2], successful_results.values()))
|
|
143
|
+
max_tns = max(map(lambda x: x[3], successful_results.values()))
|
|
144
|
+
|
|
145
|
+
table = rich.table.Table()
|
|
146
|
+
table.add_column("SYNTH_STRATEGY")
|
|
147
|
+
table.add_column("Gates")
|
|
148
|
+
table.add_column("Area (µm²)")
|
|
149
|
+
table.add_column("Worst Setup Slack (ns)")
|
|
150
|
+
table.add_column("Total -ve Setup Slack (ns)")
|
|
151
|
+
for key, result in results.items():
|
|
152
|
+
gates_s = "[red]Failed"
|
|
153
|
+
area_s = "[red]Failed"
|
|
154
|
+
slack_s = "[red]Failed"
|
|
155
|
+
tns_s = "[red]Failed"
|
|
156
|
+
if result is not None:
|
|
157
|
+
gates, area, slack, tns = result
|
|
158
|
+
gates_s = f"{'[green]' if gates == min_gates else ''}{gates}"
|
|
159
|
+
area_s = f"{'[green]' if area == min_area else ''}{area}"
|
|
160
|
+
slack_s = f"{'[green]' if slack == max_slack else ''}{slack}"
|
|
161
|
+
tns_s = f"{'[green]' if tns == max_tns else ''}{tns}"
|
|
162
|
+
table.add_row(key, gates_s, area_s, slack_s, tns_s)
|
|
163
|
+
|
|
164
|
+
console.print(table)
|
|
165
|
+
assert self.run_dir is not None
|
|
166
|
+
file_console = rich.console.Console(
|
|
167
|
+
file=open(os.path.join(self.run_dir, "summary.rpt"), "w", encoding="utf8"),
|
|
168
|
+
width=160,
|
|
169
|
+
)
|
|
170
|
+
file_console.print(table)
|
|
171
|
+
|
|
172
|
+
success("Flow complete.")
|
|
173
|
+
return (initial_state, step_list)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright 2025 LibreLane Contributors
|
|
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 ..common.cli import formatter_settings
|
|
15
|
+
from ..flows import Flow
|
|
16
|
+
from ..steps import Step
|
|
17
|
+
from ..logging import console
|
|
18
|
+
|
|
19
|
+
import cloup
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cloup.command(formatter_settings=formatter_settings)
|
|
23
|
+
@cloup.argument("step_or_flow")
|
|
24
|
+
@cloup.pass_context
|
|
25
|
+
def cli(ctx, step_or_flow):
|
|
26
|
+
"""
|
|
27
|
+
Displays rich help for the step or flow in question.
|
|
28
|
+
"""
|
|
29
|
+
if TargetFlow := Flow.factory.get(step_or_flow):
|
|
30
|
+
TargetFlow.display_help()
|
|
31
|
+
elif TargetStep := Step.factory.get(step_or_flow):
|
|
32
|
+
TargetStep.display_help()
|
|
33
|
+
else:
|
|
34
|
+
console.log(f"Unknown Flow or Step '{step_or_flow}'.")
|
|
35
|
+
ctx.exit(-1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
cli()
|
|
@@ -0,0 +1,40 @@
|
|
|
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 Logging Module
|
|
16
|
+
------------------
|
|
17
|
+
|
|
18
|
+
As the name implies, this handles LibreLane's logging using the ``logging``
|
|
19
|
+
module and the ``rich`` library.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .logger import (
|
|
23
|
+
LogLevels,
|
|
24
|
+
LevelFilter,
|
|
25
|
+
options,
|
|
26
|
+
console,
|
|
27
|
+
set_log_level,
|
|
28
|
+
reset_log_level,
|
|
29
|
+
get_log_level,
|
|
30
|
+
register_additional_handler,
|
|
31
|
+
deregister_additional_handler,
|
|
32
|
+
verbose,
|
|
33
|
+
debug,
|
|
34
|
+
info,
|
|
35
|
+
rule,
|
|
36
|
+
success,
|
|
37
|
+
warn,
|
|
38
|
+
err,
|
|
39
|
+
subprocess,
|
|
40
|
+
)
|
|
@@ -0,0 +1,323 @@
|
|
|
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 click
|
|
15
|
+
import atexit
|
|
16
|
+
import logging
|
|
17
|
+
from enum import IntEnum
|
|
18
|
+
from typing import ClassVar, Iterable, Union
|
|
19
|
+
|
|
20
|
+
import rich.console
|
|
21
|
+
import rich.logging
|
|
22
|
+
from rich.text import Text
|
|
23
|
+
from rich.style import Style, StyleType
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LogLevels(IntEnum):
|
|
27
|
+
ALL = 0
|
|
28
|
+
DEBUG = 10
|
|
29
|
+
SUBPROCESS = 12
|
|
30
|
+
VERBOSE = 15
|
|
31
|
+
INFO = 20
|
|
32
|
+
WARNING = 30
|
|
33
|
+
ERROR = 40
|
|
34
|
+
CRITICAL = 50
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
console = rich.console.Console()
|
|
38
|
+
atexit.register(lambda: rich.console.Console().show_cursor())
|
|
39
|
+
__event_logger: logging.Logger = logging.getLogger("__librelane__")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class options:
|
|
43
|
+
_condensed_mode: ClassVar[bool] = False
|
|
44
|
+
_show_progress_bar: ClassVar[bool] = True
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def get_condensed_mode(Self) -> bool:
|
|
48
|
+
return Self._condensed_mode
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def set_condensed_mode(Self, condensed: bool):
|
|
52
|
+
Self._condensed_mode = condensed
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def get_show_progress_bar(Self) -> bool:
|
|
56
|
+
return Self._show_progress_bar
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def set_show_progress_bar(Self, show: bool):
|
|
60
|
+
Self._show_progress_bar = show
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class NullFormatter(logging.Formatter):
|
|
64
|
+
def format(self, record):
|
|
65
|
+
return record.getMessage()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LevelFormatter(logging.Formatter):
|
|
69
|
+
def format(self, record):
|
|
70
|
+
message = record.getMessage()
|
|
71
|
+
if record.levelname == "WARNING":
|
|
72
|
+
message = f"[yellow]{message}"
|
|
73
|
+
elif record.levelname == "ERROR":
|
|
74
|
+
message = f"[red]{message}"
|
|
75
|
+
elif record.levelname == "CRITICAL":
|
|
76
|
+
message = f"[red][bold]{message}"
|
|
77
|
+
else:
|
|
78
|
+
message = f"{message}"
|
|
79
|
+
return message
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RichHandler(rich.logging.RichHandler):
|
|
83
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
84
|
+
super().__init__(**kwargs)
|
|
85
|
+
|
|
86
|
+
def get_level_text(self, record: logging.LogRecord) -> Text:
|
|
87
|
+
if not options.get_condensed_mode():
|
|
88
|
+
return super().get_level_text(record)
|
|
89
|
+
level_name = record.levelname
|
|
90
|
+
style: StyleType
|
|
91
|
+
if level_name == "WARNING":
|
|
92
|
+
style = Style(color="yellow", bold=True)
|
|
93
|
+
else:
|
|
94
|
+
style = f"logging.level.{level_name.lower()}"
|
|
95
|
+
level_text = Text.styled(
|
|
96
|
+
f"[{level_name[0]}]",
|
|
97
|
+
style,
|
|
98
|
+
)
|
|
99
|
+
return level_text
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class KeywordFilter(logging.Filter):
|
|
103
|
+
def __init__(self, matching_values: dict) -> None:
|
|
104
|
+
super().__init__()
|
|
105
|
+
self.matching_values = matching_values.copy()
|
|
106
|
+
|
|
107
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
108
|
+
for key, value in self.matching_values.items():
|
|
109
|
+
if value is None:
|
|
110
|
+
if hasattr(record, key) and getattr(record, key) is not None:
|
|
111
|
+
return False
|
|
112
|
+
else:
|
|
113
|
+
if not hasattr(record, key) or getattr(record, key) != value:
|
|
114
|
+
return False
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class LevelFilter(logging.Filter):
|
|
119
|
+
def __init__(self, levels: Iterable[str], invert: bool = False) -> None:
|
|
120
|
+
self.levels = levels
|
|
121
|
+
self.invert = invert
|
|
122
|
+
|
|
123
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
124
|
+
if options.get_condensed_mode():
|
|
125
|
+
if record.levelname == "SUBPROCESS":
|
|
126
|
+
return False
|
|
127
|
+
if self.invert:
|
|
128
|
+
return record.levelname not in self.levels
|
|
129
|
+
else:
|
|
130
|
+
return record.levelname in self.levels
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def initialize_logger():
|
|
134
|
+
global __event_logger, console
|
|
135
|
+
|
|
136
|
+
for level in LogLevels:
|
|
137
|
+
logging.addLevelName(level.value, level.name)
|
|
138
|
+
|
|
139
|
+
subprocess_handler = RichHandler(
|
|
140
|
+
console=console,
|
|
141
|
+
show_time=False,
|
|
142
|
+
omit_repeated_times=False,
|
|
143
|
+
show_level=False,
|
|
144
|
+
show_path=False,
|
|
145
|
+
enable_link_path=False,
|
|
146
|
+
tracebacks_word_wrap=False,
|
|
147
|
+
keywords=[],
|
|
148
|
+
markup=False,
|
|
149
|
+
)
|
|
150
|
+
subprocess_handler.addFilter(LevelFilter(["SUBPROCESS"]))
|
|
151
|
+
|
|
152
|
+
rich_handler = RichHandler(
|
|
153
|
+
console=console,
|
|
154
|
+
rich_tracebacks=True,
|
|
155
|
+
omit_repeated_times=False,
|
|
156
|
+
markup=True,
|
|
157
|
+
tracebacks_suppress=[
|
|
158
|
+
click,
|
|
159
|
+
],
|
|
160
|
+
show_level=True,
|
|
161
|
+
keywords=[],
|
|
162
|
+
)
|
|
163
|
+
rich_handler.setFormatter(LevelFormatter("%(message)s", datefmt="[%X]"))
|
|
164
|
+
rich_handler.addFilter(LevelFilter(["SUBPROCESS"], invert=True))
|
|
165
|
+
|
|
166
|
+
logger = logging.getLogger("__librelane__")
|
|
167
|
+
logger.setLevel(LogLevels.SUBPROCESS)
|
|
168
|
+
|
|
169
|
+
logger.handlers.clear()
|
|
170
|
+
|
|
171
|
+
logger.addHandler(subprocess_handler)
|
|
172
|
+
logger.addHandler(rich_handler)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
initialize_logger()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def register_additional_handler(handler: logging.Handler):
|
|
179
|
+
"""
|
|
180
|
+
Adds a new handler to the default LibreLane logger.
|
|
181
|
+
|
|
182
|
+
:param handler: The new handler. Must be of type ``logging.Handler``
|
|
183
|
+
or its subclasses.
|
|
184
|
+
"""
|
|
185
|
+
__event_logger.addHandler(handler)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def deregister_additional_handler(handler: logging.Handler):
|
|
189
|
+
"""
|
|
190
|
+
Removes a registered handler from the default LibreLane logger.
|
|
191
|
+
|
|
192
|
+
:param handler: The handler. If not registered, the behavior
|
|
193
|
+
of this function is undefined.
|
|
194
|
+
"""
|
|
195
|
+
__event_logger.removeHandler(handler)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def set_log_level(lv: Union[str, int]):
|
|
199
|
+
"""
|
|
200
|
+
Sets the log level of the default LibreLane logger.
|
|
201
|
+
|
|
202
|
+
:param lv: Either the name or number of the desired log level.
|
|
203
|
+
"""
|
|
204
|
+
__event_logger.setLevel(lv)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def reset_log_level():
|
|
208
|
+
"""
|
|
209
|
+
Sets the log level of the default LibreLane logger back to the
|
|
210
|
+
default log level.
|
|
211
|
+
"""
|
|
212
|
+
set_log_level("SUBPROCESS")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_log_level() -> int:
|
|
216
|
+
"""
|
|
217
|
+
Obtains the numeric log level of the LibreLane logger.
|
|
218
|
+
"""
|
|
219
|
+
return __event_logger.getEffectiveLevel()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def debug(*args, **kwargs):
|
|
223
|
+
"""
|
|
224
|
+
Logs to the LibreLane logger with the log level DEBUG.
|
|
225
|
+
|
|
226
|
+
:param msg: The message to log
|
|
227
|
+
"""
|
|
228
|
+
if kwargs.get("stacklevel") is None:
|
|
229
|
+
kwargs["stacklevel"] = 2
|
|
230
|
+
__event_logger.debug(*args, **kwargs)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def verbose(*args, **kwargs):
|
|
234
|
+
"""
|
|
235
|
+
Logs to the LibreLane logger with the log level VERBOSE.
|
|
236
|
+
"""
|
|
237
|
+
if kwargs.get("stacklevel") is None:
|
|
238
|
+
kwargs["stacklevel"] = 2
|
|
239
|
+
__event_logger.log(
|
|
240
|
+
LogLevels.VERBOSE,
|
|
241
|
+
*args,
|
|
242
|
+
**kwargs,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def info(msg: object, /, **kwargs):
|
|
247
|
+
"""
|
|
248
|
+
Logs to the LibreLane logger with the log level INFO.
|
|
249
|
+
|
|
250
|
+
:param msg: The message to log
|
|
251
|
+
"""
|
|
252
|
+
if kwargs.get("stacklevel") is None:
|
|
253
|
+
kwargs["stacklevel"] = 2
|
|
254
|
+
__event_logger.info(msg, **kwargs)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def subprocess(msg: object, /, **kwargs):
|
|
258
|
+
"""
|
|
259
|
+
Logs to the LibreLane logger with the log level SUBPROCESS.
|
|
260
|
+
|
|
261
|
+
:param msg: The message to log
|
|
262
|
+
"""
|
|
263
|
+
if kwargs.get("stacklevel") is None:
|
|
264
|
+
kwargs["stacklevel"] = 2
|
|
265
|
+
__event_logger.log(LogLevels.SUBPROCESS, msg, **kwargs)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def rule(title: str = "", /, **kwargs): # pragma: no cover
|
|
269
|
+
"""
|
|
270
|
+
Prints a horizontal line on the terminal enclosing the first argument
|
|
271
|
+
if the log level is <= INFO.
|
|
272
|
+
|
|
273
|
+
Kwargs are passed to https://rich.readthedocs.io/en/stable/reference/console.html#rich.console.Console.rule
|
|
274
|
+
|
|
275
|
+
:param title: A title string to enclose in the console rule
|
|
276
|
+
"""
|
|
277
|
+
console.rule(title)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def success(msg: object, /, **kwargs):
|
|
281
|
+
"""
|
|
282
|
+
Logs to the LibreLane logger with the log level INFO.
|
|
283
|
+
|
|
284
|
+
:param msg: The message to log
|
|
285
|
+
"""
|
|
286
|
+
if kwargs.get("stacklevel") is None:
|
|
287
|
+
kwargs["stacklevel"] = 2
|
|
288
|
+
__event_logger.info(f"{msg}", **kwargs)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def warn(msg: object, /, **kwargs):
|
|
292
|
+
"""
|
|
293
|
+
Logs to the LibreLane logger with the log level WARNING.
|
|
294
|
+
|
|
295
|
+
:param msg: The message to log
|
|
296
|
+
"""
|
|
297
|
+
if kwargs.get("stacklevel") is None:
|
|
298
|
+
kwargs["stacklevel"] = 2
|
|
299
|
+
__event_logger.warning(f"{msg}", **kwargs)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def err(msg: object, /, **kwargs):
|
|
303
|
+
"""
|
|
304
|
+
Logs to the LibreLane logger with the log level ERROR.
|
|
305
|
+
|
|
306
|
+
:param msg: The message to log
|
|
307
|
+
"""
|
|
308
|
+
if kwargs.get("stacklevel") is None:
|
|
309
|
+
kwargs["stacklevel"] = 2
|
|
310
|
+
__event_logger.error(f"{msg}", **kwargs)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
if __name__ == "__main__":
|
|
314
|
+
initialize_logger()
|
|
315
|
+
debug("Debug")
|
|
316
|
+
verbose("Verbose")
|
|
317
|
+
subprocess("Subprocess")
|
|
318
|
+
rule("Rule")
|
|
319
|
+
info("Info")
|
|
320
|
+
success("Success")
|
|
321
|
+
warn("Warn")
|
|
322
|
+
err("Err")
|
|
323
|
+
print("\n")
|
librelane/open_pdks_rev
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0fe599b2afb6708d281543108caf8310912f54af
|
librelane/plugins.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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 pkgutil
|
|
15
|
+
import importlib
|
|
16
|
+
|
|
17
|
+
discovered_plugins = {
|
|
18
|
+
name: importlib.import_module(name)
|
|
19
|
+
for finder, name, ispkg in pkgutil.iter_modules()
|
|
20
|
+
if name.startswith("librelane_plugin_") or name.startswith("openlane_plugin_")
|
|
21
|
+
}
|
librelane/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
set clock_port __VIRTUAL_CLK__
|
|
2
|
+
if { [info exists ::env(CLOCK_PORT)] } {
|
|
3
|
+
set port_count [llength $::env(CLOCK_PORT)]
|
|
4
|
+
|
|
5
|
+
if { $port_count == "0" } {
|
|
6
|
+
puts "\[WARNING] No CLOCK_PORT found. A dummy clock will be used."
|
|
7
|
+
} elseif { $port_count != "1" } {
|
|
8
|
+
puts "\[WARNING] Multi-clock files are not currently supported by the base SDC file. Only the first clock will be constrained."
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if { $port_count > "0" } {
|
|
12
|
+
set ::clock_port [lindex $::env(CLOCK_PORT) 0]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
set port_args [get_ports $clock_port]
|
|
16
|
+
puts "\[INFO] Using clock $clock_port…"
|
|
17
|
+
create_clock {*}$port_args -name $clock_port -period $::env(CLOCK_PERIOD)
|
|
18
|
+
|
|
19
|
+
set input_delay_value [expr $::env(CLOCK_PERIOD) * $::env(IO_DELAY_CONSTRAINT) / 100]
|
|
20
|
+
set output_delay_value [expr $::env(CLOCK_PERIOD) * $::env(IO_DELAY_CONSTRAINT) / 100]
|
|
21
|
+
puts "\[INFO] Setting output delay to: $output_delay_value"
|
|
22
|
+
puts "\[INFO] Setting input delay to: $input_delay_value"
|
|
23
|
+
|
|
24
|
+
set_max_fanout $::env(MAX_FANOUT_CONSTRAINT) [current_design]
|
|
25
|
+
if { [info exists ::env(MAX_TRANSITION_CONSTRAINT)] } {
|
|
26
|
+
set_max_transition $::env(MAX_TRANSITION_CONSTRAINT) [current_design]
|
|
27
|
+
}
|
|
28
|
+
if { [info exists ::env(MAX_CAPACITANCE_CONSTRAINT)] } {
|
|
29
|
+
set_max_capacitance $::env(MAX_CAPACITANCE_CONSTRAINT) [current_design]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
set clk_input [get_port $clock_port]
|
|
33
|
+
set clk_indx [lsearch [all_inputs] $clk_input]
|
|
34
|
+
set all_inputs_wo_clk [lreplace [all_inputs] $clk_indx $clk_indx ""]
|
|
35
|
+
|
|
36
|
+
#set rst_input [get_port resetn]
|
|
37
|
+
#set rst_indx [lsearch [all_inputs] $rst_input]
|
|
38
|
+
#set all_inputs_wo_clk_rst [lreplace $all_inputs_wo_clk $rst_indx $rst_indx ""]
|
|
39
|
+
set all_inputs_wo_clk_rst $all_inputs_wo_clk
|
|
40
|
+
|
|
41
|
+
# correct resetn
|
|
42
|
+
set clocks [get_clocks $clock_port]
|
|
43
|
+
|
|
44
|
+
set_input_delay $input_delay_value -clock $clocks $all_inputs_wo_clk_rst
|
|
45
|
+
set_output_delay $output_delay_value -clock $clocks [all_outputs]
|
|
46
|
+
|
|
47
|
+
if { ![info exists ::env(SYNTH_CLK_DRIVING_CELL)] } {
|
|
48
|
+
set ::env(SYNTH_CLK_DRIVING_CELL) $::env(SYNTH_DRIVING_CELL)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
set_driving_cell \
|
|
52
|
+
-lib_cell [lindex [split $::env(SYNTH_DRIVING_CELL) "/"] 0] \
|
|
53
|
+
-pin [lindex [split $::env(SYNTH_DRIVING_CELL) "/"] 1] \
|
|
54
|
+
$all_inputs_wo_clk_rst
|
|
55
|
+
|
|
56
|
+
set_driving_cell \
|
|
57
|
+
-lib_cell [lindex [split $::env(SYNTH_CLK_DRIVING_CELL) "/"] 0] \
|
|
58
|
+
-pin [lindex [split $::env(SYNTH_CLK_DRIVING_CELL) "/"] 1] \
|
|
59
|
+
$clk_input
|
|
60
|
+
|
|
61
|
+
set cap_load [expr $::env(OUTPUT_CAP_LOAD) / 1000.0]
|
|
62
|
+
puts "\[INFO] Setting load to: $cap_load"
|
|
63
|
+
set_load $cap_load [all_outputs]
|
|
64
|
+
|
|
65
|
+
puts "\[INFO] Setting clock uncertainty to: $::env(CLOCK_UNCERTAINTY_CONSTRAINT)"
|
|
66
|
+
set_clock_uncertainty $::env(CLOCK_UNCERTAINTY_CONSTRAINT) $clocks
|
|
67
|
+
|
|
68
|
+
puts "\[INFO] Setting clock transition to: $::env(CLOCK_TRANSITION_CONSTRAINT)"
|
|
69
|
+
set_clock_transition $::env(CLOCK_TRANSITION_CONSTRAINT) $clocks
|
|
70
|
+
|
|
71
|
+
puts "\[INFO] Setting timing derate to: $::env(TIME_DERATING_CONSTRAINT)%"
|
|
72
|
+
set_timing_derate -early [expr 1-[expr $::env(TIME_DERATING_CONSTRAINT) / 100]]
|
|
73
|
+
set_timing_derate -late [expr 1+[expr $::env(TIME_DERATING_CONSTRAINT) / 100]]
|
|
74
|
+
|
|
75
|
+
if { [info exists ::env(OPENLANE_SDC_IDEAL_CLOCKS)] && $::env(OPENLANE_SDC_IDEAL_CLOCKS) } {
|
|
76
|
+
unset_propagated_clock [all_clocks]
|
|
77
|
+
} else {
|
|
78
|
+
set_propagated_clock [all_clocks]
|
|
79
|
+
}
|
|
80
|
+
|