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
|
@@ -5,9 +5,10 @@ import os.path
|
|
|
5
5
|
|
|
6
6
|
from typing import List, Optional, TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from siliconcompiler.schema import BaseSchema, Parameter
|
|
8
9
|
from siliconcompiler.schema.parametervalue import NodeListValue, NodeSetValue
|
|
9
10
|
from siliconcompiler.utils import FilterDirectories
|
|
10
|
-
from siliconcompiler.utils.paths import collectiondir
|
|
11
|
+
from siliconcompiler.utils.paths import collectiondir, cwdir
|
|
11
12
|
from siliconcompiler.scheduler import SchedulerNode
|
|
12
13
|
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
13
14
|
|
|
@@ -43,16 +44,42 @@ def collect(project: "Project",
|
|
|
43
44
|
|
|
44
45
|
if not directory:
|
|
45
46
|
directory = collectiondir(project)
|
|
47
|
+
if not directory:
|
|
48
|
+
raise ValueError("unable to determine collection directory")
|
|
49
|
+
|
|
46
50
|
directory = os.path.abspath(directory)
|
|
47
51
|
|
|
48
|
-
#
|
|
52
|
+
# Move existing directory
|
|
53
|
+
prev_dir = None
|
|
49
54
|
if os.path.exists(directory):
|
|
50
|
-
|
|
55
|
+
prev_dir = os.path.join(os.path.dirname(directory), "sc_previous_collection")
|
|
56
|
+
os.rename(directory, prev_dir)
|
|
51
57
|
os.makedirs(directory)
|
|
52
58
|
|
|
53
59
|
if verbose:
|
|
54
60
|
project.logger.info(f'Collecting files to: {directory}')
|
|
55
61
|
|
|
62
|
+
cwd = cwdir(project)
|
|
63
|
+
|
|
64
|
+
def find_files(*key, step: Optional[str] = None, index: Optional[str] = None):
|
|
65
|
+
"""
|
|
66
|
+
Find the files in the filesystem, otherwise look in previous collection
|
|
67
|
+
"""
|
|
68
|
+
e = None
|
|
69
|
+
try:
|
|
70
|
+
return BaseSchema._find_files(project, *key, step=step, index=index,
|
|
71
|
+
cwd=cwd,
|
|
72
|
+
collection_dir=directory)
|
|
73
|
+
except FileNotFoundError as err:
|
|
74
|
+
e = err
|
|
75
|
+
if prev_dir:
|
|
76
|
+
# Try previous location next
|
|
77
|
+
return BaseSchema._find_files(project, *key, step=step, index=index,
|
|
78
|
+
cwd=cwd,
|
|
79
|
+
collection_dir=prev_dir)
|
|
80
|
+
if e:
|
|
81
|
+
raise e from None
|
|
82
|
+
|
|
56
83
|
dirs = {}
|
|
57
84
|
files = {}
|
|
58
85
|
|
|
@@ -75,17 +102,18 @@ def collect(project: "Project",
|
|
|
75
102
|
# skip flow files files from builds
|
|
76
103
|
continue
|
|
77
104
|
|
|
78
|
-
|
|
105
|
+
param: Parameter = project.get(*key, field=None)
|
|
106
|
+
leaftype: str = param.get(field='type')
|
|
79
107
|
is_dir = "dir" in leaftype
|
|
80
108
|
is_file = "file" in leaftype
|
|
81
109
|
|
|
82
110
|
if not is_dir and not is_file:
|
|
83
111
|
continue
|
|
84
112
|
|
|
85
|
-
if not
|
|
113
|
+
if not param.get(field='copy'):
|
|
86
114
|
continue
|
|
87
115
|
|
|
88
|
-
for values, step, index in
|
|
116
|
+
for values, step, index in param.getvalues(return_values=False):
|
|
89
117
|
if not values.has_value:
|
|
90
118
|
continue
|
|
91
119
|
|
|
@@ -99,73 +127,78 @@ def collect(project: "Project",
|
|
|
99
127
|
else:
|
|
100
128
|
files[(key, step, index)] = values
|
|
101
129
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
130
|
+
try:
|
|
131
|
+
path_filter = FilterDirectories(project)
|
|
132
|
+
for key, step, index in sorted(dirs.keys()):
|
|
133
|
+
abs_paths = find_files(*key, step=step, index=index)
|
|
105
134
|
|
|
106
|
-
|
|
135
|
+
new_paths = set()
|
|
107
136
|
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
if not isinstance(abs_paths, (list, tuple, set)):
|
|
138
|
+
abs_paths = [abs_paths]
|
|
110
139
|
|
|
111
|
-
|
|
112
|
-
|
|
140
|
+
abs_paths = zip(abs_paths, dirs[(key, step, index)])
|
|
141
|
+
abs_paths = sorted(abs_paths, key=lambda p: p[0])
|
|
113
142
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
for abs_path, value in abs_paths:
|
|
144
|
+
if not abs_path:
|
|
145
|
+
raise FileNotFoundError(f"{value.get()} could not be copied")
|
|
117
146
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
if abs_path.startswith(directory):
|
|
148
|
+
# File already imported in directory
|
|
149
|
+
continue
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
151
|
+
imported = False
|
|
152
|
+
for new_path in new_paths:
|
|
153
|
+
if abs_path.startswith(new_path):
|
|
154
|
+
imported = True
|
|
155
|
+
break
|
|
156
|
+
if imported:
|
|
157
|
+
continue
|
|
129
158
|
|
|
130
|
-
|
|
159
|
+
new_paths.add(abs_path)
|
|
131
160
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
import_path = os.path.join(directory, value.get_hashed_filename())
|
|
162
|
+
if os.path.exists(import_path):
|
|
163
|
+
continue
|
|
135
164
|
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
if whitelist is not None and abs_path not in whitelist:
|
|
166
|
+
raise RuntimeError(f'{abs_path} is not on the approved collection list.')
|
|
138
167
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
if verbose:
|
|
169
|
+
project.logger.info(f" Collecting directory: {abs_path}")
|
|
170
|
+
path_filter.abspath = abs_path
|
|
171
|
+
shutil.copytree(abs_path, import_path, ignore=path_filter.filter)
|
|
172
|
+
path_filter.abspath = None
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
174
|
+
for key, step, index in sorted(files.keys()):
|
|
175
|
+
abs_paths = find_files(*key, step=step, index=index)
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
if not isinstance(abs_paths, (list, tuple, set)):
|
|
178
|
+
abs_paths = [abs_paths]
|
|
150
179
|
|
|
151
|
-
|
|
152
|
-
|
|
180
|
+
abs_paths = zip(abs_paths, files[(key, step, index)])
|
|
181
|
+
abs_paths = sorted(abs_paths, key=lambda p: p[0])
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
for abs_path, value in abs_paths:
|
|
184
|
+
if not abs_path:
|
|
185
|
+
raise FileNotFoundError(f"{value.get()} could not be copied")
|
|
157
186
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
if abs_path.startswith(directory):
|
|
188
|
+
# File already imported in directory
|
|
189
|
+
continue
|
|
161
190
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
191
|
+
import_path = os.path.join(directory, value.get_hashed_filename())
|
|
192
|
+
if os.path.exists(import_path):
|
|
193
|
+
continue
|
|
165
194
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
if verbose:
|
|
196
|
+
project.logger.info(f" Collecting file: {abs_path}")
|
|
197
|
+
shutil.copy2(abs_path, import_path)
|
|
198
|
+
finally:
|
|
199
|
+
if prev_dir:
|
|
200
|
+
# Delete existing directory
|
|
201
|
+
shutil.rmtree(prev_dir)
|
|
169
202
|
|
|
170
203
|
|
|
171
204
|
def archive(project: "Project",
|
siliconcompiler/utils/issue.py
CHANGED
|
@@ -237,6 +237,10 @@ def generate_testcase(project: "Project",
|
|
|
237
237
|
with open(issue_path, 'w') as fd:
|
|
238
238
|
json.dump(issue_information, fd, indent=4, sort_keys=True)
|
|
239
239
|
|
|
240
|
+
gitignore_path = os.path.join(issue_dir.name, '.gitignore')
|
|
241
|
+
with open(gitignore_path, 'w') as fd:
|
|
242
|
+
fd.write("/*\n")
|
|
243
|
+
|
|
240
244
|
readme_path = os.path.join(issue_dir.name, 'README.txt')
|
|
241
245
|
with open(readme_path, 'w') as f:
|
|
242
246
|
f.write(get_file_template('issue/README.txt').render(
|
|
@@ -268,7 +272,8 @@ def generate_testcase(project: "Project",
|
|
|
268
272
|
# Add individual files
|
|
269
273
|
add_files = [manifest_path,
|
|
270
274
|
issue_path,
|
|
271
|
-
readme_path
|
|
275
|
+
readme_path,
|
|
276
|
+
gitignore_path]
|
|
272
277
|
if not is_python_tool and run_path:
|
|
273
278
|
add_files.append(run_path)
|
|
274
279
|
for path in add_files:
|
|
@@ -8,7 +8,11 @@ import os.path
|
|
|
8
8
|
from typing import Union, Optional
|
|
9
9
|
|
|
10
10
|
from datetime import datetime
|
|
11
|
-
from
|
|
11
|
+
from logging.handlers import QueueHandler
|
|
12
|
+
from multiprocessing.managers import SyncManager, RemoteError
|
|
13
|
+
|
|
14
|
+
from siliconcompiler.utils.settings import SettingsManager
|
|
15
|
+
from siliconcompiler.utils import default_sc_path
|
|
12
16
|
|
|
13
17
|
from siliconcompiler.report.dashboard.cli.board import Board
|
|
14
18
|
|
|
@@ -139,6 +143,10 @@ class MPManager(metaclass=_ManagerSingleton):
|
|
|
139
143
|
self.__board_lock = self.__manager.Lock()
|
|
140
144
|
self.__board = None
|
|
141
145
|
|
|
146
|
+
# Settings
|
|
147
|
+
self.__settings = SettingsManager(default_sc_path("settings.json"), self.__logger)
|
|
148
|
+
self.__transient_settings = SettingsManager(None, self.__logger)
|
|
149
|
+
|
|
142
150
|
# Register cleanup function to run at exit
|
|
143
151
|
atexit.register(MPManager.stop)
|
|
144
152
|
|
|
@@ -205,7 +213,13 @@ class MPManager(metaclass=_ManagerSingleton):
|
|
|
205
213
|
|
|
206
214
|
# Stop the dashboard service if it's running
|
|
207
215
|
if manager.__board:
|
|
208
|
-
|
|
216
|
+
try:
|
|
217
|
+
with manager.__board_lock:
|
|
218
|
+
if manager.__board:
|
|
219
|
+
manager.__board.stop()
|
|
220
|
+
manager.__board = None
|
|
221
|
+
except RemoteError:
|
|
222
|
+
# Try without the lock
|
|
209
223
|
if manager.__board:
|
|
210
224
|
manager.__board.stop()
|
|
211
225
|
manager.__board = None
|
|
@@ -249,6 +263,26 @@ class MPManager(metaclass=_ManagerSingleton):
|
|
|
249
263
|
"""
|
|
250
264
|
return MPManager().__manager
|
|
251
265
|
|
|
266
|
+
@staticmethod
|
|
267
|
+
def get_settings() -> SettingsManager:
|
|
268
|
+
"""
|
|
269
|
+
Provides access to the shared SettingsManager instance.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
SettingsManager: The singleton settings instance.
|
|
273
|
+
"""
|
|
274
|
+
return MPManager().__settings
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def get_transient_settings() -> SettingsManager:
|
|
278
|
+
"""
|
|
279
|
+
Provides access to the shared transient SettingsManager instance.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
SettingsManager: The singleton transient settings instance.
|
|
283
|
+
"""
|
|
284
|
+
return MPManager().__transient_settings
|
|
285
|
+
|
|
252
286
|
@staticmethod
|
|
253
287
|
def get_dashboard() -> Board:
|
|
254
288
|
"""
|
|
@@ -292,3 +326,13 @@ class MPManager(metaclass=_ManagerSingleton):
|
|
|
292
326
|
Get the address of the manager
|
|
293
327
|
"""
|
|
294
328
|
return MPManager.__address
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class MPQueueHandler(QueueHandler):
|
|
332
|
+
def enqueue(self, record):
|
|
333
|
+
try:
|
|
334
|
+
super().enqueue(record)
|
|
335
|
+
except BrokenPipeError:
|
|
336
|
+
# The pipe is broken so fail silently as this is likely
|
|
337
|
+
# at exit
|
|
338
|
+
pass
|
siliconcompiler/utils/paths.py
CHANGED
|
@@ -13,6 +13,27 @@ def cwdir(project: "Project") -> str:
|
|
|
13
13
|
return project._Project__cwd
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def cwdirsafe(project: "Project") -> str:
|
|
17
|
+
"""
|
|
18
|
+
Returns the current working directory for the project, with safe fallback.
|
|
19
|
+
|
|
20
|
+
This function attempts to retrieve the project's current working directory.
|
|
21
|
+
If the provided object is not a valid Project instance, it falls back to
|
|
22
|
+
the system's current working directory.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
project (Project): The SiliconCompiler project object.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: The absolute path to the project's working directory, or the
|
|
29
|
+
system's current working directory if the project is invalid.
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
return cwdir(project)
|
|
33
|
+
except TypeError:
|
|
34
|
+
return os.getcwd()
|
|
35
|
+
|
|
36
|
+
|
|
16
37
|
def builddir(project: "Project") -> str:
|
|
17
38
|
"""
|
|
18
39
|
Returns the absolute path to the project's build directory.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
import os.path
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from fasteners import InterProcessLock
|
|
11
|
+
|
|
12
|
+
from siliconcompiler import sc_open
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SettingsManager:
|
|
16
|
+
"""
|
|
17
|
+
A class to manage user settings stored in a JSON file.
|
|
18
|
+
Supports categories, robust error handling for malformed files,
|
|
19
|
+
and simple get/set operations.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, filepath: str, logger: logging.Logger, timeout: float = 1.0):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the settings manager.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
filepath (str): The path to the JSON file where settings are stored.
|
|
28
|
+
If None, settings are kept in memory only.
|
|
29
|
+
logger (logging.Logger): Logger for logging errors and information.
|
|
30
|
+
timeout (float): Timeout in seconds for acquiring the file lock.
|
|
31
|
+
"""
|
|
32
|
+
self.__filepath = filepath
|
|
33
|
+
if self.__filepath is not None:
|
|
34
|
+
self.__lock = InterProcessLock(self.__filepath + ".lock")
|
|
35
|
+
self.__settings_lock = threading.Lock()
|
|
36
|
+
self.__timeout = timeout
|
|
37
|
+
self.__logger = logger.getChild("settings")
|
|
38
|
+
self.__settings = {}
|
|
39
|
+
self._load()
|
|
40
|
+
|
|
41
|
+
def _load(self):
|
|
42
|
+
"""
|
|
43
|
+
Internal method to load settings from disk.
|
|
44
|
+
It handles missing files and malformed JSON gracefully.
|
|
45
|
+
"""
|
|
46
|
+
if self.__filepath is None or not os.path.exists(self.__filepath):
|
|
47
|
+
self.__settings = {}
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
with self.__settings_lock:
|
|
51
|
+
try:
|
|
52
|
+
if self.__lock.acquire(timeout=self.__timeout):
|
|
53
|
+
try:
|
|
54
|
+
with sc_open(self.__filepath, encoding='utf-8') as f:
|
|
55
|
+
data = json.load(f)
|
|
56
|
+
finally:
|
|
57
|
+
self.__lock.release()
|
|
58
|
+
else:
|
|
59
|
+
self.__logger.error(f"Timeout acquiring lock for {self.__filepath}. "
|
|
60
|
+
"Starting with empty settings.")
|
|
61
|
+
data = {}
|
|
62
|
+
|
|
63
|
+
# Ensure the loaded data is actually a dictionary
|
|
64
|
+
if isinstance(data, dict):
|
|
65
|
+
self.__settings = data
|
|
66
|
+
else:
|
|
67
|
+
# If valid JSON but not a dict (e.g. a list), reset to empty
|
|
68
|
+
self.__logger.warning(f"File {self.__filepath} did not contain a JSON object. "
|
|
69
|
+
"Resetting.")
|
|
70
|
+
self.__settings = {}
|
|
71
|
+
|
|
72
|
+
except json.JSONDecodeError:
|
|
73
|
+
self.__logger.error(f"File {self.__filepath} is malformed. "
|
|
74
|
+
"Starting with empty settings.")
|
|
75
|
+
self.__settings = {}
|
|
76
|
+
except Exception as e:
|
|
77
|
+
# Catch-all for permission errors, etc., to ensure __init__ doesn't crash
|
|
78
|
+
self.__logger.error(f"Unexpected error loading settings: {e}")
|
|
79
|
+
self.__settings = {}
|
|
80
|
+
|
|
81
|
+
def save(self):
|
|
82
|
+
"""
|
|
83
|
+
Save the current settings to the disk in JSON format.
|
|
84
|
+
"""
|
|
85
|
+
if self.__filepath is None:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# Ensure directory exists
|
|
90
|
+
directory = os.path.dirname(self.__filepath)
|
|
91
|
+
if directory and not os.path.exists(directory):
|
|
92
|
+
os.makedirs(directory)
|
|
93
|
+
|
|
94
|
+
with self.__settings_lock:
|
|
95
|
+
with self.__lock:
|
|
96
|
+
with open(self.__filepath, 'w', encoding='utf-8') as f:
|
|
97
|
+
json.dump(self.__settings, f, indent=4)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
self.__logger.error(f"Failed to save settings to {self.__filepath}: {e}")
|
|
100
|
+
raise e
|
|
101
|
+
|
|
102
|
+
def set(self, category: str, key: str, value, keep: bool = False):
|
|
103
|
+
"""
|
|
104
|
+
Set a specific setting within a category.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
category (str): The group name (e.g., 'showtools', 'options').
|
|
108
|
+
key (str): The specific setting name.
|
|
109
|
+
value: The value to store (must be JSON serializable).
|
|
110
|
+
keep (bool): If True, do not overwrite existing value.
|
|
111
|
+
"""
|
|
112
|
+
with self.__settings_lock:
|
|
113
|
+
if category not in self.__settings:
|
|
114
|
+
self.__settings[category] = {}
|
|
115
|
+
|
|
116
|
+
if keep and key in self.__settings[category]:
|
|
117
|
+
return
|
|
118
|
+
self.__settings[category][key] = value
|
|
119
|
+
|
|
120
|
+
def get(self, category: str, key: str, default=None):
|
|
121
|
+
"""
|
|
122
|
+
Retrieve a setting.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
category (str): The group name.
|
|
126
|
+
key (str): The specific setting name.
|
|
127
|
+
default: The value to return if the category or key is missing.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The stored value or the default.
|
|
131
|
+
"""
|
|
132
|
+
with self.__settings_lock:
|
|
133
|
+
if category not in self.__settings:
|
|
134
|
+
return default
|
|
135
|
+
|
|
136
|
+
return self.__settings[category].get(key, default)
|
|
137
|
+
|
|
138
|
+
def get_category(self, category: str):
|
|
139
|
+
"""
|
|
140
|
+
Retrieve all settings for a specific category.
|
|
141
|
+
Returns an empty dict if category does not exist.
|
|
142
|
+
"""
|
|
143
|
+
with self.__settings_lock:
|
|
144
|
+
if category not in self.__settings:
|
|
145
|
+
self.__settings[category] = {}
|
|
146
|
+
|
|
147
|
+
return self.__settings.get(category).copy()
|
|
148
|
+
|
|
149
|
+
def delete(self, category: str, key: Optional[str] = None):
|
|
150
|
+
"""
|
|
151
|
+
Remove a setting.
|
|
152
|
+
"""
|
|
153
|
+
with self.__settings_lock:
|
|
154
|
+
if category in self.__settings:
|
|
155
|
+
if key:
|
|
156
|
+
if key in self.__settings[category]:
|
|
157
|
+
del self.__settings[category][key]
|
|
158
|
+
# Clean up empty categories
|
|
159
|
+
if not self.__settings[category]:
|
|
160
|
+
del self.__settings[category]
|
|
161
|
+
else:
|
|
162
|
+
del self.__settings[category]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: siliconcompiler
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.36.0
|
|
4
4
|
Summary: A compiler framework that automates translation from source code to silicon.
|
|
5
5
|
Author: Zero ASIC
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Operating System :: POSIX :: Linux
|
|
20
21
|
Classifier: Operating System :: MacOS
|
|
21
22
|
Classifier: Operating System :: Microsoft :: Windows
|
|
@@ -37,7 +38,7 @@ Requires-Dist: PyYAML<7.0.0,>=6.0.0
|
|
|
37
38
|
Requires-Dist: GitPython<3.2,>=3.1.44
|
|
38
39
|
Requires-Dist: PyGithub<2.9.0,>=2.8.0
|
|
39
40
|
Requires-Dist: urllib3>=1.26.0
|
|
40
|
-
Requires-Dist: lambdapdk>=0.2.
|
|
41
|
+
Requires-Dist: lambdapdk>=0.2.6
|
|
41
42
|
Requires-Dist: fasteners>=0.20
|
|
42
43
|
Requires-Dist: pandas>=1.1.5
|
|
43
44
|
Requires-Dist: psutil>=5.8.0
|
|
@@ -60,7 +61,7 @@ Requires-Dist: responses==0.25.8; extra == "test"
|
|
|
60
61
|
Requires-Dist: PyVirtualDisplay==3.0; extra == "test"
|
|
61
62
|
Provides-Extra: lint
|
|
62
63
|
Requires-Dist: flake8==7.3.0; extra == "lint"
|
|
63
|
-
Requires-Dist: tclint==0.
|
|
64
|
+
Requires-Dist: tclint==0.7.0; extra == "lint"
|
|
64
65
|
Requires-Dist: codespell==2.4.1; extra == "lint"
|
|
65
66
|
Provides-Extra: docs
|
|
66
67
|
Requires-Dist: Sphinx<8.3,>=7.4; extra == "docs"
|
|
@@ -102,7 +103,7 @@ SiliconCompiler is a modular hardware build system ("make for silicon"). The pro
|
|
|
102
103
|
# Getting Started
|
|
103
104
|
|
|
104
105
|
SiliconCompiler is available as wheel packages on PyPI for macOS, Windows and
|
|
105
|
-
Linux platforms. For working Python 3.9-3.
|
|
106
|
+
Linux platforms. For working Python 3.9-3.14 environment, just use pip.
|
|
106
107
|
|
|
107
108
|
```sh
|
|
108
109
|
pip install --upgrade siliconcompiler
|