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,100 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright 2023 Efabless Corporation
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this report 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 re
16
+ import pprint
17
+ from collections import namedtuple
18
+
19
+ from reader import click_odb, click
20
+
21
+ import odb
22
+ import utl
23
+
24
+
25
+ def filter_net(net: odb.dbNet) -> bool:
26
+ # wire is the physical implementation of a net.
27
+ # if a net has no wire. there is no problem for it being unannotated
28
+ return net.getWire() is not None
29
+
30
+
31
+ @click.option("--corner")
32
+ @click.option("--checks-report", "checks_report")
33
+ @click.command()
34
+ @click_odb
35
+ def main(reader, corner, checks_report):
36
+ Net = namedtuple("Net", "name bterms")
37
+ BTerm = namedtuple("BTerm", "name type")
38
+
39
+ db = reader.db
40
+ block = db.getChip().getBlock()
41
+ nets = block.getNets()
42
+
43
+ report_content = []
44
+ with open(checks_report, "r") as f:
45
+ report_content = f.readlines()
46
+
47
+ annotation_report_start = "report_parasitic_annotation -report_unannotated\n"
48
+ annotation_report_end = (
49
+ "===========================================================================\n"
50
+ )
51
+ start_index = report_content.index(annotation_report_start)
52
+ end_index = report_content.index(annotation_report_end, start_index)
53
+
54
+ print("Unannotated report:")
55
+ pprint.pprint(report_content[start_index:end_index])
56
+
57
+ # Sample report:
58
+ # Found 324 unannotated drivers.
59
+ # analog_io[0]
60
+ # analog_io[10]
61
+ # analog_io[11]
62
+ # Found 68 partially unannotated drivers.
63
+ # wbs_adr_i[0]
64
+ # mprj/wbs_adr_i[31]
65
+ # wbs_adr_i[10]
66
+ # mprj/wbs_adr_i[21]
67
+ # wbs_adr_i[11]
68
+ # ....
69
+
70
+ reported_nets = [
71
+ line.rstrip().lstrip()
72
+ for line in report_content[start_index:end_index]
73
+ if re.match(r" \S+", line)
74
+ ]
75
+ print("Reported nets:")
76
+ pprint.pprint(reported_nets)
77
+ connected_nets = [
78
+ Net(
79
+ name=net.getName(),
80
+ bterms=[
81
+ BTerm(bterm.getName(), bterm.getIoType()) for bterm in net.getBTerms()
82
+ ],
83
+ )
84
+ for net in nets
85
+ if (net.getName() in reported_nets) and filter_net(net)
86
+ ]
87
+ print("Filtered nets:")
88
+ pprint.pprint(connected_nets)
89
+ utl.metric_integer(
90
+ f"timing__unannotated_net__count__corner:{corner}", len(reported_nets)
91
+ )
92
+ utl.metric_integer(
93
+ f"timing__unannotated_net_filtered__count__corner:{corner}",
94
+ len(connected_nets),
95
+ )
96
+ print("done")
97
+
98
+
99
+ if __name__ == "__main__":
100
+ main()
@@ -0,0 +1,482 @@
1
+ # Copyright 2020-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
+ from functools import partial
15
+ import odb
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+ import math
21
+ import click
22
+ import random
23
+ from decimal import Decimal
24
+
25
+ from reader import click_odb
26
+ import ioplace_parser
27
+
28
+
29
+ def grid_to_tracks(origin, count, step):
30
+ tracks = []
31
+ pos = origin
32
+ for _ in range(count):
33
+ tracks.append(pos)
34
+ pos += step
35
+ assert len(tracks) > 0
36
+ tracks.sort()
37
+
38
+ return tracks
39
+
40
+
41
+ def equally_spaced_sequence(side, side_pin_placement, possible_locations):
42
+ virtual_pin_count = 0
43
+ actual_pin_count = len(side_pin_placement)
44
+ total_pin_count = actual_pin_count + virtual_pin_count
45
+ for i in range(len(side_pin_placement)):
46
+ if isinstance(
47
+ side_pin_placement[i], int
48
+ ): # This is an int value indicating virtual pins
49
+ virtual_pin_count = virtual_pin_count + side_pin_placement[i]
50
+ actual_pin_count = (
51
+ actual_pin_count - 1
52
+ ) # Decrement actual pin count, this value was only there to indicate virtual pin count
53
+ total_pin_count = actual_pin_count + virtual_pin_count
54
+ result = []
55
+ tracks = len(possible_locations)
56
+
57
+ if total_pin_count > tracks:
58
+ print(
59
+ f"[ERROR] The {side} side of the floorplan doesn't have enough slots for all the pins: {total_pin_count} pins/{tracks} slots.",
60
+ file=sys.stderr,
61
+ )
62
+ print(
63
+ "[INFO] Try re-assigning pins to other sides or making the floorplan larger.",
64
+ file=sys.stderr,
65
+ )
66
+ sys.exit(1)
67
+ elif total_pin_count == tracks:
68
+ return possible_locations, side_pin_placement # All positions.
69
+ elif total_pin_count == 0:
70
+ return result, side_pin_placement
71
+
72
+ # From this point, pin_count always < tracks.
73
+ tracks_per_pin = math.floor(tracks / total_pin_count) # >=1
74
+ # O| | | O| | | O| | |
75
+ # tracks_per_pin = 3
76
+ # notice the last two tracks are unused
77
+ # thus:
78
+ used_tracks = tracks_per_pin * (total_pin_count - 1) + 1
79
+ unused_tracks = tracks - used_tracks
80
+
81
+ # Place the pins at those tracks...
82
+ current_track = unused_tracks // 2 # So that the tracks used are centered
83
+ starting_track_index = current_track
84
+ if virtual_pin_count == 0: # No virtual pins
85
+ for _ in range(0, total_pin_count):
86
+ result.append(possible_locations[current_track])
87
+ current_track += tracks_per_pin
88
+ else: # There are virtual pins
89
+ for i in range(len(side_pin_placement)):
90
+ if not isinstance(side_pin_placement[i], int): # We have an actual pin
91
+ result.append(possible_locations[current_track])
92
+ current_track += tracks_per_pin
93
+ else: # Virtual Pins, so just leave their needed spaces
94
+ current_track += tracks_per_pin * side_pin_placement[i]
95
+ side_pin_placement = [
96
+ pin for pin in side_pin_placement if not isinstance(pin, int)
97
+ ] # Remove the virtual pins from the side_pin_placement list
98
+
99
+ print(f"Placement details for the {side} side")
100
+ print("Virtual pin count: ", virtual_pin_count)
101
+ print("Actual pin count: ", actual_pin_count)
102
+ print("Total pin count: ", total_pin_count)
103
+ print("Tracks count: ", len(possible_locations))
104
+ print("Tracks per pin: ", tracks_per_pin)
105
+ print("Used tracks count: ", used_tracks)
106
+ print("Unused track count: ", unused_tracks)
107
+ print("Starting track index: ", starting_track_index)
108
+
109
+ VISUALIZE_PLACEMENT = False
110
+ if VISUALIZE_PLACEMENT:
111
+ print("Placement Map:")
112
+ print("[", end="")
113
+ used_track_indices = []
114
+ for i, location in enumerate(possible_locations):
115
+ if location in result:
116
+ print(f"\033[91m{location}\033[0m, ", end="")
117
+ used_track_indices.append(i)
118
+ else:
119
+ print(f"{location}, ", end="")
120
+ print("]")
121
+ print(f"Indices of used tracks: {used_track_indices}")
122
+ print("---")
123
+
124
+ return result, side_pin_placement
125
+
126
+
127
+ identifiers = re.compile(r"\b[A-Za-z_][A-Za-z_0-9]*\b")
128
+ standalone_numbers = re.compile(r"\b\d+\b")
129
+ trash = re.compile(r"^[^\w\d]+$")
130
+
131
+
132
+ def sorter(bterm, order: ioplace_parser.Order):
133
+ text: str = bterm.getName()
134
+ keys = []
135
+ priority_keys = []
136
+ # tokenize and add to key
137
+ while trash.match(text) is None:
138
+ if match := identifiers.search(text):
139
+ bus = match[0]
140
+ start, end = match.span(0)
141
+ if order == ioplace_parser.Order.busMajor:
142
+ priority_keys.append(bus)
143
+ else:
144
+ keys.append(bus)
145
+ text = text[:start] + text[end + 1 :]
146
+ elif match := standalone_numbers.search(text):
147
+ index = int(match[0])
148
+ if order == ioplace_parser.Order.bitMajor:
149
+ priority_keys.append(index)
150
+ else:
151
+ keys.append(index)
152
+ text = text[: match.pos] + text[match.endpos + 1 :]
153
+ else:
154
+ break
155
+ return [priority_keys, keys]
156
+
157
+
158
+ @click.command()
159
+ @click.option(
160
+ "-u",
161
+ "--unmatched-error",
162
+ type=click.Choice(["none", "unmatched_design", "unmatched_cfg", "both"]),
163
+ default=True,
164
+ help="Treat unmatched pins as error",
165
+ )
166
+ @click.option(
167
+ "-c",
168
+ "--config",
169
+ required=True,
170
+ type=click.Path(
171
+ exists=True,
172
+ file_okay=True,
173
+ dir_okay=False,
174
+ readable=True,
175
+ resolve_path=True,
176
+ ),
177
+ help="Input configuration file",
178
+ )
179
+ @click.option(
180
+ "-v",
181
+ "--ver-length",
182
+ default=None,
183
+ type=float,
184
+ help="Length for pins with N/S orientations in microns.",
185
+ )
186
+ @click.option(
187
+ "-h",
188
+ "--hor-length",
189
+ default=None,
190
+ type=float,
191
+ help="Length for pins with E/S orientations in microns.",
192
+ )
193
+ @click.option(
194
+ "-V",
195
+ "--ver-layer",
196
+ required=True,
197
+ help="Name of metal layer to place vertical pins on.",
198
+ )
199
+ @click.option(
200
+ "-H",
201
+ "--hor-layer",
202
+ required=True,
203
+ help="Name of metal layer to place horizontal pins on.",
204
+ )
205
+ @click.option(
206
+ "--hor-extension",
207
+ default=0,
208
+ type=float,
209
+ help="Extension for vertical pins in microns.",
210
+ )
211
+ @click.option(
212
+ "--ver-extension",
213
+ default=0,
214
+ type=float,
215
+ help="Extension for horizontal pins in microns.",
216
+ )
217
+ @click.option(
218
+ "--ver-width-mult", default=2, type=float, help="Multiplier for vertical pins."
219
+ )
220
+ @click.option(
221
+ "--hor-width-mult", default=2, type=float, help="Multiplier for horizontal pins."
222
+ )
223
+ @click_odb
224
+ def io_place(
225
+ reader,
226
+ config,
227
+ ver_layer,
228
+ hor_layer,
229
+ ver_width_mult,
230
+ hor_width_mult,
231
+ hor_length,
232
+ ver_length,
233
+ hor_extension,
234
+ ver_extension,
235
+ unmatched_error,
236
+ ):
237
+ """
238
+ Places the IOs in an input def with an optional config file that supports regexes.
239
+
240
+ Config format:
241
+ #N|#S|#E|#W
242
+ pin1_regex (low co-ordinates to high co-ordinates; e.g., bottom to top and left to right)
243
+ pin2_regex
244
+ ...
245
+
246
+ #S|#N|#E|#W
247
+ """
248
+ config_file_name = config
249
+ micron_in_units = reader.dbunits
250
+
251
+ H_EXTENSION = int(micron_in_units * hor_extension)
252
+ V_EXTENSION = int(micron_in_units * ver_extension)
253
+
254
+ if H_EXTENSION < 0:
255
+ H_EXTENSION = 0
256
+
257
+ if V_EXTENSION < 0:
258
+ V_EXTENSION = 0
259
+
260
+ H_LAYER = reader.tech.findLayer(hor_layer)
261
+ V_LAYER = reader.tech.findLayer(ver_layer)
262
+
263
+ H_WIDTH = int(Decimal(hor_width_mult) * H_LAYER.getWidth())
264
+ V_WIDTH = int(Decimal(ver_width_mult) * V_LAYER.getWidth())
265
+
266
+ if hor_length is not None:
267
+ H_LENGTH = int(micron_in_units * hor_length)
268
+ else:
269
+ H_LENGTH = max(
270
+ int(
271
+ math.ceil(
272
+ H_LAYER.getArea() * micron_in_units * micron_in_units / H_WIDTH
273
+ )
274
+ ),
275
+ H_WIDTH,
276
+ )
277
+
278
+ if ver_length is not None:
279
+ V_LENGTH = int(micron_in_units * ver_length)
280
+ else:
281
+ V_LENGTH = max(
282
+ int(
283
+ math.ceil(
284
+ V_LAYER.getArea() * micron_in_units * micron_in_units / V_WIDTH
285
+ )
286
+ ),
287
+ V_WIDTH,
288
+ )
289
+
290
+ # read config + calculate minima
291
+ config_file_str = open(config_file_name, "r", encoding="utf8").read()
292
+
293
+ try:
294
+ info_by_side = ioplace_parser.parse(config_file_str)
295
+ except ValueError as e:
296
+ print(f"An exception occurred: {e}")
297
+ exit(os.EX_DATAERR)
298
+
299
+ print("Top-level design name:", reader.name)
300
+
301
+ bterms = [
302
+ bterm
303
+ for bterm in reader.block.getBTerms()
304
+ if bterm.getSigType() not in ["POWER", "GROUND"]
305
+ ]
306
+
307
+ for side, side_info in info_by_side.items():
308
+ min = (
309
+ (V_WIDTH + V_LAYER.getSpacing())
310
+ if side in ["N", "S"]
311
+ else (H_WIDTH + H_LAYER.getSpacing())
312
+ ) / reader.dbunits
313
+ if side_info.min_distance is None:
314
+ side_info.min_distance = min
315
+ if side_info.min_distance < min:
316
+ print(
317
+ f"[WARNING] Overriding minimum distance {side_info.min_distance} with {min} for pins on side {side} to avoid overlap.",
318
+ file=sys.stderr,
319
+ )
320
+ side_info.min_distance = min
321
+
322
+ # build a list of pins
323
+ pin_placement = {"N": [], "E": [], "W": [], "S": []}
324
+
325
+ regex_by_bterm = {}
326
+ unmatched_regexes = set()
327
+ for side, side_info in info_by_side.items():
328
+ for pin in side_info.pins:
329
+ if isinstance(pin, int): # Virtual pins
330
+ pin_placement[side].append(pin)
331
+ continue
332
+
333
+ anchored_regex = f"^{pin}$" # anchor
334
+ matched = False
335
+ collected = []
336
+ for bterm in bterms:
337
+ pin_name = bterm.getName()
338
+ if re.match(anchored_regex, pin_name) is None:
339
+ continue
340
+ if bterm in regex_by_bterm:
341
+ print(
342
+ f"[ERROR] Multiple regexes matched {pin_name}. Those are {regex_by_bterm[bterm]} and {pin}",
343
+ file=sys.stderr,
344
+ )
345
+ sys.exit(os.EX_DATAERR)
346
+ regex_by_bterm[bterm] = pin
347
+ collected.append(bterm)
348
+ matched = True
349
+ collected.sort(key=partial(sorter, order=side_info.sort_mode))
350
+ pin_placement[side] += collected
351
+ if not matched:
352
+ unmatched_regexes.add(pin)
353
+
354
+ # check for extra or missing pins
355
+ not_in_design = unmatched_regexes
356
+ not_in_config = set(
357
+ [bterm.getName() for bterm in bterms if bterm not in regex_by_bterm]
358
+ )
359
+ mismatches_found = False
360
+ for is_in, not_in, pins in [
361
+ ("config", "design", not_in_design),
362
+ ("design", "config", not_in_config),
363
+ ]:
364
+ for name in pins:
365
+ if (
366
+ is_in == "config"
367
+ and (unmatched_error in {"unmatched_cfg", "both"})
368
+ or is_in == "design"
369
+ and (unmatched_error in {"unmatched_design", "both"})
370
+ ):
371
+ mismatches_found = True
372
+ print(
373
+ f"[ERROR] {name} not found in {not_in} but found in {is_in}.",
374
+ file=sys.stderr,
375
+ )
376
+ else:
377
+ print(
378
+ f"[WARNING] {name} not found in {not_in} but found in {is_in}.",
379
+ file=sys.stderr,
380
+ )
381
+
382
+ if mismatches_found:
383
+ print("Critical mismatches found.")
384
+ exit(os.EX_DATAERR)
385
+
386
+ if len(not_in_config) > 0:
387
+ print("Assigning random sides to unmatched pins…")
388
+ for bterm in not_in_config:
389
+ random_side = random.choice(list(pin_placement.keys()))
390
+ pin_placement[random_side].append(bterm)
391
+
392
+ # generate slots
393
+ DIE_AREA = reader.block.getDieArea()
394
+ BLOCK_LL_X = DIE_AREA.xMin()
395
+ BLOCK_LL_Y = DIE_AREA.yMin()
396
+ BLOCK_UR_X = DIE_AREA.xMax()
397
+ BLOCK_UR_Y = DIE_AREA.yMax()
398
+
399
+ print("Block boundaries:", BLOCK_LL_X, BLOCK_LL_Y, BLOCK_UR_X, BLOCK_UR_Y)
400
+
401
+ origin, count, h_step = reader.block.findTrackGrid(H_LAYER).getGridPatternY(0)
402
+ print(f"Horizontal Tracks Origin: {origin}, Count: {count}, Step: {h_step}")
403
+ h_tracks = grid_to_tracks(origin, count, h_step)
404
+
405
+ origin, count, v_step = reader.block.findTrackGrid(V_LAYER).getGridPatternX(0)
406
+ print(f"Vertical Tracks Origin: {origin}, Count: {count}, Step: {v_step}")
407
+ v_tracks = grid_to_tracks(origin, count, v_step)
408
+
409
+ pin_tracks = {}
410
+ for side in pin_placement:
411
+ if side in ["N", "S"]:
412
+ min_distance = info_by_side[side].min_distance * micron_in_units
413
+ pin_tracks[side] = [
414
+ v_tracks[i]
415
+ for i in range(len(v_tracks))
416
+ if (i % (math.ceil(min_distance / v_step))) == 0
417
+ ]
418
+ elif side in ["W", "E"]:
419
+ pin_tracks[side] = [
420
+ h_tracks[i]
421
+ for i in range(len(h_tracks))
422
+ if (
423
+ i
424
+ % (
425
+ math.ceil(
426
+ info_by_side[side].min_distance * micron_in_units / h_step
427
+ )
428
+ )
429
+ )
430
+ == 0
431
+ ]
432
+
433
+ # reversals (including randomly-assigned pins, if needed)
434
+ for side, side_info in info_by_side.items():
435
+ if side_info.reverse_result:
436
+ pin_placement[side].reverse()
437
+
438
+ # create the pins
439
+ for side in pin_placement:
440
+ slots, pin_placement[side] = equally_spaced_sequence(
441
+ side, pin_placement[side], pin_tracks[side]
442
+ )
443
+
444
+ assert len(slots) == len(pin_placement[side])
445
+
446
+ for i in range(len(pin_placement[side])):
447
+ bterm = pin_placement[side][i]
448
+ slot = slots[i]
449
+ pin_name = bterm.getName()
450
+ pins = bterm.getBPins()
451
+ if len(pins) > 0:
452
+ print(
453
+ f"[WARNING] {pin_name} already has shapes. The shapes will be modified.",
454
+ file=sys.stderr,
455
+ )
456
+ assert len(pins) == 1
457
+ pin_bpin = pins[0]
458
+ else:
459
+ pin_bpin = odb.dbBPin_create(bterm)
460
+
461
+ pin_bpin.setPlacementStatus("PLACED")
462
+
463
+ if side in ["N", "S"]:
464
+ rect = odb.Rect(0, 0, V_WIDTH, V_LENGTH + V_EXTENSION)
465
+ if side == "N":
466
+ y = BLOCK_UR_Y - V_LENGTH
467
+ else:
468
+ y = BLOCK_LL_Y - V_EXTENSION
469
+ rect.moveTo(slot - V_WIDTH // 2, y)
470
+ odb.dbBox_create(pin_bpin, V_LAYER, *rect.ll(), *rect.ur())
471
+ else:
472
+ rect = odb.Rect(0, 0, H_LENGTH + H_EXTENSION, H_WIDTH)
473
+ if side == "E":
474
+ x = BLOCK_UR_X - H_LENGTH
475
+ else:
476
+ x = BLOCK_LL_X - H_EXTENSION
477
+ rect.moveTo(x, slot - H_WIDTH // 2)
478
+ odb.dbBox_create(pin_bpin, H_LAYER, *rect.ll(), *rect.ur())
479
+
480
+
481
+ if __name__ == "__main__":
482
+ io_place()