librelane 2.4.0.dev2__py3-none-any.whl → 2.4.7__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.
- librelane/__init__.py +1 -1
- librelane/__main__.py +34 -27
- librelane/common/__init__.py +2 -0
- librelane/common/cli.py +1 -1
- librelane/common/drc.py +1 -0
- librelane/common/generic_dict.py +1 -1
- librelane/common/metrics/__main__.py +1 -1
- librelane/common/misc.py +58 -2
- librelane/common/tcl.py +2 -1
- librelane/common/types.py +2 -3
- librelane/config/__main__.py +1 -4
- librelane/config/flow.py +2 -2
- librelane/config/preprocessor.py +1 -1
- librelane/config/variable.py +136 -7
- librelane/container.py +55 -31
- librelane/env_info.py +129 -115
- librelane/examples/hold_eco_demo/config.yaml +18 -0
- librelane/examples/hold_eco_demo/demo.v +27 -0
- librelane/flows/cli.py +39 -23
- librelane/flows/flow.py +100 -36
- librelane/help/__main__.py +39 -0
- librelane/scripts/magic/def/mag_gds.tcl +0 -2
- librelane/scripts/magic/drc.tcl +0 -1
- librelane/scripts/magic/gds/extras_mag.tcl +0 -2
- librelane/scripts/magic/gds/mag_with_pointers.tcl +0 -1
- librelane/scripts/magic/lef/extras_maglef.tcl +0 -2
- librelane/scripts/magic/lef/maglef.tcl +0 -1
- librelane/scripts/magic/wrapper.tcl +2 -0
- librelane/scripts/odbpy/defutil.py +15 -10
- librelane/scripts/odbpy/eco_buffer.py +182 -0
- librelane/scripts/odbpy/eco_diode.py +140 -0
- librelane/scripts/odbpy/ioplace_parser/__init__.py +1 -1
- librelane/scripts/odbpy/ioplace_parser/parse.py +1 -1
- librelane/scripts/odbpy/power_utils.py +8 -6
- librelane/scripts/odbpy/reader.py +17 -13
- librelane/scripts/openroad/common/io.tcl +66 -2
- librelane/scripts/openroad/gui.tcl +23 -1
- librelane/state/design_format.py +16 -1
- librelane/state/state.py +11 -3
- librelane/steps/__init__.py +1 -1
- librelane/steps/__main__.py +4 -4
- librelane/steps/checker.py +7 -8
- librelane/steps/klayout.py +11 -1
- librelane/steps/magic.py +24 -14
- librelane/steps/misc.py +5 -0
- librelane/steps/odb.py +193 -28
- librelane/steps/openroad.py +64 -47
- librelane/steps/pyosys.py +18 -1
- librelane/steps/step.py +36 -17
- librelane/steps/yosys.py +9 -1
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/METADATA +10 -11
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/RECORD +54 -50
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/entry_points.txt +1 -0
- librelane/scripts/odbpy/exception_codes.py +0 -17
- {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/WHEEL +0 -0
librelane/steps/odb.py
CHANGED
|
@@ -17,30 +17,28 @@ import json
|
|
|
17
17
|
import shutil
|
|
18
18
|
from math import inf
|
|
19
19
|
from decimal import Decimal
|
|
20
|
-
from functools import reduce
|
|
21
20
|
from abc import abstractmethod
|
|
21
|
+
from dataclasses import dataclass
|
|
22
22
|
from typing import Dict, List, Literal, Optional, Tuple
|
|
23
23
|
|
|
24
|
+
from ..common import Path, get_script_dir, aggregate_metrics
|
|
25
|
+
from ..config import Instance, Macro, Variable
|
|
26
|
+
from ..logging import info, verbose
|
|
27
|
+
from ..state import DesignFormat, State
|
|
24
28
|
|
|
25
|
-
from .common_variables import io_layer_variables
|
|
26
|
-
from .openroad_alerts import (
|
|
27
|
-
OpenROADAlert,
|
|
28
|
-
OpenROADOutputProcessor,
|
|
29
|
-
)
|
|
30
29
|
from .openroad import DetailedPlacement, GlobalRouting
|
|
31
|
-
from .
|
|
30
|
+
from .openroad_alerts import OpenROADAlert, OpenROADOutputProcessor
|
|
31
|
+
from .common_variables import io_layer_variables, dpl_variables, grt_variables
|
|
32
32
|
from .step import (
|
|
33
|
-
|
|
33
|
+
CompositeStep,
|
|
34
|
+
DefaultOutputProcessor,
|
|
34
35
|
MetricsUpdate,
|
|
35
36
|
Step,
|
|
37
|
+
StepError,
|
|
36
38
|
StepException,
|
|
37
|
-
|
|
38
|
-
DefaultOutputProcessor,
|
|
39
|
+
ViewsUpdate,
|
|
39
40
|
)
|
|
40
|
-
from
|
|
41
|
-
from ..config import Variable, Macro, Instance
|
|
42
|
-
from ..state import State, DesignFormat
|
|
43
|
-
from ..common import Path, get_script_dir
|
|
41
|
+
from .tclstep import TclStep
|
|
44
42
|
|
|
45
43
|
inf_rx = re.compile(r"\b(-?)inf\b")
|
|
46
44
|
|
|
@@ -51,6 +49,8 @@ class OdbpyStep(Step):
|
|
|
51
49
|
|
|
52
50
|
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
53
51
|
|
|
52
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
53
|
+
|
|
54
54
|
def on_alert(self, alert: OpenROADAlert) -> OpenROADAlert:
|
|
55
55
|
if alert.code in [
|
|
56
56
|
"ORD-0039", # .openroad ignored with -python
|
|
@@ -63,7 +63,9 @@ class OdbpyStep(Step):
|
|
|
63
63
|
self.warn(str(alert), extra={"key": alert.code})
|
|
64
64
|
return alert
|
|
65
65
|
|
|
66
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
66
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
67
|
+
self.alerts = None
|
|
68
|
+
|
|
67
69
|
kwargs, env = self.extract_env(kwargs)
|
|
68
70
|
|
|
69
71
|
automatic_outputs = set(self.outputs).intersection(
|
|
@@ -86,15 +88,35 @@ class OdbpyStep(Step):
|
|
|
86
88
|
env["PYTHONPATH"] = (
|
|
87
89
|
f'{os.path.join(get_script_dir(), "odbpy")}:{env.get("PYTHONPATH")}'
|
|
88
90
|
)
|
|
91
|
+
check = False
|
|
92
|
+
if "check" in kwargs:
|
|
93
|
+
check = kwargs.pop("check")
|
|
89
94
|
|
|
90
95
|
subprocess_result = self.run_subprocess(
|
|
91
96
|
command,
|
|
92
97
|
env=env,
|
|
98
|
+
check=check,
|
|
93
99
|
**kwargs,
|
|
94
100
|
)
|
|
101
|
+
generated_metrics = subprocess_result["generated_metrics"]
|
|
95
102
|
|
|
103
|
+
# 1. Parse warnings and errors
|
|
104
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
105
|
+
if subprocess_result["returncode"] != 0:
|
|
106
|
+
error_strings = [
|
|
107
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
108
|
+
]
|
|
109
|
+
if len(error_strings):
|
|
110
|
+
error_string = "\n".join(error_strings)
|
|
111
|
+
raise StepError(
|
|
112
|
+
f"{self.id} failed with the following errors:\n{error_string}"
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
raise StepException(
|
|
116
|
+
f"{self.id} failed unexpectedly. Please check the logs and file an issue."
|
|
117
|
+
)
|
|
118
|
+
# 2. Metrics
|
|
96
119
|
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
97
|
-
metrics_updates: MetricsUpdate = subprocess_result["generated_metrics"]
|
|
98
120
|
if os.path.exists(metrics_path):
|
|
99
121
|
or_metrics_out = json.loads(open(metrics_path).read(), parse_float=Decimal)
|
|
100
122
|
for key, value in or_metrics_out.items():
|
|
@@ -102,9 +124,11 @@ class OdbpyStep(Step):
|
|
|
102
124
|
or_metrics_out[key] = inf
|
|
103
125
|
elif value == "-Infinity":
|
|
104
126
|
or_metrics_out[key] = -inf
|
|
105
|
-
|
|
127
|
+
generated_metrics.update(or_metrics_out)
|
|
106
128
|
|
|
107
|
-
|
|
129
|
+
metric_updates_with_aggregates = aggregate_metrics(generated_metrics)
|
|
130
|
+
|
|
131
|
+
return views_updates, metric_updates_with_aggregates
|
|
108
132
|
|
|
109
133
|
def get_command(self) -> List[str]:
|
|
110
134
|
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
@@ -152,6 +176,10 @@ class OdbpyStep(Step):
|
|
|
152
176
|
|
|
153
177
|
@Step.factory.register()
|
|
154
178
|
class CheckMacroAntennaProperties(OdbpyStep):
|
|
179
|
+
"""
|
|
180
|
+
Prints warnings if the LEF views of macros are missing antenna information.
|
|
181
|
+
"""
|
|
182
|
+
|
|
155
183
|
id = "Odb.CheckMacroAntennaProperties"
|
|
156
184
|
name = "Check Antenna Properties of Macros Pins in Their LEF Views"
|
|
157
185
|
inputs = OdbpyStep.inputs
|
|
@@ -180,7 +208,7 @@ class CheckMacroAntennaProperties(OdbpyStep):
|
|
|
180
208
|
args += ["--cell-name", name]
|
|
181
209
|
return super().get_command() + args
|
|
182
210
|
|
|
183
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
211
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
184
212
|
if not self.get_cells():
|
|
185
213
|
info("No cells provided, skipping…")
|
|
186
214
|
return {}, {}
|
|
@@ -189,6 +217,10 @@ class CheckMacroAntennaProperties(OdbpyStep):
|
|
|
189
217
|
|
|
190
218
|
@Step.factory.register()
|
|
191
219
|
class CheckDesignAntennaProperties(CheckMacroAntennaProperties):
|
|
220
|
+
"""
|
|
221
|
+
Prints warnings if the LEF view of the design is missing antenna information.
|
|
222
|
+
"""
|
|
223
|
+
|
|
192
224
|
id = "Odb.CheckDesignAntennaProperties"
|
|
193
225
|
name = "Check Antenna Properties of Pins in The Generated Design LEF view"
|
|
194
226
|
inputs = CheckMacroAntennaProperties.inputs + [DesignFormat.LEF]
|
|
@@ -244,7 +276,7 @@ class ApplyDEFTemplate(OdbpyStep):
|
|
|
244
276
|
args.append("--copy-def-power")
|
|
245
277
|
return super().get_command() + args
|
|
246
278
|
|
|
247
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
279
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
248
280
|
if self.config["FP_DEF_TEMPLATE"] is None:
|
|
249
281
|
info("No DEF template provided, skipping…")
|
|
250
282
|
return {}, {}
|
|
@@ -339,7 +371,7 @@ class WriteVerilogHeader(OdbpyStep):
|
|
|
339
371
|
|
|
340
372
|
return command
|
|
341
373
|
|
|
342
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
374
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
343
375
|
views_updates, metrics_updates = super().run(state_in, **kwargs)
|
|
344
376
|
views_updates[DesignFormat.VERILOG_HEADER] = Path(
|
|
345
377
|
os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.vh")
|
|
@@ -389,9 +421,7 @@ class ManualMacroPlacement(OdbpyStep):
|
|
|
389
421
|
)
|
|
390
422
|
shutil.copyfile(cfg_ref, cfg_file)
|
|
391
423
|
elif macros := self.config.get("MACROS"):
|
|
392
|
-
instance_count =
|
|
393
|
-
lambda x, y: x + len(y.instances), macros.values(), 0
|
|
394
|
-
)
|
|
424
|
+
instance_count = sum(len(m.instances) for m in macros.values())
|
|
395
425
|
if instance_count >= 1:
|
|
396
426
|
with open(cfg_file, "w") as f:
|
|
397
427
|
for module, macro in macros.items():
|
|
@@ -497,6 +527,11 @@ class ReportDisconnectedPins(OdbpyStep):
|
|
|
497
527
|
|
|
498
528
|
@Step.factory.register()
|
|
499
529
|
class AddRoutingObstructions(OdbpyStep):
|
|
530
|
+
"""
|
|
531
|
+
Adds obstructions on metal layers which prevent shapes from being created in
|
|
532
|
+
the designated areas.
|
|
533
|
+
"""
|
|
534
|
+
|
|
500
535
|
id = "Odb.AddRoutingObstructions"
|
|
501
536
|
name = "Add Obstructions"
|
|
502
537
|
config_vars = [
|
|
@@ -528,7 +563,7 @@ class AddRoutingObstructions(OdbpyStep):
|
|
|
528
563
|
command.append(obstruction)
|
|
529
564
|
return command
|
|
530
565
|
|
|
531
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
566
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
532
567
|
if self.config[self.get_obstruction_variable().name] is None:
|
|
533
568
|
info(
|
|
534
569
|
f"'{self.get_obstruction_variable().name}' is not defined. Skipping '{self.id}'…"
|
|
@@ -539,6 +574,11 @@ class AddRoutingObstructions(OdbpyStep):
|
|
|
539
574
|
|
|
540
575
|
@Step.factory.register()
|
|
541
576
|
class RemoveRoutingObstructions(AddRoutingObstructions):
|
|
577
|
+
"""
|
|
578
|
+
Removes any routing obstructions previously placed by
|
|
579
|
+
<#Odb.AddRoutingObstructions>`_.
|
|
580
|
+
"""
|
|
581
|
+
|
|
542
582
|
id = "Odb.RemoveRoutingObstructions"
|
|
543
583
|
name = "Remove Obstructions"
|
|
544
584
|
|
|
@@ -548,6 +588,15 @@ class RemoveRoutingObstructions(AddRoutingObstructions):
|
|
|
548
588
|
|
|
549
589
|
@Step.factory.register()
|
|
550
590
|
class AddPDNObstructions(AddRoutingObstructions):
|
|
591
|
+
"""
|
|
592
|
+
Adds obstructions on metal layers which prevent shapes from being created in
|
|
593
|
+
the designated areas.
|
|
594
|
+
|
|
595
|
+
A soft-duplicate of <#Odb.AddRoutingObstructions>`_ , though this one uses
|
|
596
|
+
a different variable name so the obstructions can be restricted for PDN
|
|
597
|
+
steps only.
|
|
598
|
+
"""
|
|
599
|
+
|
|
551
600
|
id = "Odb.AddPDNObstructions"
|
|
552
601
|
name = "Add PDN obstructions"
|
|
553
602
|
|
|
@@ -565,6 +614,11 @@ class AddPDNObstructions(AddRoutingObstructions):
|
|
|
565
614
|
|
|
566
615
|
@Step.factory.register()
|
|
567
616
|
class RemovePDNObstructions(RemoveRoutingObstructions):
|
|
617
|
+
"""
|
|
618
|
+
Removes any PDN obstructions previously placed by
|
|
619
|
+
<#Odb.RemovePDNObstructions>`_.
|
|
620
|
+
"""
|
|
621
|
+
|
|
568
622
|
id = "Odb.RemovePDNObstructions"
|
|
569
623
|
name = "Remove PDN obstructions"
|
|
570
624
|
|
|
@@ -659,7 +713,7 @@ class CustomIOPlacement(OdbpyStep):
|
|
|
659
713
|
+ length_args
|
|
660
714
|
)
|
|
661
715
|
|
|
662
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
716
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
663
717
|
if self.config["FP_PIN_ORDER_CFG"] is None:
|
|
664
718
|
info("No custom floorplan file configured, skipping…")
|
|
665
719
|
return {}, {}
|
|
@@ -895,7 +949,7 @@ class CellFrequencyTables(OdbpyStep):
|
|
|
895
949
|
def get_buffer_list_script(self):
|
|
896
950
|
return os.path.join(get_script_dir(), "openroad", "buffer_list.tcl")
|
|
897
951
|
|
|
898
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
952
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
899
953
|
kwargs, env = self.extract_env(kwargs)
|
|
900
954
|
|
|
901
955
|
env_copy = env.copy()
|
|
@@ -948,8 +1002,119 @@ class ManualGlobalPlacement(OdbpyStep):
|
|
|
948
1002
|
assert self.config_path is not None, "get_command called before start()"
|
|
949
1003
|
return super().get_command() + ["--step-config", self.config_path]
|
|
950
1004
|
|
|
951
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
1005
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
952
1006
|
if self.config["MANUAL_GLOBAL_PLACEMENTS"] is None:
|
|
953
1007
|
info("'MANUAL_GLOBAL_PLACEMENTS' not set, skipping…")
|
|
954
1008
|
return {}, {}
|
|
955
1009
|
return super().run(state_in, **kwargs)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
@dataclass
|
|
1013
|
+
class ECOBuffer:
|
|
1014
|
+
"""
|
|
1015
|
+
:param target: The driver to insert an ECO buffer after or sink to insert an
|
|
1016
|
+
ECO buffer before, in the format instance_name/pin_name.
|
|
1017
|
+
:param buffer: The kind of buffer cell to use.
|
|
1018
|
+
:param placement: The coarse placement for this buffer (to be legalized.)
|
|
1019
|
+
If unset, depending on whether the target is a driver or a sink:
|
|
1020
|
+
|
|
1021
|
+
- Driver: The placement will be the average of the driver and all sinks.
|
|
1022
|
+
|
|
1023
|
+
- Sink: The placement will be the average of the sink and all drivers.
|
|
1024
|
+
"""
|
|
1025
|
+
|
|
1026
|
+
target: str
|
|
1027
|
+
buffer: str
|
|
1028
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
@Step.factory.register()
|
|
1032
|
+
class InsertECOBuffers(OdbpyStep):
|
|
1033
|
+
"""
|
|
1034
|
+
Experimental step to insert ECO buffers on either drivers or sinks after
|
|
1035
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1036
|
+
incrementally re-run for affected nets. Useful for manually fixing some hold
|
|
1037
|
+
violations.
|
|
1038
|
+
|
|
1039
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1040
|
+
nets that are altered are removed and require re-routing.
|
|
1041
|
+
|
|
1042
|
+
INOUT and FEEDTHRU ports are not supported.
|
|
1043
|
+
"""
|
|
1044
|
+
|
|
1045
|
+
id = "Odb.InsertECOBuffers"
|
|
1046
|
+
name = "Insert ECO Buffers"
|
|
1047
|
+
|
|
1048
|
+
config_vars = (
|
|
1049
|
+
dpl_variables
|
|
1050
|
+
+ grt_variables
|
|
1051
|
+
+ [
|
|
1052
|
+
Variable(
|
|
1053
|
+
"INSERT_ECO_BUFFERS",
|
|
1054
|
+
Optional[List[ECOBuffer]],
|
|
1055
|
+
"List of buffers to insert",
|
|
1056
|
+
)
|
|
1057
|
+
]
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
def get_script_path(self):
|
|
1061
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_buffer.py")
|
|
1062
|
+
|
|
1063
|
+
def get_command(self) -> List[str]:
|
|
1064
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1065
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
@dataclass
|
|
1069
|
+
class ECODiode:
|
|
1070
|
+
"""
|
|
1071
|
+
:param target: The sink whose net gets a diode connected, in the format
|
|
1072
|
+
instance_name/pin_name.
|
|
1073
|
+
:param placement: The coarse placement for this diode (to be legalized.)
|
|
1074
|
+
If unset, the diode is placed at the same location as the target
|
|
1075
|
+
instance, with legalization later moving it to a valid location.
|
|
1076
|
+
"""
|
|
1077
|
+
|
|
1078
|
+
target: str
|
|
1079
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
@Step.factory.register()
|
|
1083
|
+
class InsertECODiodes(OdbpyStep):
|
|
1084
|
+
"""
|
|
1085
|
+
Experimental step to create and attach ECO diodes to the nets of sinks after
|
|
1086
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1087
|
+
incrementally re-run for affected nets. Useful for manually fixing some
|
|
1088
|
+
antenna violations.
|
|
1089
|
+
|
|
1090
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1091
|
+
nets that are altered are removed and require re-routing.
|
|
1092
|
+
"""
|
|
1093
|
+
|
|
1094
|
+
id = "Odb.InsertECODiodes"
|
|
1095
|
+
name = "Insert ECO Diodes"
|
|
1096
|
+
|
|
1097
|
+
config_vars = (
|
|
1098
|
+
grt_variables
|
|
1099
|
+
+ dpl_variables
|
|
1100
|
+
+ [
|
|
1101
|
+
Variable(
|
|
1102
|
+
"INSERT_ECO_DIODES",
|
|
1103
|
+
Optional[List[ECODiode]],
|
|
1104
|
+
"List of sinks to insert diodes for.",
|
|
1105
|
+
)
|
|
1106
|
+
]
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
def get_script_path(self):
|
|
1110
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_diode.py")
|
|
1111
|
+
|
|
1112
|
+
def get_command(self) -> List[str]:
|
|
1113
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1114
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1115
|
+
|
|
1116
|
+
def run(self, state_in: State, **kwargs):
|
|
1117
|
+
if self.config["DIODE_CELL"] is None:
|
|
1118
|
+
info(f"'DIODE_CELL' not set. Skipping '{self.id}'…")
|
|
1119
|
+
return {}, {}
|
|
1120
|
+
return super().run(state_in, **kwargs)
|
librelane/steps/openroad.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# Copyright 2025 LibreLane Contributors
|
|
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");
|
|
@@ -15,8 +19,6 @@ import io
|
|
|
15
19
|
import os
|
|
16
20
|
import re
|
|
17
21
|
import json
|
|
18
|
-
import tempfile
|
|
19
|
-
import functools
|
|
20
22
|
import subprocess
|
|
21
23
|
from enum import Enum
|
|
22
24
|
from math import inf
|
|
@@ -185,6 +187,8 @@ class OpenROADStep(TclStep):
|
|
|
185
187
|
|
|
186
188
|
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
187
189
|
|
|
190
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
191
|
+
|
|
188
192
|
config_vars = [
|
|
189
193
|
Variable(
|
|
190
194
|
"PDN_CONNECT_MACROS_TO_GRID",
|
|
@@ -264,6 +268,7 @@ class OpenROADStep(TclStep):
|
|
|
264
268
|
2. After the `super()` call: Processes the `or_metrics_out.json` file and
|
|
265
269
|
updates the State's `metrics` property with any new metrics in that object.
|
|
266
270
|
"""
|
|
271
|
+
self.alerts = None
|
|
267
272
|
kwargs, env = self.extract_env(kwargs)
|
|
268
273
|
env = self.prepare_env(env, state_in)
|
|
269
274
|
|
|
@@ -293,9 +298,11 @@ class OpenROADStep(TclStep):
|
|
|
293
298
|
views_updates[output] = path
|
|
294
299
|
|
|
295
300
|
# 1. Parse warnings and errors
|
|
296
|
-
alerts = subprocess_result
|
|
301
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
297
302
|
if subprocess_result["returncode"] != 0:
|
|
298
|
-
error_strings = [
|
|
303
|
+
error_strings = [
|
|
304
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
305
|
+
]
|
|
299
306
|
if len(error_strings):
|
|
300
307
|
error_string = "\n".join(error_strings)
|
|
301
308
|
raise StepError(
|
|
@@ -773,7 +780,10 @@ class STAPostPNR(STAPrePNR):
|
|
|
773
780
|
),
|
|
774
781
|
]
|
|
775
782
|
|
|
776
|
-
inputs = STAPrePNR.inputs + [
|
|
783
|
+
inputs = STAPrePNR.inputs + [
|
|
784
|
+
DesignFormat.SPEF,
|
|
785
|
+
DesignFormat.ODB.mkOptional(),
|
|
786
|
+
]
|
|
777
787
|
outputs = STAPrePNR.outputs + [DesignFormat.LIB]
|
|
778
788
|
|
|
779
789
|
def prepare_env(self, env: dict, state: State) -> dict:
|
|
@@ -846,19 +856,21 @@ class STAPostPNR(STAPrePNR):
|
|
|
846
856
|
) -> MetricsUpdate:
|
|
847
857
|
current_env["_LIB_SAVE_DIR"] = corner_dir
|
|
848
858
|
metrics_updates = super().run_corner(state_in, current_env, corner, corner_dir)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
859
|
+
filter_unannotated_metrics = {}
|
|
860
|
+
if odb := state_in[DesignFormat.ODB]:
|
|
861
|
+
try:
|
|
862
|
+
filter_unannotated_metrics = self.filter_unannotated_report(
|
|
863
|
+
corner=corner,
|
|
864
|
+
checks_report=os.path.join(corner_dir, "checks.rpt"),
|
|
865
|
+
corner_dir=corner_dir,
|
|
866
|
+
env=current_env,
|
|
867
|
+
odb_design=str(odb),
|
|
868
|
+
)
|
|
869
|
+
except subprocess.CalledProcessError as e:
|
|
870
|
+
self.err(
|
|
871
|
+
f"Failed filtering unannotated nets for the {corner} timing corner."
|
|
872
|
+
)
|
|
873
|
+
raise e
|
|
862
874
|
return {**metrics_updates, **filter_unannotated_metrics}
|
|
863
875
|
|
|
864
876
|
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
@@ -1039,11 +1051,6 @@ class IOPlacement(OpenROADStep):
|
|
|
1039
1051
|
Optional[Path],
|
|
1040
1052
|
"Path to a custom pin configuration file.",
|
|
1041
1053
|
),
|
|
1042
|
-
Variable(
|
|
1043
|
-
"FP_DEF_TEMPLATE",
|
|
1044
|
-
Optional[Path],
|
|
1045
|
-
"Points to the DEF file to be used as a template.",
|
|
1046
|
-
),
|
|
1047
1054
|
Variable(
|
|
1048
1055
|
"FP_IO_VLENGTH",
|
|
1049
1056
|
Optional[Decimal],
|
|
@@ -1134,9 +1141,7 @@ def get_psm_error_count(rpt: io.TextIOWrapper) -> int:
|
|
|
1134
1141
|
|
|
1135
1142
|
sio.seek(0)
|
|
1136
1143
|
violations = yaml.load(sio, Loader=yaml.SafeLoader) or []
|
|
1137
|
-
return
|
|
1138
|
-
lambda acc, current: acc + len(current["srcs"]), violations, 0
|
|
1139
|
-
)
|
|
1144
|
+
return sum(len(violation["srcs"]) for violation in violations)
|
|
1140
1145
|
|
|
1141
1146
|
|
|
1142
1147
|
@Step.factory.register()
|
|
@@ -1317,11 +1322,6 @@ class GlobalPlacementSkipIO(_GlobalPlacement):
|
|
|
1317
1322
|
default="matching",
|
|
1318
1323
|
deprecated_names=[("FP_IO_MODE", _migrate_ppl_mode)],
|
|
1319
1324
|
),
|
|
1320
|
-
Variable(
|
|
1321
|
-
"FP_DEF_TEMPLATE",
|
|
1322
|
-
Optional[Path],
|
|
1323
|
-
"Points to the DEF file to be used as a template.",
|
|
1324
|
-
),
|
|
1325
1325
|
]
|
|
1326
1326
|
|
|
1327
1327
|
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
@@ -2127,7 +2127,7 @@ class RepairDesignPostGPL(ResizerStep):
|
|
|
2127
2127
|
Variable(
|
|
2128
2128
|
"DESIGN_REPAIR_BUFFER_OUTPUT_PORTS",
|
|
2129
2129
|
bool,
|
|
2130
|
-
"Specifies whether or not to insert buffers on
|
|
2130
|
+
"Specifies whether or not to insert buffers on output ports when design repairs are run.",
|
|
2131
2131
|
default=True,
|
|
2132
2132
|
deprecated_names=["PL_RESIZER_BUFFER_OUTPUT_PORTS"],
|
|
2133
2133
|
),
|
|
@@ -2404,30 +2404,47 @@ class DEFtoODB(OpenROADStep):
|
|
|
2404
2404
|
|
|
2405
2405
|
|
|
2406
2406
|
@Step.factory.register()
|
|
2407
|
-
class OpenGUI(
|
|
2407
|
+
class OpenGUI(OpenSTAStep):
|
|
2408
2408
|
"""
|
|
2409
2409
|
Opens the ODB view in the OpenROAD GUI. Useful to inspect some parameters,
|
|
2410
|
-
such as routing density and whatnot.
|
|
2410
|
+
such as routing density, timing paths, clock tree and whatnot.
|
|
2411
|
+
The LIBs are loaded by default and the SPEFs if available.
|
|
2411
2412
|
"""
|
|
2412
2413
|
|
|
2413
2414
|
id = "OpenROAD.OpenGUI"
|
|
2414
2415
|
name = "Open In GUI"
|
|
2415
2416
|
|
|
2416
|
-
inputs = [
|
|
2417
|
+
inputs = [
|
|
2418
|
+
DesignFormat.ODB,
|
|
2419
|
+
DesignFormat.SPEF.mkOptional(),
|
|
2420
|
+
]
|
|
2417
2421
|
outputs = []
|
|
2418
2422
|
|
|
2423
|
+
def get_script_path(self) -> str:
|
|
2424
|
+
return os.path.join(get_script_dir(), "openroad", "gui.tcl")
|
|
2425
|
+
|
|
2426
|
+
def get_command(self) -> List[str]:
|
|
2427
|
+
return [
|
|
2428
|
+
"openroad",
|
|
2429
|
+
"-no_splash",
|
|
2430
|
+
"-gui",
|
|
2431
|
+
self.get_script_path(),
|
|
2432
|
+
]
|
|
2433
|
+
|
|
2419
2434
|
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2435
|
+
kwargs, env = self.extract_env(kwargs)
|
|
2436
|
+
|
|
2437
|
+
corner_name, file_list = self._get_corner_files(prioritize_nl=True)
|
|
2438
|
+
file_list.set_env(env)
|
|
2439
|
+
env["_CURRENT_CORNER_NAME"] = corner_name
|
|
2440
|
+
|
|
2441
|
+
env = self.prepare_env(env, state_in)
|
|
2442
|
+
|
|
2443
|
+
command = self.get_command()
|
|
2444
|
+
self.run_subprocess(
|
|
2445
|
+
command,
|
|
2446
|
+
env=env,
|
|
2447
|
+
**kwargs,
|
|
2448
|
+
)
|
|
2432
2449
|
|
|
2433
2450
|
return {}, {}
|
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
|
|
|
@@ -291,6 +302,12 @@ class VerilogStep(PyosysStep):
|
|
|
291
302
|
|
|
292
303
|
@Step.factory.register()
|
|
293
304
|
class JsonHeader(VerilogStep):
|
|
305
|
+
"""
|
|
306
|
+
Extracts a high-level hierarchical view of the circuit in JSON format,
|
|
307
|
+
including power connections. The power connections are used in later steps
|
|
308
|
+
to ensure macros and cells are connected as desired.
|
|
309
|
+
"""
|
|
310
|
+
|
|
294
311
|
id = "Yosys.JsonHeader"
|
|
295
312
|
name = "Generate JSON Header"
|
|
296
313
|
long_name = "Generate JSON Header"
|