librelane 2.4.0.dev0__py3-none-any.whl → 2.4.0.dev3__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/__main__.py +6 -1
- librelane/config/config.py +2 -2
- librelane/config/variable.py +12 -7
- librelane/container.py +1 -1
- librelane/env_info.py +1 -1
- librelane/flows/classic.py +1 -4
- librelane/flows/optimizing.py +1 -1
- librelane/scripts/odbpy/defutil.py +6 -7
- librelane/scripts/odbpy/eco_buffer.py +181 -0
- librelane/scripts/odbpy/eco_diode.py +139 -0
- librelane/scripts/odbpy/{exception_codes.py → ioplace_parser/__init__.py} +11 -5
- librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
- librelane/scripts/odbpy/reader.py +15 -11
- librelane/scripts/openroad/common/io.tcl +66 -2
- librelane/steps/__init__.py +1 -1
- librelane/steps/__main__.py +1 -1
- librelane/steps/odb.py +160 -24
- librelane/steps/openroad.py +7 -2
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/METADATA +28 -9
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/RECORD +22 -19
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/WHEEL +1 -1
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/entry_points.txt +0 -0
librelane/__main__.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# Copyright 2025 The American University in Cairo
|
|
2
|
+
#
|
|
3
|
+
# Adapted from OpenLane
|
|
4
|
+
#
|
|
1
5
|
# Copyright 2023 Efabless Corporation
|
|
2
6
|
#
|
|
3
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -202,7 +206,8 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool):
|
|
|
202
206
|
f"""
|
|
203
207
|
LibreLane v{__version__}
|
|
204
208
|
|
|
205
|
-
Copyright ©2020-
|
|
209
|
+
Copyright ©2020-2025 Efabless Corporation, The American University in
|
|
210
|
+
Cairo, and other contributors.
|
|
206
211
|
|
|
207
212
|
Available under the Apache License, version 2. Included with the source code,
|
|
208
213
|
but you can also get a copy at https://www.apache.org/licenses/LICENSE-2.0
|
librelane/config/config.py
CHANGED
|
@@ -102,7 +102,7 @@ class PassedDirectoryError(ValueError):
|
|
|
102
102
|
def __init__(self, config: AnyPath) -> None:
|
|
103
103
|
self.config = str(config)
|
|
104
104
|
super().__init__(
|
|
105
|
-
"Passing design directories as arguments is unsupported in LibreLane
|
|
105
|
+
"Passing design directories as arguments is unsupported in LibreLane: please pass the configuration file(s) directly."
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
|
|
@@ -960,7 +960,7 @@ class Config(GenericImmutableDict[str, Any]):
|
|
|
960
960
|
pass
|
|
961
961
|
if not isinstance(dis, int) or dis in [1, 2, 5] or dis > 6:
|
|
962
962
|
errors.append(
|
|
963
|
-
f"DIODE_INSERTION_STRATEGY '{dis}' is not available in LibreLane 2 or higher. See 'Migrating DIODE_INSERTION_STRATEGY' in the docs for more info."
|
|
963
|
+
f"DIODE_INSERTION_STRATEGY '{dis}' is not available in LibreLane 2.0 or higher. See 'Migrating DIODE_INSERTION_STRATEGY' in the docs for more info."
|
|
964
964
|
)
|
|
965
965
|
else:
|
|
966
966
|
warnings.append(
|
librelane/config/variable.py
CHANGED
|
@@ -238,7 +238,7 @@ def some_of(t: Type[Any]) -> Type[Any]:
|
|
|
238
238
|
return new_union # type: ignore
|
|
239
239
|
|
|
240
240
|
|
|
241
|
-
def repr_type(t: Type[Any]) -> str: # pragma: no cover
|
|
241
|
+
def repr_type(t: Type[Any], for_document: bool = False) -> str: # pragma: no cover
|
|
242
242
|
optional = is_optional(t)
|
|
243
243
|
some = some_of(t)
|
|
244
244
|
|
|
@@ -247,18 +247,25 @@ def repr_type(t: Type[Any]) -> str: # pragma: no cover
|
|
|
247
247
|
else:
|
|
248
248
|
type_string = str(some)
|
|
249
249
|
|
|
250
|
+
if is_dataclass(t):
|
|
251
|
+
type_string = (
|
|
252
|
+
f"{{class}}`{some.__qualname__} <{some.__module__}.{some.__qualname__}>`"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
separator = "|<br />" if for_document else "|"
|
|
256
|
+
|
|
250
257
|
if inspect.isclass(some) and issubclass(some, Enum):
|
|
251
|
-
type_string =
|
|
258
|
+
type_string = separator.join([str(e.name) for e in some])
|
|
252
259
|
type_string = f"`{type_string}`"
|
|
253
260
|
else:
|
|
254
261
|
origin, args = get_origin(some), get_args(some)
|
|
255
262
|
if origin is not None:
|
|
256
263
|
if origin == Union:
|
|
257
264
|
arg_strings = [repr_type(arg) for arg in args]
|
|
258
|
-
type_string =
|
|
265
|
+
type_string = separator.join(arg_strings)
|
|
259
266
|
type_string = f"({type_string})"
|
|
260
267
|
elif origin == Literal:
|
|
261
|
-
return
|
|
268
|
+
return separator.join([repr(arg) for arg in args])
|
|
262
269
|
else:
|
|
263
270
|
arg_strings = [repr_type(arg) for arg in args]
|
|
264
271
|
type_string = f"{type_string}[{', '.join(arg_strings)}]"
|
|
@@ -377,9 +384,7 @@ class Variable:
|
|
|
377
384
|
for easier wrapping by web browsers/PDF renderers/what have you
|
|
378
385
|
:returns: A pretty Markdown string representation of the Variable's type.
|
|
379
386
|
"""
|
|
380
|
-
|
|
381
|
-
return repr_type(self.type).replace("|", "|<br />")
|
|
382
|
-
return repr_type(self.type)
|
|
387
|
+
return repr_type(self.type, for_document=for_document)
|
|
383
388
|
|
|
384
389
|
def desc_repr_md(self) -> str: # pragma: no cover
|
|
385
390
|
"""
|
librelane/container.py
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
## This file is internal to LibreLane
|
|
15
|
+
## This file is internal to LibreLane and is not part of the API.
|
|
16
16
|
import os
|
|
17
17
|
import re
|
|
18
18
|
import uuid
|
librelane/env_info.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
# environment surveys. Please ensure all code as compatible as possible
|
|
19
19
|
# with ancient versions of Python.
|
|
20
20
|
|
|
21
|
-
## This file is internal to LibreLane
|
|
21
|
+
## This file is internal to LibreLane and is not part of the API.
|
|
22
22
|
import os
|
|
23
23
|
import re
|
|
24
24
|
import sys
|
librelane/flows/classic.py
CHANGED
|
@@ -30,11 +30,8 @@ from ..steps import (
|
|
|
30
30
|
@Flow.factory.register()
|
|
31
31
|
class Classic(SequentialFlow):
|
|
32
32
|
"""
|
|
33
|
-
**Note: While LibreLane 2 has a stable release, the default flow is in beta
|
|
34
|
-
pending silicon validation. Use at your own risk.**
|
|
35
|
-
|
|
36
33
|
A flow of type :class:`librelane.flows.SequentialFlow` that is the most
|
|
37
|
-
similar to the original
|
|
34
|
+
similar to the original OpenLane flow, running the Verilog RTL through
|
|
38
35
|
Yosys, OpenROAD, KLayout and Magic to produce a valid GDSII for simpler designs.
|
|
39
36
|
|
|
40
37
|
This is the default when using LibreLane via the command-line.
|
librelane/flows/optimizing.py
CHANGED
|
@@ -23,7 +23,7 @@ from ..steps import Step, Yosys, OpenROAD, StepError
|
|
|
23
23
|
from ..logging import get_log_level, set_log_level, LogLevels, success, info
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
# "Optimizing" is a custom demo flow to show what's possible with non-sequential Flows in
|
|
26
|
+
# "Optimizing" is a custom demo flow to show what's possible with non-sequential Flows in LibreLan
|
|
27
27
|
# It works across two steps:
|
|
28
28
|
# * The Synthesis Exploration - tries multiple synthesis strategies in *parallel*.
|
|
29
29
|
# The best-performing strategy in terms of minimizing the area makes it to the next stage.
|
|
@@ -21,7 +21,6 @@ from decimal import Decimal
|
|
|
21
21
|
|
|
22
22
|
from reader import click_odb, click
|
|
23
23
|
from typing import Tuple, List
|
|
24
|
-
from exception_codes import METAL_LAYER_ERROR, FORMAT_ERROR, NOT_FOUND_ERROR
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
@click.group()
|
|
@@ -481,7 +480,7 @@ def parse_obstructions(obstructions) -> List[Tuple[str, List[int]]]:
|
|
|
481
480
|
f"[ERROR] Incorrectly formatted input {obs}.\n Format: layer llx lly urx ury, ...",
|
|
482
481
|
file=sys.stderr,
|
|
483
482
|
)
|
|
484
|
-
sys.exit(
|
|
483
|
+
sys.exit(1)
|
|
485
484
|
else:
|
|
486
485
|
layer = m.group("layer")
|
|
487
486
|
bbox = [Decimal(x) for x in m.group("bbox").split()]
|
|
@@ -505,8 +504,8 @@ def add_obstructions(reader, input_lefs, obstructions):
|
|
|
505
504
|
layer = obs[0]
|
|
506
505
|
odb_layer = reader.tech.findLayer(layer)
|
|
507
506
|
if odb_layer is None:
|
|
508
|
-
print(f"[ERROR]
|
|
509
|
-
sys.exit(
|
|
507
|
+
print(f"[ERROR] Layer '{layer}' not found.", file=sys.stderr)
|
|
508
|
+
sys.exit(1)
|
|
510
509
|
bbox = obs[1]
|
|
511
510
|
dbu = reader.tech.getDbUnitsPerMicron()
|
|
512
511
|
bbox = [int(x * dbu) for x in bbox]
|
|
@@ -550,8 +549,8 @@ def remove_obstructions(reader, input_lefs, obstructions):
|
|
|
550
549
|
bbox = [int(x * dbu) for x in bbox] # To dbus
|
|
551
550
|
found = False
|
|
552
551
|
if reader.tech.findLayer(layer) is None:
|
|
553
|
-
print(f"[ERROR]
|
|
554
|
-
sys.exit(
|
|
552
|
+
print(f"[ERROR] Layer '{layer}' not found.", file=sys.stderr)
|
|
553
|
+
sys.exit(1)
|
|
555
554
|
for odb_obstruction in existing_obstructions:
|
|
556
555
|
odb_layer, odb_bbox, odb_obj = odb_obstruction
|
|
557
556
|
if (odb_layer, odb_bbox) == (layer, bbox):
|
|
@@ -565,7 +564,7 @@ def remove_obstructions(reader, input_lefs, obstructions):
|
|
|
565
564
|
f"[ERROR] Obstruction on {layer} at {bbox} (DBU) not found.",
|
|
566
565
|
file=sys.stderr,
|
|
567
566
|
)
|
|
568
|
-
sys.exit(
|
|
567
|
+
sys.exit(1)
|
|
569
568
|
|
|
570
569
|
|
|
571
570
|
cli.add_command(remove_obstructions)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2025 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 sys
|
|
16
|
+
from reader import click_odb, click, odb
|
|
17
|
+
import grt as GRT
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def average_location(instances):
|
|
21
|
+
locations = [instance.getLocation() for instance in instances]
|
|
22
|
+
x_sum = sum(loc[0] for loc in locations)
|
|
23
|
+
y_sum = sum(loc[1] for loc in locations)
|
|
24
|
+
return (x_sum // len(locations), y_sum // len(locations))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
@click_odb
|
|
29
|
+
def cli(reader):
|
|
30
|
+
grt = reader.design.getGlobalRouter()
|
|
31
|
+
dpl = reader.design.getOpendp()
|
|
32
|
+
|
|
33
|
+
insts_to_temporarily_lock_then_unlock_later = []
|
|
34
|
+
for inst in reader.block.getInsts():
|
|
35
|
+
if inst.getPlacementStatus() != "LOCKED":
|
|
36
|
+
insts_to_temporarily_lock_then_unlock_later.append(
|
|
37
|
+
(inst, inst.getPlacementStatus())
|
|
38
|
+
)
|
|
39
|
+
inst.setPlacementStatus("LOCKED")
|
|
40
|
+
|
|
41
|
+
reader._grt_setup(grt)
|
|
42
|
+
|
|
43
|
+
grt_inc = GRT.IncrementalGRoute(grt, reader.block)
|
|
44
|
+
i = 0
|
|
45
|
+
|
|
46
|
+
for target_info in reader.config["INSERT_ECO_BUFFERS"]:
|
|
47
|
+
target_name, target_pin = target_info["target"].split("/")
|
|
48
|
+
name_escaped = reader.escape_verilog_name(target_name)
|
|
49
|
+
buffer_master = target_info["buffer"]
|
|
50
|
+
|
|
51
|
+
master = reader.db.findMaster(buffer_master)
|
|
52
|
+
if master is None:
|
|
53
|
+
print(
|
|
54
|
+
f"[ERROR] Buffer type '{buffer_master}' not found.",
|
|
55
|
+
file=sys.stderr,
|
|
56
|
+
)
|
|
57
|
+
exit(-1)
|
|
58
|
+
|
|
59
|
+
target = reader.block.findInst(name_escaped)
|
|
60
|
+
if target is None:
|
|
61
|
+
print(f"[ERROR] Instance '{target_name}' not found.", file=sys.stderr)
|
|
62
|
+
exit(-1)
|
|
63
|
+
|
|
64
|
+
target_iterm = target.findITerm(target_pin)
|
|
65
|
+
if target_iterm is None:
|
|
66
|
+
print(
|
|
67
|
+
f"[ERROR] Pin '{target_pin}' not found for instance '{target_name}'.",
|
|
68
|
+
file=sys.stderr,
|
|
69
|
+
)
|
|
70
|
+
exit(-1)
|
|
71
|
+
|
|
72
|
+
net = target_iterm.getNet()
|
|
73
|
+
if net is None:
|
|
74
|
+
print(
|
|
75
|
+
f"[ERROR] Net not found on pin '{target_pin}' of instance '{target_name}'.",
|
|
76
|
+
file=sys.stderr,
|
|
77
|
+
)
|
|
78
|
+
exit(-1)
|
|
79
|
+
|
|
80
|
+
new_buf_name = f"eco_buffer_{i}"
|
|
81
|
+
new_net_name = f"eco_buffer_{i}_net"
|
|
82
|
+
while (
|
|
83
|
+
reader.block.findInst(new_buf_name) is not None
|
|
84
|
+
or reader.block.findNet(new_net_name) is not None
|
|
85
|
+
):
|
|
86
|
+
i += 1
|
|
87
|
+
new_buf_name = f"eco_buffer_{i}"
|
|
88
|
+
new_net_name = f"eco_buffer_{i}_net"
|
|
89
|
+
|
|
90
|
+
# Prepare buffer cell, net
|
|
91
|
+
eco_buffer = odb.dbInst.create(reader.block, master, new_buf_name)
|
|
92
|
+
eco_net = odb.dbNet.create(reader.block, new_net_name)
|
|
93
|
+
buffer_iterms = eco_buffer.getITerms()
|
|
94
|
+
buffer_a = None
|
|
95
|
+
for iterm in buffer_iterms:
|
|
96
|
+
if iterm.isInputSignal():
|
|
97
|
+
buffer_a = iterm
|
|
98
|
+
break # Exit loop once input is found
|
|
99
|
+
if buffer_a is None:
|
|
100
|
+
print(
|
|
101
|
+
f"[ERROR] Buffer {buffer_master} has no input signals.",
|
|
102
|
+
file=sys.stderr,
|
|
103
|
+
)
|
|
104
|
+
exit(-1)
|
|
105
|
+
|
|
106
|
+
buffer_x = None
|
|
107
|
+
for iterm in buffer_iterms:
|
|
108
|
+
if iterm.isOutputSignal():
|
|
109
|
+
buffer_x = iterm
|
|
110
|
+
break # Exit loop once output is found
|
|
111
|
+
if buffer_x is None:
|
|
112
|
+
print(
|
|
113
|
+
f"[ERROR] Buffer {buffer_master} has no output signals.",
|
|
114
|
+
file=sys.stderr,
|
|
115
|
+
)
|
|
116
|
+
exit(-1)
|
|
117
|
+
|
|
118
|
+
location_instances = [target]
|
|
119
|
+
net_iterms = net.getITerms()
|
|
120
|
+
if target_iterm.getIoType() == "INPUT":
|
|
121
|
+
driver_iterms = [
|
|
122
|
+
iterm for iterm in net_iterms if iterm.getIoType() in ["OUTPUT"]
|
|
123
|
+
]
|
|
124
|
+
drivers = [iterm.getInst() for iterm in driver_iterms]
|
|
125
|
+
location_instances.extend(drivers)
|
|
126
|
+
|
|
127
|
+
target_iterm.disconnect()
|
|
128
|
+
buffer_a.connect(net)
|
|
129
|
+
buffer_x.connect(eco_net)
|
|
130
|
+
target_iterm.connect(eco_net)
|
|
131
|
+
elif target_iterm.getIoType() == "OUTPUT":
|
|
132
|
+
sink_iterms = [
|
|
133
|
+
iterm for iterm in net_iterms if iterm.getIoType() in ["INPUT", "INOUT"]
|
|
134
|
+
]
|
|
135
|
+
sinks = [iterm.getInst() for iterm in sink_iterms]
|
|
136
|
+
location_instances.extend(sinks)
|
|
137
|
+
|
|
138
|
+
target_iterm.disconnect()
|
|
139
|
+
target_iterm.connect(eco_net)
|
|
140
|
+
buffer_a.connect(eco_net)
|
|
141
|
+
buffer_x.connect(net)
|
|
142
|
+
else:
|
|
143
|
+
print(
|
|
144
|
+
f"[ERROR] {target_name}/{target_pin} is neither an INPUT or an OUTPUT and is unsupported by this script. To buffer an INOUT port, buffer its drivers instead.",
|
|
145
|
+
file=sys.stderr,
|
|
146
|
+
)
|
|
147
|
+
exit(-1)
|
|
148
|
+
|
|
149
|
+
if target_info.get("placement") is not None:
|
|
150
|
+
eco_x, eco_y = target_info["placement"]
|
|
151
|
+
eco_x = reader.block.micronsToDbu(float(eco_x))
|
|
152
|
+
eco_y = reader.block.micronsToDbu(float(eco_y))
|
|
153
|
+
eco_loc = (eco_x, eco_y)
|
|
154
|
+
else:
|
|
155
|
+
eco_loc = average_location(location_instances)
|
|
156
|
+
|
|
157
|
+
eco_buffer.setOrient("R0")
|
|
158
|
+
eco_buffer.setLocation(*eco_loc)
|
|
159
|
+
eco_buffer.setPlacementStatus("PLACED")
|
|
160
|
+
grt.addDirtyNet(net)
|
|
161
|
+
grt.addDirtyNet(eco_net)
|
|
162
|
+
|
|
163
|
+
site = reader.rows[0].getSite()
|
|
164
|
+
max_disp_x = int(
|
|
165
|
+
reader.design.micronToDBU(reader.config["PL_MAX_DISPLACEMENT_X"])
|
|
166
|
+
/ site.getWidth()
|
|
167
|
+
)
|
|
168
|
+
max_disp_y = int(
|
|
169
|
+
reader.design.micronToDBU(reader.config["PL_MAX_DISPLACEMENT_Y"])
|
|
170
|
+
/ site.getHeight()
|
|
171
|
+
)
|
|
172
|
+
dpl.detailedPlacement(max_disp_x, max_disp_y)
|
|
173
|
+
|
|
174
|
+
grt_inc.updateRoutes(True)
|
|
175
|
+
|
|
176
|
+
for inst, previous_status in insts_to_temporarily_lock_then_unlock_later:
|
|
177
|
+
inst.setPlacementStatus(previous_status)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
cli()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2025 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 sys
|
|
16
|
+
from reader import click_odb, click, odb
|
|
17
|
+
import grt as GRT
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command()
|
|
21
|
+
@click_odb
|
|
22
|
+
def cli(reader):
|
|
23
|
+
grt = reader.design.getGlobalRouter()
|
|
24
|
+
dpl = reader.design.getOpendp()
|
|
25
|
+
|
|
26
|
+
insts_to_temporarily_lock_then_unlock_later = []
|
|
27
|
+
for inst in reader.block.getInsts():
|
|
28
|
+
if inst.getPlacementStatus() != "LOCKED":
|
|
29
|
+
insts_to_temporarily_lock_then_unlock_later.append(
|
|
30
|
+
(inst, inst.getPlacementStatus())
|
|
31
|
+
)
|
|
32
|
+
inst.setPlacementStatus("LOCKED")
|
|
33
|
+
|
|
34
|
+
reader._grt_setup(grt)
|
|
35
|
+
|
|
36
|
+
diode_master, diode_pin = reader.config["DIODE_CELL"].split("/")
|
|
37
|
+
|
|
38
|
+
# print(grt)
|
|
39
|
+
grt_inc = GRT.IncrementalGRoute(grt, reader.block)
|
|
40
|
+
i = 0
|
|
41
|
+
for target_info in reader.config["INSERT_ECO_DIODES"]:
|
|
42
|
+
target_name, target_pin = target_info["target"].split("/")
|
|
43
|
+
name_escaped = reader.escape_verilog_name(target_name)
|
|
44
|
+
|
|
45
|
+
target = reader.block.findInst(name_escaped)
|
|
46
|
+
if target is None:
|
|
47
|
+
print(
|
|
48
|
+
f"[ERROR] Instance '{target_name}' not found.",
|
|
49
|
+
file=sys.stderr,
|
|
50
|
+
)
|
|
51
|
+
exit(-1)
|
|
52
|
+
|
|
53
|
+
master = reader.db.findMaster(diode_master)
|
|
54
|
+
if master is None:
|
|
55
|
+
print(
|
|
56
|
+
f"[ERROR] Cell kind '{diode_master}' not found.",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
exit(-1)
|
|
60
|
+
|
|
61
|
+
target_iterm = target.findITerm(target_pin)
|
|
62
|
+
if target_iterm is None:
|
|
63
|
+
print(
|
|
64
|
+
f"[ERROR] Pin '{target_pin}' not found for instance {target_name}.",
|
|
65
|
+
file=sys.stderr,
|
|
66
|
+
)
|
|
67
|
+
exit(-1)
|
|
68
|
+
|
|
69
|
+
if target_iterm.getIoType() not in ["INPUT", "INOUT"]:
|
|
70
|
+
print(
|
|
71
|
+
f"[ERROR] Pin {target_info['target']} is an OUTPUT pin.",
|
|
72
|
+
file=sys.stderr,
|
|
73
|
+
)
|
|
74
|
+
exit(-1)
|
|
75
|
+
|
|
76
|
+
net = target_iterm.getNet()
|
|
77
|
+
if net is None:
|
|
78
|
+
print(
|
|
79
|
+
f"[ERROR] Pin {target_info['target']} has no nets connected.",
|
|
80
|
+
file=sys.stderr,
|
|
81
|
+
)
|
|
82
|
+
exit(-1)
|
|
83
|
+
|
|
84
|
+
eco_diode_name = f"eco_diode_{i}"
|
|
85
|
+
while reader.block.findInst(eco_diode_name) is not None:
|
|
86
|
+
i += 1
|
|
87
|
+
eco_diode_name = f"eco_diode_{i}"
|
|
88
|
+
|
|
89
|
+
eco_diode = odb.dbInst.create(
|
|
90
|
+
reader.block,
|
|
91
|
+
master,
|
|
92
|
+
eco_diode_name,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
diode_iterm = eco_diode.findITerm(diode_pin)
|
|
96
|
+
if diode_iterm is None:
|
|
97
|
+
print(
|
|
98
|
+
f"[ERROR] Pin '{diode_pin}' on ECO diode not found- invalid DIODE_CELL definition.",
|
|
99
|
+
file=sys.stderr,
|
|
100
|
+
)
|
|
101
|
+
exit(-1)
|
|
102
|
+
|
|
103
|
+
sys.stdout.flush()
|
|
104
|
+
|
|
105
|
+
if target_info["placement"] is not None:
|
|
106
|
+
x, y = target_info["placement"]
|
|
107
|
+
x = reader.block.micronsToDbu(float(x))
|
|
108
|
+
y = reader.block.micronsToDbu(float(y))
|
|
109
|
+
else:
|
|
110
|
+
x, y = target.getLocation()
|
|
111
|
+
|
|
112
|
+
eco_diode.setOrient("R0")
|
|
113
|
+
eco_diode.setLocation(x, y)
|
|
114
|
+
eco_diode.setPlacementStatus("PLACED")
|
|
115
|
+
|
|
116
|
+
diode_iterm.connect(net)
|
|
117
|
+
grt.addDirtyNet(net)
|
|
118
|
+
|
|
119
|
+
site = reader.rows[0].getSite()
|
|
120
|
+
max_disp_x = int(
|
|
121
|
+
reader.design.micronToDBU(reader.config["PL_MAX_DISPLACEMENT_X"])
|
|
122
|
+
/ site.getWidth()
|
|
123
|
+
)
|
|
124
|
+
max_disp_y = int(
|
|
125
|
+
reader.design.micronToDBU(reader.config["PL_MAX_DISPLACEMENT_Y"])
|
|
126
|
+
/ site.getHeight()
|
|
127
|
+
)
|
|
128
|
+
dpl.detailedPlacement(max_disp_x, max_disp_y)
|
|
129
|
+
|
|
130
|
+
grt_inc.updateRoutes(True)
|
|
131
|
+
|
|
132
|
+
for inst, previous_status in insts_to_temporarily_lock_then_unlock_later:
|
|
133
|
+
inst.setPlacementStatus(previous_status)
|
|
134
|
+
|
|
135
|
+
reader.design.writeDef("out.def")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
cli()
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 The American University in Cairo
|
|
2
|
+
#
|
|
3
|
+
# Adapted from ioplace_parser
|
|
4
|
+
#
|
|
5
|
+
# Copyright 2020-2023 Efabless Corporation
|
|
2
6
|
#
|
|
3
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
8
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,7 +15,9 @@
|
|
|
11
15
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
16
|
# See the License for the specific language governing permissions and
|
|
13
17
|
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
"""
|
|
19
|
+
This is a modified version of https://github.com/efabless/ioplace_parser that
|
|
20
|
+
does NOT use Antlr4 and instead uses a custom parser that is faster and
|
|
21
|
+
dependency-free.
|
|
22
|
+
"""
|
|
23
|
+
from .parse import Side, Order, parse
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright 2025 The American University in Cairo
|
|
2
|
+
#
|
|
3
|
+
# Adapted from ioplace_parser
|
|
4
|
+
#
|
|
5
|
+
# Copyright 2020-2023 Efabless Corporation
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
import re
|
|
19
|
+
from enum import IntEnum
|
|
20
|
+
from typing import Literal, Optional, Dict, List, Union
|
|
21
|
+
from decimal import Decimal
|
|
22
|
+
import warnings
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Order(IntEnum):
|
|
28
|
+
busMajor = 0
|
|
29
|
+
bitMajor = 1
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Side:
|
|
34
|
+
min_distance: Optional[Decimal] = None
|
|
35
|
+
reverse_result: bool = False
|
|
36
|
+
pins: List[Union[str, int]] = field(default_factory=list)
|
|
37
|
+
sort_mode: Optional[Order] = Order.busMajor
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
VALUE_ANNOTATIONS = ["min_distance"]
|
|
41
|
+
STANDALONE_ANNOTATIONS = [
|
|
42
|
+
"bus_major",
|
|
43
|
+
"bit_major",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse(string: str) -> Dict[Literal["N", "E", "W", "S"], Side]:
|
|
48
|
+
"""
|
|
49
|
+
Parses a pin configuration into a dictionary of the four cardinal sides.
|
|
50
|
+
|
|
51
|
+
:param string: The input configuration as a string (not a file path)
|
|
52
|
+
:returns: A dictionary where each cardinal direction points to a Side object.
|
|
53
|
+
:raises ValueError: On syntax or token recognition errors
|
|
54
|
+
"""
|
|
55
|
+
sides = {}
|
|
56
|
+
current_side: Optional[Side] = None
|
|
57
|
+
global_sort_mode: Order = Order.busMajor
|
|
58
|
+
global_min_distance: Optional[Decimal] = None
|
|
59
|
+
|
|
60
|
+
string_mut = string
|
|
61
|
+
|
|
62
|
+
ws_rx = re.compile(r"^\s+")
|
|
63
|
+
annotation_rx = re.compile(r"^@\s*(\w+)(?:\s*=\s*([0-9]+(?:.[0-9]+)?))?")
|
|
64
|
+
direction_rx = re.compile(r"^#\s*([NEWS]R?|BUS_SORT)")
|
|
65
|
+
virtual_pin_rx = re.compile(r"^\$\s*([0-9]+)")
|
|
66
|
+
non_ws_rx = re.compile(r"^\S+")
|
|
67
|
+
while len(string_mut):
|
|
68
|
+
# annotation
|
|
69
|
+
if skip_match := ws_rx.search(string_mut):
|
|
70
|
+
string_mut = string_mut[skip_match.end() :]
|
|
71
|
+
elif anno_match := annotation_rx.search(string_mut):
|
|
72
|
+
annotation = anno_match[1]
|
|
73
|
+
if annotation in VALUE_ANNOTATIONS:
|
|
74
|
+
if anno_match[2] is None:
|
|
75
|
+
raise ValueError(f"Annotation {annotation} requires a value")
|
|
76
|
+
value = anno_match[2]
|
|
77
|
+
if annotation == "min_distance":
|
|
78
|
+
if current_side is None:
|
|
79
|
+
global_min_distance = Decimal(value)
|
|
80
|
+
else:
|
|
81
|
+
current_side.min_distance = Decimal(value)
|
|
82
|
+
elif annotation in STANDALONE_ANNOTATIONS:
|
|
83
|
+
if anno_match[2] is not None:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Annotation {annotation} cannot be assigned a value"
|
|
86
|
+
)
|
|
87
|
+
if annotation == "bus_major":
|
|
88
|
+
if current_side is None:
|
|
89
|
+
global_sort_mode = Order.busMajor
|
|
90
|
+
else:
|
|
91
|
+
current_side.sort_mode = Order.busMajor
|
|
92
|
+
elif annotation == "bit_major":
|
|
93
|
+
if current_side is None:
|
|
94
|
+
global_sort_mode = Order.bitMajor
|
|
95
|
+
else:
|
|
96
|
+
current_side.sort_mode = Order.bitMajor
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f"Unknown annotation '{annotation}'")
|
|
99
|
+
string_mut = string_mut[anno_match.end() :]
|
|
100
|
+
elif dir_match := direction_rx.search(string_mut):
|
|
101
|
+
direction = dir_match[1]
|
|
102
|
+
if direction == "BUS_SORT":
|
|
103
|
+
warnings.warn(
|
|
104
|
+
"Specifying bit-major using the direction token ('#BUS_SORT') is deprecated: use @bit_major."
|
|
105
|
+
)
|
|
106
|
+
global_sort_mode = Order.bitMajor
|
|
107
|
+
else:
|
|
108
|
+
current_side = Side(
|
|
109
|
+
min_distance=global_min_distance,
|
|
110
|
+
reverse_result=len(direction) == 2,
|
|
111
|
+
sort_mode=global_sort_mode,
|
|
112
|
+
)
|
|
113
|
+
side: Literal["N", "E", "W", "S"] = direction[0] # type: ignore
|
|
114
|
+
sides[side] = current_side
|
|
115
|
+
string_mut = string_mut[dir_match.end() :]
|
|
116
|
+
elif vp_match := virtual_pin_rx.search(string_mut):
|
|
117
|
+
count = int(vp_match[1])
|
|
118
|
+
if current_side is None:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"virtual pin declaration ${count} requires a direction to be set first"
|
|
121
|
+
)
|
|
122
|
+
current_side.pins.append(count)
|
|
123
|
+
string_mut = string_mut[vp_match.end() :]
|
|
124
|
+
elif nonws_match := non_ws_rx.match(string_mut):
|
|
125
|
+
# assume regex
|
|
126
|
+
if current_side is None:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"identifier/regex '{nonws_match[0]}' requires a direction to be set first"
|
|
129
|
+
)
|
|
130
|
+
current_side.pins.append(nonws_match[0])
|
|
131
|
+
string_mut = string_mut[nonws_match.end() :]
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"Syntax Error: Unexpected character starting at {string_mut[:10]}…"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
all_sides: List[Literal["N", "E", "W", "S"]] = ["N", "E", "W", "S"]
|
|
138
|
+
for side in all_sides:
|
|
139
|
+
if side in sides:
|
|
140
|
+
continue
|
|
141
|
+
sides[side] = Side(
|
|
142
|
+
min_distance=global_min_distance,
|
|
143
|
+
reverse_result=False,
|
|
144
|
+
sort_mode=global_sort_mode,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return sides
|