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,373 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2022 Efabless Corporation
|
|
3
|
+
# place_diodes Copyright (C) 2020 Sylvain Munaut <tnt@246tNt.com>
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
import odb
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
import click
|
|
21
|
+
import random
|
|
22
|
+
from decimal import Decimal
|
|
23
|
+
from typing import Optional, List
|
|
24
|
+
from reader import click_odb, OdbReader
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group()
|
|
28
|
+
def cli():
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DiodeInserter:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
reader: OdbReader,
|
|
36
|
+
diode_cell: str,
|
|
37
|
+
diode_pin: str,
|
|
38
|
+
threshold_microns: Decimal,
|
|
39
|
+
side_strategy: str = "source",
|
|
40
|
+
port_protect_polarities: Optional[List[str]] = None,
|
|
41
|
+
verbose=False,
|
|
42
|
+
):
|
|
43
|
+
print(f"Using threshold {threshold_microns}µm…")
|
|
44
|
+
|
|
45
|
+
self.reader = reader
|
|
46
|
+
self.block = reader.block
|
|
47
|
+
self.verbose = verbose
|
|
48
|
+
|
|
49
|
+
self.diode_cell = diode_cell
|
|
50
|
+
self.diode_pin = diode_pin
|
|
51
|
+
self.side_strategy = side_strategy
|
|
52
|
+
self.threshold_microns = threshold_microns
|
|
53
|
+
self.port_protect = port_protect_polarities or []
|
|
54
|
+
|
|
55
|
+
self.diode_master = self.block.getDataBase().findMaster(diode_cell)
|
|
56
|
+
self.diode_site = self.diode_master.getSite().getConstName()
|
|
57
|
+
|
|
58
|
+
self.inserted = {}
|
|
59
|
+
self.insts_by_name = {i.getName(): i for i in self.block.getInsts()}
|
|
60
|
+
|
|
61
|
+
def debug(self, msg):
|
|
62
|
+
if self.verbose:
|
|
63
|
+
print(msg, file=sys.stderr)
|
|
64
|
+
|
|
65
|
+
def error(self, msg):
|
|
66
|
+
print(msg, file=sys.stderr)
|
|
67
|
+
|
|
68
|
+
def net_source(self, net):
|
|
69
|
+
# See if it's an input pad
|
|
70
|
+
for bt in net.getBTerms():
|
|
71
|
+
if bt.getIoType() != "INPUT":
|
|
72
|
+
continue
|
|
73
|
+
good, x, y = bt.getFirstPinLocation()
|
|
74
|
+
if good:
|
|
75
|
+
return (x, y)
|
|
76
|
+
|
|
77
|
+
# Or maybe output of a cell
|
|
78
|
+
# x = odb.new_int(0)
|
|
79
|
+
# y = odb.new_int(0)
|
|
80
|
+
|
|
81
|
+
for it in net.getITerms():
|
|
82
|
+
if not it.isOutputSignal():
|
|
83
|
+
continue
|
|
84
|
+
found, x, y = it.getAvgXY()
|
|
85
|
+
if found:
|
|
86
|
+
return x, y
|
|
87
|
+
|
|
88
|
+
# Nothing found
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def should_protect_io_net(self, net, io_types=None):
|
|
92
|
+
for bt in net.getBTerms():
|
|
93
|
+
if (io_types is None) or (bt.getIoType() in io_types):
|
|
94
|
+
return True
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def net_has_diode(self, net, *, silly_verbose=False):
|
|
98
|
+
for it in net.getITerms():
|
|
99
|
+
cell_type = it.getInst().getMaster().getConstName()
|
|
100
|
+
cell_pin = it.getMTerm().getConstName()
|
|
101
|
+
if silly_verbose:
|
|
102
|
+
print(
|
|
103
|
+
f"Net {net.getName()} is connected to {cell_type}/{cell_pin} via {it.getInst().getName()}"
|
|
104
|
+
)
|
|
105
|
+
if (cell_type == self.diode_cell) and (cell_pin == self.diode_pin):
|
|
106
|
+
if silly_verbose:
|
|
107
|
+
print("Found diode!")
|
|
108
|
+
return True
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
def net_manhattan_distance(self, net):
|
|
112
|
+
xs = []
|
|
113
|
+
ys = []
|
|
114
|
+
|
|
115
|
+
for bt in net.getBTerms():
|
|
116
|
+
good, x, y = bt.getFirstPinLocation()
|
|
117
|
+
if good:
|
|
118
|
+
xs.append(x)
|
|
119
|
+
ys.append(y)
|
|
120
|
+
|
|
121
|
+
for it in net.getITerms():
|
|
122
|
+
x, y = self.pin_position(it)
|
|
123
|
+
xs.append(x)
|
|
124
|
+
ys.append(y)
|
|
125
|
+
|
|
126
|
+
if len(xs) == 0:
|
|
127
|
+
return 0
|
|
128
|
+
|
|
129
|
+
return (max(ys) - min(ys)) + (max(xs) - min(xs))
|
|
130
|
+
|
|
131
|
+
def pin_position(self, it):
|
|
132
|
+
# px = odb.new_int(0)
|
|
133
|
+
# py = odb.new_int(0)
|
|
134
|
+
|
|
135
|
+
found, px, py = it.getAvgXY()
|
|
136
|
+
if found:
|
|
137
|
+
# Got it
|
|
138
|
+
return px, py
|
|
139
|
+
else:
|
|
140
|
+
# Failed, use the center coordinate of the instance as fall back
|
|
141
|
+
return it.getInst().getLocation()
|
|
142
|
+
|
|
143
|
+
def place_diode_stdcell(self, it, px, py, src_pos=None):
|
|
144
|
+
# Get information about the instance
|
|
145
|
+
inst_name = it.getInst().getConstName()
|
|
146
|
+
inst_width = it.getInst().getMaster().getWidth()
|
|
147
|
+
inst_pos = it.getInst().getLocation()
|
|
148
|
+
inst_ori = it.getInst().getOrient()
|
|
149
|
+
|
|
150
|
+
# Is the pin left-ish, center-ish or right-ish ?
|
|
151
|
+
pos = None
|
|
152
|
+
|
|
153
|
+
if self.side_strategy == "source":
|
|
154
|
+
# Always be on the side of the source
|
|
155
|
+
if src_pos is not None:
|
|
156
|
+
pos = "l" if (src_pos[0] < inst_pos[0]) else "r"
|
|
157
|
+
|
|
158
|
+
elif self.side_strategy == "pin":
|
|
159
|
+
# Always be on the side of the pin
|
|
160
|
+
pos = "l" if (px < (inst_pos[0] + inst_width // 2)) else "r"
|
|
161
|
+
|
|
162
|
+
elif self.side_strategy == "balanced":
|
|
163
|
+
# If pin is really on the side, use that, else use source side
|
|
164
|
+
th_left = int(inst_pos[0] + inst_width * 0.25)
|
|
165
|
+
th_right = int(inst_pos[0] + inst_width * 0.75)
|
|
166
|
+
|
|
167
|
+
if px < th_left:
|
|
168
|
+
pos = "l"
|
|
169
|
+
elif px > th_right:
|
|
170
|
+
pos = "r"
|
|
171
|
+
elif src_pos is not None:
|
|
172
|
+
# Sort of middle, so put it on the side where signal is coming from
|
|
173
|
+
pos = "l" if (src_pos[0] < inst_pos[0]) else "r"
|
|
174
|
+
|
|
175
|
+
if pos is None:
|
|
176
|
+
# Coin toss ...
|
|
177
|
+
pos = "l" if (random.random() > 0.5) else "r"
|
|
178
|
+
|
|
179
|
+
# X position
|
|
180
|
+
dw = self.diode_master.getWidth()
|
|
181
|
+
|
|
182
|
+
if pos == "l":
|
|
183
|
+
dx = inst_pos[0] - dw * (1 + self.inserted.get((inst_name, "l"), 0))
|
|
184
|
+
else:
|
|
185
|
+
dx = inst_pos[0] + inst_width + dw * self.inserted.get((inst_name, "r"), 0)
|
|
186
|
+
|
|
187
|
+
# Record insertion
|
|
188
|
+
self.inserted[(inst_name, pos)] = self.inserted.get((inst_name, pos), 0) + 1
|
|
189
|
+
|
|
190
|
+
# Done
|
|
191
|
+
return dx, inst_pos[1], inst_ori
|
|
192
|
+
|
|
193
|
+
def place_diode_macro(self, it, px, py, src_pos=None):
|
|
194
|
+
# Scan all rows to see how close we can get to the point
|
|
195
|
+
best = None
|
|
196
|
+
|
|
197
|
+
for row in self.block.getRows():
|
|
198
|
+
rbb = row.getBBox()
|
|
199
|
+
|
|
200
|
+
dx = max(min(rbb.xMax(), px), rbb.xMin())
|
|
201
|
+
dy = rbb.yMin()
|
|
202
|
+
do = row.getOrient()
|
|
203
|
+
|
|
204
|
+
d = abs(px - dx) + abs(py - dy)
|
|
205
|
+
|
|
206
|
+
if (best is None) or (best[0] > d):
|
|
207
|
+
best = (d, dx, dy, do)
|
|
208
|
+
|
|
209
|
+
return best[1:]
|
|
210
|
+
|
|
211
|
+
def insert_diode(self, net, iterm, src_pos):
|
|
212
|
+
# Get information about the instance
|
|
213
|
+
inst = iterm.getInst()
|
|
214
|
+
inst_name = inst.getConstName()
|
|
215
|
+
inst_site = (
|
|
216
|
+
inst.getMaster().getSite().getConstName()
|
|
217
|
+
if (inst.getMaster().getSite() is not None)
|
|
218
|
+
else None
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Find where the pin is
|
|
222
|
+
px, py = self.pin_position(iterm)
|
|
223
|
+
|
|
224
|
+
# Apply standard cell or macro placement ?
|
|
225
|
+
if inst_site == self.diode_site:
|
|
226
|
+
dx, dy, do = self.place_diode_stdcell(iterm, px, py, src_pos)
|
|
227
|
+
else:
|
|
228
|
+
dx, dy, do = self.place_diode_macro(iterm, px, py, src_pos)
|
|
229
|
+
|
|
230
|
+
# Insert instance and wire it up
|
|
231
|
+
base_diode_inst_name = f"ANTENNA_{inst_name}_{iterm.getMTerm().getConstName()}"
|
|
232
|
+
diode_inst_name = base_diode_inst_name
|
|
233
|
+
counter = 0
|
|
234
|
+
while self.insts_by_name.get(diode_inst_name) is not None:
|
|
235
|
+
self.debug(
|
|
236
|
+
f"[d] Net {net.getName()}: diode {diode_inst_name} appears to already exist."
|
|
237
|
+
)
|
|
238
|
+
counter += 1
|
|
239
|
+
diode_inst_name = f"{base_diode_inst_name}_{counter}"
|
|
240
|
+
|
|
241
|
+
diode_inst = odb.dbInst_create(self.block, self.diode_master, diode_inst_name)
|
|
242
|
+
|
|
243
|
+
diode_inst.setOrient(do)
|
|
244
|
+
diode_inst.setLocation(dx, dy)
|
|
245
|
+
diode_inst.setPlacementStatus("PLACED")
|
|
246
|
+
|
|
247
|
+
ait = diode_inst.findITerm(self.diode_pin)
|
|
248
|
+
ait.connect(iterm.getNet())
|
|
249
|
+
|
|
250
|
+
def execute(self):
|
|
251
|
+
# Scan all nets
|
|
252
|
+
for net in self.block.getNets():
|
|
253
|
+
# Skip special nets
|
|
254
|
+
if net.isSpecial():
|
|
255
|
+
self.debug(f"[d] Skipping special net {net.getConstName():s}")
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
# Check if we already have diode on the net
|
|
259
|
+
# if yes, then we assume that the user took care of that some
|
|
260
|
+
# other way
|
|
261
|
+
if self.net_has_diode(net, silly_verbose=False):
|
|
262
|
+
self.debug(f"[d] Skipping already-protected net {net.getConstName():s}")
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
# Find signal source (first one found ...)
|
|
266
|
+
src_pos = self.net_source(net)
|
|
267
|
+
|
|
268
|
+
# Is this an IO we need to protect
|
|
269
|
+
io_protect = None
|
|
270
|
+
if self.should_protect_io_net(net, io_types=["INPUT", "OUTPUT"]):
|
|
271
|
+
io_protect = self.should_protect_io_net(net, io_types=self.port_protect)
|
|
272
|
+
if io_protect:
|
|
273
|
+
self.debug(
|
|
274
|
+
f"[d] Forcing protection diode on I/O net {net.getConstName():s}"
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
self.debug(f"[d] Skipping I/O net {net.getConstName():s}")
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
# Determine the span of the signal and skip small internal nets
|
|
281
|
+
span = self.net_manhattan_distance(net) / self.block.getDbUnitsPerMicron()
|
|
282
|
+
if (span < self.threshold_microns) and not io_protect:
|
|
283
|
+
if self.threshold_microns != Decimal("Infinity"):
|
|
284
|
+
self.debug(
|
|
285
|
+
f"[d] Skipping small net {net.getConstName():s} ({span:f})"
|
|
286
|
+
)
|
|
287
|
+
continue
|
|
288
|
+
|
|
289
|
+
self.debug(
|
|
290
|
+
f"[d] Inserting diode(s) for net {net.getConstName():s} ({span:f})"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Scan all internal terminals
|
|
294
|
+
if len(net.getITerms()) == 0:
|
|
295
|
+
self.debug(
|
|
296
|
+
f"[d] Skipping net {net.getConstName():s}: not connected to any instances"
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
for iterm in net.getITerms():
|
|
300
|
+
self.insert_diode(net, iterm, src_pos)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@click.command()
|
|
304
|
+
@click.option(
|
|
305
|
+
"-v", "--verbose", default=False, is_flag=True, help="Verbose debug output"
|
|
306
|
+
)
|
|
307
|
+
@click.option(
|
|
308
|
+
"-c",
|
|
309
|
+
"--diode-cell",
|
|
310
|
+
default="sky130_fd_sc_hd__diode_2",
|
|
311
|
+
help="Name of the cell to use as diode",
|
|
312
|
+
)
|
|
313
|
+
@click.option(
|
|
314
|
+
"-p", "--diode-pin", default="DIODE", help="Name of the pin to use on diode cells"
|
|
315
|
+
)
|
|
316
|
+
@click.option(
|
|
317
|
+
"--side-strategy",
|
|
318
|
+
type=click.Choice(["source", "pin", "balanced", "random"]),
|
|
319
|
+
default="source",
|
|
320
|
+
help="Strategy to select if placing diode left/right of the cell",
|
|
321
|
+
)
|
|
322
|
+
@click.option(
|
|
323
|
+
"--port-protect",
|
|
324
|
+
type=click.Choice(["none", "in", "out", "both"]),
|
|
325
|
+
default="in",
|
|
326
|
+
help="Always place a true diode on nets connected to selected ports",
|
|
327
|
+
)
|
|
328
|
+
@click.option(
|
|
329
|
+
"-t",
|
|
330
|
+
"--threshold",
|
|
331
|
+
"threshold_microns",
|
|
332
|
+
type=Decimal,
|
|
333
|
+
default=None,
|
|
334
|
+
help="Minimum Manhattan distance of a net to be considered an antenna risk requiring a diode. By default, the value used is 200 * the minimum site width.",
|
|
335
|
+
)
|
|
336
|
+
@click_odb
|
|
337
|
+
def place(
|
|
338
|
+
reader,
|
|
339
|
+
verbose,
|
|
340
|
+
diode_cell,
|
|
341
|
+
diode_pin,
|
|
342
|
+
side_strategy,
|
|
343
|
+
port_protect,
|
|
344
|
+
threshold_microns,
|
|
345
|
+
):
|
|
346
|
+
print(f"Design name: {reader.name}")
|
|
347
|
+
|
|
348
|
+
pp_val = {
|
|
349
|
+
"none": [],
|
|
350
|
+
"in": ["INPUT"],
|
|
351
|
+
"out": ["OUTPUT"],
|
|
352
|
+
"both": ["INPUT", "OUTPUT"],
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
di = DiodeInserter(
|
|
356
|
+
reader,
|
|
357
|
+
diode_cell=diode_cell,
|
|
358
|
+
diode_pin=diode_pin,
|
|
359
|
+
side_strategy=side_strategy,
|
|
360
|
+
threshold_microns=threshold_microns,
|
|
361
|
+
port_protect_polarities=pp_val[port_protect],
|
|
362
|
+
verbose=verbose,
|
|
363
|
+
)
|
|
364
|
+
di.execute()
|
|
365
|
+
|
|
366
|
+
print("Inserted", len(di.inserted), "diodes.")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
cli.add_command(place)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
if __name__ == "__main__":
|
|
373
|
+
cli()
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Copyright 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
|
+
import sys
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Dict, Literal, Optional, Sequence, Union
|
|
17
|
+
|
|
18
|
+
import odb
|
|
19
|
+
import utl
|
|
20
|
+
|
|
21
|
+
from reader import click, click_odb, OdbReader
|
|
22
|
+
from reader import rich
|
|
23
|
+
from reader import Table
|
|
24
|
+
from rich.console import Console
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_connected(term: Union[odb.dbITerm, odb.dbBTerm]) -> bool:
|
|
28
|
+
if isinstance(term, odb.dbITerm):
|
|
29
|
+
return term.getNet() is not None
|
|
30
|
+
# all bterms have a net, we need to check if it has another
|
|
31
|
+
net = term.getNet()
|
|
32
|
+
iterms = net.getITerms()
|
|
33
|
+
return len(iterms) != 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Port:
|
|
38
|
+
polarity: Literal["INPUT", "OUTPUT", "INOUT"]
|
|
39
|
+
signal_type: Optional[Literal["POWER", "GROUND", "SIGNAL"]]
|
|
40
|
+
connected: bool = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Module(object):
|
|
44
|
+
@dataclass
|
|
45
|
+
class PortStats:
|
|
46
|
+
inputs = 0
|
|
47
|
+
inputs_connected = 0
|
|
48
|
+
outputs = 0
|
|
49
|
+
outputs_connected = 0
|
|
50
|
+
power_inouts = 0
|
|
51
|
+
power_inouts_connected = 0
|
|
52
|
+
ground_inouts = 0
|
|
53
|
+
ground_inouts_connected = 0
|
|
54
|
+
other_inouts = 0
|
|
55
|
+
other_inouts_connected = 0
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_object(
|
|
59
|
+
Self,
|
|
60
|
+
module: "Module",
|
|
61
|
+
):
|
|
62
|
+
result = Self()
|
|
63
|
+
for name, terminal in module.ports.items():
|
|
64
|
+
if terminal.polarity == "INPUT":
|
|
65
|
+
result.inputs += 1
|
|
66
|
+
if terminal.connected:
|
|
67
|
+
result.inputs_connected += 1
|
|
68
|
+
elif terminal.polarity == "OUTPUT":
|
|
69
|
+
result.outputs += 1
|
|
70
|
+
if terminal.connected:
|
|
71
|
+
result.outputs_connected += 1
|
|
72
|
+
elif terminal.polarity == "INOUT":
|
|
73
|
+
if terminal.signal_type == "POWER":
|
|
74
|
+
result.power_inouts += 1
|
|
75
|
+
if terminal.connected:
|
|
76
|
+
result.power_inouts_connected += 1
|
|
77
|
+
elif terminal.signal_type == "GROUND":
|
|
78
|
+
result.ground_inouts += 1
|
|
79
|
+
if terminal.connected:
|
|
80
|
+
result.ground_inouts_connected += 1
|
|
81
|
+
else:
|
|
82
|
+
result.other_inouts += 1
|
|
83
|
+
if terminal.connected:
|
|
84
|
+
result.other_inouts_connected += 1
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def disconnected_pin_count(self) -> int:
|
|
89
|
+
return (
|
|
90
|
+
(self.inputs - self.inputs_connected)
|
|
91
|
+
+ (self.outputs - self.outputs_connected)
|
|
92
|
+
+ (self.power_inouts - self.power_inouts_connected)
|
|
93
|
+
+ (self.ground_inouts - self.ground_inouts_connected)
|
|
94
|
+
+ (self.other_inouts - self.other_inouts_connected)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def top_module_critical_disconnected_pin_count(self) -> int:
|
|
99
|
+
# At least one of each kind needs to be connected, otherwise there
|
|
100
|
+
# are critical disconnects.
|
|
101
|
+
critical_disconnected_pins = 0
|
|
102
|
+
if self.inputs_connected == 0 and self.inputs != 0:
|
|
103
|
+
critical_disconnected_pins += self.inputs
|
|
104
|
+
if self.outputs_connected == 0 and self.outputs != 0:
|
|
105
|
+
critical_disconnected_pins += self.outputs
|
|
106
|
+
if self.power_inouts_connected == 0:
|
|
107
|
+
critical_disconnected_pins += self.power_inouts
|
|
108
|
+
if self.ground_inouts_connected == 0:
|
|
109
|
+
critical_disconnected_pins += self.ground_inouts
|
|
110
|
+
|
|
111
|
+
return critical_disconnected_pins
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def instance_critical_disconnected_pin_count(self):
|
|
115
|
+
critical_disconnected_pins = 0
|
|
116
|
+
if self.inputs_connected != self.inputs:
|
|
117
|
+
critical_disconnected_pins += self.inputs - self.inputs_connected
|
|
118
|
+
elif self.outputs_connected == 0:
|
|
119
|
+
critical_disconnected_pins += self.outputs
|
|
120
|
+
elif self.power_inouts != self.power_inouts_connected:
|
|
121
|
+
critical_disconnected_pins += (
|
|
122
|
+
self.power_inouts - self.power_inouts_connected
|
|
123
|
+
)
|
|
124
|
+
elif self.ground_inouts != self.ground_inouts_connected:
|
|
125
|
+
critical_disconnected_pins += (
|
|
126
|
+
self.ground_inouts - self.ground_inouts_connected
|
|
127
|
+
)
|
|
128
|
+
return critical_disconnected_pins
|
|
129
|
+
|
|
130
|
+
def __init__(self, object: Union[odb.dbBlock, odb.dbInst]) -> None:
|
|
131
|
+
self.name = object.getName()
|
|
132
|
+
self.ports: Dict[str, Port] = {}
|
|
133
|
+
terminals = (
|
|
134
|
+
object.getBTerms()
|
|
135
|
+
if isinstance(object, odb.dbBlock)
|
|
136
|
+
else object.getITerms()
|
|
137
|
+
)
|
|
138
|
+
power_found = False
|
|
139
|
+
ground_found = True
|
|
140
|
+
for terminal in terminals:
|
|
141
|
+
signal_type = terminal.getSigType()
|
|
142
|
+
if signal_type == "POWER":
|
|
143
|
+
power_found = True
|
|
144
|
+
elif signal_type == "GROUND":
|
|
145
|
+
ground_found = True
|
|
146
|
+
self.ports[terminal.getName()] = Port(
|
|
147
|
+
terminal.getIoType(),
|
|
148
|
+
signal_type=terminal.getSigType(),
|
|
149
|
+
connected=is_connected(terminal),
|
|
150
|
+
)
|
|
151
|
+
if not power_found:
|
|
152
|
+
print(
|
|
153
|
+
f"[ERROR] Macro/instance {object.getName()} has no power pins- add it to IGNORE_DISCONNECTED_MODULES if this is intentional",
|
|
154
|
+
file=sys.stderr,
|
|
155
|
+
)
|
|
156
|
+
self.ports["<ANY POWER PIN>"] = Port("INOUT", "POWER", False)
|
|
157
|
+
if not ground_found:
|
|
158
|
+
print(
|
|
159
|
+
f"[ERROR] Macro/instance {object.getName()} has no ground pins- add it to IGNORE_DISCONNECTED_MODULES if this is intentional",
|
|
160
|
+
file=sys.stderr,
|
|
161
|
+
)
|
|
162
|
+
self.ports["<ANY GROUND PIN>"] = Port("INOUT", "GROUND", False)
|
|
163
|
+
self._port_stats = Module.PortStats.from_object(self)
|
|
164
|
+
if self._port_stats.outputs != 0 and self._port_stats.outputs_connected == 0:
|
|
165
|
+
print(
|
|
166
|
+
f"[ERROR] No outputs of macro/instance '{object.getName()}' are connected- add it to IGNORE_DISCONNECTED_MODULES if this is intentional",
|
|
167
|
+
file=sys.stderr,
|
|
168
|
+
)
|
|
169
|
+
if (
|
|
170
|
+
self._port_stats.inputs_connected != self._port_stats.inputs
|
|
171
|
+
and not isinstance(object, odb.dbBlock)
|
|
172
|
+
):
|
|
173
|
+
print(
|
|
174
|
+
f"[ERROR] Some inputs of instance '{object.getName()}' are not connected- add it to IGNORE_DISCONNECTED_MODULES if this is intentional",
|
|
175
|
+
file=sys.stderr,
|
|
176
|
+
)
|
|
177
|
+
self.disconnected_pin_count = self._port_stats.disconnected_pin_count
|
|
178
|
+
self.critical_disconnected_pin_count = (
|
|
179
|
+
self._port_stats.top_module_critical_disconnected_pin_count
|
|
180
|
+
if isinstance(object, odb.dbBlock)
|
|
181
|
+
else self._port_stats.instance_critical_disconnected_pin_count
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def write_disconnected_pins(self, full_table: Table, critical_table: Table):
|
|
185
|
+
if self.disconnected_pin_count == 0:
|
|
186
|
+
return
|
|
187
|
+
row = (
|
|
188
|
+
self.name,
|
|
189
|
+
"\n".join(
|
|
190
|
+
[
|
|
191
|
+
k
|
|
192
|
+
for k, v in self.ports.items()
|
|
193
|
+
if v.signal_type in ["POWER", "GROUND"] and v.connected
|
|
194
|
+
]
|
|
195
|
+
),
|
|
196
|
+
"\n".join(
|
|
197
|
+
[
|
|
198
|
+
k
|
|
199
|
+
for k, v in self.ports.items()
|
|
200
|
+
if v.signal_type in ["POWER", "GROUND"] and not v.connected
|
|
201
|
+
]
|
|
202
|
+
),
|
|
203
|
+
"\n".join(
|
|
204
|
+
[
|
|
205
|
+
k
|
|
206
|
+
for k, v in self.ports.items()
|
|
207
|
+
if v.signal_type == "SIGNAL" and v.connected
|
|
208
|
+
]
|
|
209
|
+
),
|
|
210
|
+
"\n".join(
|
|
211
|
+
[
|
|
212
|
+
k
|
|
213
|
+
for k, v in self.ports.items()
|
|
214
|
+
if v.signal_type == "SIGNAL" and not v.connected
|
|
215
|
+
]
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
full_table.add_row(*row)
|
|
219
|
+
if self.critical_disconnected_pin_count == 0:
|
|
220
|
+
return
|
|
221
|
+
critical_table.add_row(*row)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@click.command()
|
|
225
|
+
@click.option(
|
|
226
|
+
"--write-full-table-to",
|
|
227
|
+
type=click.Path(file_okay=True, dir_okay=False, writable=True),
|
|
228
|
+
default=None,
|
|
229
|
+
help="Write a table with all disconnected pins to this file",
|
|
230
|
+
)
|
|
231
|
+
@click.option(
|
|
232
|
+
"--ignore-module",
|
|
233
|
+
"ignore_modules",
|
|
234
|
+
default=(),
|
|
235
|
+
multiple=True,
|
|
236
|
+
type=str,
|
|
237
|
+
help="Modules to ignore",
|
|
238
|
+
)
|
|
239
|
+
@click_odb
|
|
240
|
+
def main(
|
|
241
|
+
reader: OdbReader,
|
|
242
|
+
ignore_modules: Sequence[str],
|
|
243
|
+
write_full_table_to: Optional[str],
|
|
244
|
+
):
|
|
245
|
+
db = reader.db
|
|
246
|
+
block = db.getChip().getBlock()
|
|
247
|
+
instances = block.getInsts()
|
|
248
|
+
full_table = Table(
|
|
249
|
+
"Macro/Instance",
|
|
250
|
+
"Power Pins",
|
|
251
|
+
"Disconnected",
|
|
252
|
+
"Signal Pins",
|
|
253
|
+
"Disconnected",
|
|
254
|
+
title="",
|
|
255
|
+
show_lines=True,
|
|
256
|
+
)
|
|
257
|
+
critical_table = Table(
|
|
258
|
+
"Macro/Instance",
|
|
259
|
+
"Power Pins",
|
|
260
|
+
"Disconnected",
|
|
261
|
+
"Signal Pins",
|
|
262
|
+
"Disconnected",
|
|
263
|
+
title="",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
disconnected_pin_count, critical_disconnected_pin_count = (0, 0)
|
|
267
|
+
if block.getName() not in ignore_modules:
|
|
268
|
+
block_module = Module(block)
|
|
269
|
+
disconnected_pin_count += block_module.disconnected_pin_count
|
|
270
|
+
critical_disconnected_pin_count += block_module.critical_disconnected_pin_count
|
|
271
|
+
block_module.write_disconnected_pins(full_table, critical_table)
|
|
272
|
+
|
|
273
|
+
for instance in instances:
|
|
274
|
+
if instance.getMaster().getName() in ignore_modules:
|
|
275
|
+
continue
|
|
276
|
+
if instance.getName().startswith("clkload"): # TritonCTS dummy clock loads
|
|
277
|
+
continue
|
|
278
|
+
instance_module = Module(instance)
|
|
279
|
+
disconnected_pin_count += instance_module.disconnected_pin_count
|
|
280
|
+
critical_disconnected_pin_count += (
|
|
281
|
+
instance_module.critical_disconnected_pin_count
|
|
282
|
+
)
|
|
283
|
+
instance_module.write_disconnected_pins(full_table, critical_table)
|
|
284
|
+
|
|
285
|
+
print(
|
|
286
|
+
f"Found {disconnected_pin_count} disconnected pin(s), of which {critical_disconnected_pin_count} are critical."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if critical_table.row_count > 0:
|
|
290
|
+
rich.print(critical_table)
|
|
291
|
+
if full_table.row_count > 0:
|
|
292
|
+
if full_table_path := write_full_table_to:
|
|
293
|
+
console = Console(
|
|
294
|
+
file=open(full_table_path, "w", encoding="utf8"), width=160
|
|
295
|
+
)
|
|
296
|
+
console.print(full_table)
|
|
297
|
+
|
|
298
|
+
utl.metric_integer("design__disconnected_pin__count", disconnected_pin_count)
|
|
299
|
+
utl.metric_integer(
|
|
300
|
+
"design__critical_disconnected_pin__count", critical_disconnected_pin_count
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if __name__ == "__main__":
|
|
305
|
+
main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright 2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
METAL_LAYER_ERROR = 10
|
|
16
|
+
FORMAT_ERROR = 11
|
|
17
|
+
NOT_FOUND_ERROR = 12
|