Source code for fabulous.tools.opensta

"""OpenSTA tool wrapper: static timing analysis of a gate-level netlist.

Used as a singleton through classmethods (`OpenStaTool.analyze(...)`,
`OpenStaTool.run(...)`); never instantiated. Produces an SDF file for
back-annotated timing.
"""

import tempfile
from pathlib import Path

from loguru import logger

from fabulous.fabulous_settings import get_context
from fabulous.tools.tool import Tool


[docs] class OpenStaTool(Tool): """Static timing analysis wrapper backed by OpenSTA. Runs timing analysis on a synthesized netlist and writes an SDF file. Used as a singleton: call the classmethods directly, never instantiate. """ @classmethod
[docs] def executable(cls) -> Path | str: """Return the OpenSTA executable from the FABulous context. Returns ------- Path | str The configured OpenSTA executable. """ return get_context().opensta_path
@classmethod
[docs] def analyze( cls, verilog_netlist: Path, liberty_files: list[Path] | Path, top_name: str, spef_files: list[Path] | Path | None = None, ) -> Path: """Generate an SDF file from a Verilog gate-level netlist and return it. Parameters ---------- verilog_netlist : Path The gate-level netlist to analyze. liberty_files : list[Path] | Path The Liberty timing model file(s) to use for analysis. top_name : str The name of the top-level design to analyze. spef_files : list[Path] | Path | None The SPEF RC extraction file(s) to use, or None. Returns ------- Path Path to the temporary SDF file. Raises ------ RuntimeError If the SDF file is empty after running OpenSTA. """ sdf_path: Path = Path(tempfile.gettempdir()) / f"sta_{top_name}_tmp.sdf" liberty_list = ( [liberty_files] if isinstance(liberty_files, Path) else list(liberty_files) ) if spef_files is None: spef_list = None elif isinstance(spef_files, Path): spef_list = [spef_files] else: spef_list = list(spef_files) script: str = cls.render_template( "opensta_sdf.j2", liberty_files=liberty_list, verilog_netlist=verilog_netlist, top_name=top_name, spef_files=spef_list, sdf_path=sdf_path, ) logger.debug(f"Generating SDF file at temporary path: {sdf_path}") cls.run(stdin_data=script) content: str = sdf_path.read_text() if not content: sdf_path.unlink() raise RuntimeError( "Failed to generate SDF file using OpenSTA. No content in SDF file." ) return sdf_path
@classmethod
[docs] def clean_up(cls, sdf_file: Path) -> None: """Delete the temporary SDF file produced by `analyze`. Parameters ---------- sdf_file : Path The SDF file returned by `analyze`. """ if sdf_file.exists(): logger.debug(f"Cleaning up temporary SDF file at: {sdf_file}") sdf_file.unlink()