siliconcompiler 0.35.4__py3-none-any.whl → 0.36.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/constraints/__init__.py +4 -1
  3. siliconcompiler/constraints/asic_timing.py +230 -38
  4. siliconcompiler/constraints/fpga_timing.py +209 -14
  5. siliconcompiler/constraints/timing_mode.py +82 -0
  6. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +0 -6
  7. siliconcompiler/flowgraph.py +95 -42
  8. siliconcompiler/flows/generate_openroad_rcx.py +2 -2
  9. siliconcompiler/flows/highresscreenshotflow.py +37 -0
  10. siliconcompiler/library.py +2 -1
  11. siliconcompiler/package/__init__.py +39 -45
  12. siliconcompiler/project.py +4 -1
  13. siliconcompiler/scheduler/scheduler.py +64 -35
  14. siliconcompiler/scheduler/schedulernode.py +5 -2
  15. siliconcompiler/scheduler/slurm.py +7 -6
  16. siliconcompiler/scheduler/taskscheduler.py +19 -16
  17. siliconcompiler/schema/_metadata.py +1 -1
  18. siliconcompiler/schema/namedschema.py +2 -4
  19. siliconcompiler/schema_support/cmdlineschema.py +0 -3
  20. siliconcompiler/schema_support/dependencyschema.py +0 -6
  21. siliconcompiler/schema_support/record.py +4 -3
  22. siliconcompiler/tool.py +58 -27
  23. siliconcompiler/tools/_common/tcl/sc_schema_access.tcl +0 -6
  24. siliconcompiler/tools/chisel/convert.py +44 -0
  25. siliconcompiler/tools/ghdl/convert.py +37 -2
  26. siliconcompiler/tools/icarus/compile.py +14 -0
  27. siliconcompiler/tools/keplerformal/__init__.py +7 -0
  28. siliconcompiler/tools/keplerformal/lec.py +112 -0
  29. siliconcompiler/tools/klayout/drc.py +14 -0
  30. siliconcompiler/tools/klayout/export.py +40 -0
  31. siliconcompiler/tools/klayout/operations.py +40 -0
  32. siliconcompiler/tools/klayout/screenshot.py +66 -1
  33. siliconcompiler/tools/klayout/scripts/klayout_export.py +10 -40
  34. siliconcompiler/tools/klayout/scripts/klayout_show.py +4 -4
  35. siliconcompiler/tools/klayout/scripts/klayout_utils.py +13 -1
  36. siliconcompiler/tools/montage/tile.py +26 -12
  37. siliconcompiler/tools/openroad/__init__.py +11 -0
  38. siliconcompiler/tools/openroad/_apr.py +780 -11
  39. siliconcompiler/tools/openroad/antenna_repair.py +26 -0
  40. siliconcompiler/tools/openroad/fillmetal_insertion.py +14 -0
  41. siliconcompiler/tools/openroad/global_placement.py +67 -0
  42. siliconcompiler/tools/openroad/global_route.py +15 -0
  43. siliconcompiler/tools/openroad/init_floorplan.py +19 -2
  44. siliconcompiler/tools/openroad/macro_placement.py +252 -0
  45. siliconcompiler/tools/openroad/power_grid.py +43 -0
  46. siliconcompiler/tools/openroad/power_grid_analysis.py +1 -1
  47. siliconcompiler/tools/openroad/rcx_bench.py +28 -0
  48. siliconcompiler/tools/openroad/rcx_extract.py +14 -0
  49. siliconcompiler/tools/openroad/rdlroute.py +14 -0
  50. siliconcompiler/tools/openroad/repair_design.py +41 -0
  51. siliconcompiler/tools/openroad/repair_timing.py +54 -0
  52. siliconcompiler/tools/openroad/screenshot.py +31 -1
  53. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +8 -0
  54. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +54 -15
  55. siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +6 -4
  56. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +4 -4
  57. siliconcompiler/tools/openroad/scripts/common/procs.tcl +14 -5
  58. siliconcompiler/tools/openroad/scripts/common/read_liberty.tcl +2 -2
  59. siliconcompiler/tools/openroad/scripts/common/reports.tcl +6 -3
  60. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
  61. siliconcompiler/tools/openroad/scripts/common/write_data_physical.tcl +8 -0
  62. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +16 -12
  63. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -1
  64. siliconcompiler/tools/openroad/write_data.py +78 -2
  65. siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +2 -2
  66. siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +2 -2
  67. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +12 -14
  68. siliconcompiler/tools/opensta/timing.py +42 -3
  69. siliconcompiler/tools/slang/elaborate.py +16 -1
  70. siliconcompiler/tools/surelog/parse.py +54 -0
  71. siliconcompiler/tools/verilator/compile.py +120 -0
  72. siliconcompiler/tools/vivado/syn_fpga.py +27 -0
  73. siliconcompiler/tools/vpr/route.py +40 -0
  74. siliconcompiler/tools/xdm/convert.py +14 -0
  75. siliconcompiler/tools/xyce/simulate.py +26 -0
  76. siliconcompiler/tools/yosys/lec_asic.py +13 -0
  77. siliconcompiler/tools/yosys/syn_asic.py +332 -3
  78. siliconcompiler/tools/yosys/syn_fpga.py +32 -0
  79. siliconcompiler/toolscripts/_tools.json +9 -4
  80. siliconcompiler/toolscripts/ubuntu22/install-keplerformal.sh +72 -0
  81. siliconcompiler/toolscripts/ubuntu24/install-keplerformal.sh +72 -0
  82. siliconcompiler/utils/multiprocessing.py +11 -0
  83. siliconcompiler/utils/settings.py +70 -49
  84. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.1.dist-info}/METADATA +4 -4
  85. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.1.dist-info}/RECORD +89 -83
  86. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.1.dist-info}/WHEEL +0 -0
  87. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.1.dist-info}/entry_points.txt +0 -0
  88. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.1.dist-info}/licenses/LICENSE +0 -0
  89. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  # Version number following semver standard.
2
- version = '0.35.4'
2
+ version = '0.36.1'
3
3
 
4
4
  # Default server address for remote runs, if unspecified.
5
5
  default_server = 'https://server.siliconcompiler.com'
@@ -1,5 +1,7 @@
1
1
  from siliconcompiler.schema import BaseSchema
2
2
 
3
+ from siliconcompiler.constraints.timing_mode import TimingModeSchema
4
+
3
5
  from siliconcompiler.constraints.asic_timing import \
4
6
  ASICTimingConstraintSchema, ASICTimingScenarioSchema
5
7
  from siliconcompiler.constraints.asic_floorplan import ASICAreaConstraint
@@ -29,5 +31,6 @@ __all__ = [
29
31
  "ASICComponentConstraint",
30
32
  "ASICComponentConstraints",
31
33
  "FPGATimingConstraintSchema",
32
- "FPGATimingScenarioSchema"
34
+ "FPGATimingScenarioSchema",
35
+ "TimingModeSchema"
33
36
  ]
@@ -1,8 +1,10 @@
1
- from typing import Union, Set, List, Tuple, Optional
1
+ from typing import Union, Set, List, Tuple, Optional, Dict
2
2
 
3
3
  from siliconcompiler.schema import BaseSchema, NamedSchema, EditableSchema, Parameter, \
4
4
  PerNode, Scope
5
5
  from siliconcompiler import Design
6
+ from siliconcompiler.constraints.timing_mode import TimingModeSchema
7
+ from siliconcompiler.schema.baseschema import LazyLoad
6
8
 
7
9
 
8
10
  class ASICTimingScenarioSchema(NamedSchema):
@@ -93,20 +95,6 @@ class ASICTimingScenarioSchema(NamedSchema):
93
95
  help="""Operating mode for the scenario. Operating mode strings
94
96
  can be values such as test, functional, standby."""))
95
97
 
96
- schema.insert(
97
- 'sdcfileset',
98
- Parameter(
99
- '[(str,str)]',
100
- pernode=PerNode.OPTIONAL,
101
- scope=Scope.GLOBAL,
102
- shorthelp="Constraint: SDC files",
103
- switch="-constraint_timing_file 'scenario <file>'",
104
- example=["api: asic.set('constraint', 'timing', 'worst', 'file', 'hello.sdc')"],
105
- help="""List of timing constraint sets files to use for the scenario. The
106
- values are combined with any constraints specified by the design
107
- 'constraint' parameter. If no constraints are found, a default
108
- constraint file is used based on the clock definitions."""))
109
-
110
98
  schema.insert(
111
99
  'check',
112
100
  Parameter(
@@ -324,19 +312,22 @@ class ASICTimingScenarioSchema(NamedSchema):
324
312
  TypeError: If `design` is not a Design object or a string, or if `fileset` is not
325
313
  a string.
326
314
  """
327
- if isinstance(design, Design):
328
- design = design.name
315
+ import warnings
316
+ warnings.warn("This function is deprecated and will be removed in a future version, "
317
+ "use TimingModeSchema instead", DeprecationWarning, stacklevel=2)
329
318
 
330
- if not isinstance(design, str):
331
- raise TypeError("design must be a design object or string")
319
+ mode = self.get_mode(step=step, index=index)
320
+ if mode is None:
321
+ raise ValueError("Mode not defined")
332
322
 
333
- if not isinstance(fileset, str):
334
- raise TypeError("fileset must be a string")
335
-
336
- if clobber:
337
- return self.set("sdcfileset", (design, fileset), step=step, index=index)
338
- else:
339
- return self.add("sdcfileset", (design, fileset), step=step, index=index)
323
+ timing_constraints: ASICTimingConstraintSchema = self._parent()._parent()
324
+ try:
325
+ modeobj = timing_constraints.get_mode(mode)
326
+ except LookupError:
327
+ modeobj = timing_constraints.make_mode(mode)
328
+ return modeobj.add_sdcfileset(design=design, fileset=fileset,
329
+ clobber=clobber,
330
+ step=step, index=index)
340
331
 
341
332
  def get_sdcfileset(self, step: Optional[str] = None, index: Optional[Union[str, int]] = None) \
342
333
  -> List[Tuple[str, str]]:
@@ -350,7 +341,20 @@ class ASICTimingScenarioSchema(NamedSchema):
350
341
  Returns:
351
342
  A list of tuples, where each tuple contains the design name and the SDC fileset name.
352
343
  """
353
- return self.get("sdcfileset", step=step, index=index)
344
+ import warnings
345
+ warnings.warn("This function is deprecated and will be removed in a future version, "
346
+ "use TimingModeSchema instead", DeprecationWarning, stacklevel=2)
347
+
348
+ mode = self.get_mode(step=step, index=index)
349
+ if mode is None:
350
+ raise ValueError("Mode not defined")
351
+
352
+ timing_constraints: ASICTimingConstraintSchema = self._parent()._parent()
353
+ try:
354
+ modeobj = timing_constraints.get_mode(mode)
355
+ except LookupError:
356
+ modeobj = timing_constraints.make_mode(mode)
357
+ return modeobj.get_sdcfileset(step=step, index=index)
354
358
 
355
359
  def add_check(self,
356
360
  check: Union[List[str], str],
@@ -386,6 +390,30 @@ class ASICTimingScenarioSchema(NamedSchema):
386
390
  """
387
391
  return self.get("check", step=step, index=index)
388
392
 
393
+ def _from_dict(self, manifest: Dict,
394
+ keypath: Union[List[str], Tuple[str, ...]],
395
+ version: Optional[Tuple[int, ...]] = None,
396
+ lazyload: LazyLoad = LazyLoad.ON) \
397
+ -> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
398
+
399
+ sdcfileset = None
400
+ if version and version < (0, 53, 0):
401
+ sdcfileset = manifest.pop("sdcfileset", None)
402
+ lazyload = LazyLoad.OFF
403
+
404
+ ret = super()._from_dict(manifest, keypath, version, lazyload)
405
+
406
+ if sdcfileset:
407
+ param = Parameter.from_dict(sdcfileset, keypath=(*keypath, "sdcfileset"),
408
+ version=version)
409
+ for value, step, index in param.getvalues():
410
+ if self.get_mode(step=step, index=index) is None:
411
+ self.set_mode("_importcreated_", step=step, index=index)
412
+ for design, fileset in value:
413
+ self.add_sdcfileset(design, fileset, step=step, index=index)
414
+
415
+ return ret
416
+
389
417
 
390
418
  class ASICTimingConstraintSchema(BaseSchema):
391
419
  """
@@ -400,7 +428,8 @@ class ASICTimingConstraintSchema(BaseSchema):
400
428
  def __init__(self):
401
429
  super().__init__()
402
430
 
403
- EditableSchema(self).insert("default", ASICTimingScenarioSchema())
431
+ EditableSchema(self).insert("scenario", "default", ASICTimingScenarioSchema())
432
+ EditableSchema(self).insert("mode", "default", TimingModeSchema())
404
433
 
405
434
  def add_scenario(self, scenario: ASICTimingScenarioSchema):
406
435
  """
@@ -426,9 +455,10 @@ class ASICTimingConstraintSchema(BaseSchema):
426
455
  if scenario.name is None:
427
456
  raise ValueError("scenario must have a name")
428
457
 
429
- EditableSchema(self).insert(scenario.name, scenario, clobber=True)
458
+ EditableSchema(self).insert("scenario", scenario.name, scenario, clobber=True)
430
459
 
431
- def get_scenario(self, scenario: Optional[str] = None):
460
+ def get_scenario(self, scenario: Optional[str] = None) \
461
+ -> Union[ASICTimingScenarioSchema, Dict[str, ASICTimingScenarioSchema]]:
432
462
  """
433
463
  Retrieves one or all timing scenarios from the configuration.
434
464
 
@@ -452,13 +482,13 @@ class ASICTimingConstraintSchema(BaseSchema):
452
482
  """
453
483
  if scenario is None:
454
484
  scenarios = {}
455
- for name in self.getkeys():
456
- scenarios[name] = self.get(name, field="schema")
485
+ for name in self.getkeys("scenario"):
486
+ scenarios[name] = self.get("scenario", name, field="schema")
457
487
  return scenarios
458
488
 
459
- if not self.valid(scenario):
489
+ if not self.valid("scenario", scenario):
460
490
  raise LookupError(f"{scenario} is not defined")
461
- return self.get(scenario, field="schema")
491
+ return self.get("scenario", scenario, field="schema")
462
492
 
463
493
  def make_scenario(self, scenario: str) -> ASICTimingScenarioSchema:
464
494
  """
@@ -485,7 +515,7 @@ class ASICTimingConstraintSchema(BaseSchema):
485
515
  if not scenario:
486
516
  raise ValueError("scenario name is required")
487
517
 
488
- if self.valid(scenario):
518
+ if self.valid("scenario", scenario):
489
519
  raise LookupError(f"{scenario} scenario already exists")
490
520
 
491
521
  scenarioobj = ASICTimingScenarioSchema(scenario)
@@ -516,7 +546,7 @@ class ASICTimingConstraintSchema(BaseSchema):
516
546
  constraint = EditableSchema(self.get_scenario(scenario)).copy()
517
547
  EditableSchema(constraint).rename(name)
518
548
  if insert:
519
- if self.valid(name):
549
+ if self.valid("scenario", name):
520
550
  raise ValueError(f"{name} already exists")
521
551
  self.add_scenario(constraint)
522
552
  return constraint
@@ -542,8 +572,170 @@ class ASICTimingConstraintSchema(BaseSchema):
542
572
  if not scenario:
543
573
  raise ValueError("scenario name is required")
544
574
 
545
- if not self.valid(scenario):
575
+ if not self.valid("scenario", scenario):
576
+ return False
577
+
578
+ EditableSchema(self).remove("scenario", scenario)
579
+ return True
580
+
581
+ def add_mode(self, mode: TimingModeSchema):
582
+ """
583
+ Adds a timing mode to the design configuration.
584
+
585
+ This method is responsible for incorporating a new or updated timing mode
586
+ into the system's configuration. If a mode with the same name already
587
+ exists, it will be overwritten (`clobber=True`).
588
+
589
+ Args:
590
+ mode: The :class:`TimingModeSchema` object representing the timing mode
591
+ to add. This object must have a valid name defined via its `name()` method.
592
+
593
+ Raises:
594
+ TypeError: If the provided `mode` argument is not an instance of
595
+ :class:`TimingModeSchema`.
596
+ ValueError: If the `mode` object's `name()` method returns None, indicating
597
+ that the mode does not have a defined name.
598
+ """
599
+ if not isinstance(mode, TimingModeSchema):
600
+ raise TypeError("mode must be a timing mode object")
601
+
602
+ if mode.name is None:
603
+ raise ValueError("mode must have a name")
604
+
605
+ EditableSchema(self).insert("mode", mode.name, mode, clobber=True)
606
+
607
+ def get_mode(self, mode: Optional[str] = None) \
608
+ -> Union[TimingModeSchema, Dict[str, TimingModeSchema]]:
609
+ """
610
+ Retrieves one or all timing modes from the configuration.
611
+
612
+ This method provides flexibility to fetch either a specific timing mode
613
+ by its name or a collection of all currently defined modes.
614
+
615
+ Args:
616
+ mode (str, optional): The name (string) of the specific timing mode to retrieve.
617
+ If this argument is omitted or set to None, the method will
618
+ return a dictionary containing all available timing modes.
619
+
620
+ Returns:
621
+ If `mode` is provided: The :class:`TimingModeSchema` object corresponding
622
+ to the specified mode name.
623
+ If `mode` is None: A dictionary where keys are mode names (str) and
624
+ values are their respective :class:`TimingModeSchema` objects.
625
+
626
+ Raises:
627
+ LookupError: If a specific `mode` name is provided but no mode with
628
+ that name is found in the configuration.
629
+ """
630
+ if mode is None:
631
+ modes = {}
632
+ for name in self.getkeys("mode"):
633
+ modes[name] = self.get("mode", name, field="schema")
634
+ return modes
635
+
636
+ if not self.valid("mode", mode):
637
+ raise LookupError(f"{mode} is not defined")
638
+ return self.get("mode", mode, field="schema")
639
+
640
+ def make_mode(self, mode: str) -> TimingModeSchema:
641
+ """
642
+ Creates and adds a new timing mode with the specified name.
643
+
644
+ This method initializes a new :class:`TimingModeSchema` object with the given
645
+ name and immediately adds it to the constraint configuration. It ensures that
646
+ a mode with the same name does not already exist, preventing accidental
647
+ overwrites.
648
+
649
+ Args:
650
+ mode (str): The name for the new timing mode. This name must be
651
+ a non-empty string and unique within the current configuration.
652
+
653
+ Returns:
654
+ :class:`TimingModeSchema`: The newly created :class:`TimingModeSchema`
655
+ object.
656
+
657
+ Raises:
658
+ ValueError: If the provided `mode` name is empty or None.
659
+ LookupError: If a mode with the specified `mode` name already exists
660
+ in the configuration.
661
+ """
662
+ if not mode:
663
+ raise ValueError("mode name is required")
664
+
665
+ if self.valid("mode", mode):
666
+ raise LookupError(f"{mode} mode already exists")
667
+
668
+ modeobj = TimingModeSchema(mode)
669
+ self.add_mode(modeobj)
670
+ return modeobj
671
+
672
+ def copy_mode(self, mode: str, name: str, insert: bool = True) \
673
+ -> TimingModeSchema:
674
+ """
675
+ Copies an existing timing mode, renames it, and optionally adds it to the design.
676
+
677
+ This method retrieves the mode identified by ``mode``, creates a
678
+ deep copy of it, and renames the copy to ``name``. If ``insert`` is True,
679
+ the new mode is immediately added to the configuration.
680
+
681
+ Args:
682
+ mode (str): The name of the existing mode to be copied.
683
+ name (str): The name to assign to the new copied mode.
684
+ insert (bool, optional): Whether to add the newly created mode
685
+ to the configuration. Defaults to True.
686
+
687
+ Returns:
688
+ TimingModeSchema: The newly created copy of the mode.
689
+
690
+ Raises:
691
+ LookupError: If the source mode specified by ``mode`` does not exist.
692
+ """
693
+ newmode = EditableSchema(self.get_mode(mode)).copy()
694
+ EditableSchema(newmode).rename(name)
695
+ if insert:
696
+ if self.valid("mode", name):
697
+ raise ValueError(f"{name} already exists")
698
+ self.add_mode(newmode)
699
+ return newmode
700
+
701
+ def remove_mode(self, mode: str) -> bool:
702
+ """
703
+ Removes a timing mode from the design configuration.
704
+
705
+ This method deletes the specified timing mode from the system's
706
+ configuration.
707
+
708
+ Args:
709
+ mode (str): The name of the timing mode to remove.
710
+ This name must be a non-empty string.
711
+
712
+ Returns:
713
+ bool: True if the mode was successfully removed, False if no
714
+ mode with the given name was found.
715
+
716
+ Raises:
717
+ ValueError: If the provided `mode` name is empty or None.
718
+ """
719
+ if not mode:
720
+ raise ValueError("mode name is required")
721
+
722
+ if not self.valid("mode", mode):
546
723
  return False
547
724
 
548
- EditableSchema(self).remove(scenario)
725
+ EditableSchema(self).remove("mode", mode)
549
726
  return True
727
+
728
+ def _from_dict(self, manifest: Dict,
729
+ keypath: Union[List[str], Tuple[str, ...]],
730
+ version: Optional[Tuple[int, ...]] = None,
731
+ lazyload: LazyLoad = LazyLoad.ON) \
732
+ -> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
733
+ if version and version < (0, 53, 0):
734
+ manifest.pop("__meta__", None)
735
+ manifest = {
736
+ "scenario": manifest,
737
+ "mode": self.getdict("mode")
738
+ }
739
+ lazyload = LazyLoad.OFF
740
+
741
+ return super()._from_dict(manifest, keypath, version, lazyload)
@@ -1,7 +1,9 @@
1
- from typing import Union, Optional
1
+ from typing import List, Set, Tuple, Union, Optional, Dict
2
2
 
3
3
  from siliconcompiler.schema import BaseSchema, NamedSchema, EditableSchema, Parameter, \
4
4
  PerNode, Scope
5
+ from siliconcompiler.constraints.timing_mode import TimingModeSchema
6
+ from siliconcompiler.schema.baseschema import LazyLoad
5
7
 
6
8
 
7
9
  class FPGATimingScenarioSchema(NamedSchema):
@@ -69,7 +71,8 @@ class FPGATimingConstraintSchema(BaseSchema):
69
71
  def __init__(self):
70
72
  super().__init__()
71
73
 
72
- EditableSchema(self).insert("default", FPGATimingScenarioSchema())
74
+ EditableSchema(self).insert("scenario", "default", FPGATimingScenarioSchema())
75
+ EditableSchema(self).insert("mode", "default", TimingModeSchema())
73
76
 
74
77
  def add_scenario(self, scenario: FPGATimingScenarioSchema):
75
78
  """
@@ -95,9 +98,10 @@ class FPGATimingConstraintSchema(BaseSchema):
95
98
  if scenario.name is None:
96
99
  raise ValueError("scenario must have a name")
97
100
 
98
- EditableSchema(self).insert(scenario.name, scenario, clobber=True)
101
+ EditableSchema(self).insert("scenario", scenario.name, scenario, clobber=True)
99
102
 
100
- def get_scenario(self, scenario: Optional[str] = None):
103
+ def get_scenario(self, scenario: Optional[str] = None) \
104
+ -> Union[FPGATimingScenarioSchema, Dict[str, FPGATimingScenarioSchema]]:
101
105
  """
102
106
  Retrieves one or all timing scenarios from the configuration.
103
107
 
@@ -106,8 +110,8 @@ class FPGATimingConstraintSchema(BaseSchema):
106
110
 
107
111
  Args:
108
112
  scenario (str, optional): The name (string) of the specific timing scenario to retrieve.
109
- If this argument is omitted or set to None, the method will return
110
- a dictionary containing all available timing scenarios.
113
+ If this argument is omitted or set to None, the method will
114
+ return a dictionary containing all available timing scenarios.
111
115
 
112
116
  Returns:
113
117
  If `scenario` is provided: The :class:`FPGATimingScenarioSchema` object corresponding
@@ -121,13 +125,13 @@ class FPGATimingConstraintSchema(BaseSchema):
121
125
  """
122
126
  if scenario is None:
123
127
  scenarios = {}
124
- for scenario in self.getkeys():
125
- scenarios[scenario] = self.get(scenario, field="schema")
128
+ for name in self.getkeys("scenario"):
129
+ scenarios[name] = self.get("scenario", name, field="schema")
126
130
  return scenarios
127
131
 
128
- if not self.valid(scenario):
132
+ if not self.valid("scenario", scenario):
129
133
  raise LookupError(f"{scenario} is not defined")
130
- return self.get(scenario, field="schema")
134
+ return self.get("scenario", scenario, field="schema")
131
135
 
132
136
  def make_scenario(self, scenario: str) -> FPGATimingScenarioSchema:
133
137
  """
@@ -143,7 +147,7 @@ class FPGATimingConstraintSchema(BaseSchema):
143
147
  a non-empty string and unique within the current configuration.
144
148
 
145
149
  Returns:
146
- :class:FPGATimingScenarioSchema: The newly created :class:`FPGATimingScenarioSchema`
150
+ :class:`FPGATimingScenarioSchema`: The newly created :class:`FPGATimingScenarioSchema`
147
151
  object.
148
152
 
149
153
  Raises:
@@ -154,13 +158,42 @@ class FPGATimingConstraintSchema(BaseSchema):
154
158
  if not scenario:
155
159
  raise ValueError("scenario name is required")
156
160
 
157
- if self.valid(scenario):
161
+ if self.valid("scenario", scenario):
158
162
  raise LookupError(f"{scenario} scenario already exists")
159
163
 
160
164
  scenarioobj = FPGATimingScenarioSchema(scenario)
161
165
  self.add_scenario(scenarioobj)
162
166
  return scenarioobj
163
167
 
168
+ def copy_scenario(self, scenario: str, name: str, insert: bool = True) \
169
+ -> FPGATimingScenarioSchema:
170
+ """
171
+ Copies an existing timing scenario, renames it, and optionally adds it to the design.
172
+
173
+ This method retrieves the scenario identified by ``scenario``, creates a
174
+ deep copy of it, and renames the copy to ``name``. If ``insert`` is True,
175
+ the new scenario is immediately added to the configuration.
176
+
177
+ Args:
178
+ scenario (str): The name of the existing scenario to be copied.
179
+ name (str): The name to assign to the new copied scenario.
180
+ insert (bool, optional): Whether to add the newly created scenario
181
+ to the configuration. Defaults to True.
182
+
183
+ Returns:
184
+ FPGATimingScenarioSchema: The newly created copy of the scenario.
185
+
186
+ Raises:
187
+ LookupError: If the source scenario specified by ``scenario`` does not exist.
188
+ """
189
+ constraint = EditableSchema(self.get_scenario(scenario)).copy()
190
+ EditableSchema(constraint).rename(name)
191
+ if insert:
192
+ if self.valid("scenario", name):
193
+ raise ValueError(f"{name} already exists")
194
+ self.add_scenario(constraint)
195
+ return constraint
196
+
164
197
  def remove_scenario(self, scenario: str) -> bool:
165
198
  """
166
199
  Removes a timing scenario from the design configuration.
@@ -182,8 +215,170 @@ class FPGATimingConstraintSchema(BaseSchema):
182
215
  if not scenario:
183
216
  raise ValueError("scenario name is required")
184
217
 
185
- if not self.valid(scenario):
218
+ if not self.valid("scenario", scenario):
219
+ return False
220
+
221
+ EditableSchema(self).remove("scenario", scenario)
222
+ return True
223
+
224
+ def add_mode(self, mode: TimingModeSchema):
225
+ """
226
+ Adds a timing mode to the design configuration.
227
+
228
+ This method is responsible for incorporating a new or updated timing mode
229
+ into the system's configuration. If a mode with the same name already
230
+ exists, it will be overwritten (`clobber=True`).
231
+
232
+ Args:
233
+ mode: The :class:`TimingModeSchema` object representing the timing mode
234
+ to add. This object must have a valid name defined via its `name()` method.
235
+
236
+ Raises:
237
+ TypeError: If the provided `mode` argument is not an instance of
238
+ :class:`TimingModeSchema`.
239
+ ValueError: If the `mode` object's `name()` method returns None, indicating
240
+ that the mode does not have a defined name.
241
+ """
242
+ if not isinstance(mode, TimingModeSchema):
243
+ raise TypeError("mode must be a timing mode object")
244
+
245
+ if mode.name is None:
246
+ raise ValueError("mode must have a name")
247
+
248
+ EditableSchema(self).insert("mode", mode.name, mode, clobber=True)
249
+
250
+ def get_mode(self, mode: Optional[str] = None) \
251
+ -> Union[TimingModeSchema, Dict[str, TimingModeSchema]]:
252
+ """
253
+ Retrieves one or all timing modes from the configuration.
254
+
255
+ This method provides flexibility to fetch either a specific timing mode
256
+ by its name or a collection of all currently defined modes.
257
+
258
+ Args:
259
+ mode (str, optional): The name (string) of the specific timing mode to retrieve.
260
+ If this argument is omitted or set to None, the method will
261
+ return a dictionary containing all available timing modes.
262
+
263
+ Returns:
264
+ If `mode` is provided: The :class:`TimingModeSchema` object corresponding
265
+ to the specified mode name.
266
+ If `mode` is None: A dictionary where keys are mode names (str) and
267
+ values are their respective :class:`TimingModeSchema` objects.
268
+
269
+ Raises:
270
+ LookupError: If a specific `mode` name is provided but no mode with
271
+ that name is found in the configuration.
272
+ """
273
+ if mode is None:
274
+ modes = {}
275
+ for name in self.getkeys("mode"):
276
+ modes[name] = self.get("mode", name, field="schema")
277
+ return modes
278
+
279
+ if not self.valid("mode", mode):
280
+ raise LookupError(f"{mode} is not defined")
281
+ return self.get("mode", mode, field="schema")
282
+
283
+ def make_mode(self, mode: str) -> TimingModeSchema:
284
+ """
285
+ Creates and adds a new timing mode with the specified name.
286
+
287
+ This method initializes a new :class:`TimingModeSchema` object with the given
288
+ name and immediately adds it to the constraint configuration. It ensures that
289
+ a mode with the same name does not already exist, preventing accidental
290
+ overwrites.
291
+
292
+ Args:
293
+ mode (str): The name for the new timing mode. This name must be
294
+ a non-empty string and unique within the current configuration.
295
+
296
+ Returns:
297
+ :class:`TimingModeSchema`: The newly created :class:`TimingModeSchema`
298
+ object.
299
+
300
+ Raises:
301
+ ValueError: If the provided `mode` name is empty or None.
302
+ LookupError: If a mode with the specified `mode` name already exists
303
+ in the configuration.
304
+ """
305
+ if not mode:
306
+ raise ValueError("mode name is required")
307
+
308
+ if self.valid("mode", mode):
309
+ raise LookupError(f"{mode} mode already exists")
310
+
311
+ modeobj = TimingModeSchema(mode)
312
+ self.add_mode(modeobj)
313
+ return modeobj
314
+
315
+ def copy_mode(self, mode: str, name: str, insert: bool = True) \
316
+ -> TimingModeSchema:
317
+ """
318
+ Copies an existing timing mode, renames it, and optionally adds it to the design.
319
+
320
+ This method retrieves the mode identified by ``mode``, creates a
321
+ deep copy of it, and renames the copy to ``name``. If ``insert`` is True,
322
+ the new mode is immediately added to the configuration.
323
+
324
+ Args:
325
+ mode (str): The name of the existing mode to be copied.
326
+ name (str): The name to assign to the new copied mode.
327
+ insert (bool, optional): Whether to add the newly created mode
328
+ to the configuration. Defaults to True.
329
+
330
+ Returns:
331
+ TimingModeSchema: The newly created copy of the mode.
332
+
333
+ Raises:
334
+ LookupError: If the source mode specified by ``mode`` does not exist.
335
+ """
336
+ newmode = EditableSchema(self.get_mode(mode)).copy()
337
+ EditableSchema(newmode).rename(name)
338
+ if insert:
339
+ if self.valid("mode", name):
340
+ raise ValueError(f"{name} already exists")
341
+ self.add_mode(newmode)
342
+ return newmode
343
+
344
+ def remove_mode(self, mode: str) -> bool:
345
+ """
346
+ Removes a timing mode from the design configuration.
347
+
348
+ This method deletes the specified timing mode from the system's
349
+ configuration.
350
+
351
+ Args:
352
+ mode (str): The name of the timing mode to remove.
353
+ This name must be a non-empty string.
354
+
355
+ Returns:
356
+ bool: True if the mode was successfully removed, False if no
357
+ mode with the given name was found.
358
+
359
+ Raises:
360
+ ValueError: If the provided `mode` name is empty or None.
361
+ """
362
+ if not mode:
363
+ raise ValueError("mode name is required")
364
+
365
+ if not self.valid("mode", mode):
186
366
  return False
187
367
 
188
- EditableSchema(self).remove(scenario)
368
+ EditableSchema(self).remove("mode", mode)
189
369
  return True
370
+
371
+ def _from_dict(self, manifest: Dict,
372
+ keypath: Union[List[str], Tuple[str, ...]],
373
+ version: Optional[Tuple[int, ...]] = None,
374
+ lazyload: LazyLoad = LazyLoad.ON) \
375
+ -> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
376
+ if version and version < (0, 53, 0):
377
+ manifest.pop("__meta__", None)
378
+ manifest = {
379
+ "scenario": manifest,
380
+ "mode": self.getdict("mode")
381
+ }
382
+ lazyload = LazyLoad.OFF
383
+
384
+ return super()._from_dict(manifest, keypath, version, lazyload)