siliconcompiler 0.28.2__py3-none-any.whl → 0.28.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 (77) hide show
  1. siliconcompiler/_common.py +12 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/sc_dashboard.py +5 -1
  4. siliconcompiler/apps/sc_install.py +61 -13
  5. siliconcompiler/apps/sc_remote.py +1 -1
  6. siliconcompiler/core.py +39 -13
  7. siliconcompiler/remote/client.py +41 -10
  8. siliconcompiler/report/__init__.py +1 -1
  9. siliconcompiler/report/{streamlit_report.py → dashboard/__init__.py} +47 -10
  10. siliconcompiler/report/dashboard/components/__init__.py +534 -0
  11. siliconcompiler/report/dashboard/components/flowgraph.py +114 -0
  12. siliconcompiler/report/dashboard/components/graph.py +208 -0
  13. siliconcompiler/report/dashboard/layouts/__init__.py +20 -0
  14. siliconcompiler/report/dashboard/layouts/_common.py +43 -0
  15. siliconcompiler/report/dashboard/layouts/vertical_flowgraph.py +95 -0
  16. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_node_tab.py +114 -0
  17. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_sac_tabs.py +107 -0
  18. siliconcompiler/report/dashboard/state.py +215 -0
  19. siliconcompiler/report/dashboard/utils/__init__.py +73 -0
  20. siliconcompiler/report/dashboard/utils/file_utils.py +120 -0
  21. siliconcompiler/report/dashboard/viewer.py +36 -0
  22. siliconcompiler/report/report.py +22 -4
  23. siliconcompiler/scheduler/__init__.py +43 -6
  24. siliconcompiler/schema/schema_obj.py +4 -2
  25. siliconcompiler/tools/openroad/floorplan.py +5 -0
  26. siliconcompiler/tools/openroad/openroad.py +12 -3
  27. siliconcompiler/tools/openroad/scripts/sc_cts.tcl +18 -13
  28. siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +6 -1
  29. siliconcompiler/tools/openroad/scripts/sc_procs.tcl +28 -0
  30. siliconcompiler/toolscripts/_tools.json +8 -3
  31. siliconcompiler/toolscripts/rhel8/install-chisel.sh +26 -0
  32. siliconcompiler/toolscripts/rhel8/install-ghdl.sh +25 -0
  33. siliconcompiler/toolscripts/rhel8/install-icarus.sh +40 -0
  34. siliconcompiler/toolscripts/rhel8/install-klayout.sh +17 -0
  35. siliconcompiler/toolscripts/rhel8/install-magic.sh +26 -0
  36. siliconcompiler/toolscripts/rhel8/install-montage.sh +5 -0
  37. siliconcompiler/toolscripts/rhel8/install-netgen.sh +25 -0
  38. siliconcompiler/toolscripts/rhel8/install-openroad.sh +31 -0
  39. siliconcompiler/toolscripts/rhel8/install-slang.sh +31 -0
  40. siliconcompiler/toolscripts/rhel8/install-surelog.sh +32 -0
  41. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +27 -0
  42. siliconcompiler/toolscripts/rhel8/install-verible.sh +24 -0
  43. siliconcompiler/toolscripts/rhel8/install-verilator.sh +40 -0
  44. siliconcompiler/toolscripts/rhel8/install-xyce.sh +64 -0
  45. siliconcompiler/toolscripts/rhel8/install-yosys.sh +23 -0
  46. siliconcompiler/toolscripts/rhel9/install-chisel.sh +26 -0
  47. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +25 -0
  48. siliconcompiler/toolscripts/rhel9/install-icarus.sh +40 -0
  49. siliconcompiler/toolscripts/rhel9/install-klayout.sh +17 -0
  50. siliconcompiler/toolscripts/rhel9/install-magic.sh +26 -0
  51. siliconcompiler/toolscripts/rhel9/install-montage.sh +5 -0
  52. siliconcompiler/toolscripts/rhel9/install-netgen.sh +25 -0
  53. siliconcompiler/toolscripts/rhel9/install-slang.sh +31 -0
  54. siliconcompiler/toolscripts/rhel9/install-surelog.sh +32 -0
  55. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +27 -0
  56. siliconcompiler/toolscripts/rhel9/install-verible.sh +24 -0
  57. siliconcompiler/toolscripts/rhel9/install-verilator.sh +40 -0
  58. siliconcompiler/toolscripts/rhel9/install-xdm.sh +43 -0
  59. siliconcompiler/toolscripts/rhel9/install-xyce.sh +64 -0
  60. siliconcompiler/toolscripts/rhel9/install-yosys.sh +23 -0
  61. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +1 -1
  62. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +40 -0
  63. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -2
  64. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +1 -1
  65. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +40 -0
  66. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -2
  67. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +1 -1
  68. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
  69. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +40 -0
  70. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -2
  71. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/METADATA +7 -6
  72. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/RECORD +76 -32
  73. siliconcompiler/report/streamlit_viewer.py +0 -944
  74. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/LICENSE +0 -0
  75. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/WHEEL +0 -0
  76. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/entry_points.txt +0 -0
  77. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,534 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ import pandas
5
+
6
+ import streamlit
7
+ from streamlit_agraph import agraph
8
+ import streamlit_antd_components as sac
9
+
10
+ from PIL import Image
11
+
12
+ from siliconcompiler import __version__ as sc_version
13
+ from siliconcompiler import utils
14
+ from siliconcompiler.report import report
15
+
16
+ from siliconcompiler.report.dashboard import state
17
+ from siliconcompiler.report.dashboard import layouts
18
+ from siliconcompiler.report.dashboard.utils import file_utils
19
+ from siliconcompiler.report.dashboard.components import flowgraph
20
+
21
+
22
+ SC_ABOUT = [
23
+ f"SiliconCompiler {sc_version}",
24
+ '''A compiler framework that automates translation from source code to
25
+ silicon.''',
26
+ "https://www.siliconcompiler.com/",
27
+ "https://github.com/siliconcompiler/siliconcompiler/"
28
+ ]
29
+ SC_MENU = {
30
+ "Get help": "https://docs.siliconcompiler.com/",
31
+ "Report a Bug":
32
+ '''https://github.com/siliconcompiler/siliconcompiler/issues''',
33
+ "About": "\n\n".join(SC_ABOUT)}
34
+ SC_DATA_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'data'))
35
+ SC_LOGO_PATH = os.path.join(SC_DATA_ROOT, 'logo.png')
36
+ SC_FONT_PATH = os.path.join(SC_DATA_ROOT, 'RobotoMono', 'RobotoMono-Regular.ttf')
37
+
38
+
39
+ def page_header(title_col_width=0.7):
40
+ """
41
+ Displays the title and a selectbox that allows you to select a given run
42
+ to inspect.
43
+
44
+ Args:
45
+ title_col_width (float) : A number between 0 and 1 which is the percentage of the
46
+ width of the screen given to the title and logo. The rest is given to selectbox.
47
+ """
48
+
49
+ if state.DEVELOPER:
50
+ col_width = (1 - title_col_width) / 2
51
+ title_col, job_select_col, settings_col = \
52
+ streamlit.columns([title_col_width, col_width, col_width], gap="large")
53
+ else:
54
+ title_col, job_select_col = \
55
+ streamlit.columns([title_col_width, 1 - title_col_width], gap="large")
56
+
57
+ with title_col:
58
+ design_title(design=state.get_chip().design)
59
+ with job_select_col:
60
+ job_selector()
61
+
62
+ if state.DEVELOPER:
63
+ with settings_col:
64
+ with streamlit.popover("Settings", use_container_width=True):
65
+ all_layouts = layouts.get_all_layouts()
66
+ layout_index = all_layouts.index(state.get_key(state.APP_LAYOUT))
67
+ if state.set_key(
68
+ state.APP_LAYOUT,
69
+ streamlit.selectbox("Layout", all_layouts, index=layout_index)):
70
+ state.set_key(state.APP_RERUN, "Layout")
71
+
72
+ state._DEBUG = streamlit.checkbox("Debug", state._DEBUG)
73
+
74
+ state.set_key(
75
+ state.APP_RUNNING_REFRESH,
76
+ streamlit.slider(
77
+ "Running refresh rate (ms)",
78
+ min_value=1000,
79
+ max_value=10000,
80
+ step=500,
81
+ value=state.get_key(state.APP_RUNNING_REFRESH)))
82
+
83
+ state.set_key(
84
+ state.APP_STOPPED_REFRESH,
85
+ streamlit.slider(
86
+ "Stopped refresh rate (ms)",
87
+ min_value=1000,
88
+ max_value=100000,
89
+ step=1000,
90
+ value=state.get_key(state.APP_STOPPED_REFRESH)))
91
+
92
+ state.set_key(
93
+ state.MAX_DICT_ITEMS_TO_SHOW,
94
+ streamlit.number_input(
95
+ "Maximum dict item to show",
96
+ min_value=1,
97
+ max_value=10000,
98
+ value=state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)))
99
+
100
+ state.set_key(
101
+ state.MAX_FILE_LINES_TO_SHOW,
102
+ streamlit.number_input(
103
+ "Maximum file lines to show",
104
+ min_value=100,
105
+ max_value=1000,
106
+ step=100,
107
+ value=state.get_key(state.MAX_FILE_LINES_TO_SHOW)))
108
+
109
+
110
+ def design_title(design=""):
111
+ font = base64.b64encode(open(SC_FONT_PATH, "rb").read()).decode()
112
+ streamlit.html(
113
+ f'''
114
+ <head>
115
+ <style>
116
+ /* Define the @font-face rule */
117
+ @font-face {{
118
+ font-family: 'Roboto Mono';
119
+ src: url(data:font/truetype;charset=utf-8;base64,{font}) format('truetype');
120
+ font-weight: normal;
121
+ font-style: normal;
122
+ }}
123
+
124
+ /* Styles for the logo and text */
125
+ .logo-container {{
126
+ display: flex;
127
+ align-items: flex-start;
128
+ }}
129
+
130
+ .logo-image {{
131
+ margin-right: 10px;
132
+ margin-top: -10px;
133
+ }}
134
+
135
+ .logo-text {{
136
+ display: flex;
137
+ flex-direction: column;
138
+ margin-top: -20px;
139
+ }}
140
+
141
+ .design-text {{
142
+ color: #F1C437; /* Yellow color */
143
+ font-family: 'Roboto Mono', sans-serif;
144
+ font-weight: 700 !important;
145
+ font-size: 30px !important;
146
+ margin-bottom: -16px;
147
+ }}
148
+
149
+ .dashboard-text {{
150
+ color: #1D4482; /* Blue color */
151
+ font-family: 'Roboto Mono', sans-serif;
152
+ font-weight: 700 !important;
153
+ font-size: 30px !important;
154
+ }}
155
+
156
+ </style>
157
+ </head>
158
+ '''
159
+ )
160
+
161
+ logo = base64.b64encode(open(SC_LOGO_PATH, "rb").read()).decode()
162
+ streamlit.html(
163
+ f'''
164
+ <body>
165
+ <div class="logo-container">
166
+ <img src="data:image/png;base64,{logo}" alt="SiliconCompiler logo"
167
+ class="logo-image" height="61">
168
+ <div class="logo-text">
169
+ <p class="design-text">{design}</p>
170
+ <p class="dashboard-text">dashboard</p>
171
+ </div>
172
+ </div>
173
+ </body>
174
+ '''
175
+ )
176
+
177
+
178
+ def job_selector():
179
+ job = streamlit.selectbox(
180
+ 'pick a job',
181
+ state.get_chips(),
182
+ label_visibility='collapsed')
183
+
184
+ if state.set_key(state.SELECTED_JOB, job):
185
+ # Job changed, so need to run
186
+ state.set_key(state.APP_RERUN, "Job")
187
+
188
+
189
+ def setup_page():
190
+ streamlit.set_page_config(
191
+ page_title=f'{state.get_chip().design} dashboard',
192
+ page_icon=Image.open(SC_LOGO_PATH),
193
+ layout="wide",
194
+ menu_items=SC_MENU)
195
+
196
+
197
+ def file_viewer(chip, path, header_col_width=0.89):
198
+ if not path:
199
+ streamlit.error('Select a file')
200
+ return
201
+
202
+ if not os.path.isfile(path):
203
+ streamlit.error(f'{path} is not a file')
204
+ return
205
+
206
+ # Detect file type
207
+ relative_path = os.path.relpath(path, chip.getworkdir())
208
+ filename = os.path.basename(path)
209
+ file_extension = utils.get_file_ext(path)
210
+
211
+ # Build streamlit module
212
+ header_col, download_col = \
213
+ streamlit.columns([header_col_width, 1 - header_col_width], gap='small')
214
+
215
+ with header_col:
216
+ streamlit.header(relative_path)
217
+
218
+ with download_col:
219
+ streamlit.markdown(' ') # aligns download button with title
220
+ streamlit.download_button(
221
+ label="Download",
222
+ data=path,
223
+ file_name=filename,
224
+ use_container_width=True)
225
+
226
+ try:
227
+ if file_extension in ('jpg', 'jpeg', 'png'):
228
+ # Data is an image
229
+ streamlit.image(path)
230
+ elif file_extension == 'json':
231
+ # Data is a json file
232
+ data = json.loads(file_utils.read_file(path, None))
233
+ expand_keys = report.get_total_manifest_key_count(data) < \
234
+ state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
235
+ if not expand_keys:
236
+ # Open two levels
237
+ expand_keys = 2
238
+ streamlit.json(data, expanded=expand_keys)
239
+ else:
240
+ file_data = file_utils.read_file(path, None).splitlines()
241
+ max_pages = len(file_data)
242
+ page_size = state.get_key(state.MAX_FILE_LINES_TO_SHOW)
243
+
244
+ file_section = streamlit.container()
245
+
246
+ page = sac.pagination(
247
+ align='center',
248
+ jump=True,
249
+ show_total=True,
250
+ page_size=page_size,
251
+ total=max_pages,
252
+ disabled=max_pages < state.get_key(state.MAX_FILE_LINES_TO_SHOW))
253
+
254
+ start_idx = (page - 1) * state.get_key(state.MAX_FILE_LINES_TO_SHOW)
255
+ end_idx = start_idx + state.get_key(state.MAX_FILE_LINES_TO_SHOW)
256
+ file_show = file_data[start_idx:end_idx]
257
+ with file_section:
258
+ # Assume file is text
259
+ streamlit.code(
260
+ "\n".join(file_show),
261
+ language=file_utils.get_file_type(file_extension),
262
+ line_numbers=True)
263
+ except Exception as e:
264
+ streamlit.markdown(f'Error occurred reading file: {e}')
265
+
266
+
267
+ def manifest_viewer(
268
+ chip,
269
+ header_col_width=0.70):
270
+ """
271
+ Displays the manifest and a way to search through the manifest.
272
+
273
+ Args:
274
+ chip (Chip) : Chip object
275
+ header_col_width (float) : A number between 0 and 1 which is the maximum
276
+ percentage of the width of the screen given to the header. The rest
277
+ is given to the settings and download buttons.
278
+ """
279
+ end_column_widths = (1 - header_col_width) / 2
280
+ header_col, settings_col, download_col = \
281
+ streamlit.columns(
282
+ [header_col_width, end_column_widths, end_column_widths],
283
+ gap='small')
284
+ with header_col:
285
+ streamlit.header('Manifest')
286
+
287
+ with settings_col:
288
+ streamlit.markdown(' ') # aligns with title
289
+ with streamlit.popover("Settings", use_container_width=True):
290
+ if streamlit.checkbox(
291
+ 'Raw manifest',
292
+ help='Click here to see the manifest before it was made more readable'):
293
+ manifest_to_show = chip.schema.cfg
294
+ else:
295
+ manifest_to_show = report.make_manifest(chip)
296
+
297
+ if streamlit.checkbox(
298
+ 'Hide empty values',
299
+ help='Hide empty keypaths',
300
+ value=True):
301
+ manifest_to_show = report.search_manifest(
302
+ manifest_to_show,
303
+ value_search='*')
304
+
305
+ search_key = streamlit.text_input('Search Keys', '', placeholder="Keys")
306
+ search_value = streamlit.text_input('Search Values', '', placeholder="Values")
307
+
308
+ manifest_to_show = report.search_manifest(
309
+ manifest_to_show,
310
+ key_search=search_key,
311
+ value_search=search_value)
312
+
313
+ with download_col:
314
+ streamlit.markdown(' ') # aligns with title
315
+ streamlit.download_button(
316
+ label='Download',
317
+ file_name='manifest.json',
318
+ data=json.dumps(chip.schema.cfg, indent=2),
319
+ mime="application/json",
320
+ use_container_width=True)
321
+
322
+ expand_keys = report.get_total_manifest_key_count(manifest_to_show) < \
323
+ state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
324
+ if not expand_keys:
325
+ # Open two levels
326
+ expand_keys = 2
327
+ streamlit.json(manifest_to_show, expanded=expand_keys)
328
+
329
+
330
+ def metrics_viewer(metric_dataframe, metric_to_metric_unit_map, header_col_width=0.7, height=None):
331
+ """
332
+ Displays multi-select check box to the users which allows them to select
333
+ which nodes and metrics to view in the dataframe.
334
+
335
+ Args:
336
+ metric_dataframe (Pandas.DataFrame) : Contains the metrics of all nodes.
337
+ metric_to_metric_unit_map (dict) : Maps the metric to the associated metric unit.
338
+ """
339
+
340
+ all_nodes = metric_dataframe.columns.tolist()
341
+ all_metrics = list(metric_to_metric_unit_map.values())
342
+
343
+ header_col, settings_col = streamlit.columns(
344
+ [header_col_width, 1 - header_col_width],
345
+ gap="large")
346
+ with header_col:
347
+ streamlit.header('Metrics')
348
+ with settings_col:
349
+ # Align to header
350
+ streamlit.markdown('')
351
+
352
+ with streamlit.popover("Settings", use_container_width=True):
353
+ transpose = streamlit.checkbox(
354
+ 'Transpose',
355
+ help='Transpose the metrics table')
356
+
357
+ display_nodes = streamlit.multiselect('Pick nodes to include', all_nodes, [])
358
+ display_metrics = streamlit.multiselect('Pick metrics to include?', all_metrics, [])
359
+
360
+ # Filter data
361
+ if not display_nodes:
362
+ display_nodes = all_nodes
363
+
364
+ if not display_metrics:
365
+ display_metrics = all_metrics
366
+
367
+ dataframe_nodes = list(display_nodes)
368
+ dataframe_metrics = []
369
+ for metric in metric_dataframe.index.tolist():
370
+ if metric_to_metric_unit_map[metric] in display_metrics:
371
+ dataframe_metrics.append(metric)
372
+
373
+ metric_dataframe = metric_dataframe.loc[dataframe_metrics, dataframe_nodes]
374
+ if transpose:
375
+ metric_dataframe = metric_dataframe.transpose()
376
+
377
+ # TODO By July 2024, Streamlit will let catch click events on the dataframe
378
+ streamlit.dataframe(metric_dataframe, use_container_width=True, height=height)
379
+
380
+
381
+ def node_image_viewer(chip, step, index):
382
+ exts = ('png', 'jpg', 'jpeg')
383
+ images = []
384
+ for path, _, files in report.get_files(chip, step, index):
385
+ images.extend([os.path.join(path, f) for f in files if utils.get_file_ext(f) in exts])
386
+
387
+ if not images:
388
+ streamlit.markdown("No images to show")
389
+
390
+ work_dir = chip.getworkdir(step=step, index=index)
391
+
392
+ columns = streamlit.slider(
393
+ "Image columns",
394
+ min_value=1,
395
+ max_value=min(len(images), 10),
396
+ value=min(len(images), 4),
397
+ disabled=len(images) < 2)
398
+
399
+ column = 0
400
+ for image in sorted(images):
401
+ if column == 0:
402
+ cols = streamlit.columns(columns)
403
+
404
+ with cols[column]:
405
+ streamlit.image(
406
+ image,
407
+ caption=os.path.relpath(image, work_dir),
408
+ use_column_width=True)
409
+
410
+ column += 1
411
+ if column == columns:
412
+ column = 0
413
+
414
+
415
+ def node_file_tree_viewer(chip, step, index):
416
+ logs_and_reports = file_utils.convert_filepaths_to_select_tree(
417
+ report.get_files(chip, step, index))
418
+
419
+ if not logs_and_reports:
420
+ streamlit.markdown("No files to show")
421
+
422
+ lookup = {}
423
+ tree_items = []
424
+
425
+ file_metrics = report.get_metrics_source(chip, step, index)
426
+ work_dir = chip.getworkdir(step=step, index=index)
427
+
428
+ def make_item(file):
429
+ lookup[file['value']] = file['label']
430
+ item = sac.TreeItem(
431
+ file['value'],
432
+ icon=file_utils.get_file_icon(file['value']),
433
+ tag=[],
434
+ children=[])
435
+
436
+ check_file = os.path.relpath(file['value'], work_dir)
437
+ if check_file in file_metrics:
438
+ for metric in file_metrics[check_file]:
439
+ if len(item.tag) < 5:
440
+ item.tag.append(sac.Tag(metric, color='green'))
441
+ else:
442
+ item.tag.append(sac.Tag('metrics...', color='geekblue'))
443
+ break
444
+ item.tooltip = "metrics: " + ", ".join(file_metrics[check_file])
445
+
446
+ if 'children' in file:
447
+ item.icon = 'folder'
448
+ for child in file['children']:
449
+ item.children.append(make_item(child))
450
+
451
+ return item
452
+
453
+ for file in logs_and_reports:
454
+ tree_items.append(make_item(file))
455
+
456
+ def format_label(value):
457
+ return lookup[value]
458
+
459
+ selected = sac.tree(
460
+ items=tree_items,
461
+ format_func=format_label,
462
+ size='md',
463
+ icon='table',
464
+ open_all=True)
465
+
466
+ if selected and os.path.isfile(selected):
467
+ state.set_key(state.SELECTED_FILE, selected)
468
+
469
+
470
+ def node_viewer(chip, step, index, metric_dataframe, height=None):
471
+ metrics_col, records_col, logs_and_reports_col = streamlit.columns(3, gap='small')
472
+
473
+ node_name = f'{step}{index}'
474
+
475
+ with metrics_col:
476
+ streamlit.subheader(f'{node_name} metrics')
477
+ if node_name in metric_dataframe:
478
+ streamlit.dataframe(
479
+ metric_dataframe[node_name].dropna(),
480
+ use_container_width=True,
481
+ height=height)
482
+ with records_col:
483
+ streamlit.subheader(f'{step}{index} details')
484
+ nodes = {}
485
+ nodes[step + index] = report.get_flowgraph_nodes(chip, step, index)
486
+ streamlit.dataframe(
487
+ pandas.DataFrame.from_dict(nodes),
488
+ use_container_width=True,
489
+ height=height)
490
+ with logs_and_reports_col:
491
+ streamlit.subheader(f'{step}{index} files')
492
+ node_file_tree_viewer(chip, step, index)
493
+
494
+
495
+ def flowgraph_viewer(chip):
496
+ '''
497
+ This function creates, displays, and returns the selected node of the flowgraph.
498
+
499
+ Args:
500
+ chip (Chip) : The chip object that contains the schema read from.
501
+ '''
502
+
503
+ nodes, edges = flowgraph.get_nodes_and_edges(chip)
504
+ if state.set_key(state.SELECTED_FLOWGRAPH_NODE, agraph(
505
+ nodes=nodes,
506
+ edges=edges,
507
+ config=flowgraph.get_graph_config())):
508
+ if state.get_key(state.SELECTED_FLOWGRAPH_NODE):
509
+ state.set_key(state.NODE_SOURCE, "flowgraph")
510
+
511
+
512
+ def node_selector(nodes):
513
+ """
514
+ Displays selectbox for nodes to show in the node information panel. Since
515
+ both the flowgraph and selectbox show which node's information is
516
+ displayed, the one clicked more recently will be displayed.
517
+
518
+ Args:
519
+ nodes (list) : Contains the metrics of all nodes.
520
+ """
521
+ prev_node = state.get_selected_node()
522
+
523
+ with streamlit.popover("Select Node", use_container_width=True):
524
+ # Preselect node
525
+ idx = 0
526
+ if prev_node:
527
+ idx = nodes.index(prev_node)
528
+ if state.set_key(
529
+ state.SELECTED_SELECTOR_NODE,
530
+ streamlit.selectbox(
531
+ 'Pick a node to inspect',
532
+ nodes,
533
+ index=idx)):
534
+ state.set_key(state.NODE_SOURCE, "selector")
@@ -0,0 +1,114 @@
1
+ from siliconcompiler.report import report
2
+ from siliconcompiler.flowgraph import _get_flowgraph_exit_nodes, _get_flowgraph_entry_nodes
3
+ from siliconcompiler.tools._common import get_tool_task
4
+ from siliconcompiler import NodeStatus
5
+
6
+ from streamlit_agraph import Node, Edge, Config
7
+
8
+
9
+ # for flowgraph
10
+ NODE_COLORS = {
11
+ NodeStatus.SUCCESS: '#70db70', # green
12
+
13
+ NodeStatus.SKIPPED: '#ffc299', # orange
14
+
15
+ NodeStatus.PENDING: '#6699ff', # blue
16
+ NodeStatus.QUEUED: '#6699ff', # blue
17
+
18
+ NodeStatus.RUNNING: '#ffff4d', # yellow
19
+
20
+ NodeStatus.ERROR: '#ff1a1a', # red
21
+ NodeStatus.TIMEOUT: '#ff1a1a', # red
22
+
23
+ "Unknown": '#6699ff', # blue
24
+ }
25
+
26
+
27
+ def get_nodes_and_edges(chip):
28
+ """
29
+ Returns the nodes and edges required to make a streamlit_agraph.
30
+
31
+ Args:
32
+ chip (Chip) : The chip object that contains the schema read from.
33
+ """
34
+ nodes = []
35
+ edges = []
36
+
37
+ default_node_border_width = 1
38
+ successful_path_node_width = 3
39
+ default_edge_width = 3
40
+ successful_path_edge_width = 5
41
+
42
+ node_dependencies = report.get_flowgraph_edges(chip)
43
+ successful_path = report.get_flowgraph_path(chip)
44
+
45
+ entry_exit_nodes = _get_flowgraph_entry_nodes(chip, chip.get('option', 'flow')) + \
46
+ _get_flowgraph_exit_nodes(chip, chip.get('option', 'flow'))
47
+
48
+ for step, index in node_dependencies:
49
+ # Build node
50
+ node_border_width = default_node_border_width
51
+ if (step, index) in entry_exit_nodes:
52
+ node_border_width = successful_path_node_width
53
+
54
+ node_status = chip.get('record', 'status', step=step, index=index)
55
+ if node_status not in NODE_COLORS:
56
+ node_status = "Unknown"
57
+ node_color = NODE_COLORS[node_status]
58
+
59
+ tool, task = get_tool_task(chip, step, index)
60
+ node_name = f'{step}{index}'
61
+ label = node_name + "\n" + tool + "/" + task
62
+ if tool == 'builtin':
63
+ label = node_name + "\n" + tool
64
+
65
+ nodes.append(Node(
66
+ id=node_name,
67
+ label=label,
68
+ color=node_color,
69
+ opacity=1,
70
+ borderWidth=node_border_width,
71
+ shape='oval',
72
+ fixed=True))
73
+
74
+ # Build edges
75
+ path_taken = chip.get('record', 'inputnode', step=step, index=index)
76
+ all_edges = set([*node_dependencies[step, index], *path_taken])
77
+ for source_step, source_index in all_edges:
78
+ edge_width = default_edge_width
79
+ if (step, index) in successful_path and \
80
+ (source_step, source_index) in successful_path:
81
+ edge_width = successful_path_edge_width
82
+
83
+ dashes = False
84
+ color = 'black'
85
+ if (source_step, source_index) not in path_taken:
86
+ color = 'gray'
87
+ dashes = True
88
+ elif node_status != NodeStatus.SUCCESS:
89
+ color = 'gray'
90
+ elif NodeStatus.is_waiting(node_status) or NodeStatus.is_running(node_status):
91
+ color = 'blue'
92
+ dashes = True
93
+
94
+ edges.append(Edge(
95
+ source=f'{source_step}{source_index}',
96
+ target=node_name,
97
+ dir='up',
98
+ width=edge_width,
99
+ color=color,
100
+ dashes=dashes))
101
+
102
+ return nodes, edges
103
+
104
+
105
+ def get_graph_config():
106
+ return Config(
107
+ width='100%',
108
+ height=1000,
109
+ directed=True,
110
+ physics=False,
111
+ hierarchical=True,
112
+ nodeSpacing=150,
113
+ levelSeparation=100,
114
+ sortMethod='directed')