librelane 2.4.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of librelane might be problematic. Click here for more details.
- librelane/__init__.py +38 -0
- librelane/__main__.py +470 -0
- librelane/__version__.py +43 -0
- librelane/common/__init__.py +61 -0
- librelane/common/cli.py +75 -0
- librelane/common/drc.py +245 -0
- librelane/common/generic_dict.py +319 -0
- librelane/common/metrics/__init__.py +35 -0
- librelane/common/metrics/__main__.py +413 -0
- librelane/common/metrics/library.py +354 -0
- librelane/common/metrics/metric.py +186 -0
- librelane/common/metrics/util.py +279 -0
- librelane/common/misc.py +402 -0
- librelane/common/ring_buffer.py +63 -0
- librelane/common/tcl.py +80 -0
- librelane/common/toolbox.py +549 -0
- librelane/common/tpe.py +41 -0
- librelane/common/types.py +117 -0
- librelane/config/__init__.py +32 -0
- librelane/config/__main__.py +158 -0
- librelane/config/config.py +1025 -0
- librelane/config/flow.py +490 -0
- librelane/config/pdk_compat.py +255 -0
- librelane/config/preprocessor.py +464 -0
- librelane/config/removals.py +45 -0
- librelane/config/variable.py +722 -0
- librelane/container.py +264 -0
- librelane/env_info.py +306 -0
- librelane/examples/spm/config.yaml +33 -0
- librelane/examples/spm/pin_order.cfg +14 -0
- librelane/examples/spm/src/impl.sdc +73 -0
- librelane/examples/spm/src/signoff.sdc +68 -0
- librelane/examples/spm/src/spm.v +73 -0
- librelane/examples/spm/verify/spm_tb.v +106 -0
- librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
- librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
- librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
- librelane/examples/spm-user_project_wrapper/config.json +13 -0
- librelane/examples/spm-user_project_wrapper/defines.v +66 -0
- librelane/examples/spm-user_project_wrapper/template.def +7656 -0
- librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
- librelane/flows/__init__.py +24 -0
- librelane/flows/builtins.py +18 -0
- librelane/flows/classic.py +330 -0
- librelane/flows/cli.py +463 -0
- librelane/flows/flow.py +985 -0
- librelane/flows/misc.py +71 -0
- librelane/flows/optimizing.py +179 -0
- librelane/flows/sequential.py +367 -0
- librelane/flows/synth_explore.py +173 -0
- librelane/logging/__init__.py +40 -0
- librelane/logging/logger.py +323 -0
- librelane/open_pdks_rev +1 -0
- librelane/plugins.py +21 -0
- librelane/py.typed +0 -0
- librelane/scripts/base.sdc +80 -0
- librelane/scripts/klayout/Readme.md +2 -0
- librelane/scripts/klayout/open_design.py +63 -0
- librelane/scripts/klayout/render.py +121 -0
- librelane/scripts/klayout/stream_out.py +176 -0
- librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
- librelane/scripts/klayout/xor.drc +120 -0
- librelane/scripts/magic/Readme.md +1 -0
- librelane/scripts/magic/common/read.tcl +114 -0
- librelane/scripts/magic/def/antenna_check.tcl +35 -0
- librelane/scripts/magic/def/mag.tcl +19 -0
- librelane/scripts/magic/def/mag_gds.tcl +81 -0
- librelane/scripts/magic/drc.tcl +79 -0
- librelane/scripts/magic/extract_spice.tcl +98 -0
- librelane/scripts/magic/gds/drc_batch.tcl +74 -0
- librelane/scripts/magic/gds/erase_box.tcl +32 -0
- librelane/scripts/magic/gds/extras_mag.tcl +47 -0
- librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
- librelane/scripts/magic/get_bbox.tcl +11 -0
- librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
- librelane/scripts/magic/lef/maglef.tcl +27 -0
- librelane/scripts/magic/lef.tcl +57 -0
- librelane/scripts/magic/open.tcl +28 -0
- librelane/scripts/magic/wrapper.tcl +19 -0
- librelane/scripts/netgen/setup.tcl +28 -0
- librelane/scripts/odbpy/apply_def_template.py +49 -0
- librelane/scripts/odbpy/cell_frequency.py +107 -0
- librelane/scripts/odbpy/check_antenna_properties.py +116 -0
- librelane/scripts/odbpy/contextualize.py +109 -0
- librelane/scripts/odbpy/defutil.py +574 -0
- librelane/scripts/odbpy/diodes.py +373 -0
- librelane/scripts/odbpy/disconnected_pins.py +305 -0
- librelane/scripts/odbpy/exception_codes.py +17 -0
- librelane/scripts/odbpy/filter_unannotated.py +100 -0
- librelane/scripts/odbpy/io_place.py +482 -0
- librelane/scripts/odbpy/label_macro_pins.py +277 -0
- librelane/scripts/odbpy/lefutil.py +97 -0
- librelane/scripts/odbpy/placers.py +162 -0
- librelane/scripts/odbpy/power_utils.py +395 -0
- librelane/scripts/odbpy/random_place.py +57 -0
- librelane/scripts/odbpy/reader.py +246 -0
- librelane/scripts/odbpy/remove_buffers.py +173 -0
- librelane/scripts/odbpy/snap_to_grid.py +57 -0
- librelane/scripts/odbpy/wire_lengths.py +93 -0
- librelane/scripts/openroad/antenna_check.tcl +20 -0
- librelane/scripts/openroad/antenna_repair.tcl +31 -0
- librelane/scripts/openroad/basic_mp.tcl +24 -0
- librelane/scripts/openroad/buffer_list.tcl +10 -0
- librelane/scripts/openroad/common/dpl.tcl +24 -0
- librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
- librelane/scripts/openroad/common/grt.tcl +32 -0
- librelane/scripts/openroad/common/io.tcl +476 -0
- librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
- librelane/scripts/openroad/common/resizer.tcl +103 -0
- librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
- librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
- librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
- librelane/scripts/openroad/common/set_rc.tcl +75 -0
- librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
- librelane/scripts/openroad/cts.tcl +80 -0
- librelane/scripts/openroad/cut_rows.tcl +24 -0
- librelane/scripts/openroad/dpl.tcl +24 -0
- librelane/scripts/openroad/drt.tcl +37 -0
- librelane/scripts/openroad/fill.tcl +30 -0
- librelane/scripts/openroad/floorplan.tcl +145 -0
- librelane/scripts/openroad/gpl.tcl +88 -0
- librelane/scripts/openroad/grt.tcl +30 -0
- librelane/scripts/openroad/gui.tcl +15 -0
- librelane/scripts/openroad/insert_buffer.tcl +127 -0
- librelane/scripts/openroad/ioplacer.tcl +67 -0
- librelane/scripts/openroad/irdrop.tcl +51 -0
- librelane/scripts/openroad/pdn.tcl +52 -0
- librelane/scripts/openroad/rcx.tcl +32 -0
- librelane/scripts/openroad/repair_design.tcl +70 -0
- librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
- librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
- librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
- librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
- librelane/scripts/openroad/sta/corner.tcl +393 -0
- librelane/scripts/openroad/tapcell.tcl +25 -0
- librelane/scripts/openroad/write_views.tcl +27 -0
- librelane/scripts/pyosys/construct_abc_script.py +177 -0
- librelane/scripts/pyosys/json_header.py +84 -0
- librelane/scripts/pyosys/synthesize.py +493 -0
- librelane/scripts/pyosys/ys_common.py +153 -0
- librelane/scripts/tclsh/hello.tcl +1 -0
- librelane/state/__init__.py +24 -0
- librelane/state/__main__.py +61 -0
- librelane/state/design_format.py +180 -0
- librelane/state/state.py +351 -0
- librelane/steps/__init__.py +61 -0
- librelane/steps/__main__.py +511 -0
- librelane/steps/checker.py +637 -0
- librelane/steps/common_variables.py +340 -0
- librelane/steps/cvc_rv.py +169 -0
- librelane/steps/klayout.py +509 -0
- librelane/steps/magic.py +566 -0
- librelane/steps/misc.py +160 -0
- librelane/steps/netgen.py +253 -0
- librelane/steps/odb.py +955 -0
- librelane/steps/openroad.py +2433 -0
- librelane/steps/openroad_alerts.py +102 -0
- librelane/steps/pyosys.py +629 -0
- librelane/steps/step.py +1547 -0
- librelane/steps/tclstep.py +288 -0
- librelane/steps/verilator.py +222 -0
- librelane/steps/yosys.py +371 -0
- librelane-2.4.0.dev0.dist-info/METADATA +151 -0
- librelane-2.4.0.dev0.dist-info/RECORD +166 -0
- librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
- librelane-2.4.0.dev0.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,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()
|