siliconcompiler 0.34.1__py3-none-any.whl → 0.34.3__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 (129) hide show
  1. siliconcompiler/__init__.py +23 -4
  2. siliconcompiler/__main__.py +1 -7
  3. siliconcompiler/_metadata.py +1 -1
  4. siliconcompiler/apps/_common.py +104 -23
  5. siliconcompiler/apps/sc.py +4 -8
  6. siliconcompiler/apps/sc_dashboard.py +6 -4
  7. siliconcompiler/apps/sc_install.py +10 -6
  8. siliconcompiler/apps/sc_issue.py +7 -5
  9. siliconcompiler/apps/sc_remote.py +1 -1
  10. siliconcompiler/apps/sc_server.py +9 -14
  11. siliconcompiler/apps/sc_show.py +7 -6
  12. siliconcompiler/apps/smake.py +130 -94
  13. siliconcompiler/apps/utils/replay.py +4 -7
  14. siliconcompiler/apps/utils/summarize.py +3 -5
  15. siliconcompiler/asic.py +420 -0
  16. siliconcompiler/checklist.py +25 -2
  17. siliconcompiler/cmdlineschema.py +534 -0
  18. siliconcompiler/constraints/__init__.py +17 -0
  19. siliconcompiler/constraints/asic_component.py +378 -0
  20. siliconcompiler/constraints/asic_floorplan.py +449 -0
  21. siliconcompiler/constraints/asic_pins.py +489 -0
  22. siliconcompiler/constraints/asic_timing.py +517 -0
  23. siliconcompiler/core.py +10 -35
  24. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
  25. siliconcompiler/dependencyschema.py +96 -202
  26. siliconcompiler/design.py +327 -241
  27. siliconcompiler/filesetschema.py +250 -0
  28. siliconcompiler/flowgraph.py +298 -106
  29. siliconcompiler/fpga.py +124 -1
  30. siliconcompiler/library.py +331 -0
  31. siliconcompiler/metric.py +327 -92
  32. siliconcompiler/metrics/__init__.py +7 -0
  33. siliconcompiler/metrics/asic.py +245 -0
  34. siliconcompiler/metrics/fpga.py +220 -0
  35. siliconcompiler/package/__init__.py +391 -67
  36. siliconcompiler/package/git.py +92 -16
  37. siliconcompiler/package/github.py +114 -22
  38. siliconcompiler/package/https.py +79 -16
  39. siliconcompiler/packageschema.py +341 -16
  40. siliconcompiler/pathschema.py +255 -0
  41. siliconcompiler/pdk.py +566 -1
  42. siliconcompiler/project.py +1460 -0
  43. siliconcompiler/record.py +38 -1
  44. siliconcompiler/remote/__init__.py +5 -2
  45. siliconcompiler/remote/client.py +11 -6
  46. siliconcompiler/remote/schema.py +5 -23
  47. siliconcompiler/remote/server.py +41 -54
  48. siliconcompiler/report/__init__.py +3 -3
  49. siliconcompiler/report/dashboard/__init__.py +48 -14
  50. siliconcompiler/report/dashboard/cli/__init__.py +99 -21
  51. siliconcompiler/report/dashboard/cli/board.py +364 -179
  52. siliconcompiler/report/dashboard/web/__init__.py +90 -12
  53. siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
  54. siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
  55. siliconcompiler/report/dashboard/web/components/graph.py +139 -100
  56. siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
  57. siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
  58. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
  59. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
  60. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
  61. siliconcompiler/report/dashboard/web/state.py +141 -14
  62. siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
  63. siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
  64. siliconcompiler/report/dashboard/web/viewer.py +25 -1
  65. siliconcompiler/report/report.py +5 -2
  66. siliconcompiler/report/summary_image.py +29 -11
  67. siliconcompiler/scheduler/__init__.py +9 -1
  68. siliconcompiler/scheduler/docker.py +81 -4
  69. siliconcompiler/scheduler/run_node.py +37 -20
  70. siliconcompiler/scheduler/scheduler.py +211 -36
  71. siliconcompiler/scheduler/schedulernode.py +394 -60
  72. siliconcompiler/scheduler/send_messages.py +77 -29
  73. siliconcompiler/scheduler/slurm.py +76 -12
  74. siliconcompiler/scheduler/taskscheduler.py +142 -21
  75. siliconcompiler/schema/__init__.py +0 -4
  76. siliconcompiler/schema/baseschema.py +338 -59
  77. siliconcompiler/schema/editableschema.py +14 -6
  78. siliconcompiler/schema/journal.py +28 -17
  79. siliconcompiler/schema/namedschema.py +22 -14
  80. siliconcompiler/schema/parameter.py +89 -28
  81. siliconcompiler/schema/parametertype.py +2 -0
  82. siliconcompiler/schema/parametervalue.py +258 -15
  83. siliconcompiler/schema/safeschema.py +25 -2
  84. siliconcompiler/schema/schema_cfg.py +23 -19
  85. siliconcompiler/schema/utils.py +2 -2
  86. siliconcompiler/schema_obj.py +24 -5
  87. siliconcompiler/tool.py +1131 -265
  88. siliconcompiler/tools/bambu/__init__.py +41 -0
  89. siliconcompiler/tools/builtin/concatenate.py +2 -2
  90. siliconcompiler/tools/builtin/minimum.py +2 -1
  91. siliconcompiler/tools/builtin/mux.py +2 -1
  92. siliconcompiler/tools/builtin/nop.py +2 -1
  93. siliconcompiler/tools/builtin/verify.py +2 -1
  94. siliconcompiler/tools/klayout/__init__.py +95 -0
  95. siliconcompiler/tools/openroad/__init__.py +289 -0
  96. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
  97. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
  98. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
  99. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
  100. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
  101. siliconcompiler/tools/slang/__init__.py +1 -1
  102. siliconcompiler/tools/slang/elaborate.py +2 -1
  103. siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
  104. siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
  105. siliconcompiler/tools/vivado/syn_fpga.py +6 -0
  106. siliconcompiler/tools/vivado/vivado.py +35 -2
  107. siliconcompiler/tools/vpr/__init__.py +150 -0
  108. siliconcompiler/tools/yosys/__init__.py +369 -1
  109. siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
  110. siliconcompiler/toolscripts/_tools.json +5 -10
  111. siliconcompiler/utils/__init__.py +66 -0
  112. siliconcompiler/utils/flowgraph.py +2 -2
  113. siliconcompiler/utils/issue.py +2 -1
  114. siliconcompiler/utils/logging.py +14 -0
  115. siliconcompiler/utils/multiprocessing.py +256 -0
  116. siliconcompiler/utils/showtools.py +10 -0
  117. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +6 -6
  118. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +122 -115
  119. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
  120. siliconcompiler/schema/cmdlineschema.py +0 -250
  121. siliconcompiler/schema/packageschema.py +0 -101
  122. siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
  123. siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
  124. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
  125. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
  126. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
  127. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
  128. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
  129. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,18 @@
1
+ """
2
+ Manages the session state for the SiliconCompiler web dashboard.
3
+
4
+ This module defines the keys used in Streamlit's session state and provides
5
+ a set of utility functions to initialize, access, and modify that state.
6
+ It is the central point for managing UI state, loaded data (chip manifests),
7
+ and application configuration throughout the user's session.
8
+
9
+ The main functions are:
10
+ - init(): Parses command-line arguments and sets up the initial session state.
11
+ - setup(): Performs per-rerun setup, like capturing UI dimensions.
12
+ - update_manifest(): Checks for changes in the manifest file and reloads data.
13
+ - A set of getters and setters (get_key, set_key, etc.) for safe access
14
+ to the session state.
15
+ """
1
16
  import argparse
2
17
  import json
3
18
  import os
@@ -7,43 +22,70 @@ import fasteners
7
22
 
8
23
  from siliconcompiler import Chip
9
24
 
25
+ # --- State Keys ---
26
+ # These constants define the keys used to store and access data in
27
+ # streamlit.session_state, ensuring consistency across the application.
10
28
 
29
+ # UI Component State
11
30
  DISPLAY_FLOWGRAPH = "show_flowgraph"
12
31
  SELECTED_JOB = "selected_job"
13
32
  SELECTED_NODE = "selected_node"
14
- # This is needed until the graph supports setting a selected node
33
+ # The following keys differentiate the source of a node selection, as multiple
34
+ # UI components (the graph, a dropdown) can select a node.
15
35
  SELECTED_FLOWGRAPH_NODE = "selected_flowgraph_node"
16
36
  SELECTED_SELECTOR_NODE = "selected_selector_node"
17
37
  NODE_SOURCE = "node_source"
18
38
  SELECTED_FILE = "selected_file"
19
39
  SELECTED_FILE_PAGE = "selected_file_page"
20
- LOADED_CHIPS = "loaded_chips"
21
40
  UI_WIDTH = "ui_width"
41
+ SELECT_TAB = "select_tab"
42
+ TAB_INDEX = "tab-index"
43
+ TAB_STATE = "tab-state"
44
+
45
+ # Data State
46
+ LOADED_CHIPS = "loaded_chips"
22
47
  MANIFEST_FILE = "manifest_file"
23
48
  MANIFEST_LOCK = "manifest_lock"
24
49
  MANIFEST_TIME = "manifest_time"
25
50
  IS_RUNNING = "is_flow_running"
26
51
  GRAPH_JOBS = "graph_jobs"
52
+
53
+ # Application Configuration & Control
27
54
  APP_LAYOUT = "app_layout"
28
55
  APP_RERUN = "app_rerun"
29
56
  APP_RUNNING_REFRESH = "app_running_refresh"
30
57
  APP_STOPPED_REFRESH = "app_stopped_refresh"
31
58
  MAX_DICT_ITEMS_TO_SHOW = "max_dict_items"
32
59
  MAX_FILE_LINES_TO_SHOW = "max_file_lines"
33
- SELECT_TAB = "select_tab"
34
- TAB_INDEX = "tab-index"
35
- TAB_STATE = "tab-state"
36
60
 
61
+ # --- Debugging ---
37
62
  _DEBUG = False
38
63
  DEVELOPER = False
39
64
 
40
65
 
41
66
  def _add_default(key, value):
67
+ """
68
+ Initializes a key in the session state if it doesn't already exist.
69
+
70
+ Args:
71
+ key (str): The key to add to the session state.
72
+ value: The default value to assign if the key is not present.
73
+ """
42
74
  if key not in streamlit.session_state:
43
75
  streamlit.session_state[key] = value
44
76
 
45
77
 
46
78
  def update_manifest():
79
+ """
80
+ Checks for updates to the main manifest file and reloads it if necessary.
81
+
82
+ Compares the current file modification time with the stored time. If they
83
+ differ, it re-reads the manifest, re-populates the chip objects (including
84
+ history), and updates the timestamp in the session state.
85
+
86
+ Returns:
87
+ bool: True if the manifest was updated, False otherwise.
88
+ """
47
89
  file_time = os.stat(get_key(MANIFEST_FILE)).st_mtime
48
90
 
49
91
  if get_key(MANIFEST_TIME) != file_time:
@@ -56,6 +98,7 @@ def update_manifest():
56
98
 
57
99
  add_chip("default", chip)
58
100
 
101
+ # Load historical runs from the manifest
59
102
  for history in chip.getkeys('history'):
60
103
  history_chip = Chip(design='')
61
104
  history_chip.schema = chip.schema.history(history).copy()
@@ -67,6 +110,14 @@ def update_manifest():
67
110
 
68
111
 
69
112
  def init():
113
+ """
114
+ Initializes the application's session state on first run.
115
+
116
+ This function sets default values for all state keys, parses command-line
117
+ arguments to find the dashboard configuration file, and loads the initial
118
+ set of chip manifests.
119
+ """
120
+ # Set default values for all session state keys.
70
121
  _add_default(DISPLAY_FLOWGRAPH, True)
71
122
  _add_default(SELECTED_JOB, None)
72
123
  _add_default(SELECTED_NODE, None)
@@ -84,23 +135,23 @@ def init():
84
135
  _add_default(UI_WIDTH, None)
85
136
  _add_default(APP_LAYOUT, "vertical_flowgraph_sac_tabs")
86
137
  _add_default(APP_RERUN, None)
87
- _add_default(APP_RUNNING_REFRESH, 2 * 1000)
88
- _add_default(APP_STOPPED_REFRESH, 30 * 1000)
138
+ _add_default(APP_RUNNING_REFRESH, 2 * 1000) # 2 seconds
139
+ _add_default(APP_STOPPED_REFRESH, 30 * 1000) # 30 seconds
89
140
  _add_default(MAX_DICT_ITEMS_TO_SHOW, 100)
90
141
  _add_default(MAX_FILE_LINES_TO_SHOW, 100)
91
142
  _add_default(SELECT_TAB, None)
92
143
  _add_default(TAB_INDEX, 0)
93
144
 
145
+ # Parse command-line arguments to get the config file path.
94
146
  parser = argparse.ArgumentParser('dashboard')
95
147
  parser.add_argument('cfg', nargs='?')
96
148
  args = parser.parse_args()
97
149
 
98
150
  if not args.cfg:
99
- raise ValueError('configuration not provided')
151
+ raise ValueError('Dashboard configuration not provided via command line.')
100
152
 
153
+ # On the very first run, load the configuration and initial data.
101
154
  if not get_key(LOADED_CHIPS):
102
- # First time through
103
-
104
155
  with open(args.cfg, 'r') as f:
105
156
  config = json.load(f)
106
157
 
@@ -109,6 +160,8 @@ def init():
109
160
 
110
161
  update_manifest()
111
162
  chip = get_chip("default")
163
+
164
+ # Load any additional graph-related chips specified in the config.
112
165
  for graph_info in config['graph_chips']:
113
166
  file_path = graph_info['path']
114
167
  graph_chip = Chip(design='')
@@ -121,41 +174,70 @@ def init():
121
174
 
122
175
  add_chip(os.path.basename(file_path), graph_chip)
123
176
 
177
+ # Pre-select a node if specified in the chip's arguments.
124
178
  chip_step = chip.get('arg', 'step')
125
179
  chip_index = chip.get('arg', 'index')
126
-
127
180
  if chip_step and chip_index:
128
181
  set_key(SELECTED_NODE, f'{chip_step}/{chip_index}')
129
182
 
183
+ # Clean up args for subsequent runs.
130
184
  chip = get_chip("default")
131
185
  chip.unset('arg', 'step')
132
186
  chip.unset('arg', 'index')
133
187
 
188
+ # Ensure a job is selected.
134
189
  if not get_key(SELECTED_JOB):
135
190
  set_key(SELECTED_JOB, "default")
136
191
 
137
192
 
138
193
  def setup():
194
+ """
195
+ Performs setup tasks required on every page rerun.
196
+ """
197
+ # Use a javascript call to get the browser window's current width.
139
198
  with streamlit.empty():
140
- # get width
141
199
  set_key(UI_WIDTH, streamlit_javascript.st_javascript("window.innerWidth"))
142
- # replace with a empty container to avoid adding a gap at the top
200
+ # Replace with an empty container to avoid adding a visual gap at the top.
143
201
  streamlit.empty()
144
202
 
203
+ # Reset the node source on each rerun.
145
204
  set_key(NODE_SOURCE, None)
146
205
 
147
206
 
148
207
  def get_chip(job=None):
208
+ """
209
+ Retrieves a loaded Chip object from the session state.
210
+
211
+ Args:
212
+ job (str, optional): The name of the job/chip to retrieve.
213
+ Defaults to the currently selected job.
214
+
215
+ Returns:
216
+ Chip: The requested Chip object.
217
+ """
149
218
  if not job:
150
219
  job = get_key(SELECTED_JOB)
151
220
  return get_key(LOADED_CHIPS)[job]
152
221
 
153
222
 
154
223
  def add_chip(name, chip):
224
+ """
225
+ Adds a Chip object to the session state.
226
+
227
+ Args:
228
+ name (str): The name to associate with the chip (e.g., 'default' or a history ID).
229
+ chip (Chip): The Chip object to store.
230
+ """
155
231
  streamlit.session_state[LOADED_CHIPS][name] = chip
156
232
 
157
233
 
158
234
  def get_chips():
235
+ """
236
+ Gets a list of all loaded chip names.
237
+
238
+ Returns:
239
+ list: A list of strings, with 'default' guaranteed to be the first element.
240
+ """
159
241
  chips = list(get_key(LOADED_CHIPS).keys())
160
242
  chips.remove('default')
161
243
  chips.insert(0, 'default')
@@ -163,6 +245,12 @@ def get_chips():
163
245
 
164
246
 
165
247
  def get_selected_node():
248
+ """
249
+ Gets the currently selected node, accounting for the selection source.
250
+
251
+ Returns:
252
+ str or None: The identifier of the selected node (e.g., 'import/0').
253
+ """
166
254
  if get_key(NODE_SOURCE) == "flowgraph":
167
255
  return get_key(SELECTED_FLOWGRAPH_NODE)
168
256
 
@@ -173,10 +261,32 @@ def get_selected_node():
173
261
 
174
262
 
175
263
  def get_key(key):
264
+ """
265
+ Generic getter for a value from the session state.
266
+
267
+ Args:
268
+ key (str): The key of the value to retrieve.
269
+
270
+ Returns:
271
+ The value associated with the key.
272
+ """
176
273
  return streamlit.session_state[key]
177
274
 
178
275
 
179
276
  def set_key(key, value):
277
+ """
278
+ Generic setter for a value in the session state.
279
+
280
+ This function checks if the new value is different from the old one
281
+ before setting it, which helps in preventing unnecessary reruns.
282
+
283
+ Args:
284
+ key (str): The key of the value to set.
285
+ value: The new value to assign.
286
+
287
+ Returns:
288
+ bool: True if the value was changed, False otherwise.
289
+ """
180
290
  changed = value != streamlit.session_state[key]
181
291
  if changed:
182
292
  debug_print("set_key()", key, "changed", streamlit.session_state[key], "->", value)
@@ -186,12 +296,28 @@ def set_key(key, value):
186
296
 
187
297
 
188
298
  def del_key(key):
299
+ """
300
+ Deletes a key from the session state if it exists.
301
+
302
+ Args:
303
+ key (str): The key to delete.
304
+ """
189
305
  debug_print("del_key()", key)
190
306
  if key in streamlit.session_state:
191
307
  del streamlit.session_state[key]
192
308
 
193
309
 
194
310
  def compute_component_size(minimum, requested_px):
311
+ """
312
+ Utility to calculate component sizes based on UI width.
313
+
314
+ Args:
315
+ minimum (float): The minimum size as a fraction of the width.
316
+ requested_px (int): The desired size in pixels.
317
+
318
+ Returns:
319
+ float: The calculated size as a fraction of the width.
320
+ """
195
321
  ui_width = get_key(UI_WIDTH)
196
322
 
197
323
  if ui_width > 0:
@@ -201,13 +327,14 @@ def compute_component_size(minimum, requested_px):
201
327
 
202
328
 
203
329
  def debug_print(*args):
330
+ """Prints messages to the console only if the _DEBUG flag is True."""
204
331
  if not _DEBUG:
205
332
  return
206
-
207
333
  print(*args)
208
334
 
209
335
 
210
336
  def debug_print_state():
337
+ """Prints the entire Streamlit session state to the console for debugging."""
211
338
  if not _DEBUG:
212
339
  return
213
340
 
@@ -1,9 +1,31 @@
1
+ """
2
+ Utility functions for the SiliconCompiler web dashboard.
3
+
4
+ This module provides helper functions for processing chip data, formatting it
5
+ for display, and checking the status of a run. These functions are used by
6
+ the main dashboard application to interact with the chip object and prepare
7
+ data for UI components.
8
+ """
1
9
  from siliconcompiler import NodeStatus
2
-
3
10
  from pathlib import Path
4
11
 
5
12
 
6
13
  def get_chip_cwd(chip, manifest):
14
+ """
15
+ Determines the original chip working directory from a manifest path.
16
+
17
+ This function is useful for resolving relative paths when the dashboard
18
+ is run from a different location than the original compilation. It traverses
19
+ up from the manifest's location to find the parent of the build directory.
20
+
21
+ Args:
22
+ chip (Chip): The chip object.
23
+ manifest (str): The absolute path to the chip's manifest file.
24
+
25
+ Returns:
26
+ str or None: The absolute path to the chip's original working
27
+ directory, or None if it cannot be determined.
28
+ """
7
29
  build_dir = Path(chip.get('option', 'builddir'))
8
30
 
9
31
  manifest_path = Path(manifest)
@@ -15,48 +37,74 @@ def get_chip_cwd(chip, manifest):
15
37
 
16
38
 
17
39
  def make_node_to_step_index_map(chip, metric_dataframe):
18
- '''
19
- Returns a map from the name of a node to the associated step, index pair.
40
+ """
41
+ Creates a mapping from a node's string representation to its (step, index) tuple.
42
+
43
+ It also renames the columns of the metric dataframe to the 'step/index' format.
20
44
 
21
45
  Args:
22
- metric_dataframe (pandas.DataFrame) : A dataframe full of all metrics and all
23
- nodes of the selected chip
24
- '''
46
+ chip (Chip): The chip object.
47
+ metric_dataframe (pandas.DataFrame): The dataframe of metrics, with
48
+ multi-level columns of (step, index).
49
+
50
+ Returns:
51
+ tuple: A tuple containing:
52
+ - dict: A dictionary mapping 'step/index' strings to (step, index) tuples.
53
+ - pandas.DataFrame: The modified metric dataframe.
54
+ """
25
55
  node_to_step_index_map = {}
26
56
  if chip.get('option', 'flow'):
27
57
  for step, index in chip.get("flowgraph", chip.get('option', 'flow'),
28
58
  field="schema").get_nodes():
29
59
  node_to_step_index_map[f'{step}/{index}'] = (step, index)
30
60
 
31
- # concatenate step and index
32
- metric_dataframe.columns = metric_dataframe.columns.map(lambda x: f'{x[0]}{x[1]}')
61
+ # Concatenate step and index in the DataFrame columns
62
+ metric_dataframe.columns = metric_dataframe.columns.map(lambda x: f'{x[0]}/{x[1]}')
33
63
  return node_to_step_index_map, metric_dataframe
34
64
 
35
65
 
36
66
  def make_metric_to_metric_unit_map(metric_dataframe):
37
- '''
38
- Returns a map from the name of a metric to the associated metric and unit in
39
- the form f'{x[0]} ({x[1]})'
67
+ """
68
+ Creates a mapping from a formatted metric string to the raw metric name.
69
+
70
+ It also renames the index of the metric dataframe to include units,
71
+ e.g., 'cells (count)'.
40
72
 
41
73
  Args:
42
- metric_dataframe (pandas.DataFrame) : A dataframe full of all metrics and all
43
- nodes of the selected chip.
44
- '''
74
+ metric_dataframe (pandas.DataFrame): The dataframe of metrics, with
75
+ a multi-level index of (metric, unit).
76
+
77
+ Returns:
78
+ tuple: A tuple containing:
79
+ - dict: A dictionary mapping formatted metric strings to raw metric names.
80
+ - pandas.DataFrame: The modified metric dataframe.
81
+ """
45
82
  metric_to_metric_unit_map = {}
46
83
  for metric, unit in metric_dataframe.index.tolist():
47
- if unit != '':
84
+ if unit:
48
85
  metric_to_metric_unit_map[f'{metric} ({unit})'] = metric
49
86
  else:
50
87
  metric_to_metric_unit_map[metric] = metric
51
- # concatenate metric and unit
88
+
89
+ # Concatenate metric and unit in the DataFrame index
52
90
  metric_dataframe.index = metric_dataframe.index.map(lambda x: f'{x[0]} ({x[1]})'
53
91
  if x[1] else x[0])
54
92
  return metric_to_metric_unit_map, metric_dataframe
55
93
 
56
94
 
57
95
  def is_running(chip):
96
+ """
97
+ Checks if any node in the chip's flowgraph is still running.
98
+
99
+ Args:
100
+ chip (Chip): The chip object to check.
101
+
102
+ Returns:
103
+ bool: True if any node is not in a 'done' state, False otherwise.
104
+ """
58
105
  if not chip.get('option', 'flow'):
59
106
  return False
107
+
60
108
  for step, index in chip.get("flowgraph", chip.get('option', 'flow'),
61
109
  field="schema").get_nodes():
62
110
  state = chip.get('record', 'status', step=step, index=index)
@@ -66,6 +114,21 @@ def is_running(chip):
66
114
 
67
115
 
68
116
  def generate_metric_dataframe(chip):
117
+ """
118
+ Generates a fully processed metric dataframe and associated mappings.
119
+
120
+ This function orchestrates the creation of the metric dataframe and the
121
+ helper dictionaries needed to easily look up nodes and metrics in the UI.
122
+
123
+ Args:
124
+ chip (Chip): The chip object from which to generate the report.
125
+
126
+ Returns:
127
+ tuple: A tuple containing:
128
+ - pandas.DataFrame: The processed metric dataframe.
129
+ - dict: The node-to-(step, index) mapping.
130
+ - dict: The formatted-metric-to-raw-metric mapping.
131
+ """
69
132
  from siliconcompiler.report import report
70
133
 
71
134
  metric_dataframe = report.make_metric_dataframe(chip)
@@ -1,16 +1,34 @@
1
+ """
2
+ Utility functions for handling and displaying files in the web dashboard.
3
+
4
+ This module provides helpers for reading files (including compressed ones),
5
+ determining file types for syntax highlighting, selecting appropriate icons,
6
+ and structuring file lists into a hierarchical tree for UI components.
7
+ """
1
8
  import gzip
2
9
  import os
3
10
  from siliconcompiler import utils, sc_open
4
11
 
5
12
 
6
13
  def is_file_is_binary(path, compressed):
7
- # Read first chunk and check for non characters
14
+ """
15
+ Checks if a file is binary by attempting to read it as text.
16
+
17
+ Args:
18
+ path (str): The path to the file.
19
+ compressed (bool): True if the file is gzip compressed.
20
+
21
+ Returns:
22
+ bool: True if a UnicodeDecodeError occurs (indicating binary content),
23
+ False otherwise.
24
+ """
25
+ # Read the first chunk and check for non-text characters.
8
26
  try:
9
27
  if compressed:
10
- with gzip.open(path, 'rt') as f:
28
+ with gzip.open(path, 'rt', errors='strict') as f:
11
29
  f.read(8196)
12
30
  else:
13
- with open(path, "r") as f:
31
+ with open(path, "r", errors='strict') as f:
14
32
  f.read(8196)
15
33
  except UnicodeDecodeError:
16
34
  return True
@@ -18,11 +36,27 @@ def is_file_is_binary(path, compressed):
18
36
 
19
37
 
20
38
  def read_file(path, max_lines):
39
+ """
40
+ Reads the contents of a text file, with support for gzip compression.
41
+
42
+ If the file is determined to be binary, it returns a placeholder string.
43
+ If `max_lines` is specified, the file content will be truncated.
44
+
45
+ Args:
46
+ path (str): The path to the file to read.
47
+ max_lines (int or None): The maximum number of lines to read. If None,
48
+ the entire file is read.
49
+
50
+ Returns:
51
+ str: The content of the file as a single string, or a placeholder
52
+ if the file is binary.
53
+ """
21
54
  _, compressed_file_extension = os.path.splitext(path.lower())
22
55
  file_info = []
23
56
  honor_max_file = max_lines is not None
24
57
 
25
- def read_file(fid):
58
+ def _read_lines(fid):
59
+ """Helper to read lines from a file object and handle truncation."""
26
60
  for line in fid:
27
61
  file_info.append(line.rstrip())
28
62
  if honor_max_file and len(file_info) >= max_lines:
@@ -35,14 +69,23 @@ def read_file(path, max_lines):
35
69
 
36
70
  if is_compressed:
37
71
  with gzip.open(path, 'rt') as fid:
38
- read_file(fid)
72
+ _read_lines(fid)
39
73
  else:
40
74
  with sc_open(path) as fid:
41
- read_file(fid)
75
+ _read_lines(fid)
42
76
  return "\n".join(file_info)
43
77
 
44
78
 
45
79
  def get_file_type(ext):
80
+ """
81
+ Maps a file extension to a language identifier for syntax highlighting.
82
+
83
+ Args:
84
+ ext (str): The file extension (without the dot).
85
+
86
+ Returns:
87
+ str: The language identifier (e.g., 'verilog', 'tcl'). Defaults to 'log'.
88
+ """
46
89
  if ext in ("v", "vh", "sv", "svh", "vg"):
47
90
  return "verilog"
48
91
  if ext in ("vhdl", "vhd"):
@@ -61,6 +104,17 @@ def get_file_type(ext):
61
104
 
62
105
 
63
106
  def get_file_icon(file):
107
+ """
108
+ Determines an appropriate icon name for a given file path.
109
+
110
+ The icon name is based on the file's extension and type.
111
+
112
+ Args:
113
+ file (str): The path to the file.
114
+
115
+ Returns:
116
+ str: A string identifier for the icon (e.g., 'file-code', 'file-image').
117
+ """
64
118
  if not file:
65
119
  return 'file'
66
120
  ext = utils.get_file_ext(file)
@@ -80,17 +134,22 @@ def get_file_icon(file):
80
134
 
81
135
  def convert_filepaths_to_select_tree(logs_and_reports):
82
136
  """
83
- Converts the logs_and_reports found to the structure
84
- required by streamlit_tree_select. Success is predicated on the order of
85
- logs_and_reports outlined in report.get_files.
137
+ Converts a flat list of file paths into a hierarchical tree structure.
138
+
139
+ This structure is suitable for UI components like `streamlit_tree_select`.
140
+ The function recursively builds the tree based on the directory structure.
86
141
 
87
142
  Args:
88
- logs_and_reports (list) : A list of 3-tuples with order of a path name,
89
- folder in the..., and files in the....
143
+ logs_and_reports (list): A list of 3-tuples, where each tuple contains
144
+ (path_name, list_of_folders, list_of_files).
145
+
146
+ Returns:
147
+ list: A list of dictionaries representing the root nodes of the tree.
90
148
  """
91
149
  if not logs_and_reports:
92
150
  return []
93
151
 
152
+ # Create a dictionary for quick lookup of folder contents.
94
153
  all_files = {}
95
154
  for path_name, folders, files in logs_and_reports:
96
155
  all_files[path_name] = {
@@ -99,8 +158,10 @@ def convert_filepaths_to_select_tree(logs_and_reports):
99
158
  }
100
159
 
101
160
  def organize_node(base_folder):
161
+ """Recursively builds the node structure for a given folder."""
102
162
  nodes = []
103
163
 
164
+ # Add sub-folders as expandable nodes.
104
165
  for folder in all_files[base_folder]['folders']:
105
166
  path = os.path.join(base_folder, folder)
106
167
  nodes.append({
@@ -108,6 +169,7 @@ def convert_filepaths_to_select_tree(logs_and_reports):
108
169
  'label': folder,
109
170
  'children': organize_node(path)
110
171
  })
172
+ # Add files as leaf nodes.
111
173
  for file in all_files[base_folder]['files']:
112
174
  nodes.append({
113
175
  'value': os.path.join(base_folder, file),
@@ -116,5 +178,6 @@ def convert_filepaths_to_select_tree(logs_and_reports):
116
178
 
117
179
  return nodes
118
180
 
181
+ # Start the recursion from the root path.
119
182
  starting_path_name = logs_and_reports[0][0]
120
183
  return organize_node(starting_path_name)
@@ -1,3 +1,13 @@
1
+ """
2
+ Main entry point for the SiliconCompiler web-based dashboard using Streamlit.
3
+
4
+ This script initializes and runs the Streamlit application. It is responsible for:
5
+ - Initializing the application and session state.
6
+ - Setting up the page configuration (title, icon).
7
+ - Dynamically selecting and rendering the appropriate layout.
8
+ - Handling the auto-refresh mechanism to provide live updates.
9
+ - Triggering reruns when the underlying data changes.
10
+ """
1
11
  import streamlit
2
12
 
3
13
  from siliconcompiler.report.dashboard.web import components
@@ -9,28 +19,42 @@ import streamlit_autorefresh
9
19
 
10
20
 
11
21
  if __name__ == "__main__":
12
- # opened by running command in siliconcompiler/apps/sc_dashboard.py
22
+ # This script is executed by the `sc-dashboard` command.
23
+ # Initialize the Streamlit session state. This must be the first
24
+ # Streamlit command used in the app.
13
25
  state.init()
14
26
 
27
+ # Configure the page's title, icon, and other basic properties.
15
28
  components.setup_page()
29
+ # Set up the application state, loading data from the chip object.
16
30
  state.setup()
17
31
 
32
+ # Dynamically select the layout function based on the current state
33
+ # and then execute it to render the UI components for the page.
18
34
  layout = layouts.get_layout(state.get_key(state.APP_LAYOUT))
19
35
  layout()
20
36
 
37
+ # Determine if a full page reload is needed.
21
38
  reload = False
22
39
  if state.get_key(state.SELECTED_JOB) == 'default':
40
+ # Check if the underlying chip process is running and update the state.
23
41
  reload = state.set_key(state.IS_RUNNING, utils.is_running(state.get_chip()))
24
42
 
43
+ # Set the refresh interval based on the run status.
44
+ # Use a faster refresh rate when a job is running, and a slower one when stopped.
25
45
  if state.get_key(state.IS_RUNNING):
26
46
  update_interval = state.get_key(state.APP_RUNNING_REFRESH)
27
47
  else:
28
48
  update_interval = state.get_key(state.APP_STOPPED_REFRESH)
29
49
 
50
+ # Add the auto-refresh component to the page.
30
51
  streamlit_autorefresh.st_autorefresh(interval=update_interval)
31
52
 
53
+ # For debugging: print the current session state to the console.
32
54
  state.debug_print_state()
33
55
 
56
+ # Check if a full re-render of the application is required. This can be
57
+ # triggered by a data update, a manual rerun request, or a change in run status.
34
58
  if reload or state.update_manifest() or state.get_key(state.APP_RERUN):
35
59
  state.set_key(state.APP_RERUN, None)
36
60
  streamlit.rerun()