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/flows/flow.py CHANGED
@@ -375,7 +375,7 @@ class Flow(ABC):
375
375
  self.progress_bar = FlowProgressBar(self.name)
376
376
 
377
377
  @classmethod
378
- def get_help_md(Self) -> str: # pragma: no cover
378
+ def get_help_md(Self, myst_anchors: bool = False) -> str: # pragma: no cover
379
379
  """
380
380
  :returns: rendered Markdown help for this Flow
381
381
  """
@@ -383,10 +383,12 @@ class Flow(ABC):
383
383
  if Self.__doc__:
384
384
  doc_string = textwrap.dedent(Self.__doc__)
385
385
 
386
+ flow_anchor = f"(flow-{slugify(Self.__name__, lower=True)})="
387
+
386
388
  result = (
387
389
  textwrap.dedent(
388
390
  f"""\
389
- (flow-{slugify(Self.__name__, lower=True)})=
391
+ {flow_anchor * myst_anchors}
390
392
  ### {Self.__name__}
391
393
 
392
394
  ```{{eval-rst}}
@@ -413,35 +415,53 @@ class Flow(ABC):
413
415
  flow_config_vars = Self.config_vars
414
416
 
415
417
  if len(flow_config_vars):
418
+ config_var_anchors = f"({slugify(Self.__name__, lower=True)}-config-vars)="
416
419
  result += textwrap.dedent(
417
420
  f"""
418
- ({slugify(Self.__name__, lower=True)}-config-vars)=
419
-
421
+ {config_var_anchors * myst_anchors}
420
422
  #### Flow-specific Configuration Variables
421
-
422
- | Variable Name | Type | Description | Default | Units |
423
- | - | - | - | - | - |
424
423
  """
425
424
  )
426
- for var in flow_config_vars:
427
- units = var.units or ""
428
- pdk_superscript = "<sup>PDK</sup>" if var.pdk else ""
429
- result += f"| `{var.name}`{{#{var._get_docs_identifier(Self.__name__)}}}{pdk_superscript} | {var.type_repr_md()} | {var.desc_repr_md()} | `{var.default}` | {units} |\n"
425
+ result += Variable._render_table_md(
426
+ flow_config_vars,
427
+ myst_anchor_owner_id=Self.__name__ if myst_anchors else None,
428
+ )
430
429
  result += "\n"
431
430
 
432
431
  if len(Self.Steps):
433
432
  result += "#### Included Steps\n"
434
433
  for step in Self.Steps:
435
- if hasattr(step, "long_name"):
436
- name = step.long_name
437
- elif hasattr(step, "name"):
438
- name = step.name
434
+ imp_id = step.get_implementation_id()
435
+ if myst_anchors:
436
+ result += f"* [`{step.id}`](./step_config_vars.md#step-{slugify(imp_id, lower=True)})\n"
439
437
  else:
440
- name = step.id
441
- result += f"* [`{step.id}`](./step_config_vars.md#{slugify(name)})\n"
438
+ variant_str = ""
439
+ if imp_id != step.id:
440
+ variant_str = f" (implementation: `{imp_id}`)"
441
+ result += f"* `{step.id}`{variant_str}\n"
442
442
 
443
443
  return result
444
444
 
445
+ @classmethod
446
+ def display_help(Self): # pragma: no cover
447
+ """
448
+ Displays Markdown help for a given flow.
449
+
450
+ If in an IPython environment, it's rendered using ``IPython.display``.
451
+ Otherwise, it's rendered using ``rich.markdown``.
452
+ """
453
+ try:
454
+ get_ipython() # type: ignore
455
+
456
+ import IPython.display
457
+
458
+ IPython.display.display(IPython.display.Markdown(Self.get_help_md()))
459
+ except NameError:
460
+ from ..logging import console
461
+ from rich.markdown import Markdown
462
+
463
+ console.log(Markdown(Self.get_help_md()))
464
+
445
465
  def get_all_config_variables(self) -> List[Variable]:
446
466
  """
447
467
  :returns: All configuration variables for this Flow, including
@@ -510,8 +530,11 @@ class Flow(ABC):
510
530
 
511
531
  :param with_initial_state: An optional initial state object to use.
512
532
  If not provided:
533
+
513
534
  * If resuming a previous run, the latest ``state_out.json`` (by filesystem modification date)
535
+
514
536
  * If not, an empty state object is created.
537
+
515
538
  :param tag: A name for this invocation of the flow. If not provided,
516
539
  one based on a date string will be created.
517
540
 
@@ -796,7 +819,6 @@ class Flow(ABC):
796
819
  DesignFormat.POWERED_NETLIST: (os.path.join("verilog", "gl"), "v"),
797
820
  DesignFormat.DEF: ("def", "def"),
798
821
  DesignFormat.LEF: ("lef", "lef"),
799
- DesignFormat.SDF: (os.path.join("sdf", "multicorner"), "sdf"),
800
822
  DesignFormat.SPEF: (os.path.join("spef", "multicorner"), "spef"),
801
823
  DesignFormat.LIB: (os.path.join("lib", "multicorner"), "lib"),
802
824
  DesignFormat.GDS: ("gds", "gds"),
@@ -855,38 +877,67 @@ class Flow(ABC):
855
877
  file_path, os.path.join(to_dir, file), follow_symlinks=True
856
878
  )
857
879
 
858
- signoff_folder = os.path.join(
859
- path, "signoff", self.config["DESIGN_NAME"], "librelane-signoff"
860
- )
861
- mkdirp(signoff_folder)
880
+ def find_one(pattern):
881
+ result = glob.glob(pattern)
882
+ if len(result) == 0:
883
+ return None
884
+ return result[0]
862
885
 
863
- # resolved.json
886
+ signoff_dir = os.path.join(path, "signoff", self.config["DESIGN_NAME"])
887
+ openlane_signoff_dir = os.path.join(signoff_dir, "openlane-signoff")
888
+ mkdirp(openlane_signoff_dir)
889
+
890
+ ## resolved.json
864
891
  shutil.copyfile(
865
892
  self.config_resolved_path,
866
- os.path.join(signoff_folder, "resolved.json"),
893
+ os.path.join(openlane_signoff_dir, "resolved.json"),
867
894
  follow_symlinks=True,
868
895
  )
869
896
 
870
- # Logs
871
- mkdirp(signoff_folder)
872
- copy_dir_contents(self.run_dir, signoff_folder, "*.log")
897
+ ## metrics
898
+ with open(os.path.join(signoff_dir, "metrics.csv"), "w", encoding="utf8") as f:
899
+ last_state.metrics_to_csv(f)
900
+
901
+ ## flow logs
902
+ mkdirp(openlane_signoff_dir)
903
+ copy_dir_contents(self.run_dir, openlane_signoff_dir, "*.log")
873
904
 
874
- # Step-specific
905
+ ### step-specific signoff logs and reports
875
906
  for step in self.step_objects:
876
907
  reports_dir = os.path.join(step.step_dir, "reports")
877
908
  step_imp_id = step.get_implementation_id()
909
+ if step_imp_id == "Magic.DRC":
910
+ if drc_rpt := find_one(os.path.join(reports_dir, "*.rpt")):
911
+ shutil.copyfile(
912
+ drc_rpt, os.path.join(openlane_signoff_dir, "drc.rpt")
913
+ )
914
+ if drc_xml := find_one(os.path.join(reports_dir, "*.xml")):
915
+ # Despite the name, this is the Magic DRC report simply
916
+ # converted into a KLayout-compatible format. Confusing!
917
+ drc_xml_out = os.path.join(openlane_signoff_dir, "drc.klayout.xml")
918
+ with open(drc_xml, encoding="utf8") as i, open(
919
+ drc_xml_out, "w", encoding="utf8"
920
+ ) as o:
921
+ o.write(
922
+ "<!-- Despite the name, this is the Magic DRC report in KLayout format. -->\n"
923
+ )
924
+ shutil.copyfileobj(i, o)
925
+ if step_imp_id == "Netgen.LVS":
926
+ if lvs_rpt := find_one(os.path.join(reports_dir, "*.rpt")):
927
+ shutil.copyfile(
928
+ lvs_rpt, os.path.join(openlane_signoff_dir, "lvs.rpt")
929
+ )
878
930
  if step_imp_id.endswith("DRC") or step_imp_id.endswith("LVS"):
879
- if os.path.exists(reports_dir):
880
- copy_dir_contents(reports_dir, signoff_folder)
881
- if step_imp_id.endswith("LVS"):
882
- copy_dir_contents(step.step_dir, signoff_folder, "*.log")
931
+ copy_dir_contents(step.step_dir, openlane_signoff_dir, "*.log")
883
932
  if step_imp_id.endswith("CheckAntennas"):
884
933
  if os.path.exists(reports_dir):
885
934
  copy_dir_contents(
886
- reports_dir, signoff_folder, "antenna_summary.rpt"
935
+ reports_dir, openlane_signoff_dir, "antenna_summary.rpt"
887
936
  )
888
937
  if step_imp_id.endswith("STAPostPNR"):
889
- timing_report_folder = os.path.join(signoff_folder, "timing-reports")
938
+ timing_report_folder = os.path.join(
939
+ openlane_signoff_dir, "timing-reports"
940
+ )
890
941
  mkdirp(timing_report_folder)
891
942
  copy_dir_contents(step.step_dir, timing_report_folder, "*summary.rpt")
892
943
  for dir in os.listdir(step.step_dir):
@@ -897,6 +948,18 @@ class Flow(ABC):
897
948
  mkdirp(target)
898
949
  copy_dir_contents(dir_path, target, "*.rpt")
899
950
 
951
+ # 3. SDF
952
+ # (This one, as with many things in the Efabless format, is special)
953
+ if sdf := last_state[DesignFormat.SDF]:
954
+ assert isinstance(sdf, dict), "SDF is not a dictionary"
955
+ for corner, view in sdf.items():
956
+ assert isinstance(view, Path), "SDF state out returned multiple paths"
957
+ target_dir = os.path.join(signoff_dir, "sdf", corner)
958
+ mkdirp(target_dir)
959
+ shutil.copyfile(
960
+ view, os.path.join(target_dir, f"{self.config['DESIGN_NAME']}.sdf")
961
+ )
962
+
900
963
  @deprecated(
901
964
  version="2.0.0a46",
902
965
  reason="Use .progress_bar.set_max_stage_count",
@@ -938,8 +1001,9 @@ class Flow(ABC):
938
1001
  A factory singleton for Flows, allowing Flow types to be registered and then
939
1002
  retrieved by name.
940
1003
 
941
- See https://en.wikipedia.org/wiki/Factory_(object-oriented_programming) for
942
- a primer.
1004
+ See
1005
+ `Factory (object-oriented programming) on Wikipedia <https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)>`_
1006
+ for a primer.
943
1007
  """
944
1008
 
945
1009
  __registry: ClassVar[Dict[str, Type[Flow]]] = {}
@@ -0,0 +1,39 @@
1
+ # Copyright 2025 LibreLane Contributors
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
+ from ..common.cli import formatter_settings
15
+ from ..flows import Flow
16
+ from ..steps import Step
17
+ from ..logging import console
18
+
19
+ import cloup
20
+
21
+
22
+ @cloup.command(formatter_settings=formatter_settings)
23
+ @cloup.argument("step_or_flow")
24
+ @cloup.pass_context
25
+ def cli(ctx, step_or_flow):
26
+ """
27
+ Displays rich help for the step or flow in question.
28
+ """
29
+ if TargetFlow := Flow.factory.get(step_or_flow):
30
+ TargetFlow.display_help()
31
+ elif TargetStep := Step.factory.get(step_or_flow):
32
+ TargetStep.display_help()
33
+ else:
34
+ console.log(f"Unknown Flow or Step '{step_or_flow}'.")
35
+ ctx.exit(-1)
36
+
37
+
38
+ if __name__ == "__main__":
39
+ cli()
@@ -77,5 +77,3 @@ if { $::env(MAGIC_GDS_POLYGON_SUBCELLS) } {
77
77
 
78
78
  gds write $::env(SAVE_MAG_GDS)
79
79
  puts "\[INFO\] GDS Write Complete"
80
-
81
- exit 0
@@ -76,4 +76,3 @@ puts stdout "\[INFO\] Saving mag view with DRC errors ($mag_view)"
76
76
  # WARNING: changes the name of the cell; keep as last step
77
77
  save $mag_view
78
78
  puts stdout "\[INFO\] Saved"
79
- exit 0
@@ -43,5 +43,3 @@ if { [info exist ::env(EXTRA_GDS_FILES)] } {
43
43
  puts "\[INFO\] Saved mag view from $gds_file under $::env(STEP_DIR)"
44
44
  }
45
45
  }
46
-
47
- exit 0
@@ -29,4 +29,3 @@ set final_filepath $::env(signoff_tmpfiles)/gds_ptrs.mag
29
29
  file rename -force $::env(signoff_tmpfiles)/$::env(DESIGN_NAME).mag $final_filepath
30
30
 
31
31
  puts "\[INFO\] Wrote $final_filepath including GDS pointers."
32
- exit 0
@@ -59,5 +59,3 @@ foreach design_name [cellname list allcells] {
59
59
  puts $fp [join $new_mag_lines "\n"]
60
60
  close $fp
61
61
  }
62
-
63
- exit 0
@@ -24,4 +24,3 @@ cellname filepath $::env(DESIGN_NAME).lef $::env(signoff_results)
24
24
  save
25
25
 
26
26
  puts "\[INFO\] DONE GENERATING MAGLEF VIEW"
27
- exit 0
@@ -17,3 +17,5 @@ if {[catch {source $::env(_MAGIC_SCRIPT)} err]} {
17
17
  puts "Error: $err"
18
18
  exit 1
19
19
  }
20
+
21
+ exit 0
@@ -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()
@@ -249,13 +248,19 @@ def relocate_pins(db, input_lefs, template_def, permissive, copy_def_power=False
249
248
  pin_name = bterm.getName()
250
249
  pin_net_name = bterm.getNet().getName()
251
250
  pin_net = output_block.findNet(pin_net_name)
251
+ new_net_created = False
252
252
  if pin_net is None:
253
253
  pin_net = odb.dbNet.create(output_block, pin_net_name, True)
254
254
  pin_net.setSpecial()
255
255
  pin_net.setSigType(bterm.getSigType())
256
- pin_bterm = odb.dbBTerm.create(pin_net, pin_name)
257
- pin_bterm.setSigType(bterm.getSigType())
258
- output_bterms.append(pin_bterm)
256
+ new_net_created = True
257
+ pin_bterm = output_block.findBTerm(pin_name)
258
+ if pin_bterm is None:
259
+ pin_bterm = odb.dbBTerm.create(pin_net, pin_name)
260
+ pin_bterm.setSigType(bterm.getSigType())
261
+ output_bterms.append(pin_bterm)
262
+ elif new_net_created:
263
+ pin_bterm.connect(pin_net)
259
264
 
260
265
  grid_errors = False
261
266
  for output_bterm in output_bterms:
@@ -481,7 +486,7 @@ def parse_obstructions(obstructions) -> List[Tuple[str, List[int]]]:
481
486
  f"[ERROR] Incorrectly formatted input {obs}.\n Format: layer llx lly urx ury, ...",
482
487
  file=sys.stderr,
483
488
  )
484
- sys.exit(FORMAT_ERROR)
489
+ sys.exit(1)
485
490
  else:
486
491
  layer = m.group("layer")
487
492
  bbox = [Decimal(x) for x in m.group("bbox").split()]
@@ -505,8 +510,8 @@ def add_obstructions(reader, input_lefs, obstructions):
505
510
  layer = obs[0]
506
511
  odb_layer = reader.tech.findLayer(layer)
507
512
  if odb_layer is None:
508
- print(f"[ERROR] layer {layer} doesn't exist.", file=sys.stderr)
509
- sys.exit(METAL_LAYER_ERROR)
513
+ print(f"[ERROR] Layer '{layer}' not found.", file=sys.stderr)
514
+ sys.exit(1)
510
515
  bbox = obs[1]
511
516
  dbu = reader.tech.getDbUnitsPerMicron()
512
517
  bbox = [int(x * dbu) for x in bbox]
@@ -550,8 +555,8 @@ def remove_obstructions(reader, input_lefs, obstructions):
550
555
  bbox = [int(x * dbu) for x in bbox] # To dbus
551
556
  found = False
552
557
  if reader.tech.findLayer(layer) is None:
553
- print(f"[ERROR] layer {layer} doesn't exist.", file=sys.stderr)
554
- sys.exit(METAL_LAYER_ERROR)
558
+ print(f"[ERROR] Layer '{layer}' not found.", file=sys.stderr)
559
+ sys.exit(1)
555
560
  for odb_obstruction in existing_obstructions:
556
561
  odb_layer, odb_bbox, odb_obj = odb_obstruction
557
562
  if (odb_layer, odb_bbox) == (layer, bbox):
@@ -565,7 +570,7 @@ def remove_obstructions(reader, input_lefs, obstructions):
565
570
  f"[ERROR] Obstruction on {layer} at {bbox} (DBU) not found.",
566
571
  file=sys.stderr,
567
572
  )
568
- sys.exit(NOT_FOUND_ERROR)
573
+ sys.exit(1)
569
574
 
570
575
 
571
576
  cli.add_command(remove_obstructions)
@@ -0,0 +1,182 @@
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
+ eco_buffers = reader.config["INSERT_ECO_BUFFERS"] or []
47
+ for target_info in eco_buffers:
48
+ target_name, target_pin = target_info["target"].split("/")
49
+ name_escaped = reader.escape_verilog_name(target_name)
50
+ buffer_master = target_info["buffer"]
51
+
52
+ master = reader.db.findMaster(buffer_master)
53
+ if master is None:
54
+ print(
55
+ f"[ERROR] Buffer type '{buffer_master}' not found.",
56
+ file=sys.stderr,
57
+ )
58
+ exit(-1)
59
+
60
+ target = reader.block.findInst(name_escaped)
61
+ if target is None:
62
+ print(f"[ERROR] Instance '{target_name}' not found.", file=sys.stderr)
63
+ exit(-1)
64
+
65
+ target_iterm = target.findITerm(target_pin)
66
+ if target_iterm is None:
67
+ print(
68
+ f"[ERROR] Pin '{target_pin}' not found for instance '{target_name}'.",
69
+ file=sys.stderr,
70
+ )
71
+ exit(-1)
72
+
73
+ net = target_iterm.getNet()
74
+ if net is None:
75
+ print(
76
+ f"[ERROR] Net not found on pin '{target_pin}' of instance '{target_name}'.",
77
+ file=sys.stderr,
78
+ )
79
+ exit(-1)
80
+
81
+ new_buf_name = f"eco_buffer_{i}"
82
+ new_net_name = f"eco_buffer_{i}_net"
83
+ while (
84
+ reader.block.findInst(new_buf_name) is not None
85
+ or reader.block.findNet(new_net_name) is not None
86
+ ):
87
+ i += 1
88
+ new_buf_name = f"eco_buffer_{i}"
89
+ new_net_name = f"eco_buffer_{i}_net"
90
+
91
+ # Prepare buffer cell, net
92
+ eco_buffer = odb.dbInst.create(reader.block, master, new_buf_name)
93
+ eco_net = odb.dbNet.create(reader.block, new_net_name)
94
+ buffer_iterms = eco_buffer.getITerms()
95
+ buffer_a = None
96
+ for iterm in buffer_iterms:
97
+ if iterm.isInputSignal():
98
+ buffer_a = iterm
99
+ break # Exit loop once input is found
100
+ if buffer_a is None:
101
+ print(
102
+ f"[ERROR] Buffer {buffer_master} has no input signals.",
103
+ file=sys.stderr,
104
+ )
105
+ exit(-1)
106
+
107
+ buffer_x = None
108
+ for iterm in buffer_iterms:
109
+ if iterm.isOutputSignal():
110
+ buffer_x = iterm
111
+ break # Exit loop once output is found
112
+ if buffer_x is None:
113
+ print(
114
+ f"[ERROR] Buffer {buffer_master} has no output signals.",
115
+ file=sys.stderr,
116
+ )
117
+ exit(-1)
118
+
119
+ location_instances = [target]
120
+ net_iterms = net.getITerms()
121
+ if target_iterm.getIoType() == "INPUT":
122
+ driver_iterms = [
123
+ iterm for iterm in net_iterms if iterm.getIoType() in ["OUTPUT"]
124
+ ]
125
+ drivers = [iterm.getInst() for iterm in driver_iterms]
126
+ location_instances.extend(drivers)
127
+
128
+ target_iterm.disconnect()
129
+ buffer_a.connect(net)
130
+ buffer_x.connect(eco_net)
131
+ target_iterm.connect(eco_net)
132
+ elif target_iterm.getIoType() == "OUTPUT":
133
+ sink_iterms = [
134
+ iterm for iterm in net_iterms if iterm.getIoType() in ["INPUT", "INOUT"]
135
+ ]
136
+ sinks = [iterm.getInst() for iterm in sink_iterms]
137
+ location_instances.extend(sinks)
138
+
139
+ target_iterm.disconnect()
140
+ target_iterm.connect(eco_net)
141
+ buffer_a.connect(eco_net)
142
+ buffer_x.connect(net)
143
+ else:
144
+ print(
145
+ 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.",
146
+ file=sys.stderr,
147
+ )
148
+ exit(-1)
149
+
150
+ if target_info.get("placement") is not None:
151
+ eco_x, eco_y = target_info["placement"]
152
+ eco_x = reader.block.micronsToDbu(float(eco_x))
153
+ eco_y = reader.block.micronsToDbu(float(eco_y))
154
+ eco_loc = (eco_x, eco_y)
155
+ else:
156
+ eco_loc = average_location(location_instances)
157
+
158
+ eco_buffer.setOrient("R0")
159
+ eco_buffer.setLocation(*eco_loc)
160
+ eco_buffer.setPlacementStatus("PLACED")
161
+ grt.addDirtyNet(net)
162
+ grt.addDirtyNet(eco_net)
163
+
164
+ site = reader.rows[0].getSite()
165
+ max_disp_x = int(
166
+ reader.design.micronToDBU(reader.config["PL_MAX_DISPLACEMENT_X"])
167
+ / site.getWidth()
168
+ )
169
+ max_disp_y = int(
170
+ reader.design.micronToDBU(reader.config["PL_MAX_DISPLACEMENT_Y"])
171
+ / site.getHeight()
172
+ )
173
+ dpl.detailedPlacement(max_disp_x, max_disp_y)
174
+
175
+ grt_inc.updateRoutes(True)
176
+
177
+ for inst, previous_status in insts_to_temporarily_lock_then_unlock_later:
178
+ inst.setPlacementStatus(previous_status)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ cli()