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.
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_issue.py +18 -2
- siliconcompiler/checklist.py +2 -1
- siliconcompiler/constraints/__init__.py +4 -1
- siliconcompiler/constraints/asic_component.py +49 -11
- siliconcompiler/constraints/asic_floorplan.py +23 -21
- siliconcompiler/constraints/asic_pins.py +55 -17
- siliconcompiler/constraints/asic_timing.py +280 -57
- siliconcompiler/constraints/fpga_timing.py +212 -18
- siliconcompiler/constraints/timing_mode.py +82 -0
- siliconcompiler/data/templates/replay/replay.sh.j2 +27 -14
- siliconcompiler/data/templates/tcl/manifest.tcl.j2 +0 -6
- siliconcompiler/flowgraph.py +95 -42
- siliconcompiler/flows/generate_openroad_rcx.py +2 -2
- siliconcompiler/flows/highresscreenshotflow.py +37 -0
- siliconcompiler/library.py +2 -1
- siliconcompiler/package/__init__.py +56 -51
- siliconcompiler/project.py +13 -2
- siliconcompiler/scheduler/docker.py +24 -25
- siliconcompiler/scheduler/scheduler.py +143 -100
- siliconcompiler/scheduler/schedulernode.py +138 -22
- siliconcompiler/scheduler/slurm.py +120 -35
- siliconcompiler/scheduler/taskscheduler.py +19 -23
- siliconcompiler/schema/_metadata.py +1 -1
- siliconcompiler/schema/editableschema.py +29 -0
- siliconcompiler/schema/namedschema.py +2 -4
- siliconcompiler/schema/parametervalue.py +14 -2
- siliconcompiler/schema_support/cmdlineschema.py +0 -3
- siliconcompiler/schema_support/dependencyschema.py +0 -6
- siliconcompiler/schema_support/option.py +82 -1
- siliconcompiler/schema_support/pathschema.py +7 -13
- siliconcompiler/schema_support/record.py +4 -3
- siliconcompiler/tool.py +105 -52
- siliconcompiler/tools/_common/tcl/sc_schema_access.tcl +0 -6
- siliconcompiler/tools/keplerformal/__init__.py +7 -0
- siliconcompiler/tools/keplerformal/lec.py +112 -0
- siliconcompiler/tools/klayout/__init__.py +3 -0
- siliconcompiler/tools/klayout/screenshot.py +66 -1
- siliconcompiler/tools/klayout/scripts/klayout_convert_drc_db.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_export.py +11 -40
- siliconcompiler/tools/klayout/scripts/klayout_operations.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_show.py +5 -4
- siliconcompiler/tools/klayout/scripts/klayout_utils.py +16 -5
- siliconcompiler/tools/montage/tile.py +26 -12
- siliconcompiler/tools/openroad/__init__.py +27 -1
- siliconcompiler/tools/openroad/_apr.py +107 -14
- siliconcompiler/tools/openroad/clock_tree_synthesis.py +1 -0
- siliconcompiler/tools/openroad/global_placement.py +1 -0
- siliconcompiler/tools/openroad/init_floorplan.py +119 -7
- siliconcompiler/tools/openroad/power_grid_analysis.py +174 -0
- siliconcompiler/tools/openroad/repair_design.py +1 -0
- siliconcompiler/tools/openroad/repair_timing.py +1 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +91 -18
- siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +148 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +8 -10
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +15 -6
- siliconcompiler/tools/openroad/scripts/common/read_liberty.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +7 -4
- siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/common/write_data_physical.tcl +8 -0
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +16 -12
- siliconcompiler/tools/openroad/scripts/rcx/sc_rcx_bench.tcl +2 -4
- siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -1
- siliconcompiler/tools/openroad/write_data.py +2 -2
- siliconcompiler/tools/opensta/__init__.py +1 -1
- siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +2 -2
- siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +2 -2
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +13 -10
- siliconcompiler/tools/opensta/timing.py +6 -2
- siliconcompiler/tools/vivado/scripts/sc_bitstream.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_place.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_route.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +10 -0
- siliconcompiler/tools/vpr/__init__.py +28 -0
- siliconcompiler/tools/yosys/scripts/sc_screenshot.tcl +1 -1
- siliconcompiler/tools/yosys/scripts/sc_synth_asic.tcl +40 -4
- siliconcompiler/tools/yosys/scripts/sc_synth_fpga.tcl +15 -5
- siliconcompiler/tools/yosys/syn_asic.py +42 -0
- siliconcompiler/tools/yosys/syn_fpga.py +8 -0
- siliconcompiler/toolscripts/_tools.json +12 -7
- siliconcompiler/toolscripts/ubuntu22/install-keplerformal.sh +72 -0
- siliconcompiler/toolscripts/ubuntu24/install-keplerformal.sh +72 -0
- siliconcompiler/utils/__init__.py +243 -51
- siliconcompiler/utils/curation.py +89 -56
- siliconcompiler/utils/issue.py +6 -1
- siliconcompiler/utils/multiprocessing.py +46 -2
- siliconcompiler/utils/paths.py +21 -0
- siliconcompiler/utils/settings.py +162 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/METADATA +5 -4
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/RECORD +96 -87
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.36.0.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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
|
-
|
|
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'{
|
|
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
|
-
|
|
1041
|
-
|
|
1042
|
-
proc_mem_bytes =
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
2115
|
-
cls.
|
|
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'):
|
|
2168
|
+
for plugin in utils.get_plugins('showtask'):
|
|
2142
2169
|
plugin()
|
|
2143
2170
|
|
|
2144
2171
|
if not classes:
|
|
2145
2172
|
return
|
|
2146
2173
|
|
|
2147
|
-
|
|
2148
|
-
|
|
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
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2180
|
-
|
|
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
|
|
|
@@ -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
|
|
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()
|
|
@@ -39,24 +39,23 @@ import sys
|
|
|
39
39
|
import fnmatch
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def gds_export(design_name, in_def,
|
|
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
|
-
|
|
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
|
|
55
|
-
def_cells.append(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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)
|