siliconcompiler 0.33.2__py3-none-any.whl → 0.34.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 (104) hide show
  1. siliconcompiler/__init__.py +2 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/_common.py +1 -1
  4. siliconcompiler/apps/sc.py +1 -1
  5. siliconcompiler/apps/sc_issue.py +6 -4
  6. siliconcompiler/apps/sc_remote.py +3 -20
  7. siliconcompiler/apps/sc_show.py +2 -2
  8. siliconcompiler/apps/utils/replay.py +4 -4
  9. siliconcompiler/checklist.py +202 -1
  10. siliconcompiler/core.py +62 -293
  11. siliconcompiler/data/templates/email/general.j2 +3 -3
  12. siliconcompiler/data/templates/email/summary.j2 +1 -1
  13. siliconcompiler/data/templates/issue/README.txt +1 -1
  14. siliconcompiler/data/templates/report/sc_report.j2 +7 -7
  15. siliconcompiler/dependencyschema.py +392 -0
  16. siliconcompiler/design.py +758 -0
  17. siliconcompiler/flowgraph.py +79 -13
  18. siliconcompiler/optimizer/vizier.py +2 -2
  19. siliconcompiler/package/__init__.py +383 -223
  20. siliconcompiler/package/git.py +75 -77
  21. siliconcompiler/package/github.py +70 -97
  22. siliconcompiler/package/https.py +77 -93
  23. siliconcompiler/packageschema.py +260 -0
  24. siliconcompiler/pdk.py +5 -5
  25. siliconcompiler/remote/client.py +33 -15
  26. siliconcompiler/remote/server.py +2 -2
  27. siliconcompiler/report/dashboard/cli/__init__.py +6 -6
  28. siliconcompiler/report/dashboard/cli/board.py +4 -4
  29. siliconcompiler/report/dashboard/web/components/__init__.py +5 -5
  30. siliconcompiler/report/dashboard/web/components/flowgraph.py +4 -4
  31. siliconcompiler/report/dashboard/web/components/graph.py +2 -2
  32. siliconcompiler/report/dashboard/web/state.py +1 -1
  33. siliconcompiler/report/dashboard/web/utils/__init__.py +5 -5
  34. siliconcompiler/report/html_report.py +1 -1
  35. siliconcompiler/report/report.py +4 -4
  36. siliconcompiler/report/summary_table.py +2 -2
  37. siliconcompiler/report/utils.py +5 -5
  38. siliconcompiler/scheduler/__init__.py +3 -1382
  39. siliconcompiler/scheduler/docker.py +263 -0
  40. siliconcompiler/scheduler/run_node.py +10 -21
  41. siliconcompiler/scheduler/scheduler.py +311 -0
  42. siliconcompiler/scheduler/schedulernode.py +944 -0
  43. siliconcompiler/scheduler/send_messages.py +3 -3
  44. siliconcompiler/scheduler/slurm.py +149 -163
  45. siliconcompiler/scheduler/taskscheduler.py +45 -57
  46. siliconcompiler/schema/__init__.py +3 -3
  47. siliconcompiler/schema/baseschema.py +234 -11
  48. siliconcompiler/schema/editableschema.py +4 -0
  49. siliconcompiler/schema/journal.py +210 -0
  50. siliconcompiler/schema/namedschema.py +55 -2
  51. siliconcompiler/schema/parameter.py +14 -1
  52. siliconcompiler/schema/parametervalue.py +1 -34
  53. siliconcompiler/schema/schema_cfg.py +210 -349
  54. siliconcompiler/tool.py +412 -148
  55. siliconcompiler/tools/__init__.py +2 -0
  56. siliconcompiler/tools/builtin/_common.py +5 -5
  57. siliconcompiler/tools/builtin/concatenate.py +7 -7
  58. siliconcompiler/tools/builtin/minimum.py +4 -4
  59. siliconcompiler/tools/builtin/mux.py +4 -4
  60. siliconcompiler/tools/builtin/nop.py +4 -4
  61. siliconcompiler/tools/builtin/verify.py +8 -9
  62. siliconcompiler/tools/execute/exec_input.py +1 -1
  63. siliconcompiler/tools/genfasm/genfasm.py +1 -6
  64. siliconcompiler/tools/openroad/_apr.py +5 -1
  65. siliconcompiler/tools/openroad/antenna_repair.py +1 -1
  66. siliconcompiler/tools/openroad/macro_placement.py +1 -1
  67. siliconcompiler/tools/openroad/power_grid.py +1 -1
  68. siliconcompiler/tools/openroad/scripts/common/procs.tcl +32 -25
  69. siliconcompiler/tools/opensta/timing.py +26 -3
  70. siliconcompiler/tools/slang/__init__.py +2 -2
  71. siliconcompiler/tools/surfer/__init__.py +0 -0
  72. siliconcompiler/tools/surfer/show.py +53 -0
  73. siliconcompiler/tools/surfer/surfer.py +30 -0
  74. siliconcompiler/tools/vpr/route.py +82 -0
  75. siliconcompiler/tools/vpr/vpr.py +23 -6
  76. siliconcompiler/tools/yosys/__init__.py +1 -1
  77. siliconcompiler/tools/yosys/scripts/procs.tcl +143 -0
  78. siliconcompiler/tools/yosys/{sc_synth_asic.tcl → scripts/sc_synth_asic.tcl} +4 -0
  79. siliconcompiler/tools/yosys/{sc_synth_fpga.tcl → scripts/sc_synth_fpga.tcl} +24 -77
  80. siliconcompiler/tools/yosys/syn_fpga.py +14 -0
  81. siliconcompiler/toolscripts/_tools.json +9 -13
  82. siliconcompiler/toolscripts/rhel9/install-vpr.sh +0 -2
  83. siliconcompiler/toolscripts/ubuntu22/install-surfer.sh +33 -0
  84. siliconcompiler/toolscripts/ubuntu24/install-surfer.sh +33 -0
  85. siliconcompiler/utils/__init__.py +4 -24
  86. siliconcompiler/utils/flowgraph.py +29 -28
  87. siliconcompiler/utils/issue.py +23 -29
  88. siliconcompiler/utils/logging.py +37 -7
  89. siliconcompiler/utils/showtools.py +6 -1
  90. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/METADATA +16 -25
  91. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/RECORD +98 -91
  92. siliconcompiler/scheduler/docker_runner.py +0 -254
  93. siliconcompiler/schema/journalingschema.py +0 -242
  94. siliconcompiler/tools/yosys/procs.tcl +0 -71
  95. siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +0 -68
  96. siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +0 -68
  97. siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +0 -68
  98. /siliconcompiler/tools/yosys/{sc_lec.tcl → scripts/sc_lec.tcl} +0 -0
  99. /siliconcompiler/tools/yosys/{sc_screenshot.tcl → scripts/sc_screenshot.tcl} +0 -0
  100. /siliconcompiler/tools/yosys/{syn_strategies.tcl → scripts/syn_strategies.tcl} +0 -0
  101. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/WHEEL +0 -0
  102. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/entry_points.txt +0 -0
  103. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/licenses/LICENSE +0 -0
  104. {siliconcompiler-0.33.2.dist-info → siliconcompiler-0.34.1.dist-info}/top_level.txt +0 -0
siliconcompiler/tool.py CHANGED
@@ -1,4 +1,7 @@
1
1
  import contextlib
2
+ import copy
3
+ import csv
4
+ import gzip
2
5
  import logging
3
6
  import os
4
7
  import psutil
@@ -8,6 +11,7 @@ import shutil
8
11
  import subprocess
9
12
  import sys
10
13
  import time
14
+ import yaml
11
15
 
12
16
  try:
13
17
  import resource
@@ -25,12 +29,14 @@ import os.path
25
29
  from packaging.version import Version, InvalidVersion
26
30
  from packaging.specifiers import SpecifierSet, InvalidSpecifier
27
31
 
28
- from siliconcompiler.schema import NamedSchema
32
+ from siliconcompiler.schema import NamedSchema, Journal
29
33
  from siliconcompiler.schema import EditableSchema, Parameter, PerNode, Scope
34
+ from siliconcompiler.schema.parametertype import NodeType
30
35
  from siliconcompiler.schema.utils import trim
31
36
 
32
- from siliconcompiler import utils
37
+ from siliconcompiler import utils, NodeStatus
33
38
  from siliconcompiler import sc_open
39
+ from siliconcompiler import Schema
34
40
 
35
41
  from siliconcompiler.record import RecordTool
36
42
  from siliconcompiler.flowgraph import RuntimeFlowgraph
@@ -61,13 +67,6 @@ class TaskExecutableNotFound(TaskError):
61
67
 
62
68
 
63
69
  class TaskSchema(NamedSchema):
64
- def __init__(self, name=None):
65
- super().__init__(name=name)
66
-
67
- schema_task(self)
68
-
69
-
70
- class ToolSchema(NamedSchema):
71
70
  __parse_version_check_str = r"""
72
71
  (?P<operator>(==|!=|<=|>=|<|>|~=))
73
72
  \s*
@@ -84,16 +83,27 @@ class ToolSchema(NamedSchema):
84
83
  re.VERBOSE | re.IGNORECASE)
85
84
 
86
85
  def __init__(self, name=None):
87
- super().__init__(name=name)
86
+ super().__init__()
87
+ self.set_name(name)
88
88
 
89
- schema_tool(self)
89
+ schema_task(self)
90
90
 
91
- schema = EditableSchema(self)
92
- schema.insert("task", "default", TaskSchema())
91
+ self.__set_runtime(None)
92
+
93
+ @contextlib.contextmanager
94
+ def runtime(self, chip, step=None, index=None, relpath=None):
95
+ '''
96
+ Sets the runtime information needed to properly execute a task.
97
+ Note: unstable API
93
98
 
94
- self.set_runtime(None)
99
+ Args:
100
+ chip (:class:`Chip`): root schema for the runtime information
101
+ '''
102
+ obj_copy = copy.copy(self)
103
+ obj_copy.__set_runtime(chip, step=step, index=index, relpath=relpath)
104
+ yield obj_copy
95
105
 
96
- def set_runtime(self, chip, step=None, index=None):
106
+ def __set_runtime(self, chip, step=None, index=None, relpath=None):
97
107
  '''
98
108
  Sets the runtime information needed to properly execute a task.
99
109
  Note: unstable API
@@ -104,22 +114,30 @@ class ToolSchema(NamedSchema):
104
114
  self.__chip = None
105
115
  self.__schema_full = None
106
116
  self.__logger = None
117
+ self.__design_name = None
118
+ self.__design_top = None
119
+ self.__cwd = None
120
+ self.__relpath = relpath
107
121
  if chip:
108
122
  self.__chip = chip
109
123
  self.__schema_full = chip.schema
110
124
  self.__logger = chip.logger
125
+ self.__design_name = chip.design
126
+ self.__design_top = chip.top()
127
+ self.__cwd = chip.cwd
111
128
 
112
129
  self.__step = step
113
130
  self.__index = index
114
- self.__tool = None
115
- self.__task = None
116
131
 
117
132
  self.__schema_record = None
118
133
  self.__schema_metric = None
119
134
  self.__schema_flow = None
135
+ self.__schema_flow_runtime = None
136
+ self.__schema_tool = None
120
137
  if self.__schema_full:
121
138
  self.__schema_record = self.__schema_full.get("record", field="schema")
122
139
  self.__schema_metric = self.__schema_full.get("metric", field="schema")
140
+ self.__schema_tool = self._parent()._parent()
123
141
 
124
142
  if not self.__step:
125
143
  self.__step = self.__schema_full.get('arg', 'step')
@@ -133,8 +151,11 @@ class ToolSchema(NamedSchema):
133
151
  if not flow:
134
152
  raise RuntimeError("flow not specified")
135
153
  self.__schema_flow = self.__schema_full.get("flowgraph", flow, field="schema")
136
- self.__tool = self.__schema_flow.get(self.__step, self.__index, 'tool')
137
- self.__task = self.__schema_flow.get(self.__step, self.__index, 'task')
154
+
155
+ self.__schema_flow_runtime = RuntimeFlowgraph(
156
+ self.__schema_flow,
157
+ from_steps=set([step for step, _ in self.__schema_flow.get_entry_nodes()]),
158
+ prune_nodes=self.__schema_full.get('option', 'prune'))
138
159
 
139
160
  def node(self):
140
161
  '''
@@ -147,10 +168,10 @@ class ToolSchema(NamedSchema):
147
168
  def tool(self):
148
169
  '''
149
170
  Returns:
150
- task name
171
+ tool name
151
172
  '''
152
173
 
153
- return self.__tool
174
+ raise NotImplementedError("tool name must be implemented by the child class")
154
175
 
155
176
  def task(self):
156
177
  '''
@@ -158,7 +179,7 @@ class ToolSchema(NamedSchema):
158
179
  task name
159
180
  '''
160
181
 
161
- return self.__task
182
+ raise NotImplementedError("task name must be implemented by the child class")
162
183
 
163
184
  def logger(self):
164
185
  '''
@@ -185,6 +206,10 @@ class ToolSchema(NamedSchema):
185
206
  return self.__schema_metric
186
207
  elif type == "flow":
187
208
  return self.__schema_flow
209
+ elif type == "runtimeflow":
210
+ return self.__schema_flow_runtime
211
+ elif type == "tool":
212
+ return self.__schema_tool
188
213
  else:
189
214
  raise ValueError(f"{type} is not a schema section")
190
215
 
@@ -199,7 +224,7 @@ class ToolSchema(NamedSchema):
199
224
  path to executable, or None if not specified
200
225
  '''
201
226
 
202
- exe = self.get('exe')
227
+ exe = self.schema("tool").get('exe')
203
228
 
204
229
  if exe is None:
205
230
  return None
@@ -226,7 +251,7 @@ class ToolSchema(NamedSchema):
226
251
  version determined by :meth:`.parse_version`.
227
252
  '''
228
253
 
229
- veropt = self.get('vswitch')
254
+ veropt = self.schema("tool").get('vswitch')
230
255
  if not veropt:
231
256
  return None
232
257
 
@@ -239,7 +264,8 @@ class ToolSchema(NamedSchema):
239
264
  cmdlist = [exe]
240
265
  cmdlist.extend(veropt)
241
266
 
242
- self.__logger.debug(f'Running {self.name()} version check: {" ".join(cmdlist)}')
267
+ self.__logger.debug(f'Running {self.tool()}/{self.task()} version check: '
268
+ f'{" ".join(cmdlist)}')
243
269
 
244
270
  proc = subprocess.run(cmdlist,
245
271
  stdin=subprocess.DEVNULL,
@@ -254,9 +280,11 @@ class ToolSchema(NamedSchema):
254
280
  try:
255
281
  version = self.parse_version(proc.stdout)
256
282
  except NotImplementedError:
257
- raise NotImplementedError(f'{self.name()} does not implement parse_version()')
283
+ raise NotImplementedError(f'{self.tool()}/{self.task()} does not implement '
284
+ 'parse_version()')
258
285
  except Exception as e:
259
- self.__logger.error(f'{self.name()} failed to parse version string: {proc.stdout}')
286
+ self.__logger.error(f'{self.tool()}/{self.task()} failed to parse version string: '
287
+ f'{proc.stdout}')
260
288
  raise e from None
261
289
 
262
290
  self.__logger.info(f"Tool '{exe_base}' found with version '{version}' "
@@ -277,7 +305,7 @@ class ToolSchema(NamedSchema):
277
305
 
278
306
  '''
279
307
 
280
- spec_sets = self.get('version', step=self.__step, index=self.__index)
308
+ spec_sets = self.schema("tool").get('version', step=self.__step, index=self.__index)
281
309
  if not spec_sets:
282
310
  # No requirement so always true
283
311
  return True
@@ -286,7 +314,7 @@ class ToolSchema(NamedSchema):
286
314
  split_specs = [s.strip() for s in spec_set.split(",") if s.strip()]
287
315
  specs_list = []
288
316
  for spec in split_specs:
289
- match = re.match(ToolSchema.__parse_version_check, spec)
317
+ match = re.match(TaskSchema.__parse_version_check, spec)
290
318
  if match is None:
291
319
  self.__logger.warning(f'Invalid version specifier {spec}. '
292
320
  f'Defaulting to =={spec}.')
@@ -300,15 +328,15 @@ class ToolSchema(NamedSchema):
300
328
  try:
301
329
  normalized_version = self.normalize_version(reported_version)
302
330
  except Exception as e:
303
- self.__logger.error(f'Unable to normalize version for {self.name()}: '
331
+ self.__logger.error(f'Unable to normalize version for {self.tool()}/{self.task()}: '
304
332
  f'{reported_version}')
305
333
  raise e from None
306
334
 
307
335
  try:
308
336
  version = Version(normalized_version)
309
337
  except InvalidVersion:
310
- self.__logger.error(f'Version {normalized_version} reported by {self.name()} does '
311
- 'not match standard.')
338
+ self.__logger.error(f'Version {normalized_version} reported by '
339
+ f'{self.tool()}/{self.task()} does not match standard.')
312
340
  return False
313
341
 
314
342
  try:
@@ -316,7 +344,8 @@ class ToolSchema(NamedSchema):
316
344
  f'{op}{self.normalize_version(ver)}' for op, ver in specs_list]
317
345
  normalized_specs = ','.join(normalized_spec_list)
318
346
  except Exception as e:
319
- self.__logger.error(f'Unable to normalize versions for {self.name()}: '
347
+ self.__logger.error(f'Unable to normalize versions for '
348
+ f'{self.tool()}/{self.task()}: '
320
349
  f'{",".join([f"{op}{ver}" for op, ver in specs_list])}')
321
350
  raise e from None
322
351
 
@@ -331,7 +360,8 @@ class ToolSchema(NamedSchema):
331
360
  return True
332
361
 
333
362
  allowedstr = '; '.join(spec_sets)
334
- self.__logger.error(f"Version check failed for {self.name()}. Check installation.")
363
+ self.__logger.error(f"Version check failed for {self.tool()}/{self.task()}. "
364
+ "Check installation.")
335
365
  self.__logger.error(f"Found version {reported_version}, "
336
366
  f"did not satisfy any version specifier set {allowedstr}.")
337
367
  return False
@@ -353,19 +383,21 @@ class ToolSchema(NamedSchema):
353
383
  envvars[env] = self.__schema_full.get('option', 'env', env)
354
384
 
355
385
  # Add tool specific vars
356
- for lic_env in self.getkeys('licenseserver'):
357
- license_file = self.get('licenseserver', lic_env, step=self.__step, index=self.__index)
386
+ for lic_env in self.schema("tool").getkeys('licenseserver'):
387
+ license_file = self.schema("tool").get('licenseserver', lic_env,
388
+ step=self.__step, index=self.__index)
358
389
  if license_file:
359
390
  envvars[lic_env] = ':'.join(license_file)
360
391
 
361
392
  if include_path:
362
- path_param = self.get('path', field=None, step=self.__step, index=self.__index)
363
- if path_param.get(field='package'):
364
- raise NotImplementedError
393
+ path = self.schema("tool").find_files(
394
+ "path", step=self.__step, index=self.__index,
395
+ packages=self.schema().get("package", field="schema").get_resolvers(),
396
+ cwd=self.__cwd,
397
+ missing_ok=True)
365
398
 
366
399
  envvars["PATH"] = os.getenv("PATH", os.defpath)
367
400
 
368
- path = path_param.get(field=None).resolve_path() # TODO: needs package search
369
401
  if path:
370
402
  envvars["PATH"] = path + os.pathsep + envvars["PATH"]
371
403
 
@@ -376,9 +408,8 @@ class ToolSchema(NamedSchema):
376
408
  envvars[var] = val
377
409
 
378
410
  # Add task specific vars
379
- for env in self.getkeys('task', self.__task, 'env'):
380
- envvars[env] = self.get('task', self.__task, 'env', env,
381
- step=self.__step, index=self.__index)
411
+ for env in self.getkeys("env"):
412
+ envvars[env] = self.get("env", env)
382
413
 
383
414
  return envvars
384
415
 
@@ -391,19 +422,20 @@ class ToolSchema(NamedSchema):
391
422
  '''
392
423
 
393
424
  cmdargs = []
394
- cmdargs.extend(self.get('task', self.__task, 'option',
395
- step=self.__step, index=self.__index))
396
-
397
- # Add scripts files / TODO:
398
- scripts = self.__chip.find_files('tool', self.__tool, 'task', self.__task, 'script',
399
- step=self.__step, index=self.__index)
400
-
401
- cmdargs.extend(scripts)
402
-
403
425
  try:
404
- cmdargs.extend(self.runtime_options())
426
+ if self.__relpath:
427
+ args = []
428
+ for arg in self.runtime_options():
429
+ if os.path.isabs(arg) and os.path.exists(arg):
430
+ args.append(os.path.relpath(arg, self.__relpath))
431
+ else:
432
+ args.append(arg)
433
+ else:
434
+ args = self.runtime_options()
435
+
436
+ cmdargs.extend(args)
405
437
  except Exception as e:
406
- self.__logger.error(f'Failed to get runtime options for {self.name()}/{self.__task}')
438
+ self.__logger.error(f'Failed to get runtime options for {self.tool()}/{self.task()}')
407
439
  raise e from None
408
440
 
409
441
  # Cleanup args
@@ -424,13 +456,13 @@ class ToolSchema(NamedSchema):
424
456
  replay_opts["work_dir"] = workdir
425
457
  replay_opts["exports"] = self.get_runtime_environmental_variables(include_path=include_path)
426
458
 
427
- replay_opts["executable"] = self.get('exe')
459
+ replay_opts["executable"] = self.schema("tool").get('exe')
428
460
  replay_opts["step"] = self.__step
429
461
  replay_opts["index"] = self.__index
430
- replay_opts["cfg_file"] = f"inputs/{self.__chip.design}.pkg.json"
462
+ replay_opts["cfg_file"] = f"inputs/{self.__design_name}.pkg.json"
431
463
  replay_opts["node_only"] = 0 if replay_opts["executable"] else 1
432
464
 
433
- vswitch = self.get('vswitch')
465
+ vswitch = self.schema("tool").get('vswitch')
434
466
  if vswitch:
435
467
  replay_opts["version_flag"] = shlex.join(vswitch)
436
468
 
@@ -487,6 +519,66 @@ class ToolSchema(NamedSchema):
487
519
  os.makedirs(os.path.join(workdir, 'outputs'), exist_ok=True)
488
520
  os.makedirs(os.path.join(workdir, 'reports'), exist_ok=True)
489
521
 
522
+ def __write_yaml_manifest(self, fout, manifest):
523
+ class YamlIndentDumper(yaml.Dumper):
524
+ def increase_indent(self, flow=False, indentless=False):
525
+ return super().increase_indent(flow=flow, indentless=indentless)
526
+
527
+ fout.write(yaml.dump(manifest.getdict(), Dumper=YamlIndentDumper,
528
+ default_flow_style=False))
529
+
530
+ def __write_tcl_manifest(self, fout, manifest):
531
+ template = utils.get_file_template('tcl/manifest.tcl.j2')
532
+ tcl_set_cmds = []
533
+ for key in sorted(manifest.allkeys()):
534
+ # print out all non default values
535
+ if 'default' in key:
536
+ continue
537
+
538
+ param = manifest.get(*key, field=None)
539
+
540
+ # create a TCL dict
541
+ keystr = ' '.join([NodeType.to_tcl(keypart, 'str') for keypart in key])
542
+
543
+ valstr = param.gettcl(step=self.__step, index=self.__index)
544
+ if valstr is None:
545
+ continue
546
+
547
+ # Ensure empty values get something
548
+ if valstr == '':
549
+ valstr = '{}'
550
+
551
+ tcl_set_cmds.append(f"dict set sc_cfg {keystr} {valstr}")
552
+
553
+ if template:
554
+ fout.write(template.render(manifest_dict='\n'.join(tcl_set_cmds),
555
+ scroot=os.path.abspath(
556
+ os.path.join(os.path.dirname(__file__))),
557
+ record_access="get" in Journal.access(self).get_types(),
558
+ record_access_id=Schema._RECORD_ACCESS_IDENTIFIER))
559
+ else:
560
+ for cmd in tcl_set_cmds:
561
+ fout.write(cmd + '\n')
562
+ fout.write('\n')
563
+
564
+ def __write_csv_manifest(self, fout, manifest):
565
+ csvwriter = csv.writer(fout)
566
+ csvwriter.writerow(['Keypath', 'Value'])
567
+
568
+ for key in sorted(manifest.allkeys()):
569
+ keypath = ','.join(key)
570
+ param = manifest.get(*key, field=None)
571
+ if param.get(field="pernode").is_never():
572
+ value = param.get()
573
+ else:
574
+ value = param.get(step=self.__step, index=self.__index)
575
+
576
+ if isinstance(value, (set, list)):
577
+ for item in value:
578
+ csvwriter.writerow([keypath, item])
579
+ else:
580
+ csvwriter.writerow([keypath, value])
581
+
490
582
  def write_task_manifest(self, directory, backup=True):
491
583
  '''
492
584
  Write the manifest needed for the task
@@ -496,7 +588,7 @@ class ToolSchema(NamedSchema):
496
588
  backup (bool): if True and an existing manifest is found a backup is kept.
497
589
  '''
498
590
 
499
- suffix = self.get('format')
591
+ suffix = self.schema("tool").get('format')
500
592
  if not suffix:
501
593
  return
502
594
 
@@ -505,8 +597,65 @@ class ToolSchema(NamedSchema):
505
597
  if backup and os.path.exists(manifest_path):
506
598
  shutil.copyfile(manifest_path, f'{manifest_path}.bak')
507
599
 
508
- # TODO: pull in TCL/yaml here
509
- self.__chip.write_manifest(manifest_path, abspath=True)
600
+ # Generate abs paths
601
+ schema = self.__abspath_schema()
602
+
603
+ if re.search(r'\.json(\.gz)?$', manifest_path):
604
+ schema.write_manifest(manifest_path)
605
+ else:
606
+ try:
607
+ # format specific dumping
608
+ if manifest_path.endswith('.gz'):
609
+ fout = gzip.open(manifest_path, 'wt', encoding='UTF-8')
610
+ elif re.search(r'\.csv$', manifest_path):
611
+ # Files written using csv library should be opened with newline=''
612
+ # https://docs.python.org/3/library/csv.html#id3
613
+ fout = open(manifest_path, 'w', newline='')
614
+ else:
615
+ fout = open(manifest_path, 'w')
616
+
617
+ if re.search(r'(\.yaml|\.yml)(\.gz)?$', manifest_path):
618
+ self.__write_yaml_manifest(fout, schema)
619
+ elif re.search(r'\.tcl(\.gz)?$', manifest_path):
620
+ self.__write_tcl_manifest(fout, schema)
621
+ elif re.search(r'\.csv(\.gz)?$', manifest_path):
622
+ self.__write_csv_manifest(fout, schema)
623
+ else:
624
+ raise ValueError(f"{manifest_path} is not a recognized path type")
625
+ finally:
626
+ fout.close()
627
+
628
+ def __abspath_schema(self):
629
+ root = self.schema()
630
+ schema = root.copy()
631
+
632
+ strict = root.get("option", "strict")
633
+ root.set("option", "strict", False)
634
+
635
+ for keypath in root.allkeys():
636
+ paramtype = schema.get(*keypath, field='type')
637
+ if 'file' not in paramtype and 'dir' not in paramtype:
638
+ # only do something if type is file or dir
639
+ continue
640
+
641
+ for value, step, index in root.get(*keypath, field=None).getvalues():
642
+ if not value:
643
+ continue
644
+ abspaths = root.find_files(*keypath, missing_ok=True, step=step, index=index)
645
+ if isinstance(abspaths, (set, list)) and None in abspaths:
646
+ # Lists may not contain None
647
+ schema.set(*keypath, [], step=step, index=index)
648
+ else:
649
+ if self.__relpath:
650
+ if isinstance(abspaths, (set, list)):
651
+ abspaths = [os.path.relpath(path, self.__relpath) for path in abspaths]
652
+ else:
653
+ abspaths = os.path.relpath(abspaths, self.__relpath)
654
+ schema.set(*keypath, abspaths, step=step, index=index)
655
+
656
+ root.set("option", "strict", strict)
657
+
658
+ return schema
510
659
 
511
660
  def __get_io_file(self, io_type):
512
661
  '''
@@ -515,10 +664,8 @@ class ToolSchema(NamedSchema):
515
664
  Args:
516
665
  io_type (str): name of io type
517
666
  '''
518
- suffix = self.get('task', self.__task, io_type, 'suffix',
519
- step=self.__step, index=self.__index)
520
- destination = self.get('task', self.__task, io_type, 'destination',
521
- step=self.__step, index=self.__index)
667
+ suffix = self.get(io_type, "suffix")
668
+ destination = self.get(io_type, "destination")
522
669
 
523
670
  io_file = None
524
671
  io_log = False
@@ -526,7 +673,7 @@ class ToolSchema(NamedSchema):
526
673
  io_file = f"{self.__step}.{suffix}"
527
674
  io_log = True
528
675
  elif destination == 'output':
529
- io_file = os.path.join('outputs', f"{self.__chip.top()}.{suffix}")
676
+ io_file = os.path.join('outputs', f"{self.__design_top}.{suffix}")
530
677
  elif destination == 'none':
531
678
  io_file = os.devnull
532
679
 
@@ -565,13 +712,13 @@ class ToolSchema(NamedSchema):
565
712
  TERMINATE_TIMEOUT = 5
566
713
 
567
714
  terminate_process(proc.pid, timeout=TERMINATE_TIMEOUT)
568
- self.__logger.info(f'Waiting for {self.name()} to exit...')
715
+ self.__logger.info(f'Waiting for {self.tool()}/{self.task()} to exit...')
569
716
  try:
570
717
  proc.wait(timeout=TERMINATE_TIMEOUT)
571
718
  except subprocess.TimeoutExpired:
572
719
  if proc.poll() is None:
573
- self.__logger.warning(f'{self.name()} did not exit within {TERMINATE_TIMEOUT} '
574
- 'seconds. Terminating...')
720
+ self.__logger.warning(f'{self.tool()}/{self.task()} did not exit within '
721
+ f'{TERMINATE_TIMEOUT} seconds. Terminating...')
575
722
  terminate_process(proc.pid, timeout=TERMINATE_TIMEOUT)
576
723
 
577
724
  def run_task(self, workdir, quiet, loglevel, breakpoint, nice, timeout):
@@ -642,7 +789,7 @@ class ToolSchema(NamedSchema):
642
789
  contextlib.redirect_stdout(stdout_writer):
643
790
  retcode = self.run()
644
791
  except Exception as e:
645
- self.__logger.error(f'Failed in run() for {self.name()}/{self.__task}: {e}')
792
+ self.__logger.error(f'Failed in run() for {self.tool()}/{self.task()}: {e}')
646
793
  utils.print_traceback(self.__logger, e)
647
794
  raise e
648
795
  finally:
@@ -690,9 +837,9 @@ class ToolSchema(NamedSchema):
690
837
  retcode = pty.spawn([exe, *cmdlist], read)
691
838
  else:
692
839
  with open(stdout_file, 'w') as stdout_writer, \
693
- open(stdout_file, 'r', errors='replace_with_warning') as stdout_reader, \
840
+ open(stdout_file, 'r', errors='replace') as stdout_reader, \
694
841
  open(stderr_file, 'w') as stderr_writer, \
695
- open(stderr_file, 'r', errors='replace_with_warning') as stderr_reader:
842
+ open(stderr_file, 'r', errors='replace') as stderr_reader:
696
843
  # if STDOUT and STDERR are to be redirected to the same file,
697
844
  # use a single writer
698
845
  if stderr_file == stdout_file:
@@ -789,7 +936,7 @@ class ToolSchema(NamedSchema):
789
936
 
790
937
  # Remove runtime information
791
938
  for key in list(state.keys()):
792
- if key.startswith("_ToolSchema__"):
939
+ if key.startswith("_TaskSchema__"):
793
940
  del state[key]
794
941
 
795
942
  return state
@@ -798,10 +945,101 @@ class ToolSchema(NamedSchema):
798
945
  self.__dict__ = state
799
946
 
800
947
  # Reinit runtime information
801
- self.set_runtime(None)
948
+ self.__set_runtime(None)
802
949
 
803
950
  def get_output_files(self):
804
- return set(self.get("task", self.__task, "output", step=self.__step, index=self.__index))
951
+ return set(self.get("output"))
952
+
953
+ def get_files_from_input_nodes(self):
954
+ """
955
+ Returns a dictionary of files with the node they originated from
956
+ """
957
+
958
+ nodes = self.schema("runtimeflow").get_nodes()
959
+
960
+ inputs = {}
961
+ for in_step, in_index in self.schema("flow").get(*self.node(), 'input'):
962
+ if (in_step, in_index) not in nodes:
963
+ # node has been pruned so will not provide anything
964
+ continue
965
+
966
+ in_tool = self.schema("flow").get(in_step, in_index, "tool")
967
+ in_task = self.schema("flow").get(in_step, in_index, "task")
968
+
969
+ task_obj = self.schema().get("tool", in_tool, "task", in_task, field="schema")
970
+
971
+ if self.schema("record").get('status', step=in_step, index=in_index) == \
972
+ NodeStatus.SKIPPED:
973
+ with task_obj.runtime(self.__chip, step=in_step, index=in_index) as task:
974
+ for file, nodes in task.get_files_from_input_nodes().items():
975
+ inputs.setdefault(file, []).extend(nodes)
976
+ continue
977
+
978
+ for output in NamedSchema.get(task_obj, "output", step=in_step, index=in_index):
979
+ inputs.setdefault(output, []).append((in_step, in_index))
980
+
981
+ return inputs
982
+
983
+ def compute_input_file_node_name(self, filename, step, index):
984
+ """
985
+ Generate a unique name for in input file based on the originating node.
986
+
987
+ Args:
988
+ filename (str): name of inputfile
989
+ step (str): Step name
990
+ index (str): Index name
991
+ """
992
+
993
+ _, file_type = os.path.splitext(filename)
994
+
995
+ if file_type:
996
+ base = filename
997
+ total_ext = []
998
+ while file_type:
999
+ base, file_type = os.path.splitext(base)
1000
+ total_ext.append(file_type)
1001
+
1002
+ total_ext.reverse()
1003
+
1004
+ return f'{base}.{step}{index}{"".join(total_ext)}'
1005
+ else:
1006
+ return f'{filename}.{step}{index}'
1007
+
1008
+ def add_parameter(self, name, type, help, defvalue=None):
1009
+ '''
1010
+ Adds a parameter to the task definition.
1011
+
1012
+ Args:
1013
+ name (str): name of parameter
1014
+ type (str): schema type of the parameter
1015
+ help (str): help string for this parameter
1016
+ defvalue (any): default value for the parameter
1017
+ '''
1018
+ help = trim(help)
1019
+ param = Parameter(
1020
+ type,
1021
+ defvalue=defvalue,
1022
+ scope=Scope.JOB,
1023
+ pernode=PerNode.OPTIONAL,
1024
+ shorthelp=help,
1025
+ help=help
1026
+ )
1027
+
1028
+ EditableSchema(self).insert("var", name, param)
1029
+
1030
+ return param
1031
+
1032
+ ###############################################################
1033
+ def get(self, *keypath, field='value'):
1034
+ return super().get(*keypath, field=field,
1035
+ step=self.__step, index=self.__index)
1036
+
1037
+ def set(self, *args, field='value', clobber=True):
1038
+ return super().set(*args, field=field, clobber=clobber,
1039
+ step=self.__step, index=self.__index)
1040
+
1041
+ def add(self, *args, field='value'):
1042
+ return super().add(*args, field=field, step=self.__step, index=self.__index)
805
1043
 
806
1044
  ###############################################################
807
1045
  def parse_version(self, stdout):
@@ -814,19 +1052,23 @@ class ToolSchema(NamedSchema):
814
1052
  pass
815
1053
 
816
1054
  def select_input_nodes(self):
817
- flow = self.schema("flow")
818
- runtime = RuntimeFlowgraph(
819
- flow,
820
- from_steps=set([step for step, _ in flow.get_entry_nodes()]),
821
- prune_nodes=self.__chip.get('option', 'prune'))
822
-
823
- return runtime.get_node_inputs(self.__step, self.__index, record=self.schema("record"))
1055
+ return self.schema("runtimeflow").get_node_inputs(
1056
+ self.__step, self.__index, record=self.schema("record"))
824
1057
 
825
1058
  def pre_process(self):
826
1059
  pass
827
1060
 
828
1061
  def runtime_options(self):
829
- return []
1062
+ cmdargs = []
1063
+ cmdargs.extend(self.get("option"))
1064
+
1065
+ # Add scripts files / TODO:
1066
+ scripts = self.__chip.find_files('tool', self.tool(), 'task', self.task(), 'script',
1067
+ step=self.__step, index=self.__index)
1068
+
1069
+ cmdargs.extend(scripts)
1070
+
1071
+ return cmdargs
830
1072
 
831
1073
  def run(self):
832
1074
  raise NotImplementedError("must be implemented by the implementation class")
@@ -835,10 +1077,34 @@ class ToolSchema(NamedSchema):
835
1077
  pass
836
1078
 
837
1079
 
1080
+ class ToolSchema(NamedSchema):
1081
+ def __init__(self, name=None):
1082
+ super().__init__()
1083
+ self.set_name(name)
1084
+
1085
+ schema_tool(self)
1086
+
1087
+ schema = EditableSchema(self)
1088
+ schema.insert("task", "default", TaskSchema(None))
1089
+
1090
+
838
1091
  ###########################################################################
839
1092
  # Migration helper
840
1093
  ###########################################################################
841
- class ToolSchemaTmp(ToolSchema):
1094
+ class ToolSchemaTmp(NamedSchema):
1095
+ def __init__(self):
1096
+ super().__init__()
1097
+
1098
+ schema_tool(self)
1099
+
1100
+ schema = EditableSchema(self)
1101
+ schema.insert("task", "default", TaskSchemaTmp())
1102
+
1103
+
1104
+ class TaskSchemaTmp(TaskSchema):
1105
+ def __init__(self):
1106
+ super().__init__()
1107
+
842
1108
  def __module_func(self, name, modules):
843
1109
  for module in modules:
844
1110
  method = getattr(module, name, None)
@@ -848,91 +1114,101 @@ class ToolSchemaTmp(ToolSchema):
848
1114
 
849
1115
  def __tool_task_modules(self):
850
1116
  step, index = self.node()
851
- flow = self._ToolSchema__chip.get('option', 'flow')
1117
+ flow = self._TaskSchema__chip.get('option', 'flow')
852
1118
  return \
853
- self._ToolSchema__chip._get_tool_module(step, index, flow=flow), \
854
- self._ToolSchema__chip._get_task_module(step, index, flow=flow)
1119
+ self._TaskSchema__chip._get_tool_module(step, index, flow=flow), \
1120
+ self._TaskSchema__chip._get_task_module(step, index, flow=flow)
1121
+
1122
+ @contextlib.contextmanager
1123
+ def __in_step_index(self):
1124
+ prev_step, prev_index = self._TaskSchema__chip.get('arg', 'step'), \
1125
+ self._TaskSchema__chip.get('arg', 'index')
1126
+ step, index = self.node()
1127
+ self._TaskSchema__chip.set('arg', 'step', step)
1128
+ self._TaskSchema__chip.set('arg', 'index', index)
1129
+ yield
1130
+ self._TaskSchema__chip.set('arg', 'step', prev_step)
1131
+ self._TaskSchema__chip.set('arg', 'index', prev_index)
1132
+
1133
+ def tool(self):
1134
+ return self.schema("flow").get(*self.node(), 'tool')
1135
+
1136
+ def task(self):
1137
+ return self.schema("flow").get(*self.node(), 'task')
1138
+
1139
+ def get_exe(self):
1140
+ if self.tool() == "execute" and self.task() == "exec_input":
1141
+ return self.schema("tool").get("exe")
1142
+ return super().get_exe()
1143
+
1144
+ def schema(self, type=None):
1145
+ if type is None:
1146
+ return self._TaskSchema__chip
1147
+ return super().schema(type)
855
1148
 
856
1149
  def get_output_files(self):
857
1150
  _, task = self.__tool_task_modules()
858
1151
  method = self.__module_func("_gather_outputs", [task])
859
1152
  if method:
860
- return method(self._ToolSchema__chip, *self.node())
861
- return ToolSchema.get_output_files(self)
1153
+ return method(self._TaskSchema__chip, *self.node())
1154
+ return TaskSchema.get_output_files(self)
862
1155
 
863
1156
  def parse_version(self, stdout):
864
1157
  tool, _ = self.__tool_task_modules()
865
1158
  method = self.__module_func("parse_version", [tool])
866
1159
  if method:
867
1160
  return method(stdout)
868
- return ToolSchema.parse_version(self, stdout)
1161
+ return TaskSchema.parse_version(self, stdout)
869
1162
 
870
1163
  def normalize_version(self, version):
871
1164
  tool, _ = self.__tool_task_modules()
872
1165
  method = self.__module_func("normalize_version", [tool])
873
1166
  if method:
874
1167
  return method(version)
875
- return ToolSchema.normalize_version(self, version)
1168
+ return TaskSchema.normalize_version(self, version)
1169
+
1170
+ def generate_replay_script(self, filepath, workdir, include_path=True):
1171
+ with self.__in_step_index():
1172
+ ret = TaskSchema.generate_replay_script(self, filepath, workdir,
1173
+ include_path=include_path)
1174
+ return ret
876
1175
 
877
1176
  def setup(self):
878
1177
  _, task = self.__tool_task_modules()
879
1178
  method = self.__module_func("setup", [task])
880
1179
  if method:
881
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
882
- self._ToolSchema__chip.get('arg', 'index')
883
- step, index = self.node()
884
- self._ToolSchema__chip.set('arg', 'step', step)
885
- self._ToolSchema__chip.set('arg', 'index', index)
886
- ret = method(self._ToolSchema__chip)
887
- self._ToolSchema__chip.set('arg', 'step', prev_step)
888
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1180
+ with self.__in_step_index():
1181
+ ret = method(self._TaskSchema__chip)
889
1182
  return ret
890
- return ToolSchema.setup(self)
1183
+ return TaskSchema.setup(self)
891
1184
 
892
1185
  def select_input_nodes(self):
893
1186
  _, task = self.__tool_task_modules()
894
1187
  method = self.__module_func("_select_inputs", [task])
895
1188
  if method:
896
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
897
- self._ToolSchema__chip.get('arg', 'index')
898
- step, index = self.node()
899
- self._ToolSchema__chip.set('arg', 'step', step)
900
- self._ToolSchema__chip.set('arg', 'index', index)
901
- ret = method(self._ToolSchema__chip, *self.node())
902
- self._ToolSchema__chip.set('arg', 'step', prev_step)
903
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1189
+ with self.__in_step_index():
1190
+ ret = method(self._TaskSchema__chip, *self.node())
904
1191
  return ret
905
- return ToolSchema.select_input_nodes(self)
1192
+ return TaskSchema.select_input_nodes(self)
906
1193
 
907
1194
  def pre_process(self):
908
1195
  _, task = self.__tool_task_modules()
909
1196
  method = self.__module_func("pre_process", [task])
910
1197
  if method:
911
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
912
- self._ToolSchema__chip.get('arg', 'index')
913
- step, index = self.node()
914
- self._ToolSchema__chip.set('arg', 'step', step)
915
- self._ToolSchema__chip.set('arg', 'index', index)
916
- ret = method(self._ToolSchema__chip)
917
- self._ToolSchema__chip.set('arg', 'step', prev_step)
918
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1198
+ with self.__in_step_index():
1199
+ ret = method(self._TaskSchema__chip)
919
1200
  return ret
920
- return ToolSchema.pre_process(self)
1201
+ return TaskSchema.pre_process(self)
921
1202
 
922
1203
  def runtime_options(self):
923
1204
  tool, task = self.__tool_task_modules()
924
1205
  method = self.__module_func("runtime_options", [task, tool])
925
1206
  if method:
926
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
927
- self._ToolSchema__chip.get('arg', 'index')
928
- step, index = self.node()
929
- self._ToolSchema__chip.set('arg', 'step', step)
930
- self._ToolSchema__chip.set('arg', 'index', index)
931
- ret = method(self._ToolSchema__chip)
932
- self._ToolSchema__chip.set('arg', 'step', prev_step)
933
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1207
+ with self.__in_step_index():
1208
+ ret = TaskSchema.runtime_options(self)
1209
+ ret.extend(method(self._TaskSchema__chip))
934
1210
  return ret
935
- return ToolSchema.runtime_options(self)
1211
+ return TaskSchema.runtime_options(self)
936
1212
 
937
1213
  def run(self):
938
1214
  _, task = self.__tool_task_modules()
@@ -940,38 +1216,26 @@ class ToolSchemaTmp(ToolSchema):
940
1216
  if method:
941
1217
  # Handle logger stdout suppression if quiet
942
1218
  step, index = self.node()
943
- stdout_handler_level = self._ToolSchema__chip.logger._console.level
944
- if self._ToolSchema__chip.get('option', 'quiet', step=step, index=index):
945
- self._ToolSchema__chip.logger._console.setLevel(logging.CRITICAL)
1219
+ stdout_handler_level = self._TaskSchema__chip._logger_console.level
1220
+ if self._TaskSchema__chip.get('option', 'quiet', step=step, index=index):
1221
+ self._TaskSchema__chip._logger_console.setLevel(logging.CRITICAL)
946
1222
 
947
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
948
- self._ToolSchema__chip.get('arg', 'index')
949
- step, index = self.node()
950
- self._ToolSchema__chip.set('arg', 'step', step)
951
- self._ToolSchema__chip.set('arg', 'index', index)
952
- retcode = method(self._ToolSchema__chip)
953
- self._ToolSchema__chip.set('arg', 'step', prev_step)
954
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1223
+ with self.__in_step_index():
1224
+ retcode = method(self._TaskSchema__chip)
955
1225
 
956
- self._ToolSchema__chip.logger._console.setLevel(stdout_handler_level)
1226
+ self._TaskSchema__chip._logger_console.setLevel(stdout_handler_level)
957
1227
 
958
1228
  return retcode
959
- return ToolSchema.run(self)
1229
+ return TaskSchema.run(self)
960
1230
 
961
1231
  def post_process(self):
962
1232
  _, task = self.__tool_task_modules()
963
1233
  method = self.__module_func("post_process", [task])
964
1234
  if method:
965
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
966
- self._ToolSchema__chip.get('arg', 'index')
967
- step, index = self.node()
968
- self._ToolSchema__chip.set('arg', 'step', step)
969
- self._ToolSchema__chip.set('arg', 'index', index)
970
- ret = method(self._ToolSchema__chip)
971
- self._ToolSchema__chip.set('arg', 'step', prev_step)
972
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1235
+ with self.__in_step_index():
1236
+ ret = method(self._TaskSchema__chip)
973
1237
  return ret
974
- return ToolSchema.post_process(self)
1238
+ return TaskSchema.post_process(self)
975
1239
 
976
1240
 
977
1241
  ###########################################################################