diff --git a/hammer/config/defaults.yml b/hammer/config/defaults.yml index 08d2bded6..80b6dd278 100644 --- a/hammer/config/defaults.yml +++ b/hammer/config/defaults.yml @@ -92,6 +92,12 @@ vlsi.technology: # the extracted contents of foobar.tar.gz. # If this is not specified, then the tarballs will be extracted to obj//extracted/. + timing_lib_pref: "NLDM" + # Select a timing lib preference, available options include: + # NLDM, ECSM, and CCS (lower or upper case acceptable). + # If no preference is specified, then the following preference order is followed: + # NLDM -> ECSM -> CCS + # General VLSI inputs. # These will vary per run of hammer-vlsi. vlsi.inputs: diff --git a/hammer/config/defaults_types.yml b/hammer/config/defaults_types.yml index d0f825f16..1b98ec070 100644 --- a/hammer/config/defaults_types.yml +++ b/hammer/config/defaults_types.yml @@ -63,6 +63,9 @@ vlsi.technology: # Path where tarballs have been extracted. extracted_tarballs_dir: Optional[str] + # A preference for the timing lib. + timing_lib_pref: str + # General VLSI inputs. # These will vary per run of hammer-vlsi. vlsi.inputs: diff --git a/hammer/tech/__init__.py b/hammer/tech/__init__.py index a59236cb9..8763ef2be 100644 --- a/hammer/tech/__init__.py +++ b/hammer/tech/__init__.py @@ -1258,6 +1258,45 @@ def paths_func(lib: Library) -> List[str]: is_file=True ) + def get_timing_lib_with_preference(self, lib_pref: str = "NLDM") -> LibraryFilter: + """ + Select ASCII .lib timing libraries. Prefers NLDM, then ECSM, then CCS if multiple are present for + a single given .lib. + """ + lib_pref = lib_pref.upper() + + def paths_func(lib: Library) -> List[str]: + pref_list = ["NLDM", "ECSM", "CCS"] + index = None + + try: + index = pref_list.index(lib_pref) + except: + raise ValueError("Library preference must be one of NLDM, ECSM, or CCS.") + pref_list.insert(0, pref_list.pop(index)) + + for elem in pref_list: + if elem == "NLDM": + if lib.nldm_liberty_file is not None: + return [lib.nldm_liberty_file] + elif elem == "ECSM": + if lib.ecsm_liberty_file is not None: + return [lib.ecsm_liberty_file] + elif elem == "CCS": + if lib.ccs_liberty_file is not None: + return [lib.ccs_liberty_file] + else: + pass + + return [] + + return LibraryFilter( + tag="timing_lib_with_nldm", + description="ECSM/CCS/NLDM timing lib (liberty ASCII .lib)", + paths_func=paths_func, + is_file=True + ) + @property def qrc_tech_filter(self) -> LibraryFilter: """ diff --git a/tests/test_tool.py b/tests/test_tool.py index a3c8d56b4..15aed2d62 100644 --- a/tests/test_tool.py +++ b/tests/test_tool.py @@ -3,6 +3,7 @@ from typing import List, Dict, Any import sys from pathlib import Path +from abc import ABCMeta, abstractmethod import pytest @@ -69,6 +70,141 @@ def test_tool_format(lib, filt) -> List[str]: "drink {0}/orange".format(tech_dir) ] + def test_timing_lib_with_preference_filter(self, tmp_path, request) -> None: + """ + Test that the library preference filter works as expected. + """ + import hammer.config as hammer_config + + tech_dir_base = str(tmp_path) + tech_name = request.function.__name__ # create unique technology folders for each test + tech_dir = HammerToolTestHelpers.create_tech_dir(tech_dir_base, tech_name) + tech_json_filename = os.path.join(tech_dir, f"{tech_name}.tech.json") + tech_json = { + "name": f"{tech_name}", + "libraries": [ + { + "ecsm_liberty_file": "eggs.ecsm", + "ccs_liberty_file": "eggs.ccs", + "nldm_liberty_file": "eggs.nldm" + }, + { + "ccs_liberty_file": "custard.ccs", + "nldm_liberty_file": "custard.nldm" + }, + { + "nldm_liberty_file": "noodles.nldm" + }, + { + "ecsm_liberty_file": "eggplant.ecsm" + }, + { + "ccs_liberty_file": "cookies.ccs" + } + ] + + } + with open(tech_json_filename, "w") as f: + f.write(json.dumps(tech_json, cls=HammerJSONEncoder, indent=4)) + (Path(tech_dir) / "eggs.ecsm").write_text("eggs ecsm") + (Path(tech_dir) / "eggs.ccs").write_text("eggs ccs") + (Path(tech_dir) / "eggs.nldm").write_text("eggs nldm") + (Path(tech_dir) / "custard.ccs").write_text("custard ccs") + (Path(tech_dir) / "custard.nldm").write_text("custard nldm") + (Path(tech_dir) / "noodles.nldm").write_text("noodles nldm") + (Path(tech_dir) / "eggplant.ecsm").write_text("eggplant ecsm") + (Path(tech_dir) / "cookies.ccs").write_text("cookies ccs") + + sys.path.append(tech_dir_base) + tech = self.get_tech(hammer_tech.HammerTechnology.load_from_module(tech_name)) + tech.cache_dir = tech_dir + + logger = HammerVLSILogging.context("") + logger.logging_class.clear_callbacks() + tech.logger = logger + + class Tool(hammer_vlsi.DummyHammerTool, metaclass=ABCMeta): + + def __init__(self, removal=False): + self.geek = "GeekforGeeks" + + + lib_outputs = [] # type: List[str] + @property + def steps(self) -> List[hammer_vlsi.HammerToolStep]: + return self.make_steps_from_methods([ + self.step_one, + self.step_two, + self.step_three + ]) + + # Test default NLDM preference. + def step_one(self) -> bool: + Tool.lib_outputs = tech.read_libs([hammer_tech.filters.get_timing_lib_with_preference()], + hammer_tech.HammerTechnologyUtils.to_plain_item, + must_exist=False) + return True + + # Test lower case key input. + def step_two(self) -> bool: + Tool.lib_outputs = tech.read_libs([hammer_tech.filters.get_timing_lib_with_preference("ecsm")], + hammer_tech.HammerTechnologyUtils.to_plain_item, + must_exist=False) + return True + + def step_three(self) -> bool: + Tool.lib_outputs = tech.read_libs([hammer_tech.filters.get_timing_lib_with_preference("CCS")], + hammer_tech.HammerTechnologyUtils.to_plain_item, + must_exist=False) + return True + + test = Tool() + test.logger = HammerVLSILogging.context("") + test.run_dir = str(tmp_path / "rundir") + test.technology = tech + test.set_database(hammer_config.HammerDatabase()) + tech.set_database(hammer_config.HammerDatabase()) + + # Test the default case: + test.run(hook_actions=[ + hammer_vlsi.HammerTool.make_removal_hook("step_two"), + hammer_vlsi.HammerTool.make_removal_hook("step_three")]) + + assert set(Tool.lib_outputs) == { + "{0}/eggs.nldm".format(tech_dir), + "{0}/custard.nldm".format(tech_dir), + "{0}/noodles.nldm".format(tech_dir), + "{0}/eggplant.ecsm".format(tech_dir), + "{0}/cookies.ccs".format(tech_dir) + } + + # Test the lower case input key and non-default ECSM preference. + test.run(hook_actions=[ + hammer_vlsi.HammerTool.make_removal_hook("step_one"), + hammer_vlsi.HammerTool.make_removal_hook("step_three")]) + + assert set(Tool.lib_outputs) == { + "{0}/eggs.ecsm".format(tech_dir), + "{0}/custard.nldm".format(tech_dir), + "{0}/noodles.nldm".format(tech_dir), + "{0}/eggplant.ecsm".format(tech_dir), + "{0}/cookies.ccs".format(tech_dir) + } + + # Test the non-default CCS preference. + test.run(hook_actions=[ + hammer_vlsi.HammerTool.make_removal_hook("step_one"), + hammer_vlsi.HammerTool.make_removal_hook("step_two")]) + + assert set(Tool.lib_outputs) == { + "{0}/eggs.ccs".format(tech_dir), + "{0}/custard.ccs".format(tech_dir), + "{0}/noodles.nldm".format(tech_dir), + "{0}/eggplant.ecsm".format(tech_dir), + "{0}/cookies.ccs".format(tech_dir) + } + + def test_timing_lib_ecsm_filter(self, tmp_path, request) -> None: """ Test that the ECSM-first filter works as expected.