siliconcompiler 0.32.3__py3-none-any.whl → 0.33.1__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 (280) hide show
  1. siliconcompiler/__init__.py +19 -2
  2. siliconcompiler/_common.py +5 -0
  3. siliconcompiler/_metadata.py +1 -1
  4. siliconcompiler/apps/sc.py +2 -2
  5. siliconcompiler/apps/sc_install.py +10 -3
  6. siliconcompiler/apps/sc_issue.py +1 -1
  7. siliconcompiler/apps/sc_remote.py +10 -5
  8. siliconcompiler/apps/sc_show.py +2 -2
  9. siliconcompiler/apps/utils/replay.py +5 -3
  10. siliconcompiler/asic.py +120 -0
  11. siliconcompiler/checklist.py +150 -0
  12. siliconcompiler/core.py +299 -299
  13. siliconcompiler/flowgraph.py +803 -515
  14. siliconcompiler/fpga.py +84 -0
  15. siliconcompiler/metric.py +479 -0
  16. siliconcompiler/optimizer/vizier.py +2 -3
  17. siliconcompiler/package/__init__.py +29 -6
  18. siliconcompiler/pdk.py +415 -0
  19. siliconcompiler/record.py +453 -0
  20. siliconcompiler/remote/client.py +15 -5
  21. siliconcompiler/remote/schema.py +116 -112
  22. siliconcompiler/remote/server.py +9 -6
  23. siliconcompiler/report/dashboard/cli/__init__.py +14 -721
  24. siliconcompiler/report/dashboard/cli/board.py +899 -0
  25. siliconcompiler/report/dashboard/web/__init__.py +10 -10
  26. siliconcompiler/report/dashboard/web/components/__init__.py +5 -4
  27. siliconcompiler/report/dashboard/web/components/flowgraph.py +3 -3
  28. siliconcompiler/report/dashboard/web/components/graph.py +6 -3
  29. siliconcompiler/report/dashboard/web/state.py +1 -1
  30. siliconcompiler/report/dashboard/web/utils/__init__.py +4 -3
  31. siliconcompiler/report/html_report.py +2 -3
  32. siliconcompiler/report/report.py +22 -11
  33. siliconcompiler/report/summary_image.py +1 -1
  34. siliconcompiler/report/summary_table.py +3 -3
  35. siliconcompiler/report/utils.py +21 -14
  36. siliconcompiler/scheduler/__init__.py +234 -1206
  37. siliconcompiler/scheduler/run_node.py +2 -1
  38. siliconcompiler/scheduler/send_messages.py +11 -5
  39. siliconcompiler/scheduler/slurm.py +11 -44
  40. siliconcompiler/scheduler/taskscheduler.py +320 -0
  41. siliconcompiler/schema/__init__.py +19 -2
  42. siliconcompiler/schema/baseschema.py +493 -0
  43. siliconcompiler/schema/cmdlineschema.py +250 -0
  44. siliconcompiler/{sphinx_ext → schema/docs}/__init__.py +3 -1
  45. siliconcompiler/{sphinx_ext → schema/docs}/dynamicgen.py +63 -81
  46. siliconcompiler/{sphinx_ext → schema/docs}/schemagen.py +73 -85
  47. siliconcompiler/{sphinx_ext → schema/docs}/utils.py +12 -13
  48. siliconcompiler/schema/editableschema.py +136 -0
  49. siliconcompiler/schema/journalingschema.py +238 -0
  50. siliconcompiler/schema/namedschema.py +41 -0
  51. siliconcompiler/schema/packageschema.py +101 -0
  52. siliconcompiler/schema/parameter.py +791 -0
  53. siliconcompiler/schema/parametertype.py +323 -0
  54. siliconcompiler/schema/parametervalue.py +736 -0
  55. siliconcompiler/schema/safeschema.py +37 -0
  56. siliconcompiler/schema/schema_cfg.py +109 -1789
  57. siliconcompiler/schema/utils.py +5 -68
  58. siliconcompiler/schema_obj.py +119 -0
  59. siliconcompiler/tool.py +1416 -0
  60. siliconcompiler/tools/_common/__init__.py +6 -10
  61. siliconcompiler/tools/_common/asic.py +5 -5
  62. siliconcompiler/tools/_common/sdc/sc_constraints.sdc +1 -1
  63. siliconcompiler/tools/bluespec/convert.py +9 -8
  64. siliconcompiler/tools/builtin/_common.py +9 -2
  65. siliconcompiler/tools/builtin/concatenate.py +7 -3
  66. siliconcompiler/tools/builtin/minimum.py +7 -2
  67. siliconcompiler/tools/builtin/mux.py +8 -2
  68. siliconcompiler/tools/builtin/nop.py +7 -2
  69. siliconcompiler/tools/builtin/verify.py +11 -5
  70. siliconcompiler/tools/chisel/convert.py +10 -10
  71. siliconcompiler/tools/genfasm/bitstream.py +3 -3
  72. siliconcompiler/tools/ghdl/convert.py +1 -1
  73. siliconcompiler/tools/icarus/compile.py +4 -4
  74. siliconcompiler/tools/icepack/bitstream.py +6 -1
  75. siliconcompiler/tools/klayout/convert_drc_db.py +5 -0
  76. siliconcompiler/tools/klayout/drc.py +2 -2
  77. siliconcompiler/tools/klayout/klayout_export.py +0 -1
  78. siliconcompiler/tools/klayout/klayout_show.py +6 -6
  79. siliconcompiler/tools/klayout/klayout_utils.py +15 -22
  80. siliconcompiler/tools/netgen/count_lvs.py +2 -2
  81. siliconcompiler/tools/netgen/lvs.py +1 -1
  82. siliconcompiler/tools/nextpnr/apr.py +6 -1
  83. siliconcompiler/tools/nextpnr/nextpnr.py +4 -4
  84. siliconcompiler/tools/openroad/_apr.py +15 -2
  85. siliconcompiler/tools/openroad/rdlroute.py +3 -3
  86. siliconcompiler/tools/openroad/scripts/apr/postamble.tcl +1 -1
  87. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +5 -5
  88. siliconcompiler/tools/openroad/scripts/apr/sc_antenna_repair.tcl +2 -2
  89. siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +2 -2
  90. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +2 -2
  91. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +2 -2
  92. siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +2 -2
  93. siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +2 -2
  94. siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +2 -2
  95. siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +2 -2
  96. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -2
  97. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +3 -9
  98. siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +3 -3
  99. siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +2 -2
  100. siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +2 -2
  101. siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +2 -2
  102. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +2 -2
  103. siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +2 -2
  104. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +2 -2
  105. siliconcompiler/tools/openroad/scripts/common/procs.tcl +75 -1
  106. siliconcompiler/tools/openroad/scripts/common/read_input_files.tcl +1 -7
  107. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +2 -2
  108. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -3
  109. siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +1 -1
  110. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -3
  111. siliconcompiler/tools/openroad/scripts/sc_show.tcl +6 -6
  112. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +10 -0
  113. siliconcompiler/tools/opensta/timing.py +11 -0
  114. siliconcompiler/tools/slang/__init__.py +13 -13
  115. siliconcompiler/tools/slang/elaborate.py +6 -6
  116. siliconcompiler/tools/slang/lint.py +1 -3
  117. siliconcompiler/tools/surelog/parse.py +4 -4
  118. siliconcompiler/tools/sv2v/convert.py +20 -3
  119. siliconcompiler/tools/verilator/compile.py +2 -2
  120. siliconcompiler/tools/verilator/verilator.py +3 -3
  121. siliconcompiler/tools/vpr/_xml_constraint.py +8 -8
  122. siliconcompiler/tools/vpr/place.py +1 -1
  123. siliconcompiler/tools/vpr/route.py +4 -4
  124. siliconcompiler/tools/vpr/screenshot.py +1 -1
  125. siliconcompiler/tools/vpr/show.py +5 -5
  126. siliconcompiler/tools/vpr/vpr.py +24 -24
  127. siliconcompiler/tools/xdm/convert.py +2 -2
  128. siliconcompiler/tools/xyce/simulate.py +1 -1
  129. siliconcompiler/tools/yosys/prepareLib.py +2 -2
  130. siliconcompiler/tools/yosys/sc_synth_asic.tcl +111 -63
  131. siliconcompiler/tools/yosys/screenshot.py +1 -1
  132. siliconcompiler/tools/yosys/syn_asic.py +7 -7
  133. siliconcompiler/toolscripts/_tools.json +12 -10
  134. siliconcompiler/toolscripts/rhel8/install-chisel.sh +9 -2
  135. siliconcompiler/toolscripts/rhel8/install-icarus.sh +10 -3
  136. siliconcompiler/toolscripts/rhel8/install-klayout.sh +8 -1
  137. siliconcompiler/toolscripts/rhel8/install-magic.sh +9 -2
  138. siliconcompiler/toolscripts/rhel8/install-montage.sh +1 -1
  139. siliconcompiler/toolscripts/rhel8/install-netgen.sh +9 -2
  140. siliconcompiler/toolscripts/rhel8/install-slang.sh +11 -4
  141. siliconcompiler/toolscripts/rhel8/install-surelog.sh +9 -2
  142. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +11 -4
  143. siliconcompiler/toolscripts/rhel8/install-verible.sh +11 -3
  144. siliconcompiler/toolscripts/rhel8/install-verilator.sh +10 -3
  145. siliconcompiler/toolscripts/rhel8/install-xyce.sh +15 -10
  146. siliconcompiler/toolscripts/rhel9/install-chisel.sh +9 -2
  147. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +9 -2
  148. siliconcompiler/toolscripts/rhel9/install-gtkwave.sh +10 -3
  149. siliconcompiler/toolscripts/rhel9/install-icarus.sh +10 -3
  150. siliconcompiler/toolscripts/rhel9/install-klayout.sh +8 -1
  151. siliconcompiler/toolscripts/rhel9/install-magic.sh +9 -2
  152. siliconcompiler/toolscripts/rhel9/install-montage.sh +1 -1
  153. siliconcompiler/toolscripts/rhel9/install-netgen.sh +9 -2
  154. siliconcompiler/toolscripts/rhel9/install-openroad.sh +16 -3
  155. siliconcompiler/toolscripts/rhel9/install-opensta.sh +17 -5
  156. siliconcompiler/toolscripts/rhel9/install-slang.sh +11 -4
  157. siliconcompiler/toolscripts/rhel9/install-surelog.sh +9 -2
  158. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +11 -4
  159. siliconcompiler/toolscripts/rhel9/install-verible.sh +11 -3
  160. siliconcompiler/toolscripts/rhel9/install-verilator.sh +10 -3
  161. siliconcompiler/toolscripts/rhel9/install-vpr.sh +9 -2
  162. siliconcompiler/toolscripts/rhel9/install-xdm.sh +10 -2
  163. siliconcompiler/toolscripts/rhel9/install-xyce.sh +15 -10
  164. siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +9 -2
  165. siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +10 -3
  166. siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +10 -2
  167. siliconcompiler/toolscripts/rhel9/install-yosys.sh +9 -2
  168. siliconcompiler/toolscripts/ubuntu20/install-bambu.sh +10 -2
  169. siliconcompiler/toolscripts/ubuntu20/install-bluespec.sh +10 -3
  170. siliconcompiler/toolscripts/ubuntu20/install-chisel.sh +9 -2
  171. siliconcompiler/toolscripts/ubuntu20/install-ghdl.sh +9 -2
  172. siliconcompiler/toolscripts/ubuntu20/install-gtkwave.sh +9 -2
  173. siliconcompiler/toolscripts/ubuntu20/install-icarus.sh +9 -2
  174. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +9 -2
  175. siliconcompiler/toolscripts/ubuntu20/install-klayout.sh +8 -1
  176. siliconcompiler/toolscripts/ubuntu20/install-magic.sh +9 -2
  177. siliconcompiler/toolscripts/ubuntu20/install-montage.sh +1 -1
  178. siliconcompiler/toolscripts/ubuntu20/install-netgen.sh +9 -2
  179. siliconcompiler/toolscripts/ubuntu20/install-nextpnr.sh +9 -2
  180. siliconcompiler/toolscripts/ubuntu20/install-openroad.sh +16 -3
  181. siliconcompiler/toolscripts/ubuntu20/install-opensta.sh +16 -5
  182. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +11 -4
  183. siliconcompiler/toolscripts/ubuntu20/install-slurm.sh +9 -2
  184. siliconcompiler/toolscripts/ubuntu20/install-surelog.sh +10 -2
  185. siliconcompiler/toolscripts/ubuntu20/install-sv2v.sh +11 -4
  186. siliconcompiler/toolscripts/ubuntu20/install-verible.sh +11 -3
  187. siliconcompiler/toolscripts/ubuntu20/install-verilator.sh +9 -2
  188. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +10 -2
  189. siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +13 -8
  190. siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +9 -2
  191. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +9 -2
  192. siliconcompiler/toolscripts/ubuntu22/install-bambu.sh +10 -2
  193. siliconcompiler/toolscripts/ubuntu22/install-bluespec.sh +10 -3
  194. siliconcompiler/toolscripts/ubuntu22/install-chisel.sh +9 -2
  195. siliconcompiler/toolscripts/ubuntu22/install-ghdl.sh +9 -2
  196. siliconcompiler/toolscripts/ubuntu22/install-gtkwave.sh +9 -2
  197. siliconcompiler/toolscripts/ubuntu22/install-icarus.sh +9 -2
  198. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +9 -2
  199. siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +8 -1
  200. siliconcompiler/toolscripts/ubuntu22/install-magic.sh +9 -2
  201. siliconcompiler/toolscripts/ubuntu22/install-montage.sh +1 -1
  202. siliconcompiler/toolscripts/ubuntu22/install-netgen.sh +9 -2
  203. siliconcompiler/toolscripts/ubuntu22/install-nextpnr.sh +9 -2
  204. siliconcompiler/toolscripts/ubuntu22/install-openroad.sh +16 -3
  205. siliconcompiler/toolscripts/ubuntu22/install-opensta.sh +17 -5
  206. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +11 -4
  207. siliconcompiler/toolscripts/ubuntu22/install-slurm.sh +9 -2
  208. siliconcompiler/toolscripts/ubuntu22/install-surelog.sh +10 -2
  209. siliconcompiler/toolscripts/ubuntu22/install-sv2v.sh +11 -4
  210. siliconcompiler/toolscripts/ubuntu22/install-verible.sh +11 -3
  211. siliconcompiler/toolscripts/ubuntu22/install-verilator.sh +9 -2
  212. siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +9 -4
  213. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +10 -2
  214. siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +13 -8
  215. siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +9 -2
  216. siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +10 -3
  217. siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +10 -2
  218. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +9 -2
  219. siliconcompiler/toolscripts/ubuntu24/install-bambu.sh +12 -4
  220. siliconcompiler/toolscripts/ubuntu24/install-bluespec.sh +10 -3
  221. siliconcompiler/toolscripts/ubuntu24/install-chisel.sh +9 -2
  222. siliconcompiler/toolscripts/ubuntu24/install-ghdl.sh +9 -2
  223. siliconcompiler/toolscripts/ubuntu24/install-gtkwave.sh +9 -2
  224. siliconcompiler/toolscripts/ubuntu24/install-icarus.sh +9 -2
  225. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +9 -2
  226. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +8 -1
  227. siliconcompiler/toolscripts/ubuntu24/install-magic.sh +9 -2
  228. siliconcompiler/toolscripts/ubuntu24/install-montage.sh +1 -1
  229. siliconcompiler/toolscripts/ubuntu24/install-netgen.sh +9 -2
  230. siliconcompiler/toolscripts/ubuntu24/install-nextpnr.sh +9 -2
  231. siliconcompiler/toolscripts/ubuntu24/install-openroad.sh +16 -3
  232. siliconcompiler/toolscripts/ubuntu24/install-opensta.sh +17 -5
  233. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +11 -4
  234. siliconcompiler/toolscripts/ubuntu24/install-slurm.sh +9 -2
  235. siliconcompiler/toolscripts/ubuntu24/install-surelog.sh +10 -2
  236. siliconcompiler/toolscripts/ubuntu24/install-sv2v.sh +11 -4
  237. siliconcompiler/toolscripts/ubuntu24/install-verible.sh +11 -3
  238. siliconcompiler/toolscripts/ubuntu24/install-verilator.sh +9 -2
  239. siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +9 -4
  240. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +10 -2
  241. siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +13 -8
  242. siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +9 -2
  243. siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +10 -3
  244. siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +10 -2
  245. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +9 -2
  246. siliconcompiler/utils/__init__.py +19 -112
  247. siliconcompiler/utils/flowgraph.py +244 -0
  248. siliconcompiler/{issue.py → utils/issue.py} +18 -25
  249. siliconcompiler/utils/logging.py +3 -4
  250. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/METADATA +9 -8
  251. siliconcompiler-0.33.1.dist-info/RECORD +488 -0
  252. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/WHEEL +1 -1
  253. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/entry_points.txt +8 -8
  254. siliconcompiler/schema/schema_obj.py +0 -1936
  255. siliconcompiler/toolscripts/ubuntu20/install-vpr.sh +0 -29
  256. siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -61
  257. siliconcompiler-0.32.3.dist-info/RECORD +0 -470
  258. /siliconcompiler/{templates → data/templates}/__init__.py +0 -0
  259. /siliconcompiler/{templates → data/templates}/email/__init__.py +0 -0
  260. /siliconcompiler/{templates → data/templates}/email/general.j2 +0 -0
  261. /siliconcompiler/{templates → data/templates}/email/summary.j2 +0 -0
  262. /siliconcompiler/{templates → data/templates}/issue/README.txt +0 -0
  263. /siliconcompiler/{templates → data/templates}/issue/__init__.py +0 -0
  264. /siliconcompiler/{templates → data/templates}/issue/run.sh +0 -0
  265. /siliconcompiler/{templates → data/templates}/replay/replay.py.j2 +0 -0
  266. /siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +0 -0
  267. /siliconcompiler/{templates → data/templates}/replay/requirements.txt +0 -0
  268. /siliconcompiler/{templates → data/templates}/replay/setup.sh +0 -0
  269. /siliconcompiler/{templates → data/templates}/report/__init__.py +0 -0
  270. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.css +0 -0
  271. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.js +0 -0
  272. /siliconcompiler/{templates → data/templates}/report/bootstrap_LICENSE.md +0 -0
  273. /siliconcompiler/{templates → data/templates}/report/sc_report.j2 +0 -0
  274. /siliconcompiler/{templates → data/templates}/slurm/__init__.py +0 -0
  275. /siliconcompiler/{templates → data/templates}/slurm/run.sh +0 -0
  276. /siliconcompiler/{templates → data/templates}/tcl/__init__.py +0 -0
  277. /siliconcompiler/{templates → data/templates}/tcl/manifest.tcl.j2 +0 -0
  278. /siliconcompiler/{units.py → utils/units.py} +0 -0
  279. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/licenses/LICENSE +0 -0
  280. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,899 @@
1
+ import os
2
+ import threading
3
+ import logging
4
+ import queue
5
+ import time
6
+ import multiprocessing
7
+
8
+ from collections import deque
9
+ from dataclasses import dataclass, field
10
+ from typing import List, Dict
11
+
12
+ from rich import box
13
+ from rich.theme import Theme
14
+ from rich.live import Live
15
+ from rich.table import Table
16
+ from rich.progress import Progress, BarColumn, TextColumn, MofNCompleteColumn
17
+ from rich.console import Console
18
+ from rich.console import Group
19
+ from rich.padding import Padding
20
+
21
+ from siliconcompiler import SiliconCompilerError, NodeStatus
22
+ from siliconcompiler.utils.logging import SCColorLoggerFormatter
23
+ from siliconcompiler.flowgraph import RuntimeFlowgraph
24
+
25
+ import atexit
26
+
27
+
28
+ class LogBufferHandler(logging.Handler):
29
+ def __init__(self, sync_queue, n=50, event=None):
30
+ """
31
+ Initializes the handler.
32
+
33
+ Args:
34
+ n (int): Maximum number of lines to keep.
35
+ event (threading.Event): Optional event to trigger on every log line.
36
+ """
37
+ super().__init__()
38
+ self.queue = sync_queue
39
+ self.buffer = deque(maxlen=n)
40
+ self.event = event
41
+ self._lock = threading.Lock()
42
+
43
+ def emit(self, record):
44
+ """
45
+ Processes a log record.
46
+
47
+ Args:
48
+ record (logging.LogRecord): The log record to process.
49
+ """
50
+ log_entry = self.format(record)
51
+ log_entry = log_entry.replace("[", "\\[")
52
+
53
+ # Replace console coloring
54
+ for color, replacement in (
55
+ (SCColorLoggerFormatter.reset.replace("[", "\\["), "[/]"),
56
+ (SCColorLoggerFormatter.blue.replace("[", "\\["), "[blue]"),
57
+ (SCColorLoggerFormatter.yellow.replace("[", "\\["), "[yellow]"),
58
+ (SCColorLoggerFormatter.red.replace("[", "\\["), "[red]"),
59
+ (SCColorLoggerFormatter.bold_red.replace("[", "\\["), "[bold red]")):
60
+ log_entry = log_entry.replace(color, replacement)
61
+ self.queue.put(log_entry)
62
+ if self.event:
63
+ self.event.set()
64
+
65
+ def get_lines(self, lines=None):
66
+ """
67
+ Retrieves the last logged lines.
68
+
69
+ Returns:
70
+ list: A list of the last logged lines.
71
+ """
72
+ with self._lock:
73
+ while not self.queue.empty():
74
+ try:
75
+ self.buffer.append(self.queue.get_nowait())
76
+ except queue.Empty:
77
+ break
78
+ buffer_list = list(self.buffer)
79
+ if lines is None or lines > len(buffer_list):
80
+ return buffer_list
81
+ return buffer_list[-lines:]
82
+
83
+
84
+ @dataclass
85
+ class JobData:
86
+ total: int = 0
87
+ success: int = 0
88
+ error: int = 0
89
+ skipped: int = 0
90
+ finished: int = 0
91
+ jobname: str = ""
92
+ design: str = ""
93
+ runtime: float = 0.0
94
+ complete: bool = False
95
+ nodes: List[dict] = field(default_factory=list)
96
+
97
+
98
+ @dataclass
99
+ class SessionData:
100
+ total: int = 0
101
+ success: int = 0
102
+ error: int = 0
103
+ skipped: int = 0
104
+ finished: int = 0
105
+ runtime: float = 0.0
106
+ jobs: Dict[str, JobData] = field(default_factory=dict)
107
+
108
+
109
+ @dataclass
110
+ class Layout:
111
+ """
112
+ Layout class represents the configuration for a dashboard layout.
113
+
114
+ Attributes:
115
+ height (int): The total height of the layout.
116
+ width (int): The total width of the layout.
117
+ job_board_min (int): The minimum height allocated for the job board.
118
+ job_board_max (int): The maximum height allocated for the job board.
119
+ log_max (int): The maximum height allocated for the log section.
120
+ log_min (int): The minimum height allocated for the log section.
121
+ progress_bar_min (int): The minimum height allocated for the progress bar.
122
+ progress_bar_max (int): The maximum height allocated for the progress bar.
123
+ job_board_show_log (bool): A flag indicating whether to show the log in the job board.
124
+
125
+ __reserved (int): Reserved space for table headings and extra padding.
126
+
127
+ Methods:
128
+ available_height():
129
+ Calculates and returns the available height for other components in the layout
130
+ after accounting for reserved space, job board, and log sections.
131
+ Returns 0 if the total height is not set.
132
+ """
133
+
134
+ height: int = 0
135
+ width: int = 0
136
+
137
+ log_height = 0
138
+ job_board_height = 0
139
+ progress_bar_height = 0
140
+
141
+ job_board_show_log: bool = True
142
+ job_board_v_limit: int = 120
143
+
144
+ __progress_bar_height_default = 1
145
+ padding_log = 2
146
+ padding_progress_bar = 1
147
+ padding_job_board = 1
148
+ padding_job_board_header = 1
149
+
150
+ def update(self, height, width, visible_jobs, visible_bars):
151
+ self.height = height
152
+ self.width = width
153
+
154
+ min_required = (
155
+ max(visible_bars, self.__progress_bar_height_default)
156
+ + self.padding_progress_bar
157
+ )
158
+ if self.height < min_required:
159
+ self.progress_bar_height = 0
160
+ self.job_board_height = 0
161
+ self.log_height = 0
162
+ return
163
+
164
+ remaining_height = self.height
165
+
166
+ # Allocate progress bar space (highest priority)
167
+ self.progress_bar_height = max(visible_bars, self.__progress_bar_height_default)
168
+ if self.progress_bar_height > 0:
169
+ remaining_height -= self.progress_bar_height + self.padding_progress_bar
170
+
171
+ # Calculate job board requirements
172
+ job_board_min_space = self.padding_job_board_header + self.padding_job_board
173
+ job_board_max_nodes = remaining_height // 2
174
+ visible_jobs = min(visible_jobs, job_board_max_nodes)
175
+ if visible_jobs > 0:
176
+ job_board_full_space = visible_jobs + job_board_min_space
177
+ else:
178
+ job_board_full_space = 0
179
+
180
+ # Allocate job board space (second priority)
181
+ if remaining_height <= job_board_min_space:
182
+ self.job_board_height = 0
183
+ self.log_height = 0
184
+ elif remaining_height <= job_board_full_space:
185
+ self.job_board_height = remaining_height - job_board_min_space
186
+ self.log_height = 0
187
+ elif visible_jobs == 0:
188
+ self.job_board_height = 0
189
+ self.log_height = remaining_height
190
+ else:
191
+ self.job_board_height = visible_jobs
192
+ self.log_height = remaining_height - job_board_full_space - self.padding_log
193
+
194
+ if self.width < self.job_board_v_limit:
195
+ self.job_board_show_log = False
196
+
197
+
198
+ class BoardSingleton(type):
199
+ _instances = {}
200
+ _lock = multiprocessing.Lock()
201
+
202
+ def __call__(cls, *args, **kwargs):
203
+ with cls._lock:
204
+ if cls not in cls._instances:
205
+ cls._instances[cls] = super(BoardSingleton, cls).__call__(*args, **kwargs)
206
+ cls._instances[cls]._init_singleton()
207
+ return cls._instances[cls]
208
+
209
+
210
+ class Board(metaclass=BoardSingleton):
211
+ __status_color_map = {
212
+ NodeStatus.PENDING: "blue",
213
+ NodeStatus.QUEUED: "blue",
214
+ NodeStatus.RUNNING: "orange4",
215
+ NodeStatus.SUCCESS: "green",
216
+ NodeStatus.ERROR: "red",
217
+ NodeStatus.SKIPPED: "bright_black",
218
+ NodeStatus.TIMEOUT: "red",
219
+ }
220
+ __theme = Theme(
221
+ {
222
+ # Text colors
223
+ "text.primary": "white",
224
+ "text.secondary": "cyan",
225
+ # Background colors
226
+ "background.primary": "grey15",
227
+ "background.secondary": "dark_blue",
228
+ # Highlight and accent colors
229
+ "highlight": "green",
230
+ "accent": " cyan",
231
+ # Status colors
232
+ "error": "red",
233
+ "warning": "yellow",
234
+ "success": "green",
235
+ # Node status colors
236
+ **{f"node.{status}": color for status, color in __status_color_map.items()},
237
+ # Custom style for headers
238
+ "header": "bold underline cyan",
239
+ }
240
+ )
241
+
242
+ __JOB_BOARD_HEADER = True
243
+
244
+ __JOB_BOARD_BOX = box.SIMPLE_HEAD
245
+
246
+ def __init__(self):
247
+ pass
248
+
249
+ def _init_singleton(self):
250
+ self._console = Console(theme=Board.__theme)
251
+
252
+ self.live = Live(
253
+ console=self._console,
254
+ screen=False,
255
+ auto_refresh=True,
256
+ )
257
+
258
+ atexit.register(self._stop_on_exit)
259
+
260
+ self._active = self._console.is_terminal
261
+ if not self._active:
262
+ self._console = None
263
+ return
264
+
265
+ self._layout = Layout()
266
+
267
+ # Manager to thread data
268
+ self._manager = multiprocessing.Manager()
269
+
270
+ self._render_event = self._manager.Event()
271
+ self._render_stop_event = self._manager.Event()
272
+ self._render_thread = None
273
+
274
+ # Holds thread job data
275
+ self._board_info = self._manager.Namespace()
276
+ self._board_info.data_modified = False
277
+ self._job_data = self._manager.dict()
278
+ self._job_data_lock = self._manager.Lock()
279
+
280
+ self._render_data = SessionData()
281
+ self._render_data_lock = threading.Lock()
282
+
283
+ self._log_handler_queue = self._manager.Queue()
284
+
285
+ self._log_handler = LogBufferHandler(
286
+ self._log_handler_queue, n=120, event=self._render_event)
287
+
288
+ if not self.__JOB_BOARD_HEADER:
289
+ self._layout.padding_job_board_header = 0
290
+
291
+ self._metrics = ("warnings", "errors")
292
+
293
+ def _stop_on_exit(self):
294
+ self.stop()
295
+
296
+ def open_dashboard(self):
297
+ """Starts the dashboard rendering thread if it is not already running."""
298
+
299
+ if not self._active:
300
+ return
301
+
302
+ if not self.is_running():
303
+ self._update_render_data(None)
304
+
305
+ with self._job_data_lock:
306
+ if not self._render_thread or not self._render_thread.is_alive():
307
+ self._render_thread = threading.Thread(target=self._render, daemon=True)
308
+ self._render_event.clear()
309
+ self._render_stop_event.clear()
310
+
311
+ self._render_thread.start()
312
+
313
+ def update_manifest(self, chip, starttimes=None):
314
+ """
315
+ Updates the manifest file with the latest data from the chip object.
316
+ This ensures that the dashboard reflects the current state of the chip.
317
+ """
318
+
319
+ if not self._active:
320
+ return
321
+
322
+ self._update_render_data(chip, starttimes=starttimes)
323
+
324
+ def is_running(self):
325
+ """Returns True to indicate that the dashboard is running."""
326
+
327
+ if not self._active:
328
+ return False
329
+
330
+ with self._job_data_lock:
331
+ if not self._render_thread:
332
+ return False
333
+
334
+ return self._render_thread.is_alive()
335
+
336
+ def end_of_run(self, chip):
337
+ """
338
+ Stops the dashboard rendering thread and ensures all rendering operations are completed.
339
+ """
340
+
341
+ if not self._active:
342
+ return
343
+
344
+ self._update_render_data(chip, complete=True)
345
+
346
+ def stop(self):
347
+ """
348
+ Stops the dashboard rendering thread and ensures all rendering operations are completed.
349
+ """
350
+ if not self.is_running():
351
+ return
352
+
353
+ if self._job_data:
354
+ if any([not job.complete for job in self._job_data.values()]):
355
+ return
356
+
357
+ self._render_stop_event.set()
358
+ self._render_event.set()
359
+
360
+ # Wait for rendering to finish
361
+ self.wait()
362
+
363
+ # Restore terminal
364
+ self.live.stop()
365
+ self._console.show_cursor()
366
+
367
+ def wait(self):
368
+ """Waits for the dashboard rendering thread to finish."""
369
+ if not self.is_running():
370
+ return
371
+
372
+ self._render_thread.join()
373
+
374
+ @staticmethod
375
+ def format_status(status: str):
376
+ """
377
+ Formats the status of a node for display in the dashboard.
378
+
379
+ Args:
380
+ status (str): The status of the node (e.g., 'running', 'success', 'error').
381
+
382
+ Returns:
383
+ str: A formatted string with the status styled for display.
384
+ """
385
+ return f"[node.{status.lower()}]{status.upper()}[/]"
386
+
387
+ @staticmethod
388
+ def format_node(design, jobname, step, index, multi_job) -> str:
389
+ """
390
+ Formats a node's information for display in the dashboard.
391
+
392
+ Args:
393
+ design (str): The design name.
394
+ jobname (str): The job name.
395
+ step (str): The step name.
396
+ index (int): The step index.
397
+
398
+ Returns:
399
+ str: A formatted string with the node's information styled for display.
400
+ """
401
+ if multi_job:
402
+ return f"{design}/{jobname}/{step}/{index}"
403
+ else:
404
+ return f"{step}/{index}"
405
+
406
+ def _render_log(self, layout):
407
+ if layout.log_height == 0:
408
+ return None
409
+
410
+ table = Table(box=None)
411
+ table.add_column(overflow="ellipsis", no_wrap=True, vertical="bottom")
412
+ table.show_edge = False
413
+ table.show_lines = False
414
+ table.show_footer = False
415
+ table.show_header = False
416
+ for line in self._log_handler.get_lines(layout.log_height):
417
+ table.add_row(f"[bright_black]{line}[/]")
418
+ while table.row_count < layout.log_height:
419
+ table.add_row("")
420
+
421
+ return Group(table, Padding("", (0, 0)))
422
+
423
+ def _render_job_dashboard(self, layout):
424
+ """
425
+ Creates a table of jobs and their statuses for display in the dashboard.
426
+
427
+ Returns:
428
+ Group: A Rich Group object containing tables for each job.
429
+ """
430
+ # Don't render anything if there is not enough space
431
+ if layout.job_board_height == 0:
432
+ return None
433
+
434
+ with self._render_data_lock:
435
+ job_data = self._render_data.jobs.copy() # Access jobs from SessionData
436
+
437
+ if self.__JOB_BOARD_HEADER:
438
+ table_box = self.__JOB_BOARD_BOX
439
+ else:
440
+ table_box = None
441
+
442
+ table = Table(box=table_box, pad_edge=False)
443
+ table.show_edge = False
444
+ table.show_lines = False
445
+ table.show_footer = False
446
+ table.show_header = self.__JOB_BOARD_HEADER
447
+ table.add_column("Status")
448
+ table.add_column("Node")
449
+ table.add_column("Time", justify="right")
450
+ for metric in self._metrics:
451
+ table.add_column(metric.capitalize(), justify="right")
452
+ if layout.job_board_show_log:
453
+ table.add_column("Log")
454
+
455
+ multi_jobs = len(job_data) > 1 or True
456
+
457
+ # jobname, node index, priority, node order
458
+ table_data_select = []
459
+ for chipid, job in job_data.items():
460
+ for n, node in enumerate(job.nodes):
461
+ table_data_select.append(
462
+ (chipid, n, node["print"]["priority"], node["print"]["order"])
463
+ )
464
+
465
+ # sort for priority
466
+ table_data_select = sorted(table_data_select, key=lambda d: (d[2], *d[3], d[0]))
467
+
468
+ # trim to size
469
+ table_data_select = table_data_select[0:layout.job_board_height]
470
+
471
+ # sort for printing order
472
+ table_data_select = sorted(table_data_select, key=lambda d: (d[0], *d[3], d[2]))
473
+
474
+ table_data = []
475
+
476
+ for chipid, node_idx, _, _ in table_data_select:
477
+ job = job_data[chipid]
478
+ node = job.nodes[node_idx]
479
+
480
+ if (
481
+ layout.job_board_show_log
482
+ and os.path.exists(node["log"])
483
+ ):
484
+ log_file = "[bright_black]{}[/]".format(node['log'])
485
+ else:
486
+ log_file = None
487
+
488
+ if node["time"]["duration"] is not None:
489
+ duration = f'{node["time"]["duration"]:.1f}s'
490
+ elif node["time"]["start"] is not None:
491
+ duration = f'{time.time() - node["time"]["start"]:.1f}s'
492
+ else:
493
+ duration = ""
494
+
495
+ table_data.append((
496
+ Board.format_status(node["status"]),
497
+ Board.format_node(
498
+ job.design, job.jobname, node["step"], node["index"],
499
+ multi_jobs
500
+ ),
501
+ duration,
502
+ *node["metrics"],
503
+ log_file
504
+ ))
505
+
506
+ for row_data in table_data:
507
+ table.add_row(*row_data)
508
+
509
+ if table.row_count == 0:
510
+ return None
511
+
512
+ if self.__JOB_BOARD_HEADER:
513
+ return Group(table, Padding("", (0, 0)))
514
+ return Group(Padding("", (0, 0)), table, Padding("", (0, 0)))
515
+
516
+ def _render_progress_bar(self, layout):
517
+ """
518
+ Creates progress bars showing job completion for display in the dashboard.
519
+
520
+ Returns:
521
+ Group: A Rich Group object containing progress bars for each job.
522
+ """
523
+ with self._render_data_lock:
524
+ job_data = self._render_data.jobs.copy()
525
+ done = self._render_data.finished > 0 \
526
+ and self._render_data.total == self._render_data.finished \
527
+ and self._render_data.success == self._render_data.total
528
+
529
+ if done:
530
+ return None
531
+
532
+ ref_time = time.time()
533
+ runtimes = {}
534
+ for name, job in job_data.items():
535
+ if job.complete:
536
+ runtimes[name] = job.runtime
537
+ else:
538
+ runtime = 0.0
539
+ for node in job.nodes:
540
+ if node["time"]["duration"] is not None:
541
+ runtime += node["time"]["duration"]
542
+ elif node["time"]["start"] is not None:
543
+ runtime += ref_time - node["time"]["start"]
544
+ runtimes[name] = runtime
545
+
546
+ runtime_width = len(f"{max([0, *runtimes.values()]):.1f}")
547
+
548
+ progress = Progress(
549
+ TextColumn("[progress.description]{task.description}"),
550
+ MofNCompleteColumn(),
551
+ BarColumn(bar_width=60),
552
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
553
+ TextColumn(f" {{task.fields[runtime]:>{runtime_width}.1f}}s")
554
+ )
555
+ nodes = 0
556
+ for name, job in job_data.items():
557
+ nodes += len(job.nodes)
558
+ progress.add_task(
559
+ f"[text.primary]Progress ({job.design}/{job.jobname}):",
560
+ total=job.total,
561
+ completed=job.success,
562
+ runtime=runtimes[name]
563
+ )
564
+
565
+ if nodes == 0:
566
+ return Padding("", (0, 0))
567
+
568
+ return Group(progress, Padding("", (0, 0)))
569
+
570
+ def _render_final(self, layout):
571
+ """
572
+ Creates a summary of the final results, including runtime, passed, and failed jobs.
573
+
574
+ Returns:
575
+ Padding: A Rich Padding object containing the summary text.
576
+ """
577
+ with self._render_data_lock:
578
+ success = self._render_data.success
579
+ error = self._render_data.error
580
+ total = self._render_data.total
581
+ finished = self._render_data.finished
582
+ runtime = self._render_data.runtime
583
+
584
+ if finished != 0 and finished == total:
585
+ return Padding(
586
+ f"[text.primary]Results {runtime:.2f}s\n"
587
+ f" [success]{success} passed[/]\n"
588
+ f" [error]{error} failed[/]\n"
589
+ )
590
+
591
+ return self._render_log(layout)
592
+
593
+ def _render(self):
594
+ """
595
+ Main rendering method for the TUI. Continuously updates the dashboard
596
+ with the latest data until the stop event is set.
597
+ """
598
+
599
+ def update_data():
600
+ try:
601
+ self._update_rendable_data()
602
+ except: # noqa E722
603
+ # Catch any multiprocessing errors
604
+ pass
605
+
606
+ def check_stop_event():
607
+ try:
608
+ return self._render_stop_event.is_set()
609
+ except: # noqa E722
610
+ # Catch any multiprocessing errors
611
+ return True
612
+
613
+ try:
614
+ update_data()
615
+
616
+ if not self.live.is_started:
617
+ self.live.start(refresh=True)
618
+
619
+ while not check_stop_event():
620
+ try:
621
+ if self._render_event.wait(timeout=0.2):
622
+ self._render_event.clear()
623
+ except: # noqa E722
624
+ # Catch any multiprocessing errors
625
+ break
626
+
627
+ if check_stop_event():
628
+ break
629
+
630
+ update_data()
631
+ self.live.update(self._get_rendable(), refresh=True)
632
+
633
+ finally:
634
+ update_data()
635
+ if self.live:
636
+ self.live.update(self._get_rendable(), refresh=True)
637
+ else:
638
+ self._console.print(self._get_rendable())
639
+
640
+ def _update_layout(self):
641
+ with self._render_data_lock:
642
+ visible_progress_bars = len(self._render_data.jobs)
643
+ visible_jobs_count = self._render_data.total - self._render_data.skipped
644
+
645
+ self._layout.update(
646
+ self._console.height,
647
+ self._console.width,
648
+ visible_jobs_count,
649
+ visible_progress_bars,
650
+ )
651
+
652
+ return self._layout
653
+
654
+ def _update_rendable_data(self):
655
+ jobs = {}
656
+ with self._job_data_lock:
657
+ if self._board_info.data_modified:
658
+ self._board_info.data_modified = False
659
+ for job, data in self._job_data.items():
660
+ jobs[job] = data
661
+
662
+ if not jobs:
663
+ return
664
+
665
+ with self._render_data_lock:
666
+ self._render_data.jobs.clear()
667
+ for job, data in jobs.items():
668
+ self._render_data.jobs[job] = data
669
+
670
+ self._render_data.total = sum(
671
+ [0, *[job.total for job in self._render_data.jobs.values()]]
672
+ )
673
+ self._render_data.success = sum(
674
+ [0, *[job.success for job in self._render_data.jobs.values()]]
675
+ )
676
+ self._render_data.error = sum(
677
+ [0, *[job.error for job in self._render_data.jobs.values()]]
678
+ )
679
+ self._render_data.skipped = sum(
680
+ [0, *[job.skipped for job in self._render_data.jobs.values()]]
681
+ )
682
+ self._render_data.finished = sum(
683
+ [0, *[job.finished for job in self._render_data.jobs.values()]]
684
+ )
685
+ self._render_data.runtime = max(
686
+ [0, *[job.runtime for job in self._render_data.jobs.values()]]
687
+ )
688
+
689
+ def _get_rendable(self):
690
+ """
691
+ Combines all dashboard components (job table, progress bars, final summary)
692
+ into a single renderable group.
693
+
694
+ Returns:
695
+ Group: A Rich Group object containing all dashboard components.
696
+ """
697
+
698
+ layout = self._update_layout()
699
+
700
+ new_table = self._render_job_dashboard(layout)
701
+ new_bar = self._render_progress_bar(layout)
702
+ footer = self._render_final(layout)
703
+
704
+ items = []
705
+ if new_table:
706
+ items.extend([new_table])
707
+
708
+ if new_bar:
709
+ items.extend([new_bar])
710
+
711
+ if footer:
712
+ items.extend([footer])
713
+
714
+ return Group(*items)
715
+
716
+ def _update_render_data(self, chip, starttimes=None, complete=False):
717
+ """
718
+ Updates the render data with the latest job and node information from the chip object.
719
+ This data is used to populate the dashboard.
720
+ """
721
+
722
+ if not chip:
723
+ return
724
+
725
+ job_data = self._get_job(chip, starttimes=starttimes)
726
+ job_data.complete = complete
727
+
728
+ if not job_data.nodes:
729
+ return
730
+
731
+ chip_id = f"{job_data.design}/{job_data.jobname}"
732
+ with self._job_data_lock:
733
+ self._job_data[chip_id] = job_data
734
+ self._board_info.data_modified = True
735
+ self._render_event.set()
736
+
737
+ def _get_job(self, chip, starttimes=None) -> JobData:
738
+ if not starttimes:
739
+ starttimes = {}
740
+
741
+ nodes = []
742
+ nodestatus = {}
743
+ nodeorder = {}
744
+ node_priority = {}
745
+ try:
746
+ node_inputs = {}
747
+ node_outputs = {}
748
+ flow = chip.get("option", "flow")
749
+ if not flow:
750
+ raise SiliconCompilerError("dummy error")
751
+
752
+ runtime_flow = RuntimeFlowgraph(
753
+ chip.schema.get("flowgraph", flow, field='schema'),
754
+ args=(chip.get('arg', 'step'), chip.get('arg', 'index')),
755
+ to_steps=chip.get('option', 'to'),
756
+ prune_nodes=chip.get('option', 'prune'))
757
+ record = chip.schema.get("record", field='schema')
758
+
759
+ execnodes = runtime_flow.get_nodes()
760
+ lowest_priority = 3 * len(execnodes) # 2x + 1 is lowest computed, so 3x will be lower
761
+ for n, nodeset in enumerate(runtime_flow.get_execution_order()):
762
+ for m, node in enumerate(nodeset):
763
+ if node not in execnodes:
764
+ continue
765
+ nodes.append(node)
766
+
767
+ node_priority[node] = lowest_priority
768
+
769
+ status = chip.get("record", "status", step=node[0], index=node[1])
770
+ if status is None:
771
+ status = NodeStatus.PENDING
772
+ nodestatus[node] = status
773
+ nodeorder[node] = (n, m)
774
+
775
+ node_inputs[node] = runtime_flow.get_node_inputs(*node, record=record)
776
+ for in_node in chip.get('flowgraph', flow, node[0], node[1], 'input'):
777
+ node_outputs.setdefault(in_node, set()).add(node)
778
+
779
+ flow_entry_nodes = set(
780
+ chip.schema.get("flowgraph", flow, field="schema").get_entry_nodes())
781
+
782
+ running_nodes = set([node for node in nodes if NodeStatus.is_running(nodestatus[node])])
783
+ done_nodes = set([node for node in nodes if NodeStatus.is_done(nodestatus[node])])
784
+ error_nodes = set([node for node in nodes if NodeStatus.is_error(nodestatus[node])])
785
+
786
+ def get_node_distance(node, search, level=1):
787
+ dists = {}
788
+
789
+ if node not in search:
790
+ return dists
791
+
792
+ for snode in search[node]:
793
+ dists[snode] = level
794
+ dists.update(get_node_distance(snode, search, level=level+1))
795
+
796
+ return dists
797
+
798
+ # Compute relative node distances
799
+ node_dists = {}
800
+ for cnode in nodes:
801
+ # use 2x + 1 to give completed nodes sorting priority
802
+ node_dists[cnode] = {
803
+ node: 2*level+1 for node, level in get_node_distance(cnode, node_inputs).items()
804
+ }
805
+ node_dists[cnode].update({
806
+ node: 2*level for node, level in get_node_distance(cnode, node_outputs).items()
807
+ })
808
+
809
+ # Compute printing priority of nodes
810
+ remaining_entry_nodes = flow_entry_nodes - done_nodes
811
+ startnodes = running_nodes.union(remaining_entry_nodes)
812
+ priority_node = {0: startnodes.union(error_nodes)}
813
+ for node in nodes:
814
+ dists = node_dists[node]
815
+ levels = []
816
+ for snode in startnodes:
817
+ if snode not in dists:
818
+ continue
819
+ levels.append(dists[snode])
820
+ if not levels:
821
+ continue
822
+ priority_node.setdefault(min(levels), set()).add(node)
823
+ for level, level_nodes in priority_node.items():
824
+ for node in level_nodes:
825
+ if node not in node_priority:
826
+ continue
827
+ node_priority[node] = min(node_priority[node], level)
828
+ except SiliconCompilerError:
829
+ pass
830
+
831
+ design = chip.get("design")
832
+ jobname = chip.get("option", "jobname")
833
+
834
+ job_data = JobData()
835
+ job_data.jobname = jobname
836
+ job_data.design = design
837
+ totaltimes = [
838
+ chip.get("metric", "totaltime", step=step, index=index) or 0
839
+ for step, index in nodes
840
+ ]
841
+ if not totaltimes:
842
+ totaltimes = [0]
843
+ job_data.runtime = max(totaltimes)
844
+
845
+ for step, index in nodes:
846
+ status = nodestatus[(step, index)]
847
+
848
+ job_data.total += 1
849
+ if NodeStatus.is_error(status):
850
+ job_data.error += 1
851
+ if NodeStatus.is_success(status):
852
+ job_data.success += 1
853
+ if NodeStatus.is_done(status):
854
+ job_data.finished += 1
855
+
856
+ if status == NodeStatus.SKIPPED:
857
+ job_data.skipped += 1
858
+ continue
859
+
860
+ starttime = None
861
+ duration = None
862
+ if NodeStatus.is_done(status):
863
+ duration = chip.get("metric", "tasktime", step=step, index=index)
864
+ if (step, index) in starttimes:
865
+ starttime = starttimes[(step, index)]
866
+
867
+ node_metrics = []
868
+ for metric in self._metrics:
869
+ value = chip.get('metric', metric, step=step, index=index)
870
+ if value is None:
871
+ node_metrics.append("")
872
+ else:
873
+ node_metrics.append(str(value))
874
+
875
+ job_data.nodes.append(
876
+ {
877
+ "step": step,
878
+ "index": index,
879
+ "status": status,
880
+ "time": {
881
+ "start": starttime,
882
+ "duration": duration
883
+ },
884
+ "metrics": node_metrics,
885
+ "log": os.path.join(
886
+ os.path.relpath(
887
+ chip.getworkdir(step=step, index=index),
888
+ chip.cwd,
889
+ ),
890
+ f"{step}.log",
891
+ ),
892
+ "print": {
893
+ "order": nodeorder[(step, index)],
894
+ "priority": node_priority[(step, index)]
895
+ }
896
+ }
897
+ )
898
+
899
+ return job_data