librelane 2.4.0.dev9__py3-none-any.whl → 3.0.0.dev23__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.

Files changed (62) hide show
  1. librelane/__main__.py +12 -15
  2. librelane/common/__init__.py +1 -1
  3. librelane/common/drc.py +88 -7
  4. librelane/common/misc.py +6 -6
  5. librelane/common/toolbox.py +1 -1
  6. librelane/config/config.py +5 -1
  7. librelane/config/flow.py +51 -66
  8. librelane/config/pdk_compat.py +79 -2
  9. librelane/config/preprocessor.py +1 -1
  10. librelane/config/variable.py +2 -2
  11. librelane/flows/classic.py +1 -0
  12. librelane/flows/flow.py +3 -6
  13. librelane/flows/sequential.py +85 -40
  14. librelane/plugins.py +1 -1
  15. librelane/scripts/magic/common/read.tcl +2 -2
  16. librelane/scripts/magic/gds/extras_mag.tcl +2 -2
  17. librelane/scripts/odbpy/diodes.py +2 -2
  18. librelane/scripts/openroad/common/dpl.tcl +1 -1
  19. librelane/scripts/openroad/common/grt.tcl +3 -3
  20. librelane/scripts/openroad/common/io.tcl +163 -45
  21. librelane/scripts/openroad/common/resizer.tcl +1 -40
  22. librelane/scripts/openroad/common/set_global_connections.tcl +2 -2
  23. librelane/scripts/openroad/common/set_power_nets.tcl +1 -1
  24. librelane/scripts/openroad/common/set_rc.tcl +159 -40
  25. librelane/scripts/openroad/cts.tcl +37 -6
  26. librelane/scripts/openroad/cut_rows.tcl +19 -4
  27. librelane/scripts/openroad/drt.tcl +59 -8
  28. librelane/scripts/openroad/dump_rc.tcl +105 -0
  29. librelane/scripts/openroad/fill.tcl +2 -2
  30. librelane/scripts/openroad/floorplan.tcl +5 -3
  31. librelane/scripts/openroad/gpl.tcl +7 -8
  32. librelane/scripts/openroad/insert_buffer.tcl +2 -2
  33. librelane/scripts/openroad/ioplacer.tcl +1 -2
  34. librelane/scripts/openroad/irdrop.tcl +3 -3
  35. librelane/scripts/openroad/pdn.tcl +17 -18
  36. librelane/scripts/openroad/rcx.tcl +1 -1
  37. librelane/scripts/openroad/repair_design.tcl +14 -7
  38. librelane/scripts/openroad/repair_design_postgrt.tcl +13 -6
  39. librelane/scripts/openroad/rsz_timing_postcts.tcl +13 -12
  40. librelane/scripts/openroad/rsz_timing_postgrt.tcl +13 -12
  41. librelane/scripts/openroad/sta/check_macro_instances.tcl +1 -1
  42. librelane/scripts/openroad/tapcell.tcl +13 -6
  43. librelane/scripts/openroad/ungpl.tcl +23 -0
  44. librelane/state/__init__.py +1 -1
  45. librelane/state/design_format.py +194 -142
  46. librelane/state/state.py +20 -21
  47. librelane/steps/checker.py +12 -1
  48. librelane/steps/common_variables.py +4 -4
  49. librelane/steps/cvc_rv.py +1 -1
  50. librelane/steps/klayout.py +14 -6
  51. librelane/steps/magic.py +18 -2
  52. librelane/steps/misc.py +1 -1
  53. librelane/steps/odb.py +50 -31
  54. librelane/steps/openroad.py +455 -128
  55. librelane/steps/pyosys.py +20 -5
  56. librelane/steps/step.py +17 -20
  57. librelane/steps/tclstep.py +9 -7
  58. librelane/steps/yosys.py +1 -1
  59. {librelane-2.4.0.dev9.dist-info → librelane-3.0.0.dev23.dist-info}/METADATA +1 -1
  60. {librelane-2.4.0.dev9.dist-info → librelane-3.0.0.dev23.dist-info}/RECORD +62 -60
  61. {librelane-2.4.0.dev9.dist-info → librelane-3.0.0.dev23.dist-info}/WHEEL +0 -0
  62. {librelane-2.4.0.dev9.dist-info → librelane-3.0.0.dev23.dist-info}/entry_points.txt +0 -0
@@ -21,66 +21,56 @@ import re
21
21
  import json
22
22
  import functools
23
23
  import subprocess
24
- from enum import Enum
25
- from math import inf
26
- from glob import glob
27
- from decimal import Decimal
28
- from base64 import b64encode
24
+ import textwrap
25
+ import pathlib
29
26
  from abc import abstractmethod
30
- from dataclasses import dataclass
27
+ from base64 import b64encode
31
28
  from concurrent.futures import Future, ThreadPoolExecutor
32
- from typing import (
33
- Any,
34
- List,
35
- Dict,
36
- Literal,
37
- Set,
38
- Tuple,
39
- Optional,
40
- Union,
41
- )
42
-
29
+ from dataclasses import dataclass
30
+ from decimal import Decimal
31
+ from enum import Enum
32
+ from glob import glob
33
+ from math import inf
34
+ from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union
43
35
 
44
- import yaml
45
36
  import rich
46
37
  import rich.table
38
+ import yaml
47
39
 
40
+ from ..common import (
41
+ Path,
42
+ Filter,
43
+ TclUtils,
44
+ DRC as DRCObject,
45
+ _get_process_limit,
46
+ aggregate_metrics,
47
+ get_script_dir,
48
+ mkdirp,
49
+ process_list_file,
50
+ )
51
+ from ..config import Macro, Variable
52
+ from ..config.flow import option_variables
53
+ from ..logging import console, debug, info, options, verbose
54
+ from ..state import DesignFormat, State
55
+ from .common_variables import (
56
+ dpl_variables,
57
+ grt_variables,
58
+ io_layer_variables,
59
+ pdn_variables,
60
+ routing_layer_variables,
61
+ rsz_variables,
62
+ )
63
+ from .openroad_alerts import OpenROADAlert, OpenROADOutputProcessor
48
64
  from .step import (
49
65
  CompositeStep,
50
66
  DefaultOutputProcessor,
51
- StepError,
52
- ViewsUpdate,
53
67
  MetricsUpdate,
54
68
  Step,
69
+ StepError,
55
70
  StepException,
56
- )
57
- from .openroad_alerts import (
58
- OpenROADAlert,
59
- OpenROADOutputProcessor,
71
+ ViewsUpdate,
60
72
  )
61
73
  from .tclstep import TclStep
62
- from .common_variables import (
63
- io_layer_variables,
64
- pdn_variables,
65
- rsz_variables,
66
- dpl_variables,
67
- grt_variables,
68
- routing_layer_variables,
69
- )
70
-
71
- from ..config import Variable, Macro
72
- from ..config.flow import option_variables
73
- from ..state import State, DesignFormat
74
- from ..logging import debug, info, verbose, console, options
75
- from ..common import (
76
- Path,
77
- TclUtils,
78
- get_script_dir,
79
- mkdirp,
80
- aggregate_metrics,
81
- process_list_file,
82
- _get_process_limit,
83
- )
84
74
 
85
75
  EXAMPLE_INPUT = """
86
76
  li1 X 0.23 0.46
@@ -130,6 +120,22 @@ def pdn_macro_migrator(x):
130
120
  return [x.strip()]
131
121
 
132
122
 
123
+ DesignFormat(
124
+ "odb",
125
+ "odb",
126
+ "OpenDB Database",
127
+ alts=["ODB"],
128
+ ).register()
129
+
130
+ DesignFormat(
131
+ "openroad_lef",
132
+ "openroad.lef",
133
+ "Library Exchange Format Generated by OpenROAD",
134
+ alts=["OPENROAD_LEF"],
135
+ folder_override="lef",
136
+ ).register()
137
+
138
+
133
139
  @Step.factory.register()
134
140
  class CheckSDCFiles(Step):
135
141
  """
@@ -159,7 +165,7 @@ class CheckSDCFiles(Step):
159
165
 
160
166
  def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
161
167
  default_sdc_file = [
162
- var for var in option_variables if var.name == "FALLBACK_SDC_FILE"
168
+ var for var in option_variables if var.name == "FALLBACK_SDC"
163
169
  ][0]
164
170
  assert default_sdc_file is not None
165
171
 
@@ -191,6 +197,38 @@ class OpenROADStep(TclStep):
191
197
  alerts: Optional[List[OpenROADAlert]] = None
192
198
 
193
199
  config_vars = [
200
+ Variable(
201
+ "PNR_CORNERS",
202
+ Optional[List[str]],
203
+ "A list of fully-qualified IPVT corners to use during PnR. If unspecified, the value for `STA_CORNERS` from the PDK will be used.",
204
+ pdk=True,
205
+ ),
206
+ Variable(
207
+ "SET_RC_VERBOSE",
208
+ bool,
209
+ "If set to true, set_rc commands are echoed. Quite noisy, but may be useful for debugging.",
210
+ default=False,
211
+ ),
212
+ Variable(
213
+ "LAYERS_RC",
214
+ Optional[Dict[str, Dict[str, Dict[str, Decimal]]]],
215
+ "Used during PNR steps, Specific custom resistance and capacitance values for metal layers."
216
+ + " For each IPVT corner, a mapping for each metal layer is provided."
217
+ + " Each mapping describes custom resistance and capacitance values."
218
+ + " Usage of wildcards for specifying IPVT corners is allowed."
219
+ + " Units are resistance and capacitance per unit length as defined in the first lib file.",
220
+ pdk=True,
221
+ ),
222
+ Variable(
223
+ "VIAS_R",
224
+ Optional[Dict[str, Dict[str, Dict[str, Decimal]]]],
225
+ "Used during PNR steps, Specific custom resistance values for via layers."
226
+ + " For each IPVT corner, a mapping for each via layer is provided."
227
+ + " Each mapping describes custom resistance values."
228
+ + " Usage of wildcards for specifying IPVT corners is allowed."
229
+ + " Via resistance is per cut/via with units asdefined in the first lib file.",
230
+ pdk=True,
231
+ ),
194
232
  Variable(
195
233
  "PDN_CONNECT_MACROS_TO_GRID",
196
234
  bool,
@@ -221,6 +259,12 @@ class OpenROADStep(TclStep):
221
259
  Optional[Path],
222
260
  "Points to the DEF file to be used as a template.",
223
261
  ),
262
+ Variable(
263
+ "DEDUPLICATE_CORNERS",
264
+ bool,
265
+ "Cull duplicate IPVT corners during PNR, i.e. corners that share the same set of lib files and values for LAYERS_RC and VIAS_R as another corner are not considered outside of STA.",
266
+ default=False,
267
+ ),
224
268
  ]
225
269
 
226
270
  @abstractmethod
@@ -246,7 +290,7 @@ class OpenROADStep(TclStep):
246
290
  lib_list = self.toolbox.filter_views(self.config, self.config["LIB"])
247
291
  lib_list += self.toolbox.get_macro_views(self.config, DesignFormat.LIB)
248
292
 
249
- env["_SDC_IN"] = self.config["PNR_SDC_FILE"] or self.config["FALLBACK_SDC_FILE"]
293
+ env["_SDC_IN"] = self.config["PNR_SDC_FILE"] or self.config["FALLBACK_SDC"]
250
294
  env["_PNR_LIBS"] = TclStep.value_to_tcl(lib_list)
251
295
  env["_MACRO_LIBS"] = TclStep.value_to_tcl(
252
296
  self.toolbox.get_macro_views(self.config, DesignFormat.LIB)
@@ -262,9 +306,8 @@ class OpenROADStep(TclStep):
262
306
  """
263
307
  The `run()` override for the OpenROADStep class handles two things:
264
308
 
265
- 1. Before the `super()` call: It creates a version of the lib file
266
- minus cells that are known bad (i.e. those that fail DRC) and pass it on
267
- in the environment variable `_PNR_LIBS`.
309
+ 1. Before the `super()` call: Process _LIB_CORNER_<i> for liberty/corner
310
+ pairs.
268
311
 
269
312
  2. After the `super()` call: Processes the `or_metrics_out.json` file and
270
313
  updates the State's `metrics` property with any new metrics in that object.
@@ -273,16 +316,111 @@ class OpenROADStep(TclStep):
273
316
  kwargs, env = self.extract_env(kwargs)
274
317
  env = self.prepare_env(env, state_in)
275
318
 
319
+ corners: List[str] = self.config["PNR_CORNERS"] or [
320
+ self.config["DEFAULT_CORNER"]
321
+ ]
322
+
323
+ @dataclass
324
+ class IPVTCorner:
325
+ name: str
326
+ libs: List[Path]
327
+ layers_rc: Optional[Dict[str, Dict[str, Decimal]]]
328
+ vias_r: Optional[Dict[str, Dict[str, Decimal]]]
329
+
330
+ def __eq__(self, other):
331
+ if not isinstance(other, IPVTCorner):
332
+ return False
333
+ return (
334
+ self.libs == other.libs
335
+ and self.layers_rc == other.layers_rc
336
+ and self.vias_r == other.vias_r
337
+ )
338
+
339
+ def __hash__(self):
340
+ return hash(
341
+ (
342
+ frozenset([str(lib) for lib in self.libs]),
343
+ TclStep.value_to_tcl(self.layers_rc),
344
+ TclStep.value_to_tcl(self.vias_r),
345
+ )
346
+ )
347
+
348
+ if "corners" in kwargs:
349
+ corners = kwargs.pop("corners")
350
+ debug(f"Corners Override {corners}")
351
+
352
+ count = 0
353
+ ipvt_corners = {}
354
+ for corner in corners:
355
+ _, libs, _, _ = self.toolbox.get_timing_files_categorized(
356
+ self.config, corner
357
+ )
358
+ ipvt_corners[corner] = IPVTCorner(corner, libs, None, None)
359
+ # debug(f"Liberty files for '{corner}' added: {libs}")
360
+ count += 1
361
+
276
362
  check = False
277
363
  if "check" in kwargs:
278
364
  check = kwargs.pop("check")
279
365
 
366
+ layers_rc = self.config["LAYERS_RC"]
367
+ if layers_rc is not None:
368
+ for corner_wildcard, metal_layers in layers_rc.items():
369
+ for corner in Filter([corner_wildcard]).filter(corners):
370
+ ipvt_corners[corner].layers_rc = metal_layers
371
+
372
+ vias_r = self.config["VIAS_R"]
373
+ if vias_r is not None:
374
+ for corner_wildcard, metal_layers in vias_r.items():
375
+ for corner in Filter(corner_wildcard).filter(corners):
376
+ ipvt_corners[corner].vias_r = metal_layers
377
+
378
+ filtered_ipvt_corners_names_sorted = corners
379
+ if self.config["DEDUPLICATE_CORNERS"]:
380
+ filtered_ipvt_corners = {
381
+ k: v
382
+ for k, v in ipvt_corners.items()
383
+ if k in [corner.name for corner in set(ipvt_corners.values())]
384
+ }
385
+ filtered_ipvt_corners_names_sorted = [
386
+ x for _, x in sorted(zip(corners, filtered_ipvt_corners.keys()))
387
+ ]
388
+ count = 0
389
+ for corner_name in filtered_ipvt_corners_names_sorted:
390
+ vias_r = ipvt_corners[corner_name].vias_r
391
+ if vias_r is not None:
392
+ for via, rc in vias_r.items():
393
+ res = rc["res"]
394
+ env[f"_VIA_R_{count}"] = TclStep.value_to_tcl(
395
+ [corner_name, via, res]
396
+ )
397
+ count += 1
398
+ count = 0
399
+ for corner_name in filtered_ipvt_corners_names_sorted:
400
+ layers_rc = ipvt_corners[corner_name].layers_rc
401
+ if layers_rc is not None:
402
+ for layer, rc in layers_rc.items():
403
+ res = rc["res"]
404
+ cap = rc["cap"]
405
+ env[f"_LAYER_RC_{count}"] = TclStep.value_to_tcl(
406
+ [corner_name, layer, res, cap]
407
+ )
408
+ count += 1
409
+
410
+ count = 0
411
+ for corner_name in filtered_ipvt_corners_names_sorted:
412
+ env[f"_LIB_CORNER_{count}"] = TclStep.value_to_tcl(
413
+ [corner_name] + ipvt_corners[corner_name].libs
414
+ )
415
+ count += 1
416
+
280
417
  command = self.get_command()
281
418
 
282
419
  subprocess_result = self.run_subprocess(
283
420
  command,
284
421
  env=env,
285
422
  check=check,
423
+ cwd=self.step_dir,
286
424
  **kwargs,
287
425
  )
288
426
 
@@ -290,10 +428,10 @@ class OpenROADStep(TclStep):
290
428
 
291
429
  views_updates: ViewsUpdate = {}
292
430
  for output in self.outputs:
293
- if output.value.multiple:
431
+ if output.multiple:
294
432
  # Too step-specific.
295
433
  continue
296
- path = Path(env[f"SAVE_{output.name}"])
434
+ path = Path(env[f"SAVE_{output.id.upper()}"])
297
435
  if not path.exists():
298
436
  continue
299
437
  views_updates[output] = path
@@ -332,7 +470,7 @@ class OpenROADStep(TclStep):
332
470
  metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
333
471
  return [
334
472
  "openroad",
335
- "-exit",
473
+ ("-gui" if os.getenv("_OPENROAD_GUI", "0") == "1" else "-exit"),
336
474
  "-no_splash",
337
475
  "-metrics",
338
476
  metrics_path,
@@ -420,7 +558,7 @@ class OpenSTAStep(OpenROADStep):
420
558
 
421
559
  name = timing_corner
422
560
  current_corner_spef = None
423
- input_spef_dict = state_in[DesignFormat.SPEF]
561
+ input_spef_dict = state_in.get(DesignFormat.SPEF)
424
562
  if input_spef_dict is not None:
425
563
  if not isinstance(input_spef_dict, dict):
426
564
  raise StepException(
@@ -737,7 +875,7 @@ class STAPrePNR(MultiCornerSTA):
737
875
  def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
738
876
  views_updates, metrics_updates = super().run(state_in, **kwargs)
739
877
 
740
- sdf_dict = state_in[DesignFormat.SDF] or {}
878
+ sdf_dict = state_in.get(DesignFormat.SDF, {})
741
879
  if not isinstance(sdf_dict, dict):
742
880
  raise StepException(
743
881
  "Malformed input state: incoming value for SDF is not a dictionary."
@@ -876,7 +1014,7 @@ class STAPostPNR(STAPrePNR):
876
1014
 
877
1015
  def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
878
1016
  views_updates, metrics_updates = super().run(state_in, **kwargs)
879
- lib_dict = state_in[DesignFormat.LIB] or {}
1017
+ lib_dict = state_in.get(DesignFormat.LIB, {})
880
1018
  if not isinstance(lib_dict, dict):
881
1019
  raise StepException(
882
1020
  "Malformed input state: value for LIB is not a dictionary."
@@ -907,6 +1045,19 @@ class Floorplan(OpenROADStep):
907
1045
  inputs = [DesignFormat.NETLIST]
908
1046
 
909
1047
  config_vars = OpenROADStep.config_vars + [
1048
+ Variable(
1049
+ "FP_FLIP_SITES",
1050
+ Optional[List[str]],
1051
+ "Flip these sites vertically. Useful in niche alignment scenarios where single-height cells have ground at the south side and double-height cells have power at the south side, causing a short. In that situation, flipping the sites for single-height cells resolves the issue.",
1052
+ pdk=True,
1053
+ ),
1054
+ Variable(
1055
+ "FP_TRACKS_INFO",
1056
+ Path,
1057
+ "A path to the a classic OpenROAD `.tracks` file. Used by the floorplanner to generate tracks.",
1058
+ deprecated_names=["TRACKS_INFO_FILE"],
1059
+ pdk=True,
1060
+ ),
910
1061
  Variable(
911
1062
  "FP_SIZING",
912
1063
  Literal["absolute", "relative"],
@@ -1101,7 +1252,7 @@ class IOPlacement(OpenROADStep):
1101
1252
  @Step.factory.register()
1102
1253
  class TapEndcapInsertion(OpenROADStep):
1103
1254
  """
1104
- Places well TAP cells across a floorplan, as well as end-cap cells at the
1255
+ Places welltap cells across a floorplan, as well as endcap cells at the
1105
1256
  edges of the floorplan.
1106
1257
  """
1107
1258
 
@@ -1109,10 +1260,17 @@ class TapEndcapInsertion(OpenROADStep):
1109
1260
  name = "Tap/Decap Insertion"
1110
1261
 
1111
1262
  config_vars = OpenROADStep.config_vars + [
1263
+ Variable(
1264
+ "FP_TAPCELL_DIST",
1265
+ Optional[Decimal],
1266
+ "The distance between tap cell columns. Must be specified if WELLTAP_CELL is specified.",
1267
+ units="µm",
1268
+ pdk=True,
1269
+ ),
1112
1270
  Variable(
1113
1271
  "FP_MACRO_HORIZONTAL_HALO",
1114
1272
  Decimal,
1115
- "Specify the horizontal halo size around macros while cutting rows.",
1273
+ "Specify the horizontal halo size around macros.",
1116
1274
  default=10,
1117
1275
  units="µm",
1118
1276
  deprecated_names=["FP_TAP_HORIZONTAL_HALO"],
@@ -1120,7 +1278,7 @@ class TapEndcapInsertion(OpenROADStep):
1120
1278
  Variable(
1121
1279
  "FP_MACRO_VERTICAL_HALO",
1122
1280
  Decimal,
1123
- "Specify the vertical halo size around macros while cutting rows.",
1281
+ "Specify the vertical halo size around macros.",
1124
1282
  default=10,
1125
1283
  units="µm",
1126
1284
  deprecated_names=["FP_TAP_VERTICAL_HALO"],
@@ -1130,6 +1288,30 @@ class TapEndcapInsertion(OpenROADStep):
1130
1288
  def get_script_path(self):
1131
1289
  return os.path.join(get_script_dir(), "openroad", "tapcell.tcl")
1132
1290
 
1291
+ def run(self, state_in, **kwargs):
1292
+ if (
1293
+ self.config["WELLTAP_CELL"] is not None
1294
+ and self.config["FP_TAPCELL_DIST"] is None
1295
+ ):
1296
+ raise StepException("FP_TAPCELL_DIST must be set if WELLTAP_CELL is set.")
1297
+ return super().run(state_in, **kwargs)
1298
+
1299
+
1300
+ @Step.factory.register()
1301
+ class UnplaceAll(OpenROADStep):
1302
+ """
1303
+ Sets placement status of *all* instances to NONE.
1304
+
1305
+ Useful in flows where a preliminary placement is needed as a pre-requisite
1306
+ to something else but that placement must be discarded.
1307
+ """
1308
+
1309
+ id = "OpenROAD.UnplaceAll"
1310
+ name = "Unplace All"
1311
+
1312
+ def get_script_path(self):
1313
+ return os.path.join(get_script_dir(), "openroad", "ungpl.tcl")
1314
+
1133
1315
 
1134
1316
  def get_psm_error_count(rpt: io.TextIOWrapper) -> int:
1135
1317
  sio = io.StringIO()
@@ -1141,9 +1323,9 @@ def get_psm_error_count(rpt: io.TextIOWrapper) -> int:
1141
1323
  vio_type = line[len(VIO_TYPE_PFX) :].strip()
1142
1324
  sio.write(f"- type: {vio_type}\n")
1143
1325
  elif "bbox = " in line:
1144
- sio.write(line.replace("bbox = ", "- bbox ="))
1326
+ sio.write(f" {textwrap.dedent(line.replace('bbox = ', '- bbox ='))}")
1145
1327
  else:
1146
- sio.write(line)
1328
+ sio.write(f" {textwrap.dedent(line)}")
1147
1329
 
1148
1330
  sio.seek(0)
1149
1331
  violations = yaml.load(sio, Loader=yaml.SafeLoader) or []
@@ -1212,7 +1394,7 @@ class _GlobalPlacement(OpenROADStep):
1212
1394
  "The desired placement density of cells. If not specified, the value will be equal to (`FP_CORE_UTIL` + 5 * `GPL_CELL_PADDING` + 10).",
1213
1395
  units="%",
1214
1396
  deprecated_names=[
1215
- ("PL_TARGET_DENSITY", lambda d: Decimal(d) * Decimal(100.0))
1397
+ ("PL_TARGET_DENSITY", lambda d: Decimal(d) * Decimal("100"))
1216
1398
  ],
1217
1399
  ),
1218
1400
  Variable(
@@ -1248,11 +1430,16 @@ class _GlobalPlacement(OpenROADStep):
1248
1430
  ),
1249
1431
  Variable(
1250
1432
  "GPL_CELL_PADDING",
1251
- Decimal,
1433
+ int,
1252
1434
  "Cell padding value (in sites) for global placement. The number will be integer divided by 2 and placed on both sides.",
1253
1435
  units="sites",
1254
1436
  pdk=True,
1255
1437
  ),
1438
+ Variable(
1439
+ "PL_KEEP_RESIZE_BELOW_OVERFLOW",
1440
+ Optional[Decimal],
1441
+ "Only applicable when PL_TIME_DRIVEN is enabled. When the overflow is below the set value, timing-driven iterations will retain the resizer changes instead of reverting them. Allowed values are 0 to 1. If not set, a nonzero default value from OpenROAD will be used",
1442
+ ),
1256
1443
  ]
1257
1444
  )
1258
1445
 
@@ -1311,7 +1498,7 @@ class GlobalPlacement(_GlobalPlacement):
1311
1498
  @Step.factory.register()
1312
1499
  class GlobalPlacementSkipIO(_GlobalPlacement):
1313
1500
  """
1314
- Performs global placement without taking I/O into consideration.
1501
+ Performs preliminary global placement as a basis for pin placement.
1315
1502
 
1316
1503
  This is useful for flows where the:
1317
1504
  * Cells are placed
@@ -1330,6 +1517,11 @@ class GlobalPlacementSkipIO(_GlobalPlacement):
1330
1517
  default="matching",
1331
1518
  deprecated_names=[("FP_IO_MODE", _migrate_ppl_mode)],
1332
1519
  ),
1520
+ Variable(
1521
+ "FP_PIN_ORDER_CFG",
1522
+ Optional[Path],
1523
+ "Path to a custom pin configuration file.",
1524
+ ),
1333
1525
  Variable(
1334
1526
  "FP_DEF_TEMPLATE",
1335
1527
  Optional[Path],
@@ -1341,39 +1533,18 @@ class GlobalPlacementSkipIO(_GlobalPlacement):
1341
1533
  kwargs, env = self.extract_env(kwargs)
1342
1534
  if self.config["FP_DEF_TEMPLATE"] is not None:
1343
1535
  info(
1344
- f"I/O pins were loaded from {self.config['FP_DEF_TEMPLATE']}. Skipping the first global placement iteration…"
1536
+ f"I/O pins were loaded from {self.config['FP_DEF_TEMPLATE']}. Returning state unaltered…"
1537
+ )
1538
+ return {}, {}
1539
+ if self.config["FP_PIN_ORDER_CFG"] is not None:
1540
+ info(
1541
+ f"I/O pins to be placed from {self.config['FP_PIN_ORDER_CFG']}. Returning state unaltered…"
1345
1542
  )
1346
1543
  return {}, {}
1347
1544
  env["__PL_SKIP_IO"] = "1"
1348
1545
  return super().run(state_in, env=env, **kwargs)
1349
1546
 
1350
1547
 
1351
- @Step.factory.register()
1352
- class BasicMacroPlacement(OpenROADStep):
1353
- id = "OpenROAD.BasicMacroPlacement"
1354
- name = "Basic Macro Placement"
1355
-
1356
- config_vars = OpenROADStep.config_vars + [
1357
- Variable(
1358
- "PL_MACRO_HALO",
1359
- str,
1360
- "Macro placement halo. Format: `{Horizontal} {Vertical}`.",
1361
- default="0 0",
1362
- units="µm",
1363
- ),
1364
- Variable(
1365
- "PL_MACRO_CHANNEL",
1366
- str,
1367
- "Channel widths between macros. Format: `{Horizontal} {Vertical}`.",
1368
- default="0 0",
1369
- units="µm",
1370
- ),
1371
- ]
1372
-
1373
- def get_script_path(self):
1374
- raise NotImplementedError()
1375
-
1376
-
1377
1548
  @Step.factory.register()
1378
1549
  class DetailedPlacement(OpenROADStep):
1379
1550
  """
@@ -1584,6 +1755,13 @@ class RepairAntennas(CompositeStep):
1584
1755
 
1585
1756
  Steps = [_DiodeInsertion, CheckAntennas]
1586
1757
 
1758
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
1759
+ if self.config["DIODE_CELL"] is None:
1760
+ info(f"'DIODE_CELL' not set. Skipping '{self.id}'…")
1761
+ return {}, {}
1762
+
1763
+ return super().run(state_in, **kwargs)
1764
+
1587
1765
 
1588
1766
  @Step.factory.register()
1589
1767
  class DetailedRouting(OpenROADStep):
@@ -1626,6 +1804,30 @@ class DetailedRouting(OpenROADStep):
1626
1804
  "Specifies the maximum number of optimization iterations during Detailed Routing in TritonRoute.",
1627
1805
  default=64,
1628
1806
  ),
1807
+ Variable(
1808
+ "DRT_SAVE_SNAPSHOTS",
1809
+ bool,
1810
+ "This is an experimental variable. Saves an odb snapshot of the layout each routing iteration. This generates multiple odb files increasing disk usage.",
1811
+ default=False,
1812
+ ),
1813
+ Variable(
1814
+ "DRT_ANTENNA_REPAIR_ITERS",
1815
+ int,
1816
+ "The maximum number of iterations to run antenna repair. Set to a positive integer to attempt to repair antennas and then re-run DRT as appropriate.",
1817
+ default=0,
1818
+ ),
1819
+ Variable(
1820
+ "DRT_ANTENNA_MARGIN",
1821
+ int,
1822
+ "The margin to over fix antenna violations.",
1823
+ default=10,
1824
+ units="%",
1825
+ ),
1826
+ Variable(
1827
+ "DRT_SAVE_DRC_REPORT_ITERS",
1828
+ Optional[int],
1829
+ "Write a DRC report every N iterations. If DRT_SAVE_SNAPSHOTS is enabled, there is an implicit default value of 1.",
1830
+ ),
1629
1831
  ]
1630
1832
 
1631
1833
  def get_script_path(self):
@@ -1635,7 +1837,21 @@ class DetailedRouting(OpenROADStep):
1635
1837
  kwargs, env = self.extract_env(kwargs)
1636
1838
  env["DRT_THREADS"] = env.get("DRT_THREADS", str(_get_process_limit()))
1637
1839
  info(f"Running TritonRoute with {env['DRT_THREADS']} threads…")
1638
- return super().run(state_in, env=env, **kwargs)
1840
+ views_updates, metrics_updates = super().run(state_in, env=env, **kwargs)
1841
+
1842
+ drc_paths = list(pathlib.Path(self.step_dir).rglob("*.drc*"))
1843
+ for path in drc_paths:
1844
+ drc, _ = DRCObject.from_openroad(
1845
+ open(path, encoding="utf8"), self.config["DESIGN_NAME"]
1846
+ )
1847
+
1848
+ drc.to_klayout_xml(open(pathlib.Path(str(path) + ".xml"), "wb"))
1849
+ # if violation_count > 0:
1850
+ # self.warn(
1851
+ # f"DRC errors found after routing. View the report file at {report_path}.\nView KLayout xml file at {klayout_db_path}"
1852
+ # )
1853
+
1854
+ return views_updates, metrics_updates
1639
1855
 
1640
1856
 
1641
1857
  @Step.factory.register()
@@ -1793,7 +2009,7 @@ class RCX(OpenROADStep):
1793
2009
  views_updates: ViewsUpdate = {}
1794
2010
  metrics_updates: MetricsUpdate = {}
1795
2011
 
1796
- spef_dict = state_in[DesignFormat.SPEF] or {}
2012
+ spef_dict = state_in.get(DesignFormat.SPEF, {})
1797
2013
  if not isinstance(spef_dict, dict):
1798
2014
  raise StepException(
1799
2015
  "Malformed input state: value for SPEF is not a dictionary."
@@ -1930,7 +2146,7 @@ class CutRows(OpenROADStep):
1930
2146
  Variable(
1931
2147
  "FP_MACRO_HORIZONTAL_HALO",
1932
2148
  Decimal,
1933
- "Specify the horizontal halo size around macros while cutting rows.",
2149
+ "Specify the horizontal halo size around macros.",
1934
2150
  default=10,
1935
2151
  units="µm",
1936
2152
  deprecated_names=["FP_TAP_HORIZONTAL_HALO"],
@@ -1938,18 +2154,24 @@ class CutRows(OpenROADStep):
1938
2154
  Variable(
1939
2155
  "FP_MACRO_VERTICAL_HALO",
1940
2156
  Decimal,
1941
- "Specify the vertical halo size around macros while cutting rows.",
2157
+ "Specify the vertical halo size around macros.",
1942
2158
  default=10,
1943
2159
  units="µm",
1944
2160
  deprecated_names=["FP_TAP_VERTICAL_HALO"],
1945
2161
  ),
2162
+ Variable(
2163
+ "FP_PRUNE_THRESHOLD",
2164
+ Optional[Decimal],
2165
+ 'If specified, all rows smaller in width than this value will be removed. This helps avoid "islets" of cells that are hard to route and connect to PDNs.',
2166
+ pdk=True,
2167
+ units="µm",
2168
+ ),
1946
2169
  ]
1947
2170
 
1948
2171
  def get_script_path(self):
1949
2172
  return os.path.join(get_script_dir(), "openroad", "cut_rows.tcl")
1950
2173
 
1951
2174
 
1952
- @Step.factory.register()
1953
2175
  class WriteViews(OpenROADStep):
1954
2176
  """
1955
2177
  Write various layout views of an ODB design
@@ -1958,8 +2180,8 @@ class WriteViews(OpenROADStep):
1958
2180
  id = "OpenROAD.WriteViews"
1959
2181
  name = "OpenROAD Write Views"
1960
2182
  outputs = OpenROADStep.outputs + [
1961
- DesignFormat.POWERED_NETLIST_SDF_FRIENDLY,
1962
- DesignFormat.POWERED_NETLIST_NO_PHYSICAL_CELLS,
2183
+ DesignFormat.SDF_FRIENDLY_POWERED_NETLIST,
2184
+ DesignFormat.LOGICAL_POWERED_NETLIST,
1963
2185
  DesignFormat.OPENROAD_LEF,
1964
2186
  ]
1965
2187
 
@@ -1989,33 +2211,16 @@ class ResizerStep(OpenROADStep):
1989
2211
  **kwargs,
1990
2212
  ) -> Tuple[ViewsUpdate, MetricsUpdate]:
1991
2213
  kwargs, env = self.extract_env(kwargs)
1992
-
1993
- corners_key: str = "RSZ_CORNERS"
1994
-
1995
- if "corners_key" in kwargs:
1996
- corners_key = kwargs.pop("corners_key")
1997
-
1998
- corners = self.config[corners_key] or self.config["STA_CORNERS"]
1999
- lib_set_set = set()
2000
- count = 0
2001
- for corner in corners:
2002
- _, libs, _, _ = self.toolbox.get_timing_files_categorized(
2003
- self.config, corner
2004
- )
2005
- lib_set = frozenset(libs)
2006
- if lib_set in lib_set_set:
2007
- debug(f"Liberty files for '{corner}' already accounted for- skipped")
2008
- continue
2009
- lib_set_set.add(lib_set)
2010
- env[f"RSZ_CORNER_{count}"] = TclStep.value_to_tcl([corner] + libs)
2011
- debug(f"Liberty files for '{corner}' added: {libs}")
2012
- count += 1
2013
-
2014
- return super().run(state_in, env=env, **kwargs)
2214
+ return super().run(
2215
+ state_in,
2216
+ corners=self.config["RSZ_CORNERS"] or self.config["STA_CORNERS"],
2217
+ env=env,
2218
+ **kwargs,
2219
+ )
2015
2220
 
2016
2221
 
2017
2222
  @Step.factory.register()
2018
- class CTS(ResizerStep):
2223
+ class CTS(OpenROADStep):
2019
2224
  """
2020
2225
  Creates a `Clock tree <https://en.wikipedia.org/wiki/Clock_signal#Distribution>`_
2021
2226
  for an ODB file with detailed-placed cells, using reasonably accurate resistance
@@ -2030,6 +2235,32 @@ class CTS(ResizerStep):
2030
2235
  OpenROADStep.config_vars
2031
2236
  + dpl_variables
2032
2237
  + [
2238
+ # sink_buffer_max_cap_derate
2239
+ Variable(
2240
+ "CTS_BALANCE_LEVELS",
2241
+ Optional[bool],
2242
+ "Attempts to keep a similar number of levels in the clock tree across non-register cells (e.g., clock-gate or inverter).",
2243
+ ),
2244
+ Variable(
2245
+ "CTS_SINK_BUFFER_MAX_CAP_DERATE_PCT",
2246
+ Optional[Decimal],
2247
+ "Controls automatic buffer selection. To favor strong(weak) drive strength buffers use a small(large) value."
2248
+ + "The value of 100 means no derating of max cap limit",
2249
+ units="%",
2250
+ ),
2251
+ Variable(
2252
+ "CTS_DELAY_BUFFER_DERATE_PCT",
2253
+ Optional[Decimal],
2254
+ "This option balances latencies between macro cells and registers by inserting delay buffers"
2255
+ + "The value of 100 means all needed delay buffers are inserted",
2256
+ units="%",
2257
+ ),
2258
+ Variable(
2259
+ "CTS_OBSTRUCTION_AWARE",
2260
+ Optional[bool],
2261
+ "Enables obstruction-aware buffering such that clock buffers are not placed on top of blockages or hard macros. "
2262
+ + "This option may reduce legalizer displacement, leading to better latency, skew or timing QoR.",
2263
+ ),
2033
2264
  Variable(
2034
2265
  "CTS_SINK_CLUSTERING_SIZE",
2035
2266
  int,
@@ -2066,7 +2297,7 @@ class CTS(ResizerStep):
2066
2297
  Variable(
2067
2298
  "CTS_CORNERS",
2068
2299
  Optional[List[str]],
2069
- "A list of fully-qualified IPVT corners to use during clock tree synthesis. If unspecified, the value for `STA_CORNERS` from the PDK will be used.",
2300
+ "Clock tree synthesis step-specific override for PNR_CORNERS.",
2070
2301
  ),
2071
2302
  Variable(
2072
2303
  "CTS_ROOT_BUFFER",
@@ -2077,7 +2308,7 @@ class CTS(ResizerStep):
2077
2308
  Variable(
2078
2309
  "CTS_CLK_BUFFERS",
2079
2310
  List[str],
2080
- "Defines the list of clock buffers to be used in CTS.",
2311
+ "Defines the list of clock buffer names or buffer name wildcards to be used in CTS.",
2081
2312
  deprecated_names=["CTS_CLK_BUFFER_LIST"],
2082
2313
  pdk=True,
2083
2314
  ),
@@ -2114,7 +2345,10 @@ class CTS(ResizerStep):
2114
2345
  return {}, {}
2115
2346
 
2116
2347
  views_updates, metrics_updates = super().run(
2117
- state_in, corners_key="CTS_CORNERS", env=env, **kwargs
2348
+ state_in,
2349
+ corners=self.config["CTS_CORNERS"] or self.config["STA_CORNERS"],
2350
+ env=env,
2351
+ **kwargs,
2118
2352
  )
2119
2353
 
2120
2354
  return views_updates, metrics_updates
@@ -2301,10 +2535,47 @@ class ResizerTimingPostCTS(ResizerStep):
2301
2535
  default=False,
2302
2536
  ),
2303
2537
  Variable(
2304
- "PL_RESIZER_GATE_CLONING",
2538
+ "PL_RESIZER_SETUP_GATE_CLONING",
2305
2539
  bool,
2306
2540
  "Enables gate cloning when attempting to fix setup violations",
2307
2541
  default=True,
2542
+ deprecated_names=["PL_RESIZER_GATE_CLONING"],
2543
+ ),
2544
+ Variable(
2545
+ "PL_RESIZER_SETUP_BUFFERING",
2546
+ bool,
2547
+ "Rebuffering and load splitting during setup fixing.",
2548
+ default=True,
2549
+ ),
2550
+ Variable(
2551
+ "PL_RESIZER_SETUP_BUFFER_REMOVAL",
2552
+ bool,
2553
+ "Buffer removal transform during setup fixing.",
2554
+ default=True,
2555
+ ),
2556
+ Variable(
2557
+ "PL_RESIZER_SETUP_REPAIR_TNS_PCT",
2558
+ Optional[Decimal],
2559
+ "Percentage of violating endpoints to repair during setup fixing.",
2560
+ units="%",
2561
+ ),
2562
+ Variable(
2563
+ "PL_RESIZER_SETUP_MAX_UTIL_PCT",
2564
+ Optional[Decimal],
2565
+ "Defines the percentage of core area used during setup fixing.",
2566
+ units="%",
2567
+ ),
2568
+ Variable(
2569
+ "PL_RESIZER_HOLD_REPAIR_TNS_PCT",
2570
+ Optional[Decimal],
2571
+ "Percentage of violating endpoints to repair during hold fixing.",
2572
+ units="%",
2573
+ ),
2574
+ Variable(
2575
+ "PL_RESIZER_HOLD_MAX_UTIL_PCT",
2576
+ Optional[Decimal],
2577
+ "Defines the percentage of core area used during hold fixing.",
2578
+ units="%",
2308
2579
  ),
2309
2580
  Variable(
2310
2581
  "PL_RESIZER_FIX_HOLD_FIRST",
@@ -2374,10 +2645,11 @@ class ResizerTimingPostGRT(ResizerStep):
2374
2645
  deprecated_names=["GLB_RESIZER_ALLOW_SETUP_VIOS"],
2375
2646
  ),
2376
2647
  Variable(
2377
- "GRT_RESIZER_GATE_CLONING",
2648
+ "GRT_RESIZER_SETUP_GATE_CLONING",
2378
2649
  bool,
2379
2650
  "Enables gate cloning when attempting to fix setup violations",
2380
2651
  default=True,
2652
+ deprecated_names=["GRT_RESIZER_GATE_CLONING"],
2381
2653
  ),
2382
2654
  Variable(
2383
2655
  "GRT_RESIZER_RUN_GRT",
@@ -2385,6 +2657,42 @@ class ResizerTimingPostGRT(ResizerStep):
2385
2657
  "Gates running global routing after resizer steps. May be useful to disable for designs where global routing takes non-trivial time.",
2386
2658
  default=True,
2387
2659
  ),
2660
+ Variable(
2661
+ "GRT_RESIZER_SETUP_BUFFERING",
2662
+ bool,
2663
+ "Rebuffering and load splitting during setup fixing.",
2664
+ default=True,
2665
+ ),
2666
+ Variable(
2667
+ "GRT_RESIZER_SETUP_BUFFER_REMOVAL",
2668
+ bool,
2669
+ "Buffer removal transform during setup fixing.",
2670
+ default=True,
2671
+ ),
2672
+ Variable(
2673
+ "GRT_RESIZER_SETUP_REPAIR_TNS_PCT",
2674
+ Optional[Decimal],
2675
+ "Percentage of violating endpoints to repair during setup fixing.",
2676
+ units="%",
2677
+ ),
2678
+ Variable(
2679
+ "GRT_RESIZER_SETUP_MAX_UTIL_PCT",
2680
+ Optional[Decimal],
2681
+ "Defines the percentage of core area used during setup fixing.",
2682
+ units="%",
2683
+ ),
2684
+ Variable(
2685
+ "GRT_RESIZER_HOLD_REPAIR_TNS_PCT",
2686
+ Optional[Decimal],
2687
+ "Percentage of violating endpoints to repair during hold fixing.",
2688
+ units="%",
2689
+ ),
2690
+ Variable(
2691
+ "GRT_RESIZER_HOLD_MAX_UTIL_PCT",
2692
+ Optional[Decimal],
2693
+ "Defines the percentage of core area used during hold fixing.",
2694
+ units="%",
2695
+ ),
2388
2696
  Variable(
2389
2697
  "GRT_RESIZER_FIX_HOLD_FIRST",
2390
2698
  bool,
@@ -2461,3 +2769,22 @@ class OpenGUI(OpenSTAStep):
2461
2769
  )
2462
2770
 
2463
2771
  return {}, {}
2772
+
2773
+
2774
+ @Step.factory.register()
2775
+ class DumpRCValues(OpenROADStep):
2776
+ """
2777
+ Creates three reports:
2778
+
2779
+ * Initial Database Layer RC Values (from Tech LEF)
2780
+ * Modified Database Layer RC Values
2781
+ * Modified Resizer Layer RC Values
2782
+ """
2783
+
2784
+ id = "OpenROAD.DumpRCValues"
2785
+ name = "Dump RC Values"
2786
+
2787
+ inputs = [DesignFormat.DEF]
2788
+
2789
+ def get_script_path(self) -> str:
2790
+ return os.path.join(get_script_dir(), "openroad", "dump_rc.tcl")