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.
Files changed (55) hide show
  1. librelane/__init__.py +1 -1
  2. librelane/__main__.py +34 -27
  3. librelane/common/__init__.py +2 -0
  4. librelane/common/cli.py +1 -1
  5. librelane/common/drc.py +1 -0
  6. librelane/common/generic_dict.py +1 -1
  7. librelane/common/metrics/__main__.py +1 -1
  8. librelane/common/misc.py +58 -2
  9. librelane/common/tcl.py +2 -1
  10. librelane/common/types.py +2 -3
  11. librelane/config/__main__.py +1 -4
  12. librelane/config/flow.py +2 -2
  13. librelane/config/preprocessor.py +1 -1
  14. librelane/config/variable.py +136 -7
  15. librelane/container.py +55 -31
  16. librelane/env_info.py +129 -115
  17. librelane/examples/hold_eco_demo/config.yaml +18 -0
  18. librelane/examples/hold_eco_demo/demo.v +27 -0
  19. librelane/flows/cli.py +39 -23
  20. librelane/flows/flow.py +100 -36
  21. librelane/help/__main__.py +39 -0
  22. librelane/scripts/magic/def/mag_gds.tcl +0 -2
  23. librelane/scripts/magic/drc.tcl +0 -1
  24. librelane/scripts/magic/gds/extras_mag.tcl +0 -2
  25. librelane/scripts/magic/gds/mag_with_pointers.tcl +0 -1
  26. librelane/scripts/magic/lef/extras_maglef.tcl +0 -2
  27. librelane/scripts/magic/lef/maglef.tcl +0 -1
  28. librelane/scripts/magic/wrapper.tcl +2 -0
  29. librelane/scripts/odbpy/defutil.py +15 -10
  30. librelane/scripts/odbpy/eco_buffer.py +182 -0
  31. librelane/scripts/odbpy/eco_diode.py +140 -0
  32. librelane/scripts/odbpy/ioplace_parser/__init__.py +1 -1
  33. librelane/scripts/odbpy/ioplace_parser/parse.py +1 -1
  34. librelane/scripts/odbpy/power_utils.py +8 -6
  35. librelane/scripts/odbpy/reader.py +17 -13
  36. librelane/scripts/openroad/common/io.tcl +66 -2
  37. librelane/scripts/openroad/gui.tcl +23 -1
  38. librelane/state/design_format.py +16 -1
  39. librelane/state/state.py +11 -3
  40. librelane/steps/__init__.py +1 -1
  41. librelane/steps/__main__.py +4 -4
  42. librelane/steps/checker.py +7 -8
  43. librelane/steps/klayout.py +11 -1
  44. librelane/steps/magic.py +24 -14
  45. librelane/steps/misc.py +5 -0
  46. librelane/steps/odb.py +193 -28
  47. librelane/steps/openroad.py +64 -47
  48. librelane/steps/pyosys.py +18 -1
  49. librelane/steps/step.py +36 -17
  50. librelane/steps/yosys.py +9 -1
  51. {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/METADATA +10 -11
  52. {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/RECORD +54 -50
  53. {librelane-2.4.0.dev2.dist-info → librelane-2.4.7.dist-info}/entry_points.txt +1 -0
  54. librelane/scripts/odbpy/exception_codes.py +0 -17
  55. {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 .tclstep import TclStep
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
- ViewsUpdate,
33
+ CompositeStep,
34
+ DefaultOutputProcessor,
34
35
  MetricsUpdate,
35
36
  Step,
37
+ StepError,
36
38
  StepException,
37
- CompositeStep,
38
- DefaultOutputProcessor,
39
+ ViewsUpdate,
39
40
  )
40
- from ..logging import info, verbose
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
- metrics_updates.update(or_metrics_out)
127
+ generated_metrics.update(or_metrics_out)
106
128
 
107
- return views_updates, metrics_updates
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 = reduce(
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)
@@ -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["openroad_alerts"]
301
+ self.alerts = subprocess_result.get("openroad_alerts") or []
297
302
  if subprocess_result["returncode"] != 0:
298
- error_strings = [str(alert) for alert in alerts if alert.cls == "error"]
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 + [DesignFormat.SPEF, DesignFormat.ODB]
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
- try:
850
- filter_unannotated_metrics = self.filter_unannotated_report(
851
- corner=corner,
852
- checks_report=os.path.join(corner_dir, "checks.rpt"),
853
- corner_dir=corner_dir,
854
- env=current_env,
855
- odb_design=str(state_in[DesignFormat.ODB]),
856
- )
857
- except subprocess.CalledProcessError as e:
858
- self.err(
859
- f"Failed filtering unannotated nets for the {corner} timing corner."
860
- )
861
- raise e
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 functools.reduce(
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 input ports when design repairs are run.",
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(Step):
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 = [DesignFormat.ODB]
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
- with tempfile.NamedTemporaryFile("a+", suffix=".tcl") as f:
2421
- f.write(f"read_db \"{state_in['odb']}\"")
2422
- f.flush()
2423
-
2424
- subprocess.check_call(
2425
- [
2426
- "openroad",
2427
- "-no_splash",
2428
- "-gui",
2429
- f.name,
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
- cmd = ["yosys", "-y", script_path]
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"