siliconcompiler 0.32.3__py3-none-any.whl → 0.33.0__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 (154) hide show
  1. siliconcompiler/__init__.py +19 -2
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/sc.py +2 -2
  4. siliconcompiler/apps/sc_install.py +3 -3
  5. siliconcompiler/apps/sc_issue.py +1 -1
  6. siliconcompiler/apps/sc_remote.py +4 -4
  7. siliconcompiler/apps/sc_show.py +2 -2
  8. siliconcompiler/apps/utils/replay.py +5 -3
  9. siliconcompiler/asic.py +120 -0
  10. siliconcompiler/checklist.py +150 -0
  11. siliconcompiler/core.py +267 -289
  12. siliconcompiler/flowgraph.py +803 -515
  13. siliconcompiler/fpga.py +84 -0
  14. siliconcompiler/metric.py +420 -0
  15. siliconcompiler/optimizer/vizier.py +2 -3
  16. siliconcompiler/package/__init__.py +29 -6
  17. siliconcompiler/pdk.py +415 -0
  18. siliconcompiler/record.py +449 -0
  19. siliconcompiler/remote/client.py +6 -3
  20. siliconcompiler/remote/schema.py +116 -112
  21. siliconcompiler/remote/server.py +3 -5
  22. siliconcompiler/report/dashboard/cli/__init__.py +13 -722
  23. siliconcompiler/report/dashboard/cli/board.py +895 -0
  24. siliconcompiler/report/dashboard/web/__init__.py +10 -10
  25. siliconcompiler/report/dashboard/web/components/__init__.py +5 -4
  26. siliconcompiler/report/dashboard/web/components/flowgraph.py +3 -3
  27. siliconcompiler/report/dashboard/web/components/graph.py +6 -3
  28. siliconcompiler/report/dashboard/web/state.py +1 -1
  29. siliconcompiler/report/dashboard/web/utils/__init__.py +4 -3
  30. siliconcompiler/report/html_report.py +2 -3
  31. siliconcompiler/report/report.py +13 -7
  32. siliconcompiler/report/summary_image.py +1 -1
  33. siliconcompiler/report/summary_table.py +3 -3
  34. siliconcompiler/report/utils.py +11 -10
  35. siliconcompiler/scheduler/__init__.py +145 -280
  36. siliconcompiler/scheduler/run_node.py +2 -1
  37. siliconcompiler/scheduler/send_messages.py +4 -4
  38. siliconcompiler/scheduler/slurm.py +2 -2
  39. siliconcompiler/schema/__init__.py +19 -2
  40. siliconcompiler/schema/baseschema.py +493 -0
  41. siliconcompiler/schema/cmdlineschema.py +250 -0
  42. siliconcompiler/{sphinx_ext → schema/docs}/__init__.py +3 -1
  43. siliconcompiler/{sphinx_ext → schema/docs}/dynamicgen.py +63 -81
  44. siliconcompiler/{sphinx_ext → schema/docs}/schemagen.py +73 -85
  45. siliconcompiler/{sphinx_ext → schema/docs}/utils.py +12 -13
  46. siliconcompiler/schema/editableschema.py +136 -0
  47. siliconcompiler/schema/journalingschema.py +238 -0
  48. siliconcompiler/schema/namedschema.py +41 -0
  49. siliconcompiler/schema/packageschema.py +101 -0
  50. siliconcompiler/schema/parameter.py +791 -0
  51. siliconcompiler/schema/parametertype.py +323 -0
  52. siliconcompiler/schema/parametervalue.py +736 -0
  53. siliconcompiler/schema/safeschema.py +37 -0
  54. siliconcompiler/schema/schema_cfg.py +109 -1789
  55. siliconcompiler/schema/utils.py +5 -68
  56. siliconcompiler/schema_obj.py +119 -0
  57. siliconcompiler/tool.py +1308 -0
  58. siliconcompiler/tools/_common/__init__.py +6 -10
  59. siliconcompiler/tools/_common/sdc/sc_constraints.sdc +1 -1
  60. siliconcompiler/tools/bluespec/convert.py +7 -7
  61. siliconcompiler/tools/builtin/_common.py +1 -1
  62. siliconcompiler/tools/builtin/concatenate.py +2 -2
  63. siliconcompiler/tools/builtin/minimum.py +1 -1
  64. siliconcompiler/tools/builtin/mux.py +2 -1
  65. siliconcompiler/tools/builtin/nop.py +1 -1
  66. siliconcompiler/tools/builtin/verify.py +6 -4
  67. siliconcompiler/tools/chisel/convert.py +4 -4
  68. siliconcompiler/tools/genfasm/bitstream.py +3 -3
  69. siliconcompiler/tools/ghdl/convert.py +1 -1
  70. siliconcompiler/tools/icarus/compile.py +4 -4
  71. siliconcompiler/tools/icepack/bitstream.py +6 -1
  72. siliconcompiler/tools/klayout/convert_drc_db.py +5 -0
  73. siliconcompiler/tools/klayout/klayout_export.py +0 -1
  74. siliconcompiler/tools/klayout/klayout_utils.py +3 -10
  75. siliconcompiler/tools/nextpnr/apr.py +6 -1
  76. siliconcompiler/tools/nextpnr/nextpnr.py +4 -4
  77. siliconcompiler/tools/openroad/_apr.py +13 -0
  78. siliconcompiler/tools/openroad/rdlroute.py +3 -3
  79. siliconcompiler/tools/openroad/scripts/apr/postamble.tcl +1 -1
  80. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +5 -5
  81. siliconcompiler/tools/openroad/scripts/apr/sc_antenna_repair.tcl +2 -2
  82. siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +2 -2
  83. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +2 -2
  84. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +2 -2
  85. siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +2 -2
  86. siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +2 -2
  87. siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +2 -2
  88. siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +2 -2
  89. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -2
  90. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +2 -2
  91. siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +3 -3
  92. siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +2 -2
  93. siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +2 -2
  94. siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +2 -2
  95. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +2 -2
  96. siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +2 -2
  97. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +2 -2
  98. siliconcompiler/tools/openroad/scripts/common/procs.tcl +57 -1
  99. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +2 -2
  100. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -3
  101. siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +1 -1
  102. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -3
  103. siliconcompiler/tools/openroad/scripts/sc_show.tcl +6 -6
  104. siliconcompiler/tools/slang/__init__.py +10 -10
  105. siliconcompiler/tools/surelog/parse.py +4 -4
  106. siliconcompiler/tools/sv2v/convert.py +20 -3
  107. siliconcompiler/tools/verilator/compile.py +2 -2
  108. siliconcompiler/tools/verilator/verilator.py +3 -3
  109. siliconcompiler/tools/vpr/place.py +1 -1
  110. siliconcompiler/tools/vpr/route.py +4 -4
  111. siliconcompiler/tools/vpr/screenshot.py +1 -1
  112. siliconcompiler/tools/vpr/show.py +5 -5
  113. siliconcompiler/tools/vpr/vpr.py +24 -24
  114. siliconcompiler/tools/xdm/convert.py +2 -2
  115. siliconcompiler/tools/xyce/simulate.py +1 -1
  116. siliconcompiler/tools/yosys/sc_synth_asic.tcl +74 -68
  117. siliconcompiler/tools/yosys/syn_asic.py +2 -2
  118. siliconcompiler/toolscripts/_tools.json +7 -7
  119. siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +0 -2
  120. siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +0 -2
  121. siliconcompiler/utils/__init__.py +8 -112
  122. siliconcompiler/utils/flowgraph.py +339 -0
  123. siliconcompiler/{issue.py → utils/issue.py} +4 -3
  124. siliconcompiler/utils/logging.py +1 -2
  125. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/METADATA +9 -8
  126. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/RECORD +151 -134
  127. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/WHEEL +1 -1
  128. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/entry_points.txt +8 -8
  129. siliconcompiler/schema/schema_obj.py +0 -1936
  130. siliconcompiler/toolscripts/ubuntu20/install-vpr.sh +0 -29
  131. siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -61
  132. /siliconcompiler/{templates → data/templates}/__init__.py +0 -0
  133. /siliconcompiler/{templates → data/templates}/email/__init__.py +0 -0
  134. /siliconcompiler/{templates → data/templates}/email/general.j2 +0 -0
  135. /siliconcompiler/{templates → data/templates}/email/summary.j2 +0 -0
  136. /siliconcompiler/{templates → data/templates}/issue/README.txt +0 -0
  137. /siliconcompiler/{templates → data/templates}/issue/__init__.py +0 -0
  138. /siliconcompiler/{templates → data/templates}/issue/run.sh +0 -0
  139. /siliconcompiler/{templates → data/templates}/replay/replay.py.j2 +0 -0
  140. /siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +0 -0
  141. /siliconcompiler/{templates → data/templates}/replay/requirements.txt +0 -0
  142. /siliconcompiler/{templates → data/templates}/replay/setup.sh +0 -0
  143. /siliconcompiler/{templates → data/templates}/report/__init__.py +0 -0
  144. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.css +0 -0
  145. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.js +0 -0
  146. /siliconcompiler/{templates → data/templates}/report/bootstrap_LICENSE.md +0 -0
  147. /siliconcompiler/{templates → data/templates}/report/sc_report.j2 +0 -0
  148. /siliconcompiler/{templates → data/templates}/slurm/__init__.py +0 -0
  149. /siliconcompiler/{templates → data/templates}/slurm/run.sh +0 -0
  150. /siliconcompiler/{templates → data/templates}/tcl/__init__.py +0 -0
  151. /siliconcompiler/{templates → data/templates}/tcl/manifest.tcl.j2 +0 -0
  152. /siliconcompiler/{units.py → utils/units.py} +0 -0
  153. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/licenses/LICENSE +0 -0
  154. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/top_level.txt +0 -0
siliconcompiler/core.py CHANGED
@@ -11,13 +11,16 @@ import logging
11
11
  import hashlib
12
12
  import shutil
13
13
  import importlib
14
- import inspect
15
14
  import textwrap
16
15
  import graphviz
17
16
  import codecs
18
- import copy
17
+ import csv
18
+ import yaml
19
19
  from inspect import getfullargspec
20
- from siliconcompiler.schema import Schema, SCHEMA_VERSION
20
+ from siliconcompiler import Schema
21
+ from siliconcompiler.schema import SCHEMA_VERSION, PerNode, JournalingSchema, EditableSchema
22
+ from siliconcompiler.schema.parametertype import NodeType
23
+ from siliconcompiler.schema.parametervalue import FileNodeValue, PathNodeValue
21
24
  from siliconcompiler.schema import utils as schema_utils
22
25
  from siliconcompiler import utils
23
26
  from siliconcompiler.utils.logging import SCColorLoggerFormatter, \
@@ -34,12 +37,13 @@ from siliconcompiler.report.dashboard import DashboardType
34
37
  from siliconcompiler import package as sc_package
35
38
  import glob
36
39
  from siliconcompiler.scheduler import run as sc_runner
37
- from siliconcompiler.flowgraph import _get_flowgraph_nodes, nodes_to_execute, \
38
- _get_pruned_node_inputs, _get_flowgraph_exit_nodes, get_executed_nodes, \
40
+ from siliconcompiler.utils.flowgraph import nodes_to_execute, \
41
+ _get_pruned_node_inputs, \
39
42
  _get_flowgraph_execution_order, _check_flowgraph_io, \
40
43
  _get_flowgraph_information
41
44
  from siliconcompiler.tools._common import get_tool_task
42
45
  from types import FunctionType, ModuleType
46
+ from siliconcompiler.flowgraph import RuntimeFlowgraph
43
47
 
44
48
 
45
49
  class Chip:
@@ -388,7 +392,7 @@ class Chip:
388
392
 
389
393
  is_list = '[' in paramtype
390
394
 
391
- for vals, step, index in self.schema._getvals(*key):
395
+ for vals, step, index in self.schema.get(*key, field=None).getvalues():
392
396
  if not vals:
393
397
  continue
394
398
  if not self.get(*key, field='pernode').is_never():
@@ -397,9 +401,10 @@ class Chip:
397
401
  if index is None:
398
402
  index = Schema.GLOBAL_KEY
399
403
 
404
+ packages = self.get(*key, field='package', step=step, index=index)
400
405
  if not is_list:
401
406
  vals = [vals]
402
- packages = self.get(*key, field='package', step=step, index=index)
407
+ packages = [packages]
403
408
  if len(packages) == len(vals):
404
409
  continue
405
410
 
@@ -476,7 +481,6 @@ class Chip:
476
481
  progname=progname,
477
482
  description=description,
478
483
  switchlist=switchlist,
479
- input_map=input_map,
480
484
  additional_args=additional_args,
481
485
  version=_metadata.version,
482
486
  print_banner=print_banner,
@@ -659,11 +663,10 @@ class Chip:
659
663
 
660
664
  elif isinstance(use_module, (Library, Chip)):
661
665
  self._loaded_modules['libs'].append(use_module.design)
662
- cfg = use_module.schema.cfg
663
666
  keep_inputs = True
664
667
  if not isinstance(use_module, Library):
665
668
  keep_inputs = False
666
- self.__import_library(use_module.design, cfg,
669
+ self.__import_library(use_module.design, use_module,
667
670
  keep_input=keep_inputs)
668
671
 
669
672
  is_auto_enable = getattr(use_module, 'is_auto_enable', None)
@@ -677,26 +680,13 @@ class Chip:
677
680
  raise ValueError(f"{module_name} returned an object with an "
678
681
  f"unsupported type: {class_name}")
679
682
 
680
- def __import_data_sources(self, cfg):
681
- if 'package' not in cfg or 'source' not in cfg['package']:
683
+ def __import_data_sources(self, schema):
684
+ if not schema.valid('package', 'source'):
682
685
  return
683
686
 
684
- for source, config in cfg['package']['source'].items():
685
- if source == 'default':
686
- continue
687
-
688
- if 'path' not in config or \
689
- Schema.GLOBAL_KEY not in config['path']['node'] or \
690
- Schema.GLOBAL_KEY not in config['path']['node'][Schema.GLOBAL_KEY]:
691
- continue
692
-
693
- path = config['path']['node'][Schema.GLOBAL_KEY][Schema.GLOBAL_KEY]['value']
694
-
695
- ref = None
696
- if 'ref' in config and \
697
- Schema.GLOBAL_KEY in config['ref']['node'] and \
698
- Schema.GLOBAL_KEY in config['ref']['node'][Schema.GLOBAL_KEY]:
699
- ref = config['ref']['node'][Schema.GLOBAL_KEY][Schema.GLOBAL_KEY]['value']
687
+ for source in schema.getkeys('package', 'source'):
688
+ path = schema.get('package', 'source', source, 'path')
689
+ ref = schema.get('package', 'source', source, 'ref')
700
690
 
701
691
  self.register_source(
702
692
  name=source,
@@ -714,15 +704,24 @@ class Chip:
714
704
 
715
705
  importname = module.design
716
706
 
717
- src_cfg = self.schema.cfg[group]
707
+ if hasattr(module, 'schema'):
708
+ module = module.schema
718
709
 
719
- if importname in src_cfg:
710
+ if self.valid(group, importname):
720
711
  self.logger.warning(f'Overwriting existing {group} {importname}')
721
- del src_cfg[importname]
722
712
 
723
- # Copy
724
- src_cfg[importname] = module.getdict(group, importname)
725
- self.__import_data_sources(module.schema.cfg)
713
+ try:
714
+ insert_schema = EditableSchema(module).search(group, importname)
715
+ except KeyError:
716
+ self.logger.warning(f'{group} {importname} is not valid')
717
+ return
718
+
719
+ EditableSchema(self.schema).insert(
720
+ group,
721
+ importname,
722
+ insert_schema,
723
+ clobber=True)
724
+ self.__import_data_sources(module)
726
725
 
727
726
  ###########################################################################
728
727
  def help(self, *keypath):
@@ -745,13 +744,15 @@ class Chip:
745
744
 
746
745
  # Fetch Values
747
746
 
748
- description = self.get(*keypath, field='shorthelp')
749
- typestr = self.get(*keypath, field='type')
750
- switchstr = str(self.get(*keypath, field='switch'))
751
- defstr = str(self.schema.get_default(*keypath))
752
- requirement = str(self.get(*keypath, field='require'))
753
- helpstr = self.get(*keypath, field='help')
754
- example = self.get(*keypath, field='example')
747
+ param = self.get(*keypath, field=None)
748
+
749
+ description = param.get(field='shorthelp')
750
+ typestr = param.get(field='type')
751
+ switchstr = str(param.get(field='switch'))
752
+ defstr = str(param.default.get())
753
+ requirement = str(param.get(field='require'))
754
+ helpstr = param.get(field='help')
755
+ example = param.get(field='example')
755
756
 
756
757
  examplestr = ("\nExamples: " + example[0] + ''.join(
757
758
  ["\n " + ex for ex in example[1:]]))
@@ -809,9 +810,12 @@ class Chip:
809
810
  >>> check = chip.valid('metric', 'foo', '0', 'tasktime', default_valid=True)
810
811
  Returns True, even if "foo" and "0" aren't in current configuration.
811
812
  """
813
+ if job:
814
+ return self.schema.history(job).valid(*keypath,
815
+ default_valid=default_valid,
816
+ check_complete=check_complete)
812
817
  return self.schema.valid(*keypath,
813
818
  default_valid=default_valid,
814
- job=job,
815
819
  check_complete=check_complete)
816
820
 
817
821
  ###########################################################################
@@ -851,7 +855,7 @@ class Chip:
851
855
  strict = self.schema.get('option', 'strict')
852
856
  if field == 'value' and strict:
853
857
  pernode = self.schema.get(*keypath, field='pernode')
854
- if pernode == schema_utils.PerNode.OPTIONAL and \
858
+ if pernode == PerNode.OPTIONAL and \
855
859
  (step is None or index is None) and \
856
860
  (Schema.GLOBAL_KEY not in (step, index)): # allow explicit access to global
857
861
  self.error(
@@ -861,7 +865,10 @@ class Chip:
861
865
  )
862
866
  return None
863
867
 
864
- return self.schema.get(*keypath, field=field, job=job, step=step, index=index)
868
+ if job:
869
+ return self.schema.history(job).get(*keypath, field=field, step=step, index=index)
870
+
871
+ return self.schema.get(*keypath, field=field, step=step, index=index)
865
872
  except (ValueError, TypeError) as e:
866
873
  self.error(str(e))
867
874
  return None
@@ -894,7 +901,10 @@ class Chip:
894
901
  self.logger.debug('Getting all schema parameter keys.')
895
902
 
896
903
  try:
897
- return self.schema.getkeys(*keypath, job=job)
904
+ if job:
905
+ return self.schema.history(job).getkeys(*keypath)
906
+
907
+ return self.schema.getkeys(*keypath)
898
908
  except (ValueError, TypeError) as e:
899
909
  self.error(str(e))
900
910
  return None
@@ -937,23 +947,17 @@ class Chip:
937
947
  return None
938
948
 
939
949
  ###########################################################################
940
- def __add_set_package(self, keypath, value, package, step, index, clobber, add):
941
- sc_type = self.get(*keypath, field='type')
942
- if 'file' in sc_type or 'dir' in sc_type:
943
- value_list = isinstance(value, (list, tuple))
944
- package_list = isinstance(package, (list, tuple))
945
- if value_list != package_list:
946
- if value_list:
947
- package = len(value) * [package]
948
- else:
949
- raise ValueError()
950
-
951
- if add:
952
- self.schema.add(*keypath, package, field='package',
953
- step=step, index=index)
954
- else:
955
- self.schema.set(*keypath, package, field='package',
956
- step=step, index=index, clobber=clobber)
950
+ def __add_set_package(self, value_success, package):
951
+ if not isinstance(value_success, (list, tuple)):
952
+ value_success = [value_success]
953
+ if not isinstance(package, (list, tuple)):
954
+ package = [package]
955
+ if len(value_success) != len(package):
956
+ package = len(value_success) * package
957
+
958
+ for val, package in zip(value_success, package):
959
+ if val.type in ('file', 'dir'):
960
+ val.set(package, field='package')
957
961
 
958
962
  ###########################################################################
959
963
  def set(self, *args, field='value', clobber=True, step=None, index=None, package=None):
@@ -999,8 +1003,8 @@ class Chip:
999
1003
  try:
1000
1004
  value_success = self.schema.set(*keypath, value, field=field, clobber=clobber,
1001
1005
  step=step, index=index)
1002
- if field == 'value' and value_success:
1003
- self.__add_set_package(keypath, value, package, step, index, True, False)
1006
+ if field == 'value' and value_success and package:
1007
+ self.__add_set_package(value_success, package)
1004
1008
 
1005
1009
  except (ValueError, TypeError) as e:
1006
1010
  self.error(e)
@@ -1086,8 +1090,8 @@ class Chip:
1086
1090
  try:
1087
1091
  value_success = self.schema.add(*args, field=field, step=step, index=index)
1088
1092
 
1089
- if field == 'value' and value_success:
1090
- self.__add_set_package(keypath, value, package, step, index, True, True)
1093
+ if field == 'value' and value_success and package:
1094
+ self.__add_set_package(value_success, package)
1091
1095
  except (ValueError, TypeError) as e:
1092
1096
  self.error(str(e))
1093
1097
 
@@ -1109,8 +1113,10 @@ class Chip:
1109
1113
  package_name = f'flist-{os.path.basename(filename)}'
1110
1114
  package_dir = os.path.dirname(os.path.abspath(filename))
1111
1115
 
1116
+ env_vars = utils.get_env_vars(self, None, None)
1117
+
1112
1118
  def __make_path(rel, path):
1113
- path = utils._resolve_env_vars(self, path, None, None)
1119
+ path = PathNodeValue.resolve_env_vars(path, envvars=env_vars)
1114
1120
  if os.path.isabs(path):
1115
1121
  if path.startswith(rel):
1116
1122
  return os.path.relpath(path, rel), package_name
@@ -1281,7 +1287,7 @@ class Chip:
1281
1287
  """
1282
1288
  strict = self.get('option', 'strict')
1283
1289
  pernode = self.get(*keypath, field='pernode')
1284
- if strict and pernode == schema_utils.PerNode.OPTIONAL and (step is None or index is None):
1290
+ if strict and pernode == PerNode.OPTIONAL and (step is None or index is None):
1285
1291
  self.error(
1286
1292
  f"Invalid args to find_files() of keypath {keypath}: step and "
1287
1293
  "index are required for reading from this parameter while "
@@ -1313,7 +1319,9 @@ class Chip:
1313
1319
  """Internal find_files() that allows you to skip step/index for optional
1314
1320
  params, regardless of [option, strict]."""
1315
1321
 
1316
- paramtype = self.get(*keypath, field='type', job=job)
1322
+ param = self.get(*keypath, field=None, job=job)
1323
+
1324
+ paramtype = param.get(field='type')
1317
1325
 
1318
1326
  if 'file' not in paramtype and 'dir' not in paramtype:
1319
1327
  self.error('Can only call find_files on file or dir types')
@@ -1321,15 +1329,15 @@ class Chip:
1321
1329
 
1322
1330
  is_list = bool(re.match(r'\[', paramtype))
1323
1331
 
1324
- paths = self.schema.get(*keypath, job=job, step=step, index=index)
1325
- dependencies = self.schema.get(*keypath, job=job,
1326
- step=step, index=index, field='package')
1332
+ paths = param.get(step=step, index=index)
1333
+ dependencies = param.get(field='package', step=step, index=index)
1334
+
1327
1335
  # Convert to list if we have scalar
1328
1336
  if not is_list:
1329
1337
  # Dependencies are always specified as list with default []
1330
1338
  # If paths is a scalar we convert the default [] to [None]
1331
1339
  # to have a matching list with one element
1332
- if dependencies == []:
1340
+ if not dependencies:
1333
1341
  dependencies = [None]
1334
1342
  paths = [paths]
1335
1343
 
@@ -1339,7 +1347,6 @@ class Chip:
1339
1347
  dependencies = [dependencies[list_index]]
1340
1348
 
1341
1349
  paths = self.__convert_paths_to_posix(paths)
1342
- dependencies = self.__convert_paths_to_posix(dependencies)
1343
1350
 
1344
1351
  result = []
1345
1352
 
@@ -1373,29 +1380,32 @@ class Chip:
1373
1380
 
1374
1381
  if search_paths:
1375
1382
  search_paths = self.__convert_paths_to_posix(search_paths)
1383
+ else:
1384
+ search_paths = [self.cwd]
1376
1385
 
1386
+ env_vars = utils.get_env_vars(self, step, index)
1377
1387
  for (dependency, path) in zip(dependencies, paths):
1378
- if not search_paths and collection_dir:
1379
- import_path = self.__find_sc_imported_file(path, dependency, collection_dir)
1380
- if import_path:
1381
- result.append(import_path)
1382
- continue
1383
- if dependency:
1384
- depdendency_path = os.path.abspath(
1385
- os.path.join(sc_package.path(self, dependency), path))
1386
- if os.path.exists(depdendency_path):
1387
- result.append(depdendency_path)
1388
+ faux_param = FileNodeValue()
1389
+ faux_param.set(path)
1390
+ try:
1391
+ if dependency:
1392
+ faux_param.set(dependency, field='package')
1393
+ faux_search = [os.path.abspath(os.path.join(sc_package.path(self, dependency)))]
1388
1394
  else:
1389
- result.append(None)
1390
- if not missing_ok:
1391
- self.error(f'Could not find {path} in {dependency}. ({keypath})')
1392
- continue
1393
- result.append(utils.find_sc_file(self,
1394
- path,
1395
- missing_ok=missing_ok,
1396
- search_paths=search_paths,
1397
- step=step,
1398
- index=index))
1395
+ faux_search = search_paths
1396
+ resolved = faux_param.resolve_path(
1397
+ envvars=env_vars,
1398
+ search=faux_search,
1399
+ collection_dir=collection_dir)
1400
+ except FileNotFoundError:
1401
+ resolved = None
1402
+ if not missing_ok:
1403
+ if dependency:
1404
+ self.error(f'Could not find {path} in {dependency}. [{",".join(keypath)}]')
1405
+ else:
1406
+ self.error(f'Could not find {path}. [{",".join(keypath)}]')
1407
+
1408
+ result.append(resolved)
1399
1409
 
1400
1410
  if self._relative_path and not abs_path_only:
1401
1411
  rel_result = []
@@ -1408,6 +1418,8 @@ class Chip:
1408
1418
 
1409
1419
  # Convert back to scalar if that was original type
1410
1420
  if not is_list:
1421
+ if not result:
1422
+ return None
1411
1423
  return result[0]
1412
1424
 
1413
1425
  return result
@@ -1420,33 +1432,20 @@ class Chip:
1420
1432
 
1421
1433
  Returns none if not found
1422
1434
  """
1423
- if not path:
1435
+ if not collected_dir:
1424
1436
  return None
1425
1437
 
1426
- collected_files = os.listdir(collected_dir)
1427
- if not collected_files:
1428
- return None
1438
+ faux_param = FileNodeValue()
1439
+ faux_param.set(path)
1440
+ faux_param.set(package, field='package')
1429
1441
 
1430
- path_paths = pathlib.PurePosixPath(path).parts
1431
- for n in range(len(path_paths)):
1432
- # Search through the path elements to see if any of the previous path parts
1433
- # have been imported
1434
-
1435
- n += 1
1436
- basename = str(pathlib.PurePosixPath(*path_paths[0:n]))
1437
- endname = str(pathlib.PurePosixPath(*path_paths[n:]))
1438
-
1439
- import_name = utils.get_hashed_filename(basename, package=package)
1440
- if import_name not in collected_files:
1441
- continue
1442
-
1443
- abspath = os.path.join(collected_dir, import_name)
1444
- if endname:
1445
- abspath = os.path.join(abspath, endname)
1446
- abspath = os.path.abspath(abspath)
1447
- if os.path.exists(abspath):
1448
- return abspath
1442
+ try:
1443
+ resolved = faux_param.resolve_path(collection_dir=collected_dir)
1444
+ except FileNotFoundError:
1445
+ return None
1449
1446
 
1447
+ if resolved.startswith(collected_dir):
1448
+ return resolved
1450
1449
  return None
1451
1450
 
1452
1451
  def find_node_file(self, path, step, jobname=None, index='0'):
@@ -1530,7 +1529,7 @@ class Chip:
1530
1529
  # only do something if type is file or dir
1531
1530
  continue
1532
1531
 
1533
- values = self.schema._getvals(*keypath)
1532
+ values = self.schema.get(*keypath, field=None).getvalues()
1534
1533
  for value, step, index in values:
1535
1534
  if not value:
1536
1535
  continue
@@ -1565,7 +1564,7 @@ class Chip:
1565
1564
  # exist
1566
1565
  continue
1567
1566
 
1568
- for check_files, step, index in self.schema._getvals(*keypath):
1567
+ for check_files, step, index in self.schema.get(*keypath, field=None).getvalues():
1569
1568
  if not check_files:
1570
1569
  continue
1571
1570
 
@@ -1652,7 +1651,7 @@ class Chip:
1652
1651
  lib_node_check.append((step, None))
1653
1652
  lib_node_check.extend(nodes)
1654
1653
  for lib_key in libs_to_check:
1655
- for val, step, index in self.schema._getvals(*lib_key):
1654
+ for val, step, index in self.schema.get(*lib_key, field=None).getvalues():
1656
1655
  if (step, index) in lib_node_check:
1657
1656
  libraries.update(val)
1658
1657
 
@@ -1666,9 +1665,8 @@ class Chip:
1666
1665
  for key in allkeys:
1667
1666
  keypath = ",".join(key)
1668
1667
  if 'default' not in key and 'history' not in key and 'library' not in key:
1669
- key_empty = self.schema.is_empty(*key)
1670
- requirement = self.get(*key, field='require')
1671
- if key_empty and requirement:
1668
+ param = self.get(*key, field=None)
1669
+ if param.is_empty() and param.get(field='require'):
1672
1670
  error = True
1673
1671
  self.logger.error(f"Global requirement missing for [{keypath}].")
1674
1672
 
@@ -1710,12 +1708,12 @@ class Chip:
1710
1708
  step=step, index=index)
1711
1709
  for item in all_required:
1712
1710
  keypath = item.split(',')
1713
- if self.schema.is_empty(*keypath):
1711
+ if self.schema.get(*keypath, field=None).is_empty():
1714
1712
  error = True
1715
1713
  self.logger.error(f"Value empty for {keypath} for {tool}.")
1716
1714
 
1717
1715
  task_run = getattr(task_module, 'run', None)
1718
- if self.schema.is_empty('tool', tool, 'exe') and not task_run:
1716
+ if self.schema.get('tool', tool, 'exe', field=None).is_empty() and not task_run:
1719
1717
  error = True
1720
1718
  self.logger.error(f'No executable or run() function specified for {tool}/{task}')
1721
1719
 
@@ -1743,26 +1741,8 @@ class Chip:
1743
1741
  Loads the file mychip.json into the current Chip object.
1744
1742
  """
1745
1743
 
1746
- # Read from file into new schema object
1747
- schema = Schema(manifest=filename, logger=self.logger)
1748
-
1749
1744
  # Merge data in schema with Chip configuration
1750
- self.schema.merge_manifest(schema, job=job, clear=clear, clobber=clobber)
1751
-
1752
- # Read history, if we're not already reading into a job
1753
- if 'history' in schema.cfg and not job:
1754
- for historic_job in schema.cfg['history'].keys():
1755
- self.schema.merge_manifest(schema.history(historic_job),
1756
- job=historic_job,
1757
- clear=clear,
1758
- clobber=clobber)
1759
-
1760
- # TODO: better way to handle this?
1761
- if 'library' in schema.cfg:
1762
- for libname in schema.cfg['library'].keys():
1763
- self.__import_library(libname, schema.cfg['library'][libname],
1764
- job=job,
1765
- clobber=clobber)
1745
+ self.schema.read_manifest(filename)
1766
1746
 
1767
1747
  ###########################################################################
1768
1748
  def write_manifest(self, filename, prune=False, abspath=False):
@@ -1796,12 +1776,14 @@ class Chip:
1796
1776
  if abspath:
1797
1777
  schema = self.__abspath()
1798
1778
 
1799
- if prune:
1800
- if schema is self.schema:
1801
- schema = schema.copy()
1779
+ if re.search(r'(\.json|\.sup)(\.gz)*$', filepath):
1780
+ schema.write_manifest(filepath)
1781
+ return
1802
1782
 
1803
- self.logger.debug('Pruning dictionary before writing file %s', filepath)
1804
- schema.prune()
1783
+ tcl_record = False
1784
+ if isinstance(schema, JournalingSchema):
1785
+ tcl_record = "get" in schema.get_journaling_types()
1786
+ schema = schema.get_base_schema()
1805
1787
 
1806
1788
  is_csv = re.search(r'(\.csv)(\.gz)*$', filepath)
1807
1789
 
@@ -1817,26 +1799,85 @@ class Chip:
1817
1799
 
1818
1800
  # format specific printing
1819
1801
  try:
1820
- if re.search(r'(\.json|\.sup)(\.gz)*$', filepath):
1821
- schema.write_json(fout)
1822
- elif re.search(r'(\.yaml|\.yml)(\.gz)*$', filepath):
1823
- schema.write_yaml(fout)
1802
+ if re.search(r'(\.yaml|\.yml)(\.gz)*$', filepath):
1803
+ class YamlIndentDumper(yaml.Dumper):
1804
+ def increase_indent(self, flow=False, indentless=False):
1805
+ return super().increase_indent(flow=flow, indentless=False)
1806
+
1807
+ fout.write(yaml.dump(schema.getdict(), Dumper=YamlIndentDumper,
1808
+ default_flow_style=False))
1809
+
1824
1810
  elif re.search(r'(\.tcl)(\.gz)*$', filepath):
1825
1811
  # TCL only gets values associated with the current node.
1826
1812
  step = self.get('arg', 'step')
1827
1813
  index = self.get('arg', 'index')
1828
- schema.write_tcl(fout,
1814
+ self.__write_tcl(fout,
1815
+ schema,
1829
1816
  prefix="dict set sc_cfg",
1830
1817
  step=step,
1831
1818
  index=index,
1832
- template=utils.get_file_template('tcl/manifest.tcl.j2'))
1819
+ template=utils.get_file_template('tcl/manifest.tcl.j2'),
1820
+ record=tcl_record)
1833
1821
  elif is_csv:
1834
- schema.write_csv(fout)
1822
+ csvwriter = csv.writer(fout)
1823
+ csvwriter.writerow(['Keypath', 'Value'])
1824
+
1825
+ allkeys = schema.allkeys()
1826
+ for key in allkeys:
1827
+ keypath = ','.join(key)
1828
+ param = schema.get(*key, field=None)
1829
+ for value, step, index in param.getvalues():
1830
+ if step is None and index is None:
1831
+ keypath = ','.join(key)
1832
+ elif index is None:
1833
+ keypath = ','.join([*key, step, 'default'])
1834
+ else:
1835
+ keypath = ','.join([*key, step, index])
1836
+
1837
+ if isinstance(value, list):
1838
+ for item in value:
1839
+ csvwriter.writerow([keypath, item])
1840
+ else:
1841
+ csvwriter.writerow([keypath, value])
1835
1842
  else:
1836
1843
  self.error(f'File format not recognized {filepath}')
1837
1844
  finally:
1838
1845
  fout.close()
1839
1846
 
1847
+ def __write_tcl(self, fout, schema,
1848
+ prefix="", step=None, index=None, template=None, record=False):
1849
+ tcl_set_cmds = []
1850
+ for key in sorted(schema.allkeys()):
1851
+ # print out all non default values
1852
+ if 'default' in key:
1853
+ continue
1854
+
1855
+ param = schema.get(*key, field=None)
1856
+
1857
+ # create a TCL dict
1858
+ keystr = ' '.join([NodeType.to_tcl(keypart, 'str') for keypart in key])
1859
+
1860
+ valstr = param.gettcl(step=step, index=index)
1861
+ if valstr is None:
1862
+ continue
1863
+
1864
+ # Ensure empty values get something
1865
+ if valstr == '':
1866
+ valstr = '{}'
1867
+
1868
+ tcl_set_cmds.append(f"{prefix} {keystr} {valstr}")
1869
+
1870
+ if template:
1871
+ fout.write(template.render(manifest_dict='\n'.join(tcl_set_cmds),
1872
+ scroot=os.path.abspath(
1873
+ os.path.join(os.path.dirname(__file__))),
1874
+ record_access=record,
1875
+ record_access_id=Schema._RECORD_ACCESS_IDENTIFIER))
1876
+ else:
1877
+ for cmd in tcl_set_cmds:
1878
+ fout.write(cmd + '\n')
1879
+ fout.write('\n')
1880
+
1840
1881
  ###########################################################################
1841
1882
  def check_checklist(self, standard, items=None,
1842
1883
  check_ok=False, verbose=False, require_reports=True):
@@ -1978,15 +2019,17 @@ class Chip:
1978
2019
  self.get('tool', tool, 'task', task, 'report', metric, job=job,
1979
2020
  step=step, index=index)
1980
2021
 
1981
- if metric in metrics_without_reports and not has_reports:
2022
+ if allow_missing_reports and not has_reports:
1982
2023
  # No reports available and it is allowed
1983
2024
  continue
1984
2025
 
2026
+ reports = []
1985
2027
  try:
1986
- reports = self.find_files('tool', tool, 'task', task, 'report', metric,
1987
- job=job,
1988
- step=step, index=index,
1989
- missing_ok=not require_reports)
2028
+ if has_reports:
2029
+ reports = self.find_files('tool', tool, 'task', task, 'report', metric,
2030
+ job=job,
2031
+ step=step, index=index,
2032
+ missing_ok=not require_reports)
1990
2033
  except SiliconCompilerError:
1991
2034
  reports = []
1992
2035
  continue
@@ -2022,33 +2065,38 @@ class Chip:
2022
2065
  return not error
2023
2066
 
2024
2067
  ###########################################################################
2025
- def __import_library(self, libname, libcfg, job=None, clobber=True, keep_input=True):
2068
+ def __import_library(self, libname, library, job=None, clobber=True, keep_input=True):
2026
2069
  '''Helper to import library with config 'libconfig' as a library
2027
2070
  'libname' in current Chip object.'''
2028
- if job:
2029
- cfg = self.schema.cfg['history'][job]['library']
2030
- else:
2031
- cfg = self.schema.cfg['library']
2032
-
2033
- if 'library' in libcfg:
2034
- for sublib_name, sublibcfg in libcfg['library'].items():
2035
- self.__import_library(sublib_name, sublibcfg,
2036
- job=job, clobber=clobber, keep_input=keep_input)
2037
2071
 
2038
- if libname in cfg:
2072
+ if libname in self.schema.getkeys('library'):
2039
2073
  if not clobber:
2040
2074
  return
2075
+ if hasattr(library, 'schema'):
2076
+ library = library.schema
2077
+
2078
+ try:
2079
+ for sublib in library.getkeys('library'):
2080
+ self.__import_library(sublib,
2081
+ EditableSchema(library).search('library', sublib),
2082
+ job=job, clobber=clobber, keep_input=keep_input)
2083
+ except KeyError:
2084
+ pass
2041
2085
 
2042
- self.__import_data_sources(libcfg)
2043
- cfg[libname] = {}
2086
+ self.__import_data_sources(library)
2044
2087
 
2045
2088
  # Only keep some sections to avoid recursive bloat
2046
2089
  keeps = ['asic', 'design', 'fpga', 'option', 'output', 'package']
2047
2090
  if keep_input:
2048
2091
  keeps.append('input')
2049
- for section in list(libcfg.keys()):
2050
- if section in keeps:
2051
- cfg[libname][section] = copy.deepcopy(libcfg[section])
2092
+
2093
+ importlibrary = library.copy()
2094
+ edit_lib = EditableSchema(importlibrary)
2095
+ for section in list(importlibrary.getkeys()):
2096
+ if section not in keeps:
2097
+ edit_lib.remove(section)
2098
+
2099
+ EditableSchema(self.schema).insert("library", libname, importlibrary, clobber=True)
2052
2100
 
2053
2101
  ###########################################################################
2054
2102
  def write_flowgraph(self, filename, flow=None,
@@ -2333,6 +2381,8 @@ class Chip:
2333
2381
 
2334
2382
  nodes = {}
2335
2383
 
2384
+ search_schema = EditableSchema(self.schema)
2385
+
2336
2386
  def collect_library(root_type, lib, name=None):
2337
2387
  if not name:
2338
2388
  name = lib.design
@@ -2371,15 +2421,15 @@ class Chip:
2371
2421
 
2372
2422
  for in_lib in lib.get('option', 'library',
2373
2423
  step=Schema.GLOBAL_KEY, index=Schema.GLOBAL_KEY):
2374
- collect_library("library", Schema(cfg=self.getdict('library', in_lib)),
2424
+ collect_library("library", search_schema.search('library', in_lib),
2375
2425
  name=in_lib)
2376
2426
  for in_lib in lib.get('asic', 'logiclib',
2377
2427
  step=Schema.GLOBAL_KEY, index=Schema.GLOBAL_KEY):
2378
- collect_library("logiclib", Schema(cfg=self.getdict('library', in_lib)),
2428
+ collect_library("logiclib", search_schema.search('library', in_lib),
2379
2429
  name=in_lib)
2380
2430
  for in_lib in lib.get('asic', 'macrolib',
2381
2431
  step=Schema.GLOBAL_KEY, index=Schema.GLOBAL_KEY):
2382
- collect_library("macrolib", Schema(cfg=self.getdict('library', in_lib)),
2432
+ collect_library("macrolib", search_schema.search('library', in_lib),
2383
2433
  name=in_lib)
2384
2434
 
2385
2435
  collect_library("design", self)
@@ -2417,6 +2467,8 @@ class Chip:
2417
2467
  all_libraries = self.getkeys('library')
2418
2468
 
2419
2469
  def swap(*key):
2470
+ if not self.schema.valid(*key):
2471
+ return
2420
2472
  if step is not None:
2421
2473
  r_step = step
2422
2474
  r_index = index
@@ -2432,7 +2484,7 @@ class Chip:
2432
2484
  list(map(lambda x: new_library if x == org_library else x, val)),
2433
2485
  step=r_step, index=r_index)
2434
2486
  else:
2435
- for val, r_step, r_index in self.schema._getvals(*key):
2487
+ for val, r_step, r_index in self.schema.get(*key, field=None).getvalues():
2436
2488
  if r_step is None:
2437
2489
  r_step = Schema.GLOBAL_KEY
2438
2490
  if r_index is None:
@@ -2510,7 +2562,7 @@ class Chip:
2510
2562
  is_file = re.search('file', leaftype)
2511
2563
  if is_dir or is_file:
2512
2564
  if self.get(*key, field='copy'):
2513
- for value, step, index in self.schema._getvals(*key):
2565
+ for value, step, index in self.schema.get(*key, field=None).getvalues():
2514
2566
  if not value:
2515
2567
  continue
2516
2568
  packages = self.get(*key, field='package', step=step, index=index)
@@ -2694,7 +2746,7 @@ class Chip:
2694
2746
  flowgraph_nodes = [(step, index)]
2695
2747
  elif step:
2696
2748
  flow = self.get('option', 'flow')
2697
- flowgraph_nodes = _get_flowgraph_nodes(self, flow=flow, steps=[step])
2749
+ flowgraph_nodes = [(step, index) for index in self.getkeys("flowgraph", flow, step)]
2698
2750
  else:
2699
2751
  flowgraph_nodes = nodes_to_execute(self)
2700
2752
 
@@ -2815,8 +2867,12 @@ class Chip:
2815
2867
  if check:
2816
2868
  # compare previous hash to new hash
2817
2869
  oldhash = self.schema.get(*keypath, step=step, index=index, field='filehash')
2870
+ if not isinstance(oldhash, list):
2871
+ oldhash = [oldhash]
2818
2872
  check_failed = False
2819
2873
  for i, item in enumerate(oldhash):
2874
+ if item is None:
2875
+ continue
2820
2876
  if item != hashlist[i]:
2821
2877
  self.logger.error(f"Hash mismatch for [{keypath}]")
2822
2878
  check_failed = True
@@ -2829,11 +2885,11 @@ class Chip:
2829
2885
  set_step = None
2830
2886
  set_index = None
2831
2887
  pernode = self.get(*keypath, field='pernode')
2832
- if pernode == schema_utils.PerNode.REQUIRED:
2888
+ if pernode == PerNode.REQUIRED:
2833
2889
  set_step = step
2834
2890
  set_index = index
2835
- elif pernode == schema_utils.PerNode.OPTIONAL:
2836
- for vals, key_step, key_index in self.schema._getvals(*keypath):
2891
+ elif pernode == PerNode.OPTIONAL:
2892
+ for vals, key_step, key_index in self.schema.get(*keypath, field=None).getvalues():
2837
2893
  if key_step == step and key_index == index and vals:
2838
2894
  set_step = step
2839
2895
  set_index = index
@@ -2918,9 +2974,12 @@ class Chip:
2918
2974
 
2919
2975
  # display whole flowgraph if no from/to specified
2920
2976
  flow = self.get('option', 'flow')
2921
- nodes_to_execute = get_executed_nodes(self, flow)
2922
-
2923
- _show_summary_table(self, flow, nodes_to_execute, show_all_indices=show_all_indices)
2977
+ runtime = RuntimeFlowgraph(
2978
+ self.schema.get("flowgraph", flow, field='schema'),
2979
+ to_steps=self.get('option', 'to'),
2980
+ prune_nodes=self.get('option', 'prune'))
2981
+ _show_summary_table(self, flow, list(runtime.get_nodes()),
2982
+ show_all_indices=show_all_indices)
2924
2983
 
2925
2984
  # dashboard does not generate any data
2926
2985
  self.logger.info('Dashboard at "sc-dashboard '
@@ -3015,40 +3074,15 @@ class Chip:
3015
3074
  Creates a 'place' task with step='apr_place' and index=0 and binds it to the
3016
3075
  'openroad' tool.
3017
3076
  '''
3077
+ from siliconcompiler import FlowgraphSchema
3078
+ from siliconcompiler.schema import EditableSchema
3018
3079
 
3019
- if step in (Schema.GLOBAL_KEY, 'default', 'sc_collected_files'):
3020
- self.error(f'Illegal step name: {step} is reserved')
3021
- return
3022
-
3023
- index = str(index)
3024
-
3025
- # Determine task name and module
3026
- task_module = None
3027
- if (isinstance(task, str)):
3028
- task_module = task
3029
- elif inspect.ismodule(task):
3030
- task_module = task.__name__
3031
- self.modules[task_module] = task
3080
+ if not self.schema.valid("flowgraph", flow):
3081
+ graph = FlowgraphSchema(flow)
3082
+ EditableSchema(self.schema).insert("flowgraph", flow, graph)
3032
3083
  else:
3033
- raise SiliconCompilerError(
3034
- f"{task} is not a string or module and cannot be used to setup a task.",
3035
- chip=self)
3036
-
3037
- task_parts = task_module.split('.')
3038
- if len(task_parts) < 2:
3039
- raise SiliconCompilerError(
3040
- f"{task} is not a valid task, it must be associated with a tool '<tool>.<task>'.",
3041
- chip=self)
3042
- tool_name, task_name = task_parts[-2:]
3043
-
3044
- # bind tool to node
3045
- self.set('flowgraph', flow, step, index, 'tool', tool_name)
3046
- self.set('flowgraph', flow, step, index, 'task', task_name)
3047
- self.set('flowgraph', flow, step, index, 'taskmodule', task_module)
3048
-
3049
- # set default weights
3050
- for metric in self.getkeys('metric'):
3051
- self.set('flowgraph', flow, step, index, 'weight', metric, 0)
3084
+ graph = self.schema.get("flowgraph", flow, field="schema")
3085
+ graph.node(step, task, index=index)
3052
3086
 
3053
3087
  ###########################################################################
3054
3088
  def edge(self, flow, tail, head, tail_index=0, head_index=0):
@@ -3073,21 +3107,9 @@ class Chip:
3073
3107
  >>> chip.edge('place', 'cts')
3074
3108
  Creates a directed edge from place to cts.
3075
3109
  '''
3076
- head_index = str(head_index)
3077
- tail_index = str(tail_index)
3078
-
3079
- for step in (head, tail):
3080
- if step in (Schema.GLOBAL_KEY, 'default'):
3081
- self.error(f'Illegal step name: {step} is reserved')
3082
- return
3083
-
3084
- tail_node = (tail, tail_index)
3085
- if tail_node in self.get('flowgraph', flow, head, head_index, 'input'):
3086
- self.logger.warning(f'Edge from {tail}{tail_index} to {head}{head_index} already '
3087
- 'exists, skipping')
3088
- return
3089
3110
 
3090
- self.add('flowgraph', flow, head, head_index, 'input', tail_node)
3111
+ graph = self.schema.get("flowgraph", flow, field="schema")
3112
+ graph.edge(tail, head, tail_index=tail_index, head_index=head_index)
3091
3113
 
3092
3114
  ###########################################################################
3093
3115
  def remove_node(self, flow, step, index=None):
@@ -3103,34 +3125,8 @@ class Chip:
3103
3125
  if flow not in self.getkeys('flowgraph'):
3104
3126
  raise ValueError(f'{flow} is not in the manifest')
3105
3127
 
3106
- if step not in self.getkeys('flowgraph', flow):
3107
- raise ValueError(f'{step} is not a valid step in {flow}')
3108
-
3109
- if index is None:
3110
- # Iterate over all indexes
3111
- for index in self.getkeys('flowgraph', flow, step):
3112
- self.remove_node(flow, step, index)
3113
- return
3114
-
3115
- index = str(index)
3116
- if index not in self.getkeys('flowgraph', flow, step):
3117
- raise ValueError(f'{index} is not a valid index for {step} in {flow}')
3118
-
3119
- # Save input edges
3120
- node = (step, index)
3121
- node_inputs = self.get('flowgraph', flow, step, index, 'input')
3122
- self.remove('flowgraph', flow, step, index)
3123
-
3124
- if len(self.getkeys('flowgraph', flow, step)) == 0:
3125
- self.remove('flowgraph', flow, step)
3126
-
3127
- for flow_step in self.getkeys('flowgraph', flow):
3128
- for flow_index in self.getkeys('flowgraph', flow, flow_step):
3129
- inputs = self.get('flowgraph', flow, flow_step, flow_index, 'input')
3130
- if node in inputs:
3131
- inputs = [inode for inode in inputs if inode != node]
3132
- inputs.extend(node_inputs)
3133
- self.set('flowgraph', flow, flow_step, flow_index, 'input', set(inputs))
3128
+ graph = self.schema.get("flowgraph", flow, field="schema")
3129
+ graph.remove_node(step, index=index)
3134
3130
 
3135
3131
  ###########################################################################
3136
3132
  def graph(self, flow, subflow, name=None):
@@ -3146,27 +3142,9 @@ class Chip:
3146
3142
  >>> chip.graph('asicflow')
3147
3143
  Instantiates a flow named 'asicflow'.
3148
3144
  '''
3149
- for step in self.getkeys('flowgraph', subflow):
3150
- # uniquify each step
3151
- if name is None:
3152
- newstep = step
3153
- else:
3154
- newstep = name + "." + step
3155
-
3156
- for keys in self.allkeys('flowgraph', subflow, step):
3157
- val = self.get('flowgraph', subflow, step, *keys)
3158
- self.set('flowgraph', flow, newstep, *keys, val)
3159
-
3160
- if name is None:
3161
- continue
3162
-
3163
- for index in self.getkeys('flowgraph', flow, newstep):
3164
- # rename inputs
3165
- all_inputs = self.get('flowgraph', flow, newstep, index, 'input')
3166
- self.set('flowgraph', flow, newstep, index, 'input', [])
3167
- for in_step, in_index in all_inputs:
3168
- newin = name + "." + in_step
3169
- self.add('flowgraph', flow, newstep, index, 'input', (newin, in_index))
3145
+ graph = self.schema.get("flowgraph", flow, field="schema")
3146
+ subgraph = self.schema.get("flowgraph", subflow, field="schema")
3147
+ graph.graph(subgraph, name=name)
3170
3148
 
3171
3149
  ###########################################################################
3172
3150
  def run(self, raise_exception=False):
@@ -3334,7 +3312,7 @@ class Chip:
3334
3312
  self.set('option', 'jobname', f'_{taskname}_{sc_job}_{sc_step}{sc_index}', clobber=True)
3335
3313
 
3336
3314
  # Setup in step/index variables
3337
- for (step, index) in _get_flowgraph_nodes(self, 'showflow'):
3315
+ for step, index in self.get("flowgraph", "showflow", field="schema").get_nodes():
3338
3316
  if step != taskname:
3339
3317
  continue
3340
3318
  show_tool, _ = get_tool_task(self, step, index, flow='showflow')
@@ -3356,7 +3334,8 @@ class Chip:
3356
3334
  try:
3357
3335
  self.run(raise_exception=True)
3358
3336
  if screenshot:
3359
- step, index = _get_flowgraph_exit_nodes(self, flow='showflow')[0]
3337
+ step, index = self.schema.get("flowgraph", 'showflow',
3338
+ field="schema").get_exit_nodes()[0]
3360
3339
  success = self.find_result('png', step=step, index=index)
3361
3340
  else:
3362
3341
  success = True
@@ -3434,8 +3413,8 @@ class Chip:
3434
3413
  if hasattr(self, 'logger'):
3435
3414
  self.logger.error(msg)
3436
3415
 
3437
- step = self.get('arg', 'step')
3438
- index = self.get('arg', 'index')
3416
+ step = self.schema.get('arg', 'step')
3417
+ index = self.schema.get('arg', 'index')
3439
3418
  if self.schema.get('option', 'continue', step=step, index=index):
3440
3419
  self._error = True
3441
3420
  return
@@ -3467,4 +3446,3 @@ class Chip:
3467
3446
 
3468
3447
  # Reinitialize logger on restore
3469
3448
  self._init_logger()
3470
- self.schema._init_logger(self.logger)