librelane 3.0.0.dev25__py3-none-any.whl → 3.0.0.dev27__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.
- librelane/common/__init__.py +1 -0
- librelane/common/drc.py +1 -0
- librelane/common/misc.py +27 -5
- librelane/common/tcl.py +42 -0
- librelane/common/types.py +2 -3
- librelane/config/__main__.py +1 -1
- librelane/config/flow.py +12 -0
- librelane/config/variable.py +16 -0
- librelane/flows/flow.py +89 -21
- librelane/scripts/magic/def/mag_gds.tcl +1 -2
- librelane/scripts/magic/drc.tcl +0 -1
- librelane/scripts/magic/extract_spice.tcl +1 -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/power_utils.py +8 -6
- librelane/scripts/odbpy/reader.py +2 -2
- librelane/scripts/openroad/write_cdl.tcl +23 -0
- librelane/state/design_format.py +7 -0
- librelane/state/state.py +11 -3
- librelane/steps/__main__.py +1 -2
- librelane/steps/cvc_rv.py +1 -8
- librelane/steps/klayout.py +193 -1
- librelane/steps/magic.py +24 -14
- librelane/steps/odb.py +1 -4
- librelane/steps/openroad.py +15 -4
- librelane/steps/step.py +22 -7
- librelane/steps/tclstep.py +1 -1
- {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/METADATA +1 -1
- {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/RECORD +34 -33
- {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/WHEEL +0 -0
- {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/entry_points.txt +0 -0
librelane/common/__init__.py
CHANGED
librelane/common/drc.py
CHANGED
librelane/common/misc.py
CHANGED
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
import
|
|
15
|
-
import glob
|
|
16
|
-
import gzip
|
|
14
|
+
import io
|
|
17
15
|
import os
|
|
18
|
-
import pathlib
|
|
19
16
|
import re
|
|
17
|
+
import glob
|
|
18
|
+
import gzip
|
|
20
19
|
import typing
|
|
20
|
+
import pathlib
|
|
21
|
+
import fnmatch
|
|
21
22
|
import unicodedata
|
|
22
23
|
from math import inf
|
|
23
24
|
from typing import (
|
|
25
|
+
IO,
|
|
24
26
|
Any,
|
|
25
27
|
Generator,
|
|
26
28
|
Iterable,
|
|
@@ -378,7 +380,7 @@ def _get_process_limit() -> int:
|
|
|
378
380
|
return int(os.getenv("_OPENLANE_MAX_CORES", os.cpu_count() or 1))
|
|
379
381
|
|
|
380
382
|
|
|
381
|
-
def gzopen(filename, mode="rt"):
|
|
383
|
+
def gzopen(filename: AnyPath, mode="rt") -> IO[Any]:
|
|
382
384
|
"""
|
|
383
385
|
This method (tries to?) emulate the gzopen from the Linux Standard Base,
|
|
384
386
|
specifically this part:
|
|
@@ -388,6 +390,11 @@ def gzopen(filename, mode="rt"):
|
|
|
388
390
|
for reading directly from the file without any decompression.
|
|
389
391
|
|
|
390
392
|
gzip.open does not have this behavior.
|
|
393
|
+
|
|
394
|
+
:param filename: The full path to the uncompressed or gzipped file.
|
|
395
|
+
:param mode: "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for
|
|
396
|
+
binary mode, or "rt", "wt", "xt" or "at" for text mode.
|
|
397
|
+
:returns: An I/O wrapper that may very slightly based on the mode.
|
|
391
398
|
"""
|
|
392
399
|
try:
|
|
393
400
|
g = gzip.open(filename, mode=mode)
|
|
@@ -400,3 +407,18 @@ def gzopen(filename, mode="rt"):
|
|
|
400
407
|
except gzip.BadGzipFile:
|
|
401
408
|
g.close()
|
|
402
409
|
return open(filename, mode=mode)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def count_occurences(fp: io.TextIOWrapper, pattern: str = "") -> int:
|
|
413
|
+
"""
|
|
414
|
+
Counts the occurences of a certain string in a stream, line-by-line, without
|
|
415
|
+
necessarily loading the entire file into memory.
|
|
416
|
+
|
|
417
|
+
Equivalent to: ``grep -c 'pattern' <file>`` (but without regex support).
|
|
418
|
+
|
|
419
|
+
:param fp: the text stream
|
|
420
|
+
:param pattern: the substring to search for. if set to "", it will simply
|
|
421
|
+
count the lines in the file.
|
|
422
|
+
:returns: the number of matching lines
|
|
423
|
+
"""
|
|
424
|
+
return sum(pattern in line for line in fp)
|
librelane/common/tcl.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# Copyright 2025 LibreLane Contributors
|
|
2
|
+
#
|
|
3
|
+
# Adapted from OpenLane
|
|
4
|
+
#
|
|
1
5
|
# Copyright 2023 Efabless Corporation
|
|
2
6
|
#
|
|
3
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -68,12 +72,50 @@ class TclUtils(object):
|
|
|
68
72
|
if value is not None:
|
|
69
73
|
env_out[match.group(1)] = value
|
|
70
74
|
|
|
75
|
+
def py_dict(command, target=None, *args):
|
|
76
|
+
if command == "set":
|
|
77
|
+
if match := _env_rx.fullmatch(target):
|
|
78
|
+
|
|
79
|
+
if len(args) > 1:
|
|
80
|
+
value = args[-1]
|
|
81
|
+
keys = args[:-1]
|
|
82
|
+
|
|
83
|
+
# Create new dict if it does not exist
|
|
84
|
+
if not match.group(1) in env_out:
|
|
85
|
+
env_out[match.group(1)] = {}
|
|
86
|
+
|
|
87
|
+
# set ::env(...) [dict create]
|
|
88
|
+
# will create an empty string ""
|
|
89
|
+
# convert into an empty dictionary
|
|
90
|
+
if env_out[match.group(1)] == "":
|
|
91
|
+
env_out[match.group(1)] = {}
|
|
92
|
+
|
|
93
|
+
# Set key value pair
|
|
94
|
+
cur_dict = env_out[match.group(1)]
|
|
95
|
+
|
|
96
|
+
# Create all nested dicts
|
|
97
|
+
for key in keys[:-1]:
|
|
98
|
+
if key in cur_dict:
|
|
99
|
+
cur_dict = cur_dict[key]
|
|
100
|
+
else:
|
|
101
|
+
cur_dict[key] = {}
|
|
102
|
+
cur_dict = cur_dict[key]
|
|
103
|
+
|
|
104
|
+
# Finally set the value
|
|
105
|
+
cur_dict[keys[-1]] = value
|
|
106
|
+
|
|
71
107
|
py_set_name = interpreter.register(py_set)
|
|
108
|
+
py_dict_name = interpreter.register(py_dict)
|
|
72
109
|
interpreter.call("rename", py_set_name, "_py_set")
|
|
73
110
|
interpreter.call("rename", "set", "_orig_set")
|
|
111
|
+
interpreter.call("rename", py_dict_name, "_py_dict")
|
|
112
|
+
interpreter.call("rename", "dict", "_orig_dict")
|
|
74
113
|
interpreter.eval(
|
|
75
114
|
"proc set args { _py_set {*}$args; tailcall _orig_set {*}$args; }"
|
|
76
115
|
)
|
|
116
|
+
interpreter.eval(
|
|
117
|
+
"proc dict args { _py_dict {*}$args; tailcall _orig_dict {*}$args; }"
|
|
118
|
+
)
|
|
77
119
|
|
|
78
120
|
interpreter.eval(tcl_in)
|
|
79
121
|
|
librelane/common/types.py
CHANGED
|
@@ -16,6 +16,7 @@ import sys
|
|
|
16
16
|
import tempfile
|
|
17
17
|
from math import isfinite
|
|
18
18
|
from decimal import Decimal
|
|
19
|
+
from weakref import finalize
|
|
19
20
|
from collections import UserString
|
|
20
21
|
from typing import Any, Union, ClassVar, Tuple, Optional
|
|
21
22
|
|
|
@@ -112,6 +113,4 @@ class ScopedFile(Path):
|
|
|
112
113
|
super().__init__(self._ntf.name)
|
|
113
114
|
self._ntf.write(contents)
|
|
114
115
|
self._ntf.close()
|
|
115
|
-
|
|
116
|
-
def __del__(self):
|
|
117
|
-
os.unlink(self._ntf.name)
|
|
116
|
+
self._ntf_cleanup = finalize(self, os.unlink, self._ntf.name)
|
librelane/config/__main__.py
CHANGED
|
@@ -110,7 +110,7 @@ def create_config(
|
|
|
110
110
|
print("At least one source RTL file is required.", file=sys.stderr)
|
|
111
111
|
exit(1)
|
|
112
112
|
source_rtl_key = "VERILOG_FILES"
|
|
113
|
-
if not all(
|
|
113
|
+
if not all(file.endswith(".sv") or file.endswith(".v") for file in source_rtl):
|
|
114
114
|
print(
|
|
115
115
|
"Only Verilog/SystemVerilog files are supported by create-config.",
|
|
116
116
|
file=sys.stderr,
|
librelane/config/flow.py
CHANGED
|
@@ -195,6 +195,13 @@ scl_variables = [
|
|
|
195
195
|
"Path(s) to cells' SPICE model(s)",
|
|
196
196
|
pdk=True,
|
|
197
197
|
),
|
|
198
|
+
Variable(
|
|
199
|
+
"CELL_CDLS",
|
|
200
|
+
Optional[List[Path]],
|
|
201
|
+
description="A circuit-design language view of the standard cell library.",
|
|
202
|
+
pdk=True,
|
|
203
|
+
deprecated_names=["STD_CELL_LIBRARY_CDL"],
|
|
204
|
+
),
|
|
198
205
|
Variable(
|
|
199
206
|
"SYNTH_EXCLUDED_CELL_FILE",
|
|
200
207
|
Path,
|
|
@@ -424,6 +431,11 @@ option_variables = [
|
|
|
424
431
|
Optional[List[Path]],
|
|
425
432
|
"Specifies miscellaneous SPICE models to be loaded indiscriminately whenever SPICE models are loaded.",
|
|
426
433
|
),
|
|
434
|
+
Variable(
|
|
435
|
+
"EXTRA_CDLS",
|
|
436
|
+
Optional[List[Path]],
|
|
437
|
+
"Specifies miscellaneous CDL netlists to be loaded indiscriminately whenever CDL netlists are loaded.",
|
|
438
|
+
),
|
|
427
439
|
Variable(
|
|
428
440
|
"EXTRA_LIBS",
|
|
429
441
|
Optional[List[Path]],
|
librelane/config/variable.py
CHANGED
|
@@ -440,6 +440,9 @@ class Variable:
|
|
|
440
440
|
return_value = list()
|
|
441
441
|
raw = value
|
|
442
442
|
if isinstance(raw, list) or isinstance(raw, tuple):
|
|
443
|
+
if validating_type == List[Path]:
|
|
444
|
+
if any(isinstance(item, List) for item in raw):
|
|
445
|
+
Variable.__flatten_list(value)
|
|
443
446
|
pass
|
|
444
447
|
elif is_string(raw):
|
|
445
448
|
if not permissive_typing:
|
|
@@ -725,3 +728,16 @@ class Variable:
|
|
|
725
728
|
and self.type == rhs.type
|
|
726
729
|
and self.default == rhs.default
|
|
727
730
|
)
|
|
731
|
+
|
|
732
|
+
# Flatten list. Note: Must modify value, not return a new list.
|
|
733
|
+
@staticmethod
|
|
734
|
+
def __flatten_list(value: list):
|
|
735
|
+
new_list = []
|
|
736
|
+
for item in value:
|
|
737
|
+
if isinstance(item, list):
|
|
738
|
+
for sub_item in item:
|
|
739
|
+
new_list.append(sub_item)
|
|
740
|
+
else:
|
|
741
|
+
new_list.append(item)
|
|
742
|
+
|
|
743
|
+
value[:] = new_list
|
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 = True) -> 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}}
|
|
@@ -426,7 +428,8 @@ class Flow(ABC):
|
|
|
426
428
|
for var in flow_config_vars:
|
|
427
429
|
units = var.units or ""
|
|
428
430
|
pdk_superscript = "<sup>PDK</sup>" if var.pdk else ""
|
|
429
|
-
|
|
431
|
+
var_anchor = f"{{#{var._get_docs_identifier(Self.__name__)}}}"
|
|
432
|
+
result += f"| `{var.name}`{var_anchor * myst_anchors} {pdk_superscript} | {var.type_repr_md()} | {var.desc_repr_md()} | `{var.default}` | {units} |\n"
|
|
430
433
|
result += "\n"
|
|
431
434
|
|
|
432
435
|
if len(Self.Steps):
|
|
@@ -438,10 +441,35 @@ class Flow(ABC):
|
|
|
438
441
|
name = step.name
|
|
439
442
|
else:
|
|
440
443
|
name = step.id
|
|
441
|
-
|
|
444
|
+
if myst_anchors:
|
|
445
|
+
result += (
|
|
446
|
+
f"* [`{step.id}`](./step_config_vars.md#{slugify(name)})\n"
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
result += f"* {step.id}"
|
|
442
450
|
|
|
443
451
|
return result
|
|
444
452
|
|
|
453
|
+
@classmethod
|
|
454
|
+
def display_help(Self): # pragma: no cover
|
|
455
|
+
"""
|
|
456
|
+
Displays Markdown help for a given flow.
|
|
457
|
+
|
|
458
|
+
If in an IPython environment, it's rendered using ``IPython.display``.
|
|
459
|
+
Otherwise, it's rendered using ``rich.markdown``.
|
|
460
|
+
"""
|
|
461
|
+
try:
|
|
462
|
+
get_ipython() # type: ignore
|
|
463
|
+
|
|
464
|
+
import IPython.display
|
|
465
|
+
|
|
466
|
+
IPython.display.display(IPython.display.Markdown(Self.get_help_md()))
|
|
467
|
+
except NameError:
|
|
468
|
+
from ..logging import console
|
|
469
|
+
from rich.markdown import Markdown
|
|
470
|
+
|
|
471
|
+
console.log(Markdown(Self.get_help_md()))
|
|
472
|
+
|
|
445
473
|
def get_all_config_variables(self) -> List[Variable]:
|
|
446
474
|
"""
|
|
447
475
|
:returns: All configuration variables for this Flow, including
|
|
@@ -796,7 +824,6 @@ class Flow(ABC):
|
|
|
796
824
|
DesignFormat.POWERED_NETLIST: (os.path.join("verilog", "gl"), "v"),
|
|
797
825
|
DesignFormat.DEF: ("def", "def"),
|
|
798
826
|
DesignFormat.LEF: ("lef", "lef"),
|
|
799
|
-
DesignFormat.SDF: (os.path.join("sdf", "multicorner"), "sdf"),
|
|
800
827
|
DesignFormat.SPEF: (os.path.join("spef", "multicorner"), "spef"),
|
|
801
828
|
DesignFormat.LIB: (os.path.join("lib", "multicorner"), "lib"),
|
|
802
829
|
DesignFormat.GDS: ("gds", "gds"),
|
|
@@ -852,38 +879,67 @@ class Flow(ABC):
|
|
|
852
879
|
file_path, os.path.join(to_dir, file), follow_symlinks=True
|
|
853
880
|
)
|
|
854
881
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
882
|
+
def find_one(pattern):
|
|
883
|
+
result = glob.glob(pattern)
|
|
884
|
+
if len(result) == 0:
|
|
885
|
+
return None
|
|
886
|
+
return result[0]
|
|
887
|
+
|
|
888
|
+
signoff_dir = os.path.join(path, "signoff", self.config["DESIGN_NAME"])
|
|
889
|
+
openlane_signoff_dir = os.path.join(signoff_dir, "openlane-signoff")
|
|
890
|
+
mkdirp(openlane_signoff_dir)
|
|
859
891
|
|
|
860
|
-
|
|
892
|
+
## resolved.json
|
|
861
893
|
shutil.copyfile(
|
|
862
894
|
self.config_resolved_path,
|
|
863
|
-
os.path.join(
|
|
895
|
+
os.path.join(openlane_signoff_dir, "resolved.json"),
|
|
864
896
|
follow_symlinks=True,
|
|
865
897
|
)
|
|
866
898
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
899
|
+
## metrics
|
|
900
|
+
with open(os.path.join(signoff_dir, "metrics.csv"), "w", encoding="utf8") as f:
|
|
901
|
+
last_state.metrics_to_csv(f)
|
|
870
902
|
|
|
871
|
-
|
|
903
|
+
## flow logs
|
|
904
|
+
mkdirp(openlane_signoff_dir)
|
|
905
|
+
copy_dir_contents(self.run_dir, openlane_signoff_dir, "*.log")
|
|
906
|
+
|
|
907
|
+
### step-specific signoff logs and reports
|
|
872
908
|
for step in self.step_objects:
|
|
873
909
|
reports_dir = os.path.join(step.step_dir, "reports")
|
|
874
910
|
step_imp_id = step.get_implementation_id()
|
|
911
|
+
if step_imp_id == "Magic.DRC":
|
|
912
|
+
if drc_rpt := find_one(os.path.join(reports_dir, "*.rpt")):
|
|
913
|
+
shutil.copyfile(
|
|
914
|
+
drc_rpt, os.path.join(openlane_signoff_dir, "drc.rpt")
|
|
915
|
+
)
|
|
916
|
+
if drc_xml := find_one(os.path.join(reports_dir, "*.xml")):
|
|
917
|
+
# Despite the name, this is the Magic DRC report simply
|
|
918
|
+
# converted into a KLayout-compatible format. Confusing!
|
|
919
|
+
drc_xml_out = os.path.join(openlane_signoff_dir, "drc.klayout.xml")
|
|
920
|
+
with open(drc_xml, encoding="utf8") as i, open(
|
|
921
|
+
drc_xml_out, "w", encoding="utf8"
|
|
922
|
+
) as o:
|
|
923
|
+
o.write(
|
|
924
|
+
"<!-- Despite the name, this is the Magic DRC report in KLayout format. -->\n"
|
|
925
|
+
)
|
|
926
|
+
shutil.copyfileobj(i, o)
|
|
927
|
+
if step_imp_id == "Netgen.LVS":
|
|
928
|
+
if lvs_rpt := find_one(os.path.join(reports_dir, "*.rpt")):
|
|
929
|
+
shutil.copyfile(
|
|
930
|
+
lvs_rpt, os.path.join(openlane_signoff_dir, "lvs.rpt")
|
|
931
|
+
)
|
|
875
932
|
if step_imp_id.endswith("DRC") or step_imp_id.endswith("LVS"):
|
|
876
|
-
|
|
877
|
-
copy_dir_contents(reports_dir, signoff_folder)
|
|
878
|
-
if step_imp_id.endswith("LVS"):
|
|
879
|
-
copy_dir_contents(step.step_dir, signoff_folder, "*.log")
|
|
933
|
+
copy_dir_contents(step.step_dir, openlane_signoff_dir, "*.log")
|
|
880
934
|
if step_imp_id.endswith("CheckAntennas"):
|
|
881
935
|
if os.path.exists(reports_dir):
|
|
882
936
|
copy_dir_contents(
|
|
883
|
-
reports_dir,
|
|
937
|
+
reports_dir, openlane_signoff_dir, "antenna_summary.rpt"
|
|
884
938
|
)
|
|
885
939
|
if step_imp_id.endswith("STAPostPNR"):
|
|
886
|
-
timing_report_folder = os.path.join(
|
|
940
|
+
timing_report_folder = os.path.join(
|
|
941
|
+
openlane_signoff_dir, "timing-reports"
|
|
942
|
+
)
|
|
887
943
|
mkdirp(timing_report_folder)
|
|
888
944
|
copy_dir_contents(step.step_dir, timing_report_folder, "*summary.rpt")
|
|
889
945
|
for dir in os.listdir(step.step_dir):
|
|
@@ -894,6 +950,18 @@ class Flow(ABC):
|
|
|
894
950
|
mkdirp(target)
|
|
895
951
|
copy_dir_contents(dir_path, target, "*.rpt")
|
|
896
952
|
|
|
953
|
+
# 3. SDF
|
|
954
|
+
# (This one, as with many things in the Efabless format, is special)
|
|
955
|
+
if sdf := last_state[DesignFormat.SDF]:
|
|
956
|
+
assert isinstance(sdf, dict), "SDF is not a dictionary"
|
|
957
|
+
for corner, view in sdf.items():
|
|
958
|
+
assert isinstance(view, Path), "SDF state out returned multiple paths"
|
|
959
|
+
target_dir = os.path.join(signoff_dir, "sdf", corner)
|
|
960
|
+
mkdirp(target_dir)
|
|
961
|
+
shutil.copyfile(
|
|
962
|
+
view, os.path.join(target_dir, f"{self.config['DESIGN_NAME']}.sdf")
|
|
963
|
+
)
|
|
964
|
+
|
|
897
965
|
@deprecated(
|
|
898
966
|
version="2.0.0a46",
|
|
899
967
|
reason="Use .progress_bar.set_max_stage_count",
|
|
@@ -24,6 +24,7 @@ if { $::env(MAGIC_MACRO_STD_CELL_SOURCE) == "PDK" } {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
read_extra_gds
|
|
27
|
+
read_extra_lef
|
|
27
28
|
|
|
28
29
|
load (NEWCELL)
|
|
29
30
|
|
|
@@ -77,5 +78,3 @@ if { $::env(MAGIC_GDS_POLYGON_SUBCELLS) } {
|
|
|
77
78
|
|
|
78
79
|
gds write $::env(SAVE_MAG_GDS)
|
|
79
80
|
puts "\[INFO\] GDS Write Complete"
|
|
80
|
-
|
|
81
|
-
exit 0
|
librelane/scripts/magic/drc.tcl
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
set f [open $::env(STEP_DIR)/cif_scale.txt "w"]
|
|
15
|
-
puts $f
|
|
15
|
+
puts $f [expr {((round([magic::cif scale output] * 10000)) / 10000.0) * 1}]
|
|
16
16
|
close $f
|
|
17
17
|
|
|
18
18
|
if { $::env(MAGIC_EXT_USE_GDS) } {
|
|
@@ -17,7 +17,6 @@ import utl
|
|
|
17
17
|
|
|
18
18
|
import re
|
|
19
19
|
import json
|
|
20
|
-
import functools
|
|
21
20
|
from dataclasses import dataclass
|
|
22
21
|
from typing import Dict, List, Optional
|
|
23
22
|
|
|
@@ -49,11 +48,14 @@ class Design(object):
|
|
|
49
48
|
def get_verilog_net_name_by_bit(self, top_module: str, target_bit: int):
|
|
50
49
|
yosys_design_object = self.yosys_dict["modules"][top_module]
|
|
51
50
|
if top_module not in self.verilog_net_names_by_bit_by_module:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
# check git history for a version of this loop that is drunk on power
|
|
52
|
+
netname_by_bit = {}
|
|
53
|
+
|
|
54
|
+
for netname, info in yosys_design_object["netnames"].items():
|
|
55
|
+
for bit in info["bits"]:
|
|
56
|
+
netname_by_bit[bit] = netname
|
|
57
|
+
|
|
58
|
+
self.verilog_net_names_by_bit_by_module[top_module] = netname_by_bit
|
|
57
59
|
return self.verilog_net_names_by_bit_by_module[top_module][target_bit]
|
|
58
60
|
|
|
59
61
|
def get_pins(self, module_name: str) -> Dict[str, odb.dbMTerm]:
|
|
@@ -20,7 +20,7 @@ import sys
|
|
|
20
20
|
import json
|
|
21
21
|
import locale
|
|
22
22
|
import inspect
|
|
23
|
-
import
|
|
23
|
+
from functools import wraps
|
|
24
24
|
from decimal import Decimal
|
|
25
25
|
from fnmatch import fnmatch
|
|
26
26
|
from typing import Callable, Dict
|
|
@@ -188,7 +188,7 @@ class OdbReader(object):
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
def click_odb(function):
|
|
191
|
-
@
|
|
191
|
+
@wraps(function)
|
|
192
192
|
def wrapper(input_db, input_lefs, config_path, **kwargs):
|
|
193
193
|
reader = OdbReader(input_db, config_path=config_path)
|
|
194
194
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Write CDL netlist of the current design
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
# Load design database
|
|
6
|
+
source $::env(SCRIPTS_DIR)/openroad/common/io.tcl
|
|
7
|
+
read_current_odb
|
|
8
|
+
|
|
9
|
+
# Collect masters CDL
|
|
10
|
+
set masters {}
|
|
11
|
+
|
|
12
|
+
foreach cdl $::env(CELL_CDLS) {
|
|
13
|
+
lappend masters $cdl
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if { [info exist ::env(EXTRA_CDLS)] } {
|
|
17
|
+
foreach cdl $::env(EXTRA_CDLS) {
|
|
18
|
+
lappend masters $cdl
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Write only the CDL
|
|
23
|
+
write_cdl -include_fillers -masters "$masters" $::env(STEP_DIR)/$::env(DESIGN_NAME).cdl
|
librelane/state/design_format.py
CHANGED
librelane/state/state.py
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import io
|
|
16
17
|
import os
|
|
18
|
+
import csv
|
|
17
19
|
import sys
|
|
18
20
|
import json
|
|
19
21
|
import shutil
|
|
@@ -209,14 +211,20 @@ class State(GenericImmutableDict[str, StateElement]):
|
|
|
209
211
|
self._walk(self, path, visitor)
|
|
210
212
|
metrics_csv_path = os.path.join(path, "metrics.csv")
|
|
211
213
|
with open(metrics_csv_path, "w", encoding="utf8") as f:
|
|
212
|
-
|
|
213
|
-
for metric in self.metrics:
|
|
214
|
-
f.write(f"{metric},{self.metrics[metric]}\n")
|
|
214
|
+
self.metrics_to_csv(f)
|
|
215
215
|
|
|
216
216
|
metrics_json_path = os.path.join(path, "metrics.json")
|
|
217
217
|
with open(metrics_json_path, "w", encoding="utf8") as f:
|
|
218
218
|
f.write(self.metrics.dumps())
|
|
219
219
|
|
|
220
|
+
def metrics_to_csv(
|
|
221
|
+
self, fp: io.TextIOWrapper, metrics_object: Optional[Dict[str, Any]] = None
|
|
222
|
+
):
|
|
223
|
+
w = csv.writer(fp)
|
|
224
|
+
w.writerow(("Metric", "Value"))
|
|
225
|
+
for entry in (metrics_object or self.metrics).items():
|
|
226
|
+
w.writerow(entry)
|
|
227
|
+
|
|
220
228
|
def validate(self):
|
|
221
229
|
"""
|
|
222
230
|
Ensures that all paths exist in a State.
|
librelane/steps/__main__.py
CHANGED
|
@@ -15,7 +15,6 @@ import os
|
|
|
15
15
|
import shlex
|
|
16
16
|
import shutil
|
|
17
17
|
import datetime
|
|
18
|
-
import functools
|
|
19
18
|
import subprocess
|
|
20
19
|
from functools import partial
|
|
21
20
|
from typing import IO, Any, Dict, Optional, Sequence, Union
|
|
@@ -239,7 +238,7 @@ def eject(ctx, output, state_in, config, id):
|
|
|
239
238
|
found_stdin_data = found_stdin.read()
|
|
240
239
|
raise Stop()
|
|
241
240
|
|
|
242
|
-
step.run_subprocess =
|
|
241
|
+
step.run_subprocess = partial(
|
|
243
242
|
step.run_subprocess,
|
|
244
243
|
_popen_callable=popen_substitute,
|
|
245
244
|
)
|
librelane/steps/cvc_rv.py
CHANGED
|
@@ -16,7 +16,7 @@ import re
|
|
|
16
16
|
import json
|
|
17
17
|
from enum import Enum
|
|
18
18
|
from io import StringIO, TextIOWrapper
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import Optional, Tuple
|
|
20
20
|
|
|
21
21
|
from .step import StepException, ViewsUpdate, MetricsUpdate, Step
|
|
22
22
|
from ..common import Path
|
|
@@ -124,13 +124,6 @@ class ERC(Step):
|
|
|
124
124
|
description="",
|
|
125
125
|
pdk=True,
|
|
126
126
|
),
|
|
127
|
-
Variable(
|
|
128
|
-
"CELL_CDLS",
|
|
129
|
-
List[Path],
|
|
130
|
-
description="A circuit-design language view of the standard cell library.",
|
|
131
|
-
pdk=True,
|
|
132
|
-
deprecated_names=["STD_CELL_LIBRARY_CDL"],
|
|
133
|
-
),
|
|
134
127
|
]
|
|
135
128
|
|
|
136
129
|
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
librelane/steps/klayout.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# Copyright 2025 LibreLane Contributors
|
|
2
|
+
#
|
|
3
|
+
# Adapted from OpenLane
|
|
4
|
+
#
|
|
1
5
|
# Copyright 2023 Efabless Corporation
|
|
2
6
|
#
|
|
3
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -19,6 +23,7 @@ import shutil
|
|
|
19
23
|
import subprocess
|
|
20
24
|
from os.path import abspath
|
|
21
25
|
from base64 import b64encode
|
|
26
|
+
from tempfile import NamedTemporaryFile
|
|
22
27
|
from typing import Any, Dict, Optional, List, Sequence, Tuple, Union
|
|
23
28
|
|
|
24
29
|
from .step import ViewsUpdate, MetricsUpdate, Step, StepError, StepException
|
|
@@ -431,10 +436,77 @@ class DRC(KLayoutStep):
|
|
|
431
436
|
)
|
|
432
437
|
return subprocess_result["generated_metrics"]
|
|
433
438
|
|
|
439
|
+
def run_ihp_sg13g2(self, state_in: State, **kwargs) -> MetricsUpdate:
|
|
440
|
+
kwargs, env = self.extract_env(kwargs)
|
|
441
|
+
|
|
442
|
+
drc_script_path = self.config["KLAYOUT_DRC_RUNSET"]
|
|
443
|
+
|
|
444
|
+
reports_dir = os.path.join(self.step_dir, "reports")
|
|
445
|
+
mkdirp(reports_dir)
|
|
446
|
+
xml_report = os.path.join(reports_dir, "drc_violations.klayout.xml")
|
|
447
|
+
json_report = os.path.join(reports_dir, "drc_violations.klayout.json")
|
|
448
|
+
|
|
449
|
+
input_view = state_in[DesignFormat.GDS]
|
|
450
|
+
assert isinstance(input_view, Path)
|
|
451
|
+
|
|
452
|
+
opts = []
|
|
453
|
+
for k, v in self.config["KLAYOUT_DRC_OPTIONS"].items():
|
|
454
|
+
opts.extend(
|
|
455
|
+
[
|
|
456
|
+
"-rd",
|
|
457
|
+
f"{k}={v}",
|
|
458
|
+
]
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
threads = self.config["KLAYOUT_DRC_THREADS"] or str(_get_process_limit())
|
|
462
|
+
if threads != "1":
|
|
463
|
+
opts.extend(
|
|
464
|
+
[
|
|
465
|
+
"-rd",
|
|
466
|
+
f"threads={threads}",
|
|
467
|
+
]
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
info(f"Running KLayout DRC with {threads} threads…")
|
|
471
|
+
|
|
472
|
+
# Not pya script - DRC script is not part of OpenLane
|
|
473
|
+
self.run_subprocess(
|
|
474
|
+
[
|
|
475
|
+
"klayout",
|
|
476
|
+
"-b",
|
|
477
|
+
"-zz",
|
|
478
|
+
"-r",
|
|
479
|
+
drc_script_path,
|
|
480
|
+
"-rd",
|
|
481
|
+
f"in_gds={abspath(input_view)}",
|
|
482
|
+
"-rd",
|
|
483
|
+
f"report_file={abspath(xml_report)}",
|
|
484
|
+
*opts,
|
|
485
|
+
]
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
subprocess_result = self.run_pya_script(
|
|
489
|
+
[
|
|
490
|
+
"python3",
|
|
491
|
+
os.path.join(
|
|
492
|
+
get_script_dir(),
|
|
493
|
+
"klayout",
|
|
494
|
+
"xml_drc_report_to_json.py",
|
|
495
|
+
),
|
|
496
|
+
f"--xml-file={abspath(xml_report)}",
|
|
497
|
+
f"--json-file={abspath(json_report)}",
|
|
498
|
+
],
|
|
499
|
+
env=env,
|
|
500
|
+
log_to=os.path.join(self.step_dir, "xml_drc_report_to_json.log"),
|
|
501
|
+
)
|
|
502
|
+
return subprocess_result["generated_metrics"]
|
|
503
|
+
|
|
434
504
|
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
435
505
|
metrics_updates: MetricsUpdate = {}
|
|
436
506
|
if self.config["PDK"] in ["sky130A", "sky130B"]:
|
|
437
507
|
metrics_updates = self.run_sky130(state_in, **kwargs)
|
|
508
|
+
elif self.config["PDK"] in ["ihp-sg13g2"]:
|
|
509
|
+
metrics_updates = self.run_ihp_sg13g2(state_in, **kwargs)
|
|
438
510
|
else:
|
|
439
511
|
self.warn(
|
|
440
512
|
f"KLayout DRC is not supported for the {self.config['PDK']} PDK. This step will be skipped."
|
|
@@ -443,6 +515,126 @@ class DRC(KLayoutStep):
|
|
|
443
515
|
return {}, metrics_updates
|
|
444
516
|
|
|
445
517
|
|
|
518
|
+
@Step.factory.register()
|
|
519
|
+
class LVS(KLayoutStep):
|
|
520
|
+
id = "KLayout.LVS"
|
|
521
|
+
name = "Layout Versus Schematic (KLayout)"
|
|
522
|
+
|
|
523
|
+
inputs = [
|
|
524
|
+
DesignFormat.CDL,
|
|
525
|
+
DesignFormat.GDS,
|
|
526
|
+
]
|
|
527
|
+
outputs = [DesignFormat.SPICE]
|
|
528
|
+
|
|
529
|
+
config_vars = KLayoutStep.config_vars + [
|
|
530
|
+
Variable(
|
|
531
|
+
"KLAYOUT_LVS_SCRIPT",
|
|
532
|
+
Optional[Path],
|
|
533
|
+
"A path to KLayout LVS script.",
|
|
534
|
+
pdk=True,
|
|
535
|
+
),
|
|
536
|
+
Variable(
|
|
537
|
+
"KLAYOUT_LVS_OPTIONS",
|
|
538
|
+
Optional[Dict[str, Union[bool, int, str]]],
|
|
539
|
+
"Options passed directly to the KLayout LVS script. They vary from one PDK to another.",
|
|
540
|
+
pdk=True,
|
|
541
|
+
),
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
def run_ihp_sg13g2(
|
|
545
|
+
self, state_in: State, **kwargs
|
|
546
|
+
) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
547
|
+
kwargs, env = self.extract_env(kwargs)
|
|
548
|
+
|
|
549
|
+
lvs_script_path = self.config["KLAYOUT_LVS_SCRIPT"]
|
|
550
|
+
|
|
551
|
+
reports_dir = os.path.join(self.step_dir, "reports")
|
|
552
|
+
mkdirp(reports_dir)
|
|
553
|
+
lvsdb_report = os.path.join(reports_dir, "lvs.klayout.lvsdb")
|
|
554
|
+
|
|
555
|
+
input_view_gds = state_in[DesignFormat.GDS]
|
|
556
|
+
input_view_cdl = state_in[DesignFormat.CDL]
|
|
557
|
+
assert isinstance(input_view_gds, Path)
|
|
558
|
+
assert isinstance(input_view_cdl, Path)
|
|
559
|
+
|
|
560
|
+
output_spice = os.path.join(
|
|
561
|
+
self.step_dir,
|
|
562
|
+
f"{self.config['DESIGN_NAME']}.{DesignFormat.SPICE.value.extension}",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
with NamedTemporaryFile("w") as f:
|
|
566
|
+
# Merge all CDL inputs
|
|
567
|
+
cdl_lst = [input_view_cdl]
|
|
568
|
+
cdl_lst.extend(self.config["CELL_CDLS"] or [])
|
|
569
|
+
cdl_lst.extend(self.config["EXTRA_CDLS"] or [])
|
|
570
|
+
|
|
571
|
+
for fn in cdl_lst:
|
|
572
|
+
with open(fn, "r") as cdl_fh:
|
|
573
|
+
f.write(cdl_fh.read())
|
|
574
|
+
|
|
575
|
+
opts = []
|
|
576
|
+
for k, v in self.config["KLAYOUT_LVS_OPTIONS"].items():
|
|
577
|
+
opts.extend(
|
|
578
|
+
[
|
|
579
|
+
"-rd",
|
|
580
|
+
f"{k}={v}",
|
|
581
|
+
]
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Not pya script - LVS script is not part of LibreLane
|
|
585
|
+
subprocess_result = self.run_subprocess(
|
|
586
|
+
[
|
|
587
|
+
"klayout",
|
|
588
|
+
"-b",
|
|
589
|
+
"-zz",
|
|
590
|
+
"-r",
|
|
591
|
+
lvs_script_path,
|
|
592
|
+
"-rd",
|
|
593
|
+
f"input={abspath(input_view_gds)}",
|
|
594
|
+
"-rd",
|
|
595
|
+
f"schematic={abspath(f.name)}",
|
|
596
|
+
"-rd",
|
|
597
|
+
f"report={abspath(lvsdb_report)}",
|
|
598
|
+
"-rd",
|
|
599
|
+
f"target_netlist={abspath(output_spice)}",
|
|
600
|
+
]
|
|
601
|
+
+ opts,
|
|
602
|
+
env=env,
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
with open(subprocess_result["log_path"]) as fh:
|
|
606
|
+
for line in fh:
|
|
607
|
+
if "INFO : Congratulations! Netlists match" in line:
|
|
608
|
+
ok = True
|
|
609
|
+
break
|
|
610
|
+
elif "ERROR : Netlists don't match" in line:
|
|
611
|
+
ok = False
|
|
612
|
+
break
|
|
613
|
+
else:
|
|
614
|
+
ok = False
|
|
615
|
+
|
|
616
|
+
views_updates: ViewsUpdate = {
|
|
617
|
+
DesignFormat.SPICE: Path(output_spice),
|
|
618
|
+
}
|
|
619
|
+
metrics_updates: MetricsUpdate = {
|
|
620
|
+
"design__lvs_error__count": 0 if ok else 1,
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return views_updates, metrics_updates
|
|
624
|
+
|
|
625
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
626
|
+
metrics_updates: MetricsUpdate = {}
|
|
627
|
+
views_updates: ViewsUpdate = {}
|
|
628
|
+
if self.config["PDK"] in ["ihp-sg13g2"]:
|
|
629
|
+
views_updates, metrics_updates = self.run_ihp_sg13g2(state_in, **kwargs)
|
|
630
|
+
else:
|
|
631
|
+
self.warn(
|
|
632
|
+
f"KLayout LVS is not supported for the {self.config['PDK']} PDK. This step will be skipped."
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
return views_updates, metrics_updates
|
|
636
|
+
|
|
637
|
+
|
|
446
638
|
@Step.factory.register()
|
|
447
639
|
class OpenGUI(KLayoutStep):
|
|
448
640
|
"""
|
|
@@ -480,7 +672,7 @@ class OpenGUI(KLayoutStep):
|
|
|
480
672
|
|
|
481
673
|
layout = state_in[DesignFormat.DEF]
|
|
482
674
|
if self.config["KLAYOUT_GUI_USE_GDS"]:
|
|
483
|
-
if gds := state_in
|
|
675
|
+
if gds := state_in.get(DesignFormat.GDS):
|
|
484
676
|
layout = gds
|
|
485
677
|
assert isinstance(layout, Path)
|
|
486
678
|
|
librelane/steps/magic.py
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
import os
|
|
15
15
|
import re
|
|
16
16
|
import shutil
|
|
17
|
-
import functools
|
|
18
17
|
import subprocess
|
|
19
18
|
from signal import SIGKILL
|
|
20
19
|
from decimal import Decimal
|
|
@@ -34,7 +33,8 @@ from .tclstep import TclStep
|
|
|
34
33
|
from ..state import DesignFormat, State
|
|
35
34
|
|
|
36
35
|
from ..config import Variable
|
|
37
|
-
from ..common import get_script_dir, DRC as DRCObject, Path, mkdirp
|
|
36
|
+
from ..common import get_script_dir, DRC as DRCObject, Path, mkdirp, count_occurences
|
|
37
|
+
from ..logging import warn
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
DesignFormat(
|
|
@@ -480,6 +480,12 @@ class SpiceExtraction(MagicStep):
|
|
|
480
480
|
"Extracts a SPICE netlist based on black-boxed standard cells and macros (basically, anything with a LEF) rather than transistors. An error will be thrown if both this and `MAGIC_EXT_USE_GDS` is set to ``True``.",
|
|
481
481
|
default=False,
|
|
482
482
|
),
|
|
483
|
+
Variable(
|
|
484
|
+
"MAGIC_FEEDBACK_CONVERSION_THRESHOLD",
|
|
485
|
+
int,
|
|
486
|
+
"If Magic provides more feedback items than this threshold, conversion to KLayout databases is skipped (as something has gone horribly wrong.)",
|
|
487
|
+
default=10000,
|
|
488
|
+
),
|
|
483
489
|
]
|
|
484
490
|
|
|
485
491
|
def get_script_path(self):
|
|
@@ -497,22 +503,29 @@ class SpiceExtraction(MagicStep):
|
|
|
497
503
|
|
|
498
504
|
views_updates, metrics_updates = super().run(state_in, env=env, **kwargs)
|
|
499
505
|
|
|
500
|
-
cif_scale = Decimal(open(os.path.join(self.step_dir, "cif_scale.txt")).read())
|
|
501
506
|
feedback_path = os.path.join(self.step_dir, "feedback.txt")
|
|
507
|
+
with open(feedback_path, encoding="utf8") as f:
|
|
508
|
+
illegal_overlap_count = count_occurences(f, "Illegal overlap")
|
|
509
|
+
|
|
510
|
+
metrics_updates["magic__illegal_overlap__count"] = illegal_overlap_count
|
|
511
|
+
threshold = self.config["MAGIC_FEEDBACK_CONVERSION_THRESHOLD"]
|
|
512
|
+
if illegal_overlap_count > threshold:
|
|
513
|
+
warn(
|
|
514
|
+
f"Not converting the feedback to the KLayout database format: {illegal_overlap_count} > MAGIC_FEEDBACK_CONVERSION_THRESHOLD ({threshold}). You may manually increase the threshold, but it might take forever."
|
|
515
|
+
)
|
|
516
|
+
return views_updates, metrics_updates
|
|
517
|
+
|
|
518
|
+
cif_scale = Decimal(open(os.path.join(self.step_dir, "cif_scale.txt")).read())
|
|
502
519
|
try:
|
|
503
520
|
se_feedback, _ = DRCObject.from_magic_feedback(
|
|
504
521
|
open(feedback_path, encoding="utf8"),
|
|
505
522
|
cif_scale,
|
|
506
523
|
self.config["DESIGN_NAME"],
|
|
507
524
|
)
|
|
508
|
-
illegal_overlap_count =
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
for v in se_feedback.violations.values()
|
|
513
|
-
if "Illegal overlap" in v.description
|
|
514
|
-
],
|
|
515
|
-
0,
|
|
525
|
+
illegal_overlap_count = sum(
|
|
526
|
+
len(v.bounding_boxes)
|
|
527
|
+
for v in se_feedback.violations.values()
|
|
528
|
+
if "Illegal overlap" in v.description
|
|
516
529
|
)
|
|
517
530
|
with open(os.path.join(self.step_dir, "feedback.xml"), "wb") as f:
|
|
518
531
|
se_feedback.to_klayout_xml(f)
|
|
@@ -521,9 +534,6 @@ class SpiceExtraction(MagicStep):
|
|
|
521
534
|
self.warn(
|
|
522
535
|
f"Failed to convert SPICE extraction feedback to KLayout database format: {e}"
|
|
523
536
|
)
|
|
524
|
-
metrics_updates["magic__illegal_overlap__count"] = (
|
|
525
|
-
open(feedback_path, encoding="utf8").read().count("Illegal overlap")
|
|
526
|
-
)
|
|
527
537
|
return views_updates, metrics_updates
|
|
528
538
|
|
|
529
539
|
|
librelane/steps/odb.py
CHANGED
|
@@ -17,7 +17,6 @@ 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
|
|
22
21
|
from dataclasses import dataclass
|
|
23
22
|
from typing import Dict, List, Literal, Optional, Tuple
|
|
@@ -418,9 +417,7 @@ class ManualMacroPlacement(OdbpyStep):
|
|
|
418
417
|
)
|
|
419
418
|
shutil.copyfile(cfg_ref, cfg_file)
|
|
420
419
|
elif macros := self.config.get("MACROS"):
|
|
421
|
-
instance_count =
|
|
422
|
-
lambda x, y: x + len(y.instances), macros.values(), 0
|
|
423
|
-
)
|
|
420
|
+
instance_count = sum(len(m.instances) for m in macros.values())
|
|
424
421
|
if instance_count >= 1:
|
|
425
422
|
with open(cfg_file, "w") as f:
|
|
426
423
|
for module, macro in macros.items():
|
librelane/steps/openroad.py
CHANGED
|
@@ -19,7 +19,6 @@ import io
|
|
|
19
19
|
import os
|
|
20
20
|
import re
|
|
21
21
|
import json
|
|
22
|
-
import functools
|
|
23
22
|
import subprocess
|
|
24
23
|
import textwrap
|
|
25
24
|
import pathlib
|
|
@@ -1313,9 +1312,7 @@ def get_psm_error_count(rpt: io.TextIOWrapper) -> int:
|
|
|
1313
1312
|
|
|
1314
1313
|
sio.seek(0)
|
|
1315
1314
|
violations = yaml.load(sio, Loader=yaml.SafeLoader) or []
|
|
1316
|
-
return
|
|
1317
|
-
lambda acc, current: acc + len(current["srcs"]), violations, 0
|
|
1318
|
-
)
|
|
1315
|
+
return sum(len(violation["srcs"]) for violation in violations)
|
|
1319
1316
|
|
|
1320
1317
|
|
|
1321
1318
|
@Step.factory.register()
|
|
@@ -2191,6 +2188,20 @@ class WriteViews(OpenROADStep):
|
|
|
2191
2188
|
return os.path.join(get_script_dir(), "openroad", "write_views.tcl")
|
|
2192
2189
|
|
|
2193
2190
|
|
|
2191
|
+
@Step.factory.register()
|
|
2192
|
+
class WriteCDL(OpenROADStep):
|
|
2193
|
+
"""
|
|
2194
|
+
Write CDL view of an ODB design
|
|
2195
|
+
"""
|
|
2196
|
+
|
|
2197
|
+
id = "OpenROAD.WriteCDL"
|
|
2198
|
+
name = "Write CDL"
|
|
2199
|
+
outputs = [DesignFormat.CDL]
|
|
2200
|
+
|
|
2201
|
+
def get_script_path(self):
|
|
2202
|
+
return os.path.join(get_script_dir(), "openroad", "write_cdl.tcl")
|
|
2203
|
+
|
|
2204
|
+
|
|
2194
2205
|
# Resizer Steps
|
|
2195
2206
|
|
|
2196
2207
|
|
librelane/steps/step.py
CHANGED
|
@@ -619,6 +619,7 @@ class Step(ABC):
|
|
|
619
619
|
*,
|
|
620
620
|
docstring_override: str = "",
|
|
621
621
|
use_dropdown: bool = False,
|
|
622
|
+
myst_anchors: bool = False,
|
|
622
623
|
): # pragma: no cover
|
|
623
624
|
"""
|
|
624
625
|
Renders Markdown help for this step to a string.
|
|
@@ -676,11 +677,12 @@ class Step(ABC):
|
|
|
676
677
|
result += f"| {input_str} | {output_str} |\n"
|
|
677
678
|
|
|
678
679
|
if len(Self.config_vars):
|
|
680
|
+
all_vars_anchor = f"({Self.id.lower()}-configuration-variables)="
|
|
679
681
|
result += textwrap.dedent(
|
|
680
682
|
f"""
|
|
681
|
-
|
|
683
|
+
{all_vars_anchor * myst_anchors}
|
|
682
684
|
#### Configuration Variables
|
|
683
|
-
|
|
685
|
+
|
|
684
686
|
| Variable Name | Type | Description | Default | Units |
|
|
685
687
|
| - | - | - | - | - |
|
|
686
688
|
"""
|
|
@@ -688,13 +690,15 @@ class Step(ABC):
|
|
|
688
690
|
for var in Self.config_vars:
|
|
689
691
|
units = var.units or ""
|
|
690
692
|
pdk_superscript = "<sup>PDK</sup>" if var.pdk else ""
|
|
691
|
-
|
|
693
|
+
var_anchor = f"{{#{var._get_docs_identifier(Self.id)}}}"
|
|
694
|
+
result += f"| `{var.name}`{var_anchor * myst_anchors} {pdk_superscript} | {var.type_repr_md(for_document=True)} | {var.desc_repr_md()} | `{var.default}` | {units} |\n"
|
|
692
695
|
result += "\n"
|
|
693
696
|
|
|
697
|
+
step_anchor = f"(step-{slugify(Self.id.lower())})="
|
|
694
698
|
result = (
|
|
695
699
|
textwrap.dedent(
|
|
696
700
|
f"""
|
|
697
|
-
|
|
701
|
+
{step_anchor * myst_anchors}
|
|
698
702
|
### {Self.__get_desc()}
|
|
699
703
|
"""
|
|
700
704
|
)
|
|
@@ -706,11 +710,22 @@ class Step(ABC):
|
|
|
706
710
|
@classmethod
|
|
707
711
|
def display_help(Self): # pragma: no cover
|
|
708
712
|
"""
|
|
709
|
-
|
|
713
|
+
Displays Markdown help for this Step.
|
|
714
|
+
|
|
715
|
+
If in an IPython environment, it's rendered using ``IPython.display``.
|
|
716
|
+
Otherwise, it's rendered using ``rich.markdown``.
|
|
710
717
|
"""
|
|
711
|
-
|
|
718
|
+
try:
|
|
719
|
+
get_ipython() # type: ignore
|
|
720
|
+
|
|
721
|
+
import IPython.display
|
|
722
|
+
|
|
723
|
+
IPython.display.display(IPython.display.Markdown(Self.get_help_md()))
|
|
724
|
+
except NameError:
|
|
725
|
+
from ..logging import console
|
|
726
|
+
from rich.markdown import Markdown
|
|
712
727
|
|
|
713
|
-
|
|
728
|
+
console.log(Markdown(Self.get_help_md()))
|
|
714
729
|
|
|
715
730
|
def _repr_markdown_(self) -> str: # pragma: no cover
|
|
716
731
|
"""
|
librelane/steps/tclstep.py
CHANGED
|
@@ -255,7 +255,7 @@ class TclStep(Step):
|
|
|
255
255
|
#
|
|
256
256
|
# Emplace file to be sourced in dict with key ``_TCL_ENV_IN``
|
|
257
257
|
env = os.environ.copy()
|
|
258
|
-
with open(env_in_file, "
|
|
258
|
+
with open(env_in_file, "w") as f:
|
|
259
259
|
for key, value in env_in:
|
|
260
260
|
if key in env and env[key] == value:
|
|
261
261
|
continue
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
librelane/__init__.py,sha256=EMpoZrRmS_wsweKjhyAg52OXCK7HWQ8o8CVrYaX4ub0,1220
|
|
2
2
|
librelane/__main__.py,sha256=Zq2h2ZTQqA0T7qpGFELJ1bzVQnv-951nnhJ0-9WzNaM,14541
|
|
3
3
|
librelane/__version__.py,sha256=dbE4stCACDmIoxgKksesAkTa-_hi5dW6nPLWw9Pfq3Q,1486
|
|
4
|
-
librelane/common/__init__.py,sha256=
|
|
4
|
+
librelane/common/__init__.py,sha256=QhzRvLXLR2cGdYenayWYmCfgkTS836dWW41Ve1Wa5IE,1551
|
|
5
5
|
librelane/common/cli.py,sha256=xi48GBGHRsYrLGwx40ARwpykHx7GnuHbJjHxjOwtZ5Y,2349
|
|
6
|
-
librelane/common/drc.py,sha256=
|
|
6
|
+
librelane/common/drc.py,sha256=l1quZbHXGb7yjKCO5IFn-Xxf_zIx4f6kxqpNm3YmpOs,12809
|
|
7
7
|
librelane/common/generic_dict.py,sha256=ASa5wtVcLuGlsBqGfLxLYXrYksqQB62iHljV04plIqI,10010
|
|
8
8
|
librelane/common/metrics/__init__.py,sha256=nzdmeia1fN4CDPT604v-OHaBVydz-M4hz232g-jxJ6M,1521
|
|
9
9
|
librelane/common/metrics/__main__.py,sha256=VfoOHFkGXtei6GLkPUD42X6io8QKFCHhxLMWOXPz_5E,12473
|
|
10
10
|
librelane/common/metrics/library.py,sha256=CG7rubLdjuCQL9-9bzAC-64hf-KlH-iu_Fg0oKuesqs,7373
|
|
11
11
|
librelane/common/metrics/metric.py,sha256=h3Xd26z5M80IJgVmmrBTjKcdGLb4I0wyjM-H4jdyi_0,6990
|
|
12
12
|
librelane/common/metrics/util.py,sha256=Bl_9znlot7-Os2VigYLSmMf56aAkGdv3evWz9vfK7K4,9344
|
|
13
|
-
librelane/common/misc.py,sha256=
|
|
13
|
+
librelane/common/misc.py,sha256=2zj62QbSuYaD2iYqGnM7vFR3ga41E3TxZpOOBYYIka8,13257
|
|
14
14
|
librelane/common/ring_buffer.py,sha256=DGFen9y0JOmiL7E27tmzDTVSJKZtV-waF9hl5Rz9uek,1898
|
|
15
|
-
librelane/common/tcl.py,sha256=
|
|
15
|
+
librelane/common/tcl.py,sha256=AfTxbSLA0VUXVMMwoAQndyQTcEZQoQfMa4FFizZiEgU,4341
|
|
16
16
|
librelane/common/toolbox.py,sha256=ijR__rVqQ_nJtfm34H-VdSCIeArKns7lVAc1TcTUSsQ,20975
|
|
17
17
|
librelane/common/tpe.py,sha256=Txj0fVscXSDJTYmEKZ2ESFHOeqrhHnaPPiwWBgyx4g8,1285
|
|
18
|
-
librelane/common/types.py,sha256=
|
|
18
|
+
librelane/common/types.py,sha256=xo_OKq-2ue7JVpyQb6oUu6JuVSnLNEFKQCPBqNhZnQ4,3509
|
|
19
19
|
librelane/config/__init__.py,sha256=lbJmD5CbrrrnaNdIUWqFIK488ea0uyej3iExh-9mkgE,1107
|
|
20
|
-
librelane/config/__main__.py,sha256=
|
|
20
|
+
librelane/config/__main__.py,sha256=NsJGoIOb950mdXql1zmzSq10wuFovK9NGWm011NNJ3A,4474
|
|
21
21
|
librelane/config/config.py,sha256=bAxB0qpw95YoLGmMjvxAwrX1hcpHRvNhH7wjQdyW-DE,35031
|
|
22
|
-
librelane/config/flow.py,sha256=
|
|
22
|
+
librelane/config/flow.py,sha256=DLB1h3UX_0cWgJblAmCSrsWL6dV0SEx0mz4ohIsV2uo,16892
|
|
23
23
|
librelane/config/pdk_compat.py,sha256=ofqYuD-MgTcfvPVXpGJo8H1GKzCvN6sxHsK_OqCVXa8,12870
|
|
24
24
|
librelane/config/preprocessor.py,sha256=ATi29SHz0_OBq1IqUkGxvhHUDKB5z5jO0KqvoQXg8R8,14913
|
|
25
25
|
librelane/config/removals.py,sha256=vxqTuRTJ0jt2TX4KmFZCZPTwghDFkCVjIhF2iReHwJA,2958
|
|
26
|
-
librelane/config/variable.py,sha256=
|
|
26
|
+
librelane/config/variable.py,sha256=YKRlnQu6YvkwnJ5zYfWTcj0fHP0Jcy22ZTb0i4kb3h4,26823
|
|
27
27
|
librelane/container.py,sha256=3KHxs3dUSVUZVYsS6fsA7dD3Q4QEQEzRxgXZZh9dzi0,7554
|
|
28
28
|
librelane/env_info.py,sha256=vAE9AZ_vDFLt7Srtg4ZywPzE6vgVhCrIvg8PP25-BJ8,10460
|
|
29
29
|
librelane/examples/spm/config.yaml,sha256=H2ERY4xoIeXN7kM3N9yGWiFBbtByyaN2Ni1kFqYPtO4,612
|
|
@@ -43,7 +43,7 @@ librelane/flows/__init__.py,sha256=ghtmUG-taVpHJ3CKJRYZGn3dU0r93araT1EIGlBEsxg,8
|
|
|
43
43
|
librelane/flows/builtins.py,sha256=tR14Qc1ZUey2w-Ar4DWOvxuP7LGPtMecCJq8WgcYJpk,773
|
|
44
44
|
librelane/flows/classic.py,sha256=JB9gVgP2hHPhMuCJg7hvoj2BvJcvRec7suEXPgHmz14,10971
|
|
45
45
|
librelane/flows/cli.py,sha256=P2LCFn5_RQ88yB0WuetpLAuWeKQXd-DrpCOMgnVh9Mg,16705
|
|
46
|
-
librelane/flows/flow.py,sha256=
|
|
46
|
+
librelane/flows/flow.py,sha256=qly_ENbw8zHSS6ubUY56JrCRjKnfuSoN78suz1k4chw,36997
|
|
47
47
|
librelane/flows/misc.py,sha256=32Om3isexesfKKiJZCajNmINc-xdv7eVx_tgoh9SR6U,2015
|
|
48
48
|
librelane/flows/optimizing.py,sha256=OwZz6WGmXpliwO8vtmhjKHD-kzDyNv-zoCECZIigXsI,6076
|
|
49
49
|
librelane/flows/sequential.py,sha256=kBpR9kxfEfdTaNy9Ter2KNQXkW6qojCwoBsFJBwTq6I,15359
|
|
@@ -64,19 +64,19 @@ librelane/scripts/magic/Readme.md,sha256=NaQrlxY8l8GT-kokJNlMHeAR3PksWVbFpSznOWW
|
|
|
64
64
|
librelane/scripts/magic/common/read.tcl,sha256=BiKyRi2ExXaR7WcSSmbtLfW-CaJU2DAmgVAi-XXrPGA,3120
|
|
65
65
|
librelane/scripts/magic/def/antenna_check.tcl,sha256=T_r1CWgySFVlLMcoTrNXQ_aMRs_fBAUYUUjyXJV1Sg0,1019
|
|
66
66
|
librelane/scripts/magic/def/mag.tcl,sha256=PuL3MH6pmZP5Qh2cJ0GygWNzaYjdCSCoAbOli-JB4fs,707
|
|
67
|
-
librelane/scripts/magic/def/mag_gds.tcl,sha256=
|
|
68
|
-
librelane/scripts/magic/drc.tcl,sha256=
|
|
69
|
-
librelane/scripts/magic/extract_spice.tcl,sha256
|
|
67
|
+
librelane/scripts/magic/def/mag_gds.tcl,sha256=fwtQR9zPZpWqVmmLb-1hzY4WMCr4gbA3S0pTZsS9sss,2144
|
|
68
|
+
librelane/scripts/magic/drc.tcl,sha256=gPGyI96lR10dJXcJACajzHaHiT6ayAYPJqrmmuQkABc,2395
|
|
69
|
+
librelane/scripts/magic/extract_spice.tcl,sha256=-jDxVEGddch1YZLZXW1vFXhdLkeTjxk0d8eU1tBMBWc,2788
|
|
70
70
|
librelane/scripts/magic/gds/drc_batch.tcl,sha256=O76rwxSrQgoCuoxk36tRBZkQaeMfJknlHrQA3mtU2JU,2198
|
|
71
71
|
librelane/scripts/magic/gds/erase_box.tcl,sha256=wsVSwMlkZFJa_MEGNsdXLnXFvjZlrl_lzIWkJjcDBgg,929
|
|
72
|
-
librelane/scripts/magic/gds/extras_mag.tcl,sha256=
|
|
73
|
-
librelane/scripts/magic/gds/mag_with_pointers.tcl,sha256=
|
|
72
|
+
librelane/scripts/magic/gds/extras_mag.tcl,sha256=1raG0URUezUDEKkdDeJqdlS0Y5gb4IzQFnjFysHHlmU,1304
|
|
73
|
+
librelane/scripts/magic/gds/mag_with_pointers.tcl,sha256=mfYTQdio1XAA0DpWlV5JS7c3rkxtSS06kZqVfDfKGuQ,1056
|
|
74
74
|
librelane/scripts/magic/get_bbox.tcl,sha256=iC0D5Pw09ZiNxHyy70Z0_axlc7eIT_jVGmvlutM8z_4,377
|
|
75
|
-
librelane/scripts/magic/lef/extras_maglef.tcl,sha256=
|
|
76
|
-
librelane/scripts/magic/lef/maglef.tcl,sha256=
|
|
75
|
+
librelane/scripts/magic/lef/extras_maglef.tcl,sha256=V0TivPN4aLJ0is497IAmOB1qQm8vmCqqycKuwV3w0HY,1586
|
|
76
|
+
librelane/scripts/magic/lef/maglef.tcl,sha256=I6-xFtsCZQtQI4UbU53gaWrpELWUd4agNF5LLgq9Eq0,856
|
|
77
77
|
librelane/scripts/magic/lef.tcl,sha256=Ij_v63siQrgYh56juCiZMcJBBCl2Lco3Ah4YKg-xiAU,2076
|
|
78
78
|
librelane/scripts/magic/open.tcl,sha256=qgk3HiT4qI-pMWn2E2MxwijgveCVmt6oCd8wEWh0jio,915
|
|
79
|
-
librelane/scripts/magic/wrapper.tcl,sha256=
|
|
79
|
+
librelane/scripts/magic/wrapper.tcl,sha256=qO33N2AiEYBTABMB5vWMWWEd2FpyOhg8JlcGsBVD6MY,712
|
|
80
80
|
librelane/scripts/netgen/setup.tcl,sha256=LBkdtVZpxrNGQ8UNwMkEQ-xSrkn9DNIBW_jzB9b1gYk,979
|
|
81
81
|
librelane/scripts/odbpy/apply_def_template.py,sha256=Tn6y65biu0bAQ6XilYxq5jn3a_KqjTl423-aXWI864k,1534
|
|
82
82
|
librelane/scripts/odbpy/cell_frequency.py,sha256=NfGgM8wxIvjM1C_GHUghZPOh8gpdasLOWR4qBdHHLFE,3105
|
|
@@ -94,9 +94,9 @@ librelane/scripts/odbpy/ioplace_parser/parse.py,sha256=L2GXzNA-gkjyySZcTWXrRRP8r
|
|
|
94
94
|
librelane/scripts/odbpy/label_macro_pins.py,sha256=n3o9-_g6HkVP8k49yNnCkQJms9f_ykCE0Rye7bVFtIk,8620
|
|
95
95
|
librelane/scripts/odbpy/lefutil.py,sha256=XhfWSGHdn96yZWYQAPisgJM0iuY3xw4SW7jmMTzbpZs,3064
|
|
96
96
|
librelane/scripts/odbpy/placers.py,sha256=mgy_-GYeLDPMG41YAopMTtJyCHP6ucJRk7cJzI9PLRQ,4572
|
|
97
|
-
librelane/scripts/odbpy/power_utils.py,sha256=
|
|
97
|
+
librelane/scripts/odbpy/power_utils.py,sha256=qbwhvW0QRiqtFXpYNGyfDIrhNZv9dt0JX2n4CQ8YRs8,14722
|
|
98
98
|
librelane/scripts/odbpy/random_place.py,sha256=TEsV4LtXQTP8OJvnBh09Siu9fKkwG9UpIkCkQpdXAgU,1649
|
|
99
|
-
librelane/scripts/odbpy/reader.py,sha256=
|
|
99
|
+
librelane/scripts/odbpy/reader.py,sha256=uoLHGKfXRoMdrfXmkgIj3HHa_sqMOFvB3DIUT3vSSQw,8542
|
|
100
100
|
librelane/scripts/odbpy/remove_buffers.py,sha256=f-kGZIPnMtu4gnl2r2CDkng8U8vUMJKJWNV_akOpc38,5460
|
|
101
101
|
librelane/scripts/odbpy/snap_to_grid.py,sha256=lULRWlcYXvrTBUpemUPlpO2dBnbFeriuG-DlI4KnViE,1743
|
|
102
102
|
librelane/scripts/odbpy/wire_lengths.py,sha256=pSPhVnLlvcvmgEh89G8nu8DRaZVP66r-4ieVoV3zrm4,2737
|
|
@@ -138,6 +138,7 @@ librelane/scripts/openroad/sta/check_macro_instances.tcl,sha256=j8DlW1wkVk5bLbG7
|
|
|
138
138
|
librelane/scripts/openroad/sta/corner.tcl,sha256=0YAzHFGbs4zRsB5E7e8zdzFyLuzrpV1w_S2BgrLfqak,15235
|
|
139
139
|
librelane/scripts/openroad/tapcell.tcl,sha256=4Ouy5U-_ct5Cfy3vuLQudWL0c1xWF_auLsr9rYh6dP4,1177
|
|
140
140
|
librelane/scripts/openroad/ungpl.tcl,sha256=vhHxou1W3VROwJePoQzmWn0h0d5lQrrt1vofyt-Woek,761
|
|
141
|
+
librelane/scripts/openroad/write_cdl.tcl,sha256=uPO1IROPTr5NrW0-VZA8tXQD8aseGeXDXDsU8TX-9nQ,460
|
|
141
142
|
librelane/scripts/openroad/write_views.tcl,sha256=-MxTJsB4EF7l5trDaZe-VBFjhfzqRt8F5_DZrADTs0U,892
|
|
142
143
|
librelane/scripts/pyosys/construct_abc_script.py,sha256=3CCDz5ZTEPpWLco-OvikTmn361-BNitqjQE_-5zHm14,6733
|
|
143
144
|
librelane/scripts/pyosys/json_header.py,sha256=C1BmKFRbwMknXV_RVp5QGbAxCwU6ElE6UIGRZceHQpI,2315
|
|
@@ -146,26 +147,26 @@ librelane/scripts/pyosys/ys_common.py,sha256=t5LLEYoy4cfCIeEaAo8Nr51rXtlI8ZPe1h_
|
|
|
146
147
|
librelane/scripts/tclsh/hello.tcl,sha256=kkR3akY7QnGHYXsQODYwLkMkUEOgWcNFtzaMTTEV2bY,34
|
|
147
148
|
librelane/state/__init__.py,sha256=DZ_RyKMr2oj4p5d32u8MmDKfCxR7OEdDw-1HWKTpatA,949
|
|
148
149
|
librelane/state/__main__.py,sha256=Ici4Ejg1ICUZNSYZRguC3BfEk_wFxsmE0ag0Vv8iY1I,1679
|
|
149
|
-
librelane/state/design_format.py,sha256=
|
|
150
|
-
librelane/state/state.py,sha256=
|
|
150
|
+
librelane/state/design_format.py,sha256=v1Nj-glM3Nc6SVXMZKanAMiaBjgDnW8KjAlmr_TiOqk,6560
|
|
151
|
+
librelane/state/state.py,sha256=tYn2si8NlkVErOSWKfVhsgrMpyxeX2Hv9EAPsQBWx2c,11902
|
|
151
152
|
librelane/steps/__init__.py,sha256=j3JYrdnWM74dYuEvE931oSrQI7FUz-hKWr8Mts8C0wg,1668
|
|
152
|
-
librelane/steps/__main__.py,sha256=
|
|
153
|
+
librelane/steps/__main__.py,sha256=GQZiV4s-9GIF4AwP34W61zwgzMvPp-QTR4cNELi7r5c,13349
|
|
153
154
|
librelane/steps/checker.py,sha256=HD5YFPAbHQKsFmBDrIAbo_0clZcCszNhIXb4lHaNIeQ,21629
|
|
154
155
|
librelane/steps/common_variables.py,sha256=eih2eA1m0FpL8ydF5WWattwh_SxtzI55eb8gggJtBuY,12494
|
|
155
|
-
librelane/steps/cvc_rv.py,sha256=
|
|
156
|
-
librelane/steps/klayout.py,sha256=
|
|
157
|
-
librelane/steps/magic.py,sha256=
|
|
156
|
+
librelane/steps/cvc_rv.py,sha256=qeroQPjidSAMYSp3nJNiQBYt8V73kkz3JK97uioo7J8,5294
|
|
157
|
+
librelane/steps/klayout.py,sha256=oHLb1gbKUSqEX9ofDcABHlrpuRYtGAUjuYTkzw7NGnU,22785
|
|
158
|
+
librelane/steps/magic.py,sha256=m4cZH2VomJs0RudtV8avSaZVqRj1NP7Pm2P6qo2z2X0,20919
|
|
158
159
|
librelane/steps/misc.py,sha256=8ubCvFeFEspXrgnzNWINY5-TXTyalNtlvcX8TSw0qdg,5685
|
|
159
160
|
librelane/steps/netgen.py,sha256=R9sDWv-9wKMdi2rkuLQdOc4uLlbYhXcKKd6WsZsnLt0,8953
|
|
160
|
-
librelane/steps/odb.py,sha256
|
|
161
|
-
librelane/steps/openroad.py,sha256=
|
|
161
|
+
librelane/steps/odb.py,sha256=-zsXi0jVdtfBfAJI0OC4x1jI_B2OX5YVn4uAn6NyFdk,38424
|
|
162
|
+
librelane/steps/openroad.py,sha256=hgpqsVQi7tFRlj75zefQkD3Vdmq21ubZAFWyU12WxUg,99586
|
|
162
163
|
librelane/steps/openroad_alerts.py,sha256=IJyB4piBDCKXhkJswHGMYCRDwbdQsR0GZlrGGDhmW6Q,3364
|
|
163
164
|
librelane/steps/pyosys.py,sha256=LY7qqxkhjfoyBBR7vdkm7ylabbxMJDwIoYm7mAUbLVY,23348
|
|
164
|
-
librelane/steps/step.py,sha256=
|
|
165
|
-
librelane/steps/tclstep.py,sha256=
|
|
165
|
+
librelane/steps/step.py,sha256=THIxZkhtkNYt1iRgMduD0ywrOTCaV7cCfUB2EqXN6-k,55751
|
|
166
|
+
librelane/steps/tclstep.py,sha256=8-zpYOo562E86nm7f4DiTqUsLKY0AFtEJgrp9CnWWDw,10083
|
|
166
167
|
librelane/steps/verilator.py,sha256=MWx2TpLqYyea9_jSeLG9c2S5ujvYERQZRFNaMhfHxZE,7916
|
|
167
168
|
librelane/steps/yosys.py,sha256=uC72fb1yFXyIxrtcRu5DxxR3hadG19SlGh668yjhWHc,12694
|
|
168
|
-
librelane-3.0.0.
|
|
169
|
-
librelane-3.0.0.
|
|
170
|
-
librelane-3.0.0.
|
|
171
|
-
librelane-3.0.0.
|
|
169
|
+
librelane-3.0.0.dev27.dist-info/METADATA,sha256=ZwojMc8UwfZmZy2TaBKKoXJUHtoy9ODA6cvoBewu5Vo,6561
|
|
170
|
+
librelane-3.0.0.dev27.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
171
|
+
librelane-3.0.0.dev27.dist-info/entry_points.txt,sha256=GTBvXykNMMFsNKiJFgtEw7P1wb_VZIqVM35EFSpyZQE,263
|
|
172
|
+
librelane-3.0.0.dev27.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|