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.

Files changed (34) hide show
  1. librelane/common/__init__.py +1 -0
  2. librelane/common/drc.py +1 -0
  3. librelane/common/misc.py +27 -5
  4. librelane/common/tcl.py +42 -0
  5. librelane/common/types.py +2 -3
  6. librelane/config/__main__.py +1 -1
  7. librelane/config/flow.py +12 -0
  8. librelane/config/variable.py +16 -0
  9. librelane/flows/flow.py +89 -21
  10. librelane/scripts/magic/def/mag_gds.tcl +1 -2
  11. librelane/scripts/magic/drc.tcl +0 -1
  12. librelane/scripts/magic/extract_spice.tcl +1 -1
  13. librelane/scripts/magic/gds/extras_mag.tcl +0 -2
  14. librelane/scripts/magic/gds/mag_with_pointers.tcl +0 -1
  15. librelane/scripts/magic/lef/extras_maglef.tcl +0 -2
  16. librelane/scripts/magic/lef/maglef.tcl +0 -1
  17. librelane/scripts/magic/wrapper.tcl +2 -0
  18. librelane/scripts/odbpy/power_utils.py +8 -6
  19. librelane/scripts/odbpy/reader.py +2 -2
  20. librelane/scripts/openroad/write_cdl.tcl +23 -0
  21. librelane/state/design_format.py +7 -0
  22. librelane/state/state.py +11 -3
  23. librelane/steps/__main__.py +1 -2
  24. librelane/steps/cvc_rv.py +1 -8
  25. librelane/steps/klayout.py +193 -1
  26. librelane/steps/magic.py +24 -14
  27. librelane/steps/odb.py +1 -4
  28. librelane/steps/openroad.py +15 -4
  29. librelane/steps/step.py +22 -7
  30. librelane/steps/tclstep.py +1 -1
  31. {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/METADATA +1 -1
  32. {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/RECORD +34 -33
  33. {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/WHEEL +0 -0
  34. {librelane-3.0.0.dev25.dist-info → librelane-3.0.0.dev27.dist-info}/entry_points.txt +0 -0
@@ -43,6 +43,7 @@ from .misc import (
43
43
  Filter,
44
44
  get_latest_file,
45
45
  process_list_file,
46
+ count_occurences,
46
47
  _get_process_limit,
47
48
  )
48
49
  from .types import (
librelane/common/drc.py CHANGED
@@ -287,6 +287,7 @@ class DRC:
287
287
  from lxml import etree as ET
288
288
 
289
289
  with ET.xmlfile(out, encoding="utf8", buffered=False) as xf:
290
+ xf.write_declaration()
290
291
  with xf.element("report-database"):
291
292
  # 1. Cells
292
293
  with xf.element("cells"):
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 fnmatch
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)
@@ -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((file.endswith(".sv") or file.endswith(".v")) for file in source_rtl):
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]],
@@ -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
- (flow-{slugify(Self.__name__, lower=True)})=
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
- result += f"| `{var.name}`{{#{var._get_docs_identifier(Self.__name__)}}}{pdk_superscript} | {var.type_repr_md()} | {var.desc_repr_md()} | `{var.default}` | {units} |\n"
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
- result += f"* [`{step.id}`](./step_config_vars.md#{slugify(name)})\n"
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
- signoff_folder = os.path.join(
856
- path, "signoff", self.config["DESIGN_NAME"], "librelane-signoff"
857
- )
858
- mkdirp(signoff_folder)
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
- # resolved.json
892
+ ## resolved.json
861
893
  shutil.copyfile(
862
894
  self.config_resolved_path,
863
- os.path.join(signoff_folder, "resolved.json"),
895
+ os.path.join(openlane_signoff_dir, "resolved.json"),
864
896
  follow_symlinks=True,
865
897
  )
866
898
 
867
- # Logs
868
- mkdirp(signoff_folder)
869
- copy_dir_contents(self.run_dir, signoff_folder, "*.log")
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
- # Step-specific
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
- if os.path.exists(reports_dir):
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, signoff_folder, "antenna_summary.rpt"
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(signoff_folder, "timing-reports")
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
@@ -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
@@ -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 "[magic::i2u 1]"
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) } {
@@ -43,5 +43,3 @@ if { [info exist ::env(EXTRA_GDS)] } {
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
@@ -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
- self.verilog_net_names_by_bit_by_module[top_module] = functools.reduce(
53
- lambda a, b: {**a, **{bit: b[0] for bit in b[1]["bits"]}},
54
- yosys_design_object["netnames"].items(),
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 functools
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
- @functools.wraps(function)
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
@@ -239,6 +239,13 @@ DesignFormat(
239
239
  alts=["SPICE"],
240
240
  ).register()
241
241
 
242
+ DesignFormat(
243
+ "cdl",
244
+ "cdl",
245
+ "Circuit Design Language",
246
+ alts=["CDL"],
247
+ ).register()
248
+
242
249
  DesignFormat(
243
250
  "gds",
244
251
  "gds",
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
- f.write("Metric,Value\n")
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.
@@ -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 = functools.partial(
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 List, Optional, Tuple
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]:
@@ -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[DesignFormat.GDS]:
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 = functools.reduce(
509
- lambda a, b: a + len(b.bounding_boxes),
510
- [
511
- v
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 = reduce(
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():
@@ -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 functools.reduce(
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
- ({Self.id.lower()}-configuration-variables)=
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
- result += f"| `{var.name}`{{#{var._get_docs_identifier(Self.id)}}}{pdk_superscript} | {var.type_repr_md(for_document=True)} | {var.desc_repr_md()} | `{var.default}` | {units} |\n"
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
- (step-{slugify(Self.id.lower())})=
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
- IPython-only. Displays Markdown help for a given step.
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
- import IPython.display
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
- IPython.display.display(IPython.display.Markdown(Self.get_help_md()))
728
+ console.log(Markdown(Self.get_help_md()))
714
729
 
715
730
  def _repr_markdown_(self) -> str: # pragma: no cover
716
731
  """
@@ -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, "a+") as f:
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: librelane
3
- Version: 3.0.0.dev25
3
+ Version: 3.0.0.dev27
4
4
  Summary: An infrastructure for implementing chip design flows
5
5
  Home-page: https://github.com/librelane/librelane
6
6
  License: Apache-2.0
@@ -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=WTSw8kJaR6JJsi1FLUbKAekE7qmXLTYEeoInLRXQdEc,1529
4
+ librelane/common/__init__.py,sha256=QhzRvLXLR2cGdYenayWYmCfgkTS836dWW41Ve1Wa5IE,1551
5
5
  librelane/common/cli.py,sha256=xi48GBGHRsYrLGwx40ARwpykHx7GnuHbJjHxjOwtZ5Y,2349
6
- librelane/common/drc.py,sha256=3hFkUZir0d5D_uMO8-JuaJpcd3hAp9ex_teBc16XkNY,12774
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=HnkvnObQzPvPgzDmUhm8lG4eXvTb4vFOcGIPGAnmXj8,12408
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=G0__oR6azVCS2Aue87zboba7vAR7v-SVQeUbqfviiDA,2709
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=oclAQkeluz_iopI_28clHzxvac7gN5moT8Rzipy5mgM,3468
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=Uhma2IQdvDTJiZs9ZIQA7r9zWSq7fZR-19P9UJWQclA,4476
20
+ librelane/config/__main__.py,sha256=NsJGoIOb950mdXql1zmzSq10wuFovK9NGWm011NNJ3A,4474
21
21
  librelane/config/config.py,sha256=bAxB0qpw95YoLGmMjvxAwrX1hcpHRvNhH7wjQdyW-DE,35031
22
- librelane/config/flow.py,sha256=RS8uspcrepl8GT5SsWrBQ-kmssO4c15_lrc2nBPVK5M,16482
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=v92bVwy11LgS53gYhU9DBwOUABc2XMGqEglqTORpPfc,26269
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=VIPF8LqiqQX-MYv4yPiQL1oEqrOOboi6N1a2BKmYWHs,34069
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=pPNKntSkVM0fzHuyVrUtxWc7HWY2-MIMoId6PW8SIT4,2137
68
- librelane/scripts/magic/drc.tcl,sha256=slHVfI6QoApKc2S6V3MIqcV5xnKRMJ_1wSGleJzkHpk,2402
69
- librelane/scripts/magic/extract_spice.tcl,sha256=lFNXfjIIzAWb2wda9ZtBOecENOOXyFBsh9HgrPYf7VQ,2737
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=jUu7vBUlJIvgldEC0UiohJC9FGnB2yiTzT1YbG_lmIo,1312
73
- librelane/scripts/magic/gds/mag_with_pointers.tcl,sha256=mK0MEeQMRPN0MopgMlF2v5mq9jkssHEFRKv5gssWbLI,1063
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=PsLCHxLslS3UxspmYf_vniL4Jzq54nZTa2AB5F90i58,1594
76
- librelane/scripts/magic/lef/maglef.tcl,sha256=MyCuqqR9zhKaC7OAVU2iCFIMkSZxl4hyc5bsEVnBG2k,863
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=rBeBSExykheFx7MTejjnX_OKROnWeDYNE5ciCAQvueU,704
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=al12uMiv8G0yQZOPKXNHYQ1dm2KGlu9xigSuYLEAo_A,14627
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=XHHZr3e9Esz4T5j35vYXHsURyRepUEb3CmNG46Uec8I,8541
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=75-XXzCfk5HUAJQAcdpwiHYkweeR9NwaXKtubCV0dqg,6461
150
- librelane/state/state.py,sha256=3CdihPR6lryQMt8ihSef0O2F8-qaqy1w7V0wiwie3nk,11710
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=GviXtDLISKJCufKxK3oFPOSMF1GyShZbG5RXpVCYFkk,13376
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=TNnEQDJI5tEUq8OQelVmBWmNbLzygrvKsZ36utKDvp4,5543
156
- librelane/steps/klayout.py,sha256=EFtzu53bWm-Bg_xEovdR7fc1GbWTwqkcivtr1rivHWU,16615
157
- librelane/steps/magic.py,sha256=Xtsy1KWu8dcNq3pRlqM9WRRxtwbCQJq24IyxC0W8D2o,20254
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=SD5m1ulI2f1LB0_0oKanY9Yql9YpKGolMqtfXun2xTY,38498
161
- librelane/steps/openroad.py,sha256=quPV082y__la4YgRKfB4wFslcn9E9nEDUSgSIlE3yRo,99340
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=T5z0Nm5z-fq_qj3BIwchnfUobmxX67cEmZ-5dUYT76s,55163
165
- librelane/steps/tclstep.py,sha256=YwyiSXAjRIflH2vzYvTzYfN4FyAI8Td9B_CKLkBj08o,10084
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.dev25.dist-info/METADATA,sha256=jBuGDi3vE2DO6eXdV0UE5_PDXxN5SErWVZePiZEfmmA,6561
169
- librelane-3.0.0.dev25.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
170
- librelane-3.0.0.dev25.dist-info/entry_points.txt,sha256=GTBvXykNMMFsNKiJFgtEw7P1wb_VZIqVM35EFSpyZQE,263
171
- librelane-3.0.0.dev25.dist-info/RECORD,,
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,,