siliconcompiler 0.34.0__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 (88) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/_common.py +1 -1
  3. siliconcompiler/apps/sc.py +1 -1
  4. siliconcompiler/apps/sc_issue.py +1 -1
  5. siliconcompiler/apps/sc_remote.py +3 -3
  6. siliconcompiler/apps/sc_show.py +2 -2
  7. siliconcompiler/apps/utils/replay.py +4 -4
  8. siliconcompiler/checklist.py +203 -2
  9. siliconcompiler/core.py +28 -246
  10. siliconcompiler/data/templates/email/general.j2 +3 -3
  11. siliconcompiler/data/templates/email/summary.j2 +1 -1
  12. siliconcompiler/data/templates/issue/README.txt +1 -1
  13. siliconcompiler/data/templates/report/sc_report.j2 +7 -7
  14. siliconcompiler/design.py +148 -54
  15. siliconcompiler/flowgraph.py +50 -15
  16. siliconcompiler/optimizer/vizier.py +2 -2
  17. siliconcompiler/pdk.py +5 -5
  18. siliconcompiler/remote/client.py +18 -12
  19. siliconcompiler/remote/server.py +2 -2
  20. siliconcompiler/report/dashboard/cli/__init__.py +6 -6
  21. siliconcompiler/report/dashboard/cli/board.py +3 -3
  22. siliconcompiler/report/dashboard/web/components/__init__.py +5 -5
  23. siliconcompiler/report/dashboard/web/components/flowgraph.py +4 -4
  24. siliconcompiler/report/dashboard/web/components/graph.py +2 -2
  25. siliconcompiler/report/dashboard/web/state.py +1 -1
  26. siliconcompiler/report/dashboard/web/utils/__init__.py +5 -5
  27. siliconcompiler/report/html_report.py +1 -1
  28. siliconcompiler/report/report.py +4 -4
  29. siliconcompiler/report/summary_table.py +2 -2
  30. siliconcompiler/report/utils.py +5 -5
  31. siliconcompiler/scheduler/docker.py +3 -8
  32. siliconcompiler/scheduler/run_node.py +2 -7
  33. siliconcompiler/scheduler/scheduler.py +14 -11
  34. siliconcompiler/scheduler/schedulernode.py +136 -126
  35. siliconcompiler/scheduler/send_messages.py +3 -3
  36. siliconcompiler/scheduler/slurm.py +5 -3
  37. siliconcompiler/scheduler/taskscheduler.py +8 -7
  38. siliconcompiler/schema/baseschema.py +1 -2
  39. siliconcompiler/schema/namedschema.py +26 -2
  40. siliconcompiler/tool.py +398 -175
  41. siliconcompiler/tools/__init__.py +2 -0
  42. siliconcompiler/tools/builtin/_common.py +5 -5
  43. siliconcompiler/tools/builtin/concatenate.py +5 -5
  44. siliconcompiler/tools/builtin/minimum.py +4 -4
  45. siliconcompiler/tools/builtin/mux.py +4 -4
  46. siliconcompiler/tools/builtin/nop.py +4 -4
  47. siliconcompiler/tools/builtin/verify.py +7 -7
  48. siliconcompiler/tools/execute/exec_input.py +1 -1
  49. siliconcompiler/tools/genfasm/genfasm.py +1 -6
  50. siliconcompiler/tools/openroad/_apr.py +5 -1
  51. siliconcompiler/tools/openroad/antenna_repair.py +1 -1
  52. siliconcompiler/tools/openroad/macro_placement.py +1 -1
  53. siliconcompiler/tools/openroad/power_grid.py +1 -1
  54. siliconcompiler/tools/openroad/scripts/common/procs.tcl +5 -0
  55. siliconcompiler/tools/opensta/timing.py +26 -3
  56. siliconcompiler/tools/slang/__init__.py +2 -2
  57. siliconcompiler/tools/surfer/__init__.py +0 -0
  58. siliconcompiler/tools/surfer/show.py +53 -0
  59. siliconcompiler/tools/surfer/surfer.py +30 -0
  60. siliconcompiler/tools/vpr/route.py +27 -14
  61. siliconcompiler/tools/vpr/vpr.py +23 -6
  62. siliconcompiler/tools/yosys/__init__.py +1 -1
  63. siliconcompiler/tools/yosys/scripts/procs.tcl +143 -0
  64. siliconcompiler/tools/yosys/{sc_synth_asic.tcl → scripts/sc_synth_asic.tcl} +4 -0
  65. siliconcompiler/tools/yosys/{sc_synth_fpga.tcl → scripts/sc_synth_fpga.tcl} +24 -77
  66. siliconcompiler/tools/yosys/syn_fpga.py +14 -0
  67. siliconcompiler/toolscripts/_tools.json +8 -12
  68. siliconcompiler/toolscripts/rhel9/install-vpr.sh +0 -2
  69. siliconcompiler/toolscripts/ubuntu22/install-surfer.sh +33 -0
  70. siliconcompiler/toolscripts/ubuntu24/install-surfer.sh +33 -0
  71. siliconcompiler/utils/__init__.py +2 -1
  72. siliconcompiler/utils/flowgraph.py +24 -23
  73. siliconcompiler/utils/issue.py +23 -29
  74. siliconcompiler/utils/logging.py +35 -6
  75. siliconcompiler/utils/showtools.py +6 -1
  76. {siliconcompiler-0.34.0.dist-info → siliconcompiler-0.34.1.dist-info}/METADATA +15 -25
  77. {siliconcompiler-0.34.0.dist-info → siliconcompiler-0.34.1.dist-info}/RECORD +84 -82
  78. siliconcompiler/tools/yosys/procs.tcl +0 -71
  79. siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +0 -68
  80. siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +0 -68
  81. siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +0 -68
  82. /siliconcompiler/tools/yosys/{sc_lec.tcl → scripts/sc_lec.tcl} +0 -0
  83. /siliconcompiler/tools/yosys/{sc_screenshot.tcl → scripts/sc_screenshot.tcl} +0 -0
  84. /siliconcompiler/tools/yosys/{syn_strategies.tcl → scripts/syn_strategies.tcl} +0 -0
  85. {siliconcompiler-0.34.0.dist-info → siliconcompiler-0.34.1.dist-info}/WHEEL +0 -0
  86. {siliconcompiler-0.34.0.dist-info → siliconcompiler-0.34.1.dist-info}/entry_points.txt +0 -0
  87. {siliconcompiler-0.34.0.dist-info → siliconcompiler-0.34.1.dist-info}/licenses/LICENSE +0 -0
  88. {siliconcompiler-0.34.0.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,37 +67,6 @@ class TaskExecutableNotFound(TaskError):
61
67
 
62
68
 
63
69
  class TaskSchema(NamedSchema):
64
- def __init__(self, name):
65
- super().__init__(name)
66
-
67
- schema_task(self)
68
-
69
- def add_parameter(self, name, type, help, defvalue=None):
70
- '''
71
- Adds a parameter to the task definition.
72
-
73
- Args:
74
- name (str): name of parameter
75
- type (str): schema type of the parameter
76
- help (str): help string for this parameter
77
- defvalue (any): default value for the parameter
78
- '''
79
- help = trim(help)
80
- param = Parameter(
81
- type,
82
- defvalue=defvalue,
83
- scope=Scope.JOB,
84
- pernode=PerNode.OPTIONAL,
85
- shorthelp=help,
86
- help=help
87
- )
88
-
89
- EditableSchema(self).insert("var", name, param)
90
-
91
- return param
92
-
93
-
94
- class ToolSchema(NamedSchema):
95
70
  __parse_version_check_str = r"""
96
71
  (?P<operator>(==|!=|<=|>=|<|>|~=))
97
72
  \s*
@@ -107,17 +82,28 @@ class ToolSchema(NamedSchema):
107
82
  r"^\s*" + __parse_version_check_str + r"\s*$",
108
83
  re.VERBOSE | re.IGNORECASE)
109
84
 
110
- def __init__(self, name):
111
- super().__init__(name)
85
+ def __init__(self, name=None):
86
+ super().__init__()
87
+ self.set_name(name)
112
88
 
113
- schema_tool(self)
89
+ schema_task(self)
114
90
 
115
- schema = EditableSchema(self)
116
- schema.insert("task", "default", TaskSchema(None))
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
117
98
 
118
- 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
119
105
 
120
- def set_runtime(self, chip, step=None, index=None):
106
+ def __set_runtime(self, chip, step=None, index=None, relpath=None):
121
107
  '''
122
108
  Sets the runtime information needed to properly execute a task.
123
109
  Note: unstable API
@@ -128,22 +114,30 @@ class ToolSchema(NamedSchema):
128
114
  self.__chip = None
129
115
  self.__schema_full = None
130
116
  self.__logger = None
117
+ self.__design_name = None
118
+ self.__design_top = None
119
+ self.__cwd = None
120
+ self.__relpath = relpath
131
121
  if chip:
132
122
  self.__chip = chip
133
123
  self.__schema_full = chip.schema
134
124
  self.__logger = chip.logger
125
+ self.__design_name = chip.design
126
+ self.__design_top = chip.top()
127
+ self.__cwd = chip.cwd
135
128
 
136
129
  self.__step = step
137
130
  self.__index = index
138
- self.__tool = None
139
- self.__task = None
140
131
 
141
132
  self.__schema_record = None
142
133
  self.__schema_metric = None
143
134
  self.__schema_flow = None
135
+ self.__schema_flow_runtime = None
136
+ self.__schema_tool = None
144
137
  if self.__schema_full:
145
138
  self.__schema_record = self.__schema_full.get("record", field="schema")
146
139
  self.__schema_metric = self.__schema_full.get("metric", field="schema")
140
+ self.__schema_tool = self._parent()._parent()
147
141
 
148
142
  if not self.__step:
149
143
  self.__step = self.__schema_full.get('arg', 'step')
@@ -157,8 +151,11 @@ class ToolSchema(NamedSchema):
157
151
  if not flow:
158
152
  raise RuntimeError("flow not specified")
159
153
  self.__schema_flow = self.__schema_full.get("flowgraph", flow, field="schema")
160
- self.__tool = self.__schema_flow.get(self.__step, self.__index, 'tool')
161
- 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'))
162
159
 
163
160
  def node(self):
164
161
  '''
@@ -171,10 +168,10 @@ class ToolSchema(NamedSchema):
171
168
  def tool(self):
172
169
  '''
173
170
  Returns:
174
- task name
171
+ tool name
175
172
  '''
176
173
 
177
- return self.__tool
174
+ raise NotImplementedError("tool name must be implemented by the child class")
178
175
 
179
176
  def task(self):
180
177
  '''
@@ -182,7 +179,7 @@ class ToolSchema(NamedSchema):
182
179
  task name
183
180
  '''
184
181
 
185
- return self.__task
182
+ raise NotImplementedError("task name must be implemented by the child class")
186
183
 
187
184
  def logger(self):
188
185
  '''
@@ -209,6 +206,10 @@ class ToolSchema(NamedSchema):
209
206
  return self.__schema_metric
210
207
  elif type == "flow":
211
208
  return self.__schema_flow
209
+ elif type == "runtimeflow":
210
+ return self.__schema_flow_runtime
211
+ elif type == "tool":
212
+ return self.__schema_tool
212
213
  else:
213
214
  raise ValueError(f"{type} is not a schema section")
214
215
 
@@ -223,7 +224,7 @@ class ToolSchema(NamedSchema):
223
224
  path to executable, or None if not specified
224
225
  '''
225
226
 
226
- exe = self.get('exe')
227
+ exe = self.schema("tool").get('exe')
227
228
 
228
229
  if exe is None:
229
230
  return None
@@ -250,7 +251,7 @@ class ToolSchema(NamedSchema):
250
251
  version determined by :meth:`.parse_version`.
251
252
  '''
252
253
 
253
- veropt = self.get('vswitch')
254
+ veropt = self.schema("tool").get('vswitch')
254
255
  if not veropt:
255
256
  return None
256
257
 
@@ -263,7 +264,8 @@ class ToolSchema(NamedSchema):
263
264
  cmdlist = [exe]
264
265
  cmdlist.extend(veropt)
265
266
 
266
- 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)}')
267
269
 
268
270
  proc = subprocess.run(cmdlist,
269
271
  stdin=subprocess.DEVNULL,
@@ -278,9 +280,11 @@ class ToolSchema(NamedSchema):
278
280
  try:
279
281
  version = self.parse_version(proc.stdout)
280
282
  except NotImplementedError:
281
- raise NotImplementedError(f'{self.name()} does not implement parse_version()')
283
+ raise NotImplementedError(f'{self.tool()}/{self.task()} does not implement '
284
+ 'parse_version()')
282
285
  except Exception as e:
283
- 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}')
284
288
  raise e from None
285
289
 
286
290
  self.__logger.info(f"Tool '{exe_base}' found with version '{version}' "
@@ -301,7 +305,7 @@ class ToolSchema(NamedSchema):
301
305
 
302
306
  '''
303
307
 
304
- 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)
305
309
  if not spec_sets:
306
310
  # No requirement so always true
307
311
  return True
@@ -310,7 +314,7 @@ class ToolSchema(NamedSchema):
310
314
  split_specs = [s.strip() for s in spec_set.split(",") if s.strip()]
311
315
  specs_list = []
312
316
  for spec in split_specs:
313
- match = re.match(ToolSchema.__parse_version_check, spec)
317
+ match = re.match(TaskSchema.__parse_version_check, spec)
314
318
  if match is None:
315
319
  self.__logger.warning(f'Invalid version specifier {spec}. '
316
320
  f'Defaulting to =={spec}.')
@@ -324,15 +328,15 @@ class ToolSchema(NamedSchema):
324
328
  try:
325
329
  normalized_version = self.normalize_version(reported_version)
326
330
  except Exception as e:
327
- 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()}: '
328
332
  f'{reported_version}')
329
333
  raise e from None
330
334
 
331
335
  try:
332
336
  version = Version(normalized_version)
333
337
  except InvalidVersion:
334
- self.__logger.error(f'Version {normalized_version} reported by {self.name()} does '
335
- 'not match standard.')
338
+ self.__logger.error(f'Version {normalized_version} reported by '
339
+ f'{self.tool()}/{self.task()} does not match standard.')
336
340
  return False
337
341
 
338
342
  try:
@@ -340,7 +344,8 @@ class ToolSchema(NamedSchema):
340
344
  f'{op}{self.normalize_version(ver)}' for op, ver in specs_list]
341
345
  normalized_specs = ','.join(normalized_spec_list)
342
346
  except Exception as e:
343
- 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()}: '
344
349
  f'{",".join([f"{op}{ver}" for op, ver in specs_list])}')
345
350
  raise e from None
346
351
 
@@ -355,7 +360,8 @@ class ToolSchema(NamedSchema):
355
360
  return True
356
361
 
357
362
  allowedstr = '; '.join(spec_sets)
358
- 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.")
359
365
  self.__logger.error(f"Found version {reported_version}, "
360
366
  f"did not satisfy any version specifier set {allowedstr}.")
361
367
  return False
@@ -377,16 +383,17 @@ class ToolSchema(NamedSchema):
377
383
  envvars[env] = self.__schema_full.get('option', 'env', env)
378
384
 
379
385
  # Add tool specific vars
380
- for lic_env in self.getkeys('licenseserver'):
381
- 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)
382
389
  if license_file:
383
390
  envvars[lic_env] = ':'.join(license_file)
384
391
 
385
392
  if include_path:
386
- path = self.find_files(
393
+ path = self.schema("tool").find_files(
387
394
  "path", step=self.__step, index=self.__index,
388
- packages=self.__chip.get("package", field="schema").get_resolvers(),
389
- cwd=self.__chip.cwd,
395
+ packages=self.schema().get("package", field="schema").get_resolvers(),
396
+ cwd=self.__cwd,
390
397
  missing_ok=True)
391
398
 
392
399
  envvars["PATH"] = os.getenv("PATH", os.defpath)
@@ -401,9 +408,8 @@ class ToolSchema(NamedSchema):
401
408
  envvars[var] = val
402
409
 
403
410
  # Add task specific vars
404
- for env in self.getkeys('task', self.__task, 'env'):
405
- envvars[env] = self.get('task', self.__task, 'env', env,
406
- step=self.__step, index=self.__index)
411
+ for env in self.getkeys("env"):
412
+ envvars[env] = self.get("env", env)
407
413
 
408
414
  return envvars
409
415
 
@@ -417,9 +423,19 @@ class ToolSchema(NamedSchema):
417
423
 
418
424
  cmdargs = []
419
425
  try:
420
- 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)
421
437
  except Exception as e:
422
- 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()}')
423
439
  raise e from None
424
440
 
425
441
  # Cleanup args
@@ -440,13 +456,13 @@ class ToolSchema(NamedSchema):
440
456
  replay_opts["work_dir"] = workdir
441
457
  replay_opts["exports"] = self.get_runtime_environmental_variables(include_path=include_path)
442
458
 
443
- replay_opts["executable"] = self.get('exe')
459
+ replay_opts["executable"] = self.schema("tool").get('exe')
444
460
  replay_opts["step"] = self.__step
445
461
  replay_opts["index"] = self.__index
446
- replay_opts["cfg_file"] = f"inputs/{self.__chip.design}.pkg.json"
462
+ replay_opts["cfg_file"] = f"inputs/{self.__design_name}.pkg.json"
447
463
  replay_opts["node_only"] = 0 if replay_opts["executable"] else 1
448
464
 
449
- vswitch = self.get('vswitch')
465
+ vswitch = self.schema("tool").get('vswitch')
450
466
  if vswitch:
451
467
  replay_opts["version_flag"] = shlex.join(vswitch)
452
468
 
@@ -503,6 +519,66 @@ class ToolSchema(NamedSchema):
503
519
  os.makedirs(os.path.join(workdir, 'outputs'), exist_ok=True)
504
520
  os.makedirs(os.path.join(workdir, 'reports'), exist_ok=True)
505
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
+
506
582
  def write_task_manifest(self, directory, backup=True):
507
583
  '''
508
584
  Write the manifest needed for the task
@@ -512,7 +588,7 @@ class ToolSchema(NamedSchema):
512
588
  backup (bool): if True and an existing manifest is found a backup is kept.
513
589
  '''
514
590
 
515
- suffix = self.get('format')
591
+ suffix = self.schema("tool").get('format')
516
592
  if not suffix:
517
593
  return
518
594
 
@@ -521,8 +597,65 @@ class ToolSchema(NamedSchema):
521
597
  if backup and os.path.exists(manifest_path):
522
598
  shutil.copyfile(manifest_path, f'{manifest_path}.bak')
523
599
 
524
- # TODO: pull in TCL/yaml here
525
- 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
526
659
 
527
660
  def __get_io_file(self, io_type):
528
661
  '''
@@ -531,10 +664,8 @@ class ToolSchema(NamedSchema):
531
664
  Args:
532
665
  io_type (str): name of io type
533
666
  '''
534
- suffix = self.get('task', self.__task, io_type, 'suffix',
535
- step=self.__step, index=self.__index)
536
- destination = self.get('task', self.__task, io_type, 'destination',
537
- step=self.__step, index=self.__index)
667
+ suffix = self.get(io_type, "suffix")
668
+ destination = self.get(io_type, "destination")
538
669
 
539
670
  io_file = None
540
671
  io_log = False
@@ -542,7 +673,7 @@ class ToolSchema(NamedSchema):
542
673
  io_file = f"{self.__step}.{suffix}"
543
674
  io_log = True
544
675
  elif destination == 'output':
545
- io_file = os.path.join('outputs', f"{self.__chip.top()}.{suffix}")
676
+ io_file = os.path.join('outputs', f"{self.__design_top}.{suffix}")
546
677
  elif destination == 'none':
547
678
  io_file = os.devnull
548
679
 
@@ -581,13 +712,13 @@ class ToolSchema(NamedSchema):
581
712
  TERMINATE_TIMEOUT = 5
582
713
 
583
714
  terminate_process(proc.pid, timeout=TERMINATE_TIMEOUT)
584
- self.__logger.info(f'Waiting for {self.name()} to exit...')
715
+ self.__logger.info(f'Waiting for {self.tool()}/{self.task()} to exit...')
585
716
  try:
586
717
  proc.wait(timeout=TERMINATE_TIMEOUT)
587
718
  except subprocess.TimeoutExpired:
588
719
  if proc.poll() is None:
589
- self.__logger.warning(f'{self.name()} did not exit within {TERMINATE_TIMEOUT} '
590
- 'seconds. Terminating...')
720
+ self.__logger.warning(f'{self.tool()}/{self.task()} did not exit within '
721
+ f'{TERMINATE_TIMEOUT} seconds. Terminating...')
591
722
  terminate_process(proc.pid, timeout=TERMINATE_TIMEOUT)
592
723
 
593
724
  def run_task(self, workdir, quiet, loglevel, breakpoint, nice, timeout):
@@ -658,7 +789,7 @@ class ToolSchema(NamedSchema):
658
789
  contextlib.redirect_stdout(stdout_writer):
659
790
  retcode = self.run()
660
791
  except Exception as e:
661
- 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}')
662
793
  utils.print_traceback(self.__logger, e)
663
794
  raise e
664
795
  finally:
@@ -706,9 +837,9 @@ class ToolSchema(NamedSchema):
706
837
  retcode = pty.spawn([exe, *cmdlist], read)
707
838
  else:
708
839
  with open(stdout_file, 'w') as stdout_writer, \
709
- open(stdout_file, 'r', errors='replace_with_warning') as stdout_reader, \
840
+ open(stdout_file, 'r', errors='replace') as stdout_reader, \
710
841
  open(stderr_file, 'w') as stderr_writer, \
711
- open(stderr_file, 'r', errors='replace_with_warning') as stderr_reader:
842
+ open(stderr_file, 'r', errors='replace') as stderr_reader:
712
843
  # if STDOUT and STDERR are to be redirected to the same file,
713
844
  # use a single writer
714
845
  if stderr_file == stdout_file:
@@ -805,7 +936,7 @@ class ToolSchema(NamedSchema):
805
936
 
806
937
  # Remove runtime information
807
938
  for key in list(state.keys()):
808
- if key.startswith("_ToolSchema__"):
939
+ if key.startswith("_TaskSchema__"):
809
940
  del state[key]
810
941
 
811
942
  return state
@@ -814,10 +945,101 @@ class ToolSchema(NamedSchema):
814
945
  self.__dict__ = state
815
946
 
816
947
  # Reinit runtime information
817
- self.set_runtime(None)
948
+ self.__set_runtime(None)
818
949
 
819
950
  def get_output_files(self):
820
- 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)
821
1043
 
822
1044
  ###############################################################
823
1045
  def parse_version(self, stdout):
@@ -830,24 +1052,18 @@ class ToolSchema(NamedSchema):
830
1052
  pass
831
1053
 
832
1054
  def select_input_nodes(self):
833
- flow = self.schema("flow")
834
- runtime = RuntimeFlowgraph(
835
- flow,
836
- from_steps=set([step for step, _ in flow.get_entry_nodes()]),
837
- prune_nodes=self.__chip.get('option', 'prune'))
838
-
839
- 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"))
840
1057
 
841
1058
  def pre_process(self):
842
1059
  pass
843
1060
 
844
1061
  def runtime_options(self):
845
1062
  cmdargs = []
846
- cmdargs.extend(self.get('task', self.__task, 'option',
847
- step=self.__step, index=self.__index))
1063
+ cmdargs.extend(self.get("option"))
848
1064
 
849
1065
  # Add scripts files / TODO:
850
- scripts = self.__chip.find_files('tool', self.__tool, 'task', self.__task, 'script',
1066
+ scripts = self.__chip.find_files('tool', self.tool(), 'task', self.task(), 'script',
851
1067
  step=self.__step, index=self.__index)
852
1068
 
853
1069
  cmdargs.extend(scripts)
@@ -861,12 +1077,33 @@ class ToolSchema(NamedSchema):
861
1077
  pass
862
1078
 
863
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
+
864
1091
  ###########################################################################
865
1092
  # Migration helper
866
1093
  ###########################################################################
867
- 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):
868
1105
  def __init__(self):
869
- super().__init__(None)
1106
+ super().__init__()
870
1107
 
871
1108
  def __module_func(self, name, modules):
872
1109
  for module in modules:
@@ -877,103 +1114,101 @@ class ToolSchemaTmp(ToolSchema):
877
1114
 
878
1115
  def __tool_task_modules(self):
879
1116
  step, index = self.node()
880
- flow = self._ToolSchema__chip.get('option', 'flow')
1117
+ flow = self._TaskSchema__chip.get('option', 'flow')
881
1118
  return \
882
- self._ToolSchema__chip._get_tool_module(step, index, flow=flow), \
883
- 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)
884
1148
 
885
1149
  def get_output_files(self):
886
1150
  _, task = self.__tool_task_modules()
887
1151
  method = self.__module_func("_gather_outputs", [task])
888
1152
  if method:
889
- return method(self._ToolSchema__chip, *self.node())
890
- return ToolSchema.get_output_files(self)
1153
+ return method(self._TaskSchema__chip, *self.node())
1154
+ return TaskSchema.get_output_files(self)
891
1155
 
892
1156
  def parse_version(self, stdout):
893
1157
  tool, _ = self.__tool_task_modules()
894
1158
  method = self.__module_func("parse_version", [tool])
895
1159
  if method:
896
1160
  return method(stdout)
897
- return ToolSchema.parse_version(self, stdout)
1161
+ return TaskSchema.parse_version(self, stdout)
898
1162
 
899
1163
  def normalize_version(self, version):
900
1164
  tool, _ = self.__tool_task_modules()
901
1165
  method = self.__module_func("normalize_version", [tool])
902
1166
  if method:
903
1167
  return method(version)
904
- return ToolSchema.normalize_version(self, version)
1168
+ return TaskSchema.normalize_version(self, version)
905
1169
 
906
1170
  def generate_replay_script(self, filepath, workdir, include_path=True):
907
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
908
- self._ToolSchema__chip.get('arg', 'index')
909
- step, index = self.node()
910
- self._ToolSchema__chip.set('arg', 'step', step)
911
- self._ToolSchema__chip.set('arg', 'index', index)
912
- ret = ToolSchema.generate_replay_script(self, filepath, workdir, include_path=include_path)
913
- self._ToolSchema__chip.set('arg', 'step', prev_step)
914
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1171
+ with self.__in_step_index():
1172
+ ret = TaskSchema.generate_replay_script(self, filepath, workdir,
1173
+ include_path=include_path)
915
1174
  return ret
916
1175
 
917
1176
  def setup(self):
918
1177
  _, task = self.__tool_task_modules()
919
1178
  method = self.__module_func("setup", [task])
920
1179
  if method:
921
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
922
- self._ToolSchema__chip.get('arg', 'index')
923
- step, index = self.node()
924
- self._ToolSchema__chip.set('arg', 'step', step)
925
- self._ToolSchema__chip.set('arg', 'index', index)
926
- ret = method(self._ToolSchema__chip)
927
- self._ToolSchema__chip.set('arg', 'step', prev_step)
928
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1180
+ with self.__in_step_index():
1181
+ ret = method(self._TaskSchema__chip)
929
1182
  return ret
930
- return ToolSchema.setup(self)
1183
+ return TaskSchema.setup(self)
931
1184
 
932
1185
  def select_input_nodes(self):
933
1186
  _, task = self.__tool_task_modules()
934
1187
  method = self.__module_func("_select_inputs", [task])
935
1188
  if method:
936
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
937
- self._ToolSchema__chip.get('arg', 'index')
938
- step, index = self.node()
939
- self._ToolSchema__chip.set('arg', 'step', step)
940
- self._ToolSchema__chip.set('arg', 'index', index)
941
- ret = method(self._ToolSchema__chip, *self.node())
942
- self._ToolSchema__chip.set('arg', 'step', prev_step)
943
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1189
+ with self.__in_step_index():
1190
+ ret = method(self._TaskSchema__chip, *self.node())
944
1191
  return ret
945
- return ToolSchema.select_input_nodes(self)
1192
+ return TaskSchema.select_input_nodes(self)
946
1193
 
947
1194
  def pre_process(self):
948
1195
  _, task = self.__tool_task_modules()
949
1196
  method = self.__module_func("pre_process", [task])
950
1197
  if method:
951
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
952
- self._ToolSchema__chip.get('arg', 'index')
953
- step, index = self.node()
954
- self._ToolSchema__chip.set('arg', 'step', step)
955
- self._ToolSchema__chip.set('arg', 'index', index)
956
- ret = method(self._ToolSchema__chip)
957
- self._ToolSchema__chip.set('arg', 'step', prev_step)
958
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1198
+ with self.__in_step_index():
1199
+ ret = method(self._TaskSchema__chip)
959
1200
  return ret
960
- return ToolSchema.pre_process(self)
1201
+ return TaskSchema.pre_process(self)
961
1202
 
962
1203
  def runtime_options(self):
963
1204
  tool, task = self.__tool_task_modules()
964
1205
  method = self.__module_func("runtime_options", [task, tool])
965
1206
  if method:
966
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
967
- self._ToolSchema__chip.get('arg', 'index')
968
- step, index = self.node()
969
- self._ToolSchema__chip.set('arg', 'step', step)
970
- self._ToolSchema__chip.set('arg', 'index', index)
971
- ret = ToolSchema.runtime_options(self)
972
- ret.extend(method(self._ToolSchema__chip))
973
- self._ToolSchema__chip.set('arg', 'step', prev_step)
974
- 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))
975
1210
  return ret
976
- return ToolSchema.runtime_options(self)
1211
+ return TaskSchema.runtime_options(self)
977
1212
 
978
1213
  def run(self):
979
1214
  _, task = self.__tool_task_modules()
@@ -981,38 +1216,26 @@ class ToolSchemaTmp(ToolSchema):
981
1216
  if method:
982
1217
  # Handle logger stdout suppression if quiet
983
1218
  step, index = self.node()
984
- stdout_handler_level = self._ToolSchema__chip.logger._console.level
985
- if self._ToolSchema__chip.get('option', 'quiet', step=step, index=index):
986
- 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)
987
1222
 
988
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
989
- self._ToolSchema__chip.get('arg', 'index')
990
- step, index = self.node()
991
- self._ToolSchema__chip.set('arg', 'step', step)
992
- self._ToolSchema__chip.set('arg', 'index', index)
993
- retcode = method(self._ToolSchema__chip)
994
- self._ToolSchema__chip.set('arg', 'step', prev_step)
995
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1223
+ with self.__in_step_index():
1224
+ retcode = method(self._TaskSchema__chip)
996
1225
 
997
- self._ToolSchema__chip.logger._console.setLevel(stdout_handler_level)
1226
+ self._TaskSchema__chip._logger_console.setLevel(stdout_handler_level)
998
1227
 
999
1228
  return retcode
1000
- return ToolSchema.run(self)
1229
+ return TaskSchema.run(self)
1001
1230
 
1002
1231
  def post_process(self):
1003
1232
  _, task = self.__tool_task_modules()
1004
1233
  method = self.__module_func("post_process", [task])
1005
1234
  if method:
1006
- prev_step, prev_index = self._ToolSchema__chip.get('arg', 'step'), \
1007
- self._ToolSchema__chip.get('arg', 'index')
1008
- step, index = self.node()
1009
- self._ToolSchema__chip.set('arg', 'step', step)
1010
- self._ToolSchema__chip.set('arg', 'index', index)
1011
- ret = method(self._ToolSchema__chip)
1012
- self._ToolSchema__chip.set('arg', 'step', prev_step)
1013
- self._ToolSchema__chip.set('arg', 'index', prev_index)
1235
+ with self.__in_step_index():
1236
+ ret = method(self._TaskSchema__chip)
1014
1237
  return ret
1015
- return ToolSchema.post_process(self)
1238
+ return TaskSchema.post_process(self)
1016
1239
 
1017
1240
 
1018
1241
  ###########################################################################