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,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for generating and configuring the interactive flowgraph
|
|
3
|
+
display for the web dashboard using the `streamlit-agraph` library.
|
|
4
|
+
"""
|
|
1
5
|
from siliconcompiler.report import report
|
|
2
6
|
from siliconcompiler.tools._common import get_tool_task
|
|
3
7
|
from siliconcompiler import NodeStatus
|
|
@@ -5,30 +9,36 @@ from siliconcompiler import NodeStatus
|
|
|
5
9
|
from streamlit_agraph import Node, Edge, Config
|
|
6
10
|
|
|
7
11
|
|
|
8
|
-
#
|
|
12
|
+
# --- Constants ---
|
|
13
|
+
# Defines the color scheme for nodes based on their execution status.
|
|
9
14
|
NODE_COLORS = {
|
|
10
15
|
NodeStatus.SUCCESS: '#70db70', # green
|
|
11
|
-
|
|
12
16
|
NodeStatus.SKIPPED: '#ffc299', # orange
|
|
13
|
-
|
|
14
17
|
NodeStatus.PENDING: '#6699ff', # blue
|
|
15
|
-
NodeStatus.QUEUED: '#6699ff',
|
|
16
|
-
|
|
18
|
+
NodeStatus.QUEUED: '#6699ff', # blue
|
|
17
19
|
NodeStatus.RUNNING: '#ffff4d', # yellow
|
|
18
|
-
|
|
19
|
-
NodeStatus.ERROR: '#ff1a1a', # red
|
|
20
|
+
NodeStatus.ERROR: '#ff1a1a', # red
|
|
20
21
|
NodeStatus.TIMEOUT: '#ff1a1a', # red
|
|
21
|
-
|
|
22
|
-
"Unknown": '#6699ff', # blue
|
|
22
|
+
"Unknown": '#6699ff', # blue
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def get_nodes_and_edges(chip):
|
|
27
27
|
"""
|
|
28
|
-
|
|
28
|
+
Constructs the nodes and edges for the flowgraph from a chip object.
|
|
29
|
+
|
|
30
|
+
This function traverses the chip's flowgraph, creating styled `Node` and
|
|
31
|
+
`Edge` objects for `streamlit-agraph`. Node colors are based on status,
|
|
32
|
+
and edge styles (width, color, dashes) indicate the execution path and
|
|
33
|
+
dependencies.
|
|
29
34
|
|
|
30
35
|
Args:
|
|
31
|
-
chip (Chip)
|
|
36
|
+
chip (Chip): The chip object containing the flowgraph data.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
tuple: A tuple containing two lists:
|
|
40
|
+
- list[Node]: A list of `streamlit_agraph.Node` objects.
|
|
41
|
+
- list[Edge]: A list of `streamlit_agraph.Edge` objects.
|
|
32
42
|
"""
|
|
33
43
|
nodes = []
|
|
34
44
|
edges = []
|
|
@@ -36,61 +46,66 @@ def get_nodes_and_edges(chip):
|
|
|
36
46
|
if not chip.get('option', 'flow'):
|
|
37
47
|
return nodes, edges
|
|
38
48
|
|
|
49
|
+
# --- Style Configuration ---
|
|
39
50
|
default_node_border_width = 1
|
|
40
51
|
successful_path_node_width = 3
|
|
41
52
|
default_edge_width = 3
|
|
42
53
|
successful_path_edge_width = 5
|
|
43
54
|
|
|
55
|
+
# --- Data Extraction ---
|
|
44
56
|
node_dependencies = report.get_flowgraph_edges(chip)
|
|
45
57
|
successful_path = report.get_flowgraph_path(chip)
|
|
46
|
-
|
|
47
58
|
flow = chip.get('option', 'flow')
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
flowgraph_schema = chip.get("flowgraph", flow, field="schema")
|
|
60
|
+
entry_exit_nodes = flowgraph_schema.get_entry_nodes() + flowgraph_schema.get_exit_nodes()
|
|
50
61
|
|
|
62
|
+
# --- Node and Edge Creation ---
|
|
51
63
|
for step, index in node_dependencies:
|
|
52
|
-
# Build
|
|
64
|
+
# 1. Build the Node
|
|
53
65
|
node_border_width = default_node_border_width
|
|
54
66
|
if (step, index) in entry_exit_nodes:
|
|
55
67
|
node_border_width = successful_path_node_width
|
|
56
68
|
|
|
57
69
|
node_status = chip.get('record', 'status', step=step, index=index)
|
|
58
|
-
|
|
59
|
-
node_status = "Unknown"
|
|
60
|
-
node_color = NODE_COLORS[node_status]
|
|
70
|
+
node_color = NODE_COLORS.get(node_status, NODE_COLORS["Unknown"])
|
|
61
71
|
|
|
62
72
|
tool, task = get_tool_task(chip, step, index)
|
|
63
73
|
node_name = f'{step}/{index}'
|
|
64
|
-
label = node_name
|
|
74
|
+
label = f"{node_name}\n{tool}/{task}"
|
|
65
75
|
if tool == 'builtin':
|
|
66
|
-
label = node_name
|
|
76
|
+
label = f"{node_name}\n{tool}"
|
|
67
77
|
|
|
68
78
|
nodes.append(Node(
|
|
69
79
|
id=node_name,
|
|
70
80
|
label=label,
|
|
71
81
|
color=node_color,
|
|
72
|
-
opacity=1,
|
|
73
82
|
borderWidth=node_border_width,
|
|
74
83
|
shape='oval',
|
|
75
84
|
fixed=True))
|
|
76
85
|
|
|
77
|
-
# Build
|
|
78
|
-
path_taken = chip.get('record', 'inputnode', step=step, index=index)
|
|
79
|
-
|
|
86
|
+
# 2. Build Edges to this Node
|
|
87
|
+
path_taken = set(chip.get('record', 'inputnode', step=step, index=index) or [])
|
|
88
|
+
all_possible_inputs = set(node_dependencies.get((step, index), []))
|
|
89
|
+
all_edges = all_possible_inputs.union(path_taken)
|
|
90
|
+
|
|
80
91
|
for source_step, source_index in all_edges:
|
|
92
|
+
# Determine edge style based on whether it was part of the
|
|
93
|
+
# actual execution path and the critical path.
|
|
81
94
|
edge_width = default_edge_width
|
|
82
|
-
if (step, index) in successful_path and
|
|
83
|
-
(source_step, source_index) in successful_path:
|
|
95
|
+
if (step, index) in successful_path and (source_step, source_index) in successful_path:
|
|
84
96
|
edge_width = successful_path_edge_width
|
|
85
97
|
|
|
86
98
|
dashes = False
|
|
87
99
|
color = 'black'
|
|
88
100
|
if (source_step, source_index) not in path_taken:
|
|
101
|
+
# Potential but unused dependency
|
|
89
102
|
color = 'gray'
|
|
90
103
|
dashes = True
|
|
91
104
|
elif node_status != NodeStatus.SUCCESS:
|
|
105
|
+
# Executed path, but not part of a successful flow
|
|
92
106
|
color = 'gray'
|
|
93
107
|
elif NodeStatus.is_waiting(node_status) or NodeStatus.is_running(node_status):
|
|
108
|
+
# Path leading to a currently active node
|
|
94
109
|
color = 'blue'
|
|
95
110
|
dashes = True
|
|
96
111
|
|
|
@@ -106,12 +121,20 @@ def get_nodes_and_edges(chip):
|
|
|
106
121
|
|
|
107
122
|
|
|
108
123
|
def get_graph_config():
|
|
124
|
+
"""
|
|
125
|
+
Returns a `streamlit_agraph.Config` object with predefined settings
|
|
126
|
+
for a hierarchical, top-down flowgraph layout.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Config: The configuration object for the agraph component.
|
|
130
|
+
"""
|
|
109
131
|
return Config(
|
|
110
132
|
width='100%',
|
|
111
133
|
height=1000,
|
|
112
134
|
directed=True,
|
|
113
135
|
physics=False,
|
|
114
136
|
hierarchical=True,
|
|
137
|
+
# Hierarchical layout settings
|
|
115
138
|
nodeSpacing=150,
|
|
116
139
|
levelSeparation=100,
|
|
117
140
|
sortMethod='directed')
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A collection of functions for creating and managing interactive metric graphs
|
|
3
|
+
in the web dashboard using Streamlit and Altair.
|
|
4
|
+
"""
|
|
1
5
|
import altair
|
|
2
|
-
import math
|
|
3
6
|
import streamlit
|
|
4
7
|
|
|
5
8
|
from siliconcompiler.report import report
|
|
@@ -8,6 +11,13 @@ from siliconcompiler.report.dashboard.web import state
|
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
def _get_report_chips():
|
|
14
|
+
"""
|
|
15
|
+
Gathers all loaded chip objects and their names for reporting.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
list[dict]: A list of dictionaries, where each dictionary contains
|
|
19
|
+
'chip_object' and 'chip_name'.
|
|
20
|
+
"""
|
|
11
21
|
chips = []
|
|
12
22
|
for job in state.get_chips():
|
|
13
23
|
chips.append({'chip_object': state.get_chip(job), 'chip_name': job})
|
|
@@ -16,16 +26,18 @@ def _get_report_chips():
|
|
|
16
26
|
|
|
17
27
|
def job_selector():
|
|
18
28
|
"""
|
|
19
|
-
Displays a
|
|
20
|
-
in the
|
|
29
|
+
Displays a data editor in a popover for selecting which jobs to include
|
|
30
|
+
in the graphs.
|
|
31
|
+
|
|
32
|
+
The selection is stored in the session state.
|
|
21
33
|
"""
|
|
22
34
|
from pandas import DataFrame
|
|
23
35
|
|
|
24
36
|
jobs = state.get_chips()
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
all_jobs_df = DataFrame({
|
|
27
39
|
'job names': jobs,
|
|
28
|
-
'selected jobs': [
|
|
40
|
+
'selected jobs': [job in state.get_key(state.GRAPH_JOBS) for job in jobs]
|
|
29
41
|
})
|
|
30
42
|
|
|
31
43
|
configuration = {
|
|
@@ -35,41 +47,51 @@ def job_selector():
|
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
with streamlit.popover('Select Jobs', use_container_width=True):
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
selected_jobs_df = streamlit.data_editor(
|
|
51
|
+
all_jobs_df,
|
|
40
52
|
disabled=['job names'],
|
|
41
53
|
use_container_width=True,
|
|
42
54
|
hide_index=True,
|
|
43
55
|
column_config=configuration)
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if is_selected
|
|
49
|
-
|
|
57
|
+
selected_jobs_list = [
|
|
58
|
+
job_name for is_selected, job_name in zip(
|
|
59
|
+
selected_jobs_df['selected jobs'], selected_jobs_df['job names']
|
|
60
|
+
) if is_selected
|
|
61
|
+
]
|
|
50
62
|
|
|
51
|
-
state.set_key(state.GRAPH_JOBS,
|
|
63
|
+
state.set_key(state.GRAPH_JOBS, selected_jobs_list)
|
|
52
64
|
|
|
53
65
|
|
|
54
66
|
def graph_count_selector():
|
|
67
|
+
"""
|
|
68
|
+
Displays a slider to control the number of graphs shown on the page.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
int: The number of graphs selected by the user.
|
|
72
|
+
"""
|
|
55
73
|
return streamlit.slider(
|
|
56
74
|
'pick the number of graphs you want',
|
|
57
|
-
1,
|
|
58
|
-
10,
|
|
59
|
-
1,
|
|
75
|
+
1, 10, 1,
|
|
60
76
|
label_visibility='collapsed')
|
|
61
77
|
|
|
62
78
|
|
|
63
79
|
def settings(metrics, nodes, graph_number):
|
|
64
80
|
"""
|
|
65
|
-
Displays
|
|
66
|
-
|
|
81
|
+
Displays settings controls for a single graph instance.
|
|
82
|
+
|
|
83
|
+
This includes popovers for selecting a metric, nodes, and other graph
|
|
84
|
+
options like log scale, transpose, and chart type.
|
|
67
85
|
|
|
68
86
|
Args:
|
|
69
|
-
metrics (list)
|
|
70
|
-
nodes (list)
|
|
71
|
-
graph_number (int)
|
|
72
|
-
keys
|
|
87
|
+
metrics (list): A list of available metric names.
|
|
88
|
+
nodes (list): A list of available node names in 'step/index' format.
|
|
89
|
+
graph_number (int): The unique identifier for the graph, used to create
|
|
90
|
+
distinct keys for the UI widgets.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
tuple: A tuple containing the selected metric, nodes, log_scale flag,
|
|
94
|
+
transpose flag, and chart type.
|
|
73
95
|
"""
|
|
74
96
|
metric_selector_col, node_selector_col, settings_col = \
|
|
75
97
|
streamlit.columns(3, gap='small')
|
|
@@ -77,16 +99,14 @@ def settings(metrics, nodes, graph_number):
|
|
|
77
99
|
with metric_selector_col:
|
|
78
100
|
with streamlit.popover('Select a Metric', use_container_width=True):
|
|
79
101
|
selected_metric = streamlit.selectbox(
|
|
80
|
-
'Select a Metric',
|
|
81
|
-
metrics,
|
|
102
|
+
'Select a Metric', metrics,
|
|
82
103
|
label_visibility='collapsed',
|
|
83
104
|
key=f'graph-{graph_number}-metric-selection')
|
|
84
105
|
|
|
85
106
|
with node_selector_col:
|
|
86
107
|
with streamlit.popover('Select Nodes', use_container_width=True):
|
|
87
108
|
selected_nodes = streamlit.multiselect(
|
|
88
|
-
'Select a Node',
|
|
89
|
-
nodes,
|
|
109
|
+
'Select a Node', nodes,
|
|
90
110
|
label_visibility='collapsed',
|
|
91
111
|
key=f'graph-{graph_number}-node-selection',
|
|
92
112
|
default=nodes)
|
|
@@ -94,20 +114,17 @@ def settings(metrics, nodes, graph_number):
|
|
|
94
114
|
with settings_col:
|
|
95
115
|
with streamlit.popover('Settings', use_container_width=True):
|
|
96
116
|
log_scale = streamlit.checkbox(
|
|
97
|
-
"Log scale",
|
|
98
|
-
False,
|
|
117
|
+
"Log scale", False,
|
|
99
118
|
help="Make the y-axis log scale",
|
|
100
119
|
key=f'graph-{graph_number}-log-scale')
|
|
101
120
|
|
|
102
121
|
transpose = streamlit.checkbox(
|
|
103
|
-
"Transpose",
|
|
104
|
-
False,
|
|
122
|
+
"Transpose", False,
|
|
105
123
|
help="Use nodes instead of jobs as the x-axis",
|
|
106
124
|
key=f'graph-{graph_number}-transpose')
|
|
107
125
|
|
|
108
126
|
chart_type = streamlit.selectbox(
|
|
109
|
-
'Chart type',
|
|
110
|
-
['line', 'bar', 'point', 'tick'],
|
|
127
|
+
'Chart type', ['line', 'bar', 'point', 'tick'],
|
|
111
128
|
label_visibility='collapsed',
|
|
112
129
|
key=f'graph-{graph_number}-chart-selection')
|
|
113
130
|
|
|
@@ -115,86 +132,108 @@ def settings(metrics, nodes, graph_number):
|
|
|
115
132
|
|
|
116
133
|
|
|
117
134
|
def graph(metrics, nodes, node_to_step_index_map, graph_number):
|
|
135
|
+
"""
|
|
136
|
+
Renders a single, configurable metric graph using Altair.
|
|
137
|
+
|
|
138
|
+
This function gets the user's settings, fetches the corresponding data,
|
|
139
|
+
and constructs and displays an Altair chart.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
metrics (list): A list of all available metrics.
|
|
143
|
+
nodes (list): A list of all available nodes.
|
|
144
|
+
node_to_step_index_map (dict): A mapping from node names to
|
|
145
|
+
(step, index) tuples.
|
|
146
|
+
graph_number (int): The unique identifier for this graph instance.
|
|
147
|
+
"""
|
|
118
148
|
from pandas import DataFrame
|
|
119
149
|
|
|
120
150
|
metric, selected_nodes, log_scale, transpose, chart_type = \
|
|
121
151
|
settings(metrics, nodes, graph_number)
|
|
122
152
|
|
|
123
|
-
nodes_as_step_and_index = [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
nodes_as_step_and_index.append((step, index))
|
|
153
|
+
nodes_as_step_and_index = [
|
|
154
|
+
node_to_step_index_map[node] for node in selected_nodes
|
|
155
|
+
]
|
|
127
156
|
|
|
157
|
+
# --- Data Fetching and Preparation ---
|
|
128
158
|
if transpose:
|
|
129
|
-
x_axis_label = 'nodes'
|
|
130
|
-
color_label = 'runs'
|
|
159
|
+
x_axis_label, color_label = 'nodes', 'runs'
|
|
131
160
|
else:
|
|
132
|
-
x_axis_label = 'runs'
|
|
133
|
-
color_label = 'nodes'
|
|
161
|
+
x_axis_label, color_label = 'runs', 'nodes'
|
|
134
162
|
|
|
135
163
|
y_axis_label = metric
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
data, metric_unit = report.get_chart_data(
|
|
165
|
+
_get_report_chips(), metric, nodes_as_step_and_index)
|
|
138
166
|
if metric_unit:
|
|
139
167
|
y_axis_label = f'{metric} ({metric_unit})'
|
|
140
168
|
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
169
|
+
# Reshape data into a long-form DataFrame suitable for Altair
|
|
170
|
+
plot_data = []
|
|
171
|
+
if data:
|
|
172
|
+
for (step, index), job_data in data.items():
|
|
173
|
+
for job_name, value in job_data.items():
|
|
174
|
+
if job_name in state.get_key(state.GRAPH_JOBS):
|
|
175
|
+
plot_data.append({
|
|
176
|
+
'runs': job_name,
|
|
177
|
+
'nodes': f'{step}/{index}',
|
|
178
|
+
y_axis_label: value
|
|
179
|
+
})
|
|
180
|
+
filtered_df = DataFrame(plot_data).dropna()
|
|
181
|
+
|
|
182
|
+
# --- Chart Configuration and Rendering ---
|
|
183
|
+
if not filtered_df.empty:
|
|
184
|
+
sort_order = {
|
|
185
|
+
"runs": state.get_key(state.GRAPH_JOBS),
|
|
186
|
+
"nodes": selected_nodes
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
x_axis = altair.X(x_axis_label,
|
|
190
|
+
axis=altair.Axis(labelAngle=-75),
|
|
191
|
+
sort=sort_order[x_axis_label])
|
|
192
|
+
|
|
193
|
+
y_axis = altair.Y(y_axis_label)
|
|
194
|
+
if log_scale and chart_type != 'bar':
|
|
195
|
+
y_axis = altair.Y(y_axis_label, scale=altair.Scale(type="log"))
|
|
196
|
+
|
|
197
|
+
color = color_label
|
|
198
|
+
|
|
199
|
+
alt_chart = altair.Chart(filtered_df, height=500)
|
|
200
|
+
|
|
201
|
+
if chart_type == 'line':
|
|
202
|
+
chart_mark = alt_chart.mark_line(point=True)
|
|
203
|
+
elif chart_type == 'bar':
|
|
204
|
+
chart_mark = alt_chart.mark_bar()
|
|
205
|
+
elif chart_type == 'point':
|
|
206
|
+
chart_mark = alt_chart.mark_circle()
|
|
207
|
+
elif chart_type == 'tick':
|
|
208
|
+
chart_mark = alt_chart.mark_tick()
|
|
209
|
+
else:
|
|
210
|
+
raise ValueError(f'{chart_type} not supported')
|
|
211
|
+
|
|
212
|
+
chart = chart_mark.encode(x=x_axis, y=y_axis, color=color)
|
|
213
|
+
streamlit.altair_chart(chart, use_container_width=True, theme='streamlit')
|
|
183
214
|
else:
|
|
184
|
-
|
|
215
|
+
streamlit.warning("No data available for the selected metric and nodes.")
|
|
185
216
|
|
|
186
|
-
chart = chart_mark.encode(
|
|
187
|
-
x=x_axis,
|
|
188
|
-
y=y_axis,
|
|
189
|
-
color=color)
|
|
190
217
|
|
|
191
|
-
|
|
218
|
+
def viewer(node_to_step_index_map):
|
|
219
|
+
"""
|
|
220
|
+
The main container component for the graphing page.
|
|
192
221
|
|
|
222
|
+
It lays out the job selector, the graph count selector, and the
|
|
223
|
+
individual graph components in a grid.
|
|
193
224
|
|
|
194
|
-
|
|
225
|
+
Args:
|
|
226
|
+
node_to_step_index_map (dict): A mapping from node names to
|
|
227
|
+
(step, index) tuples, passed down to the graph components.
|
|
228
|
+
"""
|
|
195
229
|
nodes, metrics = report.get_chart_selection_options(_get_report_chips())
|
|
196
230
|
metrics = sorted(metrics)
|
|
197
231
|
|
|
232
|
+
# Initialize selected jobs if not already set
|
|
233
|
+
if state.get_key(state.GRAPH_JOBS) is None:
|
|
234
|
+
state.set_key(state.GRAPH_JOBS, state.get_chips())
|
|
235
|
+
|
|
236
|
+
# --- UI Layout ---
|
|
198
237
|
job_selector_col, graph_adder_col = streamlit.columns(2, gap='large')
|
|
199
238
|
with job_selector_col:
|
|
200
239
|
job_selector()
|
|
@@ -203,14 +242,14 @@ def viewer(node_to_step_index_map):
|
|
|
203
242
|
|
|
204
243
|
streamlit.divider()
|
|
205
244
|
|
|
245
|
+
# Create a responsive grid for the graphs
|
|
206
246
|
columns = 1 if graphs <= 1 else 2
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if
|
|
247
|
+
for i in range(graphs):
|
|
248
|
+
if i % columns == 0:
|
|
249
|
+
# Start a new row of columns
|
|
250
|
+
graph_cols = streamlit.columns(columns, gap='large')
|
|
251
|
+
with graph_cols[i % columns]:
|
|
252
|
+
graph(metrics, nodes, node_to_step_index_map, i)
|
|
253
|
+
# Add a divider between rows
|
|
254
|
+
if i < graphs - columns:
|
|
215
255
|
streamlit.divider()
|
|
216
|
-
graph_number += 1
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A registry for different dashboard layouts.
|
|
3
|
+
|
|
4
|
+
This module imports layout functions from other files and provides a centralized
|
|
5
|
+
way to access them by name. This allows the main application to dynamically
|
|
6
|
+
switch between different UI arrangements.
|
|
7
|
+
"""
|
|
1
8
|
from siliconcompiler.report.dashboard.web.layouts import vertical_flowgraph
|
|
2
9
|
from siliconcompiler.report.dashboard.web.layouts import vertical_flowgraph_sac_tabs
|
|
3
10
|
from siliconcompiler.report.dashboard.web.layouts import vertical_flowgraph_node_tab
|
|
4
11
|
|
|
12
|
+
# A dictionary mapping layout names to their corresponding layout functions.
|
|
5
13
|
__LAYOUTS = {
|
|
6
14
|
"vertical_flowgraph": vertical_flowgraph.layout,
|
|
7
15
|
"vertical_flowgraph_sac_tabs": vertical_flowgraph_sac_tabs.layout,
|
|
@@ -10,11 +18,31 @@ __LAYOUTS = {
|
|
|
10
18
|
|
|
11
19
|
|
|
12
20
|
def get_all_layouts():
|
|
21
|
+
"""
|
|
22
|
+
Retrieves a list of all available layout names.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
list[str]: A list of strings, where each string is the name of a
|
|
26
|
+
registered layout.
|
|
27
|
+
"""
|
|
13
28
|
return list(__LAYOUTS.keys())
|
|
14
29
|
|
|
15
30
|
|
|
16
31
|
def get_layout(name):
|
|
32
|
+
"""
|
|
33
|
+
Retrieves a layout function by its registered name.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
name (str): The name of the layout to retrieve.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
function: The corresponding layout function.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If the provided name does not match any registered layout.
|
|
43
|
+
"""
|
|
17
44
|
if name not in __LAYOUTS:
|
|
18
|
-
raise ValueError(f"{name} is not a layout"
|
|
45
|
+
raise ValueError(f"'{name}' is not a valid layout. "
|
|
46
|
+
f"Available layouts: {', '.join(get_all_layouts())}")
|
|
19
47
|
|
|
20
48
|
return __LAYOUTS[name]
|
|
@@ -1,43 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for managing the state of tab components in the web dashboard.
|
|
3
|
+
|
|
4
|
+
This module provides helpers for controlling which tab is active, especially
|
|
5
|
+
when a user action (like selecting a node or a file) should automatically
|
|
6
|
+
switch the view to a different tab.
|
|
7
|
+
"""
|
|
1
8
|
import streamlit_antd_components as sac
|
|
2
9
|
|
|
3
10
|
from siliconcompiler.report.dashboard.web import state
|
|
4
11
|
|
|
5
12
|
|
|
6
13
|
def check_rerun():
|
|
7
|
-
|
|
14
|
+
"""
|
|
15
|
+
Checks if the selected node or file has changed and updates the active tab accordingly.
|
|
16
|
+
|
|
17
|
+
This function is called on each rerun to ensure that if a user selects a
|
|
18
|
+
node in the flowgraph, the UI automatically switches to the "Node Information"
|
|
19
|
+
tab. Similarly, if a file is selected, it switches to the "File Viewer" tab.
|
|
20
|
+
"""
|
|
21
|
+
# If the globally selected node changes, flag a rerun to switch tabs.
|
|
8
22
|
if state.set_key(state.SELECTED_NODE, state.get_selected_node()):
|
|
9
23
|
state.set_key(state.APP_RERUN, "Node")
|
|
10
24
|
|
|
25
|
+
# Based on the rerun flag, force a specific tab to be selected.
|
|
11
26
|
if state.get_key(state.APP_RERUN) == "Node":
|
|
12
27
|
state.set_key(state.SELECT_TAB, "Node Information")
|
|
28
|
+
# Reset the component's internal state to ensure the change takes effect.
|
|
13
29
|
state.del_key(state.TAB_STATE)
|
|
14
30
|
elif state.get_key(state.APP_RERUN) == "File":
|
|
15
31
|
state.set_key(state.SELECT_TAB, "File Viewer")
|
|
16
32
|
state.del_key(state.TAB_STATE)
|
|
17
33
|
else:
|
|
34
|
+
# Clear the forced selection if no specific action was taken.
|
|
18
35
|
state.set_key(state.SELECT_TAB, None)
|
|
19
36
|
|
|
20
37
|
|
|
21
38
|
def sac_tabs(tab_headings):
|
|
39
|
+
"""
|
|
40
|
+
Renders a tab group using streamlit-antd-components and manages its state.
|
|
41
|
+
|
|
42
|
+
This function determines which tab should be initially selected based on
|
|
43
|
+
the application state and saves the user's new selection back to the state.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
tab_headings (list[sac.Tabs.Item]): A list of tab items to display.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: The label of the currently selected tab.
|
|
50
|
+
"""
|
|
22
51
|
index = 0
|
|
23
52
|
|
|
53
|
+
# Determine the initial tab index based on a forced selection or the last known state.
|
|
24
54
|
if state.get_key(state.SELECT_TAB):
|
|
55
|
+
# A specific tab is being forced by an action (e.g., node selection).
|
|
25
56
|
for n, tab in enumerate(tab_headings):
|
|
26
57
|
if state.get_key(state.SELECT_TAB) == tab.label:
|
|
27
58
|
index = n
|
|
59
|
+
break
|
|
28
60
|
elif state.get_key(state.TAB_INDEX) is not None:
|
|
61
|
+
# Default to the last tab the user had open.
|
|
29
62
|
index = state.get_key(state.TAB_INDEX)
|
|
30
63
|
|
|
64
|
+
# Render the tabs component.
|
|
31
65
|
tab_selected = sac.tabs(
|
|
32
66
|
tab_headings,
|
|
33
67
|
align='center',
|
|
34
68
|
variant='outline',
|
|
35
69
|
use_container_width=True,
|
|
36
70
|
index=index,
|
|
37
|
-
key=state.TAB_STATE)
|
|
71
|
+
key=state.TAB_STATE) # The key links to the component's internal state.
|
|
38
72
|
|
|
73
|
+
# Save the index of the selected tab for the next rerun.
|
|
39
74
|
for n, tab in enumerate(tab_headings):
|
|
40
75
|
if tab_selected == tab.label:
|
|
41
76
|
state.set_key(state.TAB_INDEX, n)
|
|
77
|
+
break
|
|
42
78
|
|
|
43
79
|
return tab_selected
|