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.

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