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.
Files changed (117) hide show
  1. siliconcompiler/_common.py +12 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/sc_dashboard.py +6 -2
  4. siliconcompiler/apps/sc_install.py +61 -13
  5. siliconcompiler/apps/sc_remote.py +1 -1
  6. siliconcompiler/core.py +132 -68
  7. siliconcompiler/fpgas/vpr_example.py +8 -0
  8. siliconcompiler/package.py +3 -2
  9. siliconcompiler/remote/client.py +41 -10
  10. siliconcompiler/report/__init__.py +1 -1
  11. siliconcompiler/report/{streamlit_report.py → dashboard/__init__.py} +56 -10
  12. siliconcompiler/report/dashboard/components/__init__.py +546 -0
  13. siliconcompiler/report/dashboard/components/flowgraph.py +114 -0
  14. siliconcompiler/report/dashboard/components/graph.py +208 -0
  15. siliconcompiler/report/dashboard/layouts/__init__.py +20 -0
  16. siliconcompiler/report/dashboard/layouts/_common.py +43 -0
  17. siliconcompiler/report/dashboard/layouts/vertical_flowgraph.py +96 -0
  18. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_node_tab.py +117 -0
  19. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_sac_tabs.py +110 -0
  20. siliconcompiler/report/dashboard/state.py +217 -0
  21. siliconcompiler/report/dashboard/utils/__init__.py +73 -0
  22. siliconcompiler/report/dashboard/utils/file_utils.py +120 -0
  23. siliconcompiler/report/dashboard/viewer.py +36 -0
  24. siliconcompiler/report/report.py +22 -4
  25. siliconcompiler/report/summary_table.py +1 -2
  26. siliconcompiler/report/utils.py +1 -2
  27. siliconcompiler/scheduler/__init__.py +45 -6
  28. siliconcompiler/schema/schema_obj.py +4 -2
  29. siliconcompiler/sphinx_ext/dynamicgen.py +6 -0
  30. siliconcompiler/tools/_common/__init__.py +44 -6
  31. siliconcompiler/tools/_common/asic.py +79 -23
  32. siliconcompiler/tools/genfasm/genfasm.py +7 -0
  33. siliconcompiler/tools/ghdl/convert.py +7 -0
  34. siliconcompiler/tools/klayout/convert_drc_db.py +60 -0
  35. siliconcompiler/tools/klayout/drc.py +156 -0
  36. siliconcompiler/tools/klayout/export.py +2 -0
  37. siliconcompiler/tools/klayout/klayout.py +0 -1
  38. siliconcompiler/tools/klayout/klayout_convert_drc_db.py +182 -0
  39. siliconcompiler/tools/klayout/operations.py +2 -0
  40. siliconcompiler/tools/klayout/screenshot.py +2 -0
  41. siliconcompiler/tools/klayout/show.py +4 -4
  42. siliconcompiler/tools/magic/drc.py +21 -0
  43. siliconcompiler/tools/magic/extspice.py +21 -0
  44. siliconcompiler/tools/magic/magic.py +29 -0
  45. siliconcompiler/tools/magic/sc_drc.tcl +2 -12
  46. siliconcompiler/tools/magic/sc_extspice.tcl +3 -15
  47. siliconcompiler/tools/openroad/floorplan.py +5 -0
  48. siliconcompiler/tools/openroad/openroad.py +56 -5
  49. siliconcompiler/tools/openroad/scripts/sc_apr.tcl +15 -0
  50. siliconcompiler/tools/openroad/scripts/sc_cts.tcl +18 -13
  51. siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +61 -10
  52. siliconcompiler/tools/openroad/scripts/sc_metrics.tcl +10 -0
  53. siliconcompiler/tools/openroad/scripts/sc_procs.tcl +31 -1
  54. siliconcompiler/tools/openroad/scripts/sc_route.tcl +8 -2
  55. siliconcompiler/tools/openroad/scripts/sc_screenshot.tcl +0 -5
  56. siliconcompiler/tools/openroad/scripts/sc_write_images.tcl +36 -6
  57. siliconcompiler/tools/surelog/__init__.py +12 -0
  58. siliconcompiler/tools/verilator/compile.py +27 -0
  59. siliconcompiler/tools/verilator/verilator.py +9 -0
  60. siliconcompiler/tools/vpr/vpr.py +18 -0
  61. siliconcompiler/tools/yosys/{syn_asic_fpga_shared.tcl → procs.tcl} +23 -0
  62. siliconcompiler/tools/yosys/sc_screenshot.tcl +104 -0
  63. siliconcompiler/tools/yosys/sc_syn.tcl +7 -9
  64. siliconcompiler/tools/yosys/screenshot.py +153 -0
  65. siliconcompiler/tools/yosys/syn_asic.py +3 -0
  66. siliconcompiler/tools/yosys/syn_asic.tcl +1 -3
  67. siliconcompiler/tools/yosys/syn_fpga.tcl +3 -2
  68. siliconcompiler/toolscripts/_tools.json +10 -5
  69. siliconcompiler/toolscripts/rhel8/install-chisel.sh +26 -0
  70. siliconcompiler/toolscripts/rhel8/install-ghdl.sh +25 -0
  71. siliconcompiler/toolscripts/rhel8/install-icarus.sh +40 -0
  72. siliconcompiler/toolscripts/rhel8/install-klayout.sh +17 -0
  73. siliconcompiler/toolscripts/rhel8/install-magic.sh +26 -0
  74. siliconcompiler/toolscripts/rhel8/install-montage.sh +5 -0
  75. siliconcompiler/toolscripts/rhel8/install-netgen.sh +25 -0
  76. siliconcompiler/toolscripts/rhel8/install-openroad.sh +31 -0
  77. siliconcompiler/toolscripts/rhel8/install-slang.sh +31 -0
  78. siliconcompiler/toolscripts/rhel8/install-surelog.sh +32 -0
  79. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +27 -0
  80. siliconcompiler/toolscripts/rhel8/install-verible.sh +24 -0
  81. siliconcompiler/toolscripts/rhel8/install-verilator.sh +40 -0
  82. siliconcompiler/toolscripts/rhel8/install-xyce.sh +64 -0
  83. siliconcompiler/toolscripts/rhel8/install-yosys.sh +23 -0
  84. siliconcompiler/toolscripts/rhel9/install-chisel.sh +26 -0
  85. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +25 -0
  86. siliconcompiler/toolscripts/rhel9/install-icarus.sh +40 -0
  87. siliconcompiler/toolscripts/rhel9/install-klayout.sh +17 -0
  88. siliconcompiler/toolscripts/rhel9/install-magic.sh +26 -0
  89. siliconcompiler/toolscripts/rhel9/install-montage.sh +5 -0
  90. siliconcompiler/toolscripts/rhel9/install-netgen.sh +25 -0
  91. siliconcompiler/toolscripts/rhel9/install-slang.sh +31 -0
  92. siliconcompiler/toolscripts/rhel9/install-surelog.sh +32 -0
  93. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +27 -0
  94. siliconcompiler/toolscripts/rhel9/install-verible.sh +24 -0
  95. siliconcompiler/toolscripts/rhel9/install-verilator.sh +40 -0
  96. siliconcompiler/toolscripts/rhel9/install-xdm.sh +43 -0
  97. siliconcompiler/toolscripts/rhel9/install-xyce.sh +64 -0
  98. siliconcompiler/toolscripts/rhel9/install-yosys.sh +23 -0
  99. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +1 -1
  100. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +40 -0
  101. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -2
  102. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +1 -1
  103. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +40 -0
  104. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -2
  105. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +1 -1
  106. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
  107. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +40 -0
  108. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -2
  109. siliconcompiler/utils/__init__.py +30 -1
  110. siliconcompiler/utils/showtools.py +4 -0
  111. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/METADATA +22 -8
  112. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/RECORD +116 -67
  113. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/WHEEL +1 -1
  114. siliconcompiler/report/streamlit_viewer.py +0 -944
  115. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/LICENSE +0 -0
  116. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/entry_points.txt +0 -0
  117. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.4.dist-info}/top_level.txt +0 -0
@@ -33,6 +33,18 @@ class NodeStatus():
33
33
  NodeStatus.PENDING,
34
34
  )
35
35
 
36
+ def is_success(status):
37
+ return status in (
38
+ NodeStatus.SUCCESS,
39
+ NodeStatus.SKIPPED
40
+ )
41
+
42
+ def is_error(status):
43
+ return status in (
44
+ NodeStatus.ERROR,
45
+ NodeStatus.TIMEOUT
46
+ )
47
+
36
48
 
37
49
  ###############################################################################
38
50
  # Package Customization classes
@@ -1,5 +1,5 @@
1
1
  # Version number following semver standard.
2
- version = '0.28.2'
2
+ version = '0.28.4'
3
3
 
4
4
  # Default server address for remote runs, if unspecified.
5
5
  default_server = 'https://server.siliconcompiler.com'
@@ -82,9 +82,13 @@ To include another chip object to compare to:
82
82
  raise ValueError(f'not a valid file path : {file_path}')
83
83
  graph_chip = siliconcompiler.core.Chip(design='')
84
84
  graph_chip.read_manifest(file_path)
85
- graph_chips.append({'chip': graph_chip, 'name': name})
85
+ graph_chips.append({
86
+ 'chip': graph_chip,
87
+ 'name': name,
88
+ 'cfg_path': os.path.abspath(file_path)
89
+ })
86
90
 
87
- chip._dashboard(wait=True, port=switches['port'], graph_chips=graph_chips)
91
+ chip.dashboard(wait=True, port=switches['port'], graph_chips=graph_chips)
88
92
 
89
93
  return 0
90
94
 
@@ -70,17 +70,46 @@ def show_tool(tool, script):
70
70
  print_header("end")
71
71
 
72
72
 
73
+ def _get_os_name():
74
+ machine_info = _get_machine_info()
75
+ system = machine_info.get('system', "").lower()
76
+ distro = machine_info.get('distro', "").lower()
77
+ osversion = machine_info.get('osversion', "").lower()
78
+ if system == 'linux':
79
+ if distro == 'ubuntu':
80
+ version, _ = osversion.split('.')
81
+ return f"{distro}{version}"
82
+ elif distro == 'rocky':
83
+ version, _ = osversion.split('.')
84
+ return f"rhel{version}"
85
+ elif distro == 'rhel':
86
+ version, _ = osversion.split('.')
87
+ return f"rhel{version}"
88
+ return None
89
+
90
+
91
+ def print_machine_info():
92
+ machine_info = _get_machine_info()
93
+ mapped_os = _get_os_name()
94
+
95
+ print("System: ", machine_info.get('system', None))
96
+ print("Distro: ", machine_info.get('distro', None))
97
+ print("Version: ", machine_info.get('osversion', None))
98
+ print("Mapped OS:", mapped_os)
99
+ print("Scripts: ", _get_tool_script_dir())
100
+
101
+
102
+ def _get_tool_script_dir():
103
+ return Path(siliconcompiler.__file__).parent / "toolscripts"
104
+
105
+
73
106
  def _get_tools_list():
74
- tools_root = Path(siliconcompiler.__file__).parent / "toolscripts"
107
+ tools_root = _get_tool_script_dir()
75
108
 
76
- machine_info = _get_machine_info()
77
109
  script_dir = None
78
- if machine_info['system'].lower() == 'linux':
79
- if machine_info['distro'].lower() == 'ubuntu':
80
- version, _ = machine_info['osversion'].split('.')
81
- script_dir = f"{machine_info['distro'].lower()}{version}"
82
- if script_dir:
83
- script_dir = tools_root / script_dir
110
+ os_dir = _get_os_name()
111
+ if os_dir:
112
+ script_dir = tools_root / os_dir
84
113
  if not script_dir.exists():
85
114
  script_dir = None
86
115
 
@@ -93,14 +122,20 @@ def _get_tools_list():
93
122
  return tools
94
123
 
95
124
 
96
- def _recommended_tool_groups():
97
- return {
125
+ def _recommended_tool_groups(tools):
126
+ groups = {
98
127
  "asic": {"surelog", "sv2v", "yosys", "openroad", "klayout"},
99
128
  "fpga": {"surelog", "sv2v", "yosys", "vpr"},
100
129
  "digital-simulation": {"verilator", "icarus"},
101
130
  "analog-simulation": {"xyce"}
102
131
  }
103
132
 
133
+ filter_groups = {}
134
+ for group, group_tools in groups.items():
135
+ if all([tool in tools for tool in group_tools]):
136
+ filter_groups[group] = group_tools
137
+ return filter_groups
138
+
104
139
 
105
140
  def main():
106
141
  progname = "sc-install"
@@ -125,6 +160,9 @@ To build tools in a different location:
125
160
 
126
161
  To show the install script:
127
162
  sc-install -show openroad
163
+
164
+ To system debugging information (this should only be used to debug):
165
+ sc-install -debug_machine
128
166
  -----------------------------------------------------------
129
167
  """
130
168
  parser = argparse.ArgumentParser(
@@ -141,11 +179,12 @@ To show the install script:
141
179
  choices=tool_choices,
142
180
  help="tool to install")
143
181
 
182
+ tool_groups = _recommended_tool_groups(tools)
144
183
  parser.add_argument(
145
184
  "-group",
146
185
  nargs="+",
147
- choices=_recommended_tool_groups().keys(),
148
- help="tool group to install")
186
+ choices=tool_groups.keys(),
187
+ help=f"tool group to install{' - not supported' if not tool_groups else ''}")
149
188
 
150
189
  parser.add_argument(
151
190
  "-prefix",
@@ -164,15 +203,24 @@ To show the install script:
164
203
  action="store_true",
165
204
  help="Show the install script and exit")
166
205
 
206
+ parser.add_argument(
207
+ "-debug_machine",
208
+ action="store_true",
209
+ help="Show information about this machine and exit")
210
+
167
211
  args = parser.parse_args()
168
212
 
213
+ if args.debug_machine:
214
+ print_machine_info()
215
+ return 0
216
+
169
217
  if not args.tool:
170
218
  args.tool = []
171
219
 
172
220
  args.tool = list(args.tool)
173
221
  if args.group:
174
222
  for group in args.group:
175
- args.tool.extend(_recommended_tool_groups()[group])
223
+ args.tool.extend(tool_groups[group])
176
224
 
177
225
  tools_handled = set()
178
226
  for tool in args.tool:
@@ -185,7 +185,7 @@ To delete a job, use:
185
185
  # If only a manifest is specified, make a 'check_progress/' request and report results:
186
186
  elif chip_cfg:
187
187
  try:
188
- check_progress(chip)
188
+ check_progress(chip, [], {})
189
189
  except SiliconCompilerError as e:
190
190
  chip.logger.error(f'{e}')
191
191
  return 1
siliconcompiler/core.py CHANGED
@@ -88,6 +88,9 @@ class Chip:
88
88
  # Cache of file hashes
89
89
  self.__hashes = {}
90
90
 
91
+ # Dashboard
92
+ self._dash = None
93
+
91
94
  # Showtools
92
95
  self._showtools = {}
93
96
  for plugin in utils.get_plugins('show'):
@@ -758,7 +761,7 @@ class Chip:
758
761
  return fullstr
759
762
 
760
763
  ###########################################################################
761
- def valid(self, *keypath, default_valid=False, job=None):
764
+ def valid(self, *keypath, default_valid=False, job=None, check_complete=False):
762
765
  """
763
766
  Checks validity of a keypath.
764
767
 
@@ -770,6 +773,7 @@ class Chip:
770
773
  keypaths as a wildcard. Defaults to False.
771
774
  job (str): Jobname to use for dictionary access in place of the
772
775
  current active jobname.
776
+ check_complete (bool): Require the keypath be a complete path.
773
777
 
774
778
  Returns:
775
779
  Boolean indicating validity of keypath.
@@ -782,7 +786,10 @@ class Chip:
782
786
  >>> check = chip.valid('metric', 'foo', '0', 'tasktime', default_valid=True)
783
787
  Returns True, even if "foo" and "0" aren't in current configuration.
784
788
  """
785
- return self.schema.valid(*keypath, default_valid=default_valid, job=job)
789
+ return self.schema.valid(*keypath,
790
+ default_valid=default_valid,
791
+ job=job,
792
+ check_complete=check_complete)
786
793
 
787
794
  ###########################################################################
788
795
  def get(self, *keypath, field='value', job=None, step=None, index=None):
@@ -1123,6 +1130,9 @@ class Chip:
1123
1130
  quiet=quiet)
1124
1131
  return
1125
1132
 
1133
+ if filename is None:
1134
+ raise ValueError(f"{category} cannot process None")
1135
+
1126
1136
  # Normalize value to string in case we receive a pathlib.Path
1127
1137
  filename = str(filename)
1128
1138
 
@@ -1147,8 +1157,9 @@ class Chip:
1147
1157
  use_filetype = filetype
1148
1158
 
1149
1159
  if not use_fileset or not use_filetype:
1150
- self.logger.error(f'Unable to infer {category} fileset and/or filetype for '
1151
- f'{filename} based on file extension.')
1160
+ raise SiliconCompilerError(
1161
+ f'Unable to infer {category} fileset and/or filetype for '
1162
+ f'{filename} based on file extension.')
1152
1163
  elif not quiet:
1153
1164
  if not fileset and not filetype:
1154
1165
  self.logger.info(f'{filename} inferred as {use_fileset}/{use_filetype}')
@@ -1352,7 +1363,7 @@ class Chip:
1352
1363
  basename = str(pathlib.PurePosixPath(*path_paths[0:n]))
1353
1364
  endname = str(pathlib.PurePosixPath(*path_paths[n:]))
1354
1365
 
1355
- import_name = self.__get_imported_filename(basename, package)
1366
+ import_name = utils.get_hashed_filename(basename, package=package)
1356
1367
  if import_name not in collected_files:
1357
1368
  continue
1358
1369
 
@@ -2040,6 +2051,30 @@ class Chip:
2040
2051
  dot.graph_attr['ranksep'] = '0.75'
2041
2052
  dot.attr(bgcolor=background)
2042
2053
 
2054
+ subgraphs = {
2055
+ "graphs": {
2056
+ "sc-inputs": {
2057
+ "graphs": {},
2058
+ "nodes": []
2059
+ }
2060
+ },
2061
+ "nodes": []
2062
+ }
2063
+ for node, info in nodes.items():
2064
+ if info['is_input']:
2065
+ subgraph_temp = subgraphs["graphs"]["sc-inputs"]
2066
+ else:
2067
+ subgraph_temp = subgraphs
2068
+
2069
+ for key in node.split(".")[0:-1]:
2070
+ if key not in subgraph_temp["graphs"]:
2071
+ subgraph_temp["graphs"][key] = {
2072
+ "graphs": {},
2073
+ "nodes": []
2074
+ }
2075
+ subgraph_temp = subgraph_temp["graphs"][key]
2076
+ subgraph_temp["nodes"].append(node)
2077
+
2043
2078
  with dot.subgraph(name='inputs') as input_graph:
2044
2079
  input_graph.graph_attr['cluster'] = 'true'
2045
2080
  input_graph.graph_attr['color'] = background
@@ -2051,34 +2086,72 @@ class Chip:
2051
2086
  fontcolor=fontcolor, fontsize=fontsize, ordering="in",
2052
2087
  penwidth=penwidth, fillcolor=fillcolor, shape="box")
2053
2088
 
2054
- with dot.subgraph(name='input_nodes') as input_graph_nodes:
2055
- input_graph_nodes.graph_attr['cluster'] = 'true'
2056
- input_graph_nodes.graph_attr['color'] = background
2089
+ def make_node(graph, node, prefix):
2090
+ info = nodes[node]
2057
2091
 
2058
- # add nodes
2059
2092
  shape = "oval" if not show_io else "Mrecord"
2060
- for node, info in nodes.items():
2061
- task_label = f"\\n ({info['task']})" if info['task'] is not None else ""
2062
- if show_io:
2063
- input_labels = [f"<{ikey}> {ifile}" for ifile, ikey in info['inputs'].items()]
2064
- output_labels = [f"<{okey}> {ofile}" for ofile, okey in info['outputs'].items()]
2065
- center_text = f"\\n {node} {task_label} \\n\\n"
2066
- labelname = "{"
2067
- if input_labels:
2068
- labelname += f"{{ {' | '.join(input_labels)} }} |"
2069
- labelname += center_text
2070
- if output_labels:
2071
- labelname += f"| {{ {' | '.join(output_labels)} }}"
2072
- labelname += "}"
2093
+ task_label = f"\\n ({info['task']})" if info['task'] is not None else ""
2094
+ if show_io:
2095
+ input_labels = [f"<{ikey}> {ifile}" for ifile, ikey in info['inputs'].items()]
2096
+ output_labels = [f"<{okey}> {ofile}" for ofile, okey in info['outputs'].items()]
2097
+ center_text = f"\\n {node.replace(prefix, '')} {task_label} \\n\\n"
2098
+ labelname = "{"
2099
+ if input_labels:
2100
+ labelname += f"{{ {' | '.join(input_labels)} }} |"
2101
+ labelname += center_text
2102
+ if output_labels:
2103
+ labelname += f"| {{ {' | '.join(output_labels)} }}"
2104
+ labelname += "}"
2105
+ else:
2106
+ labelname = f"{node.replace(prefix, '')}{task_label}"
2107
+
2108
+ graph.node(node, label=labelname, bordercolor=fontcolor, style='filled',
2109
+ fontcolor=fontcolor, fontsize=fontsize, ordering="in",
2110
+ penwidth=penwidth, fillcolor=fillcolor, shape=shape)
2111
+
2112
+ graph_idx = 0
2113
+
2114
+ def get_node_count(graph_info):
2115
+ nodes = len(graph_info["nodes"])
2116
+
2117
+ for subgraph in graph_info["graphs"]:
2118
+ nodes += get_node_count(graph_info["graphs"][subgraph])
2119
+
2120
+ return nodes
2121
+
2122
+ def build_graph(graph_info, parent, prefix):
2123
+ nonlocal graph_idx
2124
+
2125
+ for subgraph in graph_info["graphs"]:
2126
+ if get_node_count(graph_info["graphs"][subgraph]) > 1:
2127
+ graph = graphviz.Digraph(name=f"cluster_{graph_idx}")
2128
+ graph_idx += 1
2129
+
2130
+ graph.graph_attr['rankdir'] = rankdir
2131
+ graph.attr(bgcolor=background)
2132
+
2133
+ if subgraph == "sc-inputs":
2134
+ graph.attr(style='invis')
2135
+ else:
2136
+ graph.attr(color=fontcolor)
2137
+ graph.attr(style='rounded')
2138
+ graph.attr(shape='oval')
2139
+ graph.attr(label=subgraph)
2140
+ graph.attr(labeljust='l')
2141
+ graph.attr(fontcolor=fontcolor)
2142
+ graph.attr(fontsize=str(int(fontsize) + 2))
2073
2143
  else:
2074
- labelname = f"{node}{task_label}"
2144
+ graph = parent
2145
+
2146
+ build_graph(graph_info["graphs"][subgraph], graph, f"{prefix}{subgraph}.")
2147
+
2148
+ if graph is not parent:
2149
+ parent.subgraph(graph)
2075
2150
 
2076
- dst = dot
2077
- if info['is_input']:
2078
- dst = input_graph_nodes
2079
- dst.node(node, label=labelname, bordercolor=fontcolor, style='filled',
2080
- fontcolor=fontcolor, fontsize=fontsize, ordering="in",
2081
- penwidth=penwidth, fillcolor=fillcolor, shape=shape)
2151
+ for subnode in graph_info["nodes"]:
2152
+ make_node(parent, subnode, prefix)
2153
+
2154
+ build_graph(subgraphs, dot, "")
2082
2155
 
2083
2156
  for edge0, edge1, weight in edges:
2084
2157
  dot.edge(f'{edge0}{out_label_suffix}', f'{edge1}{in_label_suffix}', weight=str(weight))
@@ -2383,7 +2456,7 @@ class Chip:
2383
2456
 
2384
2457
  abspath = dirs[(package, path)]
2385
2458
  if abspath:
2386
- filename = self.__get_imported_filename(posix_path, package)
2459
+ filename = utils.get_hashed_filename(posix_path, package=package)
2387
2460
  dst_path = os.path.join(directory, filename)
2388
2461
  if os.path.exists(dst_path):
2389
2462
  continue
@@ -2455,7 +2528,7 @@ class Chip:
2455
2528
 
2456
2529
  abspath = files[(package, path)]
2457
2530
  if abspath:
2458
- filename = self.__get_imported_filename(posix_path, package)
2531
+ filename = utils.get_hashed_filename(posix_path, package=package)
2459
2532
  dst_path = os.path.join(directory, filename)
2460
2533
  if verbose:
2461
2534
  self.logger.info(f"Copying {abspath} to '{directory}' directory")
@@ -2691,12 +2764,12 @@ class Chip:
2691
2764
  return hashlist
2692
2765
 
2693
2766
  ###########################################################################
2694
- def _dashboard(self, wait=True, port=None, graph_chips=None):
2767
+ def dashboard(self, wait=True, port=None, graph_chips=None):
2695
2768
  '''
2696
2769
  Open a session of the dashboard.
2697
2770
 
2698
2771
  The dashboard can be viewed in any webbrowser and can be accessed via:
2699
- http://localhost:8501/
2772
+ http://localhost:<port>/
2700
2773
 
2701
2774
  Args:
2702
2775
  wait (bool): If True, this call will wait in this method
@@ -2707,21 +2780,28 @@ class Chip:
2707
2780
  {'chip': chip object, 'name': chip name}
2708
2781
 
2709
2782
  Examples:
2710
- >>> chip._dashboard()
2783
+ >>> chip.dashboard()
2711
2784
  Opens a sesison of the dashboard.
2712
2785
  '''
2713
- dash = Dashboard(self, port=port, graph_chips=graph_chips)
2714
- dash.open_dashboard()
2786
+ if self._dash:
2787
+ # Remove previous dashboard
2788
+ self._dash.stop()
2789
+ self._dash = None
2790
+
2791
+ self._dash = Dashboard(self, port=port, graph_chips=graph_chips)
2792
+ self._dash.open_dashboard()
2793
+
2715
2794
  if wait:
2716
2795
  try:
2717
- dash.wait()
2796
+ self._dash.wait()
2718
2797
  except KeyboardInterrupt:
2719
- dash._sleep()
2798
+ self._dash._sleep()
2720
2799
  finally:
2721
- dash.stop()
2800
+ self._dash.stop()
2801
+ self._dash = None
2722
2802
  return None
2723
2803
 
2724
- return dash
2804
+ return self._dash
2725
2805
 
2726
2806
  ###########################################################################
2727
2807
  def summary(self, show_all_indices=False, generate_image=True, generate_html=True):
@@ -2761,8 +2841,8 @@ class Chip:
2761
2841
  work_dir = self.getworkdir()
2762
2842
  if os.path.isdir(work_dir):
2763
2843
  # Mark file paths where the reports can be found if they were generated.
2764
- results_html = os.path.join(work_dir, 'report.html')
2765
2844
  results_img = os.path.join(work_dir, f'{self.design}.png')
2845
+ results_html = os.path.join(work_dir, 'report.html')
2766
2846
 
2767
2847
  if generate_image:
2768
2848
  _generate_summary_image(self, results_img)
@@ -2770,13 +2850,18 @@ class Chip:
2770
2850
  if generate_html:
2771
2851
  _generate_html_report(self, flow, nodes_to_execute, results_html)
2772
2852
 
2853
+ # dashboard does not generate any data
2854
+ self.logger.info(f'Dashboard at "sc-dashboard -cfg {work_dir}/{self.design}.pkg.json"')
2855
+
2773
2856
  # Try to open the results and layout only if '-nodisplay' is not set.
2774
- # Priority: PNG, PDF, HTML.
2775
- if (not self.get('option', 'nodisplay')):
2857
+ # Priority: PNG > HTML > dashboard.
2858
+ if not self.get('option', 'nodisplay'):
2776
2859
  if os.path.isfile(results_img):
2777
2860
  _open_summary_image(results_img)
2778
2861
  elif os.path.isfile(results_html):
2779
2862
  _open_html_report(self, results_html)
2863
+ else:
2864
+ self._dashboard(wait=False)
2780
2865
 
2781
2866
  ###########################################################################
2782
2867
  def clock(self, pin, period, jitter=0, mode='global'):
@@ -3120,6 +3205,7 @@ class Chip:
3120
3205
  self.set('option', 'nodisplay', False, clobber=True)
3121
3206
  self.set('option', 'continue', True, clobber=True)
3122
3207
  self.set('option', 'quiet', False, clobber=True)
3208
+ self.set('option', 'clean', True, clobber=True)
3123
3209
  self.set('arg', 'step', None, clobber=True)
3124
3210
  self.set('arg', 'index', None, clobber=True)
3125
3211
  self.unset('option', 'to')
@@ -3214,31 +3300,6 @@ class Chip:
3214
3300
 
3215
3301
  return os.path.join(*dirlist)
3216
3302
 
3217
- #######################################
3218
- def __get_imported_filename(self, pathstr, package=None):
3219
- ''' Utility to map collected file to an unambiguous name based on its path.
3220
-
3221
- The mapping looks like:
3222
- path/to/file.ext => file_<md5('path/to/file.ext')>.ext
3223
- '''
3224
- path = pathlib.PurePosixPath(pathstr)
3225
- ext = ''.join(path.suffixes)
3226
-
3227
- # strip off all file suffixes to get just the bare name
3228
- barepath = path
3229
- while barepath.suffix:
3230
- barepath = pathlib.PurePosixPath(barepath.stem)
3231
- filename = str(barepath.parts[-1])
3232
-
3233
- if not package:
3234
- package = ''
3235
- else:
3236
- package = f'{package}:'
3237
- path_to_hash = f'{package}{str(path)}'
3238
- pathhash = hashlib.sha1(path_to_hash.encode('utf-8')).hexdigest()
3239
-
3240
- return f'{filename}_{pathhash}{ext}'
3241
-
3242
3303
  def error(self, msg):
3243
3304
  '''
3244
3305
  Raises error.
@@ -3273,6 +3334,9 @@ class Chip:
3273
3334
  # Modules are not serializable, so save without cache
3274
3335
  attributes['_showtools'] = {}
3275
3336
 
3337
+ # Dashboard is not serializable
3338
+ attributes['_dash'] = None
3339
+
3276
3340
  # We have to remove the chip's logger before serializing the object
3277
3341
  # since the logger object is not serializable.
3278
3342
  del attributes['logger']
@@ -43,6 +43,14 @@ def setup():
43
43
 
44
44
  fpga.set('fpga', part_name, 'vendor', vendor)
45
45
 
46
+ # Part name is specified per architecture file. Device code specifies
47
+ # which <fixed_layout> name to use when running VPR. These examples
48
+ # use the following names:
49
+ if (part_name == 'example_arch_X005Y005'):
50
+ fpga.set('fpga', part_name, 'var', 'vpr_device_code', 'fpga_beta')
51
+ else:
52
+ fpga.set('fpga', part_name, 'var', 'vpr_device_code', part_name)
53
+
46
54
  fpga.set('fpga', part_name, 'lutsize', lut_size)
47
55
 
48
56
  arch_root = os.path.join(flow_root, 'arch', part_name)
@@ -88,7 +88,8 @@ def _path(chip, package, download_handler):
88
88
  chip._packages[package] = data_path
89
89
  return data_path
90
90
 
91
- raise SiliconCompilerError(f'Extracting {package} data to {data_path} failed')
91
+ raise SiliconCompilerError(f'Extracting {package} data to {data_path} failed',
92
+ chip=chip)
92
93
 
93
94
 
94
95
  def path(chip, package):
@@ -188,7 +189,7 @@ def clone_synchronized(chip, package, data, data_path):
188
189
  elif url.scheme in ['git', 'git+https']:
189
190
  chip.logger.error('Failed to authenticate. Please use a token or ssh.')
190
191
  else:
191
- raise e
192
+ chip.logger.error(str(e))
192
193
 
193
194
 
194
195
  def clone_from_git(chip, package, data, repo_path):
@@ -178,7 +178,7 @@ def _log_truncated_stats(chip, status, nodes_with_status, nodes_to_print):
178
178
 
179
179
 
180
180
  ###################################
181
- def _process_progress_info(chip, progress_info, nodes_to_print=3):
181
+ def _process_progress_info(chip, progress_info, recorded_nodes, all_nodes, nodes_to_print=3):
182
182
  '''
183
183
  Helper method to log information about a remote run's progress,
184
184
  based on information returned from a 'check_progress/' call.
@@ -201,6 +201,11 @@ def _process_progress_info(chip, progress_info, nodes_to_print=3):
201
201
  # collect completed
202
202
  completed.append(node)
203
203
 
204
+ if node in all_nodes:
205
+ step, index = all_nodes[node]
206
+ if (step, index) not in recorded_nodes:
207
+ chip.set('record', 'status', status, step=step, index=index)
208
+
204
209
  nodes_to_log = {key: nodes_to_log[key] for key in sorted(nodes_to_log.keys())}
205
210
 
206
211
  # Log information about the job's progress.
@@ -360,11 +365,32 @@ def __remote_run_loop(chip, check_interval):
360
365
  completed = []
361
366
  result_procs = []
362
367
 
368
+ recorded = []
369
+
363
370
  for step, index in nodes_to_execute(chip):
364
371
  if SCNodeStatus.is_done(chip.get('record', 'status', step=step, index=index)):
365
372
  continue
366
373
  all_nodes[f'{step}{index}'] = (step, index)
367
374
 
375
+ def import_manifests():
376
+ changed = False
377
+ for step, index in all_nodes.values():
378
+ if (step, index) in recorded:
379
+ continue
380
+
381
+ manifest = os.path.join(chip.getworkdir(step=step, index=index),
382
+ 'outputs',
383
+ f'{chip.design}.pkg.json')
384
+ if os.path.exists(manifest):
385
+ try:
386
+ chip.schema.read_journal(manifest)
387
+ recorded.append((step, index))
388
+ changed = True
389
+ except: # noqa E722
390
+ # Import may fail if file is still getting written
391
+ pass
392
+ return changed
393
+
368
394
  def schedule_download(node):
369
395
  node_proc = multiprocessor.Process(target=fetch_results,
370
396
  args=(chip, node))
@@ -376,7 +402,12 @@ def __remote_run_loop(chip, check_interval):
376
402
 
377
403
  while is_busy:
378
404
  time.sleep(check_interval)
379
- new_completed, is_busy = check_progress(chip)
405
+ import_manifests()
406
+ new_completed, is_busy = check_progress(chip, recorded, all_nodes)
407
+
408
+ if chip._dash:
409
+ chip._dash.update_manifest()
410
+
380
411
  nodes_to_fetch = []
381
412
  for node in new_completed:
382
413
  if node not in completed:
@@ -400,26 +431,26 @@ def __remote_run_loop(chip, check_interval):
400
431
  proc.join()
401
432
 
402
433
  # Read in node manifests
403
- for step, index in all_nodes.values():
404
- manifest = os.path.join(chip.getworkdir(step=step, index=index),
405
- 'outputs',
406
- f'{chip.design}.pkg.json')
407
- if os.path.exists(manifest):
408
- chip.schema.read_journal(manifest)
434
+ import_manifests()
409
435
 
410
436
  # Un-set the 'remote' option to avoid from/to-based summary/show errors
411
437
  chip.unset('option', 'remote')
412
438
 
439
+ if chip._dash:
440
+ chip._dash.update_manifest()
441
+
413
442
 
414
443
  ###################################
415
- def check_progress(chip):
444
+ def check_progress(chip, recorded_nodes, all_nodes):
416
445
  try:
417
446
  is_busy_info = is_job_busy(chip)
418
447
  is_busy = is_busy_info['busy']
419
448
  completed = []
420
449
  if is_busy:
421
450
  completed = _process_progress_info(chip,
422
- is_busy_info)
451
+ is_busy_info,
452
+ recorded_nodes,
453
+ all_nodes)
423
454
  return completed, is_busy
424
455
  except Exception as e:
425
456
  # Sometimes an exception is raised if the request library cannot
@@ -1,7 +1,7 @@
1
1
  from .summary_image import _generate_summary_image, _open_summary_image
2
2
  from .html_report import _generate_html_report, _open_html_report
3
3
  from .summary_table import _show_summary_table
4
- from .streamlit_report import Dashboard
4
+ from .dashboard import Dashboard
5
5
 
6
6
  __all__ = [
7
7
  "_generate_summary_image",