siliconcompiler 0.34.2__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.
- siliconcompiler/__init__.py +12 -5
- siliconcompiler/__main__.py +1 -7
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/_common.py +104 -23
- siliconcompiler/apps/sc.py +4 -8
- siliconcompiler/apps/sc_dashboard.py +6 -4
- siliconcompiler/apps/sc_install.py +10 -6
- siliconcompiler/apps/sc_issue.py +7 -5
- siliconcompiler/apps/sc_remote.py +1 -1
- siliconcompiler/apps/sc_server.py +9 -14
- siliconcompiler/apps/sc_show.py +6 -5
- siliconcompiler/apps/smake.py +130 -94
- siliconcompiler/apps/utils/replay.py +4 -7
- siliconcompiler/apps/utils/summarize.py +3 -5
- siliconcompiler/asic.py +420 -0
- siliconcompiler/checklist.py +25 -2
- siliconcompiler/cmdlineschema.py +534 -0
- siliconcompiler/constraints/asic_component.py +2 -2
- siliconcompiler/constraints/asic_pins.py +2 -2
- siliconcompiler/constraints/asic_timing.py +3 -3
- siliconcompiler/core.py +7 -32
- siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
- siliconcompiler/dependencyschema.py +89 -31
- siliconcompiler/design.py +176 -207
- siliconcompiler/filesetschema.py +250 -0
- siliconcompiler/flowgraph.py +274 -95
- siliconcompiler/fpga.py +124 -1
- siliconcompiler/library.py +218 -20
- siliconcompiler/metric.py +233 -20
- siliconcompiler/package/__init__.py +271 -50
- siliconcompiler/package/git.py +92 -16
- siliconcompiler/package/github.py +108 -12
- siliconcompiler/package/https.py +79 -16
- siliconcompiler/packageschema.py +88 -7
- siliconcompiler/pathschema.py +31 -2
- siliconcompiler/pdk.py +566 -1
- siliconcompiler/project.py +1095 -94
- siliconcompiler/record.py +38 -1
- siliconcompiler/remote/__init__.py +5 -2
- siliconcompiler/remote/client.py +11 -6
- siliconcompiler/remote/schema.py +5 -23
- siliconcompiler/remote/server.py +41 -54
- siliconcompiler/report/__init__.py +3 -3
- siliconcompiler/report/dashboard/__init__.py +48 -14
- siliconcompiler/report/dashboard/cli/__init__.py +99 -21
- siliconcompiler/report/dashboard/cli/board.py +364 -179
- siliconcompiler/report/dashboard/web/__init__.py +90 -12
- siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
- siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
- siliconcompiler/report/dashboard/web/components/graph.py +139 -100
- siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
- siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
- siliconcompiler/report/dashboard/web/state.py +141 -14
- siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
- siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
- siliconcompiler/report/dashboard/web/viewer.py +25 -1
- siliconcompiler/report/report.py +5 -2
- siliconcompiler/report/summary_image.py +29 -11
- siliconcompiler/scheduler/__init__.py +9 -1
- siliconcompiler/scheduler/docker.py +79 -1
- siliconcompiler/scheduler/run_node.py +35 -19
- siliconcompiler/scheduler/scheduler.py +208 -24
- siliconcompiler/scheduler/schedulernode.py +372 -46
- siliconcompiler/scheduler/send_messages.py +77 -29
- siliconcompiler/scheduler/slurm.py +76 -12
- siliconcompiler/scheduler/taskscheduler.py +140 -20
- siliconcompiler/schema/__init__.py +0 -2
- siliconcompiler/schema/baseschema.py +194 -38
- siliconcompiler/schema/journal.py +7 -4
- siliconcompiler/schema/namedschema.py +16 -10
- siliconcompiler/schema/parameter.py +55 -9
- siliconcompiler/schema/parametervalue.py +60 -0
- siliconcompiler/schema/safeschema.py +25 -2
- siliconcompiler/schema/schema_cfg.py +5 -5
- siliconcompiler/schema/utils.py +2 -2
- siliconcompiler/schema_obj.py +20 -3
- siliconcompiler/tool.py +979 -302
- siliconcompiler/tools/bambu/__init__.py +41 -0
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/minimum.py +2 -1
- siliconcompiler/tools/builtin/mux.py +2 -1
- siliconcompiler/tools/builtin/nop.py +2 -1
- siliconcompiler/tools/builtin/verify.py +2 -1
- siliconcompiler/tools/klayout/__init__.py +95 -0
- siliconcompiler/tools/openroad/__init__.py +289 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
- siliconcompiler/tools/slang/__init__.py +1 -1
- siliconcompiler/tools/slang/elaborate.py +2 -1
- siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
- siliconcompiler/tools/vivado/syn_fpga.py +6 -0
- siliconcompiler/tools/vivado/vivado.py +35 -2
- siliconcompiler/tools/vpr/__init__.py +150 -0
- siliconcompiler/tools/yosys/__init__.py +369 -1
- siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
- siliconcompiler/toolscripts/_tools.json +5 -10
- siliconcompiler/utils/__init__.py +66 -0
- siliconcompiler/utils/flowgraph.py +2 -2
- siliconcompiler/utils/issue.py +2 -1
- siliconcompiler/utils/logging.py +14 -0
- siliconcompiler/utils/multiprocessing.py +256 -0
- siliconcompiler/utils/showtools.py +10 -0
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +5 -5
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +115 -118
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
- siliconcompiler/schema/cmdlineschema.py +0 -250
- siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
- siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.34.2.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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
39
|
-
|
|
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)
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
72
|
+
_read_lines(fid)
|
|
39
73
|
else:
|
|
40
74
|
with sc_open(path) as fid:
|
|
41
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
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)
|
|
89
|
-
|
|
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
|
-
#
|
|
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()
|