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.
Files changed (121) hide show
  1. siliconcompiler/__init__.py +12 -5
  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 +6 -5
  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/asic_component.py +2 -2
  19. siliconcompiler/constraints/asic_pins.py +2 -2
  20. siliconcompiler/constraints/asic_timing.py +3 -3
  21. siliconcompiler/core.py +7 -32
  22. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
  23. siliconcompiler/dependencyschema.py +89 -31
  24. siliconcompiler/design.py +176 -207
  25. siliconcompiler/filesetschema.py +250 -0
  26. siliconcompiler/flowgraph.py +274 -95
  27. siliconcompiler/fpga.py +124 -1
  28. siliconcompiler/library.py +218 -20
  29. siliconcompiler/metric.py +233 -20
  30. siliconcompiler/package/__init__.py +271 -50
  31. siliconcompiler/package/git.py +92 -16
  32. siliconcompiler/package/github.py +108 -12
  33. siliconcompiler/package/https.py +79 -16
  34. siliconcompiler/packageschema.py +88 -7
  35. siliconcompiler/pathschema.py +31 -2
  36. siliconcompiler/pdk.py +566 -1
  37. siliconcompiler/project.py +1095 -94
  38. siliconcompiler/record.py +38 -1
  39. siliconcompiler/remote/__init__.py +5 -2
  40. siliconcompiler/remote/client.py +11 -6
  41. siliconcompiler/remote/schema.py +5 -23
  42. siliconcompiler/remote/server.py +41 -54
  43. siliconcompiler/report/__init__.py +3 -3
  44. siliconcompiler/report/dashboard/__init__.py +48 -14
  45. siliconcompiler/report/dashboard/cli/__init__.py +99 -21
  46. siliconcompiler/report/dashboard/cli/board.py +364 -179
  47. siliconcompiler/report/dashboard/web/__init__.py +90 -12
  48. siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
  49. siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
  50. siliconcompiler/report/dashboard/web/components/graph.py +139 -100
  51. siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
  52. siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
  53. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
  54. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
  55. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
  56. siliconcompiler/report/dashboard/web/state.py +141 -14
  57. siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
  58. siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
  59. siliconcompiler/report/dashboard/web/viewer.py +25 -1
  60. siliconcompiler/report/report.py +5 -2
  61. siliconcompiler/report/summary_image.py +29 -11
  62. siliconcompiler/scheduler/__init__.py +9 -1
  63. siliconcompiler/scheduler/docker.py +79 -1
  64. siliconcompiler/scheduler/run_node.py +35 -19
  65. siliconcompiler/scheduler/scheduler.py +208 -24
  66. siliconcompiler/scheduler/schedulernode.py +372 -46
  67. siliconcompiler/scheduler/send_messages.py +77 -29
  68. siliconcompiler/scheduler/slurm.py +76 -12
  69. siliconcompiler/scheduler/taskscheduler.py +140 -20
  70. siliconcompiler/schema/__init__.py +0 -2
  71. siliconcompiler/schema/baseschema.py +194 -38
  72. siliconcompiler/schema/journal.py +7 -4
  73. siliconcompiler/schema/namedschema.py +16 -10
  74. siliconcompiler/schema/parameter.py +55 -9
  75. siliconcompiler/schema/parametervalue.py +60 -0
  76. siliconcompiler/schema/safeschema.py +25 -2
  77. siliconcompiler/schema/schema_cfg.py +5 -5
  78. siliconcompiler/schema/utils.py +2 -2
  79. siliconcompiler/schema_obj.py +20 -3
  80. siliconcompiler/tool.py +979 -302
  81. siliconcompiler/tools/bambu/__init__.py +41 -0
  82. siliconcompiler/tools/builtin/concatenate.py +2 -2
  83. siliconcompiler/tools/builtin/minimum.py +2 -1
  84. siliconcompiler/tools/builtin/mux.py +2 -1
  85. siliconcompiler/tools/builtin/nop.py +2 -1
  86. siliconcompiler/tools/builtin/verify.py +2 -1
  87. siliconcompiler/tools/klayout/__init__.py +95 -0
  88. siliconcompiler/tools/openroad/__init__.py +289 -0
  89. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
  90. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
  91. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
  92. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
  93. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
  94. siliconcompiler/tools/slang/__init__.py +1 -1
  95. siliconcompiler/tools/slang/elaborate.py +2 -1
  96. siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
  97. siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
  98. siliconcompiler/tools/vivado/syn_fpga.py +6 -0
  99. siliconcompiler/tools/vivado/vivado.py +35 -2
  100. siliconcompiler/tools/vpr/__init__.py +150 -0
  101. siliconcompiler/tools/yosys/__init__.py +369 -1
  102. siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
  103. siliconcompiler/toolscripts/_tools.json +5 -10
  104. siliconcompiler/utils/__init__.py +66 -0
  105. siliconcompiler/utils/flowgraph.py +2 -2
  106. siliconcompiler/utils/issue.py +2 -1
  107. siliconcompiler/utils/logging.py +14 -0
  108. siliconcompiler/utils/multiprocessing.py +256 -0
  109. siliconcompiler/utils/showtools.py +10 -0
  110. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +5 -5
  111. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +115 -118
  112. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
  113. siliconcompiler/schema/cmdlineschema.py +0 -250
  114. siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
  115. siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
  116. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
  117. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
  118. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
  119. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
  120. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
  121. {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
- # for flowgraph
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', # blue
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
- Returns the nodes and edges required to make a streamlit_agraph.
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) : The chip object that contains the schema read from.
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
- entry_exit_nodes = chip.get("flowgraph", flow, field="schema").get_entry_nodes() + \
49
- chip.get("flowgraph", flow, field="schema").get_exit_nodes()
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 node
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
- if node_status not in NODE_COLORS:
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 + "\n" + tool + "/" + task
74
+ label = f"{node_name}\n{tool}/{task}"
65
75
  if tool == 'builtin':
66
- label = node_name + "\n" + tool
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 edges
78
- path_taken = chip.get('record', 'inputnode', step=step, index=index)
79
- all_edges = set([*node_dependencies[step, index], *path_taken])
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 dataframe that can be edited to select specific jobs to include
20
- in the analysis.
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
- all_jobs = DataFrame({
38
+ all_jobs_df = DataFrame({
27
39
  'job names': jobs,
28
- 'selected jobs': [True] * len(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
- selected_jobs = streamlit.data_editor(
39
- all_jobs,
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
- jobs = []
46
- for is_selected, job_name in zip(selected_jobs['selected jobs'].tolist(),
47
- selected_jobs['job names'].tolist()):
48
- if is_selected:
49
- jobs.append(job_name)
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, 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 selectbox for metrics and nodes which informs the graph on what
66
- to display.
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) : A list of metrics that are set for all chips given in chips.
70
- nodes (list) : A list of nodes given in the form f'{step}/{index}'
71
- graph_number (int) : The number of graphs there are. Used to create
72
- keys to distinguish selectboxes from each other.
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
- for selected_node in selected_nodes:
125
- step, index = node_to_step_index_map[selected_node]
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
- data, metric_unit = report.get_chart_data(_get_report_chips(), metric, nodes_as_step_and_index)
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
- # Prepare plot data
142
- filtered_data = {
143
- x_axis_label: [],
144
- y_axis_label: [],
145
- color_label: []
146
- }
147
-
148
- labels = {
149
- "runs": state.get_key(state.GRAPH_JOBS),
150
- "nodes": [f'{step}/{index}' for step, index in data]
151
- }
152
-
153
- if nodes:
154
- # filtering through data
155
- for job_name in state.get_key(state.GRAPH_JOBS):
156
- for step, index in data:
157
- filtered_data['runs'].append(job_name)
158
- filtered_data['nodes'].append(step + index)
159
- if job_name not in data[(step, index)].keys():
160
- filtered_data[y_axis_label].append(None)
161
- else:
162
- filtered_data[y_axis_label].append(data[(step, index)][job_name])
163
-
164
- # Setup chart
165
- x_axis = altair.X(x_axis_label, axis=altair.Axis(labelAngle=-75), sort=labels[x_axis_label])
166
-
167
- y_axis = y_axis_label
168
- if log_scale and chart_type != 'bar':
169
- y_axis = altair.Y(y_axis_label, scale=altair.Scale(type="log"))
170
-
171
- color = color_label
172
-
173
- alt_chart = altair.Chart(DataFrame(filtered_data).dropna(), height=500)
174
-
175
- if chart_type == 'line':
176
- chart_mark = alt_chart.mark_line(point=True)
177
- elif chart_type == 'bar':
178
- chart_mark = alt_chart.mark_bar(point=True)
179
- elif chart_type == 'point':
180
- chart_mark = alt_chart.mark_circle(point=True)
181
- elif chart_type == 'tick':
182
- chart_mark = alt_chart.mark_tick(point=True)
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
- raise ValueError(f'{chart_type} not supported')
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
- streamlit.altair_chart(chart, use_container_width=True, theme='streamlit')
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
- def viewer(node_to_step_index_map):
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
- last_row_num = int(math.floor((graphs - 1) / columns)) * columns
208
-
209
- graph_number = 0
210
- graph_cols = streamlit.columns(columns, gap='large')
211
- while graph_number < graphs:
212
- with graph_cols[graph_number % columns]:
213
- graph(metrics, nodes, node_to_step_index_map, graph_number)
214
- if graph_number < last_row_num:
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
- # Determine if node was modified
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