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/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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
-
|
|
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(
|
|
893
|
+
os.path.join(openlane_signoff_dir, "resolved.json"),
|
|
867
894
|
follow_symlinks=True,
|
|
868
895
|
)
|
|
869
896
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
942
|
-
|
|
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()
|
librelane/scripts/magic/drc.tcl
CHANGED
|
@@ -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
|
-
|
|
257
|
-
pin_bterm.
|
|
258
|
-
|
|
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(
|
|
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]
|
|
509
|
-
sys.exit(
|
|
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]
|
|
554
|
-
sys.exit(
|
|
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(
|
|
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()
|