diff --git a/Changelog.md b/Changelog.md index 5e4944483..6fe12eb3a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,6 +14,45 @@ ## Documentation --> +# Dev + +## Misc Enhancements/Bugfixes + +* `openlane.state` + * `DesignFormat` + * Now a dataclass encapsulating the information about the DesignFormat + directly. + * `.factory` is a factory for retrieval of DesignFormats by ID + * `DesignFormats` may be registered to the factory using `.register()` + * Registrations for previously included `DesignFormat`s now moved to + appropriate files. + * Renamed `POWERED_NETLIST_NO_PHYSICAL_CELLS` to + `LOGICAL_POWERED_NETLIST` + * Renamed `POWERED_NETLIST_SDF_FRIENDLY` to + `SDF_FRIENDLY_POWERED_NETLIST` + * `State` + * States initialized with keys that have values that are `None` now remove + said keys. + +## API Breaks + +* `openlane.steps` + + * `TclStep` now uses the IDs uppercased for `CURRENT_` and `SAVE_`. + +* `openlane.state` + + * `State` no longer includes all `DesignFormat`s as guaranteed keys and `.get` + must be used to avoide `KeyErrors` + * `DesignFormat` is no longer an enumeration and is not iterable. However, to + avoid massive codebase changes, you can still access `DesignFormat`s + registered to the factory using the dot notation (e.g. + `DesignFormat.NETLIST`), using either their `id` or any of their `alts`. + * Removed `DesignFormatObject`: the DesignFormat class itself is now a + dataclass incorporating these fields, except `name`, which has been renamed + to `full_name`. The enumeration's name has been added to `alts`, while + `.name` is now an alias for `.id`. + # 2.2.9 ## Steps diff --git a/openlane/config/variable.py b/openlane/config/variable.py index 405d4dbf2..7fd5d1528 100644 --- a/openlane/config/variable.py +++ b/openlane/config/variable.py @@ -160,7 +160,7 @@ def view_by_df( self, df: DesignFormat ) -> Union[None, Path, List[Path], Dict[str, List[Path]]]: try: - return getattr(self, df.value.id) + return getattr(self, df.id) except AttributeError: return None diff --git a/openlane/flows/flow.py b/openlane/flows/flow.py index 3c993523a..7ea089789 100644 --- a/openlane/flows/flow.py +++ b/openlane/flows/flow.py @@ -48,7 +48,7 @@ from openlane.common.types import Path from ..config import Config, Variable, universal_flow_config_variables, AnyConfigs -from ..state import State, DesignFormat, DesignFormatObject +from ..state import State, DesignFormat from ..steps import Step, StepNotFound from ..logging import ( LevelFilter, @@ -804,14 +804,11 @@ def _save_snapshot_ef(self, path: Union[str, os.PathLike]): } def visitor(key, value, top_key, _, __): - df = DesignFormat.by_id(top_key) + df = DesignFormat.factory.get(top_key) assert df is not None if df not in supported_formats: return - dfo = df.value - assert isinstance(dfo, DesignFormatObject) - subdirectory, extension = supported_formats[df] target_dir = os.path.join(path, subdirectory) @@ -837,7 +834,7 @@ def visitor(key, value, top_key, _, __): return target_basename = os.path.basename(str(value)) - target_basename = target_basename[: -len(dfo.extension)] + extension + target_basename = target_basename[: -len(df.extension)] + extension target_path = os.path.join(target_dir, target_basename) mkdirp(target_dir) shutil.copyfile(value, target_path, follow_symlinks=True) diff --git a/openlane/scripts/openroad/common/io.tcl b/openlane/scripts/openroad/common/io.tcl index a77a119a5..486211086 100644 --- a/openlane/scripts/openroad/common/io.tcl +++ b/openlane/scripts/openroad/common/io.tcl @@ -125,14 +125,14 @@ proc read_current_netlist {args} { flags {-powered} if { [info exists flags(-powered)] } { - puts "Reading top-level powered netlist at '$::env(CURRENT_POWERED_NETLIST)'…" - if {[catch {read_verilog $::env(CURRENT_POWERED_NETLIST)} errmsg]} { + puts "Reading top-level powered netlist at '$::env(CURRENT_PNL)'…" + if {[catch {read_verilog $::env(CURRENT_PNL)} errmsg]} { puts stderr $errmsg exit 1 } } else { - puts "Reading top-level netlist at '$::env(CURRENT_NETLIST)'…" - if {[catch {read_verilog $::env(CURRENT_NETLIST)} errmsg]} { + puts "Reading top-level netlist at '$::env(CURRENT_NL)'…" + if {[catch {read_verilog $::env(CURRENT_NL)} errmsg]} { puts stderr $errmsg exit 1 } @@ -338,32 +338,32 @@ proc write_views {args} { write_db $::env(SAVE_ODB) } - if { [info exists ::env(SAVE_NETLIST)] } { - puts "Writing netlist to '$::env(SAVE_NETLIST)'…" - write_verilog $::env(SAVE_NETLIST) + if { [info exists ::env(SAVE_NL)] } { + puts "Writing netlist to '$::env(SAVE_NL)'…" + write_verilog $::env(SAVE_NL) } - if { [info exists ::env(SAVE_POWERED_NETLIST)] } { - puts "Writing powered netlist to '$::env(SAVE_POWERED_NETLIST)'…" - write_verilog -include_pwr_gnd $::env(SAVE_POWERED_NETLIST) + if { [info exists ::env(SAVE_PNL)] } { + puts "Writing powered netlist to '$::env(SAVE_PNL)'…" + write_verilog -include_pwr_gnd $::env(SAVE_PNL) } - if { [info exists ::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY)] } { + if { [info exists ::env(SAVE_SDF_PNL)] } { set exclude_cells "[join $::env(FILL_CELL)] [join $::env(DECAP_CELL)] [join $::env(WELLTAP_CELL)] [join $::env(ENDCAP_CELL)]" - puts "Writing nofill powered netlist to '$::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY)'…" + puts "Writing nofill powered netlist to '$::env(SAVE_SDF_PNL)'…" puts "Excluding $exclude_cells" write_verilog -include_pwr_gnd \ -remove_cells "$exclude_cells"\ - $::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY) + $::env(SAVE_SDF_PNL) } - if { [info exists ::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS)] } { + if { [info exists ::env(SAVE_LOGICAL_PNL)] } { set exclude_cells "[join [lindex [split $::env(DIODE_CELL) "/"] 0]] [join $::env(FILL_CELL)] [join $::env(DECAP_CELL)] [join $::env(WELLTAP_CELL)] [join $::env(ENDCAP_CELL)]" - puts "Writing nofilldiode powered netlist to '$::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS)'…" + puts "Writing nofilldiode powered netlist to '$::env(SAVE_LOGICAL_PNL)'…" puts "Excluding $exclude_cells" write_verilog -include_pwr_gnd \ -remove_cells "$exclude_cells"\ - $::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS) + $::env(SAVE_LOGICAL_PNL) } if { [info exists ::env(SAVE_OPENROAD_LEF)] } { diff --git a/openlane/state/__init__.py b/openlane/state/__init__.py index 7748f546a..65d9de6fb 100644 --- a/openlane/state/__init__.py +++ b/openlane/state/__init__.py @@ -20,5 +20,5 @@ OpenLane step. The State is essentially a list of views in various formats in addition to the cumulative set of metrics created by previous Steps. """ -from .design_format import DesignFormat, DesignFormatObject +from .design_format import DesignFormat from .state import State, InvalidState, StateElement diff --git a/openlane/state/design_format.py b/openlane/state/design_format.py index 5c344e5ee..67bee1201 100644 --- a/openlane/state/design_format.py +++ b/openlane/state/design_format.py @@ -11,26 +11,38 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from enum import Enum -from dataclasses import dataclass -from typing import Dict, Optional +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Dict, List, Optional, ClassVar +from deprecated.sphinx import deprecated + + +class DFMetaclass(type): + def __getattr__(Self, key: str): + if df := Self.factory.get(key): + return df + raise AttributeError( + "Unknown DesignFormat or Type[DesignFormat] attribute", + key, + Self, + ) @dataclass -class DesignFormatObject: +class DesignFormat(metaclass=DFMetaclass): """ Metadata about the various possible text or binary representations (views) of any design. - For example, ``DesignFormat.NETLIST.value`` has the metadata for Netlist - views. + For example, ``DesignFormat.nl`` has the metadata for Netlist views. - :param id: A lowercase alphanumeric identifier for the design format. - Some IDs in OpenLane 2.X use dashes. This is an inconsistency that will - be addressed in the next major version of OpenLane as it would be a - breaking change. + :param id: A lowercase alphanumeric/underscore identifier for the design + format. :param extension: The file extension for designs saved in this format. - :param name: A human-readable name for this design format. + :param full_name: A human-readable name for this design format. + :param alts: A list of alternate ids used to access the DesignFormat by + the subscript operator. Includes its OpenLane <3.0.0 enumeration name + for limited backwards compatibility. :param folder_override: The subdirectory when :meth:`openlane.state.State.save_snapshot` is called on a state. If unset, the value for ``id`` will be used. @@ -40,7 +52,8 @@ class DesignFormatObject: id: str extension: str - name: str + full_name: str + alts: List[str] = field(default_factory=list) folder_override: Optional[str] = None multiple: bool = False @@ -48,133 +61,178 @@ class DesignFormatObject: def folder(self) -> str: return self.folder_override or self.id - -class DesignFormat(Enum): - """ - An `enumeration `_ of a number - of :class:`openlane.state.DesignFormatObject`\\s representing the various - possible text or binary representations (views) supported by OpenLane - states. - - Members of this enumeration are used as the keys of - :class:`openlane.state.State` objects. - """ - - NETLIST: DesignFormatObject = DesignFormatObject( - "nl", - "nl.v", - "Verilog Netlist", - ) - POWERED_NETLIST: DesignFormatObject = DesignFormatObject( - "pnl", - "pnl.v", - "Powered Verilog Netlist", - ) - POWERED_NETLIST_SDF_FRIENDLY: DesignFormatObject = DesignFormatObject( - "pnl-sdf-friendly", - "pnl-sdf.v", - "Powered Verilog Netlist For SDF Simulation (Without Fill Cells)", - folder_override="pnl", - ) - POWERED_NETLIST_NO_PHYSICAL_CELLS: DesignFormatObject = DesignFormatObject( - "pnl-npc", - "pnl-npc.v", - "Powered Verilog Netlist Without Physical Cells (Fill Cells and Diode Cells)", - folder_override="pnl", - ) - - DEF: DesignFormatObject = DesignFormatObject( - "def", - "def", - "Design Exchange Format", - ) - LEF: DesignFormatObject = DesignFormatObject( - "lef", - "lef", - "Library Exchange Format", - ) - OPENROAD_LEF: DesignFormatObject = DesignFormatObject( - "openroad-lef", - "openroad.lef", - "Library Exchange Format Generated by OpenROAD", - folder_override="lef", - ) - ODB: DesignFormatObject = DesignFormatObject( - "odb", - "odb", - "OpenDB Database", - ) - - SDC: DesignFormatObject = DesignFormatObject( - "sdc", - "sdc", - "Design Constraints", - ) - SDF: DesignFormatObject = DesignFormatObject( - "sdf", - "sdf", - "Standard Delay Format", - multiple=True, - ) - SPEF: DesignFormatObject = DesignFormatObject( - "spef", - "spef", - "Standard Parasitics Extraction Format", - multiple=True, # nom, min, max, ... - ) - LIB: DesignFormatObject = DesignFormatObject( - "lib", - "lib", - "LIB Timing Library Format", - multiple=True, - ) - SPICE: DesignFormatObject = DesignFormatObject( - "spice", - "spice", - "Simulation Program with Integrated Circuit Emphasis", - ) - - MAG: DesignFormatObject = DesignFormatObject( - "mag", - "mag", - "Magic VLSI View", + @property + @deprecated( + "The DesignFormat is directly returned now, no need for .value", + version="3.0.0", + action="once", ) + def value(self) -> DesignFormat: + return self - GDS: DesignFormatObject = DesignFormatObject( - "gds", - "gds", - "GDSII Stream", - ) - MAG_GDS: DesignFormatObject = DesignFormatObject( - "mag_gds", - "magic.gds", - "GDSII Stream (Magic)", - ) - KLAYOUT_GDS: DesignFormatObject = DesignFormatObject( - "klayout_gds", - "klayout.gds", - "GDSII Stream (KLayout)", + @property + @deprecated( + ".name has been removed because it's redundant, use .id", + version="3.0.0", + action="once", ) + def name(self) -> str: + return self.id - JSON_HEADER: DesignFormatObject = DesignFormatObject( - "json_h", - "h.json", - "Design JSON Header File", - ) - VERILOG_HEADER: DesignFormatObject = DesignFormatObject( - "vh", - "vh", - "Verilog Header", - ) + def register(self): + self.__class__.factory.register(self) def __str__(self) -> str: - return self.value.id + return self.id + + def __hash__(self): + return hash(self.id) @staticmethod + @deprecated( + "Use DesignFormat.factory.get", + version="3.0.0", + action="once", + ) def by_id(id: str) -> Optional["DesignFormat"]: - return _designformat_by_id.get(id) - - -_designformat_by_id: Dict[str, "DesignFormat"] = { - format.value.id: format for format in DesignFormat -} + return DesignFormat.factory.get(id) + + class DesignFormatFactory(object): + """ + A factory singleton for DesignFormats, allowing them to be registered + and then retrieved by a string name. + + See https://en.wikipedia.org/wiki/Factory_(object-oriented_programming) for + a primer. + """ + + _registry: ClassVar[Dict[str, DesignFormat]] = {} + + @classmethod + def register(Self, df: DesignFormat) -> DesignFormat: + """ + Adds a DesignFormat to the registry using its + :attr:`DesignFormat.id`, :attr:`DesignFormat.name` and + :attr:`DesignFormat.alts` attributes. + """ + Self._registry[df.id] = df + for alt in df.alts: + Self._registry[alt] = df + return df + + @classmethod + def get(Self, name: str) -> Optional[DesignFormat]: + """ + Retrieves a DesignFormat type from the registry using a lookup + string. + + :param name: The registered name of the Step. Case-insensitive. + """ + return Self._registry.get(name) + + @classmethod + def list(Self) -> List[str]: + """ + :returns: A list of IDs of all registered DesignFormat. + """ + return [cls.id for cls in Self._registry.values()] + + factory: ClassVar = DesignFormatFactory + + +# Common Design Formats +DesignFormat( + "nl", + "nl.v", + "Verilog Netlist", + alts=["NETLIST"], +).register() + +DesignFormat( + "pnl", + "pnl.v", + "Powered Verilog Netlist", + alts=["POWERED_NETLIST"], +).register() + +DesignFormat( + "sdf_pnl", + "sdf_pnl.v", + "Powered Verilog Netlist for SDF Simulation (No Fills)", + alts=["SDF_FRIENDLY_POWERED_NETLIST"], + folder_override="pnl", +).register() + +DesignFormat( + "logical_pnl", + "logical_pnl.v", + "Logical cell-only Powered Verilog Netlist", + alts=["LOGICAL_POWERED_NETLIST"], + folder_override="pnl", +).register() + +DesignFormat( + "def", + "def", + "Design Exchange Format", + alts=["def_", "DEF"], +).register() + +DesignFormat( + "lef", + "lef", + "Library Exchange Format", + alts=["LEF"], +).register() + +DesignFormat( + "sdc", + "sdc", + "Design Constraints", + alts=["SDC"], +).register() + +DesignFormat( + "sdf", + "sdf", + "Standard Delay Format", + alts=["SDF"], + multiple=True, +).register() + +DesignFormat( + "spef", + "spef", + "Standard Parasitics Extraction Format", + alts=["SPEF"], + multiple=True, # nom, min, max, ... +).register() + +DesignFormat( + "lib", + "lib", + "LIB Timing Library Format", + alts=["LIB"], + multiple=True, +).register() + +DesignFormat( + "spice", + "spice", + "Simulation Program with Integrated Circuit Emphasis", + alts=["SPICE"], +).register() + +DesignFormat( + "gds", + "gds", + "GDSII Stream", + alts=["GDS"], +).register() + +DesignFormat( + "vh", + "vh", + "Verilog Header", + alts=["VERILOG_HEADER"], +).register() diff --git a/openlane/state/state.py b/openlane/state/state.py index 1f8e6e377..4ade8117b 100644 --- a/openlane/state/state.py +++ b/openlane/state/state.py @@ -22,7 +22,6 @@ from .design_format import ( DesignFormat, - DesignFormatObject, ) from ..common import ( @@ -91,26 +90,21 @@ def __init__( if c_mapping := copying: for key, value in c_mapping.items(): if isinstance(key, DesignFormat): - copying_resolved[key.value.id] = value + copying_resolved[key.id] = value else: copying_resolved[key] = value - for format in DesignFormat: - assert isinstance(format.value, DesignFormatObject) # type checker shut up - if format.value.id not in copying_resolved: - copying_resolved[format.value.id] = None - overrides_resolved = {} if o_mapping := overrides: for k, value in o_mapping.items(): if isinstance(k, DesignFormat): - assert isinstance( - k.value, DesignFormatObject - ) # type checker shut up - k = k.value.id + k = k.id overrides_resolved[k] = value self.metrics = GenericImmutableDict(metrics or {}) + for key in list(copying_resolved.keys()): + if copying_resolved[key] is None: + del copying_resolved[key] super().__init__( copying_resolved, @@ -121,19 +115,19 @@ def __init__( def __getitem__(self, key: Union[DesignFormat, str]) -> StateElement: if isinstance(key, DesignFormat): - id: str = key.value.id + id: str = key.id key = id return super().__getitem__(key) def __setitem__(self, key: Union[DesignFormat, str], item: StateElement): if isinstance(key, DesignFormat): - id: str = key.value.id + id: str = key.id key = id return super().__setitem__(key, item) def __delitem__(self, key: Union[DesignFormat, str]): if isinstance(key, DesignFormat): - id: str = key.value.id + id: str = key.id key = id return super().__delitem__(key) @@ -164,10 +158,8 @@ def _walk( if current_top_key is None: current_top_key = key current_folder = key.strip("_*") - if df := DesignFormat.by_id(key): - # For type-checker: all guaranteed to be DesignFormatObjects - assert isinstance(df.value, DesignFormatObject) - current_folder = df.value.folder + if df := DesignFormat.factory.get(key): + current_folder = df.folder target_dir = os.path.join(save_directory, current_folder) @@ -228,7 +220,11 @@ def validate(self): """ def visitor(key, value, top_key, _, depth): - if depth == 0 and DesignFormat.by_id(top_key) is None: + if ( + depth == 0 + and DesignFormat.factory.get(top_key) is None + and value is not None + ): raise InvalidState( f"Key '{top_key}' does not match a known design format." ) @@ -314,8 +310,8 @@ def __mapping_to_html_rec( continue key_content = id - if format := DesignFormat.by_id(id): - key_content = format.value.id + if format := DesignFormat.factory.get(id): + key_content = format.id value_content = str(value) if isinstance(value, Mapping): diff --git a/openlane/steps/klayout.py b/openlane/steps/klayout.py index 14db3d1a0..527775633 100644 --- a/openlane/steps/klayout.py +++ b/openlane/steps/klayout.py @@ -29,6 +29,14 @@ from ..common import Path, get_script_dir, mkdirp, _get_process_limit +DesignFormat( + "klayout_gds", + "klayout.gds", + "GDSII Stream (KLayout)", + alts=["KLAYOUT_GDS"], +).register() + + class KLayoutStep(Step): config_vars = [ Variable( @@ -149,7 +157,7 @@ class Render(KLayoutStep): def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: input_view = state_in[DesignFormat.DEF] - if gds := state_in[DesignFormat.GDS]: + if gds := state_in.get(DesignFormat.GDS): input_view = gds assert isinstance(input_view, Path) @@ -193,7 +201,7 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: klayout_gds_out = os.path.join( self.step_dir, - f"{self.config['DESIGN_NAME']}.{DesignFormat.KLAYOUT_GDS.value.extension}", + f"{self.config['DESIGN_NAME']}.{DesignFormat.KLAYOUT_GDS.extension}", ) kwargs, env = self.extract_env(kwargs) @@ -205,7 +213,7 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: "klayout", "stream_out.py", ), - state_in[DesignFormat.DEF.value.id], + state_in[DesignFormat.DEF.id], "--output", abspath(klayout_gds_out), "--top", diff --git a/openlane/steps/magic.py b/openlane/steps/magic.py index bd27f9c1b..fc24fd82d 100644 --- a/openlane/steps/magic.py +++ b/openlane/steps/magic.py @@ -37,6 +37,22 @@ from ..common import get_script_dir, DRC as DRCObject, Path, mkdirp +DesignFormat( + "mag", + "mag", + "Magic VLSI View", + alts=["MAG"], +).register() + + +DesignFormat( + "mag_gds", + "magic.gds", + "GDSII Stream (Magic)", + alts=["MAG_GDS"], +).register() + + class MagicOutputProcessor(OutputProcessor): _error_patterns = [ re.compile(rx) @@ -192,10 +208,10 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: views_updates: ViewsUpdate = {} for output in self.outputs: - if output.value.multiple: + if output.multiple: # Too step-specific. continue - path = Path(env[f"SAVE_{output.name}"]) + path = Path(env[f"SAVE_{output.id.upper()}"]) if not path.exists(): continue views_updates[output] = path diff --git a/openlane/steps/odb.py b/openlane/steps/odb.py index 63a9d7e48..71ea6b77d 100644 --- a/openlane/steps/odb.py +++ b/openlane/steps/odb.py @@ -73,9 +73,9 @@ def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: views_updates: ViewsUpdate = {} command = self.get_command() for output in automatic_outputs: - filename = f"{self.config['DESIGN_NAME']}.{output.value.extension}" + filename = f"{self.config['DESIGN_NAME']}.{output.extension}" file_path = os.path.join(self.step_dir, filename) - command.append(f"--output-{output.value.id}") + command.append(f"--output-{output.id}") command.append(file_path) views_updates[output] = Path(file_path) @@ -123,7 +123,7 @@ def get_command(self) -> List[str]: for lef in extra_lefs: lefs.append("--input-lef") lefs.append(lef) - if (design_lef := self.state_in.result()[DesignFormat.LEF]) and ( + if (design_lef := self.state_in.result().get(DesignFormat.LEF)) and ( DesignFormat.LEF in self.inputs ): lefs.append("--design-lef") diff --git a/openlane/steps/openroad.py b/openlane/steps/openroad.py index 11cd6efe3..a74dafcbb 100644 --- a/openlane/steps/openroad.py +++ b/openlane/steps/openroad.py @@ -127,6 +127,22 @@ def pdn_macro_migrator(x): return [x.strip()] +DesignFormat( + "odb", + "odb", + "OpenDB Database", + alts=["ODB"], +).register() + +DesignFormat( + "openroad_lef", + "openroad.lef", + "Library Exchange Format Generated by OpenROAD", + alts=["OPENROAD_LEF"], + folder_override="lef", +).register() + + @Step.factory.register() class CheckSDCFiles(Step): """ @@ -284,10 +300,10 @@ def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: views_updates: ViewsUpdate = {} for output in self.outputs: - if output.value.multiple: + if output.multiple: # Too step-specific. continue - path = Path(env[f"SAVE_{output.name}"]) + path = Path(env[f"SAVE_{output.id.upper()}"]) if not path.exists(): continue views_updates[output] = path @@ -412,7 +428,7 @@ def _get_corner_files( name = timing_corner current_corner_spef = None - input_spef_dict = state_in[DesignFormat.SPEF] + input_spef_dict = state_in.get(DesignFormat.SPEF) if input_spef_dict is not None: if not isinstance(input_spef_dict, dict): raise StepException( @@ -729,7 +745,7 @@ def run_corner( def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: views_updates, metrics_updates = super().run(state_in, **kwargs) - sdf_dict = state_in[DesignFormat.SDF] or {} + sdf_dict = state_in.get(DesignFormat.SDF, {}) if not isinstance(sdf_dict, dict): raise StepException( "Malformed input state: incoming value for SDF is not a dictionary." @@ -863,7 +879,7 @@ def run_corner( def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: views_updates, metrics_updates = super().run(state_in, **kwargs) - lib_dict = state_in[DesignFormat.LIB] or {} + lib_dict = state_in.get(DesignFormat.LIB, {}) if not isinstance(lib_dict, dict): raise StepException( "Malformed input state: value for LIB is not a dictionary." @@ -1775,7 +1791,7 @@ def run_corner(corner: str): views_updates: ViewsUpdate = {} metrics_updates: MetricsUpdate = {} - spef_dict = state_in[DesignFormat.SPEF] or {} + spef_dict = state_in.get(DesignFormat.SPEF, {}) if not isinstance(spef_dict, dict): raise StepException( "Malformed input state: value for SPEF is not a dictionary." @@ -1940,8 +1956,8 @@ class WriteViews(OpenROADStep): id = "OpenROAD.WriteViews" name = "OpenROAD Write Views" outputs = OpenROADStep.outputs + [ - DesignFormat.POWERED_NETLIST_SDF_FRIENDLY, - DesignFormat.POWERED_NETLIST_NO_PHYSICAL_CELLS, + DesignFormat.SDF_FRIENDLY_POWERED_NETLIST, + DesignFormat.LOGICAL_POWERED_NETLIST, DesignFormat.OPENROAD_LEF, ] diff --git a/openlane/steps/pyosys.py b/openlane/steps/pyosys.py index 8f76ddc21..3ca457f23 100644 --- a/openlane/steps/pyosys.py +++ b/openlane/steps/pyosys.py @@ -133,6 +133,13 @@ def _parse_yosys_check( ), ] +DesignFormat( + "json_h", + "h.json", + "Design JSON Header File", + alts=["JSON_HEADER"], +).register() + class PyosysStep(Step): config_vars = [ @@ -308,14 +315,14 @@ def get_script_path(self) -> str: def get_command(self, state_in: State) -> List[str]: out_file = os.path.join( self.step_dir, - f"{self.config['DESIGN_NAME']}.{DesignFormat.JSON_HEADER.value.extension}", + f"{self.config['DESIGN_NAME']}.{DesignFormat.JSON_HEADER.extension}", ) return super().get_command(state_in) + ["--output", out_file] def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: out_file = os.path.join( self.step_dir, - f"{self.config['DESIGN_NAME']}.{DesignFormat.JSON_HEADER.value.extension}", + f"{self.config['DESIGN_NAME']}.{DesignFormat.JSON_HEADER.extension}", ) views_updates, metrics_updates = super().run(state_in, **kwargs) views_updates[DesignFormat.JSON_HEADER] = Path(out_file) @@ -466,7 +473,7 @@ def get_script_path(self) -> str: def get_command(self, state_in: State) -> List[str]: out_file = os.path.join( self.step_dir, - f"{self.config['DESIGN_NAME']}.{DesignFormat.NETLIST.value.extension}", + f"{self.config['DESIGN_NAME']}.{DesignFormat.NETLIST.extension}", ) cmd = super().get_command(state_in) if self.config["USE_LIGHTER"]: @@ -491,7 +498,7 @@ def get_command(self, state_in: State) -> List[str]: def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: out_file = os.path.join( self.step_dir, - f"{self.config['DESIGN_NAME']}.{DesignFormat.NETLIST.value.extension}", + f"{self.config['DESIGN_NAME']}.{DesignFormat.NETLIST.extension}", ) view_updates, metric_updates = super().run(state_in, **kwargs) diff --git a/openlane/steps/step.py b/openlane/steps/step.py index 15d18d575..2871b1110 100644 --- a/openlane/steps/step.py +++ b/openlane/steps/step.py @@ -53,7 +53,7 @@ Variable, universal_flow_config_variables, ) -from ..state import DesignFormat, DesignFormatObject, State, InvalidState, StateElement +from ..state import DesignFormat, State, InvalidState, StateElement from ..common import ( GenericDict, GenericImmutableDict, @@ -661,7 +661,7 @@ def get_help_md( for input, output in zip_longest(Self.inputs, Self.outputs): input_str = "" if input is not None: - input_str = f"{input.value.name} (.{input.value.extension})" + input_str = f"{input.full_name} (.{input.extension})" output_str = "" if output is not None: @@ -669,7 +669,7 @@ def get_help_md( raise StepException( f"Output '{output}' is not a valid DesignFormat enum object." ) - output_str = f"{output.value.name} (.{output.value.extension})" + output_str = f"{output.full_name} (.{output.extension})" result += f"| {input_str} | {output_str} |\n" if len(Self.config_vars): @@ -733,9 +733,9 @@ def _repr_markdown_(self) -> str: # pragma: no cover continue if state_in.get(id) != value: - df = DesignFormat.by_id(id) + df = DesignFormat.factory.get(id) assert df is not None - views_updated.append(df.value.name) + views_updated.append(df.full_name) if len(views_updated): result += "#### Views updated:\n" @@ -996,14 +996,14 @@ def filename_with_counter(): # 2. State state_in: GenericDict[str, Any] = self.state_in.result().copy_mut() - for format in DesignFormat: - assert isinstance(format.value, DesignFormatObject) # type checker shut up + for format_id in state_in: + format = DesignFormat.factory.get(format_id) if format not in self.__class__.inputs and not ( format == DesignFormat.DEF and DesignFormat.ODB in self.__class__.inputs # hack to write tests a bit more easily ): - state_in[format.value.id] = None + state_in[format.id] = None state_in["metrics"] = self.state_in.result().metrics.copy_mut() dumpable_state = copy_recursive(state_in, translator=visitor) state_path = os.path.join(target_dir, "state_in.json") @@ -1143,7 +1143,7 @@ def start( value = state_in_result[input] if value is None: raise StepException( - f"{type(self).__name__}: missing required input '{input.name}'" + f"{type(self).__name__}: missing required input '{input.id}'" ) from None try: @@ -1537,7 +1537,7 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: for key in state: if ( state_in.get(key) != state.get(key) - and DesignFormat.by_id(key) in self.outputs + and DesignFormat.factory.get(key) in self.outputs ): views_updates[key] = state[key] for key in state.metrics: diff --git a/openlane/steps/tclstep.py b/openlane/steps/tclstep.py index a4277b4fe..c78751680 100644 --- a/openlane/steps/tclstep.py +++ b/openlane/steps/tclstep.py @@ -158,15 +158,15 @@ def prepare_env(self, env: dict, state: State) -> dict: env[element] = TclStep.value_to_tcl(value) for input in self.inputs: - key = f"CURRENT_{input.name}" + key = f"CURRENT_{input.id.upper()}" env[key] = TclStep.value_to_tcl(state[input]) for output in self.outputs: - if output.value.multiple: + if output.multiple: # Too step-specific. continue - filename = f"{self.config['DESIGN_NAME']}.{output.value.extension}" - env[f"SAVE_{output.name}"] = os.path.join(self.step_dir, filename) + filename = f"{self.config['DESIGN_NAME']}.{output.extension}" + env[f"SAVE_{output.id.upper()}"] = os.path.join(self.step_dir, filename) return env @@ -210,10 +210,10 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: overrides: ViewsUpdate = {} for output in self.outputs: - if output.value.multiple: + if output.multiple: # Too step-specific. continue - path = Path(env[f"SAVE_{output.name}"]) + path = Path(env[f"SAVE_{output.id.upper()}"]) if not path.exists(): continue overrides[output] = path diff --git a/test/flows/test_flow.py b/test/flows/test_flow.py index 636f57174..0a8972058 100644 --- a/test/flows/test_flow.py +++ b/test/flows/test_flow.py @@ -71,7 +71,9 @@ def run(self, state_in: State, **kwargs): json.dump( { "probably_a_valid_header": False, - "previous_invalid_header": str(state_in[DesignFormat.JSON_HEADER]), + "previous_invalid_header": str( + state_in.get(DesignFormat.JSON_HEADER) + ), }, open(out_file, "w", encoding="utf8"), ) diff --git a/test/state/test_state.py b/test/state/test_state.py index 27a9d7d68..ed4bf36d9 100644 --- a/test/state/test_state.py +++ b/test/state/test_state.py @@ -126,15 +126,15 @@ def test_copy(): def test_empty(): - from openlane.state import DesignFormat, State + from openlane.state import State test_dict = {} test_metrics = {} state = State(test_dict, metrics=test_metrics) assert state.metrics == test_metrics - for format in DesignFormat: - assert state[format.value.id] is None, "new state has non-none value" + + assert len(state) == 0, "New empty state has entries" def test_path_fail_exists(): diff --git a/test/steps/test_tclstep.py b/test/steps/test_tclstep.py index 9166e0618..f2ac6c410 100644 --- a/test/steps/test_tclstep.py +++ b/test/steps/test_tclstep.py @@ -186,8 +186,8 @@ def get_script_path(self): assert ( env["STEP_DIR"] == TclStepTest.step_dir ), "Wrong prepared env. Bad STEP_DIR value" - assert env["CURRENT_NETLIST"] == "abc", "Wrong prepared env. Bad CURRENT_ input" - assert "SAVE_NETLIST" in env, "Wrong prepared env. SAVE_NETLIST missing" + assert env["CURRENT_NL"] == "abc", "Wrong prepared env. Bad CURRENT_ input" + assert "SAVE_NL" in env, "Wrong prepared env. SAVE_NL missing" for var in mock_config: if mock_config[var] is not None: assert var in env, "Wrong prepared env. Missing config variable"