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