siliconcompiler 0.35.1__py3-none-any.whl → 0.35.3__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 (57) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/sc_install.py +1 -1
  3. siliconcompiler/apps/sc_issue.py +8 -16
  4. siliconcompiler/apps/smake.py +106 -100
  5. siliconcompiler/checklist.py +349 -91
  6. siliconcompiler/design.py +8 -1
  7. siliconcompiler/flowgraph.py +419 -130
  8. siliconcompiler/flows/showflow.py +1 -2
  9. siliconcompiler/library.py +6 -5
  10. siliconcompiler/package/https.py +10 -5
  11. siliconcompiler/project.py +87 -37
  12. siliconcompiler/remote/client.py +17 -6
  13. siliconcompiler/scheduler/scheduler.py +284 -59
  14. siliconcompiler/scheduler/schedulernode.py +154 -102
  15. siliconcompiler/schema/__init__.py +3 -2
  16. siliconcompiler/schema/_metadata.py +1 -1
  17. siliconcompiler/schema/baseschema.py +210 -93
  18. siliconcompiler/schema/namedschema.py +21 -13
  19. siliconcompiler/schema/parameter.py +8 -1
  20. siliconcompiler/schema/safeschema.py +18 -7
  21. siliconcompiler/schema_support/dependencyschema.py +23 -3
  22. siliconcompiler/schema_support/filesetschema.py +10 -4
  23. siliconcompiler/schema_support/option.py +37 -34
  24. siliconcompiler/schema_support/pathschema.py +7 -2
  25. siliconcompiler/schema_support/record.py +5 -4
  26. siliconcompiler/targets/asap7_demo.py +4 -1
  27. siliconcompiler/tool.py +100 -8
  28. siliconcompiler/tools/__init__.py +10 -7
  29. siliconcompiler/tools/bambu/convert.py +19 -0
  30. siliconcompiler/tools/builtin/__init__.py +3 -2
  31. siliconcompiler/tools/builtin/filter.py +108 -0
  32. siliconcompiler/tools/builtin/importfiles.py +154 -0
  33. siliconcompiler/tools/execute/exec_input.py +4 -3
  34. siliconcompiler/tools/gtkwave/show.py +6 -2
  35. siliconcompiler/tools/icarus/compile.py +1 -0
  36. siliconcompiler/tools/klayout/scripts/klayout_show.py +1 -1
  37. siliconcompiler/tools/klayout/show.py +17 -5
  38. siliconcompiler/tools/openroad/screenshot.py +0 -1
  39. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
  40. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +2 -0
  41. siliconcompiler/tools/openroad/show.py +10 -0
  42. siliconcompiler/tools/surfer/show.py +7 -2
  43. siliconcompiler/tools/verilator/compile.py +2 -2
  44. siliconcompiler/tools/yosys/prepareLib.py +7 -2
  45. siliconcompiler/tools/yosys/syn_asic.py +20 -2
  46. siliconcompiler/toolscripts/_tools.json +5 -5
  47. siliconcompiler/toolscripts/rhel9/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
  48. siliconcompiler/toolscripts/ubuntu22/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
  49. siliconcompiler/toolscripts/ubuntu24/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
  50. siliconcompiler/utils/__init__.py +1 -2
  51. siliconcompiler/utils/issue.py +38 -45
  52. {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/METADATA +4 -4
  53. {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/RECORD +57 -55
  54. {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/WHEEL +0 -0
  55. {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/entry_points.txt +0 -0
  56. {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/licenses/LICENSE +0 -0
  57. {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/top_level.txt +0 -0
@@ -30,10 +30,36 @@ if TYPE_CHECKING:
30
30
  from siliconcompiler.schema_support.metric import MetricSchema
31
31
 
32
32
 
33
- class SchedulerFlowReset(Exception):
33
+ class _SchedulerReset(Exception):
34
+ def __init__(self, msg: str, *args: object) -> None:
35
+ super().__init__(msg, *args)
36
+ self.__msg = msg
37
+
38
+ @property
39
+ def msg(self) -> str:
40
+ return self.__msg
41
+
42
+ def log(self, logger: logging.Logger) -> None:
43
+ logger.debug(self.msg)
44
+
45
+
46
+ class SchedulerFlowReset(_SchedulerReset):
34
47
  pass
35
48
 
36
49
 
50
+ class SchedulerNodeReset(_SchedulerReset):
51
+ def log(self, logger: logging.Logger) -> None:
52
+ logger.warning(self.msg)
53
+
54
+
55
+ class SchedulerNodeResetSilent(SchedulerNodeReset):
56
+ def __init__(self, msg: str, *args: object) -> None:
57
+ super().__init__(msg, *args)
58
+
59
+ def log(self, logger: logging.Logger) -> None:
60
+ _SchedulerReset.log(self, logger)
61
+
62
+
37
63
  class SchedulerNode:
38
64
  """
39
65
  A class for managing and executing a single node in the compilation flow graph.
@@ -120,10 +146,12 @@ class SchedulerNode:
120
146
  are directed to the correct task's schema.
121
147
  """
122
148
  prev_task = self.__task
123
- with self.__task.runtime(self) as runtask:
124
- self.__task = runtask
125
- yield
126
- self.__task = prev_task
149
+ try:
150
+ with self.__task.runtime(self) as runtask:
151
+ self.__task = runtask
152
+ yield
153
+ finally:
154
+ self.__task = prev_task
127
155
 
128
156
  @staticmethod
129
157
  def init(project: "Project") -> None:
@@ -382,7 +410,7 @@ class SchedulerNode:
382
410
 
383
411
  return True
384
412
 
385
- def check_previous_run_status(self, previous_run: "SchedulerNode") -> bool:
413
+ def check_previous_run_status(self, previous_run: "SchedulerNode") -> None:
386
414
  """
387
415
  Determine whether a prior run is compatible and completed successfully for use as
388
416
  an incremental build starting point.
@@ -406,25 +434,19 @@ class SchedulerNode:
406
434
 
407
435
  # Tool name
408
436
  if self.__task.tool() != previous_run.__task.tool():
409
- self.logger.debug("Tool name changed")
410
- return False
437
+ raise SchedulerNodeResetSilent("Tool name changed")
411
438
 
412
439
  # Task name
413
440
  if self.__task.task() != previous_run.__task.task():
414
- self.logger.debug("Task name changed")
415
- return False
441
+ raise SchedulerNodeResetSilent("Task name changed")
416
442
 
417
443
  previous_status = previous_run.__project.get("record", "status",
418
444
  step=self.__step, index=self.__index)
419
445
  if not NodeStatus.is_done(previous_status):
420
- self.logger.debug("Previous step did not complete")
421
- # Not complete
422
- return False
446
+ raise SchedulerNodeResetSilent("Previous step did not complete")
423
447
 
424
448
  if not NodeStatus.is_success(previous_status):
425
- self.logger.debug("Previous step was not successful")
426
- # Not a success
427
- return False
449
+ raise SchedulerNodeResetSilent("Previous step was not successful")
428
450
 
429
451
  # Check input nodes
430
452
  log_level = self.logger.level
@@ -433,16 +455,11 @@ class SchedulerNode:
433
455
  self.logger.setLevel(log_level)
434
456
  if set(previous_run.__project.get("record", "inputnode",
435
457
  step=self.__step, index=self.__index)) != set(sel_inputs):
436
- self.logger.warning(f'inputs to {self.__step}/{self.__index} has been modified from '
437
- 'previous run')
438
- return False
439
-
440
- # Check that all output files are present?
441
-
442
- return True
458
+ raise SchedulerNodeReset(f'inputs to {self.__step}/{self.__index} has been '
459
+ 'modified from previous run')
443
460
 
444
461
  def check_values_changed(self, previous_run: "SchedulerNode", keys: Set[Tuple[str, ...]]) \
445
- -> bool:
462
+ -> None:
446
463
  """
447
464
  Checks if any specified schema parameter values have changed.
448
465
 
@@ -453,15 +470,14 @@ class SchedulerNode:
453
470
  Returns:
454
471
  bool: True if any value has changed, False otherwise.
455
472
  """
456
- def print_warning(key):
457
- self.logger.warning(f'[{",".join(key)}] in {self.__step}/{self.__index} has been '
458
- 'modified from previous run')
473
+ def gen_reset(key):
474
+ raise SchedulerNodeReset(f'[{",".join(key)}] in {self.__step}/{self.__index} has been '
475
+ 'modified from previous run')
459
476
 
460
477
  for key in sorted(keys):
461
478
  if not self.__project.valid(*key) or not previous_run.__project.valid(*key):
462
479
  # Key is missing in either run
463
- print_warning(key)
464
- return True
480
+ gen_reset(key)
465
481
 
466
482
  param = self.__project.get(*key, field=None)
467
483
  step, index = self.__step, self.__index
@@ -472,13 +488,10 @@ class SchedulerNode:
472
488
  prev_val = previous_run.__project.get(*key, step=step, index=index)
473
489
 
474
490
  if check_val != prev_val:
475
- print_warning(key)
476
- return True
477
-
478
- return False
491
+ gen_reset(key)
479
492
 
480
493
  def check_files_changed(self, previous_run: "SchedulerNode",
481
- previous_time: float, keys: Set[Tuple[str, ...]]) -> bool:
494
+ previous_time: float, keys: Set[Tuple[str, ...]]) -> None:
482
495
  """
483
496
  Checks if any specified file-based parameters have changed.
484
497
 
@@ -494,9 +507,9 @@ class SchedulerNode:
494
507
  """
495
508
  use_hash = self.__hash and previous_run.__hash
496
509
 
497
- def print_warning(key, reason):
498
- self.logger.warning(f'[{",".join(key)}] ({reason}) in {self.__step}/{self.__index} has '
499
- 'been modified from previous run')
510
+ def gen_warning(key, reason):
511
+ raise SchedulerNodeReset(f'[{",".join(key)}] ({reason}) in {self.__step}/'
512
+ f'{self.__index} has been modified from previous run')
500
513
 
501
514
  def get_file_time(path):
502
515
  times = [os.path.getmtime(path)]
@@ -521,8 +534,7 @@ class SchedulerNode:
521
534
  step=step, index=index)
522
535
 
523
536
  if check_hash != prev_hash:
524
- print_warning(key, "file hash")
525
- return True
537
+ gen_warning(key, "file hash")
526
538
  else:
527
539
  # check package values
528
540
  check_val = self.__project.get(*key, field='dataroot',
@@ -531,8 +543,7 @@ class SchedulerNode:
531
543
  step=step, index=index)
532
544
 
533
545
  if check_val != prev_val:
534
- print_warning(key, "file dataroot")
535
- return True
546
+ gen_warning(key, "file dataroot")
536
547
 
537
548
  files = self.__project.find_files(*key, step=step, index=index)
538
549
  if not isinstance(files, (list, set, tuple)):
@@ -540,10 +551,7 @@ class SchedulerNode:
540
551
 
541
552
  for check_file in files:
542
553
  if get_file_time(check_file) > previous_time:
543
- print_warning(key, "timestamp")
544
- return True
545
-
546
- return False
554
+ gen_warning(key, "timestamp")
547
555
 
548
556
  def get_check_changed_keys(self) -> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
549
557
  """
@@ -585,7 +593,7 @@ class SchedulerNode:
585
593
 
586
594
  return value_keys, path_keys
587
595
 
588
- def requires_run(self) -> bool:
596
+ def requires_run(self) -> None:
589
597
  """
590
598
  Determines if the node needs to be re-run.
591
599
 
@@ -601,8 +609,7 @@ class SchedulerNode:
601
609
 
602
610
  if self.__breakpoint:
603
611
  # Breakpoint is set to must run
604
- self.logger.debug("Breakpoint is set")
605
- return True
612
+ raise SchedulerNodeResetSilent(f"Breakpoint is set on {self.__step}/{self.__index}")
606
613
 
607
614
  # Load previous manifest
608
615
  previous_node = None
@@ -612,33 +619,27 @@ class SchedulerNode:
612
619
  try:
613
620
  i_project: Project = Project.from_manifest(filepath=self.__manifests["input"])
614
621
  except: # noqa E722
615
- self.logger.debug("Input manifest failed to load")
616
- return True
622
+ raise SchedulerNodeResetSilent("Input manifest failed to load")
617
623
  previous_node = SchedulerNode(i_project, self.__step, self.__index)
618
624
  else:
619
625
  # No manifest found so assume rerun is needed
620
- self.logger.debug("Previous run did not generate input manifest")
621
- return True
626
+ raise SchedulerNodeResetSilent("Previous run did not generate input manifest")
622
627
 
623
628
  previous_node_end = None
624
629
  if os.path.exists(self.__manifests["output"]):
625
630
  try:
626
631
  o_project = Project.from_manifest(filepath=self.__manifests["output"])
627
632
  except: # noqa E722
628
- self.logger.debug("Output manifest failed to load")
629
- return True
633
+ raise SchedulerNodeResetSilent("Output manifest failed to load")
630
634
  previous_node_end = SchedulerNode(o_project, self.__step, self.__index)
631
635
  else:
632
636
  # No manifest found so assume rerun is needed
633
- self.logger.debug("Previous run did not generate output manifest")
634
- return True
637
+ raise SchedulerNodeResetSilent("Previous run did not generate output manifest")
635
638
 
636
639
  with self.runtime():
637
640
  if previous_node_end:
638
641
  with previous_node_end.runtime():
639
- if not self.check_previous_run_status(previous_node_end):
640
- self.logger.debug("Previous run state failed")
641
- return True
642
+ self.check_previous_run_status(previous_node_end)
642
643
 
643
644
  if previous_node:
644
645
  with previous_node.runtime():
@@ -650,18 +651,10 @@ class SchedulerNode:
650
651
  value_keys.update(previous_value_keys)
651
652
  path_keys.update(previous_path_keys)
652
653
  except KeyError:
653
- self.logger.debug("Failed to acquire keys")
654
- return True
655
-
656
- if self.check_values_changed(previous_node, value_keys.union(path_keys)):
657
- self.logger.debug("Key values changed")
658
- return True
654
+ raise SchedulerNodeResetSilent("Failed to acquire keys")
659
655
 
660
- if self.check_files_changed(previous_node, previous_node_time, path_keys):
661
- self.logger.debug("Files changed")
662
- return True
663
-
664
- return False
656
+ self.check_values_changed(previous_node, value_keys.union(path_keys))
657
+ self.check_files_changed(previous_node, previous_node_time, path_keys)
665
658
 
666
659
  def setup_input_directory(self) -> None:
667
660
  """
@@ -869,6 +862,67 @@ class SchedulerNode:
869
862
  if self.__pipe:
870
863
  self.__pipe.send(Resolver.get_cache(self.__project))
871
864
 
865
+ @contextlib.contextmanager
866
+ def __set_env(self):
867
+ """Temporarily sets task-specific environment variables.
868
+
869
+ This context manager saves the current `os.environ`, updates it
870
+ with the task's runtime variables, yields control, and then
871
+ restores the original environment upon exiting the context.
872
+ """
873
+ org_env = os.environ.copy()
874
+ try:
875
+ os.environ.update(self.__task.get_runtime_environmental_variables())
876
+ yield
877
+ finally:
878
+ os.environ.clear()
879
+ os.environ.update(org_env)
880
+
881
+ def get_exe_path(self) -> Optional[str]:
882
+ """Gets the path to the requested executable for this task.
883
+
884
+ This method retrieves the executable path from the underlying task
885
+ object. It ensures that the task's specific runtime environment
886
+ variables are set before making the call.
887
+
888
+ Returns:
889
+ Optional[str]: The file path to the executable, or None if not found.
890
+ """
891
+ with self.__set_env():
892
+ return self.__task.get_exe()
893
+
894
+ def check_version(self, version: Optional[str] = None) -> Tuple[Optional[str], bool]:
895
+ """Checks the version of the tool for this task.
896
+
897
+ Compares a version string against the tool's requirements. This check
898
+ is performed within the task's specific runtime environment.
899
+
900
+ If no `version` is provided, this method will attempt to get the
901
+ version from the task itself. The check can be skipped if the
902
+ project option 'novercheck' is set.
903
+
904
+ Args:
905
+ version: The version string to check. If None, the task's
906
+ configured version is fetched and used.
907
+
908
+ Returns:
909
+ A tuple (version_str, check_passed):
910
+ - version_str (Optional[str]): The version string that was
911
+ evaluated.
912
+ - check_passed (bool): True if the version is compatible or
913
+ if the check was skipped, False otherwise.
914
+ """
915
+ if self.__project.get('option', 'novercheck', step=self.__step, index=self.__index):
916
+ return version, True
917
+
918
+ with self.__set_env():
919
+ if version is None:
920
+ version = self.__task.get_exe_version()
921
+
922
+ check = self.__task.check_exe_version(version)
923
+
924
+ return version, check
925
+
872
926
  def execute(self) -> None:
873
927
  """
874
928
  Handles the core tool execution logic.
@@ -918,38 +972,36 @@ class SchedulerNode:
918
972
 
919
973
  send_messages.send(self.__project, "skipped", self.__step, self.__index)
920
974
  else:
921
- org_env = os.environ.copy()
922
- os.environ.update(self.__task.get_runtime_environmental_variables())
923
-
924
- toolpath = self.__task.get_exe()
925
- version = self.__task.get_exe_version()
975
+ with self.__set_env():
976
+ toolpath = self.__task.get_exe()
977
+ version, version_pass = self.check_version()
926
978
 
927
- if not self.__project.get('option', 'novercheck', step=self.__step, index=self.__index):
928
- if not self.__task.check_exe_version(version):
979
+ if not version_pass:
929
980
  self.halt()
930
981
 
931
- if version:
932
- self.__record.record_tool(self.__step, self.__index, version, RecordTool.VERSION)
933
-
934
- if toolpath:
935
- self.__record.record_tool(self.__step, self.__index, toolpath, RecordTool.PATH)
936
-
937
- send_messages.send(self.__project, "begin", self.__step, self.__index)
938
-
939
- try:
940
- if not self.__replay:
941
- self.__task.generate_replay_script(self.__replay_script, self.__workdir)
942
- ret_code = self.__task.run_task(
943
- self.__workdir,
944
- self.__project.get('option', 'quiet', step=self.__step, index=self.__index),
945
- self.__breakpoint,
946
- self.__project.get('option', 'nice', step=self.__step, index=self.__index),
947
- self.__project.get('option', 'timeout', step=self.__step, index=self.__index))
948
- except Exception as e:
949
- raise e
950
-
951
- os.environ.clear()
952
- os.environ.update(org_env)
982
+ if version:
983
+ self.__record.record_tool(self.__step, self.__index, version,
984
+ RecordTool.VERSION)
985
+
986
+ if toolpath:
987
+ self.__record.record_tool(self.__step, self.__index, toolpath, RecordTool.PATH)
988
+
989
+ send_messages.send(self.__project, "begin", self.__step, self.__index)
990
+
991
+ try:
992
+ if not self.__replay:
993
+ self.__task.generate_replay_script(self.__replay_script, self.__workdir)
994
+ ret_code = self.__task.run_task(
995
+ self.__workdir,
996
+ self.__project.get('option', 'quiet',
997
+ step=self.__step, index=self.__index),
998
+ self.__breakpoint,
999
+ self.__project.get('option', 'nice',
1000
+ step=self.__step, index=self.__index),
1001
+ self.__project.get('option', 'timeout',
1002
+ step=self.__step, index=self.__index))
1003
+ except Exception as e:
1004
+ raise e
953
1005
 
954
1006
  if ret_code != 0:
955
1007
  msg = f'Command failed with code {ret_code}.'
@@ -1037,15 +1089,15 @@ class SchedulerNode:
1037
1089
  from siliconcompiler.utils.issue import generate_testcase
1038
1090
  import lambdapdk
1039
1091
 
1092
+ foss_libraries = [*lambdapdk.get_pdk_names(), *lambdapdk.get_lib_names()]
1093
+
1040
1094
  generate_testcase(
1041
1095
  self.__project,
1042
1096
  self.__step,
1043
1097
  self.__index,
1044
1098
  archive_directory=self.__jobworkdir,
1045
- include_pdks=False,
1046
- include_specific_pdks=lambdapdk.get_pdk_names(),
1047
1099
  include_libraries=False,
1048
- include_specific_libraries=lambdapdk.get_lib_names(),
1100
+ include_specific_libraries=foss_libraries,
1049
1101
  hash_files=self.__hash,
1050
1102
  verbose_collect=False)
1051
1103
 
@@ -4,7 +4,7 @@ from .parameter import Parameter, Scope, PerNode
4
4
  from .journal import Journal
5
5
  from .safeschema import SafeSchema
6
6
  from .editableschema import EditableSchema
7
- from .baseschema import BaseSchema
7
+ from .baseschema import BaseSchema, LazyLoad
8
8
  from .namedschema import NamedSchema
9
9
  from .docschema import DocsSchema
10
10
 
@@ -17,7 +17,8 @@ __all__ = [
17
17
  "Scope",
18
18
  "PerNode",
19
19
  "Journal",
20
- "DocsSchema"
20
+ "DocsSchema",
21
+ "LazyLoad"
21
22
  ]
22
23
 
23
24
  SCHEMA_VERSION = __version__
@@ -1,2 +1,2 @@
1
1
  # Version number following semver standard.
2
- version = '0.52.0'
2
+ version = '0.52.1'