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
@@ -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
- # Remove existing directory
52
+ # Move existing directory
53
+ prev_dir = None
49
54
  if os.path.exists(directory):
50
- shutil.rmtree(directory)
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
- leaftype = project.get(*key, field='type')
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 project.get(*key, field='copy'):
113
+ if not param.get(field='copy'):
86
114
  continue
87
115
 
88
- for values, step, index in project.get(*key, field=None).getvalues(return_values=False):
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
- path_filter = FilterDirectories(project)
103
- for key, step, index in sorted(dirs.keys()):
104
- abs_paths = project.find_files(*key, step=step, index=index)
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
- new_paths = set()
135
+ new_paths = set()
107
136
 
108
- if not isinstance(abs_paths, (list, tuple, set)):
109
- abs_paths = [abs_paths]
137
+ if not isinstance(abs_paths, (list, tuple, set)):
138
+ abs_paths = [abs_paths]
110
139
 
111
- abs_paths = zip(abs_paths, dirs[(key, step, index)])
112
- abs_paths = sorted(abs_paths, key=lambda p: p[0])
140
+ abs_paths = zip(abs_paths, dirs[(key, step, index)])
141
+ abs_paths = sorted(abs_paths, key=lambda p: p[0])
113
142
 
114
- for abs_path, value in abs_paths:
115
- if not abs_path:
116
- raise FileNotFoundError(f"{value.get()} could not be copied")
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
- if abs_path.startswith(directory):
119
- # File already imported in directory
120
- continue
147
+ if abs_path.startswith(directory):
148
+ # File already imported in directory
149
+ continue
121
150
 
122
- imported = False
123
- for new_path in new_paths:
124
- if abs_path.startwith(new_path):
125
- imported = True
126
- break
127
- if imported:
128
- continue
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
- new_paths.add(abs_path)
159
+ new_paths.add(abs_path)
131
160
 
132
- import_path = os.path.join(directory, value.get_hashed_filename())
133
- if os.path.exists(import_path):
134
- continue
161
+ import_path = os.path.join(directory, value.get_hashed_filename())
162
+ if os.path.exists(import_path):
163
+ continue
135
164
 
136
- if whitelist is not None and abs_path not in whitelist:
137
- raise RuntimeError(f'{abs_path} is not on the approved collection list.')
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
- if verbose:
140
- project.logger.info(f" Collecting directory: {abs_path}")
141
- path_filter.abspath = abs_path
142
- shutil.copytree(abs_path, import_path, ignore=path_filter.filter)
143
- path_filter.abspath = None
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
- for key, step, index in sorted(files.keys()):
146
- abs_paths = project.find_files(*key, step=step, index=index)
174
+ for key, step, index in sorted(files.keys()):
175
+ abs_paths = find_files(*key, step=step, index=index)
147
176
 
148
- if not isinstance(abs_paths, (list, tuple, set)):
149
- abs_paths = [abs_paths]
177
+ if not isinstance(abs_paths, (list, tuple, set)):
178
+ abs_paths = [abs_paths]
150
179
 
151
- abs_paths = zip(abs_paths, files[(key, step, index)])
152
- abs_paths = sorted(abs_paths, key=lambda p: p[0])
180
+ abs_paths = zip(abs_paths, files[(key, step, index)])
181
+ abs_paths = sorted(abs_paths, key=lambda p: p[0])
153
182
 
154
- for abs_path, value in abs_paths:
155
- if not abs_path:
156
- raise FileNotFoundError(f"{value.get()} could not be copied")
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
- if abs_path.startswith(directory):
159
- # File already imported in directory
160
- continue
187
+ if abs_path.startswith(directory):
188
+ # File already imported in directory
189
+ continue
161
190
 
162
- import_path = os.path.join(directory, value.get_hashed_filename())
163
- if os.path.exists(import_path):
164
- continue
191
+ import_path = os.path.join(directory, value.get_hashed_filename())
192
+ if os.path.exists(import_path):
193
+ continue
165
194
 
166
- if verbose:
167
- project.logger.info(f" Collecting file: {abs_path}")
168
- shutil.copy2(abs_path, import_path)
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",
@@ -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 multiprocessing.managers import SyncManager
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
- with manager.__board_lock:
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
@@ -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.35.3
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.4
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.6.1; extra == "lint"
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.13 environment, just use pip.
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