siliconcompiler 0.35.3__py3-none-any.whl → 0.36.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/sc_issue.py +18 -2
  3. siliconcompiler/checklist.py +2 -1
  4. siliconcompiler/constraints/__init__.py +4 -1
  5. siliconcompiler/constraints/asic_component.py +49 -11
  6. siliconcompiler/constraints/asic_floorplan.py +23 -21
  7. siliconcompiler/constraints/asic_pins.py +55 -17
  8. siliconcompiler/constraints/asic_timing.py +280 -57
  9. siliconcompiler/constraints/fpga_timing.py +212 -18
  10. siliconcompiler/constraints/timing_mode.py +82 -0
  11. siliconcompiler/data/templates/replay/replay.sh.j2 +27 -14
  12. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +0 -6
  13. siliconcompiler/flowgraph.py +95 -42
  14. siliconcompiler/flows/generate_openroad_rcx.py +2 -2
  15. siliconcompiler/flows/highresscreenshotflow.py +37 -0
  16. siliconcompiler/library.py +2 -1
  17. siliconcompiler/package/__init__.py +56 -51
  18. siliconcompiler/project.py +13 -2
  19. siliconcompiler/scheduler/docker.py +24 -25
  20. siliconcompiler/scheduler/scheduler.py +143 -100
  21. siliconcompiler/scheduler/schedulernode.py +138 -22
  22. siliconcompiler/scheduler/slurm.py +120 -35
  23. siliconcompiler/scheduler/taskscheduler.py +19 -23
  24. siliconcompiler/schema/_metadata.py +1 -1
  25. siliconcompiler/schema/editableschema.py +29 -0
  26. siliconcompiler/schema/namedschema.py +2 -4
  27. siliconcompiler/schema/parametervalue.py +14 -2
  28. siliconcompiler/schema_support/cmdlineschema.py +0 -3
  29. siliconcompiler/schema_support/dependencyschema.py +0 -6
  30. siliconcompiler/schema_support/option.py +82 -1
  31. siliconcompiler/schema_support/pathschema.py +7 -13
  32. siliconcompiler/schema_support/record.py +4 -3
  33. siliconcompiler/tool.py +105 -52
  34. siliconcompiler/tools/_common/tcl/sc_schema_access.tcl +0 -6
  35. siliconcompiler/tools/keplerformal/__init__.py +7 -0
  36. siliconcompiler/tools/keplerformal/lec.py +112 -0
  37. siliconcompiler/tools/klayout/__init__.py +3 -0
  38. siliconcompiler/tools/klayout/screenshot.py +66 -1
  39. siliconcompiler/tools/klayout/scripts/klayout_convert_drc_db.py +1 -0
  40. siliconcompiler/tools/klayout/scripts/klayout_export.py +11 -40
  41. siliconcompiler/tools/klayout/scripts/klayout_operations.py +1 -0
  42. siliconcompiler/tools/klayout/scripts/klayout_show.py +5 -4
  43. siliconcompiler/tools/klayout/scripts/klayout_utils.py +16 -5
  44. siliconcompiler/tools/montage/tile.py +26 -12
  45. siliconcompiler/tools/openroad/__init__.py +27 -1
  46. siliconcompiler/tools/openroad/_apr.py +107 -14
  47. siliconcompiler/tools/openroad/clock_tree_synthesis.py +1 -0
  48. siliconcompiler/tools/openroad/global_placement.py +1 -0
  49. siliconcompiler/tools/openroad/init_floorplan.py +119 -7
  50. siliconcompiler/tools/openroad/power_grid_analysis.py +174 -0
  51. siliconcompiler/tools/openroad/repair_design.py +1 -0
  52. siliconcompiler/tools/openroad/repair_timing.py +1 -0
  53. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +1 -1
  54. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +91 -18
  55. siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +148 -0
  56. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -1
  57. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +8 -10
  58. siliconcompiler/tools/openroad/scripts/common/procs.tcl +15 -6
  59. siliconcompiler/tools/openroad/scripts/common/read_liberty.tcl +2 -2
  60. siliconcompiler/tools/openroad/scripts/common/reports.tcl +7 -4
  61. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
  62. siliconcompiler/tools/openroad/scripts/common/write_data_physical.tcl +8 -0
  63. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +16 -12
  64. siliconcompiler/tools/openroad/scripts/rcx/sc_rcx_bench.tcl +2 -4
  65. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -1
  66. siliconcompiler/tools/openroad/write_data.py +2 -2
  67. siliconcompiler/tools/opensta/__init__.py +1 -1
  68. siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +2 -2
  69. siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +2 -2
  70. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +13 -10
  71. siliconcompiler/tools/opensta/timing.py +6 -2
  72. siliconcompiler/tools/vivado/scripts/sc_bitstream.tcl +11 -0
  73. siliconcompiler/tools/vivado/scripts/sc_place.tcl +11 -0
  74. siliconcompiler/tools/vivado/scripts/sc_route.tcl +11 -0
  75. siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +10 -0
  76. siliconcompiler/tools/vpr/__init__.py +28 -0
  77. siliconcompiler/tools/yosys/scripts/sc_screenshot.tcl +1 -1
  78. siliconcompiler/tools/yosys/scripts/sc_synth_asic.tcl +40 -4
  79. siliconcompiler/tools/yosys/scripts/sc_synth_fpga.tcl +15 -5
  80. siliconcompiler/tools/yosys/syn_asic.py +42 -0
  81. siliconcompiler/tools/yosys/syn_fpga.py +8 -0
  82. siliconcompiler/toolscripts/_tools.json +12 -7
  83. siliconcompiler/toolscripts/ubuntu22/install-keplerformal.sh +72 -0
  84. siliconcompiler/toolscripts/ubuntu24/install-keplerformal.sh +72 -0
  85. siliconcompiler/utils/__init__.py +243 -51
  86. siliconcompiler/utils/curation.py +89 -56
  87. siliconcompiler/utils/issue.py +6 -1
  88. siliconcompiler/utils/multiprocessing.py +46 -2
  89. siliconcompiler/utils/paths.py +21 -0
  90. siliconcompiler/utils/settings.py +162 -0
  91. {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/METADATA +5 -4
  92. {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/RECORD +96 -87
  93. {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/WHEEL +0 -0
  94. {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/entry_points.txt +0 -0
  95. {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/licenses/LICENSE +0 -0
  96. {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/top_level.txt +0 -0
siliconcompiler/tool.py CHANGED
@@ -11,7 +11,6 @@ import shlex
11
11
  import shutil
12
12
  import subprocess
13
13
  import sys
14
- import threading
15
14
  import time
16
15
  import yaml
17
16
 
@@ -35,7 +34,7 @@ from packaging.specifiers import SpecifierSet, InvalidSpecifier
35
34
  from typing import List, Dict, Tuple, Union, Optional, Set, TextIO, Type, TypeVar, TYPE_CHECKING
36
35
  from pathlib import Path
37
36
 
38
- from siliconcompiler.schema import BaseSchema, NamedSchema, Journal, DocsSchema, LazyLoad
37
+ from siliconcompiler.schema import BaseSchema, NamedSchema, DocsSchema, LazyLoad
39
38
  from siliconcompiler.schema import EditableSchema, Parameter, PerNode, Scope
40
39
  from siliconcompiler.schema.parametertype import NodeType
41
40
  from siliconcompiler.schema.utils import trim
@@ -43,6 +42,7 @@ from siliconcompiler.schema.utils import trim
43
42
  from siliconcompiler import utils, NodeStatus, Flowgraph
44
43
  from siliconcompiler import sc_open
45
44
  from siliconcompiler.utils import paths
45
+ from siliconcompiler.utils.multiprocessing import MPManager
46
46
 
47
47
  from siliconcompiler.schema_support.pathschema import PathSchema
48
48
  from siliconcompiler.schema_support.record import RecordTool, RecordSchema
@@ -130,7 +130,8 @@ class Task(NamedSchema, PathSchema, DocsSchema):
130
130
  r"^\s*" + __parse_version_check_str + r"\s*$",
131
131
  re.VERBOSE | re.IGNORECASE)
132
132
 
133
- __POLL_INTERVAL: float = 0.1
133
+ __IO_POLL_INTERVAL: float = 0.1
134
+ __MEM_POLL_INTERVAL: float = 0.5
134
135
  __MEMORY_WARN_LIMIT: int = 90
135
136
 
136
137
  def __init__(self):
@@ -389,7 +390,7 @@ class Task(NamedSchema, PathSchema, DocsSchema):
389
390
  if self.tool() in tools:
390
391
  self.logger.info(f"Missing tool can be installed via: \"sc-install {self.tool()}\"")
391
392
 
392
- def get_exe_version(self) -> Optional[str]:
393
+ def get_exe_version(self, workdir: Optional[str] = None) -> Optional[str]:
393
394
  """
394
395
  Gets the version of the task's executable by running it with a version switch.
395
396
 
@@ -397,6 +398,10 @@ class Task(NamedSchema, PathSchema, DocsSchema):
397
398
  TaskExecutableNotFound: If the executable is not found.
398
399
  NotImplementedError: If the `parse_version` method is not implemented.
399
400
 
401
+ Args:
402
+ workdir (str): The working directory to use for the version check. If None,
403
+ the current working directory is used.
404
+
400
405
  Returns:
401
406
  str: The parsed version string.
402
407
  """
@@ -415,13 +420,14 @@ class Task(NamedSchema, PathSchema, DocsSchema):
415
420
  cmdlist.extend(veropt)
416
421
 
417
422
  self.logger.debug(f'Running {self.tool()}/{self.task()} version check: '
418
- f'{" ".join(cmdlist)}')
423
+ f'{shlex.join(cmdlist)}')
419
424
 
420
425
  proc = subprocess.run(cmdlist,
421
426
  stdin=subprocess.DEVNULL,
422
427
  stdout=subprocess.PIPE,
423
428
  stderr=subprocess.STDOUT,
424
- universal_newlines=True)
429
+ universal_newlines=True,
430
+ cwd=workdir)
425
431
 
426
432
  if proc.returncode != 0:
427
433
  self.logger.warning(f"Version check on '{exe_base}' ended with "
@@ -735,9 +741,7 @@ class Task(NamedSchema, PathSchema, DocsSchema):
735
741
  fout.write(template.render(manifest_dict='\n'.join(tcl_set_cmds),
736
742
  scroot=os.path.abspath(
737
743
  os.path.join(os.path.dirname(__file__))),
738
- toolvars=self.get_tcl_variables(manifest),
739
- record_access="get" in Journal.access(self).get_types(),
740
- record_access_id="TODO"))
744
+ toolvars=self.get_tcl_variables(manifest)))
741
745
  else:
742
746
  for cmd in tcl_set_cmds:
743
747
  fout.write(cmd + '\n')
@@ -900,6 +904,37 @@ class Task(NamedSchema, PathSchema, DocsSchema):
900
904
  f'{timeout} seconds. Terminating...')
901
905
  terminate_process(proc.pid, timeout=timeout)
902
906
 
907
+ def __collect_memory(self, pid) -> Optional[int]:
908
+ try:
909
+ pproc = psutil.Process(pid)
910
+ proc_mem_bytes = pproc.memory_full_info().uss
911
+ for child in pproc.children(recursive=True):
912
+ proc_mem_bytes += child.memory_full_info().uss
913
+ return proc_mem_bytes
914
+ except psutil.Error:
915
+ # Process may have already terminated or been killed.
916
+ # Retain existing memory usage statistics in this case.
917
+ pass
918
+ except PermissionError:
919
+ # OS is preventing access to this information so it cannot
920
+ # be collected
921
+ pass
922
+ return None
923
+
924
+ def __check_memory_limit(self, warn_limit: int) -> int:
925
+ try:
926
+ memory_usage = psutil.virtual_memory()
927
+ if memory_usage.percent > warn_limit:
928
+ self.logger.warning(
929
+ 'Current system memory usage is '
930
+ f'{memory_usage.percent:.1f}%')
931
+ return int(memory_usage.percent + 1)
932
+ except psutil.Error:
933
+ pass
934
+ except PermissionError:
935
+ pass
936
+ return warn_limit
937
+
903
938
  def run_task(self,
904
939
  workdir: str,
905
940
  quiet: bool,
@@ -1034,39 +1069,29 @@ class Task(NamedSchema, PathSchema, DocsSchema):
1034
1069
  raise TaskError(f"Unable to start {exe}: {str(e)}")
1035
1070
 
1036
1071
  memory_warn_limit = Task.__MEMORY_WARN_LIMIT
1072
+ next_collection = None
1037
1073
  try:
1038
1074
  while proc.poll() is None:
1075
+ curr_time = time.time()
1076
+
1039
1077
  # Monitor subprocess memory usage
1040
- try:
1041
- pproc = psutil.Process(proc.pid)
1042
- proc_mem_bytes = pproc.memory_full_info().uss
1043
- for child in pproc.children(recursive=True):
1044
- proc_mem_bytes += child.memory_full_info().uss
1045
- max_mem_bytes = max(max_mem_bytes, proc_mem_bytes)
1046
-
1047
- memory_usage = psutil.virtual_memory()
1048
- if memory_usage.percent > memory_warn_limit:
1049
- self.logger.warning(
1050
- 'Current system memory usage is '
1051
- f'{memory_usage.percent:.1f}%')
1052
- memory_warn_limit = int(memory_usage.percent + 1)
1053
- except psutil.Error:
1054
- # Process may have already terminated or been killed.
1055
- # Retain existing memory usage statistics in this case.
1056
- pass
1057
- except PermissionError:
1058
- # OS is preventing access to this information so it cannot
1059
- # be collected
1060
- pass
1078
+ if next_collection is None or \
1079
+ next_collection <= curr_time:
1080
+ proc_mem_bytes = self.__collect_memory(proc.pid)
1081
+ if proc_mem_bytes is not None:
1082
+ max_mem_bytes = max(max_mem_bytes, proc_mem_bytes)
1083
+ next_collection = curr_time + Task.__MEM_POLL_INTERVAL
1084
+
1085
+ memory_warn_limit = self.__check_memory_limit(memory_warn_limit)
1061
1086
 
1062
1087
  read_stdio(stdout_reader, stderr_reader)
1063
1088
 
1064
1089
  # Check for timeout
1065
- duration = time.time() - cpu_start
1090
+ duration = curr_time - cpu_start
1066
1091
  if timeout is not None and duration > timeout:
1067
1092
  raise TaskTimeout(timeout=duration)
1068
1093
 
1069
- time.sleep(Task.__POLL_INTERVAL)
1094
+ time.sleep(Task.__IO_POLL_INTERVAL)
1070
1095
  except KeyboardInterrupt:
1071
1096
  self.logger.info("Received ctrl-c.")
1072
1097
  self.__terminate_exe(proc)
@@ -2062,9 +2087,6 @@ class ShowTask(Task):
2062
2087
  Subclasses should implement `get_supported_show_extentions` to declare
2063
2088
  which file types they can handle.
2064
2089
  """
2065
- __TASKS_LOCK = threading.Lock()
2066
- __TASKS = {}
2067
-
2068
2090
  def __init__(self):
2069
2091
  """Initializes a ShowTask, adding specific parameters for show tasks."""
2070
2092
  super().__init__()
@@ -2111,8 +2133,10 @@ class ShowTask(Task):
2111
2133
  if not cls.__check_task(task):
2112
2134
  raise TypeError(f"task must be a subclass of {cls.__name__}")
2113
2135
 
2114
- with cls.__TASKS_LOCK:
2115
- cls.__TASKS.setdefault(cls, set()).add(task)
2136
+ MPManager.get_transient_settings().set(
2137
+ cls.__name__,
2138
+ f"{task.__module__}/{task.__name__}",
2139
+ task)
2116
2140
 
2117
2141
  @classmethod
2118
2142
  def __populate_tasks(cls) -> None:
@@ -2124,6 +2148,9 @@ class ShowTask(Task):
2124
2148
  """
2125
2149
  cls.__check_task(None)
2126
2150
 
2151
+ if MPManager.get_transient_settings().get_category(cls.__name__):
2152
+ return # Already populated
2153
+
2127
2154
  def recurse(searchcls: Type["ShowTask"]):
2128
2155
  subclss = set()
2129
2156
  if not cls.__check_task(searchcls):
@@ -2138,20 +2165,25 @@ class ShowTask(Task):
2138
2165
  classes = recurse(cls)
2139
2166
 
2140
2167
  # Support non-SC defined tasks from plugins
2141
- for plugin in utils.get_plugins('showtask'): # TODO rename
2168
+ for plugin in utils.get_plugins('showtask'):
2142
2169
  plugin()
2143
2170
 
2144
2171
  if not classes:
2145
2172
  return
2146
2173
 
2147
- with ShowTask.__TASKS_LOCK:
2148
- ShowTask.__TASKS.setdefault(cls, set()).update(classes)
2174
+ for c in classes:
2175
+ cls.register_task(c)
2149
2176
 
2150
2177
  @classmethod
2151
2178
  def get_task(cls, ext: Optional[str]) -> Union[Optional["ShowTask"], Set[Type["ShowTask"]]]:
2152
2179
  """
2153
2180
  Retrieves a suitable show task instance for a given file extension.
2154
2181
 
2182
+ The method first checks the user's settings file (~/.sc/settings.json)
2183
+ under the 'showtask' category for a preferred tool. If no preference
2184
+ is found or the preferred tool is not available, it falls back to
2185
+ automatic discovery.
2186
+
2155
2187
  Args:
2156
2188
  ext (str): The file extension to find a viewer for.
2157
2189
 
@@ -2160,24 +2192,45 @@ class ShowTask(Task):
2160
2192
  no suitable task is found.
2161
2193
  """
2162
2194
  cls.__check_task(None)
2195
+ cls.__populate_tasks()
2163
2196
 
2164
- if cls not in ShowTask.__TASKS:
2165
- cls.__populate_tasks()
2166
-
2167
- with ShowTask.__TASKS_LOCK:
2168
- if cls not in ShowTask.__TASKS:
2169
- return None
2170
- tasks = ShowTask.__TASKS[cls].copy()
2171
-
2172
- # TODO: add user preference lookup (ext -> task)
2197
+ settings = MPManager.get_transient_settings()
2198
+ tasks = set(settings.get_category(cls.__name__).values())
2199
+ if not tasks:
2200
+ return None
2173
2201
 
2174
2202
  if ext is None:
2175
2203
  return tasks
2176
2204
 
2177
- for task in tasks:
2205
+ # 1. Check User Settings for Preference
2206
+ preference = MPManager.get_settings().get("showtask", ext)
2207
+
2208
+ if preference:
2209
+ # Preference format: "tool" or "tool/task"
2210
+ pref_parts = preference.split('/')
2211
+ pref_tool = pref_parts[0]
2212
+ pref_task = pref_parts[1] if len(pref_parts) > 1 else None
2213
+
2214
+ for task_cls in tasks:
2215
+ try:
2216
+ task_inst = task_cls()
2217
+ # Check if this task matches the preference
2218
+ if task_inst.tool() == pref_tool:
2219
+ if pref_task and task_inst.task() != pref_task:
2220
+ continue
2221
+
2222
+ # Verify the preferred tool actually supports the extension
2223
+ if ext in task_inst.get_supported_show_extentions():
2224
+ return task_inst
2225
+ except NotImplementedError:
2226
+ continue
2227
+
2228
+ # 2. Fallback to Automatic Discovery
2229
+ for task_cls in tasks:
2178
2230
  try:
2179
- if ext in task().get_supported_show_extentions():
2180
- return task()
2231
+ task_inst = task_cls()
2232
+ if ext in task_inst.get_supported_show_extentions():
2233
+ return task_inst
2181
2234
  except NotImplementedError:
2182
2235
  pass
2183
2236
 
@@ -1,11 +1,5 @@
1
- proc _sc_cfg_get_debug { args } {
2
-
3
- }
4
-
5
1
  # Shortcut to get values from configuration
6
2
  proc sc_cfg_get { args } {
7
- _sc_cfg_get_debug $args
8
-
9
3
  # Refer to global sc_cfg dictionary
10
4
  global sc_cfg
11
5
 
@@ -0,0 +1,7 @@
1
+ '''
2
+ Kepler-Formal is a logic equivalence checking (LEC) tool that operates on verilog
3
+ and the naja interchange format (https://github.com/najaeda/naja-if) and focuses
4
+ today on combinational equivalence checking only.
5
+
6
+ Sources: https://github.com/keplertech/kepler-formal
7
+ '''
@@ -0,0 +1,112 @@
1
+ import os.path
2
+
3
+ from siliconcompiler import Task
4
+ from siliconcompiler.utils import sc_open
5
+
6
+
7
+ class LECTask(Task):
8
+ """LEC task using Kepler-formal for logical equivalence checking."""
9
+ def tool(self) -> str:
10
+ return "kepler-formal"
11
+
12
+ def task(self) -> str:
13
+ return "lec"
14
+
15
+ def setup(self) -> None:
16
+ super().setup()
17
+
18
+ self.set_exe("kepler-formal")
19
+
20
+ # Log file handled by kepler-formal config
21
+ self.set_logdestination("stdout", "none")
22
+ self.set_logdestination("stderr", "none")
23
+
24
+ if f"{self.design_topmodule}.lec.vg" in self.get_files_from_input_nodes():
25
+ inputnodes = self.get_files_from_input_nodes()[f"{self.design_topmodule}.lec.vg"]
26
+ if len(inputnodes) != 2:
27
+ raise ValueError("LEC requires exactly two input netlists for comparison.")
28
+ for node in inputnodes:
29
+ self.add_input_file(
30
+ self.compute_input_file_node_name(f"{self.design_topmodule}.lec.vg", *node))
31
+
32
+ scenarios = self.project.constraint.timing.get_scenario()
33
+ if not scenarios:
34
+ raise ValueError("LEC requires at least one timing scenario to determine "
35
+ "library corners.")
36
+ scenario = list(scenarios.values())[0]
37
+ libcorners = scenario.get_libcorner(self.step, self.index)
38
+ delay_model = self.project.get("asic", "delaymodel")
39
+ for asiclib in self.project.get("asic", "asiclib"):
40
+ lib = self.project.get("library", asiclib, field="schema")
41
+ for corner in libcorners:
42
+ if not lib.valid("asic", "libcornerfileset", corner, delay_model):
43
+ continue
44
+ self.add_required_key(lib, "asic", "libcornerfileset", corner, delay_model)
45
+ for fileset in lib.get("asic", "libcornerfileset", corner, delay_model):
46
+ self.add_required_key(lib, "fileset", fileset, "file", "liberty")
47
+
48
+ def __config_file(self) -> str:
49
+ return "lec.yaml"
50
+
51
+ def pre_process(self):
52
+ super().pre_process()
53
+
54
+ with open(self.__config_file(), "w") as f:
55
+ f.write("format: verilog\n")
56
+ f.write("input_paths:\n")
57
+ for node in self.get_files_from_input_nodes()[f"{self.design_topmodule}.lec.vg"]:
58
+ filename = self.compute_input_file_node_name(f"{self.design_topmodule}.lec.vg",
59
+ *node)
60
+ f.write(f" - inputs/{filename}\n")
61
+ f.write("liberty_files:\n")
62
+
63
+ scenario = list(self.project.constraint.timing.get_scenario().values())[0]
64
+ libcorners = scenario.get_libcorner(self.step, self.index)
65
+ delay_model = self.project.get("asic", "delaymodel")
66
+ for asiclib in self.project.get("asic", "asiclib"):
67
+ lib = self.project.get("library", asiclib, field="schema")
68
+ for corner in libcorners:
69
+ if not lib.valid("asic", "libcornerfileset", corner, delay_model):
70
+ continue
71
+ for fileset in lib.get("asic", "libcornerfileset", corner, delay_model):
72
+ for file in lib.get_file(fileset=fileset, filetype="liberty"):
73
+ f.write(f" - {file}\n")
74
+ f.write(f"log_file: {self.get_logpath('exe')}\n")
75
+
76
+ def runtime_options(self):
77
+ options = super().runtime_options()
78
+ options.append("--config")
79
+ options.append(self.__config_file())
80
+ return options
81
+
82
+ def post_process(self):
83
+ super().post_process()
84
+ # Kepler-formal writes its own log file; nothing to do here.
85
+ errors = 0
86
+ log = self.get_logpath('exe')
87
+ if os.path.exists(log):
88
+ with sc_open(log, 'r') as f:
89
+ for logline in f:
90
+ if "Found difference " in logline:
91
+ errors += 1
92
+
93
+ self.record_metric("drvs", errors, source_file=log)
94
+
95
+ @classmethod
96
+ def make_docs(cls):
97
+ from siliconcompiler import Flowgraph, Design, ASIC
98
+ from siliconcompiler.scheduler import SchedulerNode
99
+ from siliconcompiler.targets import freepdk45_demo
100
+ design = Design("<design>")
101
+ with design.active_fileset("docs"):
102
+ design.set_topmodule("top")
103
+ proj = ASIC(design)
104
+ proj.add_fileset("docs")
105
+ freepdk45_demo(proj)
106
+ flow = Flowgraph("docsflow")
107
+ flow.node("<step>", cls(), index="<index>")
108
+ proj.set_flow(flow)
109
+
110
+ node = SchedulerNode(proj, "<step>", "<index>")
111
+ node.setup()
112
+ return node.task
@@ -172,6 +172,7 @@ class KLayoutTask(ASICTask):
172
172
  with self.active_dataroot("refdir"):
173
173
  self.set_refdir("scripts")
174
174
 
175
+ self.set_environmentalvariable('PYTHONUNBUFFERED', '1')
175
176
  if self.project.get('option', 'nodisplay'):
176
177
  # Tells QT to use the offscreen platform if nodisplay is used
177
178
  self.set_environmentalvariable('QT_QPA_PLATFORM', 'offscreen')
@@ -185,6 +186,8 @@ class KLayoutTask(ASICTask):
185
186
  options = super().runtime_options()
186
187
  options.extend(['-rd', f'SC_KLAYOUT_ROOT={self.find_files("refdir")[0]}'])
187
188
  options.extend(['-rd', f'SC_TOOLS_ROOT={os.path.dirname(os.path.dirname(__file__))}'])
189
+ options.extend(['-rd',
190
+ f'SC_ROOT={os.path.dirname(os.path.dirname(os.path.dirname(__file__)))}'])
188
191
  return options
189
192
 
190
193
  def post_process(self):
@@ -1,3 +1,5 @@
1
+ from typing import Optional, Union
2
+
1
3
  from siliconcompiler import ScreenshotTask, Task
2
4
  from siliconcompiler.tools.klayout.show import ShowTask
3
5
 
@@ -20,7 +22,70 @@ class ScreenshotParams(Task):
20
22
  self.add_parameter("show_linewidth", "int",
21
23
  "Width of lines in detailed screenshots", defvalue=0, unit="px")
22
24
  self.add_parameter("show_oversampling", "int",
23
- "Image oversampling used in detailed screenshots'", defvalue=2)
25
+ "Image oversampling used in detailed screenshots", defvalue=2)
26
+
27
+ def set_klayout_bins(self, xbins: int, ybins: int,
28
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
29
+ """
30
+ Set the number of bins for KLayout screenshotting.
31
+
32
+ Args:
33
+ xbins (int): Number of bins along the x-axis.
34
+ ybins (int): Number of bins along the y-axis.
35
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
36
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
37
+ """
38
+ self.set("var", "show_bins", (xbins, ybins), step=step, index=index)
39
+
40
+ def set_klayout_margin(self, margin: float,
41
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
42
+ """
43
+ Set the margin for KLayout screenshotting.
44
+
45
+ Args:
46
+ margin (float): Margin around the design in microns.
47
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
48
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
49
+ """
50
+ self.set("var", "show_margin", margin, step=step, index=index)
51
+
52
+ def set_klayout_resolution(self, xres: int, yres: int,
53
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
54
+ """
55
+ Set the resolution for KLayout screenshotting.
56
+
57
+ Args:
58
+ xres (int): Horizontal resolution in pixels.
59
+ yres (int): Vertical resolution in pixels.
60
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
61
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
62
+ """
63
+ self.set("var", "show_resolution", (xres, yres), step=step, index=index)
64
+
65
+ def set_klayout_linewidth(self, linewidth: int,
66
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
67
+ """
68
+ Set the linewidth for KLayout screenshotting.
69
+
70
+ Args:
71
+ linewidth (int): Width of lines in detailed screenshots.
72
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
73
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
74
+ """
75
+ self.set("var", "show_linewidth", linewidth, step=step, index=index)
76
+
77
+ def set_klayout_oversampling(self, oversampling: int,
78
+ step: Optional[str] = None,
79
+ index: Optional[Union[str, int]] = None):
80
+ """
81
+ Set the oversampling for KLayout screenshotting.
82
+
83
+ Args:
84
+ oversampling (int): Image oversampling used in detailed screenshots.
85
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
86
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
87
+ """
88
+ self.set("var", "show_oversampling", oversampling, step=step, index=index)
24
89
 
25
90
  def setup(self):
26
91
  super().setup()
@@ -151,6 +151,7 @@ def convert_drc(view, path):
151
151
  def main():
152
152
  # SC_ROOT provided by CLI
153
153
  sys.path.append(SC_KLAYOUT_ROOT) # noqa: F821
154
+ sys.path.append(SC_ROOT) # noqa: F821
154
155
 
155
156
  from klayout_utils import get_schema
156
157
 
@@ -39,24 +39,23 @@ import sys
39
39
  import fnmatch
40
40
 
41
41
 
42
- def gds_export(design_name, in_def, in_files, out_file, tech, allow_missing, config_file='',
43
- seal_file='',
44
- timestamps=True):
42
+ def gds_export(design_name, in_def, out_file, tech, allow_missing, timestamps=True):
45
43
  from klayout_utils import get_write_options # noqa E402
46
44
 
47
45
  # Load def file
48
46
  main_layout = pya.Layout()
49
47
  main_layout.technology_name = tech.name
50
- main_layout.read(in_def, tech.load_layout_options)
48
+ layout_options = tech.load_layout_options
49
+ main_layout.read(in_def, layout_options)
51
50
 
52
51
  # List cells
53
52
  def_cells = []
54
- for def_cell in main_layout.each_cell():
55
- def_cells.append(def_cell.name)
53
+ for cell_idx in main_layout.cell(design_name).each_child_cell():
54
+ def_cells.append(main_layout.cell_name(cell_idx))
56
55
 
57
- def_cells.remove(design_name)
58
56
  # Remove vias
59
- def_cells = sorted([cell for cell in def_cells if not cell.startswith("VIA_")])
57
+ via_prefix = layout_options.lefdef_config.via_cellname_prefix
58
+ def_cells = sorted([cell for cell in def_cells if not cell.startswith(via_prefix)])
60
59
  print(f"[INFO] Read in {len(def_cells)} cells from DEF file")
61
60
  for cell in def_cells:
62
61
  print(f" [INFO] DEF cell: {cell}")
@@ -65,16 +64,11 @@ def gds_export(design_name, in_def, in_files, out_file, tech, allow_missing, con
65
64
  def_cells.remove(f"{design_name}_DEF_FILL")
66
65
 
67
66
  # Load in the gds to merge
68
- print("[INFO] Merging GDS/OAS files...")
69
- for fil in in_files:
67
+ for fil in layout_options.lefdef_config.macro_layout_files:
70
68
  macro_layout = pya.Layout()
71
69
  macro_layout.read(fil)
72
- print(f"[INFO] Read in {fil}")
73
70
  for cell in list(def_cells):
74
71
  if macro_layout.has_cell(cell):
75
- subcell = main_layout.cell(cell)
76
- print(f" [INFO] Merging in {cell}")
77
- subcell.copy_tree(macro_layout.cell(cell))
78
72
  def_cells.remove(cell)
79
73
 
80
74
  # Copy the top level only to a new layout
@@ -100,18 +94,6 @@ def gds_export(design_name, in_def, in_files, out_file, tech, allow_missing, con
100
94
  if i.name != design_name and i.parent_cells() == 0:
101
95
  print("[ERROR] Found orphan cell '{0}'".format(i.name))
102
96
 
103
- if seal_file:
104
- top_cell = top_only_layout.top_cell()
105
-
106
- print("[INFO] Reading seal GDS/OAS file...")
107
- print("\t{0}".format(seal_file))
108
- top_only_layout.read(seal_file)
109
-
110
- for cell in top_only_layout.top_cells():
111
- if cell != top_cell:
112
- print("[INFO] Merging '{0}' as child of '{1}'".format(cell.name, top_cell.name))
113
- top.insert(pya.CellInstArray(cell.cell_index(), pya.Trans()))
114
-
115
97
  # Write out the GDS
116
98
  print("[INFO] Writing out GDS/OAS '{0}'".format(out_file))
117
99
  top_only_layout.write(out_file, get_write_options(out_file, timestamps))
@@ -121,10 +103,10 @@ def main():
121
103
  # SC_ROOT provided by CLI
122
104
  sys.path.append(SC_KLAYOUT_ROOT) # noqa: F821
123
105
  sys.path.append(SC_TOOLS_ROOT) # noqa: F821
106
+ sys.path.append(SC_ROOT) # noqa: F821
124
107
 
125
108
  from klayout_utils import (
126
109
  technology,
127
- get_streams,
128
110
  save_technology,
129
111
  get_schema,
130
112
  generate_metrics
@@ -154,18 +136,8 @@ def main():
154
136
 
155
137
  out_file = os.path.join('outputs', f'{design}.{sc_stream}')
156
138
 
157
- in_files = []
158
- libs = schema.get("asic", "asiclib")
159
- for lib in libs:
160
- libobj = schema.get("library", lib, field="schema")
161
- for s in get_streams(schema):
162
- for fileset in libobj.get("asic", "aprfileset"):
163
- if libobj.valid("fileset", fileset, "file", s):
164
- in_files.extend(libobj.get("fileset", fileset, "file", s))
165
- break
166
-
167
139
  allow_missing = []
168
- for lib in libs:
140
+ for lib in schema.get("asic", "asiclib"):
169
141
  if schema.valid('library', lib, 'tool', 'klayout', 'allow_missing_cell'):
170
142
  patterns = [pattern for pattern in schema.get('library', lib, 'tool', 'klayout',
171
143
  'allow_missing_cell') if pattern]
@@ -178,8 +150,7 @@ def main():
178
150
 
179
151
  sc_tech = technology(design, schema)
180
152
 
181
- gds_export(design, in_def, in_files, out_file, sc_tech, allow_missing,
182
- config_file='', seal_file='', timestamps=sc_timestamps)
153
+ gds_export(design, in_def, out_file, sc_tech, allow_missing, timestamps=sc_timestamps)
183
154
 
184
155
  if sc_screenshot:
185
156
  show(schema, sc_tech, out_file, f'outputs/{design}.png', screenshot=True)
@@ -326,6 +326,7 @@ if __name__ == "__main__":
326
326
  # SC_ROOT provided by CLI
327
327
  sys.path.append(SC_KLAYOUT_ROOT) # noqa: F821
328
328
  sys.path.append(SC_TOOLS_ROOT) # noqa: F821
329
+ sys.path.append(SC_ROOT) # noqa: F821
329
330
 
330
331
  from klayout_utils import (
331
332
  technology,