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.
- siliconcompiler/_common.py +12 -0
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_dashboard.py +5 -1
- siliconcompiler/apps/sc_install.py +61 -13
- siliconcompiler/apps/sc_remote.py +1 -1
- siliconcompiler/core.py +39 -13
- siliconcompiler/remote/client.py +41 -10
- siliconcompiler/report/__init__.py +1 -1
- siliconcompiler/report/{streamlit_report.py → dashboard/__init__.py} +47 -10
- siliconcompiler/report/dashboard/components/__init__.py +534 -0
- siliconcompiler/report/dashboard/components/flowgraph.py +114 -0
- siliconcompiler/report/dashboard/components/graph.py +208 -0
- siliconcompiler/report/dashboard/layouts/__init__.py +20 -0
- siliconcompiler/report/dashboard/layouts/_common.py +43 -0
- siliconcompiler/report/dashboard/layouts/vertical_flowgraph.py +95 -0
- siliconcompiler/report/dashboard/layouts/vertical_flowgraph_node_tab.py +114 -0
- siliconcompiler/report/dashboard/layouts/vertical_flowgraph_sac_tabs.py +107 -0
- siliconcompiler/report/dashboard/state.py +215 -0
- siliconcompiler/report/dashboard/utils/__init__.py +73 -0
- siliconcompiler/report/dashboard/utils/file_utils.py +120 -0
- siliconcompiler/report/dashboard/viewer.py +36 -0
- siliconcompiler/report/report.py +22 -4
- siliconcompiler/scheduler/__init__.py +43 -6
- siliconcompiler/schema/schema_obj.py +4 -2
- siliconcompiler/tools/openroad/floorplan.py +5 -0
- siliconcompiler/tools/openroad/openroad.py +12 -3
- siliconcompiler/tools/openroad/scripts/sc_cts.tcl +18 -13
- siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +6 -1
- siliconcompiler/tools/openroad/scripts/sc_procs.tcl +28 -0
- siliconcompiler/toolscripts/_tools.json +8 -3
- siliconcompiler/toolscripts/rhel8/install-chisel.sh +26 -0
- siliconcompiler/toolscripts/rhel8/install-ghdl.sh +25 -0
- siliconcompiler/toolscripts/rhel8/install-icarus.sh +40 -0
- siliconcompiler/toolscripts/rhel8/install-klayout.sh +17 -0
- siliconcompiler/toolscripts/rhel8/install-magic.sh +26 -0
- siliconcompiler/toolscripts/rhel8/install-montage.sh +5 -0
- siliconcompiler/toolscripts/rhel8/install-netgen.sh +25 -0
- siliconcompiler/toolscripts/rhel8/install-openroad.sh +31 -0
- siliconcompiler/toolscripts/rhel8/install-slang.sh +31 -0
- siliconcompiler/toolscripts/rhel8/install-surelog.sh +32 -0
- siliconcompiler/toolscripts/rhel8/install-sv2v.sh +27 -0
- siliconcompiler/toolscripts/rhel8/install-verible.sh +24 -0
- siliconcompiler/toolscripts/rhel8/install-verilator.sh +40 -0
- siliconcompiler/toolscripts/rhel8/install-xyce.sh +64 -0
- siliconcompiler/toolscripts/rhel8/install-yosys.sh +23 -0
- siliconcompiler/toolscripts/rhel9/install-chisel.sh +26 -0
- siliconcompiler/toolscripts/rhel9/install-ghdl.sh +25 -0
- siliconcompiler/toolscripts/rhel9/install-icarus.sh +40 -0
- siliconcompiler/toolscripts/rhel9/install-klayout.sh +17 -0
- siliconcompiler/toolscripts/rhel9/install-magic.sh +26 -0
- siliconcompiler/toolscripts/rhel9/install-montage.sh +5 -0
- siliconcompiler/toolscripts/rhel9/install-netgen.sh +25 -0
- siliconcompiler/toolscripts/rhel9/install-slang.sh +31 -0
- siliconcompiler/toolscripts/rhel9/install-surelog.sh +32 -0
- siliconcompiler/toolscripts/rhel9/install-sv2v.sh +27 -0
- siliconcompiler/toolscripts/rhel9/install-verible.sh +24 -0
- siliconcompiler/toolscripts/rhel9/install-verilator.sh +40 -0
- siliconcompiler/toolscripts/rhel9/install-xdm.sh +43 -0
- siliconcompiler/toolscripts/rhel9/install-xyce.sh +64 -0
- siliconcompiler/toolscripts/rhel9/install-yosys.sh +23 -0
- siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +1 -1
- siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +40 -0
- siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -2
- siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +1 -1
- siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +40 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -2
- siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +1 -1
- siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +40 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -2
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/METADATA +7 -6
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/RECORD +76 -32
- siliconcompiler/report/streamlit_viewer.py +0 -944
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/LICENSE +0 -0
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/entry_points.txt +0 -0
- {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')
|