siliconcompiler 0.28.4__py3-none-any.whl → 0.28.5__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 (41) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/_common.py +88 -56
  3. siliconcompiler/apps/sc.py +33 -14
  4. siliconcompiler/apps/sc_dashboard.py +16 -9
  5. siliconcompiler/apps/sc_show.py +17 -15
  6. siliconcompiler/core.py +3 -1
  7. siliconcompiler/flows/drcflow.py +13 -0
  8. siliconcompiler/flows/interposerflow.py +17 -0
  9. siliconcompiler/libs/interposer.py +8 -0
  10. siliconcompiler/pdks/interposer.py +8 -0
  11. siliconcompiler/remote/schema.py +11 -1
  12. siliconcompiler/remote/server.py +7 -2
  13. siliconcompiler/scheduler/__init__.py +93 -0
  14. siliconcompiler/schema/schema_cfg.py +15 -3
  15. siliconcompiler/schema/schema_obj.py +51 -1
  16. siliconcompiler/targets/interposer_demo.py +56 -0
  17. siliconcompiler/templates/tcl/manifest.tcl.j2 +2 -0
  18. siliconcompiler/tools/klayout/export.py +7 -4
  19. siliconcompiler/tools/klayout/klayout_export.py +3 -0
  20. siliconcompiler/tools/klayout/klayout_utils.py +8 -2
  21. siliconcompiler/tools/openroad/metrics.py +45 -0
  22. siliconcompiler/tools/openroad/openroad.py +3 -0
  23. siliconcompiler/tools/openroad/rdlroute.py +97 -0
  24. siliconcompiler/tools/openroad/scripts/sc_apr.tcl +1 -1
  25. siliconcompiler/tools/openroad/scripts/sc_metrics.tcl +0 -169
  26. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +184 -0
  27. siliconcompiler/tools/openroad/scripts/sc_report.tcl +170 -0
  28. siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +11 -1
  29. siliconcompiler/tools/xyce/__init__.py +1 -1
  30. siliconcompiler/toolscripts/_tools.json +3 -4
  31. siliconcompiler/toolscripts/rhel8/install-xyce.sh +4 -5
  32. siliconcompiler/toolscripts/rhel9/install-xyce.sh +4 -5
  33. siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +5 -5
  34. siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +2 -2
  35. siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +2 -2
  36. {siliconcompiler-0.28.4.dist-info → siliconcompiler-0.28.5.dist-info}/METADATA +4 -4
  37. {siliconcompiler-0.28.4.dist-info → siliconcompiler-0.28.5.dist-info}/RECORD +41 -32
  38. {siliconcompiler-0.28.4.dist-info → siliconcompiler-0.28.5.dist-info}/WHEEL +1 -1
  39. {siliconcompiler-0.28.4.dist-info → siliconcompiler-0.28.5.dist-info}/LICENSE +0 -0
  40. {siliconcompiler-0.28.4.dist-info → siliconcompiler-0.28.5.dist-info}/entry_points.txt +0 -0
  41. {siliconcompiler-0.28.4.dist-info → siliconcompiler-0.28.5.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  # Version number following semver standard.
2
- version = '0.28.4'
2
+ version = '0.28.5'
3
3
 
4
4
  # Default server address for remote runs, if unspecified.
5
5
  default_server = 'https://server.siliconcompiler.com'
@@ -1,7 +1,11 @@
1
- import glob
2
1
  import os
3
2
 
4
3
 
4
+ # TODO: this is a hack to get around design name requirement: since legal
5
+ # design names probably can't contain spaces, we can detect if it is unset.
6
+ UNSET_DESIGN = ' unset '
7
+
8
+
5
9
  def manifest_switches():
6
10
  '''
7
11
  Returns a list of manifest switches that can be used
@@ -14,63 +18,91 @@ def manifest_switches():
14
18
  '-jobname']
15
19
 
16
20
 
17
- def load_manifest(chip, src_file):
18
- manifest = None
19
- if (src_file is not None) and (not chip.get('option', 'cfg')):
20
- if not os.path.exists(src_file):
21
- chip.logger.error(f'{src_file} cannot be found.')
22
- return False
23
- # only autoload manifest if user doesn't supply manually
24
- manifest = _get_manifest(os.path.dirname(src_file))
25
- if not manifest:
26
- design = os.path.splitext(os.path.basename(src_file))[0]
27
- chip.logger.error(f'Unable to automatically find manifest for design {design}. '
28
- 'Please provide a manifest explicitly using -cfg.')
29
- return False
30
- elif not chip.get('option', 'cfg'):
31
- manifest = _get_manifest_from_design(chip)
32
- if not manifest:
33
- chip.logger.error(f'Could not find manifest for {chip.design}')
34
- return False
21
+ def _get_manifests(cwd):
22
+ manifests = {}
35
23
 
36
- if manifest:
37
- chip.logger.info(f'Loading manifest: {manifest}')
38
- chip.read_manifest(manifest)
39
- return True
40
-
41
-
42
- def _get_manifest(dirname, design='*'):
43
- # pkg.json file may have a different name from the design due to the entrypoint
44
- glob_paths = [os.path.join(dirname, f'{design}.pkg.json'),
45
- os.path.join(dirname, 'outputs', f'{design}.pkg.json')]
46
- manifest = None
47
- for path in glob_paths:
48
- manifest = glob.glob(path)
49
- if manifest:
50
- manifest = manifest[0]
51
- break
52
-
53
- if not manifest or not os.path.isfile(manifest):
24
+ def get_dirs(cwd):
25
+ dirs = []
26
+ for dirname in os.listdir(cwd):
27
+ fullpath = os.path.join(cwd, dirname)
28
+ if os.path.isdir(fullpath):
29
+ dirs.append((dirname, fullpath))
30
+ return dirs
31
+
32
+ for _, buildpath in get_dirs(cwd):
33
+ for design, designdir in get_dirs(buildpath):
34
+ for jobname, jobdir in get_dirs(designdir):
35
+ manifest = os.path.join(jobdir, f'{design}.pkg.json')
36
+ if os.path.isfile(manifest):
37
+ manifests[(design, jobname, None, None)] = manifest
38
+ for step, stepdir in get_dirs(jobdir):
39
+ for index, indexdir in get_dirs(stepdir):
40
+ manifest = os.path.join(indexdir, 'outputs', f'{design}.pkg.json')
41
+ if os.path.isfile(manifest):
42
+ manifests[(design, jobname, step, index)] = manifest
43
+ else:
44
+ manifest = os.path.join(indexdir, 'inputs', f'{design}.pkg.json')
45
+ if os.path.isfile(manifest):
46
+ manifests[(design, jobname, step, index)] = manifest
47
+
48
+ organized_manifest = {}
49
+ for (design, job, step, index), manifest in manifests.items():
50
+ jobs = organized_manifest.setdefault(design, {})
51
+ jobs.setdefault(job, {})[step, index] = os.path.abspath(manifest)
52
+
53
+ return organized_manifest
54
+
55
+
56
+ def pick_manifest_from_file(chip, src_file, all_manifests):
57
+ if src_file is None:
54
58
  return None
55
- return manifest
56
59
 
60
+ if not os.path.exists(src_file):
61
+ chip.logger.error(f'{src_file} cannot be found.')
62
+ return None
63
+
64
+ src_dir = os.path.abspath(os.path.dirname(src_file))
65
+ for _, jobs in all_manifests.items():
66
+ for _, nodes in jobs.items():
67
+ for manifest in nodes.values():
68
+ if src_dir == os.path.dirname(manifest):
69
+ return manifest
57
70
 
58
- def _get_manifest_from_design(chip):
59
- for jobname, step, index in [
60
- (chip.get('option', 'jobname'),
61
- chip.get('arg', 'step'),
62
- chip.get('arg', 'index')),
63
- (chip.get('option', 'jobname'),
64
- None,
65
- None),
66
- (chip.schema.get_default('option', 'jobname'),
67
- chip.get('arg', 'step'),
68
- chip.get('arg', 'index')),
69
- (chip.schema.get_default('option', 'jobname'),
70
- None,
71
- None)]:
72
- manifest = _get_manifest(chip.getworkdir(jobname=jobname, step=step, index=index))
73
-
74
- if manifest:
75
- return manifest
76
71
  return None
72
+
73
+
74
+ def pick_manifest(chip, src_file=None):
75
+ all_manifests = _get_manifests(os.getcwd())
76
+
77
+ manifest = pick_manifest_from_file(chip, src_file, all_manifests)
78
+ if manifest:
79
+ return manifest
80
+
81
+ if chip.design == UNSET_DESIGN:
82
+ if len(all_manifests) == 1:
83
+ chip.set('design', list(all_manifests.keys())[0])
84
+ else:
85
+ chip.logger.error('Design name is not set')
86
+ return None
87
+
88
+ if chip.design not in all_manifests:
89
+ chip.logger.error(f'Could not find manifest for {chip.design}')
90
+ return None
91
+
92
+ if chip.get('option', 'jobname') not in all_manifests[chip.design] and \
93
+ len(all_manifests[chip.design]) != 1:
94
+ chip.logger.error(f'Could not determine jobname for {chip.design}')
95
+ return None
96
+
97
+ jobname = chip.get('option', 'jobname')
98
+ if chip.get('option', 'jobname') not in all_manifests[chip.design]:
99
+ jobname = list(all_manifests[chip.design].keys())[0]
100
+
101
+ if (None, None) in all_manifests[chip.design][jobname]:
102
+ manifest = all_manifests[chip.design][jobname][None, None]
103
+ else:
104
+ # pick newest manifest
105
+ manifest = list(sorted(all_manifests[chip.design][jobname].values(),
106
+ key=lambda file: os.stat(file).st_ctime))[-1]
107
+
108
+ return manifest
@@ -10,6 +10,37 @@ from siliconcompiler.targets import skywater130_demo
10
10
  from siliconcompiler import SiliconCompilerError
11
11
 
12
12
 
13
+ def _infer_designname(chip):
14
+ topfile = None
15
+ sourcesets = chip.getkeys('input')
16
+ for sourceset in reversed(('rtl', 'hll')):
17
+ if sourceset in sourcesets:
18
+ sourcesets.remove(sourceset)
19
+ sourcesets.insert(0, sourceset)
20
+ for sourceset in sourcesets:
21
+ for filetype in chip.getkeys('input', sourceset):
22
+ all_vals = chip.schema._getvals('input', sourceset, filetype)
23
+ if all_vals:
24
+ # just look at first value
25
+ sources, _, _ = all_vals[0]
26
+ # grab first source
27
+ topfile = sources[0]
28
+ break
29
+ if topfile:
30
+ break
31
+
32
+ if not topfile:
33
+ return None
34
+
35
+ root = os.path.basename(topfile)
36
+ while True:
37
+ root, ext = os.path.splitext(root)
38
+ if not ext:
39
+ break
40
+
41
+ return root
42
+
43
+
13
44
  ###########################
14
45
  def main():
15
46
  progname = "sc"
@@ -50,24 +81,12 @@ def main():
50
81
 
51
82
  # Set design if none specified
52
83
  if chip.get('design') == UNSET_DESIGN:
53
- topfile = None
54
- for sourceset in ('rtl', 'hll'):
55
- for filetype in chip.getkeys('input', sourceset):
56
- all_vals = chip.schema._getvals('input', sourceset, filetype)
57
- if all_vals:
58
- # just look at first value
59
- sources, _, _ = all_vals[0]
60
- # grab first source
61
- topfile = sources[0]
62
- break
63
- if topfile:
64
- break
84
+ topmodule = _infer_designname(chip)
65
85
 
66
- if not topfile:
86
+ if not topmodule:
67
87
  chip.logger.error('Invalid arguments: either specify -design or provide sources.')
68
88
  return 1
69
89
 
70
- topmodule = os.path.splitext(os.path.basename(topfile))[0]
71
90
  chip.set('design', topmodule)
72
91
 
73
92
  # Set demo target if none specified
@@ -2,7 +2,7 @@
2
2
  import sys
3
3
  import siliconcompiler
4
4
  import os
5
- from siliconcompiler.apps._common import load_manifest, manifest_switches
5
+ from siliconcompiler.apps._common import pick_manifest, manifest_switches, UNSET_DESIGN
6
6
 
7
7
 
8
8
  def main():
@@ -11,9 +11,16 @@ def main():
11
11
  -----------------------------------------------------------
12
12
  SC app to open a dashboard for a given manifest.
13
13
 
14
- To open:
14
+ To open and allow sc-dashboard to autoload manifest:
15
+ sc-dashboard
16
+
17
+ To open by specifying manifest:
15
18
  sc-dashboard -cfg <path to manifest>
16
19
 
20
+ To open by specifying design and optionally jobname:
21
+ sc-dashboard -design <name>
22
+ sc-dashboard -design <name> -jobname <jobname>
23
+
17
24
  To specify a different port than the default:
18
25
  sc-dashboard -cfg <path to manifest> -port 10000
19
26
 
@@ -23,10 +30,6 @@ To include another chip object to compare to:
23
30
  -----------------------------------------------------------
24
31
  """
25
32
 
26
- # TODO: this is a hack to get around design name requirement: since legal
27
- # design names probably can't contain spaces, we can detect if it is unset.
28
- UNSET_DESIGN = ' unset '
29
-
30
33
  # Create a base chip class.
31
34
  chip = siliconcompiler.Chip(UNSET_DESIGN)
32
35
 
@@ -54,15 +57,19 @@ To include another chip object to compare to:
54
57
  chip.logger.error(e)
55
58
  return 1
56
59
 
60
+ if not chip.get('option', 'cfg'):
61
+ manifest = pick_manifest(chip)
62
+
63
+ if manifest:
64
+ chip.logger.info(f'Loading manifest: {manifest}')
65
+ chip.read_manifest(manifest)
66
+
57
67
  # Error checking
58
68
  design = chip.get('design')
59
69
  if design == UNSET_DESIGN:
60
70
  chip.logger.error('Design not loaded')
61
71
  return 1
62
72
 
63
- if not load_manifest(chip, None):
64
- return 1
65
-
66
73
  graph_chips = []
67
74
  if switches['graph_cfg']:
68
75
  for i, name_and_file_path in enumerate(switches['graph_cfg']):
@@ -3,7 +3,7 @@ import sys
3
3
  import os
4
4
  import siliconcompiler
5
5
  from siliconcompiler.utils import get_default_iomap
6
- from siliconcompiler.apps._common import load_manifest, manifest_switches
6
+ from siliconcompiler.apps._common import manifest_switches, pick_manifest, UNSET_DESIGN
7
7
  from siliconcompiler.utils import get_file_ext
8
8
 
9
9
 
@@ -18,6 +18,9 @@ def main():
18
18
 
19
19
  Examples:
20
20
 
21
+ sc-show
22
+ (displays build/adder/job0/write_gds/0/outputs/adder.gds)
23
+
21
24
  sc-show -design adder
22
25
  (displays build/adder/job0/write_gds/0/outputs/adder.gds)
23
26
 
@@ -40,10 +43,6 @@ def main():
40
43
  (displays build/adder/job0/route/1/outputs/adder.def)
41
44
  """
42
45
 
43
- # TODO: this is a hack to get around design name requirement: since legal
44
- # design names probably can't contain spaces, we can detect if it is unset.
45
- UNSET_DESIGN = ' unset '
46
-
47
46
  # Create a base chip class.
48
47
  chip = siliconcompiler.Chip(UNSET_DESIGN)
49
48
 
@@ -81,10 +80,6 @@ def main():
81
80
  chip.logger.error(e)
82
81
  return 1
83
82
 
84
- # Error checking
85
- design = chip.get('design')
86
- design_set = design != UNSET_DESIGN
87
-
88
83
  # Search input keys for files
89
84
  input_mode = []
90
85
  for fileset in chip.getkeys('input'):
@@ -92,11 +87,6 @@ def main():
92
87
  if chip.schema._getvals('input', fileset, mode):
93
88
  input_mode = [('input', fileset, mode)]
94
89
 
95
- if not (design_set or input_mode):
96
- chip.logger.error('Nothing to load: please define a target with '
97
- '-cfg, -design, and/or inputs.')
98
- return 1
99
-
100
90
  filename = None
101
91
  if input_mode:
102
92
  check_ext = list(chip._showtools.keys())
@@ -115,7 +105,19 @@ def main():
115
105
 
116
106
  filename = get_file_from_keys()
117
107
 
118
- if not load_manifest(chip, filename):
108
+ # Attempt to load a manifest
109
+ if not chip.get('option', 'cfg'):
110
+ manifest = pick_manifest(chip, src_file=filename)
111
+ if manifest:
112
+ chip.logger.info(f'Loading manifest: {manifest}')
113
+ chip.read_manifest(manifest)
114
+
115
+ # Error checking
116
+ design = chip.get('design')
117
+ design_set = design != UNSET_DESIGN
118
+ if not (design_set or input_mode):
119
+ chip.logger.error('Nothing to load: please define a target with '
120
+ '-cfg, -design, and/or inputs.')
119
121
  return 1
120
122
 
121
123
  # Read in file
siliconcompiler/core.py CHANGED
@@ -2123,7 +2123,9 @@ class Chip:
2123
2123
  nonlocal graph_idx
2124
2124
 
2125
2125
  for subgraph in graph_info["graphs"]:
2126
+ child_prefix = prefix
2126
2127
  if get_node_count(graph_info["graphs"][subgraph]) > 1:
2128
+ child_prefix = f"{child_prefix}{subgraph}."
2127
2129
  graph = graphviz.Digraph(name=f"cluster_{graph_idx}")
2128
2130
  graph_idx += 1
2129
2131
 
@@ -2143,7 +2145,7 @@ class Chip:
2143
2145
  else:
2144
2146
  graph = parent
2145
2147
 
2146
- build_graph(graph_info["graphs"][subgraph], graph, f"{prefix}{subgraph}.")
2148
+ build_graph(graph_info["graphs"][subgraph], graph, child_prefix)
2147
2149
 
2148
2150
  if graph is not parent:
2149
2151
  parent.subgraph(graph)
@@ -0,0 +1,13 @@
1
+ from siliconcompiler import Flow
2
+
3
+ from siliconcompiler.tools.klayout import drc
4
+
5
+
6
+ def setup():
7
+ '''
8
+ Perform a DRC run on an input GDS
9
+ '''
10
+ flow = Flow('drcflow')
11
+ flow.node('drcflow', 'drc', drc)
12
+
13
+ return flow
@@ -0,0 +1,17 @@
1
+ from siliconcompiler import Flow
2
+
3
+ from siliconcompiler.tools.openroad import rdlroute
4
+ from siliconcompiler.tools.klayout import export
5
+
6
+
7
+ def setup():
8
+ '''
9
+ A flow to perform RDL routing and generate a GDS
10
+ '''
11
+ flow = Flow('interposerflow')
12
+ flow.node('interposerflow', 'rdlroute', rdlroute)
13
+ flow.node('interposerflow', 'write_gds', export)
14
+
15
+ flow.edge('interposerflow', 'rdlroute', 'write_gds')
16
+
17
+ return flow
@@ -0,0 +1,8 @@
1
+ import siliconcompiler
2
+ from lambdapdk.interposer.libs.bumps import setup
3
+
4
+
5
+ #########################
6
+ if __name__ == "__main__":
7
+ lib = setup(siliconcompiler.Chip('<lib>'))
8
+ lib.write_manifest(f'{lib.top()}.json')
@@ -0,0 +1,8 @@
1
+ import siliconcompiler
2
+ from lambdapdk.interposer import setup
3
+
4
+
5
+ #########################
6
+ if __name__ == "__main__":
7
+ pdk = setup(siliconcompiler.Chip('<lib>'))
8
+ pdk.write_manifest(f'{pdk.top()}.json')
@@ -2,7 +2,7 @@ from siliconcompiler.schema.schema_cfg import scparam
2
2
  from siliconcompiler.schema import Schema
3
3
 
4
4
 
5
- SCHEMA_VERSION = '0.0.1'
5
+ SCHEMA_VERSION = '0.0.2'
6
6
 
7
7
 
8
8
  def schema_cfg():
@@ -93,6 +93,16 @@ def schema_cfg():
93
93
  schelp="""
94
94
  Provides explicit control over the level of debug logging printed.""")
95
95
 
96
+ scparam(cfg, ['option', 'checkinterval'],
97
+ sctype='int',
98
+ defvalue=30,
99
+ shorthelp="Interval for client",
100
+ switch="-checkinterval <int>",
101
+ example=["cli: -checkinterval 10",
102
+ "api: chip.set('option', 'checkinterval', 10)"],
103
+ schelp="""
104
+ Interval between checks to announce to clients""")
105
+
96
106
  return cfg
97
107
 
98
108
 
@@ -239,7 +239,7 @@ class Server:
239
239
 
240
240
  # Return a response to the client.
241
241
  return web.json_response({'message': f"Starting job: {job_hash}",
242
- 'interval': 30,
242
+ 'interval': self.checkinterval,
243
243
  'job_hash': job_hash})
244
244
 
245
245
  ####################
@@ -375,7 +375,7 @@ class Server:
375
375
  'sc_schema': sc_schema_version,
376
376
  'sc_server': Server.__version__,
377
377
  },
378
- 'progress_interval': 30
378
+ 'progress_interval': self.checkinterval
379
379
  }
380
380
 
381
381
  username = job_params['username']
@@ -490,6 +490,11 @@ class Server:
490
490
  # Ensure that NFS mounting path is absolute.
491
491
  return os.path.abspath(self.get('option', 'nfsmount'))
492
492
 
493
+ ###################
494
+ @property
495
+ def checkinterval(self):
496
+ return self.get('option', 'checkinterval')
497
+
493
498
  def get(self, *keypath, field='value'):
494
499
  return self.schema.get(*keypath, field=field)
495
500
 
@@ -688,7 +688,9 @@ def _makecmd(chip, tool, task, step, index, script_name='replay.sh', include_pat
688
688
  runtime_options = getattr(chip._get_tool_module(step, index), 'runtime_options', None)
689
689
  if runtime_options:
690
690
  try:
691
+ chip.schema._start_record_access()
691
692
  cmdlist.extend(parse_options(runtime_options(chip)))
693
+ chip.schema._stop_record_access()
692
694
  except Exception as e:
693
695
  chip.logger.error(f'Failed to get runtime options for {tool}/{task}')
694
696
  raise e
@@ -983,7 +985,9 @@ def _post_process(chip, step, index):
983
985
  func = getattr(chip._get_task_module(step, index, flow=flow), 'post_process', None)
984
986
  if func:
985
987
  try:
988
+ chip.schema._start_record_access()
986
989
  func(chip)
990
+ chip.schema._stop_record_access()
987
991
  except Exception as e:
988
992
  chip.logger.error(f'Failed to run post-process for {tool}/{task}.')
989
993
  print_traceback(chip, e)
@@ -1076,7 +1080,9 @@ def _pre_process(chip, step, index):
1076
1080
  func = getattr(chip._get_task_module(step, index, flow=flow), 'pre_process', None)
1077
1081
  if func:
1078
1082
  try:
1083
+ chip.schema._start_record_access()
1079
1084
  func(chip)
1085
+ chip.schema._stop_record_access()
1080
1086
  except Exception as e:
1081
1087
  chip.logger.error(f"Pre-processing failed for '{tool}/{task}'.")
1082
1088
  raise e
@@ -1088,6 +1094,8 @@ def _pre_process(chip, step, index):
1088
1094
  def _set_env_vars(chip, step, index):
1089
1095
  flow = chip.get('option', 'flow')
1090
1096
  tool, task = get_tool_task(chip, step, index, flow)
1097
+
1098
+ chip.schema._start_record_access()
1091
1099
  # License file configuration.
1092
1100
  for item in chip.getkeys('tool', tool, 'licenseserver'):
1093
1101
  license_file = chip.get('tool', tool, 'licenseserver', item, step=step, index=index)
@@ -1100,6 +1108,8 @@ def _set_env_vars(chip, step, index):
1100
1108
  if val:
1101
1109
  os.environ[item] = val
1102
1110
 
1111
+ chip.schema._stop_record_access()
1112
+
1103
1113
 
1104
1114
  def _check_tool_version(chip, step, index, run_func=None):
1105
1115
  '''
@@ -1191,6 +1201,9 @@ def _hash_files(chip, step, index, setup=False):
1191
1201
 
1192
1202
 
1193
1203
  def _finalizenode(chip, step, index, replay):
1204
+ if chip.schema._do_record_access():
1205
+ assert_required_accesses(chip, step, index)
1206
+
1194
1207
  flow = chip.get('option', 'flow')
1195
1208
  tool, task = get_tool_task(chip, step, index, flow)
1196
1209
  quiet = (
@@ -1286,6 +1299,86 @@ def assert_output_files(chip, step, index):
1286
1299
  chip=chip)
1287
1300
 
1288
1301
 
1302
+ def assert_required_accesses(chip, step, index):
1303
+ flow = chip.get('option', 'flow')
1304
+ jobname = chip.get('option', 'jobname')
1305
+ tool, task = get_tool_task(chip, step, index, flow)
1306
+
1307
+ if tool == 'builtin':
1308
+ return
1309
+
1310
+ gets = chip.schema._get_record_access()
1311
+ logfile = os.path.join(
1312
+ chip.getworkdir(jobname=jobname, step=step, index=index),
1313
+ f'{step}.log')
1314
+
1315
+ with sc_open(logfile) as f:
1316
+ for line in f:
1317
+ if line.startswith(Schema._RECORD_ACCESS_IDENTIFIER):
1318
+ key = line[len(Schema._RECORD_ACCESS_IDENTIFIER):].strip().split(',')
1319
+ if chip.valid(*key, check_complete=True):
1320
+ gets.add(tuple(key))
1321
+
1322
+ def get_value(*key):
1323
+ if chip.get(*key, field='pernode') == 'never':
1324
+ return chip.get(*key)
1325
+ else:
1326
+ return chip.get(*key, step=step, index=index)
1327
+
1328
+ getkeys = set()
1329
+ # Remove keys with empty values
1330
+ for key in set(sorted(gets)):
1331
+ if get_value(*key):
1332
+ getkeys.add(key)
1333
+
1334
+ # Remove keys that dont matter
1335
+ exempt = [
1336
+ ('design',),
1337
+ ('arg', 'step'), ('arg', 'index'),
1338
+ ('option', 'jobname'), ('option', 'flow'), ('option', 'strict'), ('option', 'builddir'),
1339
+ ('option', 'quiet'),
1340
+ ('tool', tool, 'exe'),
1341
+ ('tool', tool, 'task', task, 'require'),
1342
+ ('tool', tool, 'task', task, 'threads'),
1343
+ ('flowgraph', flow, step, index, 'tool'), ('flowgraph', flow, step, index, 'task'),
1344
+ ('flowgraph', flow, step, index, 'taskmodule')]
1345
+ for key in chip.getkeys('metric'):
1346
+ exempt.append(('metric', key))
1347
+ for key in chip.getkeys('tool', tool, 'task', task, 'report'):
1348
+ exempt.append(('tool', tool, 'task', task, 'report', key))
1349
+
1350
+ # Get exempted keys from task
1351
+ func = getattr(chip._get_task_module(step, index, flow=flow), 'exempt_keys', None)
1352
+ if func:
1353
+ # No need for try / except since this must work properly
1354
+ exempt.extend(func(chip))
1355
+
1356
+ required = set(
1357
+ [tuple(key.split(',')) for key in chip.get('tool', tool, 'task', task, 'require',
1358
+ step=step, index=index)])
1359
+
1360
+ for key in set(exempt):
1361
+ if key in getkeys:
1362
+ getkeys.remove(key)
1363
+ if key in required:
1364
+ required.remove(key)
1365
+
1366
+ excess_require = required.difference(getkeys)
1367
+ if True:
1368
+ for key in sorted(excess_require):
1369
+ chip.logger.error(f"{step}{index} does not require requirement: {','.join(key)}")
1370
+ missing_require = getkeys.difference(required)
1371
+ for key in sorted(missing_require):
1372
+ chip.logger.error(f"{step}{index} has an unexpressed requirement: "
1373
+ f"{','.join(key)} = {get_value(*key)}")
1374
+
1375
+ if missing_require:
1376
+ raise SiliconCompilerError(
1377
+ f'Requirements for {step}{index} does not match access list: '
1378
+ f'{", ".join([",".join(key) for key in sorted(missing_require)])}',
1379
+ chip=chip)
1380
+
1381
+
1289
1382
  def _reset_flow_nodes(chip, flow, nodes_to_execute):
1290
1383
  # Reset flowgraph/records/metrics by probing build directory. We need
1291
1384
  # to set values to None for steps we may re-run so that merging
@@ -10,7 +10,7 @@ try:
10
10
  except ImportError:
11
11
  from siliconcompiler.schema.utils import trim
12
12
 
13
- SCHEMA_VERSION = '0.48.2'
13
+ SCHEMA_VERSION = '0.48.4'
14
14
 
15
15
  #############################################################################
16
16
  # PARAM DEFINITION
@@ -1252,7 +1252,6 @@ def schema_datasheet(cfg, name='default', mode='default'):
1252
1252
  # Package Description
1253
1253
  #########################
1254
1254
 
1255
- # high level description
1256
1255
  scparam(cfg, ['datasheet', 'package', name, 'type'],
1257
1256
  sctype='enum',
1258
1257
  enum=['bga', 'lga', 'csp', 'qfn', 'qfp', 'sop', 'die', 'wafer'],
@@ -1263,6 +1262,16 @@ def schema_datasheet(cfg, name='default', mode='default'):
1263
1262
  "api: chip.set('datasheet', 'package', 'abcd', 'type', 'bga')"],
1264
1263
  schelp="""Package type specified on a named package basis.""")
1265
1264
 
1265
+ scparam(cfg, ['datasheet', 'package', name, 'footprint'],
1266
+ sctype='str',
1267
+ shorthelp="Datasheet: package footprint",
1268
+ switch="-datasheet_package_footprint 'name <str>'",
1269
+ example=[
1270
+ "cli: -datasheet_package_footprint 'abcd soic8'",
1271
+ "api: chip.set('datasheet', 'package', 'abcd', 'footprint', 'soic8')"],
1272
+ schelp="""Package footprint name. The name of the footprint can be a standard
1273
+ footprint name or a reference designator from a footprint library.""")
1274
+
1266
1275
  scparam(cfg, ['datasheet', 'package', name, 'drawing'],
1267
1276
  sctype='[file]',
1268
1277
  shorthelp="Datasheet: package drawing",
@@ -2158,7 +2167,10 @@ def schema_metric(cfg, step='default', index='default'):
2158
2167
  device families.""")
2159
2168
 
2160
2169
  metrics = {'cellarea': 'cell area (ignoring fillers)',
2161
- 'totalarea': 'physical die area'}
2170
+ 'totalarea': 'physical die area',
2171
+ 'macroarea': 'macro cell area',
2172
+ 'padcellarea': 'io pad cell area',
2173
+ 'stdcellarea': 'standard cell area'}
2162
2174
 
2163
2175
  for item, val in metrics.items():
2164
2176
  scparam(cfg, ['metric', item],