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.

Files changed (170) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +479 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +63 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +246 -0
  7. librelane/common/generic_dict.py +319 -0
  8. librelane/common/metrics/__init__.py +35 -0
  9. librelane/common/metrics/__main__.py +413 -0
  10. librelane/common/metrics/library.py +354 -0
  11. librelane/common/metrics/metric.py +186 -0
  12. librelane/common/metrics/util.py +279 -0
  13. librelane/common/misc.py +456 -0
  14. librelane/common/ring_buffer.py +63 -0
  15. librelane/common/tcl.py +80 -0
  16. librelane/common/toolbox.py +549 -0
  17. librelane/common/tpe.py +41 -0
  18. librelane/common/types.py +116 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +155 -0
  21. librelane/config/config.py +1025 -0
  22. librelane/config/flow.py +490 -0
  23. librelane/config/pdk_compat.py +255 -0
  24. librelane/config/preprocessor.py +464 -0
  25. librelane/config/removals.py +45 -0
  26. librelane/config/variable.py +743 -0
  27. librelane/container.py +285 -0
  28. librelane/env_info.py +320 -0
  29. librelane/examples/spm/config.yaml +33 -0
  30. librelane/examples/spm/pin_order.cfg +14 -0
  31. librelane/examples/spm/src/impl.sdc +73 -0
  32. librelane/examples/spm/src/signoff.sdc +68 -0
  33. librelane/examples/spm/src/spm.v +73 -0
  34. librelane/examples/spm/verify/spm_tb.v +106 -0
  35. librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
  36. librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
  37. librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
  38. librelane/examples/spm-user_project_wrapper/config.json +13 -0
  39. librelane/examples/spm-user_project_wrapper/defines.v +66 -0
  40. librelane/examples/spm-user_project_wrapper/template.def +7656 -0
  41. librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
  42. librelane/flows/__init__.py +24 -0
  43. librelane/flows/builtins.py +18 -0
  44. librelane/flows/classic.py +327 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +1049 -0
  47. librelane/flows/misc.py +71 -0
  48. librelane/flows/optimizing.py +179 -0
  49. librelane/flows/sequential.py +367 -0
  50. librelane/flows/synth_explore.py +173 -0
  51. librelane/help/__main__.py +39 -0
  52. librelane/logging/__init__.py +40 -0
  53. librelane/logging/logger.py +323 -0
  54. librelane/open_pdks_rev +1 -0
  55. librelane/plugins.py +21 -0
  56. librelane/py.typed +0 -0
  57. librelane/scripts/base.sdc +80 -0
  58. librelane/scripts/klayout/Readme.md +2 -0
  59. librelane/scripts/klayout/open_design.py +63 -0
  60. librelane/scripts/klayout/render.py +121 -0
  61. librelane/scripts/klayout/stream_out.py +176 -0
  62. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  63. librelane/scripts/klayout/xor.drc +120 -0
  64. librelane/scripts/magic/Readme.md +1 -0
  65. librelane/scripts/magic/common/read.tcl +114 -0
  66. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  67. librelane/scripts/magic/def/mag.tcl +19 -0
  68. librelane/scripts/magic/def/mag_gds.tcl +79 -0
  69. librelane/scripts/magic/drc.tcl +78 -0
  70. librelane/scripts/magic/extract_spice.tcl +98 -0
  71. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  72. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  73. librelane/scripts/magic/gds/extras_mag.tcl +45 -0
  74. librelane/scripts/magic/gds/mag_with_pointers.tcl +31 -0
  75. librelane/scripts/magic/get_bbox.tcl +11 -0
  76. librelane/scripts/magic/lef/extras_maglef.tcl +61 -0
  77. librelane/scripts/magic/lef/maglef.tcl +26 -0
  78. librelane/scripts/magic/lef.tcl +57 -0
  79. librelane/scripts/magic/open.tcl +28 -0
  80. librelane/scripts/magic/wrapper.tcl +21 -0
  81. librelane/scripts/netgen/setup.tcl +28 -0
  82. librelane/scripts/odbpy/apply_def_template.py +49 -0
  83. librelane/scripts/odbpy/cell_frequency.py +107 -0
  84. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  85. librelane/scripts/odbpy/contextualize.py +109 -0
  86. librelane/scripts/odbpy/defutil.py +573 -0
  87. librelane/scripts/odbpy/diodes.py +373 -0
  88. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  89. librelane/scripts/odbpy/eco_buffer.py +181 -0
  90. librelane/scripts/odbpy/eco_diode.py +139 -0
  91. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  92. librelane/scripts/odbpy/io_place.py +482 -0
  93. librelane/scripts/odbpy/ioplace_parser/__init__.py +23 -0
  94. librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
  95. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  96. librelane/scripts/odbpy/lefutil.py +97 -0
  97. librelane/scripts/odbpy/placers.py +162 -0
  98. librelane/scripts/odbpy/power_utils.py +397 -0
  99. librelane/scripts/odbpy/random_place.py +57 -0
  100. librelane/scripts/odbpy/reader.py +250 -0
  101. librelane/scripts/odbpy/remove_buffers.py +173 -0
  102. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  103. librelane/scripts/odbpy/wire_lengths.py +93 -0
  104. librelane/scripts/openroad/antenna_check.tcl +20 -0
  105. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  106. librelane/scripts/openroad/basic_mp.tcl +24 -0
  107. librelane/scripts/openroad/buffer_list.tcl +10 -0
  108. librelane/scripts/openroad/common/dpl.tcl +24 -0
  109. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  110. librelane/scripts/openroad/common/grt.tcl +32 -0
  111. librelane/scripts/openroad/common/io.tcl +540 -0
  112. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  113. librelane/scripts/openroad/common/resizer.tcl +103 -0
  114. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  115. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  116. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  117. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  118. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  119. librelane/scripts/openroad/cts.tcl +80 -0
  120. librelane/scripts/openroad/cut_rows.tcl +24 -0
  121. librelane/scripts/openroad/dpl.tcl +24 -0
  122. librelane/scripts/openroad/drt.tcl +37 -0
  123. librelane/scripts/openroad/fill.tcl +30 -0
  124. librelane/scripts/openroad/floorplan.tcl +145 -0
  125. librelane/scripts/openroad/gpl.tcl +88 -0
  126. librelane/scripts/openroad/grt.tcl +30 -0
  127. librelane/scripts/openroad/gui.tcl +37 -0
  128. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  129. librelane/scripts/openroad/ioplacer.tcl +67 -0
  130. librelane/scripts/openroad/irdrop.tcl +51 -0
  131. librelane/scripts/openroad/pdn.tcl +52 -0
  132. librelane/scripts/openroad/rcx.tcl +32 -0
  133. librelane/scripts/openroad/repair_design.tcl +70 -0
  134. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  135. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  136. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  137. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  138. librelane/scripts/openroad/sta/corner.tcl +393 -0
  139. librelane/scripts/openroad/tapcell.tcl +25 -0
  140. librelane/scripts/openroad/write_views.tcl +27 -0
  141. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  142. librelane/scripts/pyosys/json_header.py +84 -0
  143. librelane/scripts/pyosys/synthesize.py +493 -0
  144. librelane/scripts/pyosys/ys_common.py +153 -0
  145. librelane/scripts/tclsh/hello.tcl +1 -0
  146. librelane/state/__init__.py +24 -0
  147. librelane/state/__main__.py +61 -0
  148. librelane/state/design_format.py +195 -0
  149. librelane/state/state.py +359 -0
  150. librelane/steps/__init__.py +61 -0
  151. librelane/steps/__main__.py +510 -0
  152. librelane/steps/checker.py +637 -0
  153. librelane/steps/common_variables.py +340 -0
  154. librelane/steps/cvc_rv.py +169 -0
  155. librelane/steps/klayout.py +509 -0
  156. librelane/steps/magic.py +576 -0
  157. librelane/steps/misc.py +160 -0
  158. librelane/steps/netgen.py +253 -0
  159. librelane/steps/odb.py +1088 -0
  160. librelane/steps/openroad.py +2460 -0
  161. librelane/steps/openroad_alerts.py +102 -0
  162. librelane/steps/pyosys.py +640 -0
  163. librelane/steps/step.py +1571 -0
  164. librelane/steps/tclstep.py +288 -0
  165. librelane/steps/verilator.py +222 -0
  166. librelane/steps/yosys.py +371 -0
  167. librelane-2.4.0.dist-info/METADATA +169 -0
  168. librelane-2.4.0.dist-info/RECORD +170 -0
  169. librelane-2.4.0.dist-info/WHEEL +4 -0
  170. 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()