siliconcompiler 0.28.2__py3-none-any.whl → 0.28.4__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 +6 -2
- siliconcompiler/apps/sc_install.py +61 -13
- siliconcompiler/apps/sc_remote.py +1 -1
- siliconcompiler/core.py +132 -68
- siliconcompiler/fpgas/vpr_example.py +8 -0
- siliconcompiler/package.py +3 -2
- siliconcompiler/remote/client.py +41 -10
- siliconcompiler/report/__init__.py +1 -1
- siliconcompiler/report/{streamlit_report.py → dashboard/__init__.py} +56 -10
- siliconcompiler/report/dashboard/components/__init__.py +546 -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 +96 -0
- siliconcompiler/report/dashboard/layouts/vertical_flowgraph_node_tab.py +117 -0
- siliconcompiler/report/dashboard/layouts/vertical_flowgraph_sac_tabs.py +110 -0
- siliconcompiler/report/dashboard/state.py +217 -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/report/summary_table.py +1 -2
- siliconcompiler/report/utils.py +1 -2
- siliconcompiler/scheduler/__init__.py +45 -6
- siliconcompiler/schema/schema_obj.py +4 -2
- siliconcompiler/sphinx_ext/dynamicgen.py +6 -0
- siliconcompiler/tools/_common/__init__.py +44 -6
- siliconcompiler/tools/_common/asic.py +79 -23
- siliconcompiler/tools/genfasm/genfasm.py +7 -0
- siliconcompiler/tools/ghdl/convert.py +7 -0
- siliconcompiler/tools/klayout/convert_drc_db.py +60 -0
- siliconcompiler/tools/klayout/drc.py +156 -0
- siliconcompiler/tools/klayout/export.py +2 -0
- siliconcompiler/tools/klayout/klayout.py +0 -1
- siliconcompiler/tools/klayout/klayout_convert_drc_db.py +182 -0
- siliconcompiler/tools/klayout/operations.py +2 -0
- siliconcompiler/tools/klayout/screenshot.py +2 -0
- siliconcompiler/tools/klayout/show.py +4 -4
- siliconcompiler/tools/magic/drc.py +21 -0
- siliconcompiler/tools/magic/extspice.py +21 -0
- siliconcompiler/tools/magic/magic.py +29 -0
- siliconcompiler/tools/magic/sc_drc.tcl +2 -12
- siliconcompiler/tools/magic/sc_extspice.tcl +3 -15
- siliconcompiler/tools/openroad/floorplan.py +5 -0
- siliconcompiler/tools/openroad/openroad.py +56 -5
- siliconcompiler/tools/openroad/scripts/sc_apr.tcl +15 -0
- siliconcompiler/tools/openroad/scripts/sc_cts.tcl +18 -13
- siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +61 -10
- siliconcompiler/tools/openroad/scripts/sc_metrics.tcl +10 -0
- siliconcompiler/tools/openroad/scripts/sc_procs.tcl +31 -1
- siliconcompiler/tools/openroad/scripts/sc_route.tcl +8 -2
- siliconcompiler/tools/openroad/scripts/sc_screenshot.tcl +0 -5
- siliconcompiler/tools/openroad/scripts/sc_write_images.tcl +36 -6
- siliconcompiler/tools/surelog/__init__.py +12 -0
- siliconcompiler/tools/verilator/compile.py +27 -0
- siliconcompiler/tools/verilator/verilator.py +9 -0
- siliconcompiler/tools/vpr/vpr.py +18 -0
- siliconcompiler/tools/yosys/{syn_asic_fpga_shared.tcl → procs.tcl} +23 -0
- siliconcompiler/tools/yosys/sc_screenshot.tcl +104 -0
- siliconcompiler/tools/yosys/sc_syn.tcl +7 -9
- siliconcompiler/tools/yosys/screenshot.py +153 -0
- siliconcompiler/tools/yosys/syn_asic.py +3 -0
- siliconcompiler/tools/yosys/syn_asic.tcl +1 -3
- siliconcompiler/tools/yosys/syn_fpga.tcl +3 -2
- siliconcompiler/toolscripts/_tools.json +10 -5
- 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/utils/__init__.py +30 -1
- siliconcompiler/utils/showtools.py +4 -0
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/METADATA +22 -8
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/RECORD +116 -67
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/WHEEL +1 -1
- siliconcompiler/report/streamlit_viewer.py +0 -944
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/LICENSE +0 -0
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/top_level.txt +0 -0
|
@@ -10,12 +10,24 @@ import multiprocessing
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import atexit
|
|
12
12
|
import shutil
|
|
13
|
+
import fasteners
|
|
14
|
+
import signal
|
|
15
|
+
import socketserver
|
|
16
|
+
|
|
17
|
+
from siliconcompiler.report.dashboard import utils
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class Dashboard():
|
|
16
21
|
__port = 8501
|
|
17
22
|
|
|
23
|
+
@staticmethod
|
|
24
|
+
def __signal_handler(signal, frame):
|
|
25
|
+
# used to avoid issues during shutdown
|
|
26
|
+
pass
|
|
27
|
+
|
|
18
28
|
def __init__(self, chip, port=None, graph_chips=None):
|
|
29
|
+
if not port:
|
|
30
|
+
port = Dashboard.get_next_port()
|
|
19
31
|
if not port:
|
|
20
32
|
port = Dashboard.__port
|
|
21
33
|
|
|
@@ -24,16 +36,18 @@ class Dashboard():
|
|
|
24
36
|
self.__directory = tempfile.mkdtemp(prefix='sc_dashboard_',
|
|
25
37
|
suffix=f'_{self.__chip.design}')
|
|
26
38
|
self.__manifest = os.path.join(self.__directory, 'manifest.json')
|
|
39
|
+
self.__manifest_lock = os.path.join(self.__directory, 'manifest.lock')
|
|
27
40
|
self.__port = port
|
|
28
41
|
dirname = os.path.dirname(__file__)
|
|
29
|
-
self.__streamlit_file = os.path.join(dirname, '
|
|
42
|
+
self.__streamlit_file = os.path.join(dirname, 'viewer.py')
|
|
30
43
|
|
|
31
44
|
self.__streamlit_args = [
|
|
32
45
|
("browser.gatherUsageStats", False),
|
|
33
46
|
("browser.serverPort", self.__port),
|
|
34
47
|
("logger.level", 'error'),
|
|
35
48
|
("runner.fastReruns", True),
|
|
36
|
-
("server.port", self.__port)
|
|
49
|
+
("server.port", self.__port),
|
|
50
|
+
("client.toolbarMode", "viewer")
|
|
37
51
|
]
|
|
38
52
|
|
|
39
53
|
# pass in a json object called __graph_chips
|
|
@@ -42,20 +56,33 @@ class Dashboard():
|
|
|
42
56
|
|
|
43
57
|
# use of list is to preserve order
|
|
44
58
|
self.__graph_chips = []
|
|
45
|
-
|
|
59
|
+
graph_chips_config = []
|
|
46
60
|
if graph_chips:
|
|
47
61
|
for chip_object_and_name in graph_chips:
|
|
48
62
|
chip_file_path = \
|
|
49
63
|
os.path.join(self.__directory,
|
|
50
64
|
f"{chip_object_and_name['name']}.json")
|
|
51
|
-
self.__graph_chips.append({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
self.__graph_chips.append({
|
|
66
|
+
'chip': chip_object_and_name['chip'],
|
|
67
|
+
'name': chip_file_path
|
|
68
|
+
})
|
|
69
|
+
graph_chips_config.append({
|
|
70
|
+
"path": chip_file_path,
|
|
71
|
+
"cwd": utils.get_chip_cwd(
|
|
72
|
+
chip_object_and_name['chip'],
|
|
73
|
+
chip_object_and_name['cfg_path'])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
self.__config = {
|
|
77
|
+
"manifest": self.__manifest,
|
|
78
|
+
"lock": self.__manifest_lock,
|
|
79
|
+
"graph_chips": graph_chips_config
|
|
80
|
+
}
|
|
57
81
|
|
|
58
82
|
self.__sleep_time = 0.5
|
|
83
|
+
self.__signal_handler = None
|
|
84
|
+
|
|
85
|
+
self.__lock = fasteners.InterProcessLock(self.__manifest_lock)
|
|
59
86
|
|
|
60
87
|
atexit.register(self.__cleanup)
|
|
61
88
|
|
|
@@ -70,10 +97,19 @@ class Dashboard():
|
|
|
70
97
|
self.__dashboard = multiprocessing.Process(
|
|
71
98
|
target=self._run_streamlit_bootstrap)
|
|
72
99
|
|
|
100
|
+
self.__signal_handler = signal.signal(signal.SIGINT, Dashboard.__signal_handler)
|
|
101
|
+
|
|
73
102
|
self.__dashboard.start()
|
|
74
103
|
|
|
75
104
|
def update_manifest(self):
|
|
76
|
-
self.
|
|
105
|
+
if not self.__manifest:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
new_file = f"{self.__manifest}.new.json"
|
|
109
|
+
self.__chip.write_manifest(new_file)
|
|
110
|
+
|
|
111
|
+
with self.__lock:
|
|
112
|
+
shutil.move(new_file, self.__manifest)
|
|
77
113
|
|
|
78
114
|
def update_graph_manifests(self):
|
|
79
115
|
for chip_object_and_name in self.__graph_chips:
|
|
@@ -103,8 +139,12 @@ class Dashboard():
|
|
|
103
139
|
self.__dashboard.terminate()
|
|
104
140
|
self._sleep()
|
|
105
141
|
|
|
142
|
+
if self.__signal_handler:
|
|
143
|
+
signal.signal(signal.SIGINT, self.__signal_handler)
|
|
144
|
+
|
|
106
145
|
self.__dashboard = None
|
|
107
146
|
self.__manifest = None
|
|
147
|
+
self.__signal_handler = None
|
|
108
148
|
|
|
109
149
|
def wait(self):
|
|
110
150
|
self.__dashboard.join()
|
|
@@ -135,3 +175,9 @@ class Dashboard():
|
|
|
135
175
|
|
|
136
176
|
if os.path.exists(self.__directory):
|
|
137
177
|
shutil.rmtree(self.__directory)
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def get_next_port():
|
|
181
|
+
with socketserver.TCPServer(("localhost", 0), None) as s:
|
|
182
|
+
return s.server_address[1]
|
|
183
|
+
return None
|
|
@@ -0,0 +1,546 @@
|
|
|
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, page_key=None, 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
|
+
if page_key:
|
|
247
|
+
if state.get_key(page_key) is None:
|
|
248
|
+
state.set_key(page_key, 1)
|
|
249
|
+
index = state.get_key(page_key)
|
|
250
|
+
else:
|
|
251
|
+
index = 1
|
|
252
|
+
|
|
253
|
+
page = sac.pagination(
|
|
254
|
+
align='center',
|
|
255
|
+
index=index,
|
|
256
|
+
jump=True,
|
|
257
|
+
show_total=True,
|
|
258
|
+
page_size=page_size,
|
|
259
|
+
total=max_pages,
|
|
260
|
+
disabled=max_pages < state.get_key(state.MAX_FILE_LINES_TO_SHOW))
|
|
261
|
+
|
|
262
|
+
if page_key:
|
|
263
|
+
state.set_key(page_key, page)
|
|
264
|
+
|
|
265
|
+
start_idx = (page - 1) * state.get_key(state.MAX_FILE_LINES_TO_SHOW)
|
|
266
|
+
end_idx = start_idx + state.get_key(state.MAX_FILE_LINES_TO_SHOW)
|
|
267
|
+
file_show = file_data[start_idx:end_idx]
|
|
268
|
+
with file_section:
|
|
269
|
+
# Assume file is text
|
|
270
|
+
streamlit.code(
|
|
271
|
+
"\n".join(file_show),
|
|
272
|
+
language=file_utils.get_file_type(file_extension),
|
|
273
|
+
line_numbers=True)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
streamlit.markdown(f'Error occurred reading file: {e}')
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def manifest_viewer(
|
|
279
|
+
chip,
|
|
280
|
+
header_col_width=0.70):
|
|
281
|
+
"""
|
|
282
|
+
Displays the manifest and a way to search through the manifest.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
chip (Chip) : Chip object
|
|
286
|
+
header_col_width (float) : A number between 0 and 1 which is the maximum
|
|
287
|
+
percentage of the width of the screen given to the header. The rest
|
|
288
|
+
is given to the settings and download buttons.
|
|
289
|
+
"""
|
|
290
|
+
end_column_widths = (1 - header_col_width) / 2
|
|
291
|
+
header_col, settings_col, download_col = \
|
|
292
|
+
streamlit.columns(
|
|
293
|
+
[header_col_width, end_column_widths, end_column_widths],
|
|
294
|
+
gap='small')
|
|
295
|
+
with header_col:
|
|
296
|
+
streamlit.header('Manifest')
|
|
297
|
+
|
|
298
|
+
with settings_col:
|
|
299
|
+
streamlit.markdown(' ') # aligns with title
|
|
300
|
+
with streamlit.popover("Settings", use_container_width=True):
|
|
301
|
+
if streamlit.checkbox(
|
|
302
|
+
'Raw manifest',
|
|
303
|
+
help='Click here to see the manifest before it was made more readable'):
|
|
304
|
+
manifest_to_show = chip.schema.cfg
|
|
305
|
+
else:
|
|
306
|
+
manifest_to_show = report.make_manifest(chip)
|
|
307
|
+
|
|
308
|
+
if streamlit.checkbox(
|
|
309
|
+
'Hide empty values',
|
|
310
|
+
help='Hide empty keypaths',
|
|
311
|
+
value=True):
|
|
312
|
+
manifest_to_show = report.search_manifest(
|
|
313
|
+
manifest_to_show,
|
|
314
|
+
value_search='*')
|
|
315
|
+
|
|
316
|
+
search_key = streamlit.text_input('Search Keys', '', placeholder="Keys")
|
|
317
|
+
search_value = streamlit.text_input('Search Values', '', placeholder="Values")
|
|
318
|
+
|
|
319
|
+
manifest_to_show = report.search_manifest(
|
|
320
|
+
manifest_to_show,
|
|
321
|
+
key_search=search_key,
|
|
322
|
+
value_search=search_value)
|
|
323
|
+
|
|
324
|
+
with download_col:
|
|
325
|
+
streamlit.markdown(' ') # aligns with title
|
|
326
|
+
streamlit.download_button(
|
|
327
|
+
label='Download',
|
|
328
|
+
file_name='manifest.json',
|
|
329
|
+
data=json.dumps(chip.schema.cfg, indent=2),
|
|
330
|
+
mime="application/json",
|
|
331
|
+
use_container_width=True)
|
|
332
|
+
|
|
333
|
+
expand_keys = report.get_total_manifest_key_count(manifest_to_show) < \
|
|
334
|
+
state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
|
|
335
|
+
if not expand_keys:
|
|
336
|
+
# Open two levels
|
|
337
|
+
expand_keys = 2
|
|
338
|
+
streamlit.json(manifest_to_show, expanded=expand_keys)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def metrics_viewer(metric_dataframe, metric_to_metric_unit_map, header_col_width=0.7, height=None):
|
|
342
|
+
"""
|
|
343
|
+
Displays multi-select check box to the users which allows them to select
|
|
344
|
+
which nodes and metrics to view in the dataframe.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
metric_dataframe (Pandas.DataFrame) : Contains the metrics of all nodes.
|
|
348
|
+
metric_to_metric_unit_map (dict) : Maps the metric to the associated metric unit.
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
all_nodes = metric_dataframe.columns.tolist()
|
|
352
|
+
all_metrics = list(metric_to_metric_unit_map.values())
|
|
353
|
+
|
|
354
|
+
header_col, settings_col = streamlit.columns(
|
|
355
|
+
[header_col_width, 1 - header_col_width],
|
|
356
|
+
gap="large")
|
|
357
|
+
with header_col:
|
|
358
|
+
streamlit.header('Metrics')
|
|
359
|
+
with settings_col:
|
|
360
|
+
# Align to header
|
|
361
|
+
streamlit.markdown('')
|
|
362
|
+
|
|
363
|
+
with streamlit.popover("Settings", use_container_width=True):
|
|
364
|
+
transpose = streamlit.checkbox(
|
|
365
|
+
'Transpose',
|
|
366
|
+
help='Transpose the metrics table')
|
|
367
|
+
|
|
368
|
+
display_nodes = streamlit.multiselect('Pick nodes to include', all_nodes, [])
|
|
369
|
+
display_metrics = streamlit.multiselect('Pick metrics to include?', all_metrics, [])
|
|
370
|
+
|
|
371
|
+
# Filter data
|
|
372
|
+
if not display_nodes:
|
|
373
|
+
display_nodes = all_nodes
|
|
374
|
+
|
|
375
|
+
if not display_metrics:
|
|
376
|
+
display_metrics = all_metrics
|
|
377
|
+
|
|
378
|
+
dataframe_nodes = list(display_nodes)
|
|
379
|
+
dataframe_metrics = []
|
|
380
|
+
for metric in metric_dataframe.index.tolist():
|
|
381
|
+
if metric_to_metric_unit_map[metric] in display_metrics:
|
|
382
|
+
dataframe_metrics.append(metric)
|
|
383
|
+
|
|
384
|
+
metric_dataframe = metric_dataframe.loc[dataframe_metrics, dataframe_nodes]
|
|
385
|
+
if transpose:
|
|
386
|
+
metric_dataframe = metric_dataframe.transpose()
|
|
387
|
+
|
|
388
|
+
# TODO By July 2024, Streamlit will let catch click events on the dataframe
|
|
389
|
+
streamlit.dataframe(metric_dataframe, use_container_width=True, height=height)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def node_image_viewer(chip, step, index):
|
|
393
|
+
exts = ('png', 'jpg', 'jpeg')
|
|
394
|
+
images = []
|
|
395
|
+
for path, _, files in report.get_files(chip, step, index):
|
|
396
|
+
images.extend([os.path.join(path, f) for f in files if utils.get_file_ext(f) in exts])
|
|
397
|
+
|
|
398
|
+
if not images:
|
|
399
|
+
streamlit.markdown("No images to show")
|
|
400
|
+
|
|
401
|
+
work_dir = chip.getworkdir(step=step, index=index)
|
|
402
|
+
|
|
403
|
+
columns = streamlit.slider(
|
|
404
|
+
"Image columns",
|
|
405
|
+
min_value=1,
|
|
406
|
+
max_value=min(len(images), 10),
|
|
407
|
+
value=min(len(images), 4),
|
|
408
|
+
disabled=len(images) < 2)
|
|
409
|
+
|
|
410
|
+
column = 0
|
|
411
|
+
for image in sorted(images):
|
|
412
|
+
if column == 0:
|
|
413
|
+
cols = streamlit.columns(columns)
|
|
414
|
+
|
|
415
|
+
with cols[column]:
|
|
416
|
+
streamlit.image(
|
|
417
|
+
image,
|
|
418
|
+
caption=os.path.relpath(image, work_dir),
|
|
419
|
+
use_column_width=True)
|
|
420
|
+
|
|
421
|
+
column += 1
|
|
422
|
+
if column == columns:
|
|
423
|
+
column = 0
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def node_file_tree_viewer(chip, step, index):
|
|
427
|
+
logs_and_reports = file_utils.convert_filepaths_to_select_tree(
|
|
428
|
+
report.get_files(chip, step, index))
|
|
429
|
+
|
|
430
|
+
if not logs_and_reports:
|
|
431
|
+
streamlit.markdown("No files to show")
|
|
432
|
+
|
|
433
|
+
lookup = {}
|
|
434
|
+
tree_items = []
|
|
435
|
+
|
|
436
|
+
file_metrics = report.get_metrics_source(chip, step, index)
|
|
437
|
+
work_dir = chip.getworkdir(step=step, index=index)
|
|
438
|
+
|
|
439
|
+
def make_item(file):
|
|
440
|
+
lookup[file['value']] = file['label']
|
|
441
|
+
item = sac.TreeItem(
|
|
442
|
+
file['value'],
|
|
443
|
+
icon=file_utils.get_file_icon(file['value']),
|
|
444
|
+
tag=[],
|
|
445
|
+
children=[])
|
|
446
|
+
|
|
447
|
+
check_file = os.path.relpath(file['value'], work_dir)
|
|
448
|
+
if check_file in file_metrics:
|
|
449
|
+
for metric in file_metrics[check_file]:
|
|
450
|
+
if len(item.tag) < 5:
|
|
451
|
+
item.tag.append(sac.Tag(metric, color='green'))
|
|
452
|
+
else:
|
|
453
|
+
item.tag.append(sac.Tag('metrics...', color='geekblue'))
|
|
454
|
+
break
|
|
455
|
+
item.tooltip = "metrics: " + ", ".join(file_metrics[check_file])
|
|
456
|
+
|
|
457
|
+
if 'children' in file:
|
|
458
|
+
item.icon = 'folder'
|
|
459
|
+
for child in file['children']:
|
|
460
|
+
item.children.append(make_item(child))
|
|
461
|
+
|
|
462
|
+
return item
|
|
463
|
+
|
|
464
|
+
for file in logs_and_reports:
|
|
465
|
+
tree_items.append(make_item(file))
|
|
466
|
+
|
|
467
|
+
def format_label(value):
|
|
468
|
+
return lookup[value]
|
|
469
|
+
|
|
470
|
+
selected = sac.tree(
|
|
471
|
+
items=tree_items,
|
|
472
|
+
format_func=format_label,
|
|
473
|
+
size='md',
|
|
474
|
+
icon='table',
|
|
475
|
+
open_all=True)
|
|
476
|
+
|
|
477
|
+
if selected and os.path.isfile(selected):
|
|
478
|
+
state.set_key(state.SELECTED_FILE, selected)
|
|
479
|
+
state.set_key(state.SELECTED_FILE_PAGE, None)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def node_viewer(chip, step, index, metric_dataframe, height=None):
|
|
483
|
+
metrics_col, records_col, logs_and_reports_col = streamlit.columns(3, gap='small')
|
|
484
|
+
|
|
485
|
+
node_name = f'{step}{index}'
|
|
486
|
+
|
|
487
|
+
with metrics_col:
|
|
488
|
+
streamlit.subheader(f'{node_name} metrics')
|
|
489
|
+
if node_name in metric_dataframe:
|
|
490
|
+
streamlit.dataframe(
|
|
491
|
+
metric_dataframe[node_name].dropna(),
|
|
492
|
+
use_container_width=True,
|
|
493
|
+
height=height)
|
|
494
|
+
with records_col:
|
|
495
|
+
streamlit.subheader(f'{step}{index} details')
|
|
496
|
+
nodes = {}
|
|
497
|
+
nodes[step + index] = report.get_flowgraph_nodes(chip, step, index)
|
|
498
|
+
streamlit.dataframe(
|
|
499
|
+
pandas.DataFrame.from_dict(nodes),
|
|
500
|
+
use_container_width=True,
|
|
501
|
+
height=height)
|
|
502
|
+
with logs_and_reports_col:
|
|
503
|
+
streamlit.subheader(f'{step}{index} files')
|
|
504
|
+
node_file_tree_viewer(chip, step, index)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def flowgraph_viewer(chip):
|
|
508
|
+
'''
|
|
509
|
+
This function creates, displays, and returns the selected node of the flowgraph.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
chip (Chip) : The chip object that contains the schema read from.
|
|
513
|
+
'''
|
|
514
|
+
|
|
515
|
+
nodes, edges = flowgraph.get_nodes_and_edges(chip)
|
|
516
|
+
if state.set_key(state.SELECTED_FLOWGRAPH_NODE, agraph(
|
|
517
|
+
nodes=nodes,
|
|
518
|
+
edges=edges,
|
|
519
|
+
config=flowgraph.get_graph_config())):
|
|
520
|
+
if state.get_key(state.SELECTED_FLOWGRAPH_NODE):
|
|
521
|
+
state.set_key(state.NODE_SOURCE, "flowgraph")
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def node_selector(nodes):
|
|
525
|
+
"""
|
|
526
|
+
Displays selectbox for nodes to show in the node information panel. Since
|
|
527
|
+
both the flowgraph and selectbox show which node's information is
|
|
528
|
+
displayed, the one clicked more recently will be displayed.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
nodes (list) : Contains the metrics of all nodes.
|
|
532
|
+
"""
|
|
533
|
+
prev_node = state.get_selected_node()
|
|
534
|
+
|
|
535
|
+
with streamlit.popover("Select Node", use_container_width=True):
|
|
536
|
+
# Preselect node
|
|
537
|
+
idx = 0
|
|
538
|
+
if prev_node:
|
|
539
|
+
idx = nodes.index(prev_node)
|
|
540
|
+
if state.set_key(
|
|
541
|
+
state.SELECTED_SELECTOR_NODE,
|
|
542
|
+
streamlit.selectbox(
|
|
543
|
+
'Pick a node to inspect',
|
|
544
|
+
nodes,
|
|
545
|
+
index=idx)):
|
|
546
|
+
state.set_key(state.NODE_SOURCE, "selector")
|