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
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2020-2022 Efabless Corporation
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import odb
|
|
16
|
+
import utl
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
import json
|
|
20
|
+
import functools
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
from reader import OdbReader, click_odb, click
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group()
|
|
28
|
+
def cli():
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Design(object):
|
|
33
|
+
@dataclass
|
|
34
|
+
class Instance:
|
|
35
|
+
name: str
|
|
36
|
+
module_name: str
|
|
37
|
+
power_connections: Dict[str, str]
|
|
38
|
+
ground_connections: Dict[str, str]
|
|
39
|
+
|
|
40
|
+
def __init__(self, reader: OdbReader, yosys_dict: dict) -> None:
|
|
41
|
+
self.reader = reader
|
|
42
|
+
self.design_name = reader.block.getName()
|
|
43
|
+
self.yosys_dict = yosys_dict
|
|
44
|
+
|
|
45
|
+
self.pins_by_module_name: Dict[str, Dict[str, odb.dbMTerm]] = {}
|
|
46
|
+
self.verilog_net_names_by_bit_by_module: Dict[str, Dict[int, str]] = {}
|
|
47
|
+
self.nets_by_net_name = {net.getName(): net for net in reader.block.getNets()}
|
|
48
|
+
|
|
49
|
+
def get_verilog_net_name_by_bit(self, top_module: str, target_bit: int):
|
|
50
|
+
yosys_design_object = self.yosys_dict["modules"][top_module]
|
|
51
|
+
if top_module not in self.verilog_net_names_by_bit_by_module:
|
|
52
|
+
self.verilog_net_names_by_bit_by_module[top_module] = functools.reduce(
|
|
53
|
+
lambda a, b: {**a, **{bit: b[0] for bit in b[1]["bits"]}},
|
|
54
|
+
yosys_design_object["netnames"].items(),
|
|
55
|
+
{},
|
|
56
|
+
)
|
|
57
|
+
return self.verilog_net_names_by_bit_by_module[top_module][target_bit]
|
|
58
|
+
|
|
59
|
+
def get_pins(self, module_name: str) -> Dict[str, odb.dbMTerm]:
|
|
60
|
+
if module_name not in self.pins_by_module_name:
|
|
61
|
+
master = self.reader.db.findMaster(module_name)
|
|
62
|
+
if master is None:
|
|
63
|
+
print(
|
|
64
|
+
f"[ERROR] No LEF view for {module_name} was found in the database even though it is declared in the Verilog."
|
|
65
|
+
)
|
|
66
|
+
exit(-1)
|
|
67
|
+
pins = {pin.getName(): pin for pin in master.getMTerms()}
|
|
68
|
+
self.pins_by_module_name[module_name] = pins
|
|
69
|
+
return self.pins_by_module_name[module_name]
|
|
70
|
+
|
|
71
|
+
def is_power(self, module_name: str, pin_name: str) -> bool:
|
|
72
|
+
module_pins = self.get_pins(module_name)
|
|
73
|
+
if pin_name not in module_pins:
|
|
74
|
+
print(
|
|
75
|
+
f"[ERROR] No pin {pin_name} found in the module {module_name}'s LEF view: the LEF and Verilog views of the module may be mismatched."
|
|
76
|
+
)
|
|
77
|
+
exit(-1)
|
|
78
|
+
|
|
79
|
+
return module_pins[pin_name].getSigType() == "POWER"
|
|
80
|
+
|
|
81
|
+
def is_ground(self, module_name: str, pin_name: str) -> bool:
|
|
82
|
+
module_pins = self.get_pins(module_name)
|
|
83
|
+
if pin_name not in module_pins:
|
|
84
|
+
print(
|
|
85
|
+
f"[ERROR] No pin {pin_name} found in the module {module_name}'s LEF view: the LEF and Verilog views of the module may be mismatched."
|
|
86
|
+
)
|
|
87
|
+
exit(-1)
|
|
88
|
+
|
|
89
|
+
return module_pins[pin_name].getSigType() == "GROUND"
|
|
90
|
+
|
|
91
|
+
def extract_pg_pins(self, top_module: str, cell_name: str) -> dict:
|
|
92
|
+
yosys_design_object = self.yosys_dict["modules"][top_module]
|
|
93
|
+
cells = yosys_design_object["cells"]
|
|
94
|
+
module_name = cells[cell_name]["type"]
|
|
95
|
+
master = self.reader.db.findMaster(module_name)
|
|
96
|
+
if master is None:
|
|
97
|
+
print(
|
|
98
|
+
f"[ERROR] Could not find master for cell type '{module_name}' in the database."
|
|
99
|
+
)
|
|
100
|
+
exit(-1)
|
|
101
|
+
|
|
102
|
+
lef_pg_pins = []
|
|
103
|
+
for pin in master.getMTerms():
|
|
104
|
+
if pin.getSigType() in ["POWER", "GROUND"]:
|
|
105
|
+
lef_pg_pins.append((pin.getName(), pin.getSigType()))
|
|
106
|
+
|
|
107
|
+
power_pins = {}
|
|
108
|
+
ground_pins = {}
|
|
109
|
+
connections = cells[cell_name]["connections"]
|
|
110
|
+
for pin_name, sigtype in lef_pg_pins:
|
|
111
|
+
if pin_name not in connections:
|
|
112
|
+
# Bad Verilog view-- error would break backcompat
|
|
113
|
+
continue
|
|
114
|
+
connection_bits = connections[pin_name]
|
|
115
|
+
if len(connection_bits) != 1:
|
|
116
|
+
print(
|
|
117
|
+
f"[ERROR] Unexpectedly found more than one bit connected to {sigtype} pin {module_name}/{pin_name}."
|
|
118
|
+
)
|
|
119
|
+
exit(-1)
|
|
120
|
+
connection_bit = connection_bits[0]
|
|
121
|
+
connected_to_v = self.get_verilog_net_name_by_bit(
|
|
122
|
+
top_module,
|
|
123
|
+
connection_bit,
|
|
124
|
+
)
|
|
125
|
+
(power_pins if sigtype == "POWER" else ground_pins)[
|
|
126
|
+
pin_name
|
|
127
|
+
] = connected_to_v
|
|
128
|
+
|
|
129
|
+
return power_pins, ground_pins
|
|
130
|
+
|
|
131
|
+
def extract_instances(
|
|
132
|
+
self,
|
|
133
|
+
top_module: str,
|
|
134
|
+
prefix: str = "",
|
|
135
|
+
) -> List["Design.Instance"]:
|
|
136
|
+
yosys_design_object = self.yosys_dict["modules"][top_module]
|
|
137
|
+
instances = []
|
|
138
|
+
cells = yosys_design_object["cells"]
|
|
139
|
+
for cell_name in cells.keys():
|
|
140
|
+
module_name = cells[cell_name]["type"]
|
|
141
|
+
if module_name.startswith("$"):
|
|
142
|
+
# yosys primitive
|
|
143
|
+
continue
|
|
144
|
+
if module_name in self.yosys_dict["modules"]:
|
|
145
|
+
print(
|
|
146
|
+
f"[WARNING] Macros inside hierarchical netlists are not currently supported in LibreLane: skipping submodule '{cell_name}' of type '{module_name}'."
|
|
147
|
+
)
|
|
148
|
+
continue
|
|
149
|
+
# sub_instances = self.extract_instances(
|
|
150
|
+
# self.yosys_dict["modules"][module_name],
|
|
151
|
+
# prefix=prefix + f"{cell_name}/",
|
|
152
|
+
# )
|
|
153
|
+
# instances += sub_instances
|
|
154
|
+
power_pins, ground_pins = self.extract_pg_pins(
|
|
155
|
+
top_module,
|
|
156
|
+
cell_name,
|
|
157
|
+
)
|
|
158
|
+
instances.append(
|
|
159
|
+
Design.Instance(
|
|
160
|
+
name=prefix + cell_name,
|
|
161
|
+
ground_connections=ground_pins,
|
|
162
|
+
power_connections=power_pins,
|
|
163
|
+
module_name=module_name,
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return instances
|
|
168
|
+
|
|
169
|
+
def add_global_connection(
|
|
170
|
+
self,
|
|
171
|
+
net_name: str,
|
|
172
|
+
inst_name: str,
|
|
173
|
+
pin_name: str,
|
|
174
|
+
power: bool = False,
|
|
175
|
+
ground: bool = False,
|
|
176
|
+
region: Optional[odb.dbRegion] = None,
|
|
177
|
+
):
|
|
178
|
+
# Function adapted from OpenROAD
|
|
179
|
+
#
|
|
180
|
+
# Copyright (c) 2022, The Regents of the University of California
|
|
181
|
+
# All rights reserved.
|
|
182
|
+
#
|
|
183
|
+
# Redistribution and use in source and binary forms, with or without
|
|
184
|
+
# modification, are permitted provided that the following conditions are met:
|
|
185
|
+
#
|
|
186
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
|
187
|
+
# list of conditions and the following disclaimer.
|
|
188
|
+
#
|
|
189
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
190
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
191
|
+
# and/or other materials provided with the distribution.
|
|
192
|
+
#
|
|
193
|
+
# * Neither the name of the copyright holder nor the names of its
|
|
194
|
+
# contributors may be used to endorse or promote products derived from
|
|
195
|
+
# this software without specific prior written permission.
|
|
196
|
+
#
|
|
197
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
198
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
199
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
200
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
201
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
202
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
203
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
204
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
205
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
206
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
207
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
208
|
+
|
|
209
|
+
design = self.reader.chip
|
|
210
|
+
net = design.getBlock().findNet(net_name)
|
|
211
|
+
if net is None:
|
|
212
|
+
net = odb.dbNet_create(design.getBlock(), net_name)
|
|
213
|
+
|
|
214
|
+
if power and ground:
|
|
215
|
+
utl.error(utl.PDN, 1551, "Only power or ground can be specified")
|
|
216
|
+
elif power:
|
|
217
|
+
net.setSpecial()
|
|
218
|
+
net.setSigType("POWER")
|
|
219
|
+
elif ground:
|
|
220
|
+
net.setSpecial()
|
|
221
|
+
net.setSigType("GROUND")
|
|
222
|
+
|
|
223
|
+
if region is not None:
|
|
224
|
+
region = design.getBlock().findRegion(region)
|
|
225
|
+
if region is None:
|
|
226
|
+
utl.error(utl.PDN, 1504, f"Region {region} not defined")
|
|
227
|
+
exit(-1)
|
|
228
|
+
|
|
229
|
+
inst_def_name = self.reader.escape_verilog_name(inst_name)
|
|
230
|
+
pin_def_name = self.reader.escape_verilog_name(pin_name)
|
|
231
|
+
|
|
232
|
+
for term in net.getITerms():
|
|
233
|
+
if (
|
|
234
|
+
term.getInst().getName() == inst_def_name
|
|
235
|
+
and term.getMTerm().getName() == pin_def_name
|
|
236
|
+
):
|
|
237
|
+
print(
|
|
238
|
+
f"[INFO] {inst_name}/{pin_name} is already connected to {net.getName()} in the layout."
|
|
239
|
+
)
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
connected_items = design.getBlock().addGlobalConnect(
|
|
243
|
+
region,
|
|
244
|
+
re.escape(inst_def_name),
|
|
245
|
+
re.escape(pin_def_name),
|
|
246
|
+
net,
|
|
247
|
+
True,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
print(f"[INFO] Made {connected_items} connections.")
|
|
251
|
+
if connected_items == 0:
|
|
252
|
+
print(
|
|
253
|
+
f"[ERROR] add_global_connections failed to make any connections for '{inst_name}/{pin_name}' to {net_name}."
|
|
254
|
+
)
|
|
255
|
+
exit(-1)
|
|
256
|
+
elif connected_items > 1:
|
|
257
|
+
print(
|
|
258
|
+
f"[ERROR] add_global_connections somehow made {connected_items} connections for '{inst_name}/{pin_name}' to {net_name} -- please report this as a bug"
|
|
259
|
+
)
|
|
260
|
+
exit(-1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@click.command()
|
|
264
|
+
@click.option(
|
|
265
|
+
"--input-json",
|
|
266
|
+
type=click.Path(
|
|
267
|
+
exists=True,
|
|
268
|
+
file_okay=True,
|
|
269
|
+
dir_okay=False,
|
|
270
|
+
readable=True,
|
|
271
|
+
),
|
|
272
|
+
required=True,
|
|
273
|
+
)
|
|
274
|
+
@click_odb
|
|
275
|
+
def set_power_connections(input_json, reader: OdbReader):
|
|
276
|
+
design_str = open(input_json).read()
|
|
277
|
+
yosys_dict = json.loads(design_str)
|
|
278
|
+
|
|
279
|
+
design = Design(reader, yosys_dict)
|
|
280
|
+
macro_instances = design.extract_instances(design.design_name)
|
|
281
|
+
for instance in macro_instances:
|
|
282
|
+
for pin in instance.power_connections.keys():
|
|
283
|
+
net_name = instance.power_connections[pin]
|
|
284
|
+
print(f"Connecting power net {net_name} to {instance.name}/{pin}…")
|
|
285
|
+
design.add_global_connection(
|
|
286
|
+
inst_name=instance.name,
|
|
287
|
+
net_name=net_name,
|
|
288
|
+
pin_name=pin,
|
|
289
|
+
power=True,
|
|
290
|
+
)
|
|
291
|
+
for pin in instance.ground_connections.keys():
|
|
292
|
+
net_name = instance.ground_connections[pin]
|
|
293
|
+
print(f"Connecting ground net {net_name} to {instance.name}/{pin}…")
|
|
294
|
+
design.add_global_connection(
|
|
295
|
+
inst_name=instance.name,
|
|
296
|
+
net_name=net_name,
|
|
297
|
+
pin_name=pin,
|
|
298
|
+
ground=True,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
cli.add_command(set_power_connections)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@click.command()
|
|
306
|
+
@click.option(
|
|
307
|
+
"--output-vh",
|
|
308
|
+
"output_vh",
|
|
309
|
+
type=click.Path(exists=False, dir_okay=False, writable=True),
|
|
310
|
+
required=True,
|
|
311
|
+
)
|
|
312
|
+
@click.option(
|
|
313
|
+
"--input-json",
|
|
314
|
+
type=click.Path(
|
|
315
|
+
exists=True,
|
|
316
|
+
file_okay=True,
|
|
317
|
+
dir_okay=False,
|
|
318
|
+
readable=True,
|
|
319
|
+
),
|
|
320
|
+
required=True,
|
|
321
|
+
)
|
|
322
|
+
@click.option(
|
|
323
|
+
"--power-define",
|
|
324
|
+
type=str,
|
|
325
|
+
required=False,
|
|
326
|
+
)
|
|
327
|
+
@click_odb
|
|
328
|
+
def write_verilog_header(
|
|
329
|
+
output_vh: str,
|
|
330
|
+
input_json: str,
|
|
331
|
+
power_define: Optional[str],
|
|
332
|
+
reader: OdbReader,
|
|
333
|
+
):
|
|
334
|
+
input_dict = json.load(open(input_json))
|
|
335
|
+
design_name = reader.block.getName()
|
|
336
|
+
pg_bterms = {}
|
|
337
|
+
for bterm in reader.block.getBTerms():
|
|
338
|
+
name, sigtype = bterm.getName(), bterm.getSigType()
|
|
339
|
+
if sigtype in ["POWER", "GROUND"]:
|
|
340
|
+
pg_bterms[name] = sigtype
|
|
341
|
+
|
|
342
|
+
design_dict = input_dict["modules"][design_name]
|
|
343
|
+
ports = design_dict["ports"]
|
|
344
|
+
with open(output_vh, "w") as f:
|
|
345
|
+
# For power/ground pins, we rely primarily on the information from the
|
|
346
|
+
# layout as the user may not have defined them in Verilog at all
|
|
347
|
+
pg_decls = []
|
|
348
|
+
for name in pg_bterms:
|
|
349
|
+
direction = "inout"
|
|
350
|
+
if verilog_info := ports.get(name):
|
|
351
|
+
direction = verilog_info["direction"]
|
|
352
|
+
pg_decls.append(f"{direction} {name}")
|
|
353
|
+
|
|
354
|
+
# For signals, we rely on the information from the Verilog-generated
|
|
355
|
+
# header as the layout separates buses into individual pins
|
|
356
|
+
signal_decls = []
|
|
357
|
+
for name, info in ports.items():
|
|
358
|
+
if name in pg_bterms:
|
|
359
|
+
continue
|
|
360
|
+
bus_postfix = ""
|
|
361
|
+
# See https://github.com/YosysHQ/yosys/blob/91685355a082f1b5fbc539d0ec484f4d484f5baa/passes/cmds/portlist.cc#L65
|
|
362
|
+
upto = info.get("upto", 0) == 1
|
|
363
|
+
offset = info.get("offset", 0)
|
|
364
|
+
width = len(info["bits"])
|
|
365
|
+
if width > 1:
|
|
366
|
+
msb = offset + width - 1
|
|
367
|
+
lsb = offset
|
|
368
|
+
if upto:
|
|
369
|
+
msb, lsb = lsb, msb
|
|
370
|
+
bus_postfix = f"[{msb}:{lsb}]"
|
|
371
|
+
signal_decls.append(f"{info['direction']}{bus_postfix} {name}")
|
|
372
|
+
|
|
373
|
+
# Write module
|
|
374
|
+
print("// Auto-generated by LibreLane", file=f)
|
|
375
|
+
print(f"module {design_name}(", file=f)
|
|
376
|
+
last_pos = f.tell()
|
|
377
|
+
if power_define is not None:
|
|
378
|
+
print(f"`ifdef {power_define}", file=f)
|
|
379
|
+
for decl in pg_decls:
|
|
380
|
+
print(f" {decl}", file=f, end="")
|
|
381
|
+
last_pos = f.tell()
|
|
382
|
+
print(",", file=f)
|
|
383
|
+
print("`endif", file=f)
|
|
384
|
+
for decl in signal_decls:
|
|
385
|
+
print(f" {decl}", file=f, end="")
|
|
386
|
+
last_pos = f.tell()
|
|
387
|
+
print(",", file=f)
|
|
388
|
+
f.seek(last_pos) # Overwrite ,\n
|
|
389
|
+
print("\n);\nendmodule", file=f)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
cli.add_command(write_verilog_header)
|
|
393
|
+
|
|
394
|
+
if __name__ == "__main__":
|
|
395
|
+
cli()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2020-2022 Efabless Corporation
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import random
|
|
16
|
+
|
|
17
|
+
from reader import click_odb, click
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def gridify(n, f):
|
|
21
|
+
"""
|
|
22
|
+
e.g., (1.1243, 0.005) -> 1.120
|
|
23
|
+
"""
|
|
24
|
+
return round(n / f) * f
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
@click_odb
|
|
29
|
+
def random_place(reader):
|
|
30
|
+
core_area = reader.block.getCoreArea()
|
|
31
|
+
LLX, LLY = core_area.ll()
|
|
32
|
+
URX, URY = core_area.ur()
|
|
33
|
+
insts = reader.block.getInsts()
|
|
34
|
+
|
|
35
|
+
print("Design name:", reader.name)
|
|
36
|
+
print("Core Area Boundaries:", LLX, LLY, URX, URY)
|
|
37
|
+
print("Number of instances", len(insts))
|
|
38
|
+
|
|
39
|
+
placed_cnt = 0
|
|
40
|
+
for inst in insts:
|
|
41
|
+
if inst.isFixed():
|
|
42
|
+
continue
|
|
43
|
+
master = inst.getMaster()
|
|
44
|
+
master_width = master.getWidth()
|
|
45
|
+
master_height = master.getHeight()
|
|
46
|
+
x = gridify(random.randint(LLX, max(LLX, URX - master_width)), 5)
|
|
47
|
+
y = gridify(random.randint(LLY, max(LLY, URY - master_height)), 5)
|
|
48
|
+
inst.setLocation(x, y)
|
|
49
|
+
inst.setPlacementStatus("PLACED")
|
|
50
|
+
|
|
51
|
+
placed_cnt += 1
|
|
52
|
+
|
|
53
|
+
print(f"Placed {placed_cnt} instances.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
random_place()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Copyright 2021-2022 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
|
+
# flake8: noqa E402
|
|
15
|
+
import odb
|
|
16
|
+
from openroad import Tech, Design
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import json
|
|
21
|
+
import locale
|
|
22
|
+
import inspect
|
|
23
|
+
import functools
|
|
24
|
+
from decimal import Decimal
|
|
25
|
+
from fnmatch import fnmatch
|
|
26
|
+
from typing import Callable, Dict
|
|
27
|
+
|
|
28
|
+
# -- START: Environment Fixes
|
|
29
|
+
try:
|
|
30
|
+
locale.setlocale(locale.LC_ALL, "C.UTF-8")
|
|
31
|
+
except locale.Error:
|
|
32
|
+
# We tried. :)
|
|
33
|
+
pass
|
|
34
|
+
# -- END
|
|
35
|
+
|
|
36
|
+
import click
|
|
37
|
+
import rich
|
|
38
|
+
from rich.table import Table
|
|
39
|
+
|
|
40
|
+
# Re-export for subfunctions
|
|
41
|
+
rich
|
|
42
|
+
click
|
|
43
|
+
Table
|
|
44
|
+
odb
|
|
45
|
+
|
|
46
|
+
write_fn: Dict[str, Callable] = {
|
|
47
|
+
"def": lambda reader, file: file and reader.design.writeDef(file),
|
|
48
|
+
"odb": lambda reader, file: file and reader.design.writeDb(file),
|
|
49
|
+
}
|
|
50
|
+
auto_handled_output_opts = [f"output_{key}" for key in write_fn]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OdbReader(object):
|
|
54
|
+
def __init__(self, *args, **kwargs):
|
|
55
|
+
self.ord_tech = Tech()
|
|
56
|
+
self.design = Design(self.ord_tech)
|
|
57
|
+
|
|
58
|
+
if len(args) == 1:
|
|
59
|
+
db_in = args[0]
|
|
60
|
+
self.design.readDb(db_in)
|
|
61
|
+
elif len(args) == 2:
|
|
62
|
+
lef_in, def_in = args
|
|
63
|
+
if not (isinstance(lef_in, list) or isinstance(lef_in, tuple)):
|
|
64
|
+
lef_in = [lef_in]
|
|
65
|
+
for lef in lef_in:
|
|
66
|
+
self.ord_tech.readLef(lef)
|
|
67
|
+
if def_in is not None:
|
|
68
|
+
self.design.readDef(def_in)
|
|
69
|
+
|
|
70
|
+
self.config = None
|
|
71
|
+
if "config_path" in kwargs and kwargs["config_path"] is not None:
|
|
72
|
+
self.config = json.load(
|
|
73
|
+
open(kwargs["config_path"], encoding="utf8"),
|
|
74
|
+
parse_float=Decimal,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
self.db = self.ord_tech.getDB()
|
|
78
|
+
self.tech = self.db.getTech()
|
|
79
|
+
self.chip = self.db.getChip()
|
|
80
|
+
self.layers = {l.getName(): l for l in self.tech.getLayers()}
|
|
81
|
+
self.libs = self.db.getLibs()
|
|
82
|
+
self.cells = {}
|
|
83
|
+
for lib in self.libs:
|
|
84
|
+
self.cells.update({m: m for m in lib.getMasters()})
|
|
85
|
+
if self.chip is not None:
|
|
86
|
+
self.block = self.db.getChip().getBlock()
|
|
87
|
+
self.name = self.block.getName()
|
|
88
|
+
self.rows = self.block.getRows()
|
|
89
|
+
self.dbunits = self.block.getDefUnits()
|
|
90
|
+
self.instances = self.block.getInsts()
|
|
91
|
+
|
|
92
|
+
busbitchars = re.escape("[]") # TODO: Get alternatives from LEF parser
|
|
93
|
+
# dividerchar = re.escape("/") # TODO: Get alternatives from LEF parser
|
|
94
|
+
self.escape_verilog_rx = re.compile(rf"([{busbitchars}])")
|
|
95
|
+
|
|
96
|
+
def add_lef(self, new_lef):
|
|
97
|
+
self.ord_tech.readLef(new_lef)
|
|
98
|
+
|
|
99
|
+
def escape_verilog_name(self, name_in: str) -> str:
|
|
100
|
+
return self.escape_verilog_rx.sub(r"\\\1", name_in)
|
|
101
|
+
|
|
102
|
+
def _dpl(self):
|
|
103
|
+
"""
|
|
104
|
+
The ``._dpl()`` method is EXPERIMENTAL and SHOULD NOT BE USED YET.
|
|
105
|
+
|
|
106
|
+
Use a composite step with ``OpenROAD.DetailedPlacement``.
|
|
107
|
+
"""
|
|
108
|
+
if self.config is None:
|
|
109
|
+
raise RuntimeError("Attempted to call dpl without config file")
|
|
110
|
+
|
|
111
|
+
cell_pad_value = int(self.config["DPL_CELL_PADDING"])
|
|
112
|
+
cell_pad_side = cell_pad_value // 2
|
|
113
|
+
dpl = self.design.getOpendp()
|
|
114
|
+
dpl.setPaddingGlobal(cell_pad_side, cell_pad_side)
|
|
115
|
+
if wildcards := self.config["CELL_PAD_EXCLUDE"]:
|
|
116
|
+
for wildcard in wildcards:
|
|
117
|
+
masters = [
|
|
118
|
+
self.cells[name] for name in self.cells if fnmatch(name, wildcard)
|
|
119
|
+
]
|
|
120
|
+
for master in masters:
|
|
121
|
+
dpl.setPadding(master, 0, 0)
|
|
122
|
+
if diode_padding := self.config["DIODE_PADDING"]:
|
|
123
|
+
name, _ = self.config["DIODE_CELL"].split("/")
|
|
124
|
+
master = self.cells[name]
|
|
125
|
+
dpl.setPadding(master, int(diode_padding), 0)
|
|
126
|
+
|
|
127
|
+
dpl.detailedPlacement(
|
|
128
|
+
self.config["PL_MAX_DISPLACEMENT_X"],
|
|
129
|
+
self.config["PL_MAX_DISPLACEMENT_Y"],
|
|
130
|
+
)
|
|
131
|
+
dpl.reportLegalizationStats()
|
|
132
|
+
dpl.optimizeMirroring()
|
|
133
|
+
|
|
134
|
+
def _grt(self):
|
|
135
|
+
"""
|
|
136
|
+
The ``._grt()`` method is EXPERIMENTAL and SHOULD NOT BE USED YET.
|
|
137
|
+
|
|
138
|
+
Use a composite step with ``OpenROAD.GlobalRouting``.
|
|
139
|
+
"""
|
|
140
|
+
if self.config is None:
|
|
141
|
+
raise RuntimeError("Attempted to call grt without config file")
|
|
142
|
+
|
|
143
|
+
grt = self.design.getGlobalRouter()
|
|
144
|
+
routing_layers = [l for l in self.layers.values() if l.getRoutingLevel() >= 1]
|
|
145
|
+
for layer, adj in zip(routing_layers, self.config["GRT_LAYER_ADJUSTMENTS"]):
|
|
146
|
+
grt.addLayerAdjustment(
|
|
147
|
+
layer.getRoutingLevel(),
|
|
148
|
+
float(adj),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
min_layer_name = self.config["RT_MIN_LAYER"]
|
|
152
|
+
if not min_layer_name in self.layers:
|
|
153
|
+
raise RuntimeError(f"Unknown layer name '{min_layer_name}'")
|
|
154
|
+
min_layer_idx = self.layers[min_layer_name].getRoutingLevel()
|
|
155
|
+
|
|
156
|
+
max_layer_name = self.config["RT_MAX_LAYER"]
|
|
157
|
+
if not max_layer_name in self.layers:
|
|
158
|
+
raise RuntimeError(f"Unknown layer name '{max_layer_name}'")
|
|
159
|
+
max_layer_idx = self.layers[max_layer_name].getRoutingLevel()
|
|
160
|
+
|
|
161
|
+
min_clk_idx = min_layer_idx
|
|
162
|
+
if min_clk_name := self.config["RT_CLOCK_MIN_LAYER"]:
|
|
163
|
+
if not min_clk_name in self.layers:
|
|
164
|
+
raise RuntimeError(f"Unknown layer name '{min_clk_name}'")
|
|
165
|
+
min_clk_idx = self.layers[min_clk_name].getRoutingLevel()
|
|
166
|
+
|
|
167
|
+
max_clk_idx = max_layer_idx
|
|
168
|
+
if max_clk_name := self.config["RT_CLOCK_MAX_LAYER"]:
|
|
169
|
+
if not max_clk_name in self.layers:
|
|
170
|
+
raise RuntimeError(f"Unknown layer name '{max_clk_name}'")
|
|
171
|
+
max_clk_idx = self.layers[max_clk_name].getRoutingLevel()
|
|
172
|
+
|
|
173
|
+
grt.setMinRoutingLayer(min_layer_idx)
|
|
174
|
+
grt.setMaxRoutingLayer(max_layer_idx)
|
|
175
|
+
grt.setMinLayerForClock(min_clk_idx)
|
|
176
|
+
grt.setMaxLayerForClock(max_clk_idx)
|
|
177
|
+
grt.setMacroExtension(self.config["GRT_MACRO_EXTENSION"])
|
|
178
|
+
grt.setOverflowIterations(self.config["GRT_OVERFLOW_ITERS"])
|
|
179
|
+
grt.setAllowCongestion(self.config["GRT_ALLOW_CONGESTION"])
|
|
180
|
+
grt.setVerbose(True)
|
|
181
|
+
grt.globalRoute(
|
|
182
|
+
True
|
|
183
|
+
) # The first variable updates guides- not sure why the default is False
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def click_odb(function):
|
|
187
|
+
@functools.wraps(function)
|
|
188
|
+
def wrapper(input_db, input_lefs, config_path, **kwargs):
|
|
189
|
+
reader = OdbReader(input_db, config_path=config_path)
|
|
190
|
+
|
|
191
|
+
signature = inspect.signature(function)
|
|
192
|
+
parameter_keys = signature.parameters.keys()
|
|
193
|
+
|
|
194
|
+
kwargs = kwargs.copy()
|
|
195
|
+
kwargs["reader"] = reader
|
|
196
|
+
|
|
197
|
+
outputs = []
|
|
198
|
+
for key, value in kwargs.items():
|
|
199
|
+
if key in auto_handled_output_opts:
|
|
200
|
+
id = key[7:]
|
|
201
|
+
outputs.append((id, value))
|
|
202
|
+
|
|
203
|
+
kwargs = {
|
|
204
|
+
k: kwargs[k] for k in kwargs.keys() if not k in auto_handled_output_opts
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if "input_db" in parameter_keys:
|
|
208
|
+
kwargs["input_db"] = input_db
|
|
209
|
+
if "input_lefs" in parameter_keys:
|
|
210
|
+
kwargs["input_lefs"] = input_lefs
|
|
211
|
+
|
|
212
|
+
if input_db.endswith(".def"):
|
|
213
|
+
print(
|
|
214
|
+
"Error: Invocation was not updated to use an odb file.", file=sys.stderr
|
|
215
|
+
)
|
|
216
|
+
exit(1)
|
|
217
|
+
function(**kwargs)
|
|
218
|
+
|
|
219
|
+
for format, path in outputs:
|
|
220
|
+
fn = write_fn[format]
|
|
221
|
+
fn(reader, path)
|
|
222
|
+
|
|
223
|
+
for format in write_fn:
|
|
224
|
+
wrapper = click.option(
|
|
225
|
+
f"--output-{format}",
|
|
226
|
+
default=None,
|
|
227
|
+
help=f"Write {format} view",
|
|
228
|
+
)(wrapper)
|
|
229
|
+
|
|
230
|
+
wrapper = click.option(
|
|
231
|
+
"-l",
|
|
232
|
+
"--input-lef",
|
|
233
|
+
"input_lefs",
|
|
234
|
+
default=(),
|
|
235
|
+
help="LEF file needed to have a proper view of the DEF files",
|
|
236
|
+
multiple=True,
|
|
237
|
+
)(wrapper)
|
|
238
|
+
wrapper = click.option(
|
|
239
|
+
"--step-config",
|
|
240
|
+
"config_path",
|
|
241
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True),
|
|
242
|
+
required=False,
|
|
243
|
+
)(wrapper)
|
|
244
|
+
wrapper = click.argument("input_db")(wrapper)
|
|
245
|
+
|
|
246
|
+
return wrapper
|