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,573 @@
|
|
|
1
|
+
# Copyright 2021-2022 Efabless Corporation
|
|
2
|
+
# Copyright 2022 Arman Avetisyan
|
|
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
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
from decimal import Decimal
|
|
21
|
+
|
|
22
|
+
from reader import click_odb, click
|
|
23
|
+
from typing import Tuple, List
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.group()
|
|
27
|
+
def cli():
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.command("mark_component_fixed")
|
|
32
|
+
@click.option(
|
|
33
|
+
"-c", "--cell-name", required=True, help="Cell name of the components to mark fixed"
|
|
34
|
+
)
|
|
35
|
+
@click_odb
|
|
36
|
+
def mark_component_fixed(cell_name, reader):
|
|
37
|
+
instances = reader.block.getInsts()
|
|
38
|
+
for instance in instances:
|
|
39
|
+
if instance.getMaster().getName() == cell_name:
|
|
40
|
+
instance.setPlacementStatus("FIRM")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
cli.add_command(mark_component_fixed)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_die_area(def_file, input_lefs):
|
|
47
|
+
die_area_dbu = (-1, -1, -1, -1)
|
|
48
|
+
db = odb.dbDatabase.create()
|
|
49
|
+
for lef in input_lefs:
|
|
50
|
+
odb.read_lef(db, lef)
|
|
51
|
+
odb.read_def(db.getTech(), def_file)
|
|
52
|
+
die_area = db.getChip().getBlock().getDieArea()
|
|
53
|
+
if die_area:
|
|
54
|
+
dbu = db.getChip().getBlock().getDefUnits()
|
|
55
|
+
die_area_dbu = (
|
|
56
|
+
die_area.xMin() / dbu,
|
|
57
|
+
die_area.yMin() / dbu,
|
|
58
|
+
die_area.xMax() / dbu,
|
|
59
|
+
die_area.yMax() / dbu,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return die_area_dbu
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def move_diearea(target_db, input_lefs, template_def):
|
|
66
|
+
source_db = odb.dbDatabase.create()
|
|
67
|
+
|
|
68
|
+
for lef in input_lefs:
|
|
69
|
+
odb.read_lef(source_db, lef)
|
|
70
|
+
odb.read_def(source_db.getTech(), template_def)
|
|
71
|
+
|
|
72
|
+
assert (
|
|
73
|
+
source_db.getTech().getManufacturingGrid()
|
|
74
|
+
== target_db.getTech().getManufacturingGrid()
|
|
75
|
+
)
|
|
76
|
+
assert (
|
|
77
|
+
source_db.getTech().getDbUnitsPerMicron()
|
|
78
|
+
== target_db.getTech().getDbUnitsPerMicron()
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
diearea = source_db.getChip().getBlock().getDieArea()
|
|
82
|
+
output_block = target_db.getChip().getBlock()
|
|
83
|
+
output_block.setDieArea(diearea)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@click.command("move_diearea")
|
|
87
|
+
@click.option("-i", "--template-def", required=True, help="Input DEF")
|
|
88
|
+
@click_odb
|
|
89
|
+
def move_diearea_command(reader, input_lefs, template_def):
|
|
90
|
+
"""
|
|
91
|
+
Move die area from input def to output def
|
|
92
|
+
"""
|
|
93
|
+
move_diearea(reader.db, input_lefs, template_def)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def check_pin_grid(manufacturing_grid, dbu_per_microns, pin_name, pin_coordinate):
|
|
97
|
+
if (pin_coordinate % manufacturing_grid) != 0:
|
|
98
|
+
print(
|
|
99
|
+
f"[ERROR] Pin {pin_name}'s coordinate {pin_coordinate} does not lie on the manufacturing grid.",
|
|
100
|
+
file=sys.stderr,
|
|
101
|
+
) # IDK how to do this
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def relocate_pins(db, input_lefs, template_def, permissive, copy_def_power=False):
|
|
106
|
+
# --------------------------------
|
|
107
|
+
# 1. Find list of all bterms in existing database
|
|
108
|
+
# --------------------------------
|
|
109
|
+
source_db = db
|
|
110
|
+
source_bterms = source_db.getChip().getBlock().getBTerms()
|
|
111
|
+
|
|
112
|
+
manufacturing_grid = source_db.getTech().getManufacturingGrid()
|
|
113
|
+
dbu_per_microns = source_db.getTech().getDbUnitsPerMicron()
|
|
114
|
+
|
|
115
|
+
print(
|
|
116
|
+
f"Using manufacturing grid: {manufacturing_grid}",
|
|
117
|
+
f"Using dbu per mircons: {dbu_per_microns}",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
all_bterm_names = set()
|
|
121
|
+
|
|
122
|
+
for source_bterm in source_bterms:
|
|
123
|
+
source_name = source_bterm.getName()
|
|
124
|
+
# TODO: Check for pin name matches net name
|
|
125
|
+
# print("Bterm", source_name, "is declared as", source_bterm.getSigType())
|
|
126
|
+
|
|
127
|
+
# --------------------------------
|
|
128
|
+
# 3. Check no bterms should be marked as power, because it is assumed that caller already removed them
|
|
129
|
+
# --------------------------------
|
|
130
|
+
sigtype = source_bterm.getSigType()
|
|
131
|
+
if sigtype in ["POWER", "GROUND"]:
|
|
132
|
+
print(
|
|
133
|
+
f"[WARNING] Bterm {source_name} is declared as a '{sigtype}' pin. It will be ignored.",
|
|
134
|
+
file=sys.stderr,
|
|
135
|
+
)
|
|
136
|
+
continue
|
|
137
|
+
all_bterm_names.add(source_name)
|
|
138
|
+
|
|
139
|
+
print(
|
|
140
|
+
f"Found {len(all_bterm_names)} block terminals in existing database...",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# --------------------------------
|
|
144
|
+
# 2. Read the donor def
|
|
145
|
+
# --------------------------------
|
|
146
|
+
template_db = odb.dbDatabase.create()
|
|
147
|
+
for lef in input_lefs:
|
|
148
|
+
odb.read_lef(template_db, lef)
|
|
149
|
+
odb.read_def(template_db.getTech(), template_def)
|
|
150
|
+
template_bterms = template_db.getChip().getBlock().getBTerms()
|
|
151
|
+
|
|
152
|
+
assert (
|
|
153
|
+
source_db.getTech().getManufacturingGrid()
|
|
154
|
+
== template_db.getTech().getManufacturingGrid()
|
|
155
|
+
)
|
|
156
|
+
assert (
|
|
157
|
+
source_db.getTech().getDbUnitsPerMicron()
|
|
158
|
+
== template_db.getTech().getDbUnitsPerMicron()
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# --------------------------------
|
|
162
|
+
# 3. Create a dict with net -> pin locations.
|
|
163
|
+
# --------------------------------
|
|
164
|
+
template_bterm_locations = dict()
|
|
165
|
+
|
|
166
|
+
for template_bterm in template_bterms:
|
|
167
|
+
template_name = template_bterm.getName()
|
|
168
|
+
template_pins = template_bterm.getBPins()
|
|
169
|
+
|
|
170
|
+
# TODO: Check for pin name matches net name
|
|
171
|
+
for template_pin in template_pins:
|
|
172
|
+
boxes = template_pin.getBoxes()
|
|
173
|
+
|
|
174
|
+
for box in boxes:
|
|
175
|
+
layer = box.getTechLayer().getName()
|
|
176
|
+
if template_name not in template_bterm_locations:
|
|
177
|
+
template_bterm_locations[template_name] = []
|
|
178
|
+
template_bterm_locations[template_name].append(
|
|
179
|
+
(
|
|
180
|
+
layer,
|
|
181
|
+
box.xMin(),
|
|
182
|
+
box.yMin(),
|
|
183
|
+
box.xMax(),
|
|
184
|
+
box.yMax(),
|
|
185
|
+
template_pin.getPlacementStatus(),
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
template_bterm_names = set(
|
|
190
|
+
[
|
|
191
|
+
bterm.getName()
|
|
192
|
+
for bterm in template_bterms
|
|
193
|
+
if bterm.getSigType() not in ["POWER", "GROUND"]
|
|
194
|
+
]
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
print(f"Found {len(template_bterm_locations)} template_bterms…")
|
|
198
|
+
|
|
199
|
+
# for name in template_bterm_locations.keys():
|
|
200
|
+
# print(f" * {name}: {template_bterm_locations[name]}")
|
|
201
|
+
|
|
202
|
+
# --------------------------------
|
|
203
|
+
# 4. Modify the pins in out def, according to dict
|
|
204
|
+
# --------------------------------
|
|
205
|
+
output_db = db
|
|
206
|
+
output_tech = output_db.getTech()
|
|
207
|
+
output_block = output_db.getChip().getBlock()
|
|
208
|
+
output_bterms = output_block.getBTerms()
|
|
209
|
+
|
|
210
|
+
if copy_def_power:
|
|
211
|
+
output_bterm_names = set([bterm.getName() for bterm in output_bterms])
|
|
212
|
+
else:
|
|
213
|
+
output_bterm_names = set(
|
|
214
|
+
[
|
|
215
|
+
bterm.getName()
|
|
216
|
+
for bterm in output_bterms
|
|
217
|
+
if bterm.getNet().getSigType() not in ["POWER", "GROUND"]
|
|
218
|
+
]
|
|
219
|
+
)
|
|
220
|
+
not_in_design = template_bterm_names - output_bterm_names
|
|
221
|
+
not_in_template = output_bterm_names - template_bterm_names
|
|
222
|
+
|
|
223
|
+
mismatches_found = False
|
|
224
|
+
for is_in, not_in, pins in [
|
|
225
|
+
("template", "design", not_in_design),
|
|
226
|
+
("design", "template", not_in_template),
|
|
227
|
+
]:
|
|
228
|
+
for name in pins:
|
|
229
|
+
mismatches_found = True
|
|
230
|
+
if permissive:
|
|
231
|
+
print(
|
|
232
|
+
f"[WARNING] {name} not found in {not_in} layout, but found in {is_in} layout.",
|
|
233
|
+
)
|
|
234
|
+
else:
|
|
235
|
+
print(
|
|
236
|
+
f"[ERROR] {name} not found in {not_in} layout, but found in {is_in} layout.",
|
|
237
|
+
file=sys.stderr,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if mismatches_found and not permissive:
|
|
241
|
+
exit(os.EX_DATAERR)
|
|
242
|
+
|
|
243
|
+
if copy_def_power:
|
|
244
|
+
# If asked, we copy power pins from template
|
|
245
|
+
for bterm in template_bterms:
|
|
246
|
+
if bterm.getSigType() not in ["POWER", "GROUND"]:
|
|
247
|
+
continue
|
|
248
|
+
pin_name = bterm.getName()
|
|
249
|
+
pin_net_name = bterm.getNet().getName()
|
|
250
|
+
pin_net = output_block.findNet(pin_net_name)
|
|
251
|
+
if pin_net is None:
|
|
252
|
+
pin_net = odb.dbNet.create(output_block, pin_net_name, True)
|
|
253
|
+
pin_net.setSpecial()
|
|
254
|
+
pin_net.setSigType(bterm.getSigType())
|
|
255
|
+
pin_bterm = odb.dbBTerm.create(pin_net, pin_name)
|
|
256
|
+
pin_bterm.setSigType(bterm.getSigType())
|
|
257
|
+
output_bterms.append(pin_bterm)
|
|
258
|
+
|
|
259
|
+
grid_errors = False
|
|
260
|
+
for output_bterm in output_bterms:
|
|
261
|
+
name = output_bterm.getName()
|
|
262
|
+
output_bpins = output_bterm.getBPins()
|
|
263
|
+
|
|
264
|
+
if name not in template_bterm_locations:
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
if (name not in all_bterm_names) and not copy_def_power:
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
for output_bpin in output_bpins:
|
|
271
|
+
odb.dbBPin.destroy(output_bpin)
|
|
272
|
+
|
|
273
|
+
for template_bterm_location_tuple in template_bterm_locations[name]:
|
|
274
|
+
layer = output_tech.findLayer(template_bterm_location_tuple[0])
|
|
275
|
+
|
|
276
|
+
# --------------------------------
|
|
277
|
+
# 6.2 Create new pin
|
|
278
|
+
# --------------------------------
|
|
279
|
+
|
|
280
|
+
output_new_bpin = odb.dbBPin.create(output_bterm)
|
|
281
|
+
|
|
282
|
+
print(
|
|
283
|
+
f"Wrote pin {name} at layer {layer.getName()} at {template_bterm_location_tuple[1:]}..."
|
|
284
|
+
)
|
|
285
|
+
grid_errors = (
|
|
286
|
+
check_pin_grid(
|
|
287
|
+
manufacturing_grid,
|
|
288
|
+
dbu_per_microns,
|
|
289
|
+
name,
|
|
290
|
+
template_bterm_location_tuple[1],
|
|
291
|
+
)
|
|
292
|
+
or grid_errors
|
|
293
|
+
)
|
|
294
|
+
grid_errors = (
|
|
295
|
+
check_pin_grid(
|
|
296
|
+
manufacturing_grid,
|
|
297
|
+
dbu_per_microns,
|
|
298
|
+
name,
|
|
299
|
+
template_bterm_location_tuple[2],
|
|
300
|
+
)
|
|
301
|
+
or grid_errors
|
|
302
|
+
)
|
|
303
|
+
grid_errors = (
|
|
304
|
+
check_pin_grid(
|
|
305
|
+
manufacturing_grid,
|
|
306
|
+
dbu_per_microns,
|
|
307
|
+
name,
|
|
308
|
+
template_bterm_location_tuple[3],
|
|
309
|
+
)
|
|
310
|
+
or grid_errors
|
|
311
|
+
)
|
|
312
|
+
grid_errors = (
|
|
313
|
+
check_pin_grid(
|
|
314
|
+
manufacturing_grid,
|
|
315
|
+
dbu_per_microns,
|
|
316
|
+
name,
|
|
317
|
+
template_bterm_location_tuple[4],
|
|
318
|
+
)
|
|
319
|
+
or grid_errors
|
|
320
|
+
)
|
|
321
|
+
odb.dbBox.create(
|
|
322
|
+
output_new_bpin,
|
|
323
|
+
layer,
|
|
324
|
+
template_bterm_location_tuple[1],
|
|
325
|
+
template_bterm_location_tuple[2],
|
|
326
|
+
template_bterm_location_tuple[3],
|
|
327
|
+
template_bterm_location_tuple[4],
|
|
328
|
+
)
|
|
329
|
+
output_new_bpin.setPlacementStatus(template_bterm_location_tuple[5])
|
|
330
|
+
|
|
331
|
+
if grid_errors:
|
|
332
|
+
print(
|
|
333
|
+
"[ERROR] Some pins were grid-misaligned. Please check the log.",
|
|
334
|
+
file=sys.stderr,
|
|
335
|
+
)
|
|
336
|
+
exit(os.EX_DATAERR)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@click.command("relocate_pins")
|
|
340
|
+
@click.option(
|
|
341
|
+
"-t",
|
|
342
|
+
"--template-def",
|
|
343
|
+
required=True,
|
|
344
|
+
help="Template DEF to use the locations of pins from.",
|
|
345
|
+
)
|
|
346
|
+
@click_odb
|
|
347
|
+
def relocate_pins_command(reader, input_lefs, template_def):
|
|
348
|
+
"""
|
|
349
|
+
Moves pins that are common between a template_def and the database to the
|
|
350
|
+
location specified in the template_def.
|
|
351
|
+
|
|
352
|
+
Assumptions:
|
|
353
|
+
* The template def lacks power pins.
|
|
354
|
+
* All pins are on metal layers (none on vias.)
|
|
355
|
+
* All pins are rectangular.
|
|
356
|
+
* All pins have unique names.
|
|
357
|
+
* All pin names match the net names in the template DEF.
|
|
358
|
+
"""
|
|
359
|
+
relocate_pins(reader.db, input_lefs, template_def)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
cli.add_command(relocate_pins_command)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@click.command("remove_components")
|
|
366
|
+
@click.option(
|
|
367
|
+
"-m",
|
|
368
|
+
"--match",
|
|
369
|
+
"rx_str",
|
|
370
|
+
default="^.+$",
|
|
371
|
+
help="Regular expression to match for components to be removed. (Default: '^.+$', matches all strings.)",
|
|
372
|
+
)
|
|
373
|
+
@click_odb
|
|
374
|
+
def remove_components(rx_str, reader):
|
|
375
|
+
matcher = re.compile(rx_str)
|
|
376
|
+
instances = reader.block.getInsts()
|
|
377
|
+
for instance in instances:
|
|
378
|
+
name = instance.getName()
|
|
379
|
+
name_m = matcher.search(name)
|
|
380
|
+
if name_m is not None:
|
|
381
|
+
odb.dbInst.destroy(instance)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
cli.add_command(remove_components)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@click.command("remove_nets")
|
|
388
|
+
@click.option(
|
|
389
|
+
"-m",
|
|
390
|
+
"--match",
|
|
391
|
+
"rx_str",
|
|
392
|
+
default="^.+$",
|
|
393
|
+
help="Regular expression to match for nets to be removed. (Default: '^.+$', matches all strings.)",
|
|
394
|
+
)
|
|
395
|
+
@click.option(
|
|
396
|
+
"--empty-only",
|
|
397
|
+
is_flag=True,
|
|
398
|
+
default=False,
|
|
399
|
+
help="Adds a further condition to only remove empty nets (i.e. unconnected nets).",
|
|
400
|
+
)
|
|
401
|
+
@click_odb
|
|
402
|
+
def remove_nets(rx_str, empty_only, reader):
|
|
403
|
+
matcher = re.compile(rx_str)
|
|
404
|
+
nets = reader.block.getNets()
|
|
405
|
+
for net in nets:
|
|
406
|
+
name = net.getName()
|
|
407
|
+
name_m = matcher.match(name)
|
|
408
|
+
if name_m is not None:
|
|
409
|
+
if empty_only and len(net.getITerms()) > 0:
|
|
410
|
+
continue
|
|
411
|
+
# BTerms = PINS, if it has a pin we need to keep the net
|
|
412
|
+
if len(net.getBTerms()) > 0:
|
|
413
|
+
for port in net.getITerms():
|
|
414
|
+
odb.dbITerm.disconnect(port)
|
|
415
|
+
else:
|
|
416
|
+
odb.dbNet.destroy(net)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
cli.add_command(remove_nets)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@click.command("remove_pins")
|
|
423
|
+
@click.option(
|
|
424
|
+
"-m",
|
|
425
|
+
"--match",
|
|
426
|
+
"rx_str",
|
|
427
|
+
default="^.+$",
|
|
428
|
+
help="Regular expression to match for components to be removed. (Default: '^.+$', matches all strings.)",
|
|
429
|
+
)
|
|
430
|
+
@click_odb
|
|
431
|
+
def remove_pins(rx_str, reader):
|
|
432
|
+
matcher = re.compile(rx_str)
|
|
433
|
+
pins = reader.block.getBTerms()
|
|
434
|
+
for pin in pins:
|
|
435
|
+
name = pin.getName()
|
|
436
|
+
name_m = matcher.search(name)
|
|
437
|
+
if name_m is not None:
|
|
438
|
+
odb.dbBTerm.destroy(pin)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
cli.add_command(remove_pins)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@click.command("replace_instance_prefixes")
|
|
445
|
+
@click.option("-f", "--original-prefix", required=True, help="The original prefix.")
|
|
446
|
+
@click.option("-t", "--new-prefix", required=True, help="The new prefix.")
|
|
447
|
+
@click_odb
|
|
448
|
+
def replace_instance_prefixes(original_prefix, new_prefix, reader):
|
|
449
|
+
for instance in reader.block.getInsts():
|
|
450
|
+
name: str = instance.getName()
|
|
451
|
+
if name.startswith(f"{original_prefix}_"):
|
|
452
|
+
new_name = name.replace(f"{original_prefix}_", f"{new_prefix}_")
|
|
453
|
+
instance.rename(new_name)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
cli.add_command(replace_instance_prefixes)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def parse_obstructions(obstructions) -> List[Tuple[str, List[int]]]:
|
|
460
|
+
RE_NUMBER = r"[\-]?[0-9]+(\.[0-9]+)?"
|
|
461
|
+
RE_OBS = (
|
|
462
|
+
r"(?P<layer>\S+)\s+"
|
|
463
|
+
+ r"(?P<bbox>"
|
|
464
|
+
+ RE_NUMBER
|
|
465
|
+
+ r"\s+"
|
|
466
|
+
+ RE_NUMBER
|
|
467
|
+
+ r"\s+"
|
|
468
|
+
+ RE_NUMBER
|
|
469
|
+
+ r"\s+"
|
|
470
|
+
+ RE_NUMBER
|
|
471
|
+
+ r") *$"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
obs_list = []
|
|
475
|
+
for obs in obstructions:
|
|
476
|
+
obs = obs.strip()
|
|
477
|
+
m = re.match(RE_OBS, obs)
|
|
478
|
+
if m is None:
|
|
479
|
+
print(
|
|
480
|
+
f"[ERROR] Incorrectly formatted input {obs}.\n Format: layer llx lly urx ury, ...",
|
|
481
|
+
file=sys.stderr,
|
|
482
|
+
)
|
|
483
|
+
sys.exit(1)
|
|
484
|
+
else:
|
|
485
|
+
layer = m.group("layer")
|
|
486
|
+
bbox = [Decimal(x) for x in m.group("bbox").split()]
|
|
487
|
+
obs_list.append((layer, bbox))
|
|
488
|
+
|
|
489
|
+
return obs_list
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@click.command("add_obstructions")
|
|
493
|
+
@click.option(
|
|
494
|
+
"-O",
|
|
495
|
+
"--obstructions",
|
|
496
|
+
multiple=True,
|
|
497
|
+
required=True,
|
|
498
|
+
help="Format: layer llx lly urx ury, (microns)",
|
|
499
|
+
)
|
|
500
|
+
@click_odb
|
|
501
|
+
def add_obstructions(reader, input_lefs, obstructions):
|
|
502
|
+
obs_list = parse_obstructions(obstructions)
|
|
503
|
+
for obs in obs_list:
|
|
504
|
+
layer = obs[0]
|
|
505
|
+
odb_layer = reader.tech.findLayer(layer)
|
|
506
|
+
if odb_layer is None:
|
|
507
|
+
print(f"[ERROR] Layer '{layer}' not found.", file=sys.stderr)
|
|
508
|
+
sys.exit(1)
|
|
509
|
+
bbox = obs[1]
|
|
510
|
+
dbu = reader.tech.getDbUnitsPerMicron()
|
|
511
|
+
bbox = [int(x * dbu) for x in bbox]
|
|
512
|
+
print(f"Creating an obstruction on {layer} at {bbox} (DBU)…")
|
|
513
|
+
odb.dbObstruction_create(reader.block, reader.tech.findLayer(layer), *bbox)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
cli.add_command(add_obstructions)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@click.command("remove_obstructions")
|
|
520
|
+
@click.option(
|
|
521
|
+
"-O",
|
|
522
|
+
"--obstructions",
|
|
523
|
+
multiple=True,
|
|
524
|
+
required=True,
|
|
525
|
+
help="Format: layer llx lly urx ury, (microns)",
|
|
526
|
+
)
|
|
527
|
+
@click_odb
|
|
528
|
+
def remove_obstructions(reader, input_lefs, obstructions):
|
|
529
|
+
dbu: int = reader.tech.getDbUnitsPerMicron()
|
|
530
|
+
existing_obstructions: List[Tuple[str, List[int], odb.dbObstruction]] = []
|
|
531
|
+
|
|
532
|
+
for odb_obstruction in reader.block.getObstructions():
|
|
533
|
+
bbox = odb_obstruction.getBBox()
|
|
534
|
+
existing_obstructions.append(
|
|
535
|
+
(
|
|
536
|
+
bbox.getTechLayer().getName(),
|
|
537
|
+
[
|
|
538
|
+
bbox.xMin(),
|
|
539
|
+
bbox.yMin(),
|
|
540
|
+
bbox.xMax(),
|
|
541
|
+
bbox.yMax(),
|
|
542
|
+
],
|
|
543
|
+
odb_obstruction,
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
for obs in parse_obstructions(obstructions):
|
|
548
|
+
layer, bbox = obs
|
|
549
|
+
bbox = [int(x * dbu) for x in bbox] # To dbus
|
|
550
|
+
found = False
|
|
551
|
+
if reader.tech.findLayer(layer) is None:
|
|
552
|
+
print(f"[ERROR] Layer '{layer}' not found.", file=sys.stderr)
|
|
553
|
+
sys.exit(1)
|
|
554
|
+
for odb_obstruction in existing_obstructions:
|
|
555
|
+
odb_layer, odb_bbox, odb_obj = odb_obstruction
|
|
556
|
+
if (odb_layer, odb_bbox) == (layer, bbox):
|
|
557
|
+
print(f"Removing obstruction on {layer} at {bbox} (DBU)…")
|
|
558
|
+
found = True
|
|
559
|
+
odb.dbObstruction_destroy(odb_obj)
|
|
560
|
+
if found:
|
|
561
|
+
break
|
|
562
|
+
if not found:
|
|
563
|
+
print(
|
|
564
|
+
f"[ERROR] Obstruction on {layer} at {bbox} (DBU) not found.",
|
|
565
|
+
file=sys.stderr,
|
|
566
|
+
)
|
|
567
|
+
sys.exit(1)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
cli.add_command(remove_obstructions)
|
|
571
|
+
|
|
572
|
+
if __name__ == "__main__":
|
|
573
|
+
cli()
|