diff --git a/hammer/par/openroad/__init__.py b/hammer/par/openroad/__init__.py index dfe3085c4..e19bad778 100644 --- a/hammer/par/openroad/__init__.py +++ b/hammer/par/openroad/__init__.py @@ -1448,7 +1448,12 @@ def write_gds(self) -> str: ilm_gds = list(map(lambda ilm: ilm.gds, self.get_input_ilms())) gds_files.extend(ilm_gds) - layer_map_file=self.get_setting('par.inputs.gds_map_file') + layer_map_file=self.get_gds_map_file() + if layer_map_file is None: + self.logger.error("Must have GDS layer map for macro PG library generation! Skipping.") + return True + else: + assert isinstance(layer_map_file, str) # the first entry of $KLAYOUT_PATH will be the one where the configuration is stored when KLayout exits # otherwise KLayout tries to write everything to the same directory as the klayout binary and throws an error if it is not writeable diff --git a/hammer/tech/__init__.py b/hammer/tech/__init__.py index 5044745de..f93d0a2b0 100644 --- a/hammer/tech/__init__.py +++ b/hammer/tech/__init__.py @@ -353,8 +353,14 @@ def __init__(self): self.time_unit: Optional[str] = None self.cap_unit: Optional[str] = None + @abstractmethod + def gen_config(self) -> None: + """For subclasses to set self.config directly, instead of from static JSON file""" + pass + @classmethod - def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]: + #def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]: + def load_from_module(cls, tech_module: str) -> "HammerTechnology": """Load a technology from a given module. :param tech_module: Technology module (e.g. "hammer.technology.asap7") @@ -366,17 +372,21 @@ def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]: tech.name = technology_name tech.package = tech_module + #tech.config = tech.gen_config() tech_json = importlib.resources.files(tech_module) / f"{technology_name}.tech.json" tech_yaml = importlib.resources.files(tech_module) / f"{technology_name}.tech.yml" + #if tech.config is not None: # pydantic model already created + # print("Tech gen") + # return tech if tech_json.is_file(): tech.config = TechJSON.model_validate_json(tech_json.read_text()) return tech elif tech_yaml.is_file(): tech.config = TechJSON.model_validate_json(json.dumps(load_yaml(tech_yaml.read_text()))) return tech - else: #TODO - from Pydantic model instance - return None + else: # Assume tech implents gen_config() + return tech def get_lib_units(self) -> None: """ diff --git a/hammer/technology/sky130/__init__.py b/hammer/technology/sky130/__init__.py index 35528bc7b..71a40c9e8 100644 --- a/hammer/technology/sky130/__init__.py +++ b/hammer/technology/sky130/__init__.py @@ -9,9 +9,13 @@ from typing import NamedTuple, List, Optional, Tuple, Dict, Set, Any import importlib import json +import functools -import hammer.tech -from hammer.tech import HammerTechnology +#import hammer.tech +from hammer.config import HammerDatabase +from hammer.tech import * +from hammer.tech.stackup import * +from hammer.tech.specialcells import * from hammer.vlsi import HammerTool, HammerPlaceAndRouteTool, TCLTool, HammerDRCTool, HammerLVSTool, \ HammerToolHookAction, HierarchicalMode @@ -23,6 +27,280 @@ class SKY130Tech(HammerTechnology): Override the HammerTechnology used in `hammer_tech.py` This class is loaded by function `load_from_json`, and will pass the `try` in `importlib`. """ + def gen_config(self) -> None: + """Generate the tech config, based on the library type selected""" + slib = self.get_setting("technology.sky130.stdcell_library") + SKY130A = self.get_setting("technology.sky130.sky130A") + + # Common tech LEF and IO cell spice netlists + libs = [ + Library(lef_file = "cache/sky130_fd_sc_hd__nom.tlef", verilog_sim = "cache/primitives.v", provides = [Provide(lib_type = "technology")]), + Library(spice_file = "$SKY130A/libs.ref/sky130_fd_io/spice/sky130_ef_io__analog.spice", provides = [Provide(lib_type = "IO library")]) + ] + # Generate IO cells + library='sky130_fd_io' + SKYWATER_LIBS = os.path.join('$SKY130A', 'libs.ref', library) + LIBRARY_PATH = os.path.join( SKY130A, 'libs.ref', library, 'lib') + lib_corner_files=os.listdir(LIBRARY_PATH) + lib_corner_files.sort() + for cornerfilename in lib_corner_files: + # Skip versions with no internal power + if ('nointpwr' in cornerfilename) : continue + + tmp = cornerfilename.replace('.lib','') + # Split into cell, and corner strings + # Resulting list if only one ff/ss/tt in name: [, , , , ] + # Resulting list if ff_ff/ss_ss/tt_tt in name: [, , , , '', , , , ] + split_cell_corner = re.split('_(ff)|_(ss)|_(tt)', tmp) + cell_name = split_cell_corner[0] + process = split_cell_corner[1:-1] + temp_volt = split_cell_corner[-1].split('_')[1:] + + # Filter out cross corners (e.g ff_ss or ss_ff) + if len(process) > 3: + if not functools.reduce(lambda x,y: x and y, map(lambda p,q: p==q, process[0:3], process[4:]), True): + continue + # Determine actual corner + speed = next(c for c in process if c is not None).replace('_','') + if (speed == 'ff'): speed = 'fast' + if (speed == 'tt'): speed = 'typical' + if (speed == 'ss'): speed = 'slow' + + temp = temp_volt[0] + temp = temp.replace('n','-') + temp = temp.split('C')[0]+' C' + + vdd = ('.').join(temp_volt[1].split('v')) + ' V' + # Filter out IO/analog voltages that are not high voltage + if temp_volt[2].startswith('1'): continue + if len(temp_volt) == 4: + if temp_volt[3].startswith('1'): continue + + # gpiov2_pad_wrapped has separate GDS + if cell_name == 'sky130_ef_io__gpiov2_pad_wrapped': + file_lib = 'sky130_ef_io' + gds_file = cell_name + '.gds' + lef_file = 'cache/sky130_ef_io.lef' + spice_file = os.path.join(SKYWATER_LIBS,'cdl', file_lib + '.cdl') + elif 'sky130_ef_io' in cell_name: + file_lib = 'sky130_ef_io' + gds_file = file_lib + '.gds' + lef_file = 'cache/' + file_lib + '.lef' + spice_file = os.path.join(SKYWATER_LIBS,'cdl', file_lib + '.cdl') + else: + file_lib = library + gds_file = file_lib + '.gds' + lef_file = os.path.join(SKYWATER_LIBS,'lef', file_lib + '.lef') + spice_file = os.path.join(SKYWATER_LIBS,'spice', file_lib + '.spice') + + lib_entry = Library( + nldm_liberty_file = os.path.join(SKYWATER_LIBS,'lib', cornerfilename), + verilog_sim = os.path.join(SKYWATER_LIBS,'verilog', file_lib + '.v'), + lef_file = lef_file, + spice_file = spice_file, + gds_file = os.path.join(SKYWATER_LIBS,'gds', gds_file), + corner = Corner( + nmos = speed, + pmos = speed, + temperature = temp + ), + supplies = Supplies( + VDD = vdd, + GND ="0 V" + ), + provides = [Provide( + lib_type = cell_name, + vt = "RVT" + ) + ] + ) + libs.append(lib_entry) + + # Stdcell library-dependent lists + stackups = [] # type: List[Stackup] + phys_only = [] # type: List[Cell] + dont_use = [] # type: List[Cell] + spcl_cells = [] # type: List[SpecialCell] + + # Select standard cell libraries + if slib == "sky130_fd_sc_hd": + phys_only = [ + "sky130_fd_sc_hd__tap_1", "sky130_fd_sc_hd__tap_2", "sky130_fd_sc_hd__tapvgnd_1", "sky130_fd_sc_hd__tapvpwrvgnd_1", + "sky130_fd_sc_hd__fill_1", "sky130_fd_sc_hd__fill_2", "sky130_fd_sc_hd__fill_4", "sky130_fd_sc_hd__fill_8", + "sky130_fd_sc_hd__diode_2"] + dont_use = [ + "*sdf*", + "sky130_fd_sc_hd__probe_p_*", + "sky130_fd_sc_hd__probec_p_*" + ] + spcl_cells = [ + SpecialCell(cell_type = "tiehilocell", name = ["sky130_fd_sc_hd__conb_1"]), + SpecialCell(cell_type = "tiehicell", name = ["sky130_fd_sc_hd__conb_1"], output_ports = ["HI"]), + SpecialCell(cell_type = "tielocell", name = ["sky130_fd_sc_hd__conb_1"], output_ports = ["LO"]), + SpecialCell(cell_type = "endcap", name = ["sky130_fd_sc_hd__tap_1"]), + SpecialCell(cell_type = "tapcell", name = ["sky130_fd_sc_hd__tapvpwrvgnd_1"]), + SpecialCell(cell_type = "stdfiller", name = ["sky130_fd_sc_hd__fill_1", "sky130_fd_sc_hd__fill_2", "sky130_fd_sc_hd__fill_4", "sky130_fd_sc_hd__fill_8"]), + SpecialCell(cell_type = "decap", name = ["sky130_fd_sc_hd__decap_3", "sky130_fd_sc_hd__decap_4", "sky130_fd_sc_hd__decap_6", "sky130_fd_sc_hd__decap_8", "sky130_fd_sc_hd__decap_12"]), + SpecialCell(cell_type = "driver", name = ["sky130_fd_sc_hd__buf_4"], input_ports = ["A"], output_ports = ["X"]), + SpecialCell(cell_type = "ctsbuffer", name = ["sky130_fd_sc_hd__clkbuf_1"]) + ] + + # Generate standard cell library + library=slib + + SKYWATER_LIBS = os.path.join('$SKY130A', 'libs.ref', library) + LIBRARY_PATH = os.path.join( SKY130A, 'libs.ref', library, 'lib') + lib_corner_files=os.listdir(LIBRARY_PATH) + lib_corner_files.sort() + for cornerfilename in lib_corner_files: + if (not (library in cornerfilename) ) : continue + if ('ccsnoise' in cornerfilename): continue # ignore duplicate corner.lib/corner_ccsnoise.lib files + + tmp = cornerfilename.replace('.lib','') + if (tmp+'_ccsnoise.lib' in lib_corner_files): + cornerfilename=tmp+'_ccsnoise.lib' # use ccsnoise version of lib file + + cornername = tmp.split('__')[1] + cornerparts = cornername.split('_') + + speed = cornerparts[0] + if (speed == 'ff'): speed = 'fast' + if (speed == 'tt'): speed = 'typical' + if (speed == 'ss'): speed = 'slow' + + temp = cornerparts[1] + temp = temp.replace('n','-') + temp = temp.split('C')[0]+' C' + + vdd = cornerparts[2] + vdd = vdd.split('v')[0]+'.'+vdd.split('v')[1]+' V' + + lib_entry = Library( + nldm_liberty_file = os.path.join(SKYWATER_LIBS,'lib', cornerfilename), + verilog_sim = os.path.join('cache', library+'.v'), + lef_file = os.path.join(SKYWATER_LIBS,'lef', library+'.lef'), + spice_file = os.path.join('cache', library+'.cdl'), + gds_file = os.path.join(SKYWATER_LIBS,'gds', library+'.gds'), + corner = Corner( + nmos = speed, + pmos = speed, + temperature = temp + ), + supplies = Supplies( + VDD = vdd, + GND = "0 V" + ), + provides = [Provide( + lib_type = "stdcell", + vt = "RVT" + ) + ] + ) + + libs.append(lib_entry) + + # Generate stackup + metals = [] #type: List[Metal] + def is_float(string): + try: + float(string) + return True + except ValueError: + return False + + def get_min_from_line(line): + words = line.split() + nums = [float(w) for w in words if is_float(w)] + return min(nums) + + tlef_path = os.path.join(SKY130A, 'libs.ref', library, 'techlef', f"{library}__min.tlef") + with open(tlef_path, 'r') as f: + metal_name = None + metal_index = 0 + lines = f.readlines() + idx = -1 + while idx < len(lines): + idx += 1 + if idx == len(lines) - 1: break + line = lines[idx] + if '#' in line: line = line[:line.index('#')] + words = line.split() + if line.startswith('LAYER') and len(words) > 1: + if words[1].startswith('li') or words[1].startswith('met'): + metal_name = words[1] + metal_index += 1 + metal = {} + metal["name"] = metal_name + metal["index"] = metal_index + + if metal_name is not None: + line = line.strip() + if line.startswith("DIRECTION"): + metal["direction"] = words[1].lower() + if line.startswith("PITCH"): + metal["pitch"] = get_min_from_line(line) + if line.startswith("OFFSET"): + metal["offset"] = get_min_from_line(line) + if line.startswith("WIDTH"): + metal["min_width"] = get_min_from_line(line) + if line.startswith("SPACINGTABLE"): + metal["power_strap_widths_and_spacings"] = [] + while ';' not in line: + idx += 1 + if idx == len(lines) - 1: break + line = lines[idx].strip() + if '#' in line: line = line[:line.index('#')] + words = line.split() + d = {} + if line.startswith("WIDTH"): + d["width_at_least"] = float(words[1]) + d["min_spacing"] = float(words[2]) + metal["power_strap_widths_and_spacings"].append(d.copy()) + if line.startswith("END"): + metal["grid_unit"] = 0.001 + metals.append(Metal.model_validate(metal.copy())) + metal_name = None + stackups.append(Stackup(name = slib, grid_unit = Decimal("0.001"), metals = metals)) + + elif slib == "sky130_scl": + raise NotImplementedError(f"sky130_scl not yet supported") + else: + raise ValueError(f"Incorrect standard cell library selection: {slib}") + + self.config = TechJSON( + name = "Skywater 130nm Library", + grid_unit = "0.001", + shrink_factor = None, + installs = [ + PathPrefix(id = "$SKY130_NDA", path = "technology.sky130.sky130_nda"), + PathPrefix(id = "$SKY130A", path = "technology.sky130.sky130A"), + PathPrefix(id = "$SKY130_CDS", path = "technology.sky130.sky130_cds") + ], + libraries = libs, + gds_map_file = "extra/sky130_lefpin.map", + physical_only_cells_list = phys_only, + dont_use_list = dont_use, + drc_decks = [ + DRCDeck(tool_name = "calibre", deck_name = "calibre_drc", path = "$SKY130_NDA/s8/V2.0.1/DRC/Calibre/s8_drcRules"), + DRCDeck(tool_name = "klayout", deck_name = "klayout_drc", path = "$SKY130A/libs.tech/klayout/drc/sky130A.lydrc"), + DRCDeck(tool_name = "pegasus", deck_name = "pegasus_drc", path = "$SKY130_CDS/Sky130_DRC/sky130_rev_0.0_1.0.drc.pvl") + ], + additional_drc_text = "", + lvs_decks = [ + LVSDeck(tool_name = "calibre", deck_name = "calibre_lvs", path = "$SKY130_NDA/s8/V2.0.1/LVS/Calibre/lvsRules_s8"), + LVSDeck(tool_name = "pegasus", deck_name = "pegasus_lvs", path = "$SKY130_CDS/Sky130_LVS/Sky130_rev_0.0_0.1.lvs.pvl") + ], + additional_lvs_text = "", + tarballs = None, + sites = [ + Site(name = "unithd", x = Decimal("0.46"), y = Decimal("2.72")), + Site(name = "unithddbl", x = Decimal("0.46"), y = Decimal("5.44")) + ], + stackups = stackups, + special_cells = spcl_cells, + extra_prefixes = None + ) + def post_install_script(self) -> None: self.library_name = 'sky130_fd_sc_hd' # check whether variables were overriden to point to a valid path @@ -94,7 +372,7 @@ def setup_verilog(self) -> None: line = line.replace('wire 1','// wire 1') line = line.replace('`endif SKY130_FD_SC_HD__LPFLOW_BLEEDER_FUNCTIONAL_V','`endif // SKY130_FD_SC_HD__LPFLOW_BLEEDER_FUNCTIONAL_V') df.write(line) - + # Additionally hack out the specifies sl = [] with open(dest_path, 'r') as sf: @@ -127,7 +405,7 @@ def setup_verilog(self) -> None: for list_idx, pattern_tuple in enumerate(pattern_idx): if list_idx != len(pattern_idx)-1: search_range = range(pattern_tuple[0]+1, pattern_idx[list_idx+1][0]) - else: + else: search_range = range(pattern_tuple[0]+1, len(sl)) for idx in search_range: list = re.findall(capture_pattern, sl[idx]) @@ -135,8 +413,8 @@ def setup_verilog(self) -> None: if elem != pattern_tuple[1]: sl[idx] = sl[idx].replace(elem, pattern_tuple[1]) self.logger.info(f"Incorrect reference `{elem}` to be replaced with: `{pattern_tuple[1]}` on raw line {idx}.") - - # Write back into destination + + # Write back into destination with open(dest_path, 'w') as df: df.writelines(sl) @@ -234,7 +512,7 @@ def setup_io_lefs(self) -> None: # Fix if idx_end_broken_macro < idx_start_next_macro: sl[idx_end_broken_macro] = end_fixed_macro - + df.writelines(sl) def get_tech_par_hooks(self, tool_name: str) -> List[HammerToolHookAction]: diff --git a/hammer/technology/sky130/defaults.yml b/hammer/technology/sky130/defaults.yml index 59bff2f12..5de945ec9 100644 --- a/hammer/technology/sky130/defaults.yml +++ b/hammer/technology/sky130/defaults.yml @@ -32,7 +32,8 @@ technology.sky130: io_file: "extra/efabless_template.io" # IO ring - take this template and modify for your own use io_file_meta: prependlocal - + + stdcell_library: "sky130_fd_sc_hd" # Choose between "sky130_fd_sc_hd" (open-source) or "sky130_scl" (Cadence) mentor.extra_env_vars_meta: lazydeepsubst # Mentor environment variables # Override this in project @@ -109,9 +110,6 @@ synthesis.yosys: par.inputs: gds_merge: true - gds_map_mode: manual - gds_map_file: "extra/sky130_lefpin.map" - gds_map_file_meta: prependlocal par.openroad: # openroad setup/files setrc_file: "extra/setRC.tcl" diff --git a/hammer/technology/sky130/defaults_types.yml b/hammer/technology/sky130/defaults_types.yml index f46c2e6f7..c3510631e 100644 --- a/hammer/technology/sky130/defaults_types.yml +++ b/hammer/technology/sky130/defaults_types.yml @@ -27,3 +27,6 @@ technology.sky130: # Path to IO file io_file: str + + # Choose between "sky130_fd_sc_hd" (open-source) or "sky130_scl" (Cadence) + stdcell_library: str diff --git a/hammer/technology/sky130/extra/sky130-tech-gen-files/beginning.json b/hammer/technology/sky130/extra/sky130-tech-gen-files/beginning.json deleted file mode 100644 index 7fbc7cd3c..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen-files/beginning.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Skywater 130nm Library", - "grid_unit": "0.001", - "installs": [ - { - "id": "$SKY130A", - "path": "technology.sky130.sky130A" - } - ], - "libraries": [ - { - "lef_file": "tech-sky130-cache/sky130_fd_sc_hd__nom.tlef", - "verilog_sim": "tech-sky130-cache/primitives.v", - "provides": [ - { - "lib_type": "technology" - } - ] - } - ] -} diff --git a/hammer/technology/sky130/extra/sky130-tech-gen-files/beginning_nda.json b/hammer/technology/sky130/extra/sky130-tech-gen-files/beginning_nda.json deleted file mode 100644 index 2d248220c..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen-files/beginning_nda.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "Skywater 130nm Library", - "grid_unit": "0.001", - "installs": [ - { - "id": "$SKY130_NDA", - "path": "technology.sky130.sky130_nda" - }, - { - "id": "$SKY130A", - "path": "technology.sky130.sky130A" - } - ], - "layer_map_file": "$SKY130_NDA/s8/V2.0.1/VirtuosoOA/libs/technology_library/technology_library.layermap", - "drc_decks": [ - { - "tool_name": "calibre", - "deck_name": "calibre_drc", - "path": "$SKY130_NDA/s8/V2.0.1/DRC/Calibre/s8_drcRules" - }, - { - "tool_name": "klayout", - "deck_name": "klayout_drc", - "path": "$SKY130A/libs.tech/klayout/drc/sky130A.lydrc" - } - ], - "additional_drc_text": "", - "lvs_decks": [ - { - "tool_name": "calibre", - "deck_name": "calibre_lvs", - "old_path": "$SKY130_NDA/s8/V2.0.1/LVS/Calibre/lvsRules_s8", - "path": "cache/lvsControlFile_s8" - } - ], - "additional_lvs_text": "", - "libraries": [ - { - "lef_file": "cache/sky130_fd_sc_hd__nom.tlef", - "verilog_sim": "cache/primitives.v", - "provides": [ - { - "lib_type": "technology" - } - ] - } - ] -} diff --git a/hammer/technology/sky130/extra/sky130-tech-gen-files/cells.json b/hammer/technology/sky130/extra/sky130-tech-gen-files/cells.json deleted file mode 100644 index 440b8afcf..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen-files/cells.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "physical_only_cells_list": [ - "sky130_fd_sc_hd__tap_1", "sky130_fd_sc_hd__tap_2", "sky130_fd_sc_hd__tapvgnd_1", "sky130_fd_sc_hd__tapvpwrvgnd_1", - "sky130_fd_sc_hd__fill_1", "sky130_fd_sc_hd__fill_2", "sky130_fd_sc_hd__fill_4", "sky130_fd_sc_hd__fill_8", - "sky130_fd_sc_hd__diode_2" - ], - "dont_use_list": [ - "*sdf*", - "sky130_fd_sc_hd__probe_p_*", - "sky130_fd_sc_hd__probec_p_*" - ], - "special_cells": [ - { - "cell_type": "tiehilocell", - "name": ["sky130_fd_sc_hd__conb_1"] - }, - { - "cell_type": "tiehicell", - "name": ["sky130_fd_sc_hd__conb_1"], - "output_ports": ["HI"] - }, - { - "cell_type": "tielocell", - "name": ["sky130_fd_sc_hd__conb_1"], - "output_ports": ["LO"] - }, - { - "cell_type": "endcap", - "name": ["sky130_fd_sc_hd__tap_1"] - }, - { - "cell_type": "tapcell", - "name": ["sky130_fd_sc_hd__tapvpwrvgnd_1"] - }, - { - "cell_type": "stdfiller", - "name": ["sky130_fd_sc_hd__fill_1", "sky130_fd_sc_hd__fill_2", "sky130_fd_sc_hd__fill_4", "sky130_fd_sc_hd__fill_8"] - }, - { - "cell_type": "decap", - "name": ["sky130_fd_sc_hd__decap_3", "sky130_fd_sc_hd__decap_4", "sky130_fd_sc_hd__decap_6", "sky130_fd_sc_hd__decap_8", "sky130_fd_sc_hd__decap_12"] - }, - { - "cell_type": "driver", - "name": ["sky130_fd_sc_hd__buf_4"], - "input_ports": ["A"], - "output_ports": ["X"] - }, - { - "cell_type": "ctsbuffer", - "name": ["sky130_fd_sc_hd__clkbuf_1"] - } - ] -} \ No newline at end of file diff --git a/hammer/technology/sky130/extra/sky130-tech-gen-files/sites.json b/hammer/technology/sky130/extra/sky130-tech-gen-files/sites.json deleted file mode 100644 index e243250cb..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen-files/sites.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sites": [ - {"name": "unithd", "x": 0.46, "y": 2.72}, - {"name": "unithddbl", "x": 0.46, "y": 5.44} - ] -} \ No newline at end of file diff --git a/hammer/technology/sky130/extra/sky130-tech-gen-files/stackups-gen.py b/hammer/technology/sky130/extra/sky130-tech-gen-files/stackups-gen.py deleted file mode 100755 index b6abc3511..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen-files/stackups-gen.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -# type: ignore -# tell mypy to ignore this file during typechecking -# -*- coding: utf-8 -*- -# -# Generate Hammer Sky130 tech plugin file: sky130.tech.json -# -# See LICENSE for licence details. -import sys -import json -import os - -library='sky130_fd_sc_hd' - -def main(args) -> int: - if len(args) != 3: - print("Usage: ./stackups-gen.py /path/to/sky130A stackups.json") - return 1 - - SKY130A = sys.argv[1] - - stackup = {} - stackup["name"] = library - stackup["grid_unit"] = 0.001 - stackup["metals"] = [] - - def is_float(string): - try: - float(string) - return True - except ValueError: - return False - - def get_min_from_line(line): - words = line.split() - nums = [float(w) for w in words if is_float(w)] - return min(nums) - - - tlef_path = os.path.join(SKY130A, 'libs.ref', library, 'techlef', f"{library}__min.tlef") - with open(tlef_path, 'r') as f: - metal_name = None - metal_index = 0 - lines = f.readlines() - idx = -1 - while idx < len(lines): - idx += 1 - if idx == len(lines) - 1: break - line = lines[idx] - if '#' in line: line = line[:line.index('#')] - words = line.split() - if line.startswith('LAYER') and len(words) > 1: - if words[1].startswith('li') or words[1].startswith('met'): - metal_name = words[1] - metal_index += 1 - metal = {} - metal["name"] = metal_name - metal["index"] = metal_index - - if metal_name is not None: - line = line.strip() - if line.startswith("DIRECTION"): - metal["direction"] = words[1].lower() - if line.startswith("PITCH"): - metal["pitch"] = get_min_from_line(line) - if line.startswith("OFFSET"): - metal["offset"] = get_min_from_line(line) - if line.startswith("WIDTH"): - metal["min_width"] = get_min_from_line(line) - if line.startswith("SPACINGTABLE"): - metal["power_strap_widths_and_spacings"] = [] - while ';' not in line: - idx += 1 - if idx == len(lines) - 1: break - line = lines[idx].strip() - if '#' in line: line = line[:line.index('#')] - words = line.split() - d = {} - if line.startswith("WIDTH"): - d["width_at_least"] = float(words[1]) - d["min_spacing"] = float(words[2]) - metal["power_strap_widths_and_spacings"].append(d.copy()) - if line.startswith("END"): - metal["grid_unit"] = 0.001 - stackup["metals"].append(metal.copy()) - metal_name = None - - - with open(sys.argv[2], 'w') as f: - json.dump(stackup, f, indent=2) - - return 0 - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/hammer/technology/sky130/extra/sky130-tech-gen-files/stackups.json b/hammer/technology/sky130/extra/sky130-tech-gen-files/stackups.json deleted file mode 100644 index 576f61446..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen-files/stackups.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "name": "sky130_fd_sc_hd", - "grid_unit": 0.001, - "metals": [ - { - "name": "li1", - "index": 1, - "direction": "vertical", - "pitch": 0.34, - "offset": 0.17, - "min_width": 0.17, - "power_strap_widths_and_spacings": [ - { - "width_at_least": 0.0, - "min_spacing": 0.17 - } - ], - "grid_unit": 0.001 - }, - { - "name": "met1", - "index": 2, - "direction": "horizontal", - "pitch": 0.34, - "offset": 0.17, - "min_width": 0.14, - "power_strap_widths_and_spacings": [ - { - "width_at_least": 0.0, - "min_spacing": 0.14 - }, - { - "width_at_least": 3.0, - "min_spacing": 0.28 - } - ], - "grid_unit": 0.001 - }, - { - "name": "met2", - "index": 3, - "direction": "vertical", - "pitch": 0.46, - "offset": 0.23, - "min_width": 0.14, - "power_strap_widths_and_spacings": [ - { - "width_at_least": 0.0, - "min_spacing": 0.14 - }, - { - "width_at_least": 3.0, - "min_spacing": 0.28 - } - ], - "grid_unit": 0.001 - }, - { - "name": "met3", - "index": 4, - "direction": "horizontal", - "pitch": 0.68, - "offset": 0.34, - "min_width": 0.3, - "power_strap_widths_and_spacings": [ - { - "width_at_least": 0.0, - "min_spacing": 0.3 - }, - { - "width_at_least": 3.0, - "min_spacing": 0.4 - } - ], - "grid_unit": 0.001 - }, - { - "name": "met4", - "index": 5, - "direction": "vertical", - "pitch": 0.92, - "offset": 0.46, - "min_width": 0.3, - "power_strap_widths_and_spacings": [ - { - "width_at_least": 0.0, - "min_spacing": 0.3 - }, - { - "width_at_least": 3.0, - "min_spacing": 0.4 - } - ], - "grid_unit": 0.001 - }, - { - "name": "met5", - "index": 6, - "direction": "horizontal", - "pitch": 3.4, - "offset": 1.7, - "min_width": 1.6, - "power_strap_widths_and_spacings": [ - { - "width_at_least": 0.0, - "min_spacing": 1.6 - } - ], - "grid_unit": 0.001 - } - ] -} \ No newline at end of file diff --git a/hammer/technology/sky130/extra/sky130-tech-gen.py b/hammer/technology/sky130/extra/sky130-tech-gen.py deleted file mode 100755 index 19ddfe8ee..000000000 --- a/hammer/technology/sky130/extra/sky130-tech-gen.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -# type: ignore -# tell mypy to ignore this file during typechecking -# -*- coding: utf-8 -*- -# -# Generate Hammer Sky130 tech plugin file: sky130.tech.json -# -# See LICENSE for licence details. -import sys -import json -import os -import re -import functools - -use_nda_files=True - -def main(args) -> int: - if len(args) != 3: - print("Usage: ./sky130-tech-gen.py /path/to/sky130A sky130.tech.json") - return 1 - - SKY130A = sys.argv[1] - - if use_nda_files: - with open('sky130-tech-gen-files/beginning_nda.json', 'r') as f: data = json.load(f) - else: - with open('sky130-tech-gen-files/beginning.json', 'r') as f: data = json.load(f) - - with open('sky130-tech-gen-files/cells.json', 'r') as f: - cells = json.load(f) - data["physical_only_cells_list"] = cells["physical_only_cells_list"] - data["dont_use_list"] = cells["dont_use_list"] - data["special_cells"] = cells["special_cells"] - - # Standard cells - library='sky130_fd_sc_hd' - - SKYWATER_LIBS = os.path.join('$SKY130A', 'libs.ref', library) - LIBRARY_PATH = os.path.join( SKY130A, 'libs.ref', library, 'lib') - lib_corner_files=os.listdir(LIBRARY_PATH) - lib_corner_files.sort() - for cornerfilename in lib_corner_files: - if (not (library in cornerfilename) ) : continue - if ('ccsnoise' in cornerfilename): continue # ignore duplicate corner.lib/corner_ccsnoise.lib files - - tmp = cornerfilename.replace('.lib','') - if (tmp+'_ccsnoise.lib' in lib_corner_files): - cornerfilename=tmp+'_ccsnoise.lib' # use ccsnoise version of lib file - - cornername = tmp.split('__')[1] - cornerparts = cornername.split('_') - - speed = cornerparts[0] - if (speed == 'ff'): speed = 'fast' - if (speed == 'tt'): speed = 'typical' - if (speed == 'ss'): speed = 'slow' - - temp = cornerparts[1] - temp = temp.replace('n','-') - temp = temp.split('C')[0]+' C' - - vdd = cornerparts[2] - vdd = vdd.split('v')[0]+'.'+vdd.split('v')[1]+' V' - - lib_entry = { - "nldm_liberty_file": os.path.join(SKYWATER_LIBS,'lib', cornerfilename), - "verilog_sim": os.path.join('cache', library+'.v'), - "lef_file": os.path.join(SKYWATER_LIBS,'lef', library+'.lef'), - "spice_file": os.path.join('cache', library+'.cdl'), - "gds_file": os.path.join(SKYWATER_LIBS,'gds', library+'.gds'), - "corner": { - "nmos": speed, - "pmos": speed, - "temperature": temp - }, - "supplies": { - "VDD": vdd, - "GND": "0 V" - }, - "provides": [ - { - "lib_type": "stdcell", - "vt": "RVT" - } - ] - } - - data["libraries"].append(lib_entry) - - # IO cells - library='sky130_fd_io' - SKYWATER_LIBS = os.path.join('$SKY130A', 'libs.ref', library) - LIBRARY_PATH = os.path.join( SKY130A, 'libs.ref', library, 'lib') - lib_corner_files=os.listdir(LIBRARY_PATH) - lib_corner_files.sort() - for cornerfilename in lib_corner_files: - # Skip versions with no internal power - if ('nointpwr' in cornerfilename) : continue - - tmp = cornerfilename.replace('.lib','') - # Split into cell, and corner strings - # Resulting list if only one ff/ss/tt in name: [, , , , ] - # Resulting list if ff_ff/ss_ss/tt_tt in name: [, , , , '', , , , ] - split_cell_corner = re.split('_(ff)|_(ss)|_(tt)', tmp) - cell_name = split_cell_corner[0] - process = split_cell_corner[1:-1] - temp_volt = split_cell_corner[-1].split('_')[1:] - - # Filter out cross corners (e.g ff_ss or ss_ff) - if len(process) > 3: - if not functools.reduce(lambda x,y: x and y, map(lambda p,q: p==q, process[0:3], process[4:]), True): - continue - # Determine actual corner - speed = next(c for c in process if c is not None).replace('_','') - if (speed == 'ff'): speed = 'fast' - if (speed == 'tt'): speed = 'typical' - if (speed == 'ss'): speed = 'slow' - - temp = temp_volt[0] - temp = temp.replace('n','-') - temp = temp.split('C')[0]+' C' - - vdd = ('.').join(temp_volt[1].split('v')) + ' V' - # Filter out IO/analog voltages that are not high voltage - if temp_volt[2].startswith('1'): continue - if len(temp_volt) == 4: - if temp_volt[3].startswith('1'): continue - - # gpiov2_pad_wrapped has separate GDS - if cell_name == 'sky130_ef_io__gpiov2_pad_wrapped': - file_lib = 'sky130_ef_io' - gds_file = cell_name + '.gds' - lef_file = 'cache/sky130_ef_io.lef' - spice_file = os.path.join(SKYWATER_LIBS,'cdl', file_lib + '.cdl') - elif 'sky130_ef_io' in cell_name: - file_lib = 'sky130_ef_io' - gds_file = file_lib + '.gds' - lef_file = 'cache/' + file_lib + '.lef' - spice_file = os.path.join(SKYWATER_LIBS,'cdl', file_lib + '.cdl') - else: - file_lib = library - gds_file = file_lib + '.gds' - lef_file = os.path.join(SKYWATER_LIBS,'lef', file_lib + '.lef') - spice_file = os.path.join(SKYWATER_LIBS,'spice', file_lib + '.spice') - - lib_entry = { - "nldm_liberty_file": os.path.join(SKYWATER_LIBS,'lib', cornerfilename), - "verilog_sim": os.path.join(SKYWATER_LIBS,'verilog', file_lib + '.v'), - "lef_file": lef_file, - "spice_file": spice_file, - "gds_file": os.path.join(SKYWATER_LIBS,'gds', gds_file), - "corner": { - "nmos": speed, - "pmos": speed, - "temperature": temp - }, - "supplies": { - "VDD": vdd, - "GND": "0 V" - }, - "provides": [ - { - "lib_type": cell_name, - "vt": "RVT" - } - ] - } - - data["libraries"].append(lib_entry) - - with open('sky130-tech-gen-files/stackups.json', 'r') as f: - stackups = json.load(f) - data["stackups"] = [stackups] - - with open('sky130-tech-gen-files/sites.json', 'r') as f: - sites = json.load(f) - data["sites"] = sites["sites"] - - with open(sys.argv[2], 'w') as f: - json.dump(data, f, indent=2) - - return 0 - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/hammer/vlsi/driver.py b/hammer/vlsi/driver.py index 8b2862bdd..9a97c53da 100644 --- a/hammer/vlsi/driver.py +++ b/hammer/vlsi/driver.py @@ -65,7 +65,7 @@ def __init__(self, options: HammerDriverOptions, extra_project_config: dict = {} file_logger = HammerVLSIFileLogger(options.log_file) HammerVLSILogging.add_callback(file_logger.callback) self.log = HammerVLSILogging.context() # type: HammerVLSILoggingContext - + # Create a new hammer database. self.database = hammer_config.HammerDatabase() # type: hammer_config.HammerDatabase @@ -152,17 +152,15 @@ def load_technology(self, cache_dir: str = "") -> None: cache_dir = os.path.join(self.obj_dir, "tech-%s-cache" % tech_name) self.log.info("Loading technology '{0}'".format(tech_module)) - tech_opt = hammer_tech.HammerTechnology.load_from_module(tech_module) - if tech_opt is None: - self.log.fatal("Technology {0} not found or missing .tech.[json/yml]!".format(tech_module)) - return - else: - tech: hammer_tech.HammerTechnology = tech_opt - # Update database as soon as possible since e.g. extract_technology_files could use those settings + tech = hammer_tech.HammerTechnology.load_from_module(tech_module) self.database.update_technology(*tech.get_config()) tech.logger = self.log.context("tech") tech.set_database(self.database) tech.cache_dir = cache_dir + tech.gen_config() + if tech.config is None: + self.log.fatal("Technology {0} config not generated or missing .tech.[json/yml]!".format(tech_module)) + return tech.extract_technology_files() tech.get_lib_units()