librelane 2.4.0.dev2__py3-none-any.whl → 2.4.0.dev4__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/config/variable.py +12 -7
- 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/reader.py +15 -11
- librelane/scripts/openroad/common/io.tcl +66 -2
- librelane/steps/__init__.py +1 -1
- librelane/steps/odb.py +160 -24
- librelane/steps/openroad.py +7 -2
- librelane/steps/pyosys.py +12 -1
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.0.dev4.dist-info}/METADATA +1 -1
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.0.dev4.dist-info}/RECORD +14 -13
- librelane/scripts/odbpy/exception_codes.py +0 -17
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.0.dev4.dist-info}/WHEEL +0 -0
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.0.dev4.dist-info}/entry_points.txt +0 -0
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
|
"""
|
|
@@ -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()
|
|
@@ -131,16 +131,9 @@ class OdbReader(object):
|
|
|
131
131
|
dpl.reportLegalizationStats()
|
|
132
132
|
dpl.optimizeMirroring()
|
|
133
133
|
|
|
134
|
-
def
|
|
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")
|
|
134
|
+
def _grt_setup(self, grt):
|
|
135
|
+
grt.setAdjustment(float(self.config["GRT_ADJUSTMENT"]))
|
|
142
136
|
|
|
143
|
-
grt = self.design.getGlobalRouter()
|
|
144
137
|
routing_layers = [l for l in self.layers.values() if l.getRoutingLevel() >= 1]
|
|
145
138
|
for layer, adj in zip(routing_layers, self.config["GRT_LAYER_ADJUSTMENTS"]):
|
|
146
139
|
grt.addLayerAdjustment(
|
|
@@ -170,14 +163,25 @@ class OdbReader(object):
|
|
|
170
163
|
raise RuntimeError(f"Unknown layer name '{max_clk_name}'")
|
|
171
164
|
max_clk_idx = self.layers[max_clk_name].getRoutingLevel()
|
|
172
165
|
|
|
173
|
-
grt.setMinRoutingLayer(min_layer_idx)
|
|
174
|
-
grt.setMaxRoutingLayer(max_layer_idx)
|
|
175
166
|
grt.setMinLayerForClock(min_clk_idx)
|
|
176
167
|
grt.setMaxLayerForClock(max_clk_idx)
|
|
177
168
|
grt.setMacroExtension(self.config["GRT_MACRO_EXTENSION"])
|
|
178
169
|
grt.setOverflowIterations(self.config["GRT_OVERFLOW_ITERS"])
|
|
179
170
|
grt.setAllowCongestion(self.config["GRT_ALLOW_CONGESTION"])
|
|
180
171
|
grt.setVerbose(True)
|
|
172
|
+
grt.initFastRoute(min_layer_idx, max_layer_idx)
|
|
173
|
+
|
|
174
|
+
def _grt(self):
|
|
175
|
+
"""
|
|
176
|
+
The ``._grt()`` method is EXPERIMENTAL and SHOULD NOT BE USED YET.
|
|
177
|
+
|
|
178
|
+
Use a composite step with ``OpenROAD.GlobalRouting``.
|
|
179
|
+
"""
|
|
180
|
+
if self.config is None:
|
|
181
|
+
raise RuntimeError("Attempted to call grt without config file")
|
|
182
|
+
|
|
183
|
+
grt = self.design.getGlobalRouter()
|
|
184
|
+
self._grt_setup(grt)
|
|
181
185
|
grt.globalRoute(
|
|
182
186
|
True
|
|
183
187
|
) # The first variable updates guides- not sure why the default is False
|
|
@@ -316,6 +316,70 @@ proc read_current_odb {args} {
|
|
|
316
316
|
set_dont_use_cells
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
proc _populate_cells_by_class {} {
|
|
320
|
+
if { [info exists ::_cells_by_class(physical)] } {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
set ::_cells_by_class(physical) [list]
|
|
325
|
+
set ::_cells_by_class(non_timing) [list]
|
|
326
|
+
set _comment_ {
|
|
327
|
+
We naïvely assume anything not in these classes is not a cell with a
|
|
328
|
+
logical function. This may not be comprehensive, but is good enough.
|
|
329
|
+
|
|
330
|
+
CORE just means a macro used in the core area (i.e. a standard cell.)
|
|
331
|
+
|
|
332
|
+
Thing is, it has a lot of subclasses for physical cells:
|
|
333
|
+
|
|
334
|
+
`FEEDTHRU`,`SPACER`,`ANTENNACELL`,`WELLTAP`
|
|
335
|
+
|
|
336
|
+
Only `TIEHIGH`, `TIELOW` are for logical cells. Thus, the inclusion
|
|
337
|
+
list allows them as well. `BLOCKS` are macros, which we cannot discern
|
|
338
|
+
whether they have a logical function or not, so we include them
|
|
339
|
+
regardless.
|
|
340
|
+
|
|
341
|
+
We do make one exception for `ANTENNACELL`s. These are not counted as
|
|
342
|
+
logical cells but they are not exempt from the so-called SDF-friendly
|
|
343
|
+
netlist as they do affect timing ever so slightly.
|
|
344
|
+
}
|
|
345
|
+
set logical_classes {
|
|
346
|
+
BLOCK
|
|
347
|
+
BUMP
|
|
348
|
+
CORE
|
|
349
|
+
CORE_TIEHIGH
|
|
350
|
+
CORE_TIELOW
|
|
351
|
+
COVER
|
|
352
|
+
PAD
|
|
353
|
+
PAD_AREAIO
|
|
354
|
+
PAD_INOUT
|
|
355
|
+
PAD_INPUT
|
|
356
|
+
PAD_OUTPUT
|
|
357
|
+
PAD_POWER
|
|
358
|
+
PAD_SPACER
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
foreach lib $::libs {
|
|
362
|
+
foreach master [$lib getMasters] {
|
|
363
|
+
if { [lsearch -exact $logical_classes [$master getType]] == -1 } {
|
|
364
|
+
lappend ::_cells_by_class(physical) [$master getName]
|
|
365
|
+
if { "[$master getType]" != "CORE_ANTENNACELL" } {
|
|
366
|
+
lappend ::_cells_by_class(non_timing) [$master getName]
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
proc get_timing_excluded_cells {args} {
|
|
374
|
+
_populate_cells_by_class
|
|
375
|
+
return $::_cells_by_class(non_timing)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
proc get_physical_cells {args} {
|
|
379
|
+
_populate_cells_by_class
|
|
380
|
+
return $::_cells_by_class(physical)
|
|
381
|
+
}
|
|
382
|
+
|
|
319
383
|
proc write_views {args} {
|
|
320
384
|
# This script will attempt to write views based on existing "SAVE_"
|
|
321
385
|
# environment variables. If the SAVE_ variable exists, the script will
|
|
@@ -349,7 +413,7 @@ proc write_views {args} {
|
|
|
349
413
|
}
|
|
350
414
|
|
|
351
415
|
if { [info exists ::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY)] } {
|
|
352
|
-
set exclude_cells "[
|
|
416
|
+
set exclude_cells "[get_timing_excluded_cells]"
|
|
353
417
|
puts "Writing nofill powered netlist to '$::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY)'…"
|
|
354
418
|
puts "Excluding $exclude_cells"
|
|
355
419
|
write_verilog -include_pwr_gnd \
|
|
@@ -358,7 +422,7 @@ proc write_views {args} {
|
|
|
358
422
|
}
|
|
359
423
|
|
|
360
424
|
if { [info exists ::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS)] } {
|
|
361
|
-
set exclude_cells "[
|
|
425
|
+
set exclude_cells "[get_physical_cells]"
|
|
362
426
|
puts "Writing nofilldiode powered netlist to '$::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS)'…"
|
|
363
427
|
puts "Excluding $exclude_cells"
|
|
364
428
|
write_verilog -include_pwr_gnd \
|
librelane/steps/__init__.py
CHANGED
librelane/steps/odb.py
CHANGED
|
@@ -19,28 +19,27 @@ from math import inf
|
|
|
19
19
|
from decimal import Decimal
|
|
20
20
|
from functools import reduce
|
|
21
21
|
from abc import abstractmethod
|
|
22
|
+
from dataclasses import dataclass
|
|
22
23
|
from typing import Dict, List, Literal, Optional, Tuple
|
|
23
24
|
|
|
25
|
+
from ..common import Path, get_script_dir, aggregate_metrics
|
|
26
|
+
from ..config import Instance, Macro, Variable
|
|
27
|
+
from ..logging import info, verbose
|
|
28
|
+
from ..state import DesignFormat, State
|
|
24
29
|
|
|
25
|
-
from .common_variables import io_layer_variables
|
|
26
|
-
from .openroad_alerts import (
|
|
27
|
-
OpenROADAlert,
|
|
28
|
-
OpenROADOutputProcessor,
|
|
29
|
-
)
|
|
30
30
|
from .openroad import DetailedPlacement, GlobalRouting
|
|
31
|
-
from .
|
|
31
|
+
from .openroad_alerts import OpenROADAlert, OpenROADOutputProcessor
|
|
32
|
+
from .common_variables import io_layer_variables, dpl_variables, grt_variables
|
|
32
33
|
from .step import (
|
|
33
|
-
|
|
34
|
+
CompositeStep,
|
|
35
|
+
DefaultOutputProcessor,
|
|
34
36
|
MetricsUpdate,
|
|
35
37
|
Step,
|
|
38
|
+
StepError,
|
|
36
39
|
StepException,
|
|
37
|
-
|
|
38
|
-
DefaultOutputProcessor,
|
|
40
|
+
ViewsUpdate,
|
|
39
41
|
)
|
|
40
|
-
from
|
|
41
|
-
from ..config import Variable, Macro, Instance
|
|
42
|
-
from ..state import State, DesignFormat
|
|
43
|
-
from ..common import Path, get_script_dir
|
|
42
|
+
from .tclstep import TclStep
|
|
44
43
|
|
|
45
44
|
inf_rx = re.compile(r"\b(-?)inf\b")
|
|
46
45
|
|
|
@@ -51,6 +50,8 @@ class OdbpyStep(Step):
|
|
|
51
50
|
|
|
52
51
|
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
53
52
|
|
|
53
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
54
|
+
|
|
54
55
|
def on_alert(self, alert: OpenROADAlert) -> OpenROADAlert:
|
|
55
56
|
if alert.code in [
|
|
56
57
|
"ORD-0039", # .openroad ignored with -python
|
|
@@ -63,7 +64,9 @@ class OdbpyStep(Step):
|
|
|
63
64
|
self.warn(str(alert), extra={"key": alert.code})
|
|
64
65
|
return alert
|
|
65
66
|
|
|
66
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
67
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
68
|
+
self.alerts = None
|
|
69
|
+
|
|
67
70
|
kwargs, env = self.extract_env(kwargs)
|
|
68
71
|
|
|
69
72
|
automatic_outputs = set(self.outputs).intersection(
|
|
@@ -86,15 +89,35 @@ class OdbpyStep(Step):
|
|
|
86
89
|
env["PYTHONPATH"] = (
|
|
87
90
|
f'{os.path.join(get_script_dir(), "odbpy")}:{env.get("PYTHONPATH")}'
|
|
88
91
|
)
|
|
92
|
+
check = False
|
|
93
|
+
if "check" in kwargs:
|
|
94
|
+
check = kwargs.pop("check")
|
|
89
95
|
|
|
90
96
|
subprocess_result = self.run_subprocess(
|
|
91
97
|
command,
|
|
92
98
|
env=env,
|
|
99
|
+
check=check,
|
|
93
100
|
**kwargs,
|
|
94
101
|
)
|
|
102
|
+
generated_metrics = subprocess_result["generated_metrics"]
|
|
95
103
|
|
|
104
|
+
# 1. Parse warnings and errors
|
|
105
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
106
|
+
if subprocess_result["returncode"] != 0:
|
|
107
|
+
error_strings = [
|
|
108
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
109
|
+
]
|
|
110
|
+
if len(error_strings):
|
|
111
|
+
error_string = "\n".join(error_strings)
|
|
112
|
+
raise StepError(
|
|
113
|
+
f"{self.id} failed with the following errors:\n{error_string}"
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
raise StepException(
|
|
117
|
+
f"{self.id} failed unexpectedly. Please check the logs and file an issue."
|
|
118
|
+
)
|
|
119
|
+
# 2. Metrics
|
|
96
120
|
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
97
|
-
metrics_updates: MetricsUpdate = subprocess_result["generated_metrics"]
|
|
98
121
|
if os.path.exists(metrics_path):
|
|
99
122
|
or_metrics_out = json.loads(open(metrics_path).read(), parse_float=Decimal)
|
|
100
123
|
for key, value in or_metrics_out.items():
|
|
@@ -102,9 +125,11 @@ class OdbpyStep(Step):
|
|
|
102
125
|
or_metrics_out[key] = inf
|
|
103
126
|
elif value == "-Infinity":
|
|
104
127
|
or_metrics_out[key] = -inf
|
|
105
|
-
|
|
128
|
+
generated_metrics.update(or_metrics_out)
|
|
106
129
|
|
|
107
|
-
|
|
130
|
+
metric_updates_with_aggregates = aggregate_metrics(generated_metrics)
|
|
131
|
+
|
|
132
|
+
return views_updates, metric_updates_with_aggregates
|
|
108
133
|
|
|
109
134
|
def get_command(self) -> List[str]:
|
|
110
135
|
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
@@ -180,7 +205,7 @@ class CheckMacroAntennaProperties(OdbpyStep):
|
|
|
180
205
|
args += ["--cell-name", name]
|
|
181
206
|
return super().get_command() + args
|
|
182
207
|
|
|
183
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
208
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
184
209
|
if not self.get_cells():
|
|
185
210
|
info("No cells provided, skipping…")
|
|
186
211
|
return {}, {}
|
|
@@ -244,7 +269,7 @@ class ApplyDEFTemplate(OdbpyStep):
|
|
|
244
269
|
args.append("--copy-def-power")
|
|
245
270
|
return super().get_command() + args
|
|
246
271
|
|
|
247
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
272
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
248
273
|
if self.config["FP_DEF_TEMPLATE"] is None:
|
|
249
274
|
info("No DEF template provided, skipping…")
|
|
250
275
|
return {}, {}
|
|
@@ -339,7 +364,7 @@ class WriteVerilogHeader(OdbpyStep):
|
|
|
339
364
|
|
|
340
365
|
return command
|
|
341
366
|
|
|
342
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
367
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
343
368
|
views_updates, metrics_updates = super().run(state_in, **kwargs)
|
|
344
369
|
views_updates[DesignFormat.VERILOG_HEADER] = Path(
|
|
345
370
|
os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.vh")
|
|
@@ -528,7 +553,7 @@ class AddRoutingObstructions(OdbpyStep):
|
|
|
528
553
|
command.append(obstruction)
|
|
529
554
|
return command
|
|
530
555
|
|
|
531
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
556
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
532
557
|
if self.config[self.get_obstruction_variable().name] is None:
|
|
533
558
|
info(
|
|
534
559
|
f"'{self.get_obstruction_variable().name}' is not defined. Skipping '{self.id}'…"
|
|
@@ -659,7 +684,7 @@ class CustomIOPlacement(OdbpyStep):
|
|
|
659
684
|
+ length_args
|
|
660
685
|
)
|
|
661
686
|
|
|
662
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
687
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
663
688
|
if self.config["FP_PIN_ORDER_CFG"] is None:
|
|
664
689
|
info("No custom floorplan file configured, skipping…")
|
|
665
690
|
return {}, {}
|
|
@@ -895,7 +920,7 @@ class CellFrequencyTables(OdbpyStep):
|
|
|
895
920
|
def get_buffer_list_script(self):
|
|
896
921
|
return os.path.join(get_script_dir(), "openroad", "buffer_list.tcl")
|
|
897
922
|
|
|
898
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
923
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
899
924
|
kwargs, env = self.extract_env(kwargs)
|
|
900
925
|
|
|
901
926
|
env_copy = env.copy()
|
|
@@ -948,8 +973,119 @@ class ManualGlobalPlacement(OdbpyStep):
|
|
|
948
973
|
assert self.config_path is not None, "get_command called before start()"
|
|
949
974
|
return super().get_command() + ["--step-config", self.config_path]
|
|
950
975
|
|
|
951
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
976
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
952
977
|
if self.config["MANUAL_GLOBAL_PLACEMENTS"] is None:
|
|
953
978
|
info("'MANUAL_GLOBAL_PLACEMENTS' not set, skipping…")
|
|
954
979
|
return {}, {}
|
|
955
980
|
return super().run(state_in, **kwargs)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
@dataclass
|
|
984
|
+
class ECOBuffer:
|
|
985
|
+
"""
|
|
986
|
+
:param target: The driver to insert an ECO buffer after or sink to insert an
|
|
987
|
+
ECO buffer before, in the format instance_name/pin_name.
|
|
988
|
+
:param buffer: The kind of buffer cell to use.
|
|
989
|
+
:param placement: The coarse placement for this buffer (to be legalized.)
|
|
990
|
+
If unset, depending on whether the target is a driver or a sink:
|
|
991
|
+
|
|
992
|
+
- Driver: The placement will be the average of the driver and all sinks.
|
|
993
|
+
|
|
994
|
+
- Sink: The placement will be the average of the sink and all drivers.
|
|
995
|
+
"""
|
|
996
|
+
|
|
997
|
+
target: str
|
|
998
|
+
buffer: str
|
|
999
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
@Step.factory.register()
|
|
1003
|
+
class InsertECOBuffers(OdbpyStep):
|
|
1004
|
+
"""
|
|
1005
|
+
Experimental step to insert ECO buffers on either drivers or sinks after
|
|
1006
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1007
|
+
incrementally re-run for affected nets. Useful for manually fixing some hold
|
|
1008
|
+
violations.
|
|
1009
|
+
|
|
1010
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1011
|
+
nets that are altered are removed and require re-routing.
|
|
1012
|
+
|
|
1013
|
+
INOUT and FEEDTHRU ports are not supported.
|
|
1014
|
+
"""
|
|
1015
|
+
|
|
1016
|
+
id = "Odb.InsertECOBuffers"
|
|
1017
|
+
name = "Insert ECO Buffers"
|
|
1018
|
+
|
|
1019
|
+
config_vars = (
|
|
1020
|
+
dpl_variables
|
|
1021
|
+
+ grt_variables
|
|
1022
|
+
+ [
|
|
1023
|
+
Variable(
|
|
1024
|
+
"INSERT_ECO_BUFFERS",
|
|
1025
|
+
Optional[List[ECOBuffer]],
|
|
1026
|
+
"List of buffers to insert",
|
|
1027
|
+
)
|
|
1028
|
+
]
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
def get_script_path(self):
|
|
1032
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_buffer.py")
|
|
1033
|
+
|
|
1034
|
+
def get_command(self) -> List[str]:
|
|
1035
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1036
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
@dataclass
|
|
1040
|
+
class ECODiode:
|
|
1041
|
+
"""
|
|
1042
|
+
:param target: The sink whose net gets a diode connected, in the format
|
|
1043
|
+
instance_name/pin_name.
|
|
1044
|
+
:param placement: The coarse placement for this diode (to be legalized.)
|
|
1045
|
+
If unset, the diode is placed at the same location as the target
|
|
1046
|
+
instance, with legalization later moving it to a valid location.
|
|
1047
|
+
"""
|
|
1048
|
+
|
|
1049
|
+
target: str
|
|
1050
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
@Step.factory.register()
|
|
1054
|
+
class InsertECODiodes(OdbpyStep):
|
|
1055
|
+
"""
|
|
1056
|
+
Experimental step to create and attach ECO diodes to the nets of sinks after
|
|
1057
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1058
|
+
incrementally re-run for affected nets. Useful for manually fixing some
|
|
1059
|
+
antenna violations.
|
|
1060
|
+
|
|
1061
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1062
|
+
nets that are altered are removed and require re-routing.
|
|
1063
|
+
"""
|
|
1064
|
+
|
|
1065
|
+
id = "Odb.InsertECODiodes"
|
|
1066
|
+
name = "Insert ECO Diodes"
|
|
1067
|
+
|
|
1068
|
+
config_vars = (
|
|
1069
|
+
grt_variables
|
|
1070
|
+
+ dpl_variables
|
|
1071
|
+
+ [
|
|
1072
|
+
Variable(
|
|
1073
|
+
"INSERT_ECO_DIODES",
|
|
1074
|
+
Optional[List[ECODiode]],
|
|
1075
|
+
"List of sinks to insert diodes for.",
|
|
1076
|
+
)
|
|
1077
|
+
]
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
def get_script_path(self):
|
|
1081
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_diode.py")
|
|
1082
|
+
|
|
1083
|
+
def get_command(self) -> List[str]:
|
|
1084
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1085
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1086
|
+
|
|
1087
|
+
def run(self, state_in: State, **kwargs):
|
|
1088
|
+
if self.config["DIODE_CELL"] is None:
|
|
1089
|
+
info(f"'DIODE_CELL' not set. Skipping '{self.id}'…")
|
|
1090
|
+
return {}, {}
|
|
1091
|
+
return super().run(state_in, **kwargs)
|
librelane/steps/openroad.py
CHANGED
|
@@ -185,6 +185,8 @@ class OpenROADStep(TclStep):
|
|
|
185
185
|
|
|
186
186
|
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
187
187
|
|
|
188
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
189
|
+
|
|
188
190
|
config_vars = [
|
|
189
191
|
Variable(
|
|
190
192
|
"PDN_CONNECT_MACROS_TO_GRID",
|
|
@@ -264,6 +266,7 @@ class OpenROADStep(TclStep):
|
|
|
264
266
|
2. After the `super()` call: Processes the `or_metrics_out.json` file and
|
|
265
267
|
updates the State's `metrics` property with any new metrics in that object.
|
|
266
268
|
"""
|
|
269
|
+
self.alerts = None
|
|
267
270
|
kwargs, env = self.extract_env(kwargs)
|
|
268
271
|
env = self.prepare_env(env, state_in)
|
|
269
272
|
|
|
@@ -293,9 +296,11 @@ class OpenROADStep(TclStep):
|
|
|
293
296
|
views_updates[output] = path
|
|
294
297
|
|
|
295
298
|
# 1. Parse warnings and errors
|
|
296
|
-
alerts = subprocess_result
|
|
299
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
297
300
|
if subprocess_result["returncode"] != 0:
|
|
298
|
-
error_strings = [
|
|
301
|
+
error_strings = [
|
|
302
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
303
|
+
]
|
|
299
304
|
if len(error_strings):
|
|
300
305
|
error_string = "\n".join(error_strings)
|
|
301
306
|
raise StepError(
|
librelane/steps/pyosys.py
CHANGED
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
import os
|
|
15
15
|
import re
|
|
16
16
|
import io
|
|
17
|
+
import sys
|
|
17
18
|
import json
|
|
18
19
|
import fnmatch
|
|
20
|
+
import shutil
|
|
19
21
|
import subprocess
|
|
20
22
|
from decimal import Decimal
|
|
21
23
|
from abc import abstractmethod
|
|
@@ -207,7 +209,11 @@ class PyosysStep(Step):
|
|
|
207
209
|
|
|
208
210
|
def get_command(self, state_in: State) -> List[str]:
|
|
209
211
|
script_path = self.get_script_path()
|
|
210
|
-
|
|
212
|
+
# HACK: Get Colab working
|
|
213
|
+
yosys_bin = "yosys"
|
|
214
|
+
if "google.colab" in sys.modules:
|
|
215
|
+
yosys_bin = shutil.which("yosys") or "yosys"
|
|
216
|
+
cmd = [yosys_bin, "-y", script_path]
|
|
211
217
|
if self.config["YOSYS_LOG_LEVEL"] != "ALL":
|
|
212
218
|
cmd += ["-Q"]
|
|
213
219
|
if self.config["YOSYS_LOG_LEVEL"] == "WARNING":
|
|
@@ -220,6 +226,11 @@ class PyosysStep(Step):
|
|
|
220
226
|
|
|
221
227
|
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
222
228
|
cmd = self.get_command(state_in)
|
|
229
|
+
# HACK: Get Colab working
|
|
230
|
+
if "google.colab" in sys.modules:
|
|
231
|
+
kwargs, env = self.extract_env(kwargs)
|
|
232
|
+
env.pop("PATH", "")
|
|
233
|
+
kwargs["env"] = env
|
|
223
234
|
subprocess_result = super().run_subprocess(cmd, **kwargs)
|
|
224
235
|
return {}, subprocess_result["generated_metrics"]
|
|
225
236
|
|
|
@@ -23,7 +23,7 @@ librelane/config/flow.py,sha256=qCGaUOj12j57gORzoE10m7_WG-n600llnFDMlZagUF4,1666
|
|
|
23
23
|
librelane/config/pdk_compat.py,sha256=rznq5xIny9M0PmddhPOGtCIrSdv98ysAoYgkpyM0gUA,8450
|
|
24
24
|
librelane/config/preprocessor.py,sha256=I239Y01dC2o5eb1UtcSbLdybVrZgqGyDr7ecT234I4Y,14913
|
|
25
25
|
librelane/config/removals.py,sha256=lJ0xpkCqnZAdA_ug4yq0NDjRBFuw4XsdORwymbEVGyQ,2907
|
|
26
|
-
librelane/config/variable.py,sha256=
|
|
26
|
+
librelane/config/variable.py,sha256=tqH7po203Q7usX9jdVoyng2fdKPzDO36UmbCPZcIcsA,26232
|
|
27
27
|
librelane/container.py,sha256=3KHxs3dUSVUZVYsS6fsA7dD3Q4QEQEzRxgXZZh9dzi0,7554
|
|
28
28
|
librelane/env_info.py,sha256=vAE9AZ_vDFLt7Srtg4ZywPzE6vgVhCrIvg8PP25-BJ8,10460
|
|
29
29
|
librelane/examples/spm/config.yaml,sha256=YKBm0lsY3AJZNcxAh1sQ1QMmJeVCpOpil6dw_RgQh4c,633
|
|
@@ -82,10 +82,11 @@ librelane/scripts/odbpy/apply_def_template.py,sha256=Tn6y65biu0bAQ6XilYxq5jn3a_K
|
|
|
82
82
|
librelane/scripts/odbpy/cell_frequency.py,sha256=NfGgM8wxIvjM1C_GHUghZPOh8gpdasLOWR4qBdHHLFE,3105
|
|
83
83
|
librelane/scripts/odbpy/check_antenna_properties.py,sha256=dMD-RcoA7plcAu9IIqa2e-8XCO0EMcKG-6P39D3Gpic,3942
|
|
84
84
|
librelane/scripts/odbpy/contextualize.py,sha256=G8EEgmK6ISikXD2-Pw-RTs1JxLWPnuppwL7oPfAsb84,4020
|
|
85
|
-
librelane/scripts/odbpy/defutil.py,sha256=
|
|
85
|
+
librelane/scripts/odbpy/defutil.py,sha256=g0UaAQRt8hXb9nfI6SbMp_Hx__0o1POw33_fS6xyibU,17849
|
|
86
86
|
librelane/scripts/odbpy/diodes.py,sha256=ZS_1niaTcwvaTTNjJcth4Qepcwxa6aV6E9WM_KiMtTI,11811
|
|
87
87
|
librelane/scripts/odbpy/disconnected_pins.py,sha256=hS_Iletg7N6K6yN_ccvWxZ3nWNZp4ecUJM-oY0kkEfA,11139
|
|
88
|
-
librelane/scripts/odbpy/
|
|
88
|
+
librelane/scripts/odbpy/eco_buffer.py,sha256=QOL2J0UJQiVvuGFbpdyAj-RRsPfEL-rT_qro3Rp0KeE,6256
|
|
89
|
+
librelane/scripts/odbpy/eco_diode.py,sha256=2LN7fHh9uO9JP3PYIxIwUiP1lyeqdTNF2ADTcY_Bu-g,4281
|
|
89
90
|
librelane/scripts/odbpy/filter_unannotated.py,sha256=Gvcaj_WNr6TPiHk-36nkMu4betNHZo1g2lD3UcA9hDQ,2950
|
|
90
91
|
librelane/scripts/odbpy/io_place.py,sha256=LSJIJQDLSOpENyQOg_kVTIbh1AbYLiHIXx0siduo-lg,15589
|
|
91
92
|
librelane/scripts/odbpy/ioplace_parser/__init__.py,sha256=TMKTIWwGJfdSr7dJcsoisUuKlbTKJdHV6-0kB77v2P8,887
|
|
@@ -95,7 +96,7 @@ librelane/scripts/odbpy/lefutil.py,sha256=XhfWSGHdn96yZWYQAPisgJM0iuY3xw4SW7jmMT
|
|
|
95
96
|
librelane/scripts/odbpy/placers.py,sha256=mgy_-GYeLDPMG41YAopMTtJyCHP6ucJRk7cJzI9PLRQ,4572
|
|
96
97
|
librelane/scripts/odbpy/power_utils.py,sha256=al12uMiv8G0yQZOPKXNHYQ1dm2KGlu9xigSuYLEAo_A,14627
|
|
97
98
|
librelane/scripts/odbpy/random_place.py,sha256=TEsV4LtXQTP8OJvnBh09Siu9fKkwG9UpIkCkQpdXAgU,1649
|
|
98
|
-
librelane/scripts/odbpy/reader.py,sha256=
|
|
99
|
+
librelane/scripts/odbpy/reader.py,sha256=5-hpG3TgmKwJtnbEDQoAPejeDx1QyRBRbIMULMkBesM,8539
|
|
99
100
|
librelane/scripts/odbpy/remove_buffers.py,sha256=f-kGZIPnMtu4gnl2r2CDkng8U8vUMJKJWNV_akOpc38,5460
|
|
100
101
|
librelane/scripts/odbpy/snap_to_grid.py,sha256=lULRWlcYXvrTBUpemUPlpO2dBnbFeriuG-DlI4KnViE,1743
|
|
101
102
|
librelane/scripts/odbpy/wire_lengths.py,sha256=pSPhVnLlvcvmgEh89G8nu8DRaZVP66r-4ieVoV3zrm4,2737
|
|
@@ -106,7 +107,7 @@ librelane/scripts/openroad/buffer_list.tcl,sha256=sXygy1KRSUS4dZi1UOpBkGGOuXRVLM
|
|
|
106
107
|
librelane/scripts/openroad/common/dpl.tcl,sha256=Nqq5e5OwRoyk8mHfVa5uw3DGKDGMEFrx6odpxanc_Ns,912
|
|
107
108
|
librelane/scripts/openroad/common/dpl_cell_pad.tcl,sha256=KWVuj8u1-y3ZUiQr48TAsFv1GSzOCVnAjdqfBjtoQxQ,1066
|
|
108
109
|
librelane/scripts/openroad/common/grt.tcl,sha256=DCKe5D7INCBctitbRdgZNRsBrI9Qo5v7Ag7DF45W4_U,1137
|
|
109
|
-
librelane/scripts/openroad/common/io.tcl,sha256=
|
|
110
|
+
librelane/scripts/openroad/common/io.tcl,sha256=cPJ4O2nBKrGKON7lO7ZX1j_TpcxQFCw3gH858yTWg8Q,18289
|
|
110
111
|
librelane/scripts/openroad/common/pdn_cfg.tcl,sha256=KnQAxzlL_261Kp4M02cQ6usZHIRNBj56SAZNn1CqrZc,4552
|
|
111
112
|
librelane/scripts/openroad/common/resizer.tcl,sha256=3p59NDcXgEkUzg_43dcUYr2KkD__fkADdqCZ3uWsUU4,3478
|
|
112
113
|
librelane/scripts/openroad/common/set_global_connections.tcl,sha256=zGMz0Hu57ZVdLhz4djJASyre0qOi-dszylo6HD8ClUM,2899
|
|
@@ -145,7 +146,7 @@ librelane/state/__init__.py,sha256=rLUdAkeB278r8pB2Jpv-ccmmmP32FR90wANIFHXdA0w,9
|
|
|
145
146
|
librelane/state/__main__.py,sha256=Ici4Ejg1ICUZNSYZRguC3BfEk_wFxsmE0ag0Vv8iY1I,1679
|
|
146
147
|
librelane/state/design_format.py,sha256=9mWBbHrhzludtv3dKB6P4UQncBVTMMtoj69kNgRvh5o,5306
|
|
147
148
|
librelane/state/state.py,sha256=J05gAeSVDiF76ITuw4WJZ7WkMyG4oTjt_7kpsI3E3PE,11957
|
|
148
|
-
librelane/steps/__init__.py,sha256=
|
|
149
|
+
librelane/steps/__init__.py,sha256=j3JYrdnWM74dYuEvE931oSrQI7FUz-hKWr8Mts8C0wg,1668
|
|
149
150
|
librelane/steps/__main__.py,sha256=GviXtDLISKJCufKxK3oFPOSMF1GyShZbG5RXpVCYFkk,13376
|
|
150
151
|
librelane/steps/checker.py,sha256=vul1D0cT03144qKK5QAKswClKKICK7kNB4PB6xXykvc,21353
|
|
151
152
|
librelane/steps/common_variables.py,sha256=qT0VeIstFsDbe8VGAyqXrXxQw69OZ08haM6u1IbdFiE,10429
|
|
@@ -154,15 +155,15 @@ librelane/steps/klayout.py,sha256=g7jYz-1cLwgfPTiMJIdAQ9zmkrwNtJLPoRg6PqOUv6Y,16
|
|
|
154
155
|
librelane/steps/magic.py,sha256=4o_WarBAQdTTuekP72uovjvqW5wsaDCpMB3LtAhC_IY,20051
|
|
155
156
|
librelane/steps/misc.py,sha256=Xk_a6JJPljkk8pemu-NtlzDRs-8S7vuRKZKj4pnCRlE,5690
|
|
156
157
|
librelane/steps/netgen.py,sha256=R9sDWv-9wKMdi2rkuLQdOc4uLlbYhXcKKd6WsZsnLt0,8953
|
|
157
|
-
librelane/steps/odb.py,sha256=
|
|
158
|
-
librelane/steps/openroad.py,sha256=
|
|
158
|
+
librelane/steps/odb.py,sha256=_WhAFEVbFioSGsVrGbXQVqcXYAnE22gLA4eF1v028EQ,38030
|
|
159
|
+
librelane/steps/openroad.py,sha256=0uHbFYO3wT9rFkovxMgr4rMzaMpA9DvG8sWLM4L3jn8,85836
|
|
159
160
|
librelane/steps/openroad_alerts.py,sha256=IJyB4piBDCKXhkJswHGMYCRDwbdQsR0GZlrGGDhmW6Q,3364
|
|
160
|
-
librelane/steps/pyosys.py,sha256=
|
|
161
|
+
librelane/steps/pyosys.py,sha256=mnbPR267XzJvDhtXW4cdTAB3IqvksUZt4ch5xQHgdY0,22705
|
|
161
162
|
librelane/steps/step.py,sha256=YkUWbxx2dOz0QR90jM5NC7neFkWO1RW5zJc0I_XxRyc,54975
|
|
162
163
|
librelane/steps/tclstep.py,sha256=0PMWJ6C3dKnlQf9mA9rZntgxUBCiByE9csHcEcM1iq0,10027
|
|
163
164
|
librelane/steps/verilator.py,sha256=MWx2TpLqYyea9_jSeLG9c2S5ujvYERQZRFNaMhfHxZE,7916
|
|
164
165
|
librelane/steps/yosys.py,sha256=GX6rTiQG-ZhDxfB9SxrPQ9Sab3WC84p0OUtqiL1Nubk,12533
|
|
165
|
-
librelane-2.4.0.
|
|
166
|
-
librelane-2.4.0.
|
|
167
|
-
librelane-2.4.0.
|
|
168
|
-
librelane-2.4.0.
|
|
166
|
+
librelane-2.4.0.dev4.dist-info/METADATA,sha256=EjKTd191ptXMgWO4Qd-SrS08v70GdDtJORd-kJS38Z0,6600
|
|
167
|
+
librelane-2.4.0.dev4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
168
|
+
librelane-2.4.0.dev4.dist-info/entry_points.txt,sha256=GTBvXykNMMFsNKiJFgtEw7P1wb_VZIqVM35EFSpyZQE,263
|
|
169
|
+
librelane-2.4.0.dev4.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Copyright 2023 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
|
-
|
|
15
|
-
METAL_LAYER_ERROR = 10
|
|
16
|
-
FORMAT_ERROR = 11
|
|
17
|
-
NOT_FOUND_ERROR = 12
|
|
File without changes
|
|
File without changes
|