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
librelane/steps/odb.py
ADDED
|
@@ -0,0 +1,1088 @@
|
|
|
1
|
+
# Copyright 2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import json
|
|
17
|
+
import shutil
|
|
18
|
+
from math import inf
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
from abc import abstractmethod
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Dict, List, Literal, Optional, Tuple
|
|
23
|
+
|
|
24
|
+
from ..common import Path, get_script_dir, aggregate_metrics
|
|
25
|
+
from ..config import Instance, Macro, Variable
|
|
26
|
+
from ..logging import info, verbose
|
|
27
|
+
from ..state import DesignFormat, State
|
|
28
|
+
|
|
29
|
+
from .openroad import DetailedPlacement, GlobalRouting
|
|
30
|
+
from .openroad_alerts import OpenROADAlert, OpenROADOutputProcessor
|
|
31
|
+
from .common_variables import io_layer_variables, dpl_variables, grt_variables
|
|
32
|
+
from .step import (
|
|
33
|
+
CompositeStep,
|
|
34
|
+
DefaultOutputProcessor,
|
|
35
|
+
MetricsUpdate,
|
|
36
|
+
Step,
|
|
37
|
+
StepError,
|
|
38
|
+
StepException,
|
|
39
|
+
ViewsUpdate,
|
|
40
|
+
)
|
|
41
|
+
from .tclstep import TclStep
|
|
42
|
+
|
|
43
|
+
inf_rx = re.compile(r"\b(-?)inf\b")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OdbpyStep(Step):
|
|
47
|
+
inputs = [DesignFormat.ODB]
|
|
48
|
+
outputs = [DesignFormat.ODB, DesignFormat.DEF]
|
|
49
|
+
|
|
50
|
+
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
51
|
+
|
|
52
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
53
|
+
|
|
54
|
+
def on_alert(self, alert: OpenROADAlert) -> OpenROADAlert:
|
|
55
|
+
if alert.code in [
|
|
56
|
+
"ORD-0039", # .openroad ignored with -python
|
|
57
|
+
"ODB-0220", # LEF thing obsolete
|
|
58
|
+
]:
|
|
59
|
+
return alert
|
|
60
|
+
if alert.cls == "error":
|
|
61
|
+
self.err(str(alert), extra={"key": alert.code})
|
|
62
|
+
elif alert.cls == "warning":
|
|
63
|
+
self.warn(str(alert), extra={"key": alert.code})
|
|
64
|
+
return alert
|
|
65
|
+
|
|
66
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
67
|
+
self.alerts = None
|
|
68
|
+
|
|
69
|
+
kwargs, env = self.extract_env(kwargs)
|
|
70
|
+
|
|
71
|
+
automatic_outputs = set(self.outputs).intersection(
|
|
72
|
+
[DesignFormat.ODB, DesignFormat.DEF]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
views_updates: ViewsUpdate = {}
|
|
76
|
+
command = self.get_command()
|
|
77
|
+
for output in automatic_outputs:
|
|
78
|
+
filename = f"{self.config['DESIGN_NAME']}.{output.value.extension}"
|
|
79
|
+
file_path = os.path.join(self.step_dir, filename)
|
|
80
|
+
command.append(f"--output-{output.value.id}")
|
|
81
|
+
command.append(file_path)
|
|
82
|
+
views_updates[output] = Path(file_path)
|
|
83
|
+
|
|
84
|
+
command += [
|
|
85
|
+
str(state_in[DesignFormat.ODB]),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
env["PYTHONPATH"] = (
|
|
89
|
+
f'{os.path.join(get_script_dir(), "odbpy")}:{env.get("PYTHONPATH")}'
|
|
90
|
+
)
|
|
91
|
+
check = False
|
|
92
|
+
if "check" in kwargs:
|
|
93
|
+
check = kwargs.pop("check")
|
|
94
|
+
|
|
95
|
+
subprocess_result = self.run_subprocess(
|
|
96
|
+
command,
|
|
97
|
+
env=env,
|
|
98
|
+
check=check,
|
|
99
|
+
**kwargs,
|
|
100
|
+
)
|
|
101
|
+
generated_metrics = subprocess_result["generated_metrics"]
|
|
102
|
+
|
|
103
|
+
# 1. Parse warnings and errors
|
|
104
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
105
|
+
if subprocess_result["returncode"] != 0:
|
|
106
|
+
error_strings = [
|
|
107
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
108
|
+
]
|
|
109
|
+
if len(error_strings):
|
|
110
|
+
error_string = "\n".join(error_strings)
|
|
111
|
+
raise StepError(
|
|
112
|
+
f"{self.id} failed with the following errors:\n{error_string}"
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
raise StepException(
|
|
116
|
+
f"{self.id} failed unexpectedly. Please check the logs and file an issue."
|
|
117
|
+
)
|
|
118
|
+
# 2. Metrics
|
|
119
|
+
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
120
|
+
if os.path.exists(metrics_path):
|
|
121
|
+
or_metrics_out = json.loads(open(metrics_path).read(), parse_float=Decimal)
|
|
122
|
+
for key, value in or_metrics_out.items():
|
|
123
|
+
if value == "Infinity":
|
|
124
|
+
or_metrics_out[key] = inf
|
|
125
|
+
elif value == "-Infinity":
|
|
126
|
+
or_metrics_out[key] = -inf
|
|
127
|
+
generated_metrics.update(or_metrics_out)
|
|
128
|
+
|
|
129
|
+
metric_updates_with_aggregates = aggregate_metrics(generated_metrics)
|
|
130
|
+
|
|
131
|
+
return views_updates, metric_updates_with_aggregates
|
|
132
|
+
|
|
133
|
+
def get_command(self) -> List[str]:
|
|
134
|
+
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
135
|
+
|
|
136
|
+
tech_lefs = self.toolbox.filter_views(self.config, self.config["TECH_LEFS"])
|
|
137
|
+
if len(tech_lefs) != 1:
|
|
138
|
+
raise StepException(
|
|
139
|
+
"Misconfigured SCL: 'TECH_LEFS' must return exactly one Tech LEF for its default timing corner."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
lefs = ["--input-lef", str(tech_lefs[0])]
|
|
143
|
+
for lef in self.config["CELL_LEFS"]:
|
|
144
|
+
lefs.append("--input-lef")
|
|
145
|
+
lefs.append(lef)
|
|
146
|
+
if extra_lefs := self.config["EXTRA_LEFS"]:
|
|
147
|
+
for lef in extra_lefs:
|
|
148
|
+
lefs.append("--input-lef")
|
|
149
|
+
lefs.append(lef)
|
|
150
|
+
if (design_lef := self.state_in.result()[DesignFormat.LEF]) and (
|
|
151
|
+
DesignFormat.LEF in self.inputs
|
|
152
|
+
):
|
|
153
|
+
lefs.append("--design-lef")
|
|
154
|
+
lefs.append(str(design_lef))
|
|
155
|
+
return (
|
|
156
|
+
[
|
|
157
|
+
"openroad",
|
|
158
|
+
"-exit",
|
|
159
|
+
"-no_splash",
|
|
160
|
+
"-metrics",
|
|
161
|
+
str(metrics_path),
|
|
162
|
+
"-python",
|
|
163
|
+
self.get_script_path(),
|
|
164
|
+
]
|
|
165
|
+
+ self.get_subcommand()
|
|
166
|
+
+ lefs
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def get_script_path(self) -> str:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
def get_subcommand(self) -> List[str]:
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@Step.factory.register()
|
|
178
|
+
class CheckMacroAntennaProperties(OdbpyStep):
|
|
179
|
+
id = "Odb.CheckMacroAntennaProperties"
|
|
180
|
+
name = "Check Antenna Properties of Macros Pins in Their LEF Views"
|
|
181
|
+
inputs = OdbpyStep.inputs
|
|
182
|
+
outputs = []
|
|
183
|
+
|
|
184
|
+
def get_script_path(self):
|
|
185
|
+
return os.path.join(
|
|
186
|
+
get_script_dir(),
|
|
187
|
+
"odbpy",
|
|
188
|
+
"check_antenna_properties.py",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def get_cells(self) -> List[str]:
|
|
192
|
+
macros = self.config["MACROS"]
|
|
193
|
+
cells = []
|
|
194
|
+
if macros:
|
|
195
|
+
cells = list(macros.keys())
|
|
196
|
+
return cells
|
|
197
|
+
|
|
198
|
+
def get_report_path(self) -> str:
|
|
199
|
+
return os.path.join(self.step_dir, "report.yaml")
|
|
200
|
+
|
|
201
|
+
def get_command(self) -> List[str]:
|
|
202
|
+
args = ["--report-file", self.get_report_path()]
|
|
203
|
+
for name in self.get_cells():
|
|
204
|
+
args += ["--cell-name", name]
|
|
205
|
+
return super().get_command() + args
|
|
206
|
+
|
|
207
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
208
|
+
if not self.get_cells():
|
|
209
|
+
info("No cells provided, skipping…")
|
|
210
|
+
return {}, {}
|
|
211
|
+
return super().run(state_in, **kwargs)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@Step.factory.register()
|
|
215
|
+
class CheckDesignAntennaProperties(CheckMacroAntennaProperties):
|
|
216
|
+
id = "Odb.CheckDesignAntennaProperties"
|
|
217
|
+
name = "Check Antenna Properties of Pins in The Generated Design LEF view"
|
|
218
|
+
inputs = CheckMacroAntennaProperties.inputs + [DesignFormat.LEF]
|
|
219
|
+
|
|
220
|
+
def get_cells(self) -> List[str]:
|
|
221
|
+
return [self.config["DESIGN_NAME"]]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@Step.factory.register()
|
|
225
|
+
class ApplyDEFTemplate(OdbpyStep):
|
|
226
|
+
"""
|
|
227
|
+
Copies the floorplan of a "template" DEF file for a new design, i.e.,
|
|
228
|
+
it will copy the die area, core area, and non-power pin names and locations.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
id = "Odb.ApplyDEFTemplate"
|
|
232
|
+
name = "Apply DEF Template"
|
|
233
|
+
|
|
234
|
+
config_vars = [
|
|
235
|
+
Variable(
|
|
236
|
+
"FP_DEF_TEMPLATE",
|
|
237
|
+
Optional[Path],
|
|
238
|
+
"Points to the DEF file to be used as a template.",
|
|
239
|
+
),
|
|
240
|
+
Variable(
|
|
241
|
+
"FP_TEMPLATE_MATCH_MODE",
|
|
242
|
+
Literal["strict", "permissive"],
|
|
243
|
+
"Whether to require that the pin set of the DEF template and the design should be identical. In permissive mode, pins that are in the design and not in the template will be excluded, and vice versa.",
|
|
244
|
+
default="strict",
|
|
245
|
+
),
|
|
246
|
+
Variable(
|
|
247
|
+
"FP_TEMPLATE_COPY_POWER_PINS",
|
|
248
|
+
bool,
|
|
249
|
+
"Whether to *always* copy all power pins from the DEF template to the design.",
|
|
250
|
+
default=False,
|
|
251
|
+
),
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
def get_script_path(self):
|
|
255
|
+
return os.path.join(
|
|
256
|
+
get_script_dir(),
|
|
257
|
+
"odbpy",
|
|
258
|
+
"apply_def_template.py",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def get_command(self) -> List[str]:
|
|
262
|
+
args = [
|
|
263
|
+
"--def-template",
|
|
264
|
+
self.config["FP_DEF_TEMPLATE"],
|
|
265
|
+
f"--{self.config['FP_TEMPLATE_MATCH_MODE']}",
|
|
266
|
+
]
|
|
267
|
+
if self.config["FP_TEMPLATE_COPY_POWER_PINS"]:
|
|
268
|
+
args.append("--copy-def-power")
|
|
269
|
+
return super().get_command() + args
|
|
270
|
+
|
|
271
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
272
|
+
if self.config["FP_DEF_TEMPLATE"] is None:
|
|
273
|
+
info("No DEF template provided, skipping…")
|
|
274
|
+
return {}, {}
|
|
275
|
+
|
|
276
|
+
views_updates, metrics_updates = super().run(state_in, **kwargs)
|
|
277
|
+
design_area_string = self.state_in.result().metrics.get("design__die__bbox")
|
|
278
|
+
if design_area_string:
|
|
279
|
+
template_area_string = metrics_updates["design__die__bbox"]
|
|
280
|
+
template_area = [Decimal(point) for point in template_area_string.split()]
|
|
281
|
+
design_area = [Decimal(point) for point in design_area_string.split()]
|
|
282
|
+
if template_area != design_area:
|
|
283
|
+
self.warn(
|
|
284
|
+
"The die area specificied in FP_DEF_TEMPLATE is different than the design die area. Pin placement may be incorrect."
|
|
285
|
+
)
|
|
286
|
+
self.warn(
|
|
287
|
+
f"Design area: {design_area_string}. Template def area: {template_area_string}"
|
|
288
|
+
)
|
|
289
|
+
return views_updates, {}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@Step.factory.register()
|
|
293
|
+
class SetPowerConnections(OdbpyStep):
|
|
294
|
+
"""
|
|
295
|
+
Uses JSON netlist and module information in Odb to add global power
|
|
296
|
+
connections for macros at the top level of a design.
|
|
297
|
+
|
|
298
|
+
If the JSON netlist is hierarchical (e.g. by using a keep hierarchy
|
|
299
|
+
attribute) this Step emits a warning and does not attempt to connect any
|
|
300
|
+
macros instantiated within submodules.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
id = "Odb.SetPowerConnections"
|
|
304
|
+
name = "Set Power Connections"
|
|
305
|
+
inputs = [DesignFormat.JSON_HEADER, DesignFormat.ODB]
|
|
306
|
+
|
|
307
|
+
def get_script_path(self):
|
|
308
|
+
return os.path.join(get_script_dir(), "odbpy", "power_utils.py")
|
|
309
|
+
|
|
310
|
+
def get_subcommand(self) -> List[str]:
|
|
311
|
+
return ["set-power-connections"]
|
|
312
|
+
|
|
313
|
+
def get_command(self) -> List[str]:
|
|
314
|
+
state_in = self.state_in.result()
|
|
315
|
+
return super().get_command() + [
|
|
316
|
+
"--input-json",
|
|
317
|
+
str(state_in[DesignFormat.JSON_HEADER]),
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@Step.factory.register()
|
|
322
|
+
class WriteVerilogHeader(OdbpyStep):
|
|
323
|
+
"""
|
|
324
|
+
Writes a Verilog header of the module using information from the generated
|
|
325
|
+
PDN, guarded by the value of ``VERILOG_POWER_DEFINE``, and the JSON header.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
id = "Odb.WriteVerilogHeader"
|
|
329
|
+
name = "Write Verilog Header"
|
|
330
|
+
inputs = [DesignFormat.ODB, DesignFormat.JSON_HEADER]
|
|
331
|
+
outputs = [DesignFormat.VERILOG_HEADER]
|
|
332
|
+
|
|
333
|
+
config_vars = OdbpyStep.config_vars + [
|
|
334
|
+
Variable(
|
|
335
|
+
"VERILOG_POWER_DEFINE",
|
|
336
|
+
Optional[str],
|
|
337
|
+
"Specifies the name of the define used to guard power and ground connections in the output Verilog header.",
|
|
338
|
+
deprecated_names=["SYNTH_USE_PG_PINS_DEFINES", "SYNTH_POWER_DEFINE"],
|
|
339
|
+
default="USE_POWER_PINS",
|
|
340
|
+
),
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
def get_script_path(self):
|
|
344
|
+
return os.path.join(get_script_dir(), "odbpy", "power_utils.py")
|
|
345
|
+
|
|
346
|
+
def get_subcommand(self) -> List[str]:
|
|
347
|
+
return ["write-verilog-header"]
|
|
348
|
+
|
|
349
|
+
def get_command(self) -> List[str]:
|
|
350
|
+
state_in = self.state_in.result()
|
|
351
|
+
command = super().get_command() + [
|
|
352
|
+
"--output-vh",
|
|
353
|
+
os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.vh"),
|
|
354
|
+
"--input-json",
|
|
355
|
+
str(state_in[DesignFormat.JSON_HEADER]),
|
|
356
|
+
]
|
|
357
|
+
if self.config.get("VERILOG_POWER_DEFINE") is not None:
|
|
358
|
+
command += ["--power-define", self.config["VERILOG_POWER_DEFINE"]]
|
|
359
|
+
else:
|
|
360
|
+
self.warn(
|
|
361
|
+
"VERILOG_POWER_DEFINE undefined. Verilog Header will not include power ports."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return command
|
|
365
|
+
|
|
366
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
367
|
+
views_updates, metrics_updates = super().run(state_in, **kwargs)
|
|
368
|
+
views_updates[DesignFormat.VERILOG_HEADER] = Path(
|
|
369
|
+
os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.vh")
|
|
370
|
+
)
|
|
371
|
+
return views_updates, metrics_updates
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@Step.factory.register()
|
|
375
|
+
class ManualMacroPlacement(OdbpyStep):
|
|
376
|
+
"""
|
|
377
|
+
Performs macro placement using a simple configuration file. The file is
|
|
378
|
+
defined as a line-break delimited list of instances and positions, in the
|
|
379
|
+
format ``instance_name X_pos Y_pos Orientation``.
|
|
380
|
+
|
|
381
|
+
If no macro instances are configured, this step is skipped.
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
id = "Odb.ManualMacroPlacement"
|
|
385
|
+
name = "Manual Macro Placement"
|
|
386
|
+
|
|
387
|
+
config_vars = [
|
|
388
|
+
Variable(
|
|
389
|
+
"MACRO_PLACEMENT_CFG",
|
|
390
|
+
Optional[Path],
|
|
391
|
+
"Path to an optional override for instance placement instead of the `MACROS` object for compatibility with LibreLane 1. If both are `None`, this step is skipped.",
|
|
392
|
+
),
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
def get_script_path(self):
|
|
396
|
+
return os.path.join(get_script_dir(), "odbpy", "placers.py")
|
|
397
|
+
|
|
398
|
+
def get_subcommand(self) -> List[str]:
|
|
399
|
+
return ["manual-macro-placement"]
|
|
400
|
+
|
|
401
|
+
def get_command(self) -> List[str]:
|
|
402
|
+
return super().get_command() + [
|
|
403
|
+
"--config",
|
|
404
|
+
os.path.join(self.step_dir, "placement.cfg"),
|
|
405
|
+
"--fixed",
|
|
406
|
+
]
|
|
407
|
+
|
|
408
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
409
|
+
cfg_file = Path(os.path.join(self.step_dir, "placement.cfg"))
|
|
410
|
+
if cfg_ref := self.config.get("MACRO_PLACEMENT_CFG"):
|
|
411
|
+
self.warn(
|
|
412
|
+
"Using 'MACRO_PLACEMENT_CFG' is deprecated. It is recommended to use the new 'MACROS' configuration variable."
|
|
413
|
+
)
|
|
414
|
+
shutil.copyfile(cfg_ref, cfg_file)
|
|
415
|
+
elif macros := self.config.get("MACROS"):
|
|
416
|
+
instance_count = sum(len(m.instances) for m in macros.values())
|
|
417
|
+
if instance_count >= 1:
|
|
418
|
+
with open(cfg_file, "w") as f:
|
|
419
|
+
for module, macro in macros.items():
|
|
420
|
+
if not isinstance(macro, Macro):
|
|
421
|
+
raise StepException(
|
|
422
|
+
f"Misconstructed configuration: macro definition for key {module} is not of type 'Macro'."
|
|
423
|
+
)
|
|
424
|
+
for name, data in macro.instances.items():
|
|
425
|
+
if data.location is not None:
|
|
426
|
+
if data.orientation is None:
|
|
427
|
+
raise StepException(
|
|
428
|
+
f"Instance {name} of macro {module} has a location configured, but no orientation."
|
|
429
|
+
)
|
|
430
|
+
f.write(
|
|
431
|
+
f"{name} {data.location[0]} {data.location[1]} {data.orientation}\n"
|
|
432
|
+
)
|
|
433
|
+
else:
|
|
434
|
+
verbose(
|
|
435
|
+
f"Instance {name} of macro {module} has no location configured, ignoring…"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if not cfg_file.exists():
|
|
439
|
+
info(f"No instances found, skipping '{self.id}'…")
|
|
440
|
+
return {}, {}
|
|
441
|
+
|
|
442
|
+
return super().run(state_in, **kwargs)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@Step.factory.register()
|
|
446
|
+
class ReportWireLength(OdbpyStep):
|
|
447
|
+
"""
|
|
448
|
+
Outputs a CSV of long wires, printed by length. Useful as a design aid to
|
|
449
|
+
detect when one wire is connected to too many things.
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
outputs = []
|
|
453
|
+
|
|
454
|
+
id = "Odb.ReportWireLength"
|
|
455
|
+
name = "Report Wire Length"
|
|
456
|
+
outputs = []
|
|
457
|
+
|
|
458
|
+
def get_script_path(self):
|
|
459
|
+
return os.path.join(get_script_dir(), "odbpy", "wire_lengths.py")
|
|
460
|
+
|
|
461
|
+
def get_command(self) -> List[str]:
|
|
462
|
+
return super().get_command() + [
|
|
463
|
+
"--human-readable",
|
|
464
|
+
"--report-out",
|
|
465
|
+
os.path.join(self.step_dir, "wire_lengths.csv"),
|
|
466
|
+
]
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
@Step.factory.register()
|
|
470
|
+
class ReportDisconnectedPins(OdbpyStep):
|
|
471
|
+
"""
|
|
472
|
+
Creates a table of disconnected pins in the design, updating metrics as
|
|
473
|
+
appropriate.
|
|
474
|
+
|
|
475
|
+
Disconnected pins may be marked "critical" if they are very likely to
|
|
476
|
+
result in a dead design. We determine if a pin is critical as follows:
|
|
477
|
+
|
|
478
|
+
* For the top-level macro: for these four kinds of pins: inputs, outputs,
|
|
479
|
+
power inouts, and ground inouts, at least one of each kind must be
|
|
480
|
+
connected or else all pins of a certain kind are counted as critical
|
|
481
|
+
disconnected pins.
|
|
482
|
+
* For instances:
|
|
483
|
+
* Any unconnected input is a critical disconnected pin.
|
|
484
|
+
* If there isn't at least one output connected, all disconnected
|
|
485
|
+
outputs are critical disconnected pins.
|
|
486
|
+
* Any disconnected power inout pins are critical disconnected pins.
|
|
487
|
+
|
|
488
|
+
The metrics ``design__disconnected_pin__count`` and
|
|
489
|
+
``design__critical_disconnected_pin__count`` is updated. It is recommended
|
|
490
|
+
to use the checker ``Checker.DisconnectedPins`` to check that there are
|
|
491
|
+
no critical disconnected pins.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
id = "Odb.ReportDisconnectedPins"
|
|
495
|
+
name = "Report Disconnected Pins"
|
|
496
|
+
|
|
497
|
+
config_vars = OdbpyStep.config_vars + [
|
|
498
|
+
Variable(
|
|
499
|
+
"IGNORE_DISCONNECTED_MODULES",
|
|
500
|
+
Optional[List[str]],
|
|
501
|
+
"Modules (or cells) to ignore when checking for disconnected pins.",
|
|
502
|
+
pdk=True,
|
|
503
|
+
),
|
|
504
|
+
]
|
|
505
|
+
|
|
506
|
+
def get_script_path(self):
|
|
507
|
+
return os.path.join(get_script_dir(), "odbpy", "disconnected_pins.py")
|
|
508
|
+
|
|
509
|
+
def get_command(self) -> List[str]:
|
|
510
|
+
command = super().get_command()
|
|
511
|
+
if ignored_modules := self.config["IGNORE_DISCONNECTED_MODULES"]:
|
|
512
|
+
for module in ignored_modules:
|
|
513
|
+
command.append("--ignore-module")
|
|
514
|
+
command.append(module)
|
|
515
|
+
command.append("--write-full-table-to")
|
|
516
|
+
command.append(os.path.join(self.step_dir, "full_disconnected_pins_table.txt"))
|
|
517
|
+
return command
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@Step.factory.register()
|
|
521
|
+
class AddRoutingObstructions(OdbpyStep):
|
|
522
|
+
id = "Odb.AddRoutingObstructions"
|
|
523
|
+
name = "Add Obstructions"
|
|
524
|
+
config_vars = [
|
|
525
|
+
Variable(
|
|
526
|
+
"ROUTING_OBSTRUCTIONS",
|
|
527
|
+
Optional[List[str]],
|
|
528
|
+
"Add routing obstructions to the design. If set to `None`, this step is skipped."
|
|
529
|
+
+ " Format of each obstruction item is: layer llx lly urx ury.",
|
|
530
|
+
units="µm",
|
|
531
|
+
default=None,
|
|
532
|
+
deprecated_names=["GRT_OBS"],
|
|
533
|
+
),
|
|
534
|
+
]
|
|
535
|
+
|
|
536
|
+
def get_obstruction_variable(self):
|
|
537
|
+
return self.config_vars[0]
|
|
538
|
+
|
|
539
|
+
def get_script_path(self):
|
|
540
|
+
return os.path.join(get_script_dir(), "odbpy", "defutil.py")
|
|
541
|
+
|
|
542
|
+
def get_subcommand(self) -> List[str]:
|
|
543
|
+
return ["add_obstructions"]
|
|
544
|
+
|
|
545
|
+
def get_command(self) -> List[str]:
|
|
546
|
+
command = super().get_command()
|
|
547
|
+
if obstructions := self.config[self.config_vars[0].name]:
|
|
548
|
+
for obstruction in obstructions:
|
|
549
|
+
command.append("--obstructions")
|
|
550
|
+
command.append(obstruction)
|
|
551
|
+
return command
|
|
552
|
+
|
|
553
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
554
|
+
if self.config[self.get_obstruction_variable().name] is None:
|
|
555
|
+
info(
|
|
556
|
+
f"'{self.get_obstruction_variable().name}' is not defined. Skipping '{self.id}'…"
|
|
557
|
+
)
|
|
558
|
+
return {}, {}
|
|
559
|
+
return super().run(state_in, **kwargs)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
@Step.factory.register()
|
|
563
|
+
class RemoveRoutingObstructions(AddRoutingObstructions):
|
|
564
|
+
id = "Odb.RemoveRoutingObstructions"
|
|
565
|
+
name = "Remove Obstructions"
|
|
566
|
+
|
|
567
|
+
def get_subcommand(self) -> List[str]:
|
|
568
|
+
return ["remove_obstructions"]
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@Step.factory.register()
|
|
572
|
+
class AddPDNObstructions(AddRoutingObstructions):
|
|
573
|
+
id = "Odb.AddPDNObstructions"
|
|
574
|
+
name = "Add PDN obstructions"
|
|
575
|
+
|
|
576
|
+
config_vars = [
|
|
577
|
+
Variable(
|
|
578
|
+
"PDN_OBSTRUCTIONS",
|
|
579
|
+
Optional[List[str]],
|
|
580
|
+
"Add routing obstructions to the design before PDN stage. If set to `None`, this step is skipped."
|
|
581
|
+
+ " Format of each obstruction item is: layer llx lly urx ury.",
|
|
582
|
+
units="µm",
|
|
583
|
+
default=None,
|
|
584
|
+
),
|
|
585
|
+
]
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@Step.factory.register()
|
|
589
|
+
class RemovePDNObstructions(RemoveRoutingObstructions):
|
|
590
|
+
id = "Odb.RemovePDNObstructions"
|
|
591
|
+
name = "Remove PDN obstructions"
|
|
592
|
+
|
|
593
|
+
config_vars = AddPDNObstructions.config_vars
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
_migrate_unmatched_io = lambda x: "unmatched_design" if x else "none"
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
@Step.factory.register()
|
|
600
|
+
class CustomIOPlacement(OdbpyStep):
|
|
601
|
+
"""
|
|
602
|
+
Places I/O pins using a custom script, which uses a "pin order configuration"
|
|
603
|
+
file.
|
|
604
|
+
|
|
605
|
+
Check the reference documentation for the structure of said file.
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
id = "Odb.CustomIOPlacement"
|
|
609
|
+
name = "Custom I/O Placement"
|
|
610
|
+
long_name = "Custom I/O Pin Placement Script"
|
|
611
|
+
|
|
612
|
+
config_vars = io_layer_variables + [
|
|
613
|
+
Variable(
|
|
614
|
+
"FP_IO_VLENGTH",
|
|
615
|
+
Optional[Decimal],
|
|
616
|
+
"""
|
|
617
|
+
The length of the pins with a north or south orientation. If unspecified by a PDK, the script will use whichever is higher of the following two values:
|
|
618
|
+
* The pin width
|
|
619
|
+
* The minimum value satisfying the minimum area constraint given the pin width
|
|
620
|
+
""",
|
|
621
|
+
units="µm",
|
|
622
|
+
pdk=True,
|
|
623
|
+
),
|
|
624
|
+
Variable(
|
|
625
|
+
"FP_IO_HLENGTH",
|
|
626
|
+
Optional[Decimal],
|
|
627
|
+
"""
|
|
628
|
+
The length of the pins with an east or west orientation. If unspecified by a PDK, the script will use whichever is higher of the following two values:
|
|
629
|
+
* The pin width
|
|
630
|
+
* The minimum value satisfying the minimum area constraint given the pin width
|
|
631
|
+
""",
|
|
632
|
+
units="µm",
|
|
633
|
+
pdk=True,
|
|
634
|
+
),
|
|
635
|
+
Variable(
|
|
636
|
+
"FP_PIN_ORDER_CFG",
|
|
637
|
+
Optional[Path],
|
|
638
|
+
"Path to the configuration file. If set to `None`, this step is skipped.",
|
|
639
|
+
),
|
|
640
|
+
Variable(
|
|
641
|
+
"ERRORS_ON_UNMATCHED_IO",
|
|
642
|
+
Literal["none", "unmatched_design", "unmatched_cfg", "both"],
|
|
643
|
+
"Controls whether to emit an error in: no situation, when pins exist in the design that do not exist in the config file, when pins exist in the config file that do not exist in the design, and both respectively. `both` is recommended, as the default is only for backwards compatibility with LibreLane 1.",
|
|
644
|
+
default="unmatched_design", # Backwards compatible with LibreLane 1
|
|
645
|
+
deprecated_names=[
|
|
646
|
+
("QUIT_ON_UNMATCHED_IO", _migrate_unmatched_io),
|
|
647
|
+
],
|
|
648
|
+
),
|
|
649
|
+
]
|
|
650
|
+
|
|
651
|
+
def get_script_path(self):
|
|
652
|
+
return os.path.join(get_script_dir(), "odbpy", "io_place.py")
|
|
653
|
+
|
|
654
|
+
def get_command(self) -> List[str]:
|
|
655
|
+
length_args = []
|
|
656
|
+
if self.config["FP_IO_VLENGTH"] is not None:
|
|
657
|
+
length_args += ["--ver-length", self.config["FP_IO_VLENGTH"]]
|
|
658
|
+
if self.config["FP_IO_HLENGTH"] is not None:
|
|
659
|
+
length_args += ["--hor-length", self.config["FP_IO_HLENGTH"]]
|
|
660
|
+
|
|
661
|
+
return (
|
|
662
|
+
super().get_command()
|
|
663
|
+
+ [
|
|
664
|
+
"--config",
|
|
665
|
+
self.config["FP_PIN_ORDER_CFG"],
|
|
666
|
+
"--hor-layer",
|
|
667
|
+
self.config["FP_IO_HLAYER"],
|
|
668
|
+
"--ver-layer",
|
|
669
|
+
self.config["FP_IO_VLAYER"],
|
|
670
|
+
"--hor-width-mult",
|
|
671
|
+
str(self.config["FP_IO_VTHICKNESS_MULT"]),
|
|
672
|
+
"--ver-width-mult",
|
|
673
|
+
str(self.config["FP_IO_HTHICKNESS_MULT"]),
|
|
674
|
+
"--hor-extension",
|
|
675
|
+
str(self.config["FP_IO_HEXTEND"]),
|
|
676
|
+
"--ver-extension",
|
|
677
|
+
str(self.config["FP_IO_VEXTEND"]),
|
|
678
|
+
"--unmatched-error",
|
|
679
|
+
self.config["ERRORS_ON_UNMATCHED_IO"],
|
|
680
|
+
]
|
|
681
|
+
+ length_args
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
685
|
+
if self.config["FP_PIN_ORDER_CFG"] is None:
|
|
686
|
+
info("No custom floorplan file configured, skipping…")
|
|
687
|
+
return {}, {}
|
|
688
|
+
return super().run(state_in, **kwargs)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@Step.factory.register()
|
|
692
|
+
class PortDiodePlacement(OdbpyStep):
|
|
693
|
+
"""
|
|
694
|
+
Unconditionally inserts diodes on design ports diodes on ports,
|
|
695
|
+
to mitigate the `antenna effect <https://en.wikipedia.org/wiki/Antenna_effect>`_.
|
|
696
|
+
|
|
697
|
+
Useful for hardening macros, where ports may get long wires that are
|
|
698
|
+
unaccounted for when hardening a top-level chip.
|
|
699
|
+
|
|
700
|
+
The placement is **not legalized**.
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
id = "Odb.PortDiodePlacement"
|
|
704
|
+
name = "Port Diode Placement Script"
|
|
705
|
+
|
|
706
|
+
config_vars = [
|
|
707
|
+
Variable(
|
|
708
|
+
"DIODE_ON_PORTS",
|
|
709
|
+
Literal["none", "in", "out", "both"],
|
|
710
|
+
"Always insert diodes on ports with the specified polarities.",
|
|
711
|
+
default="none",
|
|
712
|
+
),
|
|
713
|
+
Variable(
|
|
714
|
+
"GPL_CELL_PADDING",
|
|
715
|
+
Decimal,
|
|
716
|
+
"Cell padding value (in sites) for global placement. Used by this step only to emit a warning if it's 0.",
|
|
717
|
+
units="sites",
|
|
718
|
+
pdk=True,
|
|
719
|
+
),
|
|
720
|
+
]
|
|
721
|
+
|
|
722
|
+
def get_script_path(self):
|
|
723
|
+
return os.path.join(get_script_dir(), "odbpy", "diodes.py")
|
|
724
|
+
|
|
725
|
+
def get_subcommand(self) -> List[str]:
|
|
726
|
+
return ["place"]
|
|
727
|
+
|
|
728
|
+
def get_command(self) -> List[str]:
|
|
729
|
+
cell, pin = self.config["DIODE_CELL"].split("/")
|
|
730
|
+
|
|
731
|
+
return super().get_command() + [
|
|
732
|
+
"--diode-cell",
|
|
733
|
+
cell,
|
|
734
|
+
"--diode-pin",
|
|
735
|
+
pin,
|
|
736
|
+
"--port-protect",
|
|
737
|
+
self.config["DIODE_ON_PORTS"],
|
|
738
|
+
"--threshold",
|
|
739
|
+
"Infinity",
|
|
740
|
+
]
|
|
741
|
+
|
|
742
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
743
|
+
if self.config["DIODE_ON_PORTS"] == "none":
|
|
744
|
+
info("'DIODE_ON_PORTS' is set to 'none': skipping…")
|
|
745
|
+
return {}, {}
|
|
746
|
+
|
|
747
|
+
if self.config["GPL_CELL_PADDING"] == 0:
|
|
748
|
+
self.warn(
|
|
749
|
+
"'GPL_CELL_PADDING' is set to 0. This step may cause overlap failures."
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
return super().run(state_in, **kwargs)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
@Step.factory.register()
|
|
756
|
+
class DiodesOnPorts(CompositeStep):
|
|
757
|
+
"""
|
|
758
|
+
Unconditionally inserts diodes on design ports diodes on ports,
|
|
759
|
+
to mitigate the `antenna effect <https://en.wikipedia.org/wiki/Antenna_effect>`_.
|
|
760
|
+
|
|
761
|
+
Useful for hardening macros, where ports may get long wires that are
|
|
762
|
+
unaccounted for when hardening a top-level chip.
|
|
763
|
+
|
|
764
|
+
The placement is legalized by performing detailed placement and global
|
|
765
|
+
routing after inserting the diodes.
|
|
766
|
+
|
|
767
|
+
Prior to beta 16, this step did not legalize its placement: if you would
|
|
768
|
+
like to retain the old behavior without legalization, try
|
|
769
|
+
``Odb.PortDiodePlacement``.
|
|
770
|
+
"""
|
|
771
|
+
|
|
772
|
+
id = "Odb.DiodesOnPorts"
|
|
773
|
+
name = "Diodes on Ports"
|
|
774
|
+
long_name = "Diodes on Ports Protection Routine"
|
|
775
|
+
|
|
776
|
+
Steps = [
|
|
777
|
+
PortDiodePlacement,
|
|
778
|
+
DetailedPlacement,
|
|
779
|
+
GlobalRouting,
|
|
780
|
+
]
|
|
781
|
+
|
|
782
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
783
|
+
if self.config["DIODE_ON_PORTS"] == "none":
|
|
784
|
+
info("'DIODE_ON_PORTS' is set to 'none': skipping…")
|
|
785
|
+
return {}, {}
|
|
786
|
+
return super().run(state_in, **kwargs)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
@Step.factory.register()
|
|
790
|
+
class FuzzyDiodePlacement(OdbpyStep):
|
|
791
|
+
"""
|
|
792
|
+
Runs a custom diode placement script to mitigate the `antenna effect <https://en.wikipedia.org/wiki/Antenna_effect>`_.
|
|
793
|
+
|
|
794
|
+
This script uses the `Manhattan length <https://en.wikipedia.org/wiki/Manhattan_distance>`_
|
|
795
|
+
of a (non-existent) wire at the global placement stage, and places diodes
|
|
796
|
+
if they exceed a certain threshold. This, however, requires some padding:
|
|
797
|
+
`GPL_CELL_PADDING` and `DPL_CELL_PADDING` must be higher than 0 for this
|
|
798
|
+
script to work reliably.
|
|
799
|
+
|
|
800
|
+
The placement is *not* legalized.
|
|
801
|
+
|
|
802
|
+
The original script was written by `Sylvain "tnt" Munaut <https://github.com/smunaut>`_.
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
id = "Odb.FuzzyDiodePlacement"
|
|
806
|
+
name = "Fuzzy Diode Placement"
|
|
807
|
+
|
|
808
|
+
config_vars = [
|
|
809
|
+
Variable(
|
|
810
|
+
"HEURISTIC_ANTENNA_THRESHOLD",
|
|
811
|
+
Decimal,
|
|
812
|
+
"A Manhattan distance above which a diode is recommended to be inserted by the heuristic inserter. If not specified, the heuristic algorithm.",
|
|
813
|
+
units="µm",
|
|
814
|
+
pdk=True,
|
|
815
|
+
),
|
|
816
|
+
Variable(
|
|
817
|
+
"GPL_CELL_PADDING",
|
|
818
|
+
Decimal,
|
|
819
|
+
"Cell padding value (in sites) for global placement. Used by this step only to emit a warning if it's 0.",
|
|
820
|
+
units="sites",
|
|
821
|
+
pdk=True,
|
|
822
|
+
),
|
|
823
|
+
]
|
|
824
|
+
|
|
825
|
+
def get_script_path(self):
|
|
826
|
+
return os.path.join(get_script_dir(), "odbpy", "diodes.py")
|
|
827
|
+
|
|
828
|
+
def get_subcommand(self) -> List[str]:
|
|
829
|
+
return ["place"]
|
|
830
|
+
|
|
831
|
+
def get_command(self) -> List[str]:
|
|
832
|
+
cell, pin = self.config["DIODE_CELL"].split("/")
|
|
833
|
+
|
|
834
|
+
threshold_opts = []
|
|
835
|
+
if threshold := self.config["HEURISTIC_ANTENNA_THRESHOLD"]:
|
|
836
|
+
threshold_opts = ["--threshold", threshold]
|
|
837
|
+
|
|
838
|
+
return (
|
|
839
|
+
super().get_command()
|
|
840
|
+
+ [
|
|
841
|
+
"--diode-cell",
|
|
842
|
+
cell,
|
|
843
|
+
"--diode-pin",
|
|
844
|
+
pin,
|
|
845
|
+
]
|
|
846
|
+
+ threshold_opts
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
850
|
+
if self.config["GPL_CELL_PADDING"] == 0:
|
|
851
|
+
self.warn(
|
|
852
|
+
"'GPL_CELL_PADDING' is set to 0. This step may cause overlap failures."
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
return super().run(state_in, **kwargs)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
@Step.factory.register()
|
|
859
|
+
class HeuristicDiodeInsertion(CompositeStep):
|
|
860
|
+
"""
|
|
861
|
+
Runs a custom diode insertion routine to mitigate the `antenna effect <https://en.wikipedia.org/wiki/Antenna_effect>`_.
|
|
862
|
+
|
|
863
|
+
This script uses the `Manhattan length <https://en.wikipedia.org/wiki/Manhattan_distance>`_
|
|
864
|
+
of a (non-existent) wire at the global placement stage, and places diodes
|
|
865
|
+
if they exceed a certain threshold. This, however, requires some padding:
|
|
866
|
+
`GPL_CELL_PADDING` and `DPL_CELL_PADDING` must be higher than 0 for this
|
|
867
|
+
script to work reliably.
|
|
868
|
+
|
|
869
|
+
The placement is then legalized by performing detailed placement and global
|
|
870
|
+
routing after inserting the diodes.
|
|
871
|
+
|
|
872
|
+
The original script was written by `Sylvain "tnt" Munaut <https://github.com/smunaut>`_.
|
|
873
|
+
|
|
874
|
+
Prior to beta 16, this step did not legalize its placement: if you would
|
|
875
|
+
like to retain the old behavior without legalization, try
|
|
876
|
+
``Odb.FuzzyDiodePlacement``.
|
|
877
|
+
"""
|
|
878
|
+
|
|
879
|
+
id = "Odb.HeuristicDiodeInsertion"
|
|
880
|
+
name = "Heuristic Diode Insertion"
|
|
881
|
+
long_name = "Heuristic Diode Insertion Routine"
|
|
882
|
+
|
|
883
|
+
Steps = [
|
|
884
|
+
FuzzyDiodePlacement,
|
|
885
|
+
DetailedPlacement,
|
|
886
|
+
GlobalRouting,
|
|
887
|
+
]
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
@Step.factory.register()
|
|
891
|
+
class CellFrequencyTables(OdbpyStep):
|
|
892
|
+
"""
|
|
893
|
+
Creates a number of tables to show the cell frequencies by:
|
|
894
|
+
|
|
895
|
+
- Cells
|
|
896
|
+
- Buffer cells only
|
|
897
|
+
- Cell Function*
|
|
898
|
+
- Standard Cell Library*
|
|
899
|
+
|
|
900
|
+
* These tables only return meaningful info with PDKs distributed in the
|
|
901
|
+
Open_PDKs format, i.e., all cells are named ``{scl}__{cell_fn}_{size}``.
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
id = "Odb.CellFrequencyTables"
|
|
905
|
+
name = "Generate Cell Frequency Tables"
|
|
906
|
+
|
|
907
|
+
def get_script_path(self):
|
|
908
|
+
return os.path.join(
|
|
909
|
+
get_script_dir(),
|
|
910
|
+
"odbpy",
|
|
911
|
+
"cell_frequency.py",
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
def get_buffer_list_file(self):
|
|
915
|
+
return os.path.join(self.step_dir, "buffer_list.txt")
|
|
916
|
+
|
|
917
|
+
def get_buffer_list_script(self):
|
|
918
|
+
return os.path.join(get_script_dir(), "openroad", "buffer_list.tcl")
|
|
919
|
+
|
|
920
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
921
|
+
kwargs, env = self.extract_env(kwargs)
|
|
922
|
+
|
|
923
|
+
env_copy = env.copy()
|
|
924
|
+
lib_list = self.toolbox.filter_views(self.config, self.config["LIB"])
|
|
925
|
+
env_copy["_PNR_LIBS"] = TclStep.value_to_tcl(lib_list)
|
|
926
|
+
super().run_subprocess(
|
|
927
|
+
["openroad", "-no_splash", "-exit", self.get_buffer_list_script()],
|
|
928
|
+
env=env_copy,
|
|
929
|
+
log_to=self.get_buffer_list_file(),
|
|
930
|
+
)
|
|
931
|
+
return super().run(state_in, env=env, **kwargs)
|
|
932
|
+
|
|
933
|
+
def get_command(self) -> List[str]:
|
|
934
|
+
command = super().get_command()
|
|
935
|
+
command.append("--buffer-list")
|
|
936
|
+
command.append(self.get_buffer_list_file())
|
|
937
|
+
command.append("--out-dir")
|
|
938
|
+
command.append(self.step_dir)
|
|
939
|
+
return command
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
@Step.factory.register()
|
|
943
|
+
class ManualGlobalPlacement(OdbpyStep):
|
|
944
|
+
"""
|
|
945
|
+
This is an step to override the placement of one or more instances at
|
|
946
|
+
user-specified locations.
|
|
947
|
+
|
|
948
|
+
Alternatively, if this is a custom design with a few cells, this can be used
|
|
949
|
+
in place of the global placement entirely.
|
|
950
|
+
"""
|
|
951
|
+
|
|
952
|
+
id = "Odb.ManualGlobalPlacement"
|
|
953
|
+
name = "Manual Global Placement"
|
|
954
|
+
|
|
955
|
+
config_vars = OdbpyStep.config_vars + [
|
|
956
|
+
Variable(
|
|
957
|
+
"MANUAL_GLOBAL_PLACEMENTS",
|
|
958
|
+
Optional[Dict[str, Instance]],
|
|
959
|
+
description="A dictionary of instances to their global (non-legalized and unfixed) placement location.",
|
|
960
|
+
)
|
|
961
|
+
]
|
|
962
|
+
|
|
963
|
+
def get_script_path(self) -> str:
|
|
964
|
+
return os.path.join(get_script_dir(), "odbpy", "placers.py")
|
|
965
|
+
|
|
966
|
+
def get_subcommand(self) -> List[str]:
|
|
967
|
+
return ["manual-global-placement"]
|
|
968
|
+
|
|
969
|
+
def get_command(self) -> List[str]:
|
|
970
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
971
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
972
|
+
|
|
973
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
974
|
+
if self.config["MANUAL_GLOBAL_PLACEMENTS"] is None:
|
|
975
|
+
info("'MANUAL_GLOBAL_PLACEMENTS' not set, skipping…")
|
|
976
|
+
return {}, {}
|
|
977
|
+
return super().run(state_in, **kwargs)
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
@dataclass
|
|
981
|
+
class ECOBuffer:
|
|
982
|
+
"""
|
|
983
|
+
:param target: The driver to insert an ECO buffer after or sink to insert an
|
|
984
|
+
ECO buffer before, in the format instance_name/pin_name.
|
|
985
|
+
:param buffer: The kind of buffer cell to use.
|
|
986
|
+
:param placement: The coarse placement for this buffer (to be legalized.)
|
|
987
|
+
If unset, depending on whether the target is a driver or a sink:
|
|
988
|
+
|
|
989
|
+
- Driver: The placement will be the average of the driver and all sinks.
|
|
990
|
+
|
|
991
|
+
- Sink: The placement will be the average of the sink and all drivers.
|
|
992
|
+
"""
|
|
993
|
+
|
|
994
|
+
target: str
|
|
995
|
+
buffer: str
|
|
996
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
@Step.factory.register()
|
|
1000
|
+
class InsertECOBuffers(OdbpyStep):
|
|
1001
|
+
"""
|
|
1002
|
+
Experimental step to insert ECO buffers on either drivers or sinks after
|
|
1003
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1004
|
+
incrementally re-run for affected nets. Useful for manually fixing some hold
|
|
1005
|
+
violations.
|
|
1006
|
+
|
|
1007
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1008
|
+
nets that are altered are removed and require re-routing.
|
|
1009
|
+
|
|
1010
|
+
INOUT and FEEDTHRU ports are not supported.
|
|
1011
|
+
"""
|
|
1012
|
+
|
|
1013
|
+
id = "Odb.InsertECOBuffers"
|
|
1014
|
+
name = "Insert ECO Buffers"
|
|
1015
|
+
|
|
1016
|
+
config_vars = (
|
|
1017
|
+
dpl_variables
|
|
1018
|
+
+ grt_variables
|
|
1019
|
+
+ [
|
|
1020
|
+
Variable(
|
|
1021
|
+
"INSERT_ECO_BUFFERS",
|
|
1022
|
+
Optional[List[ECOBuffer]],
|
|
1023
|
+
"List of buffers to insert",
|
|
1024
|
+
)
|
|
1025
|
+
]
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
def get_script_path(self):
|
|
1029
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_buffer.py")
|
|
1030
|
+
|
|
1031
|
+
def get_command(self) -> List[str]:
|
|
1032
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1033
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
@dataclass
|
|
1037
|
+
class ECODiode:
|
|
1038
|
+
"""
|
|
1039
|
+
:param target: The sink whose net gets a diode connected, in the format
|
|
1040
|
+
instance_name/pin_name.
|
|
1041
|
+
:param placement: The coarse placement for this diode (to be legalized.)
|
|
1042
|
+
If unset, the diode is placed at the same location as the target
|
|
1043
|
+
instance, with legalization later moving it to a valid location.
|
|
1044
|
+
"""
|
|
1045
|
+
|
|
1046
|
+
target: str
|
|
1047
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
@Step.factory.register()
|
|
1051
|
+
class InsertECODiodes(OdbpyStep):
|
|
1052
|
+
"""
|
|
1053
|
+
Experimental step to create and attach ECO diodes to the nets of sinks after
|
|
1054
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1055
|
+
incrementally re-run for affected nets. Useful for manually fixing some
|
|
1056
|
+
antenna violations.
|
|
1057
|
+
|
|
1058
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1059
|
+
nets that are altered are removed and require re-routing.
|
|
1060
|
+
"""
|
|
1061
|
+
|
|
1062
|
+
id = "Odb.InsertECODiodes"
|
|
1063
|
+
name = "Insert ECO Diodes"
|
|
1064
|
+
|
|
1065
|
+
config_vars = (
|
|
1066
|
+
grt_variables
|
|
1067
|
+
+ dpl_variables
|
|
1068
|
+
+ [
|
|
1069
|
+
Variable(
|
|
1070
|
+
"INSERT_ECO_DIODES",
|
|
1071
|
+
Optional[List[ECODiode]],
|
|
1072
|
+
"List of sinks to insert diodes for.",
|
|
1073
|
+
)
|
|
1074
|
+
]
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
def get_script_path(self):
|
|
1078
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_diode.py")
|
|
1079
|
+
|
|
1080
|
+
def get_command(self) -> List[str]:
|
|
1081
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1082
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1083
|
+
|
|
1084
|
+
def run(self, state_in: State, **kwargs):
|
|
1085
|
+
if self.config["DIODE_CELL"] is None:
|
|
1086
|
+
info(f"'DIODE_CELL' not set. Skipping '{self.id}'…")
|
|
1087
|
+
return {}, {}
|
|
1088
|
+
return super().run(state_in, **kwargs)
|